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