]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/user-record-util.c
Merge pull request #16981 from keszybz/use-crypt_ra
[thirdparty/systemd.git] / src / home / user-record-util.c
CommitLineData
70a5db58
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
9be99f81
LP
3#include <sys/xattr.h>
4
70a5db58
LP
5#include "errno-util.h"
6#include "home-util.h"
7#include "id128-util.h"
8#include "libcrypt-util.h"
aecbc87d
LP
9#include "memory-util.h"
10#include "modhex.h"
70a5db58
LP
11#include "mountpoint-util.h"
12#include "path-util.h"
13#include "stat-util.h"
14#include "user-record-util.h"
15#include "user-util.h"
16
17int user_record_synthesize(
18 UserRecord *h,
19 const char *user_name,
20 const char *realm,
21 const char *image_path,
22 UserStorage storage,
23 uid_t uid,
24 gid_t gid) {
25
26 _cleanup_free_ char *hd = NULL, *un = NULL, *ip = NULL, *rr = NULL, *user_name_and_realm = NULL;
27 char smid[SD_ID128_STRING_MAX];
28 sd_id128_t mid;
29 int r;
30
31 assert(h);
32 assert(user_name);
33 assert(image_path);
34 assert(IN_SET(storage, USER_LUKS, USER_SUBVOLUME, USER_FSCRYPT, USER_DIRECTORY));
35 assert(uid_is_valid(uid));
36 assert(gid_is_valid(gid));
37
38 /* Fill in a home record from just a username and an image path. */
39
40 if (h->json)
41 return -EBUSY;
42
43 if (!suitable_user_name(user_name))
44 return -EINVAL;
45
46 if (realm) {
47 r = suitable_realm(realm);
48 if (r < 0)
49 return r;
50 if (r == 0)
51 return -EINVAL;
52 }
53
54 if (!suitable_image_path(image_path))
55 return -EINVAL;
56
57 r = sd_id128_get_machine(&mid);
58 if (r < 0)
59 return r;
60
61 un = strdup(user_name);
62 if (!un)
63 return -ENOMEM;
64
65 if (realm) {
66 rr = strdup(realm);
67 if (!rr)
68 return -ENOMEM;
69
70 user_name_and_realm = strjoin(user_name, "@", realm);
71 if (!user_name_and_realm)
72 return -ENOMEM;
73 }
74
75 ip = strdup(image_path);
76 if (!ip)
77 return -ENOMEM;
78
79 hd = path_join("/home/", user_name);
80 if (!hd)
81 return -ENOMEM;
82
83 r = json_build(&h->json,
84 JSON_BUILD_OBJECT(
85 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(user_name)),
86 JSON_BUILD_PAIR_CONDITION(!!rr, "realm", JSON_BUILD_STRING(realm)),
87 JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("regular")),
88 JSON_BUILD_PAIR("binding", JSON_BUILD_OBJECT(
89 JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT(
90 JSON_BUILD_PAIR("imagePath", JSON_BUILD_STRING(image_path)),
91 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING(hd)),
92 JSON_BUILD_PAIR("storage", JSON_BUILD_STRING(user_storage_to_string(storage))),
93 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid)),
94 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid))))))));
95 if (r < 0)
96 return r;
97
98 free_and_replace(h->user_name, un);
99 free_and_replace(h->realm, rr);
100 free_and_replace(h->user_name_and_realm_auto, user_name_and_realm);
101 free_and_replace(h->image_path, ip);
102 free_and_replace(h->home_directory, hd);
103 h->storage = storage;
104 h->uid = uid;
105
106 h->mask = USER_RECORD_REGULAR|USER_RECORD_BINDING;
107 return 0;
108}
109
110int group_record_synthesize(GroupRecord *g, UserRecord *h) {
0a388dfc 111 _cleanup_free_ char *un = NULL, *rr = NULL, *group_name_and_realm = NULL, *description = NULL;
70a5db58
LP
112 char smid[SD_ID128_STRING_MAX];
113 sd_id128_t mid;
114 int r;
115
116 assert(g);
117 assert(h);
118
119 if (g->json)
120 return -EBUSY;
121
122 r = sd_id128_get_machine(&mid);
123 if (r < 0)
124 return r;
125
126 un = strdup(h->user_name);
127 if (!un)
128 return -ENOMEM;
129
130 if (h->realm) {
131 rr = strdup(h->realm);
132 if (!rr)
133 return -ENOMEM;
134
135 group_name_and_realm = strjoin(un, "@", rr);
136 if (!group_name_and_realm)
137 return -ENOMEM;
138 }
139
0a388dfc
LP
140 description = strjoin("Primary Group of User ", un);
141 if (!description)
142 return -ENOMEM;
143
70a5db58
LP
144 r = json_build(&g->json,
145 JSON_BUILD_OBJECT(
146 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(un)),
147 JSON_BUILD_PAIR_CONDITION(!!rr, "realm", JSON_BUILD_STRING(rr)),
0a388dfc 148 JSON_BUILD_PAIR("description", JSON_BUILD_STRING(description)),
70a5db58
LP
149 JSON_BUILD_PAIR("binding", JSON_BUILD_OBJECT(
150 JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT(
151 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(user_record_gid(h))))))),
152 JSON_BUILD_PAIR_CONDITION(h->disposition >= 0, "disposition", JSON_BUILD_STRING(user_disposition_to_string(user_record_disposition(h)))),
153 JSON_BUILD_PAIR("status", JSON_BUILD_OBJECT(
154 JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT(
155 JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.Home"))))))));
156 if (r < 0)
157 return r;
158
159 free_and_replace(g->group_name, un);
160 free_and_replace(g->realm, rr);
161 free_and_replace(g->group_name_and_realm_auto, group_name_and_realm);
162 g->gid = user_record_gid(h);
163 g->disposition = h->disposition;
164
165 g->mask = USER_RECORD_REGULAR|USER_RECORD_BINDING;
166 return 0;
167}
168
169int user_record_reconcile(
170 UserRecord *host,
171 UserRecord *embedded,
172 UserReconcileMode mode,
173 UserRecord **ret) {
174
175 int r, result;
176
177 /* Reconciles the identity record stored on the host with the one embedded in a $HOME
178 * directory. Returns the following error codes:
179 *
180 * -EINVAL: one of the records not valid
181 * -REMCHG: identity records are not about the same user
182 * -ESTALE: embedded identity record is equally new or newer than supplied record
183 *
37b22b3b 184 * Return the new record to use, which is either the embedded record updated with the host
70a5db58
LP
185 * binding or the host record. In both cases the secret data is stripped. */
186
187 assert(host);
188 assert(embedded);
189
190 /* Make sure both records are initialized */
191 if (!host->json || !embedded->json)
192 return -EINVAL;
193
194 /* Ensure these records actually contain user data */
195 if (!(embedded->mask & host->mask & USER_RECORD_REGULAR))
196 return -EINVAL;
197
198 /* Make sure the user name and realm matches */
199 if (!user_record_compatible(host, embedded))
200 return -EREMCHG;
201
202 /* Embedded identities may not contain secrets or binding info*/
203 if ((embedded->mask & (USER_RECORD_SECRET|USER_RECORD_BINDING)) != 0)
204 return -EINVAL;
205
206 /* The embedded record checked out, let's now figure out which of the two identities we'll consider
207 * in effect from now on. We do this by checking the last change timestamp, and in doubt always let
208 * the embedded data win. */
209 if (host->last_change_usec != UINT64_MAX &&
210 (embedded->last_change_usec == UINT64_MAX || host->last_change_usec > embedded->last_change_usec))
211
212 /* The host version is definitely newer, either because it has a version at all and the
213 * embedded version doesn't or because it is numerically newer. */
214 result = USER_RECONCILE_HOST_WON;
215
216 else if (host->last_change_usec == embedded->last_change_usec) {
217
218 /* The nominal version number of the host and the embedded identity is the same. If so, let's
219 * verify that, and tell the caller if we are ignoring embedded data. */
220
221 r = user_record_masked_equal(host, embedded, USER_RECORD_REGULAR|USER_RECORD_PRIVILEGED|USER_RECORD_PER_MACHINE);
222 if (r < 0)
223 return r;
224 if (r > 0) {
225 if (mode == USER_RECONCILE_REQUIRE_NEWER)
226 return -ESTALE;
227
228 result = USER_RECONCILE_IDENTICAL;
229 } else
230 result = USER_RECONCILE_HOST_WON;
231 } else {
232 _cleanup_(json_variant_unrefp) JsonVariant *extended = NULL;
233 _cleanup_(user_record_unrefp) UserRecord *merged = NULL;
234 JsonVariant *e;
235
236 /* The embedded version is newer */
237
238 if (mode == USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL)
239 return -ESTALE;
240
241 /* Copy in the binding data */
242 extended = json_variant_ref(embedded->json);
243
244 e = json_variant_by_key(host->json, "binding");
245 if (e) {
246 r = json_variant_set_field(&extended, "binding", e);
247 if (r < 0)
248 return r;
249 }
250
251 merged = user_record_new();
252 if (!merged)
253 return -ENOMEM;
254
255 r = user_record_load(merged, extended, USER_RECORD_LOAD_MASK_SECRET);
256 if (r < 0)
257 return r;
258
259 *ret = TAKE_PTR(merged);
260 return USER_RECONCILE_EMBEDDED_WON; /* update */
261 }
262
263 /* Strip out secrets */
264 r = user_record_clone(host, USER_RECORD_LOAD_MASK_SECRET, ret);
265 if (r < 0)
266 return r;
267
268 return result;
269}
270
271int user_record_add_binding(
272 UserRecord *h,
273 UserStorage storage,
274 const char *image_path,
275 sd_id128_t partition_uuid,
276 sd_id128_t luks_uuid,
277 sd_id128_t fs_uuid,
278 const char *luks_cipher,
279 const char *luks_cipher_mode,
280 uint64_t luks_volume_key_size,
281 const char *file_system_type,
282 const char *home_directory,
283 uid_t uid,
284 gid_t gid) {
285
286 _cleanup_(json_variant_unrefp) JsonVariant *new_binding_entry = NULL, *binding = NULL;
11579a95 287 char smid[SD_ID128_STRING_MAX], partition_uuids[ID128_UUID_STRING_MAX], luks_uuids[ID128_UUID_STRING_MAX], fs_uuids[ID128_UUID_STRING_MAX];
0d5e5234 288 _cleanup_free_ char *ip = NULL, *hd = NULL, *ip_auto = NULL, *lc = NULL, *lcm = NULL, *fst = NULL;
70a5db58
LP
289 sd_id128_t mid;
290 int r;
291
292 assert(h);
293
294 if (!h->json)
295 return -EUNATCH;
296
297 r = sd_id128_get_machine(&mid);
298 if (r < 0)
299 return r;
300 sd_id128_to_string(mid, smid);
301
302 if (image_path) {
303 ip = strdup(image_path);
304 if (!ip)
305 return -ENOMEM;
0d5e5234
LP
306 } else if (!h->image_path && storage >= 0) {
307 r = user_record_build_image_path(storage, user_record_user_name_and_realm(h), &ip_auto);
308 if (r < 0)
309 return r;
70a5db58
LP
310 }
311
312 if (home_directory) {
313 hd = strdup(home_directory);
314 if (!hd)
315 return -ENOMEM;
316 }
317
0d5e5234
LP
318 if (file_system_type) {
319 fst = strdup(file_system_type);
320 if (!fst)
321 return -ENOMEM;
322 }
323
324 if (luks_cipher) {
325 lc = strdup(luks_cipher);
326 if (!lc)
327 return -ENOMEM;
328 }
329
330 if (luks_cipher_mode) {
331 lcm = strdup(luks_cipher_mode);
332 if (!lcm)
333 return -ENOMEM;
334 }
335
70a5db58
LP
336 r = json_build(&new_binding_entry,
337 JSON_BUILD_OBJECT(
338 JSON_BUILD_PAIR_CONDITION(!!image_path, "imagePath", JSON_BUILD_STRING(image_path)),
339 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(partition_uuid), "partitionUuid", JSON_BUILD_STRING(id128_to_uuid_string(partition_uuid, partition_uuids))),
340 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(luks_uuid), "luksUuid", JSON_BUILD_STRING(id128_to_uuid_string(luks_uuid, luks_uuids))),
341 JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(fs_uuid), "fileSystemUuid", JSON_BUILD_STRING(id128_to_uuid_string(fs_uuid, fs_uuids))),
342 JSON_BUILD_PAIR_CONDITION(!!luks_cipher, "luksCipher", JSON_BUILD_STRING(luks_cipher)),
343 JSON_BUILD_PAIR_CONDITION(!!luks_cipher_mode, "luksCipherMode", JSON_BUILD_STRING(luks_cipher_mode)),
344 JSON_BUILD_PAIR_CONDITION(luks_volume_key_size != UINT64_MAX, "luksVolumeKeySize", JSON_BUILD_UNSIGNED(luks_volume_key_size)),
345 JSON_BUILD_PAIR_CONDITION(!!file_system_type, "fileSystemType", JSON_BUILD_STRING(file_system_type)),
346 JSON_BUILD_PAIR_CONDITION(!!home_directory, "homeDirectory", JSON_BUILD_STRING(home_directory)),
347 JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid)),
348 JSON_BUILD_PAIR_CONDITION(gid_is_valid(gid), "gid", JSON_BUILD_UNSIGNED(gid)),
349 JSON_BUILD_PAIR_CONDITION(storage >= 0, "storage", JSON_BUILD_STRING(user_storage_to_string(storage)))));
350 if (r < 0)
351 return r;
352
353 binding = json_variant_ref(json_variant_by_key(h->json, "binding"));
354 if (binding) {
355 _cleanup_(json_variant_unrefp) JsonVariant *be = NULL;
356
357 /* Merge the new entry with an old one, if that exists */
358 be = json_variant_ref(json_variant_by_key(binding, smid));
359 if (be) {
360 r = json_variant_merge(&be, new_binding_entry);
361 if (r < 0)
362 return r;
363
364 json_variant_unref(new_binding_entry);
365 new_binding_entry = TAKE_PTR(be);
366 }
367 }
368
369 r = json_variant_set_field(&binding, smid, new_binding_entry);
370 if (r < 0)
371 return r;
372
373 r = json_variant_set_field(&h->json, "binding", binding);
374 if (r < 0)
375 return r;
376
377 if (storage >= 0)
378 h->storage = storage;
379
380 if (ip)
381 free_and_replace(h->image_path, ip);
0d5e5234
LP
382 if (ip_auto)
383 free_and_replace(h->image_path_auto, ip_auto);
70a5db58
LP
384
385 if (!sd_id128_is_null(partition_uuid))
386 h->partition_uuid = partition_uuid;
387
388 if (!sd_id128_is_null(luks_uuid))
389 h->luks_uuid = luks_uuid;
390
391 if (!sd_id128_is_null(fs_uuid))
392 h->file_system_uuid = fs_uuid;
393
0d5e5234
LP
394 if (lc)
395 free_and_replace(h->luks_cipher, lc);
396 if (lcm)
397 free_and_replace(h->luks_cipher_mode, lcm);
398 if (luks_volume_key_size != UINT64_MAX)
399 h->luks_volume_key_size = luks_volume_key_size;
400
401 if (fst)
402 free_and_replace(h->file_system_type, fst);
70a5db58
LP
403 if (hd)
404 free_and_replace(h->home_directory, hd);
405
406 if (uid_is_valid(uid))
407 h->uid = uid;
0d5e5234
LP
408 if (gid_is_valid(gid))
409 h->gid = gid;
70a5db58
LP
410
411 h->mask |= USER_RECORD_BINDING;
412 return 1;
413}
414
415int user_record_test_home_directory(UserRecord *h) {
416 const char *hd;
417 int r;
418
419 assert(h);
420
421 /* Returns one of USER_TEST_ABSENT, USER_TEST_MOUNTED, USER_TEST_EXISTS on success */
422
423 hd = user_record_home_directory(h);
424 if (!hd)
425 return -ENXIO;
426
427 r = is_dir(hd, false);
428 if (r == -ENOENT)
429 return USER_TEST_ABSENT;
430 if (r < 0)
431 return r;
432 if (r == 0)
433 return -ENOTDIR;
434
435 r = path_is_mount_point(hd, NULL, 0);
436 if (r < 0)
437 return r;
438 if (r > 0)
439 return USER_TEST_MOUNTED;
440
441 /* If the image path and the home directory are identical, then it's OK if the directory is
442 * populated. */
443 if (IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT)) {
444 const char *ip;
445
446 ip = user_record_image_path(h);
447 if (ip && path_equal(ip, hd))
448 return USER_TEST_EXISTS;
449 }
450
451 /* Otherwise it's not OK */
452 r = dir_is_empty(hd);
453 if (r < 0)
454 return r;
455 if (r == 0)
456 return -EBUSY;
457
458 return USER_TEST_EXISTS;
459}
460
461int user_record_test_home_directory_and_warn(UserRecord *h) {
462 int r;
463
464 assert(h);
465
466 r = user_record_test_home_directory(h);
467 if (r == -ENXIO)
468 return log_error_errno(r, "User record lacks home directory, refusing.");
469 if (r == -ENOTDIR)
470 return log_error_errno(r, "Home directory %s is not a directory, refusing.", user_record_home_directory(h));
471 if (r == -EBUSY)
472 return log_error_errno(r, "Home directory %s exists, is not mounted but populated, refusing.", user_record_home_directory(h));
473 if (r < 0)
474 return log_error_errno(r, "Failed to test whether the home directory %s exists: %m", user_record_home_directory(h));
475
476 return r;
477}
478
479int user_record_test_image_path(UserRecord *h) {
480 const char *ip;
481 struct stat st;
482
483 assert(h);
484
485 if (user_record_storage(h) == USER_CIFS)
486 return USER_TEST_UNDEFINED;
487
488 ip = user_record_image_path(h);
489 if (!ip)
490 return -ENXIO;
491
492 if (stat(ip, &st) < 0) {
493 if (errno == ENOENT)
494 return USER_TEST_ABSENT;
495
496 return -errno;
497 }
498
499 switch (user_record_storage(h)) {
500
501 case USER_LUKS:
9be99f81
LP
502 if (S_ISREG(st.st_mode)) {
503 ssize_t n;
504 char x[2];
505
506 n = getxattr(ip, "user.home-dirty", x, sizeof(x));
507 if (n < 0) {
508 if (errno != ENODATA)
509 log_debug_errno(errno, "Unable to read dirty xattr off image file, ignoring: %m");
510
511 } else if (n == 1 && x[0] == '1')
512 return USER_TEST_DIRTY;
513
70a5db58 514 return USER_TEST_EXISTS;
9be99f81
LP
515 }
516
70a5db58
LP
517 if (S_ISBLK(st.st_mode)) {
518 /* For block devices we can't really be sure if the device referenced actually is the
519 * fs we look for or some other file system (think: what does /dev/sdb1 refer
162392b7 520 * to?). Hence, let's return USER_TEST_MAYBE as an ambiguous return value for these
70a5db58
LP
521 * case, except if the device path used is one of the paths that is based on a
522 * filesystem or partition UUID or label, because in those cases we can be sure we
523 * are referring to the right device. */
524
525 if (PATH_STARTSWITH_SET(ip,
526 "/dev/disk/by-uuid/",
527 "/dev/disk/by-partuuid/",
528 "/dev/disk/by-partlabel/",
529 "/dev/disk/by-label/"))
530 return USER_TEST_EXISTS;
531
532 return USER_TEST_MAYBE;
533 }
534
535 return -EBADFD;
536
537 case USER_CLASSIC:
538 case USER_DIRECTORY:
539 case USER_SUBVOLUME:
540 case USER_FSCRYPT:
541 if (S_ISDIR(st.st_mode))
542 return USER_TEST_EXISTS;
543
544 return -ENOTDIR;
545
546 default:
547 assert_not_reached("Unexpected record type");
548 }
549}
550
551int user_record_test_image_path_and_warn(UserRecord *h) {
552 int r;
553
554 assert(h);
555
556 r = user_record_test_image_path(h);
557 if (r == -ENXIO)
558 return log_error_errno(r, "User record lacks image path, refusing.");
559 if (r == -EBADFD)
560 return log_error_errno(r, "Image path %s is not a regular file or block device, refusing.", user_record_image_path(h));
561 if (r == -ENOTDIR)
562 return log_error_errno(r, "Image path %s is not a directory, refusing.", user_record_image_path(h));
563 if (r < 0)
564 return log_error_errno(r, "Failed to test whether image path %s exists: %m", user_record_image_path(h));
565
566 return r;
567}
568
87d7893c 569int user_record_test_password(UserRecord *h, UserRecord *secret) {
70a5db58
LP
570 char **i;
571 int r;
572
573 assert(h);
574
575 /* Checks whether any of the specified passwords matches any of the hashed passwords of the entry */
576
577 if (strv_isempty(h->hashed_password))
578 return -ENXIO;
579
580 STRV_FOREACH(i, secret->password) {
581 r = test_password_many(h->hashed_password, *i);
582 if (r < 0)
583 return r;
584 if (r > 0)
585 return 0;
586 }
587
588 return -ENOKEY;
589}
590
aecbc87d
LP
591int user_record_test_recovery_key(UserRecord *h, UserRecord *secret) {
592 char **i;
593 int r;
594
595 assert(h);
596
597 /* Checks whether any of the specified passwords matches any of the hashed recovery keys of the entry */
598
599 if (h->n_recovery_key == 0)
600 return -ENXIO;
601
602 STRV_FOREACH(i, secret->password) {
603 for (size_t j = 0; j < h->n_recovery_key; j++) {
604 _cleanup_(erase_and_freep) char *mangled = NULL;
605 const char *p;
606
607 if (streq(h->recovery_key[j].type, "modhex64")) {
608 /* If this key is for a modhex64 recovery key, then try to normalize the
609 * passphrase to make things more robust: that way the password becomes case
610 * insensitive and the dashes become optional. */
611
612 r = normalize_recovery_key(*i, &mangled);
613 if (r == -EINVAL) /* Not a valid modhex64 passphrase, don't bother */
614 continue;
615 if (r < 0)
616 return r;
617
618 p = mangled;
619 } else
620 p = *i; /* Unknown recovery key types process as is */
621
622 r = test_password_one(h->recovery_key[j].hashed_password, p);
623 if (r < 0)
624 return r;
625 if (r > 0)
626 return 0;
627 }
628 }
629
630 return -ENOKEY;
631}
632
70a5db58
LP
633int user_record_set_disk_size(UserRecord *h, uint64_t disk_size) {
634 _cleanup_(json_variant_unrefp) JsonVariant *new_per_machine = NULL, *midv = NULL, *midav = NULL, *ne = NULL;
635 _cleanup_free_ JsonVariant **array = NULL;
636 char smid[SD_ID128_STRING_MAX];
637 size_t idx = SIZE_MAX, n;
638 JsonVariant *per_machine;
639 sd_id128_t mid;
640 int r;
641
642 assert(h);
643
644 if (!h->json)
645 return -EUNATCH;
646
647 if (disk_size < USER_DISK_SIZE_MIN || disk_size > USER_DISK_SIZE_MAX)
648 return -ERANGE;
649
650 r = sd_id128_get_machine(&mid);
651 if (r < 0)
652 return r;
653
654 sd_id128_to_string(mid, smid);
655
656 r = json_variant_new_string(&midv, smid);
657 if (r < 0)
658 return r;
659
660 r = json_variant_new_array(&midav, (JsonVariant*[]) { midv }, 1);
661 if (r < 0)
662 return r;
663
664 per_machine = json_variant_by_key(h->json, "perMachine");
665 if (per_machine) {
666 size_t i;
667
668 if (!json_variant_is_array(per_machine))
669 return -EINVAL;
670
671 n = json_variant_elements(per_machine);
672
673 array = new(JsonVariant*, n + 1);
674 if (!array)
675 return -ENOMEM;
676
677 for (i = 0; i < n; i++) {
678 JsonVariant *m;
679
680 array[i] = json_variant_by_index(per_machine, i);
681
682 if (!json_variant_is_object(array[i]))
683 return -EINVAL;
684
685 m = json_variant_by_key(array[i], "matchMachineId");
686 if (!m) {
687 /* No machineId field? Let's ignore this, but invalidate what we found so far */
688 idx = SIZE_MAX;
689 continue;
690 }
691
692 if (json_variant_equal(m, midv) ||
693 json_variant_equal(m, midav)) {
694 /* Matches exactly what we are looking for. Let's use this */
695 idx = i;
696 continue;
697 }
698
699 r = per_machine_id_match(m, JSON_PERMISSIVE);
700 if (r < 0)
701 return r;
702 if (r > 0)
703 /* Also matches what we are looking for, but with a broader match. In this
704 * case let's ignore this entry, and add a new specific one to the end. */
705 idx = SIZE_MAX;
706 }
707
708 if (idx == SIZE_MAX)
709 idx = n++; /* Nothing suitable found, place new entry at end */
710 else
711 ne = json_variant_ref(array[idx]);
712
713 } else {
714 array = new(JsonVariant*, 1);
715 if (!array)
716 return -ENOMEM;
717
718 idx = 0;
719 n = 1;
720 }
721
722 if (!ne) {
723 r = json_variant_set_field(&ne, "matchMachineId", midav);
724 if (r < 0)
725 return r;
726 }
727
728 r = json_variant_set_field_unsigned(&ne, "diskSize", disk_size);
729 if (r < 0)
730 return r;
731
732 assert(idx < n);
733 array[idx] = ne;
734
735 r = json_variant_new_array(&new_per_machine, array, n);
736 if (r < 0)
737 return r;
738
739 r = json_variant_set_field(&h->json, "perMachine", new_per_machine);
740 if (r < 0)
741 return r;
742
743 h->disk_size = disk_size;
744 h->mask |= USER_RECORD_PER_MACHINE;
745 return 0;
746}
747
748int user_record_update_last_changed(UserRecord *h, bool with_password) {
749 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
750 usec_t n;
751 int r;
752
753 assert(h);
754
755 if (!h->json)
756 return -EUNATCH;
757
758 n = now(CLOCK_REALTIME);
759
760 /* refuse downgrading */
761 if (h->last_change_usec != UINT64_MAX && h->last_change_usec >= n)
762 return -ECHRNG;
763 if (h->last_password_change_usec != UINT64_MAX && h->last_password_change_usec >= n)
764 return -ECHRNG;
765
766 v = json_variant_ref(h->json);
767
768 r = json_variant_set_field_unsigned(&v, "lastChangeUSec", n);
769 if (r < 0)
770 return r;
771
772 if (with_password) {
773 r = json_variant_set_field_unsigned(&v, "lastPasswordChangeUSec", n);
774 if (r < 0)
775 return r;
776
777 h->last_password_change_usec = n;
778 }
779
780 h->last_change_usec = n;
781
782 json_variant_unref(h->json);
783 h->json = TAKE_PTR(v);
784
785 h->mask |= USER_RECORD_REGULAR;
786 return 0;
787}
788
789int user_record_make_hashed_password(UserRecord *h, char **secret, bool extend) {
790 _cleanup_(json_variant_unrefp) JsonVariant *priv = NULL;
791 _cleanup_strv_free_ char **np = NULL;
792 char **i;
793 int r;
794
795 assert(h);
796 assert(secret);
797
798 /* Initializes the hashed password list from the specified plaintext passwords */
799
800 if (extend) {
801 np = strv_copy(h->hashed_password);
802 if (!np)
803 return -ENOMEM;
804
805 strv_uniq(np);
806 }
807
808 STRV_FOREACH(i, secret) {
0e98d17e 809 _cleanup_(erase_and_freep) char *hashed = NULL;
70a5db58 810
0e98d17e 811 r = hash_password(*i, &hashed);
70a5db58
LP
812 if (r < 0)
813 return r;
814
0e98d17e 815 r = strv_consume(&np, TAKE_PTR(hashed));
70a5db58
LP
816 if (r < 0)
817 return r;
818 }
819
820 priv = json_variant_ref(json_variant_by_key(h->json, "privileged"));
821
822 if (strv_isempty(np))
823 r = json_variant_filter(&priv, STRV_MAKE("hashedPassword"));
824 else {
825 _cleanup_(json_variant_unrefp) JsonVariant *new_array = NULL;
826
827 r = json_variant_new_array_strv(&new_array, np);
828 if (r < 0)
829 return r;
830
831 r = json_variant_set_field(&priv, "hashedPassword", new_array);
02cec156
YW
832 if (r < 0)
833 return r;
70a5db58
LP
834 }
835
836 r = json_variant_set_field(&h->json, "privileged", priv);
837 if (r < 0)
838 return r;
839
840 strv_free_and_replace(h->hashed_password, np);
841
842 SET_FLAG(h->mask, USER_RECORD_PRIVILEGED, !json_variant_is_blank_object(priv));
843 return 0;
844}
845
846int user_record_set_hashed_password(UserRecord *h, char **hashed_password) {
847 _cleanup_(json_variant_unrefp) JsonVariant *priv = NULL;
848 _cleanup_strv_free_ char **copy = NULL;
849 int r;
850
851 assert(h);
852
853 priv = json_variant_ref(json_variant_by_key(h->json, "privileged"));
854
855 if (strv_isempty(hashed_password))
856 r = json_variant_filter(&priv, STRV_MAKE("hashedPassword"));
857 else {
858 _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
859
860 copy = strv_copy(hashed_password);
861 if (!copy)
862 return -ENOMEM;
863
864 strv_uniq(copy);
865
866 r = json_variant_new_array_strv(&array, copy);
867 if (r < 0)
868 return r;
869
870 r = json_variant_set_field(&priv, "hashedPassword", array);
871 }
872 if (r < 0)
873 return r;
874
875 r = json_variant_set_field(&h->json, "privileged", priv);
876 if (r < 0)
877 return r;
878
879 strv_free_and_replace(h->hashed_password, copy);
880
881 SET_FLAG(h->mask, USER_RECORD_PRIVILEGED, !json_variant_is_blank_object(priv));
882 return 0;
883}
884
885int user_record_set_password(UserRecord *h, char **password, bool prepend) {
886 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
887 _cleanup_(strv_free_erasep) char **e = NULL;
888 int r;
889
890 assert(h);
891
892 if (prepend) {
893 e = strv_copy(password);
894 if (!e)
895 return -ENOMEM;
896
897 r = strv_extend_strv(&e, h->password, true);
898 if (r < 0)
899 return r;
900
901 strv_uniq(e);
902
903 if (strv_equal(h->password, e))
904 return 0;
905
906 } else {
907 if (strv_equal(h->password, password))
908 return 0;
909
910 e = strv_copy(password);
911 if (!e)
912 return -ENOMEM;
913
914 strv_uniq(e);
915 }
916
917 w = json_variant_ref(json_variant_by_key(h->json, "secret"));
918
919 if (strv_isempty(e))
920 r = json_variant_filter(&w, STRV_MAKE("password"));
921 else {
922 _cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
923
924 r = json_variant_new_array_strv(&l, e);
925 if (r < 0)
926 return r;
927
928 json_variant_sensitive(l);
929
930 r = json_variant_set_field(&w, "password", l);
931 }
932 if (r < 0)
933 return r;
934
560a3e5d
LP
935 json_variant_sensitive(w);
936
70a5db58
LP
937 r = json_variant_set_field(&h->json, "secret", w);
938 if (r < 0)
939 return r;
940
941 strv_free_and_replace(h->password, e);
942
943 SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
944 return 0;
945}
946
c0bde0d2 947int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend) {
70a5db58
LP
948 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
949 _cleanup_(strv_free_erasep) char **e = NULL;
950 int r;
951
952 assert(h);
953
954 if (prepend) {
955 e = strv_copy(pin);
956 if (!e)
957 return -ENOMEM;
958
c0bde0d2 959 r = strv_extend_strv(&e, h->token_pin, true);
70a5db58
LP
960 if (r < 0)
961 return r;
962
963 strv_uniq(e);
964
c0bde0d2 965 if (strv_equal(h->token_pin, e))
70a5db58
LP
966 return 0;
967
968 } else {
c0bde0d2 969 if (strv_equal(h->token_pin, pin))
70a5db58
LP
970 return 0;
971
972 e = strv_copy(pin);
973 if (!e)
974 return -ENOMEM;
975
976 strv_uniq(e);
977 }
978
979 w = json_variant_ref(json_variant_by_key(h->json, "secret"));
980
981 if (strv_isempty(e))
c0bde0d2 982 r = json_variant_filter(&w, STRV_MAKE("tokenPin"));
70a5db58
LP
983 else {
984 _cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
985
986 r = json_variant_new_array_strv(&l, e);
987 if (r < 0)
988 return r;
989
990 json_variant_sensitive(l);
991
c0bde0d2 992 r = json_variant_set_field(&w, "tokenPin", l);
70a5db58
LP
993 }
994 if (r < 0)
995 return r;
996
560a3e5d
LP
997 json_variant_sensitive(w);
998
70a5db58
LP
999 r = json_variant_set_field(&h->json, "secret", w);
1000 if (r < 0)
1001 return r;
1002
c0bde0d2 1003 strv_free_and_replace(h->token_pin, e);
70a5db58
LP
1004
1005 SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
1006 return 0;
1007}
1008
1009int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h, int b) {
1010 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
1011 int r;
1012
1013 assert(h);
1014
1015 w = json_variant_ref(json_variant_by_key(h->json, "secret"));
1016
1017 if (b < 0)
1018 r = json_variant_filter(&w, STRV_MAKE("pkcs11ProtectedAuthenticationPathPermitted"));
1019 else
1020 r = json_variant_set_field_boolean(&w, "pkcs11ProtectedAuthenticationPathPermitted", b);
1021 if (r < 0)
1022 return r;
1023
1024 if (json_variant_is_blank_object(w))
1025 r = json_variant_filter(&h->json, STRV_MAKE("secret"));
560a3e5d
LP
1026 else {
1027 json_variant_sensitive(w);
1028
70a5db58 1029 r = json_variant_set_field(&h->json, "secret", w);
560a3e5d 1030 }
70a5db58
LP
1031 if (r < 0)
1032 return r;
1033
1034 h->pkcs11_protected_authentication_path_permitted = b;
1035
1036 SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
1037 return 0;
1038}
1039
7b78db28
LP
1040int user_record_set_fido2_user_presence_permitted(UserRecord *h, int b) {
1041 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
1042 int r;
1043
1044 assert(h);
1045
1046 w = json_variant_ref(json_variant_by_key(h->json, "secret"));
1047
1048 if (b < 0)
1049 r = json_variant_filter(&w, STRV_MAKE("fido2UserPresencePermitted"));
1050 else
1051 r = json_variant_set_field_boolean(&w, "fido2UserPresencePermitted", b);
1052 if (r < 0)
1053 return r;
1054
1055 if (json_variant_is_blank_object(w))
1056 r = json_variant_filter(&h->json, STRV_MAKE("secret"));
1057 else
1058 r = json_variant_set_field(&h->json, "secret", w);
1059 if (r < 0)
1060 return r;
1061
1062 h->fido2_user_presence_permitted = b;
1063
1064 SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
1065 return 0;
1066}
1067
70a5db58
LP
1068static bool per_machine_entry_empty(JsonVariant *v) {
1069 const char *k;
1070 _unused_ JsonVariant *e;
1071
1072 JSON_VARIANT_OBJECT_FOREACH(k, e, v)
1073 if (!STR_IN_SET(k, "matchMachineId", "matchHostname"))
1074 return false;
1075
1076 return true;
1077}
1078
1079int user_record_set_password_change_now(UserRecord *h, int b) {
1080 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
1081 JsonVariant *per_machine;
1082 int r;
1083
1084 assert(h);
1085
1086 w = json_variant_ref(h->json);
1087
1088 if (b < 0)
1089 r = json_variant_filter(&w, STRV_MAKE("passwordChangeNow"));
1090 else
1091 r = json_variant_set_field_boolean(&w, "passwordChangeNow", b);
1092 if (r < 0)
1093 return r;
1094
1095 /* Also drop the field from all perMachine entries */
1096 per_machine = json_variant_by_key(w, "perMachine");
1097 if (per_machine) {
1098 _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
1099 JsonVariant *e;
1100
1101 JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
1102 _cleanup_(json_variant_unrefp) JsonVariant *z = NULL;
1103
1104 if (!json_variant_is_object(e))
1105 return -EINVAL;
1106
1107 z = json_variant_ref(e);
1108
1109 r = json_variant_filter(&z, STRV_MAKE("passwordChangeNow"));
1110 if (r < 0)
1111 return r;
1112
1113 if (per_machine_entry_empty(z))
1114 continue;
1115
1116 r = json_variant_append_array(&array, z);
1117 if (r < 0)
1118 return r;
1119 }
1120
1121 if (json_variant_is_blank_array(array))
1122 r = json_variant_filter(&w, STRV_MAKE("perMachine"));
1123 else
1124 r = json_variant_set_field(&w, "perMachine", array);
1125 if (r < 0)
1126 return r;
1127
1128 SET_FLAG(h->mask, USER_RECORD_PER_MACHINE, !json_variant_is_blank_array(array));
1129 }
1130
1131 json_variant_unref(h->json);
1132 h->json = TAKE_PTR(w);
1133
1134 h->password_change_now = b;
1135
1136 return 0;
1137}
1138
1139int user_record_merge_secret(UserRecord *h, UserRecord *secret) {
1140 int r;
1141
1142 assert(h);
1143
1144 /* Merges the secrets from 'secret' into 'h'. */
1145
1146 r = user_record_set_password(h, secret->password, true);
1147 if (r < 0)
1148 return r;
1149
c0bde0d2 1150 r = user_record_set_token_pin(h, secret->token_pin, true);
70a5db58
LP
1151 if (r < 0)
1152 return r;
1153
1154 if (secret->pkcs11_protected_authentication_path_permitted >= 0) {
7b78db28
LP
1155 r = user_record_set_pkcs11_protected_authentication_path_permitted(
1156 h,
1157 secret->pkcs11_protected_authentication_path_permitted);
1158 if (r < 0)
1159 return r;
1160 }
1161
1162 if (secret->fido2_user_presence_permitted >= 0) {
1163 r = user_record_set_fido2_user_presence_permitted(
1164 h,
1165 secret->fido2_user_presence_permitted);
70a5db58
LP
1166 if (r < 0)
1167 return r;
1168 }
1169
1170 return 0;
1171}
1172
1173int user_record_good_authentication(UserRecord *h) {
1174 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL, *z = NULL;
1175 char buf[SD_ID128_STRING_MAX];
1176 uint64_t counter, usec;
1177 sd_id128_t mid;
1178 int r;
1179
1180 assert(h);
1181
1182 switch (h->good_authentication_counter) {
1183 case UINT64_MAX:
1184 counter = 1;
1185 break;
1186 case UINT64_MAX-1:
1187 counter = h->good_authentication_counter; /* saturate */
1188 break;
1189 default:
1190 counter = h->good_authentication_counter + 1;
1191 break;
1192 }
1193
1194 usec = now(CLOCK_REALTIME);
1195
1196 r = sd_id128_get_machine(&mid);
1197 if (r < 0)
1198 return r;
1199
1200 v = json_variant_ref(h->json);
1201 w = json_variant_ref(json_variant_by_key(v, "status"));
1202 z = json_variant_ref(json_variant_by_key(w, sd_id128_to_string(mid, buf)));
1203
1204 r = json_variant_set_field_unsigned(&z, "goodAuthenticationCounter", counter);
1205 if (r < 0)
1206 return r;
1207
1208 r = json_variant_set_field_unsigned(&z, "lastGoodAuthenticationUSec", usec);
1209 if (r < 0)
1210 return r;
1211
1212 r = json_variant_set_field(&w, buf, z);
1213 if (r < 0)
1214 return r;
1215
1216 r = json_variant_set_field(&v, "status", w);
1217 if (r < 0)
1218 return r;
1219
1220 json_variant_unref(h->json);
1221 h->json = TAKE_PTR(v);
1222
1223 h->good_authentication_counter = counter;
1224 h->last_good_authentication_usec = usec;
1225
1226 h->mask |= USER_RECORD_STATUS;
1227 return 0;
1228}
1229
1230int user_record_bad_authentication(UserRecord *h) {
1231 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL, *z = NULL;
1232 char buf[SD_ID128_STRING_MAX];
1233 uint64_t counter, usec;
1234 sd_id128_t mid;
1235 int r;
1236
1237 assert(h);
1238
1239 switch (h->bad_authentication_counter) {
1240 case UINT64_MAX:
1241 counter = 1;
1242 break;
1243 case UINT64_MAX-1:
1244 counter = h->bad_authentication_counter; /* saturate */
1245 break;
1246 default:
1247 counter = h->bad_authentication_counter + 1;
1248 break;
1249 }
1250
1251 usec = now(CLOCK_REALTIME);
1252
1253 r = sd_id128_get_machine(&mid);
1254 if (r < 0)
1255 return r;
1256
1257 v = json_variant_ref(h->json);
1258 w = json_variant_ref(json_variant_by_key(v, "status"));
1259 z = json_variant_ref(json_variant_by_key(w, sd_id128_to_string(mid, buf)));
1260
1261 r = json_variant_set_field_unsigned(&z, "badAuthenticationCounter", counter);
1262 if (r < 0)
1263 return r;
1264
1265 r = json_variant_set_field_unsigned(&z, "lastBadAuthenticationUSec", usec);
1266 if (r < 0)
1267 return r;
1268
1269 r = json_variant_set_field(&w, buf, z);
1270 if (r < 0)
1271 return r;
1272
1273 r = json_variant_set_field(&v, "status", w);
1274 if (r < 0)
1275 return r;
1276
1277 json_variant_unref(h->json);
1278 h->json = TAKE_PTR(v);
1279
1280 h->bad_authentication_counter = counter;
1281 h->last_bad_authentication_usec = usec;
1282
1283 h->mask |= USER_RECORD_STATUS;
1284 return 0;
1285}
1286
1287int user_record_ratelimit(UserRecord *h) {
1288 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL, *z = NULL;
1289 usec_t usec, new_ratelimit_begin_usec, new_ratelimit_count;
1290 char buf[SD_ID128_STRING_MAX];
1291 sd_id128_t mid;
1292 int r;
1293
1294 assert(h);
1295
1296 usec = now(CLOCK_REALTIME);
1297
1298 if (h->ratelimit_begin_usec != UINT64_MAX && h->ratelimit_begin_usec > usec)
1299 /* Hmm, time is running backwards? Say no! */
1300 return 0;
1301 else if (h->ratelimit_begin_usec == UINT64_MAX ||
1302 usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h)) <= usec) {
1303 /* Fresh start */
1304 new_ratelimit_begin_usec = usec;
1305 new_ratelimit_count = 1;
1306 } else if (h->ratelimit_count < user_record_ratelimit_burst(h)) {
1307 /* Count up */
1308 new_ratelimit_begin_usec = h->ratelimit_begin_usec;
1309 new_ratelimit_count = h->ratelimit_count + 1;
1310 } else
1311 /* Limit hit */
1312 return 0;
1313
1314 r = sd_id128_get_machine(&mid);
1315 if (r < 0)
1316 return r;
1317
1318 v = json_variant_ref(h->json);
1319 w = json_variant_ref(json_variant_by_key(v, "status"));
1320 z = json_variant_ref(json_variant_by_key(w, sd_id128_to_string(mid, buf)));
1321
1322 r = json_variant_set_field_unsigned(&z, "rateLimitBeginUSec", new_ratelimit_begin_usec);
1323 if (r < 0)
1324 return r;
1325
1326 r = json_variant_set_field_unsigned(&z, "rateLimitCount", new_ratelimit_count);
1327 if (r < 0)
1328 return r;
1329
1330 r = json_variant_set_field(&w, buf, z);
1331 if (r < 0)
1332 return r;
1333
1334 r = json_variant_set_field(&v, "status", w);
1335 if (r < 0)
1336 return r;
1337
1338 json_variant_unref(h->json);
1339 h->json = TAKE_PTR(v);
1340
1341 h->ratelimit_begin_usec = new_ratelimit_begin_usec;
1342 h->ratelimit_count = new_ratelimit_count;
1343
1344 h->mask |= USER_RECORD_STATUS;
1345 return 1;
1346}
1347
1348int user_record_is_supported(UserRecord *hr, sd_bus_error *error) {
1349 assert(hr);
1350
1351 if (hr->disposition >= 0 && hr->disposition != USER_REGULAR)
1352 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot manage anything but regular users.");
1353
1354 if (hr->storage >= 0 && !IN_SET(hr->storage, USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
1355 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User record has storage type this service cannot manage.");
1356
1357 if (gid_is_valid(hr->gid) && hr->uid != (uid_t) hr->gid)
1358 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User record has to have matching UID/GID fields.");
1359
1360 if (hr->service && !streq(hr->service, "io.systemd.Home"))
1361 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not accepted with service not matching io.systemd.Home.");
1362
1363 return 0;
1364}