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