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