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