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