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