]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/homectl.c
json: add generic cmdline parser for --json= switch
[thirdparty/systemd.git] / src / home / homectl.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
4aa0a8ac
LP
2
3#include <getopt.h>
4
5#include "sd-bus.h"
6
4aa0a8ac
LP
7#include "ask-password-api.h"
8#include "bus-common-errors.h"
9#include "bus-error.h"
9b71e4ab 10#include "bus-locator.h"
4aa0a8ac
LP
11#include "cgroup-util.h"
12#include "dns-domain.h"
13#include "env-util.h"
14#include "fd-util.h"
15#include "fileio.h"
16#include "format-table.h"
4aa0a8ac 17#include "home-util.h"
1c0c4a43 18#include "homectl-fido2.h"
93295a25 19#include "homectl-pkcs11.h"
80c41552 20#include "homectl-recovery-key.h"
fb2d839c 21#include "libfido2-util.h"
4aa0a8ac
LP
22#include "locale-util.h"
23#include "main-func.h"
24#include "memory-util.h"
4aa0a8ac
LP
25#include "pager.h"
26#include "parse-util.h"
27#include "path-util.h"
28#include "pkcs11-util.h"
29#include "pretty-print.h"
30#include "process-util.h"
31#include "pwquality-util.h"
4aa0a8ac
LP
32#include "rlimit-util.h"
33#include "spawn-polkit-agent.h"
34#include "terminal-util.h"
679badd7 35#include "user-record-pwquality.h"
4aa0a8ac
LP
36#include "user-record-show.h"
37#include "user-record-util.h"
38#include "user-record.h"
39#include "user-util.h"
40#include "verbs.h"
41
42static PagerFlags arg_pager_flags = 0;
43static bool arg_legend = true;
44static bool arg_ask_password = true;
45static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
46static const char *arg_host = NULL;
47static const char *arg_identity = NULL;
48static JsonVariant *arg_identity_extra = NULL;
49static JsonVariant *arg_identity_extra_privileged = NULL;
50static JsonVariant *arg_identity_extra_this_machine = NULL;
51static JsonVariant *arg_identity_extra_rlimits = NULL;
52static char **arg_identity_filter = NULL; /* this one is also applied to 'privileged' and 'thisMachine' subobjects */
53static char **arg_identity_filter_rlimits = NULL;
54static uint64_t arg_disk_size = UINT64_MAX;
55static uint64_t arg_disk_size_relative = UINT64_MAX;
56static char **arg_pkcs11_token_uri = NULL;
1c0c4a43 57static char **arg_fido2_device = NULL;
80c41552 58static bool arg_recovery_key = false;
4aa0a8ac
LP
59static bool arg_json = false;
60static JsonFormatFlags arg_json_format_flags = 0;
61static bool arg_and_resize = false;
62static bool arg_and_change_password = false;
63static enum {
64 EXPORT_FORMAT_FULL, /* export the full record */
65 EXPORT_FORMAT_STRIPPED, /* strip "state" + "binding", but leave signature in place */
66 EXPORT_FORMAT_MINIMAL, /* also strip signature */
67} arg_export_format = EXPORT_FORMAT_FULL;
68
69STATIC_DESTRUCTOR_REGISTER(arg_identity_extra, json_variant_unrefp);
70STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, json_variant_unrefp);
71STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_privileged, json_variant_unrefp);
72STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_rlimits, json_variant_unrefp);
73STATIC_DESTRUCTOR_REGISTER(arg_identity_filter, strv_freep);
74STATIC_DESTRUCTOR_REGISTER(arg_identity_filter_rlimits, strv_freep);
75STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, strv_freep);
1c0c4a43 76STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, strv_freep);
4aa0a8ac 77
cc9886bc
LP
78static const BusLocator *bus_mgr;
79
4aa0a8ac
LP
80static bool identity_properties_specified(void) {
81 return
82 arg_identity ||
83 !json_variant_is_blank_object(arg_identity_extra) ||
84 !json_variant_is_blank_object(arg_identity_extra_privileged) ||
85 !json_variant_is_blank_object(arg_identity_extra_this_machine) ||
86 !json_variant_is_blank_object(arg_identity_extra_rlimits) ||
87 !strv_isempty(arg_identity_filter) ||
88 !strv_isempty(arg_identity_filter_rlimits) ||
1c0c4a43
LP
89 !strv_isempty(arg_pkcs11_token_uri) ||
90 !strv_isempty(arg_fido2_device);
4aa0a8ac
LP
91}
92
93static int acquire_bus(sd_bus **bus) {
94 int r;
95
96 assert(bus);
97
98 if (*bus)
99 return 0;
100
101 r = bus_connect_transport(arg_transport, arg_host, false, bus);
102 if (r < 0)
ab4a88eb 103 return bus_log_connect_error(r);
4aa0a8ac
LP
104
105 (void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
106
107 return 0;
108}
109
110static int list_homes(int argc, char *argv[], void *userdata) {
111 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
112 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
113 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
114 _cleanup_(table_unrefp) Table *table = NULL;
115 int r;
116
117 (void) pager_open(arg_pager_flags);
118
119 r = acquire_bus(&bus);
120 if (r < 0)
121 return r;
122
cc9886bc 123 r = bus_call_method(bus, bus_mgr, "ListHomes", &error, &reply, NULL);
4aa0a8ac
LP
124 if (r < 0)
125 return log_error_errno(r, "Failed to list homes: %s", bus_error_message(&error, r));
126
127 table = table_new("name", "uid", "gid", "state", "realname", "home", "shell");
128 if (!table)
129 return log_oom();
130
131 r = sd_bus_message_enter_container(reply, 'a', "(susussso)");
132 if (r < 0)
133 return bus_log_parse_error(r);
134
135 for (;;) {
136 const char *name, *state, *realname, *home, *shell, *color;
137 TableCell *cell;
138 uint32_t uid, gid;
139
140 r = sd_bus_message_read(reply, "(susussso)", &name, &uid, &state, &gid, &realname, &home, &shell, NULL);
141 if (r < 0)
142 return bus_log_parse_error(r);
143 if (r == 0)
144 break;
145
146 r = table_add_many(table,
147 TABLE_STRING, name,
148 TABLE_UID, uid,
149 TABLE_GID, gid);
150 if (r < 0)
f987a261 151 return table_log_add_error(r);
4aa0a8ac
LP
152
153
154 r = table_add_cell(table, &cell, TABLE_STRING, state);
155 if (r < 0)
f987a261 156 return table_log_add_error(r);
4aa0a8ac
LP
157
158 color = user_record_state_color(state);
159 if (color)
160 (void) table_set_color(table, cell, color);
161
162 r = table_add_many(table,
163 TABLE_STRING, strna(empty_to_null(realname)),
164 TABLE_STRING, home,
165 TABLE_STRING, strna(empty_to_null(shell)));
166 if (r < 0)
f987a261 167 return table_log_add_error(r);
4aa0a8ac
LP
168 }
169
170 r = sd_bus_message_exit_container(reply);
171 if (r < 0)
172 return bus_log_parse_error(r);
173
174 if (table_get_rows(table) > 1 || arg_json) {
175 r = table_set_sort(table, (size_t) 0, (size_t) -1);
176 if (r < 0)
df83eb54 177 return table_log_sort_error(r);
4aa0a8ac
LP
178
179 table_set_header(table, arg_legend);
180
181 if (arg_json)
182 r = table_print_json(table, stdout, arg_json_format_flags);
183 else
184 r = table_print(table, NULL);
185 if (r < 0)
4b6607d9 186 return table_log_print_error(r);
4aa0a8ac
LP
187 }
188
189 if (arg_legend && !arg_json) {
190 if (table_get_rows(table) > 1)
c11428ad 191 printf("\n%zu home areas listed.\n", table_get_rows(table) - 1);
4aa0a8ac 192 else
c11428ad 193 printf("No home areas.\n");
4aa0a8ac
LP
194 }
195
196 return 0;
197}
198
199static int acquire_existing_password(const char *user_name, UserRecord *hr, bool emphasize_current) {
200 _cleanup_(strv_free_erasep) char **password = NULL;
201 _cleanup_free_ char *question = NULL;
202 char *e;
203 int r;
204
205 assert(user_name);
206 assert(hr);
207
208 e = getenv("PASSWORD");
209 if (e) {
210 /* People really shouldn't use environment variables for passing passwords. We support this
211 * only for testing purposes, and do not document the behaviour, so that people won't
212 * actually use this outside of testing. */
213
214 r = user_record_set_password(hr, STRV_MAKE(e), true);
215 if (r < 0)
216 return log_error_errno(r, "Failed to store password: %m");
217
218 string_erase(e);
44ee03d1 219 assert_se(unsetenv("PASSWORD") == 0);
4aa0a8ac
LP
220
221 return 0;
222 }
223
224 if (asprintf(&question, emphasize_current ?
225 "Please enter current password for user %s:" :
226 "Please enter password for user %s:",
227 user_name) < 0)
228 return log_oom();
229
230 r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &password);
231 if (r < 0)
232 return log_error_errno(r, "Failed to acquire password: %m");
233
234 r = user_record_set_password(hr, password, true);
235 if (r < 0)
236 return log_error_errno(r, "Failed to store password: %m");
237
238 return 0;
239}
240
c0bde0d2 241static int acquire_token_pin(const char *user_name, UserRecord *hr) {
4aa0a8ac
LP
242 _cleanup_(strv_free_erasep) char **pin = NULL;
243 _cleanup_free_ char *question = NULL;
244 char *e;
245 int r;
246
247 assert(user_name);
248 assert(hr);
249
250 e = getenv("PIN");
251 if (e) {
c0bde0d2 252 r = user_record_set_token_pin(hr, STRV_MAKE(e), false);
4aa0a8ac 253 if (r < 0)
c0bde0d2 254 return log_error_errno(r, "Failed to store token PIN: %m");
4aa0a8ac
LP
255
256 string_erase(e);
44ee03d1 257 assert_se(unsetenv("PIN") == 0);
4aa0a8ac
LP
258
259 return 0;
260 }
261
262 if (asprintf(&question, "Please enter security token PIN for user %s:", user_name) < 0)
263 return log_oom();
264
265 /* We never cache or use cached PINs, since usually there are only very few attempts allowed before the PIN is blocked */
c0bde0d2 266 r = ask_password_auto(question, "user-home", NULL, "token-pin", USEC_INFINITY, 0, &pin);
4aa0a8ac
LP
267 if (r < 0)
268 return log_error_errno(r, "Failed to acquire security token PIN: %m");
269
c0bde0d2 270 r = user_record_set_token_pin(hr, pin, false);
4aa0a8ac
LP
271 if (r < 0)
272 return log_error_errno(r, "Failed to store security token PIN: %m");
273
274 return 0;
275}
276
277static int handle_generic_user_record_error(
278 const char *user_name,
279 UserRecord *hr,
280 const sd_bus_error *error,
281 int ret,
282 bool emphasize_current_password) {
283 int r;
284
285 assert(user_name);
286 assert(hr);
287
288 if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT))
289 return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
36e4a8f2 290 "Home of user %s is currently absent, please plug in the necessary storage device or backing file system.", user_name);
4aa0a8ac
LP
291
292 else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT))
293 return log_error_errno(SYNTHETIC_ERRNO(ETOOMANYREFS),
294 "Too frequent unsuccessful login attempts for user %s, try again later.", user_name);
295
296 else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD)) {
297
298 if (!strv_isempty(hr->password))
299 log_notice("Password incorrect or not sufficient, please try again.");
300
301 r = acquire_existing_password(user_name, hr, emphasize_current_password);
302 if (r < 0)
303 return r;
304
305 } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) {
306
307 if (strv_isempty(hr->password))
308 log_notice("Security token not inserted, please enter password.");
309 else
310 log_notice("Password incorrect or not sufficient, and configured security token not inserted, please try again.");
311
312 r = acquire_existing_password(user_name, hr, emphasize_current_password);
313 if (r < 0)
314 return r;
315
316 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
317
c0bde0d2 318 r = acquire_token_pin(user_name, hr);
4aa0a8ac
LP
319 if (r < 0)
320 return r;
321
322 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
323
f737186a
LP
324 log_notice("%s%sPlease authenticate physically on security token.",
325 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
326 emoji_enabled() ? " " : "");
4aa0a8ac
LP
327
328 r = user_record_set_pkcs11_protected_authentication_path_permitted(hr, true);
329 if (r < 0)
330 return log_error_errno(r, "Failed to set PKCS#11 protected authentication path permitted flag: %m");
331
7b78db28
LP
332 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
333
334 log_notice("%s%sAuthentication requires presence verification on security token.",
335 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
336 emoji_enabled() ? " " : "");
337
338 r = user_record_set_fido2_user_presence_permitted(hr, true);
339 if (r < 0)
340 return log_error_errno(r, "Failed to set FIDO2 user presence permitted flag: %m");
341
4aa0a8ac 342 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED))
7b78db28 343 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)");
4aa0a8ac
LP
344
345 else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
346
347 log_notice("Security token PIN incorrect, please try again.");
348
c0bde0d2 349 r = acquire_token_pin(user_name, hr);
4aa0a8ac
LP
350 if (r < 0)
351 return r;
352
353 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT)) {
354
355 log_notice("Security token PIN incorrect, please try again (only a few tries left!).");
356
c0bde0d2 357 r = acquire_token_pin(user_name, hr);
4aa0a8ac
LP
358 if (r < 0)
359 return r;
360
361 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT)) {
362
363 log_notice("Security token PIN incorrect, please try again (only one try left!).");
364
c0bde0d2 365 r = acquire_token_pin(user_name, hr);
4aa0a8ac
LP
366 if (r < 0)
367 return r;
368 } else
369 return log_error_errno(ret, "Operation on home %s failed: %s", user_name, bus_error_message(error, ret));
370
371 return 0;
372}
373
374static int activate_home(int argc, char *argv[], void *userdata) {
375 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
376 int r, ret = 0;
377 char **i;
378
379 r = acquire_bus(&bus);
380 if (r < 0)
381 return r;
382
383 STRV_FOREACH(i, strv_skip(argv, 1)) {
384 _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
385
386 secret = user_record_new();
387 if (!secret)
388 return log_oom();
389
390 for (;;) {
391 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
392 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
393
cc9886bc 394 r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHome");
4aa0a8ac
LP
395 if (r < 0)
396 return bus_log_create_error(r);
397
398 r = sd_bus_message_append(m, "s", *i);
399 if (r < 0)
400 return bus_log_create_error(r);
401
402 r = bus_message_append_secret(m, secret);
403 if (r < 0)
404 return bus_log_create_error(r);
405
406 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
407 if (r < 0) {
408 r = handle_generic_user_record_error(*i, secret, &error, r, false);
409 if (r < 0) {
410 if (ret == 0)
411 ret = r;
412
413 break;
414 }
415 } else
416 break;
417 }
418 }
419
420 return ret;
421}
422
423static int deactivate_home(int argc, char *argv[], void *userdata) {
424 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
425 int r, ret = 0;
426 char **i;
427
428 r = acquire_bus(&bus);
429 if (r < 0)
430 return r;
431
432 STRV_FOREACH(i, strv_skip(argv, 1)) {
433 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
434 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
435
cc9886bc 436 r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateHome");
4aa0a8ac
LP
437 if (r < 0)
438 return bus_log_create_error(r);
439
440 r = sd_bus_message_append(m, "s", *i);
441 if (r < 0)
442 return bus_log_create_error(r);
443
444 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
445 if (r < 0) {
446 log_error_errno(r, "Failed to deactivate user home: %s", bus_error_message(&error, r));
447 if (ret == 0)
448 ret = r;
449 }
450 }
451
452 return ret;
453}
454
455static void dump_home_record(UserRecord *hr) {
456 int r;
457
458 assert(hr);
459
460 if (hr->incomplete) {
461 fflush(stdout);
462 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", hr->user_name);
463 }
464
465 if (arg_json) {
466 _cleanup_(user_record_unrefp) UserRecord *stripped = NULL;
467
468 if (arg_export_format == EXPORT_FORMAT_STRIPPED)
469 r = user_record_clone(hr, USER_RECORD_EXTRACT_EMBEDDED, &stripped);
470 else if (arg_export_format == EXPORT_FORMAT_MINIMAL)
471 r = user_record_clone(hr, USER_RECORD_EXTRACT_SIGNABLE, &stripped);
472 else
473 r = 0;
474 if (r < 0)
475 log_warning_errno(r, "Failed to strip user record, ignoring: %m");
476 if (stripped)
477 hr = stripped;
478
479 json_variant_dump(hr->json, arg_json_format_flags, stdout, NULL);
480 } else
481 user_record_show(hr, true);
482}
483
484static char **mangle_user_list(char **list, char ***ret_allocated) {
485 _cleanup_free_ char *myself = NULL;
486 char **l;
487
488 if (!strv_isempty(list)) {
489 *ret_allocated = NULL;
490 return list;
491 }
492
493 myself = getusername_malloc();
494 if (!myself)
495 return NULL;
496
497 l = new(char*, 2);
498 if (!l)
499 return NULL;
500
501 l[0] = TAKE_PTR(myself);
502 l[1] = NULL;
503
504 *ret_allocated = l;
505 return l;
506}
507
508static int inspect_home(int argc, char *argv[], void *userdata) {
509 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
510 _cleanup_(strv_freep) char **mangled_list = NULL;
511 int r, ret = 0;
512 char **items, **i;
513
514 (void) pager_open(arg_pager_flags);
515
516 r = acquire_bus(&bus);
517 if (r < 0)
518 return r;
519
520 items = mangle_user_list(strv_skip(argv, 1), &mangled_list);
521 if (!items)
522 return log_oom();
523
524 STRV_FOREACH(i, items) {
525 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
526 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
527 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
528 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
529 const char *json;
530 int incomplete;
531 uid_t uid;
532
533 r = parse_uid(*i, &uid);
534 if (r < 0) {
7a8867ab 535 if (!valid_user_group_name(*i, 0)) {
4aa0a8ac
LP
536 log_error("Invalid user name '%s'.", *i);
537 if (ret == 0)
538 ret = -EINVAL;
539
540 continue;
541 }
542
cc9886bc 543 r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", *i);
38cd55b0 544 } else
cc9886bc 545 r = bus_call_method(bus, bus_mgr, "GetUserRecordByUID", &error, &reply, "u", (uint32_t) uid);
4aa0a8ac
LP
546
547 if (r < 0) {
548 log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r));
549 if (ret == 0)
550 ret = r;
551
552 continue;
553 }
554
555 r = sd_bus_message_read(reply, "sbo", &json, &incomplete, NULL);
556 if (r < 0) {
557 bus_log_parse_error(r);
558 if (ret == 0)
559 ret = r;
560
561 continue;
562 }
563
564 r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
565 if (r < 0) {
566 log_error_errno(r, "Failed to parse JSON identity: %m");
567 if (ret == 0)
568 ret = r;
569
570 continue;
571 }
572
573 hr = user_record_new();
574 if (!hr)
575 return log_oom();
576
577 r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_LOG);
578 if (r < 0) {
579 if (ret == 0)
580 ret = r;
581
582 continue;
583 }
584
585 hr->incomplete = incomplete;
586 dump_home_record(hr);
587 }
588
589 return ret;
590}
591
592static int authenticate_home(int argc, char *argv[], void *userdata) {
593 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
594 _cleanup_(strv_freep) char **mangled_list = NULL;
595 int r, ret = 0;
596 char **i, **items;
597
598 items = mangle_user_list(strv_skip(argv, 1), &mangled_list);
599 if (!items)
600 return log_oom();
601
602 r = acquire_bus(&bus);
603 if (r < 0)
604 return r;
605
606 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
607
608 STRV_FOREACH(i, items) {
609 _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
610
611 secret = user_record_new();
612 if (!secret)
613 return log_oom();
614
615 for (;;) {
616 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
617 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
618
cc9886bc 619 r = bus_message_new_method_call(bus, &m, bus_mgr, "AuthenticateHome");
4aa0a8ac
LP
620 if (r < 0)
621 return bus_log_create_error(r);
622
623 r = sd_bus_message_append(m, "s", *i);
624 if (r < 0)
625 return bus_log_create_error(r);
626
627 r = bus_message_append_secret(m, secret);
628 if (r < 0)
629 return bus_log_create_error(r);
630
631 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
632 if (r < 0) {
633 r = handle_generic_user_record_error(*i, secret, &error, r, false);
634 if (r < 0) {
635 if (ret == 0)
636 ret = r;
637
638 break;
639 }
640 } else
641 break;
642 }
643 }
644
645 return ret;
646}
647
648static int update_last_change(JsonVariant **v, bool with_password, bool override) {
649 JsonVariant *c;
650 usec_t n;
651 int r;
652
653 assert(v);
654
655 n = now(CLOCK_REALTIME);
656
657 c = json_variant_by_key(*v, "lastChangeUSec");
658 if (c) {
659 uintmax_t u;
660
661 if (!override)
662 goto update_password;
663
664 if (!json_variant_is_unsigned(c))
665 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastChangeUSec field is not an unsigned integer, refusing.");
666
667 u = json_variant_unsigned(c);
668 if (u >= n)
669 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastChangeUSec is from the future, can't update.");
670 }
671
672 r = json_variant_set_field_unsigned(v, "lastChangeUSec", n);
673 if (r < 0)
674 return log_error_errno(r, "Failed to update lastChangeUSec: %m");
675
676update_password:
677 if (!with_password)
678 return 0;
679
680 c = json_variant_by_key(*v, "lastPasswordChangeUSec");
681 if (c) {
682 uintmax_t u;
683
684 if (!override)
685 return 0;
686
687 if (!json_variant_is_unsigned(c))
688 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastPasswordChangeUSec field is not an unsigned integer, refusing.");
689
690 u = json_variant_unsigned(c);
691 if (u >= n)
692 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastPasswordChangeUSec is from the future, can't update.");
693 }
694
695 r = json_variant_set_field_unsigned(v, "lastPasswordChangeUSec", n);
696 if (r < 0)
697 return log_error_errno(r, "Failed to update lastPasswordChangeUSec: %m");
698
699 return 1;
700}
701
702static int apply_identity_changes(JsonVariant **_v) {
703 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
704 int r;
705
706 assert(_v);
707
708 v = json_variant_ref(*_v);
709
710 r = json_variant_filter(&v, arg_identity_filter);
711 if (r < 0)
712 return log_error_errno(r, "Failed to filter identity: %m");
713
714 r = json_variant_merge(&v, arg_identity_extra);
715 if (r < 0)
716 return log_error_errno(r, "Failed to merge identities: %m");
717
718 if (arg_identity_extra_this_machine || !strv_isempty(arg_identity_filter)) {
719 _cleanup_(json_variant_unrefp) JsonVariant *per_machine = NULL, *mmid = NULL;
720 char mids[SD_ID128_STRING_MAX];
721 sd_id128_t mid;
722
723 r = sd_id128_get_machine(&mid);
724 if (r < 0)
725 return log_error_errno(r, "Failed to acquire machine ID: %m");
726
727 r = json_variant_new_string(&mmid, sd_id128_to_string(mid, mids));
728 if (r < 0)
729 return log_error_errno(r, "Failed to allocate matchMachineId object: %m");
730
731 per_machine = json_variant_ref(json_variant_by_key(v, "perMachine"));
732 if (per_machine) {
733 _cleanup_(json_variant_unrefp) JsonVariant *npm = NULL, *add = NULL;
734 _cleanup_free_ JsonVariant **array = NULL;
735 JsonVariant *z;
736 size_t i = 0;
737
738 if (!json_variant_is_array(per_machine))
739 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "perMachine field is not an array, refusing.");
740
741 array = new(JsonVariant*, json_variant_elements(per_machine) + 1);
742 if (!array)
743 return log_oom();
744
745 JSON_VARIANT_ARRAY_FOREACH(z, per_machine) {
746 JsonVariant *u;
747
748 if (!json_variant_is_object(z))
749 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "perMachine entry is not an object, refusing.");
750
751 array[i++] = z;
752
753 u = json_variant_by_key(z, "matchMachineId");
754 if (!u)
755 continue;
756
757 if (!json_variant_equal(u, mmid))
758 continue;
759
760 r = json_variant_merge(&add, z);
761 if (r < 0)
762 return log_error_errno(r, "Failed to merge perMachine entry: %m");
763
764 i--;
765 }
766
767 r = json_variant_filter(&add, arg_identity_filter);
768 if (r < 0)
769 return log_error_errno(r, "Failed to filter perMachine: %m");
770
771 r = json_variant_merge(&add, arg_identity_extra_this_machine);
772 if (r < 0)
773 return log_error_errno(r, "Failed to merge in perMachine fields: %m");
774
775 if (arg_identity_filter_rlimits || arg_identity_extra_rlimits) {
776 _cleanup_(json_variant_unrefp) JsonVariant *rlv = NULL;
777
778 rlv = json_variant_ref(json_variant_by_key(add, "resourceLimits"));
779
780 r = json_variant_filter(&rlv, arg_identity_filter_rlimits);
781 if (r < 0)
782 return log_error_errno(r, "Failed to filter resource limits: %m");
783
784 r = json_variant_merge(&rlv, arg_identity_extra_rlimits);
785 if (r < 0)
786 return log_error_errno(r, "Failed to set resource limits: %m");
787
788 if (json_variant_is_blank_object(rlv)) {
789 r = json_variant_filter(&add, STRV_MAKE("resourceLimits"));
790 if (r < 0)
791 return log_error_errno(r, "Failed to drop resource limits field from identity: %m");
792 } else {
793 r = json_variant_set_field(&add, "resourceLimits", rlv);
794 if (r < 0)
795 return log_error_errno(r, "Failed to update resource limits of identity: %m");
796 }
797 }
798
799 if (!json_variant_is_blank_object(add)) {
800 r = json_variant_set_field(&add, "matchMachineId", mmid);
801 if (r < 0)
802 return log_error_errno(r, "Failed to set matchMachineId field: %m");
803
804 array[i++] = add;
805 }
806
807 r = json_variant_new_array(&npm, array, i);
808 if (r < 0)
809 return log_error_errno(r, "Failed to allocate new perMachine array: %m");
810
811 json_variant_unref(per_machine);
812 per_machine = TAKE_PTR(npm);
813 } else {
814 _cleanup_(json_variant_unrefp) JsonVariant *item = json_variant_ref(arg_identity_extra_this_machine);
815
816 if (arg_identity_extra_rlimits) {
817 r = json_variant_set_field(&item, "resourceLimits", arg_identity_extra_rlimits);
818 if (r < 0)
819 return log_error_errno(r, "Failed to update resource limits of identity: %m");
820 }
821
822 r = json_variant_set_field(&item, "matchMachineId", mmid);
823 if (r < 0)
824 return log_error_errno(r, "Failed to set matchMachineId field: %m");
825
826 r = json_variant_append_array(&per_machine, item);
827 if (r < 0)
828 return log_error_errno(r, "Failed to append to perMachine array: %m");
829 }
830
831 r = json_variant_set_field(&v, "perMachine", per_machine);
832 if (r < 0)
833 return log_error_errno(r, "Failed to update per machine record: %m");
834 }
835
836 if (arg_identity_extra_privileged || arg_identity_filter) {
837 _cleanup_(json_variant_unrefp) JsonVariant *privileged = NULL;
838
839 privileged = json_variant_ref(json_variant_by_key(v, "privileged"));
840
841 r = json_variant_filter(&privileged, arg_identity_filter);
842 if (r < 0)
843 return log_error_errno(r, "Failed to filter identity (privileged part): %m");
844
845 r = json_variant_merge(&privileged, arg_identity_extra_privileged);
846 if (r < 0)
847 return log_error_errno(r, "Failed to merge identities (privileged part): %m");
848
849 if (json_variant_is_blank_object(privileged)) {
850 r = json_variant_filter(&v, STRV_MAKE("privileged"));
851 if (r < 0)
852 return log_error_errno(r, "Failed to drop privileged part from identity: %m");
853 } else {
854 r = json_variant_set_field(&v, "privileged", privileged);
855 if (r < 0)
856 return log_error_errno(r, "Failed to update privileged part of identity: %m");
857 }
858 }
859
860 if (arg_identity_filter_rlimits) {
861 _cleanup_(json_variant_unrefp) JsonVariant *rlv = NULL;
862
863 rlv = json_variant_ref(json_variant_by_key(v, "resourceLimits"));
864
865 r = json_variant_filter(&rlv, arg_identity_filter_rlimits);
866 if (r < 0)
867 return log_error_errno(r, "Failed to filter resource limits: %m");
868
869 /* Note that we only filter resource limits here, but don't apply them. We do that in the perMachine section */
870
871 if (json_variant_is_blank_object(rlv)) {
872 r = json_variant_filter(&v, STRV_MAKE("resourceLimits"));
873 if (r < 0)
874 return log_error_errno(r, "Failed to drop resource limits field from identity: %m");
875 } else {
876 r = json_variant_set_field(&v, "resourceLimits", rlv);
877 if (r < 0)
878 return log_error_errno(r, "Failed to update resource limits of identity: %m");
879 }
880 }
881
882 json_variant_unref(*_v);
883 *_v = TAKE_PTR(v);
884
885 return 0;
886}
887
888static int add_disposition(JsonVariant **v) {
889 int r;
890
891 assert(v);
892
893 if (json_variant_by_key(*v, "disposition"))
894 return 0;
895
896 /* Set the disposition to regular, if not configured explicitly */
897 r = json_variant_set_field_string(v, "disposition", "regular");
898 if (r < 0)
899 return log_error_errno(r, "Failed to set disposition field: %m");
900
901 return 1;
902}
903
4aa0a8ac
LP
904static int acquire_new_home_record(UserRecord **ret) {
905 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
906 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
907 char **i;
908 int r;
909
910 assert(ret);
911
912 if (arg_identity) {
913 unsigned line, column;
914
915 r = json_parse_file(
916 streq(arg_identity, "-") ? stdin : NULL,
917 streq(arg_identity, "-") ? "<stdin>" : arg_identity, JSON_PARSE_SENSITIVE, &v, &line, &column);
918 if (r < 0)
919 return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column);
920 }
921
922 r = apply_identity_changes(&v);
923 if (r < 0)
924 return r;
925
926 r = add_disposition(&v);
927 if (r < 0)
928 return r;
929
930 STRV_FOREACH(i, arg_pkcs11_token_uri) {
93295a25 931 r = identity_add_pkcs11_key_data(&v, *i);
4aa0a8ac
LP
932 if (r < 0)
933 return r;
934 }
935
1c0c4a43
LP
936 STRV_FOREACH(i, arg_fido2_device) {
937 r = identity_add_fido2_parameters(&v, *i);
938 if (r < 0)
939 return r;
940 }
941
80c41552
LP
942 if (arg_recovery_key) {
943 r = identity_add_recovery_key(&v);
944 if (r < 0)
945 return r;
946 }
947
4aa0a8ac
LP
948 r = update_last_change(&v, true, false);
949 if (r < 0)
950 return r;
951
952 if (DEBUG_LOGGING)
953 json_variant_dump(v, JSON_FORMAT_PRETTY, NULL, NULL);
954
955 hr = user_record_new();
956 if (!hr)
957 return log_oom();
958
959 r = user_record_load(hr, v, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_LOG);
960 if (r < 0)
961 return r;
962
963 *ret = TAKE_PTR(hr);
964 return 0;
965}
966
967static int acquire_new_password(
968 const char *user_name,
969 UserRecord *hr,
80c41552
LP
970 bool suggest,
971 char **ret) {
4aa0a8ac
LP
972
973 unsigned i = 5;
974 char *e;
975 int r;
976
977 assert(user_name);
978 assert(hr);
979
980 e = getenv("NEWPASSWORD");
981 if (e) {
80c41552
LP
982 _cleanup_(erase_and_freep) char *copy = NULL;
983
4aa0a8ac
LP
984 /* As above, this is not for use, just for testing */
985
80c41552
LP
986 if (ret) {
987 copy = strdup(e);
988 if (!copy)
989 return log_oom();
990 }
991
992 r = user_record_set_password(hr, STRV_MAKE(e), /* prepend = */ true);
4aa0a8ac
LP
993 if (r < 0)
994 return log_error_errno(r, "Failed to store password: %m");
995
996 string_erase(e);
44ee03d1 997 assert_se(unsetenv("NEWPASSWORD") == 0);
4aa0a8ac 998
80c41552
LP
999 if (ret)
1000 *ret = TAKE_PTR(copy);
1001
4aa0a8ac
LP
1002 return 0;
1003 }
1004
1005 if (suggest)
1006 (void) suggest_passwords();
1007
1008 for (;;) {
1009 _cleanup_(strv_free_erasep) char **first = NULL, **second = NULL;
1010 _cleanup_free_ char *question = NULL;
1011
1012 if (--i == 0)
1013 return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up:");
1014
1015 if (asprintf(&question, "Please enter new password for user %s:", user_name) < 0)
1016 return log_oom();
1017
1018 r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, 0, &first);
1019 if (r < 0)
1020 return log_error_errno(r, "Failed to acquire password: %m");
1021
1022 question = mfree(question);
1023 if (asprintf(&question, "Please enter new password for user %s (repeat):", user_name) < 0)
1024 return log_oom();
1025
1026 r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, 0, &second);
1027 if (r < 0)
1028 return log_error_errno(r, "Failed to acquire password: %m");
1029
1030 if (strv_equal(first, second)) {
80c41552
LP
1031 _cleanup_(erase_and_freep) char *copy = NULL;
1032
1033 if (ret) {
1034 copy = strdup(first[0]);
1035 if (!copy)
1036 return log_oom();
1037 }
1038
1039 r = user_record_set_password(hr, first, /* prepend = */ true);
4aa0a8ac
LP
1040 if (r < 0)
1041 return log_error_errno(r, "Failed to store password: %m");
1042
80c41552
LP
1043 if (ret)
1044 *ret = TAKE_PTR(copy);
1045
4aa0a8ac
LP
1046 return 0;
1047 }
1048
80ace4f2 1049 log_error("Password didn't match, try again.");
4aa0a8ac
LP
1050 }
1051}
1052
1053static int create_home(int argc, char *argv[], void *userdata) {
1054 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1055 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
4aa0a8ac
LP
1056 int r;
1057
1058 r = acquire_bus(&bus);
1059 if (r < 0)
1060 return r;
1061
1062 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1063
1064 if (argc >= 2) {
1065 /* If a username was specified, use it */
1066
7a8867ab 1067 if (valid_user_group_name(argv[1], 0))
4aa0a8ac
LP
1068 r = json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]);
1069 else {
1070 _cleanup_free_ char *un = NULL, *rr = NULL;
1071
1072 /* Before we consider the user name invalid, let's check if we can split it? */
1073 r = split_user_name_realm(argv[1], &un, &rr);
1074 if (r < 0)
1075 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name '%s' is not valid: %m", argv[1]);
1076
1077 if (rr) {
1078 r = json_variant_set_field_string(&arg_identity_extra, "realm", rr);
1079 if (r < 0)
1080 return log_error_errno(r, "Failed to set realm field: %m");
1081 }
1082
1083 r = json_variant_set_field_string(&arg_identity_extra, "userName", un);
1084 }
1085 if (r < 0)
1086 return log_error_errno(r, "Failed to set userName field: %m");
1087 } else {
1088 /* If neither a username nor an identity have been specified we cannot operate. */
1089 if (!arg_identity)
1090 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name required.");
1091 }
1092
1093 r = acquire_new_home_record(&hr);
1094 if (r < 0)
1095 return r;
1096
80c41552
LP
1097 /* If the JSON record carries no plain text password (besides the recovery key), then let's query it
1098 * manually. */
1099 if (strv_length(hr->password) <= arg_recovery_key) {
4aa0a8ac
LP
1100
1101 if (strv_isempty(hr->hashed_password)) {
80c41552
LP
1102 _cleanup_(erase_and_freep) char *new_password = NULL;
1103
4aa0a8ac 1104 /* No regular (i.e. non-PKCS#11) hashed passwords set in the record, let's fix that. */
80c41552 1105 r = acquire_new_password(hr->user_name, hr, /* suggest = */ true, &new_password);
4aa0a8ac
LP
1106 if (r < 0)
1107 return r;
1108
80c41552 1109 r = user_record_make_hashed_password(hr, STRV_MAKE(new_password), /* extend = */ false);
4aa0a8ac
LP
1110 if (r < 0)
1111 return log_error_errno(r, "Failed to hash password: %m");
1112 } else {
1113 /* There's a hash password set in the record, acquire the unhashed version of it. */
80c41552 1114 r = acquire_existing_password(hr->user_name, hr, /* emphasize_current= */ false);
4aa0a8ac
LP
1115 if (r < 0)
1116 return r;
1117 }
1118 }
1119
1120 if (hr->enforce_password_policy == 0) {
1121 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1122
1123 /* If password quality enforcement is disabled, let's at least warn client side */
1124
679badd7 1125 r = user_record_quality_check_password(hr, hr, &error);
4aa0a8ac
LP
1126 if (r < 0)
1127 log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", bus_error_message(&error, r));
1128 }
1129
1130 for (;;) {
1131 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1132 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1133 _cleanup_(erase_and_freep) char *formatted = NULL;
1134
1135 r = json_variant_format(hr->json, 0, &formatted);
1136 if (r < 0)
7b8d55b7 1137 return log_error_errno(r, "Failed to format user record: %m");
4aa0a8ac 1138
cc9886bc 1139 r = bus_message_new_method_call(bus, &m, bus_mgr, "CreateHome");
4aa0a8ac
LP
1140 if (r < 0)
1141 return bus_log_create_error(r);
1142
2ffee2c9
LP
1143 (void) sd_bus_message_sensitive(m);
1144
4aa0a8ac
LP
1145 r = sd_bus_message_append(m, "s", formatted);
1146 if (r < 0)
1147 return bus_log_create_error(r);
1148
1149 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1150 if (r < 0) {
8e62dfb1 1151 if (sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY)) {
80c41552
LP
1152 _cleanup_(erase_and_freep) char *new_password = NULL;
1153
8e62dfb1
LP
1154 log_error_errno(r, "%s", bus_error_message(&error, r));
1155 log_info("(Use --enforce-password-policy=no to turn off password quality checks for this account.)");
4aa0a8ac 1156
80c41552 1157 r = acquire_new_password(hr->user_name, hr, /* suggest = */ false, &new_password);
8e62dfb1
LP
1158 if (r < 0)
1159 return r;
4aa0a8ac 1160
80c41552 1161 r = user_record_make_hashed_password(hr, STRV_MAKE(new_password), /* extend = */ false);
8e62dfb1
LP
1162 if (r < 0)
1163 return log_error_errno(r, "Failed to hash passwords: %m");
1164 } else {
1165 r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
1166 if (r < 0)
1167 return r;
1168 }
1169 } else
1170 break; /* done */
4aa0a8ac
LP
1171 }
1172
1173 return 0;
1174}
1175
1176static int remove_home(int argc, char *argv[], void *userdata) {
1177 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1178 int r, ret = 0;
1179 char **i;
1180
1181 r = acquire_bus(&bus);
1182 if (r < 0)
1183 return r;
1184
1185 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1186
1187 STRV_FOREACH(i, strv_skip(argv, 1)) {
1188 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1189 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1190
cc9886bc 1191 r = bus_message_new_method_call(bus, &m, bus_mgr, "RemoveHome");
4aa0a8ac
LP
1192 if (r < 0)
1193 return bus_log_create_error(r);
1194
1195 r = sd_bus_message_append(m, "s", *i);
1196 if (r < 0)
1197 return bus_log_create_error(r);
1198
1199 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1200 if (r < 0) {
1201 log_error_errno(r, "Failed to remove home: %s", bus_error_message(&error, r));
1202 if (ret == 0)
1203 ret = r;
1204 }
1205 }
1206
1207 return ret;
1208}
1209
1210static int acquire_updated_home_record(
1211 sd_bus *bus,
1212 const char *username,
1213 UserRecord **ret) {
1214
1215 _cleanup_(json_variant_unrefp) JsonVariant *json = NULL;
1216 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
1217 char **i;
1218 int r;
1219
1220 assert(ret);
1221
1222 if (arg_identity) {
1223 unsigned line, column;
1224 JsonVariant *un;
1225
1226 r = json_parse_file(
1227 streq(arg_identity, "-") ? stdin : NULL,
1228 streq(arg_identity, "-") ? "<stdin>" : arg_identity, JSON_PARSE_SENSITIVE, &json, &line, &column);
1229 if (r < 0)
1230 return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column);
1231
1232 un = json_variant_by_key(json, "userName");
1233 if (un) {
1234 if (!json_variant_is_string(un) || (username && !streq(json_variant_string(un), username)))
1235 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name specified on command line and in JSON record do not match.");
1236 } else {
1237 if (!username)
1238 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No username specified.");
1239
1240 r = json_variant_set_field_string(&arg_identity_extra, "userName", username);
1241 if (r < 0)
1242 return log_error_errno(r, "Failed to set userName field: %m");
1243 }
1244
1245 } else {
1246 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1247 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1248 int incomplete;
1249 const char *text;
1250
1251 if (!identity_properties_specified())
1252 return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "No field to change specified.");
1253
cc9886bc 1254 r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", username);
4aa0a8ac
LP
1255 if (r < 0)
1256 return log_error_errno(r, "Failed to acquire user home record: %s", bus_error_message(&error, r));
1257
1258 r = sd_bus_message_read(reply, "sbo", &text, &incomplete, NULL);
1259 if (r < 0)
1260 return bus_log_parse_error(r);
1261
1262 if (incomplete)
1263 return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Lacking rights to acquire user record including privileged metadata, can't update record.");
1264
1265 r = json_parse(text, JSON_PARSE_SENSITIVE, &json, NULL, NULL);
1266 if (r < 0)
1267 return log_error_errno(r, "Failed to parse JSON identity: %m");
1268
1269 reply = sd_bus_message_unref(reply);
1270
1271 r = json_variant_filter(&json, STRV_MAKE("binding", "status", "signature"));
1272 if (r < 0)
1273 return log_error_errno(r, "Failed to strip binding and status from record to update: %m");
1274 }
1275
1276 r = apply_identity_changes(&json);
1277 if (r < 0)
1278 return r;
1279
1280 STRV_FOREACH(i, arg_pkcs11_token_uri) {
93295a25 1281 r = identity_add_pkcs11_key_data(&json, *i);
4aa0a8ac
LP
1282 if (r < 0)
1283 return r;
1284 }
1285
1c0c4a43
LP
1286 STRV_FOREACH(i, arg_fido2_device) {
1287 r = identity_add_fido2_parameters(&json, *i);
1288 if (r < 0)
1289 return r;
1290 }
1291
4aa0a8ac
LP
1292 /* If the user supplied a full record, then add in lastChange, but do not override. Otherwise always
1293 * override. */
1c0c4a43 1294 r = update_last_change(&json, arg_pkcs11_token_uri || arg_fido2_device, !arg_identity);
4aa0a8ac
LP
1295 if (r < 0)
1296 return r;
1297
1298 if (DEBUG_LOGGING)
1299 json_variant_dump(json, JSON_FORMAT_PRETTY, NULL, NULL);
1300
1301 hr = user_record_new();
1302 if (!hr)
1303 return log_oom();
1304
1305 r = user_record_load(hr, json, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_LOG);
1306 if (r < 0)
1307 return r;
1308
1309 *ret = TAKE_PTR(hr);
1310 return 0;
1311}
1312
c98811d8
LP
1313static int home_record_reset_human_interaction_permission(UserRecord *hr) {
1314 int r;
1315
1316 assert(hr);
1317
1318 /* When we execute multiple operations one after the other, let's reset the permission to ask the
1319 * user each time, so that if interaction is necessary we will be told so again and thus can print a
1320 * nice message to the user, telling the user so. */
1321
1322 r = user_record_set_pkcs11_protected_authentication_path_permitted(hr, -1);
1323 if (r < 0)
1324 return log_error_errno(r, "Failed to reset PKCS#11 protected authentication path permission flag: %m");
1325
1326 r = user_record_set_fido2_user_presence_permitted(hr, -1);
1327 if (r < 0)
1328 return log_error_errno(r, "Failed to reset FIDO2 user presence permission flag: %m");
1329
1330 return 0;
1331}
1332
4aa0a8ac
LP
1333static int update_home(int argc, char *argv[], void *userdata) {
1334 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1335 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
1336 _cleanup_free_ char *buffer = NULL;
1337 const char *username;
1338 int r;
1339
1340 if (argc >= 2)
1341 username = argv[1];
1342 else if (!arg_identity) {
1343 buffer = getusername_malloc();
1344 if (!buffer)
1345 return log_oom();
1346
1347 username = buffer;
1348 } else
1349 username = NULL;
1350
1351 r = acquire_bus(&bus);
1352 if (r < 0)
1353 return r;
1354
1355 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1356
1357 r = acquire_updated_home_record(bus, username, &hr);
1358 if (r < 0)
1359 return r;
1360
c98811d8
LP
1361 /* If we do multiple operations, let's output things more verbosely, since otherwise the repeated
1362 * authentication might be confusing. */
1363
1364 if (arg_and_resize || arg_and_change_password)
1365 log_info("Updating home directory.");
1366
4aa0a8ac
LP
1367 for (;;) {
1368 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1369 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1370 _cleanup_free_ char *formatted = NULL;
1371
cc9886bc 1372 r = bus_message_new_method_call(bus, &m, bus_mgr, "UpdateHome");
4aa0a8ac
LP
1373 if (r < 0)
1374 return bus_log_create_error(r);
1375
1376 r = json_variant_format(hr->json, 0, &formatted);
1377 if (r < 0)
7b8d55b7 1378 return log_error_errno(r, "Failed to format user record: %m");
4aa0a8ac 1379
2ffee2c9
LP
1380 (void) sd_bus_message_sensitive(m);
1381
4aa0a8ac
LP
1382 r = sd_bus_message_append(m, "s", formatted);
1383 if (r < 0)
1384 return bus_log_create_error(r);
1385
1386 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1387 if (r < 0) {
1388 if (arg_and_change_password &&
1389 sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
1390 /* In the generic handler we'd ask for a password in this case, but when
1391 * changing passwords that's not sufficient, as we need to acquire all keys
1392 * first. */
1393 return log_error_errno(r, "Security token not inserted, refusing.");
1394
1395 r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
1396 if (r < 0)
1397 return r;
1398 } else
1399 break;
1400 }
1401
c98811d8
LP
1402 if (arg_and_resize)
1403 log_info("Resizing home.");
1404
1405 (void) home_record_reset_human_interaction_permission(hr);
1406
4aa0a8ac
LP
1407 /* Also sync down disk size to underlying LUKS/fscrypt/quota */
1408 while (arg_and_resize) {
1409 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1410 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1411
cc9886bc 1412 r = bus_message_new_method_call(bus, &m, bus_mgr, "ResizeHome");
4aa0a8ac
LP
1413 if (r < 0)
1414 return bus_log_create_error(r);
1415
1416 /* Specify UINT64_MAX as size, in which case the underlying disk size will just be synced */
1417 r = sd_bus_message_append(m, "st", hr->user_name, UINT64_MAX);
1418 if (r < 0)
1419 return bus_log_create_error(r);
1420
1421 r = bus_message_append_secret(m, hr);
1422 if (r < 0)
1423 return bus_log_create_error(r);
1424
1425 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1426 if (r < 0) {
1427 if (arg_and_change_password &&
1428 sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
1429 return log_error_errno(r, "Security token not inserted, refusing.");
1430
1431 r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
1432 if (r < 0)
1433 return r;
1434 } else
1435 break;
1436 }
1437
c98811d8
LP
1438 if (arg_and_change_password)
1439 log_info("Synchronizing passwords and encryption keys.");
1440
1441 (void) home_record_reset_human_interaction_permission(hr);
1442
4aa0a8ac
LP
1443 /* Also sync down passwords to underlying LUKS/fscrypt */
1444 while (arg_and_change_password) {
1445 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1446 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1447
cc9886bc 1448 r = bus_message_new_method_call(bus, &m, bus_mgr, "ChangePasswordHome");
4aa0a8ac
LP
1449 if (r < 0)
1450 return bus_log_create_error(r);
1451
1452 /* Specify an empty new secret, in which case the underlying LUKS/fscrypt password will just be synced */
1453 r = sd_bus_message_append(m, "ss", hr->user_name, "{}");
1454 if (r < 0)
1455 return bus_log_create_error(r);
1456
1457 r = bus_message_append_secret(m, hr);
1458 if (r < 0)
1459 return bus_log_create_error(r);
1460
1461 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1462 if (r < 0) {
1463 if (sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
1464 return log_error_errno(r, "Security token not inserted, refusing.");
1465
1466 r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
1467 if (r < 0)
1468 return r;
1469 } else
1470 break;
1471 }
1472
1473 return 0;
1474}
1475
1476static int passwd_home(int argc, char *argv[], void *userdata) {
1477 _cleanup_(user_record_unrefp) UserRecord *old_secret = NULL, *new_secret = NULL;
1478 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1479 _cleanup_free_ char *buffer = NULL;
1480 const char *username;
1481 int r;
1482
1483 if (arg_pkcs11_token_uri)
1484 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the PKCS#11 security token use 'homectl update --pkcs11-token-uri=…'.");
1c0c4a43
LP
1485 if (arg_fido2_device)
1486 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the FIDO2 security token use 'homectl update --fido2-device=…'.");
4aa0a8ac
LP
1487 if (identity_properties_specified())
1488 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The 'passwd' verb does not permit changing other record properties at the same time.");
1489
1490 if (argc >= 2)
1491 username = argv[1];
1492 else {
1493 buffer = getusername_malloc();
1494 if (!buffer)
1495 return log_oom();
1496
1497 username = buffer;
1498 }
1499
1500 r = acquire_bus(&bus);
1501 if (r < 0)
1502 return r;
1503
1504 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1505
1506 old_secret = user_record_new();
1507 if (!old_secret)
1508 return log_oom();
1509
1510 new_secret = user_record_new();
1511 if (!new_secret)
1512 return log_oom();
1513
80c41552 1514 r = acquire_new_password(username, new_secret, /* suggest = */ true, NULL);
4aa0a8ac
LP
1515 if (r < 0)
1516 return r;
1517
1518 for (;;) {
1519 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1520 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1521
cc9886bc 1522 r = bus_message_new_method_call(bus, &m, bus_mgr, "ChangePasswordHome");
4aa0a8ac
LP
1523 if (r < 0)
1524 return bus_log_create_error(r);
1525
1526 r = sd_bus_message_append(m, "s", username);
1527 if (r < 0)
1528 return bus_log_create_error(r);
1529
1530 r = bus_message_append_secret(m, new_secret);
1531 if (r < 0)
1532 return bus_log_create_error(r);
1533
1534 r = bus_message_append_secret(m, old_secret);
1535 if (r < 0)
1536 return bus_log_create_error(r);
1537
1538 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1539 if (r < 0) {
1540 if (sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY)) {
1541
1542 log_error_errno(r, "%s", bus_error_message(&error, r));
1543
80c41552 1544 r = acquire_new_password(username, new_secret, /* suggest = */ false, NULL);
4aa0a8ac
LP
1545
1546 } else if (sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
1547
1548 /* In the generic handler we'd ask for a password in this case, but when
1549 * changing passwords that's not sufficeint, as we need to acquire all keys
1550 * first. */
1551 return log_error_errno(r, "Security token not inserted, refusing.");
1552 else
1553 r = handle_generic_user_record_error(username, old_secret, &error, r, true);
1554 if (r < 0)
1555 return r;
1556 } else
1557 break;
1558 }
1559
1560 return 0;
1561}
1562
1563static int resize_home(int argc, char *argv[], void *userdata) {
1564 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1565 _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
1566 uint64_t ds = UINT64_MAX;
1567 int r;
1568
1569 r = acquire_bus(&bus);
1570 if (r < 0)
1571 return r;
1572
1573 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1574
1575 if (arg_disk_size_relative != UINT64_MAX ||
1576 (argc > 2 && parse_percent(argv[2]) >= 0))
1577 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
1578 "Relative disk size specification currently not supported when resizing.");
1579
1580 if (argc > 2) {
1581 r = parse_size(argv[2], 1024, &ds);
1582 if (r < 0)
1583 return log_error_errno(r, "Failed to parse disk size parameter: %s", argv[2]);
1584 }
1585
1586 if (arg_disk_size != UINT64_MAX) {
1587 if (ds != UINT64_MAX && ds != arg_disk_size)
1588 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Disk size specified twice and doesn't match, refusing.");
1589
1590 ds = arg_disk_size;
1591 }
1592
1593 secret = user_record_new();
1594 if (!secret)
1595 return log_oom();
1596
1597 for (;;) {
1598 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1599 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1600
cc9886bc 1601 r = bus_message_new_method_call(bus, &m, bus_mgr, "ResizeHome");
4aa0a8ac
LP
1602 if (r < 0)
1603 return bus_log_create_error(r);
1604
1605 r = sd_bus_message_append(m, "st", argv[1], ds);
1606 if (r < 0)
1607 return bus_log_create_error(r);
1608
1609 r = bus_message_append_secret(m, secret);
1610 if (r < 0)
1611 return bus_log_create_error(r);
1612
1613 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1614 if (r < 0) {
1615 r = handle_generic_user_record_error(argv[1], secret, &error, r, false);
1616 if (r < 0)
1617 return r;
1618 } else
1619 break;
1620 }
1621
1622 return 0;
1623}
1624
1625static int lock_home(int argc, char *argv[], void *userdata) {
1626 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1627 int r, ret = 0;
1628 char **i;
1629
1630 r = acquire_bus(&bus);
1631 if (r < 0)
1632 return r;
1633
1634 STRV_FOREACH(i, strv_skip(argv, 1)) {
1635 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1636 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1637
cc9886bc 1638 r = bus_message_new_method_call(bus, &m, bus_mgr, "LockHome");
4aa0a8ac
LP
1639 if (r < 0)
1640 return bus_log_create_error(r);
1641
1642 r = sd_bus_message_append(m, "s", *i);
1643 if (r < 0)
1644 return bus_log_create_error(r);
1645
1646 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1647 if (r < 0) {
1648 log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r));
1649 if (ret == 0)
1650 ret = r;
1651 }
1652 }
1653
1654 return ret;
1655}
1656
1657static int unlock_home(int argc, char *argv[], void *userdata) {
1658 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1659 int r, ret = 0;
1660 char **i;
1661
1662 r = acquire_bus(&bus);
1663 if (r < 0)
1664 return r;
1665
1666 STRV_FOREACH(i, strv_skip(argv, 1)) {
1667 _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
1668
1669 secret = user_record_new();
1670 if (!secret)
1671 return log_oom();
1672
1673 for (;;) {
1674 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1675 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1676
cc9886bc 1677 r = bus_message_new_method_call(bus, &m, bus_mgr, "UnlockHome");
4aa0a8ac
LP
1678 if (r < 0)
1679 return bus_log_create_error(r);
1680
1681 r = sd_bus_message_append(m, "s", *i);
1682 if (r < 0)
1683 return bus_log_create_error(r);
1684
1685 r = bus_message_append_secret(m, secret);
1686 if (r < 0)
1687 return bus_log_create_error(r);
1688
1689 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1690 if (r < 0) {
1691 r = handle_generic_user_record_error(argv[1], secret, &error, r, false);
1692 if (r < 0) {
1693 if (ret == 0)
1694 ret = r;
1695
1696 break;
1697 }
1698 } else
1699 break;
1700 }
1701 }
1702
1703 return ret;
1704}
1705
1706static int with_home(int argc, char *argv[], void *userdata) {
1707 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1708 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
1709 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1710 _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
1711 _cleanup_close_ int acquired_fd = -1;
1712 _cleanup_strv_free_ char **cmdline = NULL;
1713 const char *home;
1714 int r, ret;
1715 pid_t pid;
1716
1717 r = acquire_bus(&bus);
1718 if (r < 0)
1719 return r;
1720
1721 if (argc < 3) {
1722 _cleanup_free_ char *shell = NULL;
1723
1724 /* If no command is specified, spawn a shell */
1725 r = get_shell(&shell);
1726 if (r < 0)
1727 return log_error_errno(r, "Failed to acquire shell: %m");
1728
1729 cmdline = strv_new(shell);
1730 } else
1731 cmdline = strv_copy(argv + 2);
1732 if (!cmdline)
1733 return log_oom();
1734
1735 secret = user_record_new();
1736 if (!secret)
1737 return log_oom();
1738
1739 for (;;) {
cc9886bc 1740 r = bus_message_new_method_call(bus, &m, bus_mgr, "AcquireHome");
4aa0a8ac
LP
1741 if (r < 0)
1742 return bus_log_create_error(r);
1743
1744 r = sd_bus_message_append(m, "s", argv[1]);
1745 if (r < 0)
1746 return bus_log_create_error(r);
1747
1748 r = bus_message_append_secret(m, secret);
1749 if (r < 0)
1750 return bus_log_create_error(r);
1751
1752 r = sd_bus_message_append(m, "b", /* please_suspend = */ getenv_bool("SYSTEMD_PLEASE_SUSPEND_HOME") > 0);
1753 if (r < 0)
1754 return bus_log_create_error(r);
1755
1756 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
1757 m = sd_bus_message_unref(m);
1758 if (r < 0) {
1759 r = handle_generic_user_record_error(argv[1], secret, &error, r, false);
1760 if (r < 0)
1761 return r;
1762
1763 sd_bus_error_free(&error);
1764 } else {
1765 int fd;
1766
1767 r = sd_bus_message_read(reply, "h", &fd);
1768 if (r < 0)
1769 return bus_log_parse_error(r);
1770
1771 acquired_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
1772 if (acquired_fd < 0)
1773 return log_error_errno(errno, "Failed to duplicate acquired fd: %m");
1774
1775 reply = sd_bus_message_unref(reply);
1776 break;
1777 }
1778 }
1779
cc9886bc 1780 r = bus_call_method(bus, bus_mgr, "GetHomeByName", &error, &reply, "s", argv[1]);
4aa0a8ac
LP
1781 if (r < 0)
1782 return log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r));
1783
1784 r = sd_bus_message_read(reply, "usussso", NULL, NULL, NULL, NULL, &home, NULL, NULL);
1785 if (r < 0)
1786 return bus_log_parse_error(r);
1787
1788 r = safe_fork("(with)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REOPEN_LOG, &pid);
1789 if (r < 0)
1790 return r;
1791 if (r == 0) {
1792 if (chdir(home) < 0) {
1793 log_error_errno(errno, "Failed to change to directory %s: %m", home);
1794 _exit(255);
1795 }
1796
1797 execvp(cmdline[0], cmdline);
1798 log_error_errno(errno, "Failed to execute %s: %m", cmdline[0]);
1799 _exit(255);
1800 }
1801
1802 ret = wait_for_terminate_and_check(cmdline[0], pid, WAIT_LOG_ABNORMAL);
1803
1804 /* Close the fd that pings the home now. */
1805 acquired_fd = safe_close(acquired_fd);
1806
cc9886bc 1807 r = bus_message_new_method_call(bus, &m, bus_mgr, "ReleaseHome");
4aa0a8ac
LP
1808 if (r < 0)
1809 return bus_log_create_error(r);
1810
1811 r = sd_bus_message_append(m, "s", argv[1]);
1812 if (r < 0)
1813 return bus_log_create_error(r);
1814
1815 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1816 if (r < 0) {
1817 if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_BUSY))
1818 log_notice("Not deactivating home directory of %s, as it is still used.", argv[1]);
1819 else
1820 return log_error_errno(r, "Failed to release user home: %s", bus_error_message(&error, r));
1821 }
1822
1823 return ret;
1824}
1825
1826static int lock_all_homes(int argc, char *argv[], void *userdata) {
1827 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1828 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1829 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1830 int r;
1831
1832 r = acquire_bus(&bus);
1833 if (r < 0)
1834 return r;
1835
cc9886bc 1836 r = bus_message_new_method_call(bus, &m, bus_mgr, "LockAllHomes");
4aa0a8ac
LP
1837 if (r < 0)
1838 return bus_log_create_error(r);
1839
1840 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1841 if (r < 0)
d1f6e01e
LP
1842 return log_error_errno(r, "Failed to lock all homes: %s", bus_error_message(&error, r));
1843
1844 return 0;
1845}
1846
1847static int deactivate_all_homes(int argc, char *argv[], void *userdata) {
1848 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1849 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1850 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1851 int r;
1852
1853 r = acquire_bus(&bus);
1854 if (r < 0)
1855 return r;
1856
1857 r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateAllHomes");
1858 if (r < 0)
1859 return bus_log_create_error(r);
1860
1861 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1862 if (r < 0)
1863 return log_error_errno(r, "Failed to deactivate all homes: %s", bus_error_message(&error, r));
4aa0a8ac
LP
1864
1865 return 0;
1866}
1867
1868static int drop_from_identity(const char *field) {
1869 int r;
1870
1871 assert(field);
1872
1873 /* If we are called to update an identity record and drop some field, let's keep track of what to
1874 * remove from the old record */
1875 r = strv_extend(&arg_identity_filter, field);
1876 if (r < 0)
1877 return log_oom();
1878
1879 /* Let's also drop the field if it was previously set to a new value on the same command line */
1880 r = json_variant_filter(&arg_identity_extra, STRV_MAKE(field));
1881 if (r < 0)
1882 return log_error_errno(r, "Failed to filter JSON identity data: %m");
1883
1884 r = json_variant_filter(&arg_identity_extra_this_machine, STRV_MAKE(field));
1885 if (r < 0)
1886 return log_error_errno(r, "Failed to filter JSON identity data: %m");
1887
1888 r = json_variant_filter(&arg_identity_extra_privileged, STRV_MAKE(field));
1889 if (r < 0)
1890 return log_error_errno(r, "Failed to filter JSON identity data: %m");
1891
1892 return 0;
1893}
1894
1895static int help(int argc, char *argv[], void *userdata) {
1896 _cleanup_free_ char *link = NULL;
1897 int r;
1898
1899 (void) pager_open(arg_pager_flags);
1900
1901 r = terminal_urlify_man("homectl", "1", &link);
1902 if (r < 0)
1903 return log_oom();
1904
1905 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
1906 "%2$sCreate, manipulate or inspect home directories.%3$s\n"
1907 "\n%4$sCommands:%5$s\n"
c11428ad
ZJS
1908 " list List home areas\n"
1909 " activate USER… Activate a home area\n"
1910 " deactivate USER… Deactivate a home area\n"
1911 " inspect USER… Inspect a home area\n"
1912 " authenticate USER… Authenticate a home area\n"
18143cd7
ZJS
1913 " create USER Create a home area\n"
1914 " remove USER… Remove a home area\n"
1915 " update USER Update a home area\n"
1916 " passwd USER Change password of a home area\n"
1917 " resize USER SIZE Resize a home area\n"
c11428ad
ZJS
1918 " lock USER… Temporarily lock an active home area\n"
1919 " unlock USER… Unlock a temporarily locked home area\n"
1920 " lock-all Lock all suitable home areas\n"
d1f6e01e 1921 " deactivate-all Deactivate all active home areas\n"
c11428ad 1922 " with USER [COMMAND…] Run shell or command with access to a home area\n"
4aa0a8ac
LP
1923 "\n%4$sOptions:%5$s\n"
1924 " -h --help Show this help\n"
1925 " --version Show package version\n"
1926 " --no-pager Do not pipe output into a pager\n"
1927 " --no-legend Do not show the headers and footers\n"
1928 " --no-ask-password Do not ask for system passwords\n"
1929 " -H --host=[USER@]HOST Operate on remote host\n"
1930 " -M --machine=CONTAINER Operate on local container\n"
1931 " --identity=PATH Read JSON identity from file\n"
1932 " --json=FORMAT Output inspection data in JSON (takes one of\n"
1933 " pretty, short, off)\n"
1934 " -j Equivalent to --json=pretty (on TTY) or\n"
1935 " --json=short (otherwise)\n"
1936 " --export-format= Strip JSON inspection data (full, stripped,\n"
1937 " minimal)\n"
1938 " -E When specified once equals -j --export-format=\n"
1939 " stripped, when specified twice equals\n"
1940 " -j --export-format=minimal\n"
1941 "\n%4$sGeneral User Record Properties:%5$s\n"
1942 " -c --real-name=REALNAME Real name for user\n"
1943 " --realm=REALM Realm to create user in\n"
1944 " --email-address=EMAIL Email address for user\n"
1945 " --location=LOCATION Set location of user on earth\n"
1946 " --icon-name=NAME Icon name for user\n"
1947 " -d --home-dir=PATH Home directory\n"
a07e9625 1948 " -u --uid=UID Numeric UID for user\n"
4aa0a8ac
LP
1949 " -G --member-of=GROUP Add user to group\n"
1950 " --skel=PATH Skeleton directory to use\n"
1951 " --shell=PATH Shell for account\n"
1952 " --setenv=VARIABLE=VALUE Set an environment variable at log-in\n"
1953 " --timezone=TIMEZONE Set a time-zone\n"
1954 " --language=LOCALE Set preferred language\n"
1955 " --ssh-authorized-keys=KEYS\n"
1956 " Specify SSH public keys\n"
1957 " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n"
1958 " private key and matching X.509 certificate\n"
1c0c4a43
LP
1959 " --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n"
1960 " extension\n"
80c41552 1961 " --recovery-key=BOOL Add a recovery key\n"
4aa0a8ac
LP
1962 "\n%4$sAccount Management User Record Properties:%5$s\n"
1963 " --locked=BOOL Set locked account state\n"
1964 " --not-before=TIMESTAMP Do not allow logins before\n"
1965 " --not-after=TIMESTAMP Do not allow logins after\n"
1966 " --rate-limit-interval=SECS\n"
1967 " Login rate-limit interval in seconds\n"
1968 " --rate-limit-burst=NUMBER\n"
1969 " Login rate-limit attempts per interval\n"
1970 "\n%4$sPassword Policy User Record Properties:%5$s\n"
1971 " --password-hint=HINT Set Password hint\n"
1972 " --enforce-password-policy=BOOL\n"
1973 " Control whether to enforce system's password\n"
1974 " policy for this user\n"
1975 " -P Equivalent to --enforce-password-password=no\n"
1976 " --password-change-now=BOOL\n"
1977 " Require the password to be changed on next login\n"
1978 " --password-change-min=TIME\n"
1979 " Require minimum time between password changes\n"
1980 " --password-change-max=TIME\n"
1981 " Require maximum time between password changes\n"
1982 " --password-change-warn=TIME\n"
1983 " How much time to warn before password expiry\n"
1984 " --password-change-inactive=TIME\n"
1985 " How much time to block password after expiry\n"
1986 "\n%4$sResource Management User Record Properties:%5$s\n"
1987 " --disk-size=BYTES Size to assign the user on disk\n"
1988 " --access-mode=MODE User home directory access mode\n"
1989 " --umask=MODE Umask for user when logging in\n"
1990 " --nice=NICE Nice level for user\n"
1991 " --rlimit=LIMIT=VALUE[:VALUE]\n"
1992 " Set resource limits\n"
1993 " --tasks-max=MAX Set maximum number of per-user tasks\n"
1994 " --memory-high=BYTES Set high memory threshold in bytes\n"
1995 " --memory-max=BYTES Set maximum memory limit\n"
1996 " --cpu-weight=WEIGHT Set CPU weight\n"
1997 " --io-weight=WEIGHT Set IO weight\n"
1998 "\n%4$sStorage User Record Properties:%5$s\n"
1999 " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n"
2000 " subvolume, cifs)\n"
2001 " --image-path=PATH Path to image file/directory\n"
2002 "\n%4$sLUKS Storage User Record Properties:%5$s\n"
2003 " --fs-type=TYPE File system type to use in case of luks\n"
caf6bd16 2004 " storage (btrfs, ext4, xfs)\n"
4aa0a8ac 2005 " --luks-discard=BOOL Whether to use 'discard' feature of file system\n"
cba11699
LP
2006 " when activated (mounted)\n"
2007 " --luks-offline-discard=BOOL\n"
2008 " Whether to trim file on logout\n"
4aa0a8ac
LP
2009 " --luks-cipher=CIPHER Cipher to use for LUKS encryption\n"
2010 " --luks-cipher-mode=MODE Cipher mode to use for LUKS encryption\n"
2011 " --luks-volume-key-size=BITS\n"
2012 " Volume key size to use for LUKS encryption\n"
2013 " --luks-pbkdf-type=TYPE Password-based Key Derivation Function to use\n"
2014 " --luks-pbkdf-hash-algorithm=ALGORITHM\n"
2015 " PBKDF hash algorithm to use\n"
2016 " --luks-pbkdf-time-cost=SECS\n"
2017 " Time cost for PBKDF in seconds\n"
2018 " --luks-pbkdf-memory-cost=BYTES\n"
2019 " Memory cost for PBKDF in bytes\n"
2020 " --luks-pbkdf-parallel-threads=NUMBER\n"
2021 " Number of parallel threads for PKBDF\n"
2022 "\n%4$sMounting User Record Properties:%5$s\n"
2023 " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n"
2024 " --nodev=BOOL Control the 'nodev' flag of the home mount\n"
2025 " --noexec=BOOL Control the 'noexec' flag of the home mount\n"
2026 "\n%4$sCIFS User Record Properties:%5$s\n"
2027 " --cifs-domain=DOMAIN CIFS (Windows) domain\n"
2028 " --cifs-user-name=USER CIFS (Windows) user name\n"
c11428ad 2029 " --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n"
4aa0a8ac
LP
2030 "\n%4$sLogin Behaviour User Record Properties:%5$s\n"
2031 " --stop-delay=SECS How long to leave user services running after\n"
2032 " logout\n"
2033 " --kill-processes=BOOL Whether to kill user processes when sessions\n"
2034 " terminate\n"
2035 " --auto-login=BOOL Try to log this user in automatically\n"
2036 "\nSee the %6$s for details.\n"
2037 , program_invocation_short_name
2038 , ansi_highlight(), ansi_normal()
2039 , ansi_underline(), ansi_normal()
2040 , link
2041 );
2042
2043 return 0;
2044}
2045
2046static int parse_argv(int argc, char *argv[]) {
2047
2048 enum {
2049 ARG_VERSION = 0x100,
2050 ARG_NO_PAGER,
2051 ARG_NO_LEGEND,
2052 ARG_NO_ASK_PASSWORD,
2053 ARG_REALM,
2054 ARG_EMAIL_ADDRESS,
2055 ARG_DISK_SIZE,
2056 ARG_ACCESS_MODE,
2057 ARG_STORAGE,
2058 ARG_FS_TYPE,
2059 ARG_IMAGE_PATH,
2060 ARG_UMASK,
2061 ARG_LUKS_DISCARD,
cba11699 2062 ARG_LUKS_OFFLINE_DISCARD,
4aa0a8ac
LP
2063 ARG_JSON,
2064 ARG_SETENV,
2065 ARG_TIMEZONE,
2066 ARG_LANGUAGE,
2067 ARG_LOCKED,
2068 ARG_SSH_AUTHORIZED_KEYS,
2069 ARG_LOCATION,
2070 ARG_ICON_NAME,
2071 ARG_PASSWORD_HINT,
2072 ARG_NICE,
2073 ARG_RLIMIT,
2074 ARG_NOT_BEFORE,
2075 ARG_NOT_AFTER,
2076 ARG_LUKS_CIPHER,
2077 ARG_LUKS_CIPHER_MODE,
2078 ARG_LUKS_VOLUME_KEY_SIZE,
2079 ARG_NOSUID,
2080 ARG_NODEV,
2081 ARG_NOEXEC,
2082 ARG_CIFS_DOMAIN,
2083 ARG_CIFS_USER_NAME,
2084 ARG_CIFS_SERVICE,
2085 ARG_TASKS_MAX,
2086 ARG_MEMORY_HIGH,
2087 ARG_MEMORY_MAX,
2088 ARG_CPU_WEIGHT,
2089 ARG_IO_WEIGHT,
2090 ARG_LUKS_PBKDF_TYPE,
2091 ARG_LUKS_PBKDF_HASH_ALGORITHM,
2092 ARG_LUKS_PBKDF_TIME_COST,
2093 ARG_LUKS_PBKDF_MEMORY_COST,
2094 ARG_LUKS_PBKDF_PARALLEL_THREADS,
2095 ARG_RATE_LIMIT_INTERVAL,
2096 ARG_RATE_LIMIT_BURST,
2097 ARG_STOP_DELAY,
2098 ARG_KILL_PROCESSES,
2099 ARG_ENFORCE_PASSWORD_POLICY,
2100 ARG_PASSWORD_CHANGE_NOW,
2101 ARG_PASSWORD_CHANGE_MIN,
2102 ARG_PASSWORD_CHANGE_MAX,
2103 ARG_PASSWORD_CHANGE_WARN,
2104 ARG_PASSWORD_CHANGE_INACTIVE,
2105 ARG_EXPORT_FORMAT,
2106 ARG_AUTO_LOGIN,
2107 ARG_PKCS11_TOKEN_URI,
1c0c4a43 2108 ARG_FIDO2_DEVICE,
80c41552 2109 ARG_RECOVERY_KEY,
4aa0a8ac
LP
2110 ARG_AND_RESIZE,
2111 ARG_AND_CHANGE_PASSWORD,
2112 };
2113
2114 static const struct option options[] = {
2115 { "help", no_argument, NULL, 'h' },
2116 { "version", no_argument, NULL, ARG_VERSION },
2117 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
2118 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
2119 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
2120 { "host", required_argument, NULL, 'H' },
2121 { "machine", required_argument, NULL, 'M' },
2122 { "identity", required_argument, NULL, 'I' },
2123 { "real-name", required_argument, NULL, 'c' },
2124 { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */
2125 { "realm", required_argument, NULL, ARG_REALM },
2126 { "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS },
2127 { "location", required_argument, NULL, ARG_LOCATION },
2128 { "password-hint", required_argument, NULL, ARG_PASSWORD_HINT },
2129 { "icon-name", required_argument, NULL, ARG_ICON_NAME },
2130 { "home-dir", required_argument, NULL, 'd' }, /* Compatible with useradd(8) */
2131 { "uid", required_argument, NULL, 'u' }, /* Compatible with useradd(8) */
2132 { "member-of", required_argument, NULL, 'G' },
2133 { "groups", required_argument, NULL, 'G' }, /* Compat alias to keep thing in sync with useradd(8) */
2134 { "skel", required_argument, NULL, 'k' }, /* Compatible with useradd(8) */
2135 { "shell", required_argument, NULL, 's' }, /* Compatible with useradd(8) */
2136 { "setenv", required_argument, NULL, ARG_SETENV },
2137 { "timezone", required_argument, NULL, ARG_TIMEZONE },
2138 { "language", required_argument, NULL, ARG_LANGUAGE },
2139 { "locked", required_argument, NULL, ARG_LOCKED },
2140 { "not-before", required_argument, NULL, ARG_NOT_BEFORE },
2141 { "not-after", required_argument, NULL, ARG_NOT_AFTER },
2142 { "expiredate", required_argument, NULL, 'e' }, /* Compat alias to keep thing in sync with useradd(8) */
2143 { "ssh-authorized-keys", required_argument, NULL, ARG_SSH_AUTHORIZED_KEYS },
2144 { "disk-size", required_argument, NULL, ARG_DISK_SIZE },
2145 { "access-mode", required_argument, NULL, ARG_ACCESS_MODE },
2146 { "umask", required_argument, NULL, ARG_UMASK },
2147 { "nice", required_argument, NULL, ARG_NICE },
2148 { "rlimit", required_argument, NULL, ARG_RLIMIT },
2149 { "tasks-max", required_argument, NULL, ARG_TASKS_MAX },
2150 { "memory-high", required_argument, NULL, ARG_MEMORY_HIGH },
2151 { "memory-max", required_argument, NULL, ARG_MEMORY_MAX },
2152 { "cpu-weight", required_argument, NULL, ARG_CPU_WEIGHT },
2153 { "io-weight", required_argument, NULL, ARG_IO_WEIGHT },
2154 { "storage", required_argument, NULL, ARG_STORAGE },
2155 { "image-path", required_argument, NULL, ARG_IMAGE_PATH },
2156 { "fs-type", required_argument, NULL, ARG_FS_TYPE },
2157 { "luks-discard", required_argument, NULL, ARG_LUKS_DISCARD },
cba11699 2158 { "luks-offline-discard", required_argument, NULL, ARG_LUKS_OFFLINE_DISCARD },
4aa0a8ac
LP
2159 { "luks-cipher", required_argument, NULL, ARG_LUKS_CIPHER },
2160 { "luks-cipher-mode", required_argument, NULL, ARG_LUKS_CIPHER_MODE },
2161 { "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE },
2162 { "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE },
2163 { "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM },
2164 { "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST },
2165 { "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST },
2166 { "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS },
2167 { "nosuid", required_argument, NULL, ARG_NOSUID },
2168 { "nodev", required_argument, NULL, ARG_NODEV },
2169 { "noexec", required_argument, NULL, ARG_NOEXEC },
2170 { "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME },
2171 { "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN },
2172 { "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE },
2173 { "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL },
2174 { "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST },
2175 { "stop-delay", required_argument, NULL, ARG_STOP_DELAY },
2176 { "kill-processes", required_argument, NULL, ARG_KILL_PROCESSES },
2177 { "enforce-password-policy", required_argument, NULL, ARG_ENFORCE_PASSWORD_POLICY },
2178 { "password-change-now", required_argument, NULL, ARG_PASSWORD_CHANGE_NOW },
2179 { "password-change-min", required_argument, NULL, ARG_PASSWORD_CHANGE_MIN },
2180 { "password-change-max", required_argument, NULL, ARG_PASSWORD_CHANGE_MAX },
2181 { "password-change-warn", required_argument, NULL, ARG_PASSWORD_CHANGE_WARN },
2182 { "password-change-inactive", required_argument, NULL, ARG_PASSWORD_CHANGE_INACTIVE },
2183 { "auto-login", required_argument, NULL, ARG_AUTO_LOGIN },
2184 { "json", required_argument, NULL, ARG_JSON },
2185 { "export-format", required_argument, NULL, ARG_EXPORT_FORMAT },
2186 { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
1c0c4a43 2187 { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
80c41552 2188 { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY },
4aa0a8ac
LP
2189 { "and-resize", required_argument, NULL, ARG_AND_RESIZE },
2190 { "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD },
2191 {}
2192 };
2193
2194 int r;
2195
2196 assert(argc >= 0);
2197 assert(argv);
2198
2199 for (;;) {
2200 int c;
2201
2202 c = getopt_long(argc, argv, "hH:M:I:c:d:u:k:s:e:G:jPE", options, NULL);
2203 if (c < 0)
2204 break;
2205
2206 switch (c) {
2207
2208 case 'h':
2209 return help(0, NULL, NULL);
2210
2211 case ARG_VERSION:
2212 return version();
2213
2214 case ARG_NO_PAGER:
2215 arg_pager_flags |= PAGER_DISABLE;
2216 break;
2217
2218 case ARG_NO_LEGEND:
2219 arg_legend = false;
2220 break;
2221
2222 case ARG_NO_ASK_PASSWORD:
2223 arg_ask_password = false;
2224 break;
2225
2226 case 'H':
2227 arg_transport = BUS_TRANSPORT_REMOTE;
2228 arg_host = optarg;
2229 break;
2230
2231 case 'M':
2232 arg_transport = BUS_TRANSPORT_MACHINE;
2233 arg_host = optarg;
2234 break;
2235
2236 case 'I':
2237 arg_identity = optarg;
2238 break;
2239
2240 case 'c':
2241 if (isempty(optarg)) {
2242 r = drop_from_identity("realName");
2243 if (r < 0)
2244 return r;
2245
2246 break;
2247 }
2248
2249 if (!valid_gecos(optarg))
2250 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Real name '%s' not a valid GECOS field.", optarg);
2251
2252 r = json_variant_set_field_string(&arg_identity_extra, "realName", optarg);
2253 if (r < 0)
2254 return log_error_errno(r, "Failed to set realName field: %m");
2255
2256 break;
2257
2258 case 'd': {
2259 _cleanup_free_ char *hd = NULL;
2260
2261 if (isempty(optarg)) {
2262 r = drop_from_identity("homeDirectory");
2263 if (r < 0)
2264 return r;
2265
2266 break;
2267 }
2268
2269 r = parse_path_argument_and_warn(optarg, false, &hd);
2270 if (r < 0)
2271 return r;
2272
2273 if (!valid_home(hd))
2274 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd);
2275
2276 r = json_variant_set_field_string(&arg_identity_extra, "homeDirectory", hd);
2277 if (r < 0)
2278 return log_error_errno(r, "Failed to set homeDirectory field: %m");
2279
2280 break;
2281 }
2282
2283 case ARG_REALM:
2284 if (isempty(optarg)) {
2285 r = drop_from_identity("realm");
2286 if (r < 0)
2287 return r;
2288
2289 break;
2290 }
2291
2292 r = dns_name_is_valid(optarg);
2293 if (r < 0)
2294 return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", optarg);
2295 if (r == 0)
2296 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain: %m", optarg);
2297
2298 r = json_variant_set_field_string(&arg_identity_extra, "realm", optarg);
2299 if (r < 0)
2300 return log_error_errno(r, "Failed to set realm field: %m");
2301 break;
2302
2303 case ARG_EMAIL_ADDRESS:
2304 case ARG_LOCATION:
2305 case ARG_ICON_NAME:
2306 case ARG_CIFS_USER_NAME:
2307 case ARG_CIFS_DOMAIN:
2308 case ARG_CIFS_SERVICE: {
2309
2310 const char *field =
2311 c == ARG_EMAIL_ADDRESS ? "emailAddress" :
2312 c == ARG_LOCATION ? "location" :
2313 c == ARG_ICON_NAME ? "iconName" :
2314 c == ARG_CIFS_USER_NAME ? "cifsUserName" :
2315 c == ARG_CIFS_DOMAIN ? "cifsDomain" :
2316 c == ARG_CIFS_SERVICE ? "cifsService" :
2317 NULL;
2318
2319 assert(field);
2320
2321 if (isempty(optarg)) {
2322 r = drop_from_identity(field);
2323 if (r < 0)
2324 return r;
2325
2326 break;
2327 }
2328
2329 r = json_variant_set_field_string(&arg_identity_extra, field, optarg);
2330 if (r < 0)
2331 return log_error_errno(r, "Failed to set %s field: %m", field);
2332
2333 break;
2334 }
2335
2336 case ARG_PASSWORD_HINT:
2337 if (isempty(optarg)) {
2338 r = drop_from_identity("passwordHint");
2339 if (r < 0)
2340 return r;
2341
2342 break;
2343 }
2344
2345 r = json_variant_set_field_string(&arg_identity_extra_privileged, "passwordHint", optarg);
2346 if (r < 0)
2347 return log_error_errno(r, "Failed to set passwordHint field: %m");
2348
2349 string_erase(optarg);
2350 break;
2351
2352 case ARG_NICE: {
2353 int nc;
2354
2355 if (isempty(optarg)) {
2356 r = drop_from_identity("niceLevel");
2357 if (r < 0)
2358 return r;
2359 break;
2360 }
2361
2362 r = parse_nice(optarg, &nc);
2363 if (r < 0)
2364 return log_error_errno(r, "Failed to parse nice level: %s", optarg);
2365
2366 r = json_variant_set_field_integer(&arg_identity_extra, "niceLevel", nc);
2367 if (r < 0)
2368 return log_error_errno(r, "Failed to set niceLevel field: %m");
2369
2370 break;
2371 }
2372
2373 case ARG_RLIMIT: {
2374 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *jcur = NULL, *jmax = NULL;
2375 _cleanup_free_ char *field = NULL, *t = NULL;
2376 const char *eq;
2377 struct rlimit rl;
2378 int l;
2379
2380 if (isempty(optarg)) {
2381 /* Remove all resource limits */
2382
2383 r = drop_from_identity("resourceLimits");
2384 if (r < 0)
2385 return r;
2386
2387 arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits);
2388 arg_identity_extra_rlimits = json_variant_unref(arg_identity_extra_rlimits);
2389 break;
2390 }
2391
2392 eq = strchr(optarg, '=');
2393 if (!eq)
2394 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", optarg);
2395
2396 field = strndup(optarg, eq - optarg);
2397 if (!field)
2398 return log_oom();
2399
2400 l = rlimit_from_string_harder(field);
2401 if (l < 0)
2402 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown resource limit type: %s", field);
2403
2404 if (isempty(eq + 1)) {
2405 /* Remove only the specific rlimit */
2406
2407 r = strv_extend(&arg_identity_filter_rlimits, rlimit_to_string(l));
2408 if (r < 0)
2409 return r;
2410
2411 r = json_variant_filter(&arg_identity_extra_rlimits, STRV_MAKE(field));
2412 if (r < 0)
2413 return log_error_errno(r, "Failed to filter JSON identity data: %m");
2414
2415 break;
2416 }
2417
2418 r = rlimit_parse(l, eq + 1, &rl);
2419 if (r < 0)
2420 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse resource limit value: %s", eq + 1);
2421
2422 r = rl.rlim_cur == RLIM_INFINITY ? json_variant_new_null(&jcur) : json_variant_new_unsigned(&jcur, rl.rlim_cur);
2423 if (r < 0)
2424 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to allocate current integer: %m");
2425
2426 r = rl.rlim_max == RLIM_INFINITY ? json_variant_new_null(&jmax) : json_variant_new_unsigned(&jmax, rl.rlim_max);
2427 if (r < 0)
2428 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to allocate maximum integer: %m");
2429
2430 r = json_build(&v,
2431 JSON_BUILD_OBJECT(
2432 JSON_BUILD_PAIR("cur", JSON_BUILD_VARIANT(jcur)),
2433 JSON_BUILD_PAIR("max", JSON_BUILD_VARIANT(jmax))));
2434 if (r < 0)
2435 return log_error_errno(r, "Failed to build resource limit: %m");
2436
2437 t = strjoin("RLIMIT_", rlimit_to_string(l));
2438 if (!t)
2439 return log_oom();
2440
2441 r = json_variant_set_field(&arg_identity_extra_rlimits, t, v);
2442 if (r < 0)
2443 return log_error_errno(r, "Failed to set %s field: %m", rlimit_to_string(l));
2444
2445 break;
2446 }
2447
2448 case 'u': {
2449 uid_t uid;
2450
2451 if (isempty(optarg)) {
2452 r = drop_from_identity("uid");
2453 if (r < 0)
2454 return r;
2455
2456 break;
2457 }
2458
2459 r = parse_uid(optarg, &uid);
2460 if (r < 0)
2461 return log_error_errno(r, "Failed to parse UID '%s'.", optarg);
2462
2463 if (uid_is_system(uid))
2464 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is in system range, refusing.", uid);
2465 if (uid_is_dynamic(uid))
2466 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is in dynamic range, refusing.", uid);
2467 if (uid == UID_NOBODY)
2468 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is nobody UID, refusing.", uid);
2469
2470 r = json_variant_set_field_unsigned(&arg_identity_extra, "uid", uid);
2471 if (r < 0)
2472 return log_error_errno(r, "Failed to set realm field: %m");
2473
2474 break;
2475 }
2476
2477 case 'k':
2478 case ARG_IMAGE_PATH: {
2479 const char *field = c == 'k' ? "skeletonDirectory" : "imagePath";
2480 _cleanup_free_ char *v = NULL;
2481
2482 if (isempty(optarg)) {
2483 r = drop_from_identity(field);
2484 if (r < 0)
2485 return r;
2486
2487 break;
2488 }
2489
2490 r = parse_path_argument_and_warn(optarg, false, &v);
2491 if (r < 0)
2492 return r;
2493
2494 r = json_variant_set_field_string(&arg_identity_extra_this_machine, field, v);
2495 if (r < 0)
2496 return log_error_errno(r, "Failed to set %s field: %m", v);
2497
2498 break;
2499 }
2500
2501 case 's':
2502 if (isempty(optarg)) {
2503 r = drop_from_identity("shell");
2504 if (r < 0)
2505 return r;
2506
2507 break;
2508 }
2509
2510 if (!valid_shell(optarg))
2511 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Shell '%s' not valid.", optarg);
2512
2513 r = json_variant_set_field_string(&arg_identity_extra, "shell", optarg);
2514 if (r < 0)
2515 return log_error_errno(r, "Failed to set shell field: %m");
2516
2517 break;
2518
2519 case ARG_SETENV: {
2520 _cleanup_free_ char **l = NULL, **k = NULL;
2521 _cleanup_(json_variant_unrefp) JsonVariant *ne = NULL;
2522 JsonVariant *e;
2523
2524 if (isempty(optarg)) {
2525 r = drop_from_identity("environment");
2526 if (r < 0)
2527 return r;
2528
2529 break;
2530 }
2531
2532 if (!env_assignment_is_valid(optarg))
2533 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Environment assignment '%s' not valid.", optarg);
2534
2535 e = json_variant_by_key(arg_identity_extra, "environment");
2536 if (e) {
2537 r = json_variant_strv(e, &l);
2538 if (r < 0)
2539 return log_error_errno(r, "Failed to parse JSON environment field: %m");
2540 }
2541
2542 k = strv_env_set(l, optarg);
2543 if (!k)
2544 return log_oom();
2545
2546 strv_sort(k);
2547
2548 r = json_variant_new_array_strv(&ne, k);
2549 if (r < 0)
2550 return log_error_errno(r, "Failed to allocate environment list JSON: %m");
2551
2552 r = json_variant_set_field(&arg_identity_extra, "environment", ne);
2553 if (r < 0)
162392b7 2554 return log_error_errno(r, "Failed to set environment list: %m");
4aa0a8ac
LP
2555
2556 break;
2557 }
2558
2559 case ARG_TIMEZONE:
2560
2561 if (isempty(optarg)) {
2562 r = drop_from_identity("timeZone");
2563 if (r < 0)
2564 return r;
2565
2566 break;
2567 }
2568
2569 if (!timezone_is_valid(optarg, LOG_DEBUG))
2570 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timezone '%s' is not valid.", optarg);
2571
2572 r = json_variant_set_field_string(&arg_identity_extra, "timeZone", optarg);
2573 if (r < 0)
2574 return log_error_errno(r, "Failed to set timezone field: %m");
2575
2576 break;
2577
2578 case ARG_LANGUAGE:
2579 if (isempty(optarg)) {
2580 r = drop_from_identity("language");
2581 if (r < 0)
2582 return r;
2583
2584 break;
2585 }
2586
2587 if (!locale_is_valid(optarg))
2588 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", optarg);
2589
a00a78b8
LP
2590 if (locale_is_installed(optarg) <= 0)
2591 log_warning("Locale '%s' is not installed, accepting anyway.", optarg);
2592
4aa0a8ac
LP
2593 r = json_variant_set_field_string(&arg_identity_extra, "preferredLanguage", optarg);
2594 if (r < 0)
2595 return log_error_errno(r, "Failed to set preferredLanguage field: %m");
2596
2597 break;
2598
2599 case ARG_NOSUID:
2600 case ARG_NODEV:
2601 case ARG_NOEXEC:
2602 case ARG_LOCKED:
2603 case ARG_KILL_PROCESSES:
2604 case ARG_ENFORCE_PASSWORD_POLICY:
2605 case ARG_AUTO_LOGIN:
2606 case ARG_PASSWORD_CHANGE_NOW: {
2607 const char *field =
2608 c == ARG_LOCKED ? "locked" :
2609 c == ARG_NOSUID ? "mountNoSuid" :
2610 c == ARG_NODEV ? "mountNoDevices" :
2611 c == ARG_NOEXEC ? "mountNoExecute" :
2612 c == ARG_KILL_PROCESSES ? "killProcesses" :
2613 c == ARG_ENFORCE_PASSWORD_POLICY ? "enforcePasswordPolicy" :
2614 c == ARG_AUTO_LOGIN ? "autoLogin" :
2615 c == ARG_PASSWORD_CHANGE_NOW ? "passwordChangeNow" :
2616 NULL;
2617
2618 assert(field);
2619
2620 if (isempty(optarg)) {
2621 r = drop_from_identity(field);
2622 if (r < 0)
2623 return r;
2624
2625 break;
2626 }
2627
2628 r = parse_boolean(optarg);
2629 if (r < 0)
2630 return log_error_errno(r, "Failed to parse %s boolean: %m", field);
2631
2632 r = json_variant_set_field_boolean(&arg_identity_extra, field, r > 0);
2633 if (r < 0)
2634 return log_error_errno(r, "Failed to set %s field: %m", field);
2635
2636 break;
2637 }
2638
2639 case 'P':
2640 r = json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false);
2641 if (r < 0)
2642 return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m");
2643
2644 break;
2645
2646 case ARG_DISK_SIZE:
2647 if (isempty(optarg)) {
2648 r = drop_from_identity("diskSize");
2649 if (r < 0)
2650 return r;
2651
2652 r = drop_from_identity("diskSizeRelative");
2653 if (r < 0)
2654 return r;
2655
2656 arg_disk_size = arg_disk_size_relative = UINT64_MAX;
2657 break;
2658 }
2659
2660 r = parse_permille(optarg);
2661 if (r < 0) {
2662 r = parse_size(optarg, 1024, &arg_disk_size);
2663 if (r < 0)
2664 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Disk size '%s' not valid.", optarg);
2665
2666 r = drop_from_identity("diskSizeRelative");
2667 if (r < 0)
2668 return r;
2669
2670 r = json_variant_set_field_unsigned(&arg_identity_extra_this_machine, "diskSize", arg_disk_size);
2671 if (r < 0)
2672 return log_error_errno(r, "Failed to set diskSize field: %m");
2673
2674 arg_disk_size_relative = UINT64_MAX;
2675 } else {
2676 /* Normalize to UINT32_MAX == 100% */
2677 arg_disk_size_relative = (uint64_t) r * UINT32_MAX / 1000U;
2678
2679 r = drop_from_identity("diskSize");
2680 if (r < 0)
2681 return r;
2682
2683 r = json_variant_set_field_unsigned(&arg_identity_extra_this_machine, "diskSizeRelative", arg_disk_size_relative);
2684 if (r < 0)
2685 return log_error_errno(r, "Failed to set diskSizeRelative field: %m");
2686
2687 arg_disk_size = UINT64_MAX;
2688 }
2689
2690 break;
2691
2692 case ARG_ACCESS_MODE: {
2693 mode_t mode;
2694
2695 if (isempty(optarg)) {
2696 r = drop_from_identity("accessMode");
2697 if (r < 0)
2698 return r;
2699
2700 break;
2701 }
2702
2703 r = parse_mode(optarg, &mode);
2704 if (r < 0)
2705 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", optarg);
2706
2707 r = json_variant_set_field_unsigned(&arg_identity_extra, "accessMode", mode);
2708 if (r < 0)
2709 return log_error_errno(r, "Failed to set access mode field: %m");
2710
2711 break;
2712 }
2713
2714 case ARG_LUKS_DISCARD:
2715 if (isempty(optarg)) {
2716 r = drop_from_identity("luksDiscard");
2717 if (r < 0)
2718 return r;
2719
2720 break;
2721 }
2722
2723 r = parse_boolean(optarg);
2724 if (r < 0)
2725 return log_error_errno(r, "Failed to parse --luks-discard= parameter: %s", optarg);
2726
2727 r = json_variant_set_field_boolean(&arg_identity_extra, "luksDiscard", r);
2728 if (r < 0)
2729 return log_error_errno(r, "Failed to set discard field: %m");
2730
2731 break;
2732
cba11699
LP
2733 case ARG_LUKS_OFFLINE_DISCARD:
2734 if (isempty(optarg)) {
2735 r = drop_from_identity("luksOfflineDiscard");
2736 if (r < 0)
2737 return r;
2738
2739 break;
2740 }
2741
2742 r = parse_boolean(optarg);
2743 if (r < 0)
2744 return log_error_errno(r, "Failed to parse --luks-offline-discard= parameter: %s", optarg);
2745
2746 r = json_variant_set_field_boolean(&arg_identity_extra, "luksOfflineDiscard", r);
2747 if (r < 0)
2748 return log_error_errno(r, "Failed to set offline discard field: %m");
2749
2750 break;
2751
4aa0a8ac
LP
2752 case ARG_LUKS_VOLUME_KEY_SIZE:
2753 case ARG_LUKS_PBKDF_PARALLEL_THREADS:
2754 case ARG_RATE_LIMIT_BURST: {
2755 const char *field =
2756 c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" :
2757 c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" :
2758 c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : NULL;
2759 unsigned n;
2760
2761 assert(field);
2762
2763 if (isempty(optarg)) {
2764 r = drop_from_identity(field);
2765 if (r < 0)
2766 return r;
2767 }
2768
2769 r = safe_atou(optarg, &n);
2770 if (r < 0)
2771 return log_error_errno(r, "Failed to parse %s parameter: %s", field, optarg);
2772
2773 r = json_variant_set_field_unsigned(&arg_identity_extra, field, n);
2774 if (r < 0)
2775 return log_error_errno(r, "Failed to set %s field: %m", field);
2776
2777 break;
2778 }
2779
2780 case ARG_UMASK: {
2781 mode_t m;
2782
2783 if (isempty(optarg)) {
2784 r = drop_from_identity("umask");
2785 if (r < 0)
2786 return r;
2787
2788 break;
2789 }
2790
2791 r = parse_mode(optarg, &m);
2792 if (r < 0)
2793 return log_error_errno(r, "Failed to parse umask: %m");
2794
2795 r = json_variant_set_field_integer(&arg_identity_extra, "umask", m);
2796 if (r < 0)
2797 return log_error_errno(r, "Failed to set umask field: %m");
2798
2799 break;
2800 }
2801
2802 case ARG_SSH_AUTHORIZED_KEYS: {
2803 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
2804 _cleanup_(strv_freep) char **l = NULL, **add = NULL;
2805
2806 if (isempty(optarg)) {
2807 r = drop_from_identity("sshAuthorizedKeys");
2808 if (r < 0)
2809 return r;
2810
2811 break;
2812 }
2813
2814 if (optarg[0] == '@') {
2815 _cleanup_fclose_ FILE *f = NULL;
2816
2817 /* If prefixed with '@' read from a file */
2818
2819 f = fopen(optarg+1, "re");
2820 if (!f)
2821 return log_error_errno(errno, "Failed to open '%s': %m", optarg+1);
2822
2823 for (;;) {
2824 _cleanup_free_ char *line = NULL;
2825
2826 r = read_line(f, LONG_LINE_MAX, &line);
2827 if (r < 0)
80ace4f2 2828 return log_error_errno(r, "Failed to read from '%s': %m", optarg+1);
4aa0a8ac
LP
2829 if (r == 0)
2830 break;
2831
2832 if (isempty(line))
2833 continue;
2834
2835 if (line[0] == '#')
2836 continue;
2837
2838 r = strv_consume(&add, TAKE_PTR(line));
2839 if (r < 0)
2840 return log_oom();
2841 }
2842 } else {
2843 /* Otherwise, assume it's a literal key. Let's do some superficial checks
2844 * before accept it though. */
2845
2846 if (string_has_cc(optarg, NULL))
2847 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Authorized key contains control characters, refusing.");
2848 if (optarg[0] == '#')
2849 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?");
2850
2851 add = strv_new(optarg);
2852 if (!add)
2853 return log_oom();
2854 }
2855
2856 v = json_variant_ref(json_variant_by_key(arg_identity_extra_privileged, "sshAuthorizedKeys"));
2857 if (v) {
2858 r = json_variant_strv(v, &l);
2859 if (r < 0)
2860 return log_error_errno(r, "Failed to parse SSH authorized keys list: %m");
2861 }
2862
2863 r = strv_extend_strv(&l, add, true);
2864 if (r < 0)
2865 return log_oom();
2866
2867 v = json_variant_unref(v);
2868
2869 r = json_variant_new_array_strv(&v, l);
2870 if (r < 0)
2871 return log_oom();
2872
2873 r = json_variant_set_field(&arg_identity_extra_privileged, "sshAuthorizedKeys", v);
2874 if (r < 0)
2875 return log_error_errno(r, "Failed to set authorized keys: %m");
2876
2877 break;
2878 }
2879
2880 case ARG_NOT_BEFORE:
2881 case ARG_NOT_AFTER:
2882 case 'e': {
2883 const char *field;
2884 usec_t n;
2885
2886 field = c == ARG_NOT_BEFORE ? "notBeforeUSec" :
2887 IN_SET(c, ARG_NOT_AFTER, 'e') ? "notAfterUSec" : NULL;
2888
2889 assert(field);
2890
2891 if (isempty(optarg)) {
2892 r = drop_from_identity(field);
2893 if (r < 0)
2894 return r;
2895
2896 break;
2897 }
2898
2899 /* Note the minor discrepancy regarding -e parsing here: we support that for compat
2900 * reasons, and in the original useradd(8) implementation it accepts dates in the
2901 * format YYYY-MM-DD. Coincidentally, we accept dates formatted like that too, but
2902 * with greater precision. */
2903 r = parse_timestamp(optarg, &n);
2904 if (r < 0)
2905 return log_error_errno(r, "Failed to parse %s parameter: %m", field);
2906
2907 r = json_variant_set_field_unsigned(&arg_identity_extra, field, n);
2908 if (r < 0)
2909 return log_error_errno(r, "Failed to set %s field: %m", field);
2910 break;
2911 }
2912
2913 case ARG_PASSWORD_CHANGE_MIN:
2914 case ARG_PASSWORD_CHANGE_MAX:
2915 case ARG_PASSWORD_CHANGE_WARN:
2916 case ARG_PASSWORD_CHANGE_INACTIVE: {
2917 const char *field;
2918 usec_t n;
2919
2920 field = c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" :
2921 c == ARG_PASSWORD_CHANGE_MAX ? "passwordChangeMaxUSec" :
2922 c == ARG_PASSWORD_CHANGE_WARN ? "passwordChangeWarnUSec" :
2923 c == ARG_PASSWORD_CHANGE_INACTIVE ? "passwordChangeInactiveUSec" :
2924 NULL;
2925
2926 assert(field);
2927
2928 if (isempty(optarg)) {
2929 r = drop_from_identity(field);
2930 if (r < 0)
2931 return r;
2932
2933 break;
2934 }
2935
2936 r = parse_sec(optarg, &n);
2937 if (r < 0)
2938 return log_error_errno(r, "Failed to parse %s parameter: %m", field);
2939
2940 r = json_variant_set_field_unsigned(&arg_identity_extra, field, n);
2941 if (r < 0)
2942 return log_error_errno(r, "Failed to set %s field: %m", field);
2943 break;
2944 }
2945
2946 case ARG_STORAGE:
2947 case ARG_FS_TYPE:
2948 case ARG_LUKS_CIPHER:
2949 case ARG_LUKS_CIPHER_MODE:
2950 case ARG_LUKS_PBKDF_TYPE:
2951 case ARG_LUKS_PBKDF_HASH_ALGORITHM: {
2952
2953 const char *field =
2954 c == ARG_STORAGE ? "storage" :
d900701e 2955 c == ARG_FS_TYPE ? "fileSystemType" :
4aa0a8ac
LP
2956 c == ARG_LUKS_CIPHER ? "luksCipher" :
2957 c == ARG_LUKS_CIPHER_MODE ? "luksCipherMode" :
2958 c == ARG_LUKS_PBKDF_TYPE ? "luksPbkdfType" :
2959 c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : NULL;
2960
2961 assert(field);
2962
2963 if (isempty(optarg)) {
2964 r = drop_from_identity(field);
2965 if (r < 0)
2966 return r;
2967
2968 break;
2969 }
2970
2971 if (!string_is_safe(optarg))
2972 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parameter for %s field not valid: %s", field, optarg);
2973
2974 r = json_variant_set_field_string(
2975 IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ?
2976 &arg_identity_extra_this_machine :
2977 &arg_identity_extra, field, optarg);
2978 if (r < 0)
2979 return log_error_errno(r, "Failed to set %s field: %m", field);
2980
2981 break;
2982 }
2983
2984 case ARG_LUKS_PBKDF_TIME_COST:
2985 case ARG_RATE_LIMIT_INTERVAL:
2986 case ARG_STOP_DELAY: {
2987 const char *field =
2988 c == ARG_LUKS_PBKDF_TIME_COST ? "luksPbkdfTimeCostUSec" :
2989 c == ARG_RATE_LIMIT_INTERVAL ? "rateLimitIntervalUSec" :
2990 c == ARG_STOP_DELAY ? "stopDelayUSec" :
2991 NULL;
2992 usec_t t;
2993
2994 assert(field);
2995
2996 if (isempty(optarg)) {
2997 r = drop_from_identity(field);
2998 if (r < 0)
2999 return r;
3000
3001 break;
3002 }
3003
3004 r = parse_sec(optarg, &t);
3005 if (r < 0)
3006 return log_error_errno(r, "Failed to parse %s field: %s", field, optarg);
3007
3008 r = json_variant_set_field_unsigned(&arg_identity_extra, field, t);
3009 if (r < 0)
3010 return log_error_errno(r, "Failed to set %s field: %m", field);
3011
3012 break;
3013 }
3014
3015 case 'G': {
3016 const char *p = optarg;
3017
3018 if (isempty(p)) {
3019 r = drop_from_identity("memberOf");
3020 if (r < 0)
3021 return r;
3022
3023 break;
3024 }
3025
3026 for (;;) {
3027 _cleanup_(json_variant_unrefp) JsonVariant *mo = NULL;
3028 _cleanup_strv_free_ char **list = NULL;
3029 _cleanup_free_ char *word = NULL;
3030
3031 r = extract_first_word(&p, &word, ",", 0);
3032 if (r < 0)
3033 return log_error_errno(r, "Failed to parse group list: %m");
3034 if (r == 0)
3035 break;
3036
7a8867ab 3037 if (!valid_user_group_name(word, 0))
4aa0a8ac
LP
3038 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word);
3039
3040 mo = json_variant_ref(json_variant_by_key(arg_identity_extra, "memberOf"));
3041
3042 r = json_variant_strv(mo, &list);
3043 if (r < 0)
3044 return log_error_errno(r, "Failed to parse group list: %m");
3045
3046 r = strv_extend(&list, word);
3047 if (r < 0)
3048 return log_oom();
3049
3050 strv_sort(list);
3051 strv_uniq(list);
3052
3053 mo = json_variant_unref(mo);
3054 r = json_variant_new_array_strv(&mo, list);
3055 if (r < 0)
3056 return log_error_errno(r, "Failed to create group list JSON: %m");
3057
3058 r = json_variant_set_field(&arg_identity_extra, "memberOf", mo);
3059 if (r < 0)
3060 return log_error_errno(r, "Failed to update group list: %m");
3061 }
3062
3063 break;
3064 }
3065
3066 case ARG_TASKS_MAX: {
3067 uint64_t u;
3068
3069 if (isempty(optarg)) {
3070 r = drop_from_identity("tasksMax");
3071 if (r < 0)
3072 return r;
3073 break;
3074 }
3075
3076 r = safe_atou64(optarg, &u);
3077 if (r < 0)
3078 return log_error_errno(r, "Failed to parse --tasks-max= parameter: %s", optarg);
3079
3080 r = json_variant_set_field_unsigned(&arg_identity_extra, "tasksMax", u);
3081 if (r < 0)
3082 return log_error_errno(r, "Failed to set tasksMax field: %m");
3083
3084 break;
3085 }
3086
3087 case ARG_MEMORY_MAX:
3088 case ARG_MEMORY_HIGH:
3089 case ARG_LUKS_PBKDF_MEMORY_COST: {
3090 const char *field =
3091 c == ARG_MEMORY_MAX ? "memoryMax" :
3092 c == ARG_MEMORY_HIGH ? "memoryHigh" :
3093 c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : NULL;
3094
3095 uint64_t u;
3096
3097 assert(field);
3098
3099 if (isempty(optarg)) {
3100 r = drop_from_identity(field);
3101 if (r < 0)
3102 return r;
3103 break;
3104 }
3105
3106 r = parse_size(optarg, 1024, &u);
3107 if (r < 0)
3108 return log_error_errno(r, "Failed to parse %s parameter: %s", field, optarg);
3109
3110 r = json_variant_set_field_unsigned(&arg_identity_extra_this_machine, field, u);
3111 if (r < 0)
3112 return log_error_errno(r, "Failed to set %s field: %m", field);
3113
3114 break;
3115 }
3116
3117 case ARG_CPU_WEIGHT:
3118 case ARG_IO_WEIGHT: {
3119 const char *field = c == ARG_CPU_WEIGHT ? "cpuWeight" :
3120 c == ARG_IO_WEIGHT ? "ioWeight" : NULL;
3121 uint64_t u;
3122
3123 assert(field);
3124
3125 if (isempty(optarg)) {
3126 r = drop_from_identity(field);
3127 if (r < 0)
3128 return r;
3129 break;
3130 }
3131
3132 r = safe_atou64(optarg, &u);
3133 if (r < 0)
3134 return log_error_errno(r, "Failed to parse --cpu-weight=/--io-weight= parameter: %s", optarg);
3135
3136 if (!CGROUP_WEIGHT_IS_OK(u))
3137 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Weight %" PRIu64 " is out of valid weight range.", u);
3138
3139 r = json_variant_set_field_unsigned(&arg_identity_extra, field, u);
3140 if (r < 0)
3141 return log_error_errno(r, "Failed to set %s field: %m", field);
3142
3143 break;
3144 }
3145
3146 case ARG_PKCS11_TOKEN_URI: {
3147 const char *p;
3148
0eb3be46 3149 if (streq(optarg, "list"))
f240cbb6 3150 return pkcs11_list_tokens();
0eb3be46 3151
4aa0a8ac
LP
3152 /* If --pkcs11-token-uri= is specified we always drop everything old */
3153 FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") {
3154 r = drop_from_identity(p);
3155 if (r < 0)
3156 return r;
3157 }
3158
3159 if (isempty(optarg)) {
3160 arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri);
3161 break;
3162 }
3163
0eb3be46
LP
3164 if (streq(optarg, "auto")) {
3165 _cleanup_free_ char *found = NULL;
4aa0a8ac 3166
f240cbb6 3167 r = pkcs11_find_token_auto(&found);
0eb3be46
LP
3168 if (r < 0)
3169 return r;
3170 r = strv_consume(&arg_pkcs11_token_uri, TAKE_PTR(found));
3171 } else {
3172 if (!pkcs11_uri_valid(optarg))
3173 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
3174
3175 r = strv_extend(&arg_pkcs11_token_uri, optarg);
3176 }
4aa0a8ac
LP
3177 if (r < 0)
3178 return r;
3179
3180 strv_uniq(arg_pkcs11_token_uri);
3181 break;
3182 }
3183
1c0c4a43
LP
3184 case ARG_FIDO2_DEVICE: {
3185 const char *p;
3186
3187 if (streq(optarg, "list"))
fb2d839c 3188 return fido2_list_devices();
1c0c4a43
LP
3189
3190 FOREACH_STRING(p, "fido2HmacCredential", "fido2HmacSalt") {
3191 r = drop_from_identity(p);
3192 if (r < 0)
3193 return r;
3194 }
3195
3196 if (isempty(optarg)) {
3197 arg_fido2_device = strv_free(arg_fido2_device);
3198 break;
3199 }
3200
3201 if (streq(optarg, "auto")) {
3202 _cleanup_free_ char *found = NULL;
3203
fb2d839c 3204 r = fido2_find_device_auto(&found);
1c0c4a43
LP
3205 if (r < 0)
3206 return r;
3207
3208 r = strv_consume(&arg_fido2_device, TAKE_PTR(found));
3209 } else
3210 r = strv_extend(&arg_fido2_device, optarg);
3211
3212 if (r < 0)
3213 return r;
3214
3215 strv_uniq(arg_fido2_device);
3216 break;
3217 }
3218
80c41552
LP
3219 case ARG_RECOVERY_KEY: {
3220 const char *p;
3221
3222 r = parse_boolean(optarg);
3223 if (r < 0)
3224 return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg);
3225
3226 arg_recovery_key = r;
3227
3228 FOREACH_STRING(p, "recoveryKey", "recoveryKeyType") {
3229 r = drop_from_identity(p);
3230 if (r < 0)
3231 return r;
3232 }
3233
3234 break;
3235 }
3236
4aa0a8ac
LP
3237 case 'j':
3238 arg_json = true;
3239 arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
3240 break;
3241
3242 case ARG_JSON:
3243 if (streq(optarg, "pretty")) {
3244 arg_json = true;
3245 arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO;
3246 } else if (streq(optarg, "short")) {
3247 arg_json = true;
3248 arg_json_format_flags = JSON_FORMAT_NEWLINE;
3249 } else if (streq(optarg, "off")) {
3250 arg_json = false;
3251 arg_json_format_flags = 0;
3252 } else if (streq(optarg, "help")) {
3253 puts("pretty\n"
3254 "short\n"
3255 "off");
3256 return 0;
3257 } else
3258 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json=: %s", optarg);
3259
3260 break;
3261
3262 case 'E':
3263 if (arg_export_format == EXPORT_FORMAT_FULL)
3264 arg_export_format = EXPORT_FORMAT_STRIPPED;
3265 else if (arg_export_format == EXPORT_FORMAT_STRIPPED)
3266 arg_export_format = EXPORT_FORMAT_MINIMAL;
3267 else
3268 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported.");
3269
3270 arg_json = true;
3271 if (arg_json_format_flags == 0)
3272 arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
3273 break;
3274
3275 case ARG_EXPORT_FORMAT:
3276 if (streq(optarg, "full"))
3277 arg_export_format = EXPORT_FORMAT_FULL;
3278 else if (streq(optarg, "stripped"))
3279 arg_export_format = EXPORT_FORMAT_STRIPPED;
3280 else if (streq(optarg, "minimal"))
3281 arg_export_format = EXPORT_FORMAT_MINIMAL;
3282 else if (streq(optarg, "help")) {
3283 puts("full\n"
3284 "stripped\n"
3285 "minimal");
3286 return 0;
3287 }
3288
3289 break;
3290
3291 case ARG_AND_RESIZE:
3292 arg_and_resize = true;
3293 break;
3294
3295 case ARG_AND_CHANGE_PASSWORD:
3296 arg_and_change_password = true;
3297 break;
3298
3299 case '?':
3300 return -EINVAL;
3301
3302 default:
3303 assert_not_reached("Unhandled option");
3304 }
3305 }
3306
1c0c4a43 3307 if (!strv_isempty(arg_pkcs11_token_uri) || !strv_isempty(arg_fido2_device))
4aa0a8ac
LP
3308 arg_and_change_password = true;
3309
3310 if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX)
3311 arg_and_resize = true;
3312
3313 return 1;
3314}
3315
cc9886bc
LP
3316static int redirect_bus_mgr(void) {
3317 const char *suffix;
3318
3319 /* Talk to a different service if that's requested. (The same env var is also understood by homed, so
3320 * that it is relatively easily possible to invoke a second instance of homed for debug purposes and
3321 * have homectl talk to it, without colliding with the host version. This is handy when operating
3322 * from a homed-managed account.) */
3323
3324 suffix = getenv("SYSTEMD_HOME_DEBUG_SUFFIX");
3325 if (suffix) {
3326 static BusLocator locator = {
3327 .path = "/org/freedesktop/home1",
3328 .interface = "org.freedesktop.home1.Manager",
3329 };
3330
3331 /* Yes, we leak this memory, but there's little point to collect this, given that we only do
3332 * this in a debug environment, do it only once, and the string shall live for out entire
3333 * process runtime. */
3334
3335 locator.destination = strjoin("org.freedesktop.home1.", suffix);
3336 if (!locator.destination)
3337 return log_oom();
3338
3339 bus_mgr = &locator;
3340 } else
3341 bus_mgr = bus_home_mgr;
3342
3343 return 0;
3344}
3345
4aa0a8ac
LP
3346static int run(int argc, char *argv[]) {
3347 static const Verb verbs[] = {
d1f6e01e
LP
3348 { "help", VERB_ANY, VERB_ANY, 0, help },
3349 { "list", VERB_ANY, 1, VERB_DEFAULT, list_homes },
3350 { "activate", 2, VERB_ANY, 0, activate_home },
3351 { "deactivate", 2, VERB_ANY, 0, deactivate_home },
3352 { "inspect", VERB_ANY, VERB_ANY, 0, inspect_home },
3353 { "authenticate", VERB_ANY, VERB_ANY, 0, authenticate_home },
3354 { "create", VERB_ANY, 2, 0, create_home },
3355 { "remove", 2, VERB_ANY, 0, remove_home },
3356 { "update", VERB_ANY, 2, 0, update_home },
3357 { "passwd", VERB_ANY, 2, 0, passwd_home },
3358 { "resize", 2, 3, 0, resize_home },
3359 { "lock", 2, VERB_ANY, 0, lock_home },
3360 { "unlock", 2, VERB_ANY, 0, unlock_home },
3361 { "with", 2, VERB_ANY, 0, with_home },
3362 { "lock-all", VERB_ANY, 1, 0, lock_all_homes },
3363 { "deactivate-all", VERB_ANY, 1, 0, deactivate_all_homes },
4aa0a8ac
LP
3364 {}
3365 };
3366
3367 int r;
3368
41d1f469 3369 log_setup_cli();
4aa0a8ac 3370
cc9886bc
LP
3371 r = redirect_bus_mgr();
3372 if (r < 0)
3373 return r;
3374
4aa0a8ac
LP
3375 r = parse_argv(argc, argv);
3376 if (r <= 0)
3377 return r;
3378
3379 return dispatch_verb(argc, argv, verbs, NULL);
3380}
3381
3382DEFINE_MAIN_FUNCTION(run);