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