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