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