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