]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/home/user-record-util.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[thirdparty/systemd.git] / src / home / user-record-util.c
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
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
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
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
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
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);
449 if (r < 0)
450 return r;
451 if (r == 0)
452 return -EBUSY;
453
454 return USER_TEST_EXISTS;
455 }
456
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 }