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