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