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