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