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