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