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