]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/user-record.c
nspawn, vmspawn, run0: add env var for turning off background tinting
[thirdparty/systemd.git] / src / shared / user-record.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
71d0b9d4
LP
2
3#include <sys/mount.h>
4
8e1bc689 5#include "cap-list.h"
71d0b9d4
LP
6#include "cgroup-util.h"
7#include "dns-domain.h"
8#include "env-util.h"
9#include "fs-util.h"
28e5e1e9 10#include "glyph-util.h"
71d0b9d4
LP
11#include "hexdecoct.h"
12#include "hostname-util.h"
49e55abb 13#include "locale-util.h"
71d0b9d4
LP
14#include "memory-util.h"
15#include "path-util.h"
16#include "pkcs11-util.h"
17#include "rlimit-util.h"
1b466c09 18#include "sha256.h"
71d0b9d4
LP
19#include "string-table.h"
20#include "strv.h"
8e1ac16b 21#include "uid-classification.h"
71d0b9d4
LP
22#include "user-record.h"
23#include "user-util.h"
1b466c09 24#include "utf8.h"
71d0b9d4
LP
25
26#define DEFAULT_RATELIMIT_BURST 30
27#define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE)
28
29UserRecord* user_record_new(void) {
30 UserRecord *h;
31
32 h = new(UserRecord, 1);
33 if (!h)
34 return NULL;
35
36 *h = (UserRecord) {
37 .n_ref = 1,
38 .disposition = _USER_DISPOSITION_INVALID,
39 .last_change_usec = UINT64_MAX,
40 .last_password_change_usec = UINT64_MAX,
41 .umask = MODE_INVALID,
42 .nice_level = INT_MAX,
43 .not_before_usec = UINT64_MAX,
44 .not_after_usec = UINT64_MAX,
45 .locked = -1,
46 .storage = _USER_STORAGE_INVALID,
47 .access_mode = MODE_INVALID,
48 .disk_size = UINT64_MAX,
49 .disk_size_relative = UINT64_MAX,
50 .tasks_max = UINT64_MAX,
51 .memory_high = UINT64_MAX,
52 .memory_max = UINT64_MAX,
53 .cpu_weight = UINT64_MAX,
54 .io_weight = UINT64_MAX,
55 .uid = UID_INVALID,
56 .gid = GID_INVALID,
57 .nodev = true,
58 .nosuid = true,
59 .luks_discard = -1,
5e86c82a 60 .luks_offline_discard = -1,
71d0b9d4 61 .luks_volume_key_size = UINT64_MAX,
b04ff66b 62 .luks_pbkdf_force_iterations = UINT64_MAX,
71d0b9d4
LP
63 .luks_pbkdf_time_cost_usec = UINT64_MAX,
64 .luks_pbkdf_memory_cost = UINT64_MAX,
65 .luks_pbkdf_parallel_threads = UINT64_MAX,
fd83c98e 66 .luks_sector_size = UINT64_MAX,
71d0b9d4
LP
67 .disk_usage = UINT64_MAX,
68 .disk_free = UINT64_MAX,
69 .disk_ceiling = UINT64_MAX,
70 .disk_floor = UINT64_MAX,
71 .signed_locally = -1,
72 .good_authentication_counter = UINT64_MAX,
73 .bad_authentication_counter = UINT64_MAX,
74 .last_good_authentication_usec = UINT64_MAX,
75 .last_bad_authentication_usec = UINT64_MAX,
76 .ratelimit_begin_usec = UINT64_MAX,
77 .ratelimit_count = UINT64_MAX,
78 .ratelimit_interval_usec = UINT64_MAX,
79 .ratelimit_burst = UINT64_MAX,
80 .removable = -1,
81 .enforce_password_policy = -1,
82 .auto_login = -1,
83 .stop_delay_usec = UINT64_MAX,
84 .kill_processes = -1,
85 .password_change_min_usec = UINT64_MAX,
86 .password_change_max_usec = UINT64_MAX,
87 .password_change_warn_usec = UINT64_MAX,
88 .password_change_inactive_usec = UINT64_MAX,
89 .password_change_now = -1,
90 .pkcs11_protected_authentication_path_permitted = -1,
7b78db28 91 .fido2_user_presence_permitted = -1,
17e7561a 92 .fido2_user_verification_permitted = -1,
86019efa 93 .drop_caches = -1,
8bec643c 94 .auto_resize_mode = _AUTO_RESIZE_MODE_INVALID,
9aa3e5eb 95 .rebalance_weight = REBALANCE_WEIGHT_UNSET,
71d0b9d4
LP
96 };
97
98 return h;
99}
100
101static void pkcs11_encrypted_key_done(Pkcs11EncryptedKey *k) {
102 if (!k)
103 return;
104
105 free(k->uri);
106 erase_and_free(k->data);
107 erase_and_free(k->hashed_password);
108}
109
5e4fa456
LP
110static void fido2_hmac_credential_done(Fido2HmacCredential *c) {
111 if (!c)
112 return;
113
114 free(c->id);
115}
116
117static void fido2_hmac_salt_done(Fido2HmacSalt *s) {
118 if (!s)
119 return;
120
121 fido2_hmac_credential_done(&s->credential);
122 erase_and_free(s->salt);
123 erase_and_free(s->hashed_password);
124}
125
b3a97fd3
LP
126static void recovery_key_done(RecoveryKey *k) {
127 if (!k)
128 return;
129
130 free(k->type);
131 erase_and_free(k->hashed_password);
132}
133
71d0b9d4
LP
134static UserRecord* user_record_free(UserRecord *h) {
135 if (!h)
136 return NULL;
137
138 free(h->user_name);
139 free(h->realm);
140 free(h->user_name_and_realm_auto);
141 free(h->real_name);
142 free(h->email_address);
143 erase_and_free(h->password_hint);
144 free(h->location);
145 free(h->icon_name);
146
1b466c09
AV
147 free(h->blob_directory);
148 hashmap_free(h->blob_manifest);
149
71d0b9d4
LP
150 free(h->shell);
151
152 strv_free(h->environment);
153 free(h->time_zone);
154 free(h->preferred_language);
49e55abb 155 strv_free(h->additional_languages);
71d0b9d4
LP
156 rlimit_free_all(h->rlimits);
157
158 free(h->skeleton_directory);
159
160 strv_free_erase(h->hashed_password);
161 strv_free_erase(h->ssh_authorized_keys);
162 strv_free_erase(h->password);
c0bde0d2 163 strv_free_erase(h->token_pin);
71d0b9d4
LP
164
165 free(h->cifs_service);
166 free(h->cifs_user_name);
167 free(h->cifs_domain);
4c2ee5c7 168 free(h->cifs_extra_mount_options);
71d0b9d4
LP
169
170 free(h->image_path);
171 free(h->image_path_auto);
172 free(h->home_directory);
173 free(h->home_directory_auto);
174
46c60f72
LP
175 free(h->fallback_shell);
176 free(h->fallback_home_directory);
177
71d0b9d4 178 strv_free(h->member_of);
8e1bc689
LP
179 strv_free(h->capability_bounding_set);
180 strv_free(h->capability_ambient_set);
71d0b9d4
LP
181
182 free(h->file_system_type);
183 free(h->luks_cipher);
184 free(h->luks_cipher_mode);
185 free(h->luks_pbkdf_hash_algorithm);
186 free(h->luks_pbkdf_type);
2e0001c2 187 free(h->luks_extra_mount_options);
71d0b9d4
LP
188
189 free(h->state);
190 free(h->service);
191
793ceda1
AV
192 free(h->preferred_session_type);
193 free(h->preferred_session_launcher);
194
71d0b9d4
LP
195 strv_free(h->pkcs11_token_uri);
196 for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++)
197 pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
198 free(h->pkcs11_encrypted_key);
199
5e4fa456
LP
200 for (size_t i = 0; i < h->n_fido2_hmac_credential; i++)
201 fido2_hmac_credential_done(h->fido2_hmac_credential + i);
202 for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
203 fido2_hmac_salt_done(h->fido2_hmac_salt + i);
204
b3a97fd3
LP
205 strv_free(h->recovery_key_type);
206 for (size_t i = 0; i < h->n_recovery_key; i++)
207 recovery_key_done(h->recovery_key + i);
208
71d0b9d4
LP
209 json_variant_unref(h->json);
210
211 return mfree(h);
212}
213
214DEFINE_TRIVIAL_REF_UNREF_FUNC(UserRecord, user_record, user_record_free);
215
216int json_dispatch_realm(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
217 char **s = userdata;
218 const char *n;
219 int r;
220
221 if (json_variant_is_null(variant)) {
222 *s = mfree(*s);
223 return 0;
224 }
225
226 if (!json_variant_is_string(variant))
227 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
228
229 n = json_variant_string(variant);
230 r = dns_name_is_valid(n);
231 if (r < 0)
232 return json_log(variant, flags, r, "Failed to check if JSON field '%s' is a valid DNS domain.", strna(name));
233 if (r == 0)
234 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid DNS domain.", strna(name));
235
236 r = free_and_strdup(s, n);
237 if (r < 0)
238 return json_log(variant, flags, r, "Failed to allocate string: %m");
239
240 return 0;
241}
242
0bb43080 243int json_dispatch_gecos(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
71d0b9d4
LP
244 char **s = userdata;
245 const char *n;
71d0b9d4
LP
246
247 if (json_variant_is_null(variant)) {
248 *s = mfree(*s);
249 return 0;
250 }
251
252 if (!json_variant_is_string(variant))
253 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
254
255 n = json_variant_string(variant);
5cd12aba
LP
256 if (valid_gecos(n)) {
257 if (free_and_strdup(s, n) < 0)
258 return json_log_oom(variant, flags);
259 } else {
260 _cleanup_free_ char *m = NULL;
71d0b9d4 261
5cd12aba
LP
262 json_log(variant, flags|JSON_DEBUG, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid GECOS compatible string, mangling.", strna(name));
263
264 m = mangle_gecos(n);
265 if (!m)
266 return json_log_oom(variant, flags);
267
268 free_and_replace(*s, m);
269 }
71d0b9d4
LP
270
271 return 0;
272}
273
274static int json_dispatch_nice(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
275 int *nl = userdata;
718ca772 276 int64_t m;
71d0b9d4
LP
277
278 if (json_variant_is_null(variant)) {
279 *nl = INT_MAX;
280 return 0;
281 }
282
283 if (!json_variant_is_integer(variant))
284 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
285
286 m = json_variant_integer(variant);
287 if (m < PRIO_MIN || m >= PRIO_MAX)
288 return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not a valid nice level.", strna(name));
289
290 *nl = m;
291 return 0;
292}
293
294static int json_dispatch_rlimit_value(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
295 rlim_t *ret = userdata;
296
297 if (json_variant_is_null(variant))
298 *ret = RLIM_INFINITY;
299 else if (json_variant_is_unsigned(variant)) {
718ca772 300 uint64_t w;
71d0b9d4
LP
301
302 w = json_variant_unsigned(variant);
718ca772 303 if (w == RLIM_INFINITY || (uint64_t) w != json_variant_unsigned(variant))
71d0b9d4
LP
304 return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "Resource limit value '%s' is out of range.", name);
305
306 *ret = (rlim_t) w;
307 } else
308 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit value '%s' is not an unsigned integer.", name);
309
310 return 0;
311}
312
313static int json_dispatch_rlimits(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
314 struct rlimit** limits = userdata;
315 JsonVariant *value;
316 const char *key;
317 int r;
318
319 assert_se(limits);
320
321 if (json_variant_is_null(variant)) {
322 rlimit_free_all(limits);
323 return 0;
324 }
325
326 if (!json_variant_is_object(variant))
327 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
328
329 JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
330 JsonVariant *jcur, *jmax;
331 struct rlimit rl;
332 const char *p;
333 int l;
334
335 p = startswith(key, "RLIMIT_");
336 if (!p)
7211c853 337 l = -SYNTHETIC_ERRNO(EINVAL);
71d0b9d4
LP
338 else
339 l = rlimit_from_string(p);
340 if (l < 0)
7211c853 341 return json_log(variant, flags, l, "Resource limit '%s' not known.", key);
71d0b9d4
LP
342
343 if (!json_variant_is_object(value))
344 return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' has invalid value.", key);
345
346 if (json_variant_elements(value) != 4)
347 return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' value is does not have two fields as expected.", key);
348
349 jcur = json_variant_by_key(value, "cur");
350 if (!jcur)
351 return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'cur' field.", key);
352 r = json_dispatch_rlimit_value("cur", jcur, flags, &rl.rlim_cur);
353 if (r < 0)
354 return r;
355
356 jmax = json_variant_by_key(value, "max");
357 if (!jmax)
358 return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'max' field.", key);
359 r = json_dispatch_rlimit_value("max", jmax, flags, &rl.rlim_max);
360 if (r < 0)
361 return r;
362
363 if (limits[l])
364 *(limits[l]) = rl;
365 else {
366 limits[l] = newdup(struct rlimit, &rl, 1);
367 if (!limits[l])
368 return log_oom();
369 }
370 }
371
372 return 0;
373}
374
375static int json_dispatch_filename_or_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
99534007 376 char **s = ASSERT_PTR(userdata);
71d0b9d4
LP
377 const char *n;
378 int r;
379
71d0b9d4
LP
380 if (json_variant_is_null(variant)) {
381 *s = mfree(*s);
382 return 0;
383 }
384
385 if (!json_variant_is_string(variant))
386 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
387
388 n = json_variant_string(variant);
389 if (!filename_is_valid(n) && !path_is_normalized(n))
390 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid file name or normalized path.", strna(name));
391
392 r = free_and_strdup(s, n);
393 if (r < 0)
394 return json_log(variant, flags, r, "Failed to allocate string: %m");
395
396 return 0;
397}
398
399static int json_dispatch_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
400 char **s = userdata;
401 const char *n;
402 int r;
403
404 if (json_variant_is_null(variant)) {
405 *s = mfree(*s);
406 return 0;
407 }
408
409 if (!json_variant_is_string(variant))
410 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
411
412 n = json_variant_string(variant);
413 if (!path_is_normalized(n))
414 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a normalized file system path.", strna(name));
415 if (!path_is_absolute(n))
416 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an absolute file system path.", strna(name));
417
418 r = free_and_strdup(s, n);
419 if (r < 0)
420 return json_log(variant, flags, r, "Failed to allocate string: %m");
421
422 return 0;
423}
424
425static int json_dispatch_home_directory(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
426 char **s = userdata;
427 const char *n;
428 int r;
429
430 if (json_variant_is_null(variant)) {
431 *s = mfree(*s);
432 return 0;
433 }
434
435 if (!json_variant_is_string(variant))
436 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
437
438 n = json_variant_string(variant);
439 if (!valid_home(n))
440 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid home directory path.", strna(name));
441
442 r = free_and_strdup(s, n);
443 if (r < 0)
444 return json_log(variant, flags, r, "Failed to allocate string: %m");
445
446 return 0;
447}
448
449static int json_dispatch_image_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
450 char **s = userdata;
451 const char *n;
452 int r;
453
454 if (json_variant_is_null(variant)) {
455 *s = mfree(*s);
456 return 0;
457 }
458
459 if (!json_variant_is_string(variant))
460 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
461
462 n = json_variant_string(variant);
463 if (empty_or_root(n) || !path_is_valid(n) || !path_is_absolute(n))
464 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid image path.", strna(name));
465
466 r = free_and_strdup(s, n);
467 if (r < 0)
468 return json_log(variant, flags, r, "Failed to allocate string: %m");
469
470 return 0;
471}
472
473static int json_dispatch_umask(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
474 mode_t *m = userdata;
718ca772 475 uint64_t k;
71d0b9d4
LP
476
477 if (json_variant_is_null(variant)) {
f5fbe71d 478 *m = MODE_INVALID;
71d0b9d4
LP
479 return 0;
480 }
481
482 if (!json_variant_is_unsigned(variant))
483 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
484
485 k = json_variant_unsigned(variant);
486 if (k > 0777)
28e5e1e9
DT
487 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
488 "JSON field '%s' outside of valid range 0%s0777.",
489 strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
71d0b9d4
LP
490
491 *m = (mode_t) k;
492 return 0;
493}
494
495static int json_dispatch_access_mode(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
496 mode_t *m = userdata;
718ca772 497 uint64_t k;
71d0b9d4
LP
498
499 if (json_variant_is_null(variant)) {
f5fbe71d 500 *m = MODE_INVALID;
71d0b9d4
LP
501 return 0;
502 }
503
504 if (!json_variant_is_unsigned(variant))
505 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
506
507 k = json_variant_unsigned(variant);
508 if (k > 07777)
28e5e1e9
DT
509 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
510 "JSON field '%s' outside of valid range 0%s07777.",
511 strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
71d0b9d4
LP
512
513 *m = (mode_t) k;
514 return 0;
515}
516
517static int json_dispatch_environment(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
518 _cleanup_strv_free_ char **n = NULL;
519 char ***l = userdata;
71d0b9d4
LP
520 int r;
521
522 if (json_variant_is_null(variant)) {
523 *l = strv_free(*l);
524 return 0;
525 }
526
527 if (!json_variant_is_array(variant))
528 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
529
6f8f8688 530 for (size_t i = 0; i < json_variant_elements(variant); i++) {
71d0b9d4
LP
531 JsonVariant *e;
532 const char *a;
533
534 e = json_variant_by_index(variant, i);
535 if (!json_variant_is_string(e))
536 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
537
538 assert_se(a = json_variant_string(e));
539
540 if (!env_assignment_is_valid(a))
541 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of environment variables.", strna(name));
542
aaf057c4 543 r = strv_env_replace_strdup(&n, a);
71d0b9d4
LP
544 if (r < 0)
545 return json_log_oom(variant, flags);
71d0b9d4
LP
546 }
547
aaf057c4 548 return strv_free_and_replace(*l, n);
71d0b9d4
LP
549}
550
49e55abb
AV
551static int json_dispatch_locale(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
552 char **s = userdata;
553 const char *n;
554 int r;
555
556 if (json_variant_is_null(variant)) {
557 *s = mfree(*s);
558 return 0;
559 }
560
561 if (!json_variant_is_string(variant))
562 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
563
564 n = json_variant_string(variant);
565
566 if (!locale_is_valid(n))
567 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid locale.", strna(name));
568
569 r = free_and_strdup(s, n);
570 if (r < 0)
571 return json_log(variant, flags, r, "Failed to allocate string: %m");
572
573 return 0;
574}
575
576static int json_dispatch_locales(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
577 _cleanup_strv_free_ char **n = NULL;
578 char ***l = userdata;
579 const char *locale;
580 JsonVariant *e;
581 int r;
582
583 if (json_variant_is_null(variant)) {
584 *l = strv_free(*l);
585 return 0;
586 }
587
588 if (!json_variant_is_array(variant))
589 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
590
591 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
592 if (!json_variant_is_string(e))
593 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
594
595 locale = json_variant_string(e);
596 if (!locale_is_valid(locale))
597 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name));
598
599 r = strv_extend(&n, locale);
600 if (r < 0)
601 return json_log_oom(variant, flags);
602 }
603
604 return strv_free_and_replace(*l, n);
605}
606
19f32829
LP
607JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
608static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
71d0b9d4 609
71d0b9d4 610static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
718ca772 611 uint64_t *limit = userdata, k;
71d0b9d4
LP
612
613 if (json_variant_is_null(variant)) {
614 *limit = UINT64_MAX;
615 return 0;
616 }
617
618 if (!json_variant_is_unsigned(variant))
387f6955 619 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
71d0b9d4
LP
620
621 k = json_variant_unsigned(variant);
622 if (k <= 0 || k >= UINT64_MAX)
28e5e1e9
DT
623 return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
624 "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
625 strna(name), (uint64_t) 1, special_glyph(SPECIAL_GLYPH_ELLIPSIS), UINT64_MAX-1);
71d0b9d4
LP
626
627 *limit = k;
628 return 0;
629}
630
631static int json_dispatch_weight(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
718ca772 632 uint64_t *weight = userdata, k;
71d0b9d4
LP
633
634 if (json_variant_is_null(variant)) {
635 *weight = UINT64_MAX;
636 return 0;
637 }
638
639 if (!json_variant_is_unsigned(variant))
387f6955 640 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
71d0b9d4
LP
641
642 k = json_variant_unsigned(variant);
643 if (k <= CGROUP_WEIGHT_MIN || k >= CGROUP_WEIGHT_MAX)
28e5e1e9
DT
644 return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
645 "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
646 strna(name), (uint64_t) CGROUP_WEIGHT_MIN,
647 special_glyph(SPECIAL_GLYPH_ELLIPSIS), (uint64_t) CGROUP_WEIGHT_MAX);
71d0b9d4
LP
648
649 *weight = k;
650 return 0;
651}
652
653int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
654 _cleanup_strv_free_ char **l = NULL;
655 char ***list = userdata;
656 JsonVariant *e;
657 int r;
658
659 if (!json_variant_is_array(variant))
660 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
661
662 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
663
664 if (!json_variant_is_string(e))
665 return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
666
7a8867ab 667 if (!valid_user_group_name(json_variant_string(e), FLAGS_SET(flags, JSON_RELAX) ? VALID_USER_RELAX : 0))
71d0b9d4
LP
668 return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", json_variant_string(e));
669
670 r = strv_extend(&l, json_variant_string(e));
671 if (r < 0)
672 return json_log(e, flags, r, "Failed to append array element: %m");
673 }
674
675 r = strv_extend_strv(list, l, true);
676 if (r < 0)
677 return json_log(variant, flags, r, "Failed to merge user/group arrays: %m");
678
679 return 0;
680}
681
682static int dispatch_secret(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
683
684 static const JsonDispatch secret_dispatch_table[] = {
685 { "password", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, password), 0 },
c0bde0d2
LP
686 { "tokenPin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
687 { "pkcs11Pin", /* legacy alias */ _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
71d0b9d4 688 { "pkcs11ProtectedAuthenticationPathPermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
7b78db28 689 { "fido2UserPresencePermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, fido2_user_presence_permitted), 0 },
17e7561a 690 { "fido2UserVerificationPermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, fido2_user_verification_permitted), 0 },
71d0b9d4
LP
691 {},
692 };
693
f1b622a0 694 return json_dispatch(variant, secret_dispatch_table, flags, userdata);
71d0b9d4
LP
695}
696
697static int dispatch_pkcs11_uri(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
698 char **s = userdata;
699 const char *n;
700 int r;
701
702 if (json_variant_is_null(variant)) {
703 *s = mfree(*s);
704 return 0;
705 }
706
707 if (!json_variant_is_string(variant))
708 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
709
710 n = json_variant_string(variant);
711 if (!pkcs11_uri_valid(n))
712 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
713
714 r = free_and_strdup(s, n);
715 if (r < 0)
716 return json_log(variant, flags, r, "Failed to allocate string: %m");
717
718 return 0;
719}
720
721static int dispatch_pkcs11_uri_array(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
722 _cleanup_strv_free_ char **z = NULL;
723 char ***l = userdata;
724 JsonVariant *e;
725 int r;
726
727 if (json_variant_is_null(variant)) {
728 *l = strv_free(*l);
729 return 0;
730 }
731
732 if (json_variant_is_string(variant)) {
733 const char *n;
734
735 n = json_variant_string(variant);
736 if (!pkcs11_uri_valid(n))
737 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
738
739 z = strv_new(n);
740 if (!z)
741 return log_oom();
742
743 } else {
744
745 if (!json_variant_is_array(variant))
746 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string or array of strings.", strna(name));
747
748 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
749 const char *n;
750
751 if (!json_variant_is_string(e))
752 return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
753
754 n = json_variant_string(e);
755 if (!pkcs11_uri_valid(n))
756 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element in '%s' is not a valid RFC7512 PKCS#11 URI: %s", strna(name), n);
757
758 r = strv_extend(&z, n);
759 if (r < 0)
760 return log_oom();
761 }
762 }
763
764 strv_free_and_replace(*l, z);
765 return 0;
766}
767
768static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
769 Pkcs11EncryptedKey *k = userdata;
770 size_t l;
771 void *b;
772 int r;
773
774 if (json_variant_is_null(variant)) {
d00f3183 775 k->data = erase_and_free(k->data);
71d0b9d4
LP
776 k->size = 0;
777 return 0;
778 }
779
780 if (!json_variant_is_string(variant))
781 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
782
bdd2036e 783 r = unbase64mem(json_variant_string(variant), &b, &l);
71d0b9d4
LP
784 if (r < 0)
785 return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
786
787 erase_and_free(k->data);
788 k->data = b;
789 k->size = l;
790
791 return 0;
792}
793
794static int dispatch_pkcs11_key(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
795 UserRecord *h = userdata;
796 JsonVariant *e;
797 int r;
798
799 if (!json_variant_is_array(variant))
800 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
801
802 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
803 Pkcs11EncryptedKey *array, *k;
804
805 static const JsonDispatch pkcs11_key_dispatch_table[] = {
806 { "uri", JSON_VARIANT_STRING, dispatch_pkcs11_uri, offsetof(Pkcs11EncryptedKey, uri), JSON_MANDATORY },
807 { "data", JSON_VARIANT_STRING, dispatch_pkcs11_key_data, 0, JSON_MANDATORY },
808 { "hashedPassword", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Pkcs11EncryptedKey, hashed_password), JSON_MANDATORY },
809 {},
810 };
811
812 if (!json_variant_is_object(e))
813 return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
814
815 array = reallocarray(h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key + 1, sizeof(Pkcs11EncryptedKey));
816 if (!array)
817 return log_oom();
818
819 h->pkcs11_encrypted_key = array;
820 k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key;
821 *k = (Pkcs11EncryptedKey) {};
822
f1b622a0 823 r = json_dispatch(e, pkcs11_key_dispatch_table, flags, k);
71d0b9d4
LP
824 if (r < 0) {
825 pkcs11_encrypted_key_done(k);
826 return r;
827 }
828
829 h->n_pkcs11_encrypted_key++;
830 }
831
832 return 0;
833}
834
5e4fa456
LP
835static int dispatch_fido2_hmac_credential(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
836 Fido2HmacCredential *k = userdata;
837 size_t l;
838 void *b;
839 int r;
840
841 if (json_variant_is_null(variant)) {
842 k->id = mfree(k->id);
843 k->size = 0;
844 return 0;
845 }
846
847 if (!json_variant_is_string(variant))
848 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
849
bdd2036e 850 r = unbase64mem(json_variant_string(variant), &b, &l);
5e4fa456
LP
851 if (r < 0)
852 return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
853
854 free_and_replace(k->id, b);
855 k->size = l;
856
857 return 0;
858}
859
860static int dispatch_fido2_hmac_credential_array(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
861 UserRecord *h = userdata;
862 JsonVariant *e;
863 int r;
864
865 if (!json_variant_is_array(variant))
866 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
867
868 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
869 Fido2HmacCredential *array;
870 size_t l;
871 void *b;
872
873 if (!json_variant_is_string(e))
874 return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
875
876 array = reallocarray(h->fido2_hmac_credential, h->n_fido2_hmac_credential + 1, sizeof(Fido2HmacCredential));
877 if (!array)
878 return log_oom();
879
bdd2036e 880 r = unbase64mem(json_variant_string(e), &b, &l);
5e4fa456
LP
881 if (r < 0)
882 return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
883
884 h->fido2_hmac_credential = array;
885
886 h->fido2_hmac_credential[h->n_fido2_hmac_credential++] = (Fido2HmacCredential) {
887 .id = b,
888 .size = l,
889 };
890 }
891
892 return 0;
893}
894
895static int dispatch_fido2_hmac_salt_value(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
896 Fido2HmacSalt *k = userdata;
897 size_t l;
898 void *b;
899 int r;
900
901 if (json_variant_is_null(variant)) {
902 k->salt = erase_and_free(k->salt);
903 k->salt_size = 0;
904 return 0;
905 }
906
907 if (!json_variant_is_string(variant))
908 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
909
bdd2036e 910 r = unbase64mem(json_variant_string(variant), &b, &l);
5e4fa456
LP
911 if (r < 0)
912 return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
913
914 erase_and_free(k->salt);
915 k->salt = b;
916 k->salt_size = l;
917
918 return 0;
919}
920
921static int dispatch_fido2_hmac_salt(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
922 UserRecord *h = userdata;
923 JsonVariant *e;
924 int r;
925
926 if (!json_variant_is_array(variant))
927 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
928
929 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
930 Fido2HmacSalt *array, *k;
931
932 static const JsonDispatch fido2_hmac_salt_dispatch_table[] = {
17e7561a
LP
933 { "credential", JSON_VARIANT_STRING, dispatch_fido2_hmac_credential, offsetof(Fido2HmacSalt, credential), JSON_MANDATORY },
934 { "salt", JSON_VARIANT_STRING, dispatch_fido2_hmac_salt_value, 0, JSON_MANDATORY },
935 { "hashedPassword", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Fido2HmacSalt, hashed_password), JSON_MANDATORY },
936 { "up", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(Fido2HmacSalt, up), 0 },
937 { "uv", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(Fido2HmacSalt, uv), 0 },
938 { "clientPin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(Fido2HmacSalt, client_pin), 0 },
5e4fa456
LP
939 {},
940 };
941
942 if (!json_variant_is_object(e))
943 return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
944
945 array = reallocarray(h->fido2_hmac_salt, h->n_fido2_hmac_salt + 1, sizeof(Fido2HmacSalt));
946 if (!array)
947 return log_oom();
948
949 h->fido2_hmac_salt = array;
950 k = h->fido2_hmac_salt + h->n_fido2_hmac_salt;
17e7561a
LP
951 *k = (Fido2HmacSalt) {
952 .uv = -1,
953 .up = -1,
954 .client_pin = -1,
955 };
5e4fa456 956
f1b622a0 957 r = json_dispatch(e, fido2_hmac_salt_dispatch_table, flags, k);
5e4fa456
LP
958 if (r < 0) {
959 fido2_hmac_salt_done(k);
960 return r;
961 }
962
963 h->n_fido2_hmac_salt++;
964 }
965
966 return 0;
967}
968
b3a97fd3
LP
969static int dispatch_recovery_key(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
970 UserRecord *h = userdata;
971 JsonVariant *e;
972 int r;
973
974 if (!json_variant_is_array(variant))
975 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
976
977 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
978 RecoveryKey *array, *k;
979
980 static const JsonDispatch recovery_key_dispatch_table[] = {
981 { "type", JSON_VARIANT_STRING, json_dispatch_string, 0, JSON_MANDATORY },
982 { "hashedPassword", JSON_VARIANT_STRING, json_dispatch_string, offsetof(RecoveryKey, hashed_password), JSON_MANDATORY },
983 {},
984 };
985
986 if (!json_variant_is_object(e))
987 return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
988
989 array = reallocarray(h->recovery_key, h->n_recovery_key + 1, sizeof(RecoveryKey));
990 if (!array)
991 return log_oom();
992
993 h->recovery_key = array;
994 k = h->recovery_key + h->n_recovery_key;
995 *k = (RecoveryKey) {};
996
f1b622a0 997 r = json_dispatch(e, recovery_key_dispatch_table, flags, k);
b3a97fd3
LP
998 if (r < 0) {
999 recovery_key_done(k);
1000 return r;
1001 }
1002
1003 h->n_recovery_key++;
1004 }
1005
1006 return 0;
1007}
1008
8bec643c
LP
1009static int dispatch_auto_resize_mode(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
1010 AutoResizeMode *mode = userdata, m;
1011
1012 assert_se(mode);
1013
1014 if (json_variant_is_null(variant)) {
1015 *mode = _AUTO_RESIZE_MODE_INVALID;
1016 return 0;
1017 }
1018
1019 if (json_variant_is_boolean(variant)) {
1020 *mode = json_variant_boolean(variant) ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
1021 return 0;
1022 }
1023
1024 if (!json_variant_is_string(variant))
1025 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string, boolean or null.", strna(name));
1026
1027 m = auto_resize_mode_from_string(json_variant_string(variant));
1028 if (m < 0)
1029 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid automatic resize mode.", strna(name));
1030
1031 *mode = m;
1032 return 0;
1033}
1034
9aa3e5eb
LP
1035static int dispatch_rebalance_weight(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
1036 uint64_t *rebalance_weight = userdata;
1037 uintmax_t u;
1038
1039 assert_se(rebalance_weight);
1040
1041 if (json_variant_is_null(variant)) {
1042 *rebalance_weight = REBALANCE_WEIGHT_UNSET;
1043 return 0;
1044 }
1045
1046 if (json_variant_is_boolean(variant)) {
1047 *rebalance_weight = json_variant_boolean(variant) ? REBALANCE_WEIGHT_DEFAULT : REBALANCE_WEIGHT_OFF;
1048 return 0;
1049 }
1050
1051 if (!json_variant_is_unsigned(variant))
1052 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer, boolean or null.", strna(name));
1053
1054 u = json_variant_unsigned(variant);
1055 if (u >= REBALANCE_WEIGHT_MIN && u <= REBALANCE_WEIGHT_MAX)
1056 *rebalance_weight = (uint64_t) u;
1057 else if (u == 0)
1058 *rebalance_weight = REBALANCE_WEIGHT_OFF;
1059 else
28e5e1e9
DT
1060 return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
1061 "Rebalance weight is out of valid range %" PRIu64 "%s%" PRIu64 ".",
1062 REBALANCE_WEIGHT_MIN, special_glyph(SPECIAL_GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX);
9aa3e5eb
LP
1063
1064 return 0;
1065}
1066
71d0b9d4
LP
1067static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
1068
1069 static const JsonDispatch privileged_dispatch_table[] = {
5e4fa456
LP
1070 { "passwordHint", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, password_hint), 0 },
1071 { "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, hashed_password), JSON_SAFE },
1072 { "sshAuthorizedKeys", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, ssh_authorized_keys), 0 },
1073 { "pkcs11EncryptedKey", JSON_VARIANT_ARRAY, dispatch_pkcs11_key, 0, 0 },
1074 { "fido2HmacSalt", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_salt, 0, 0 },
b3a97fd3 1075 { "recoveryKey", JSON_VARIANT_ARRAY, dispatch_recovery_key, 0, 0 },
71d0b9d4
LP
1076 {},
1077 };
1078
f1b622a0 1079 return json_dispatch(variant, privileged_dispatch_table, flags, userdata);
71d0b9d4
LP
1080}
1081
1082static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
1083
1084 static const JsonDispatch binding_dispatch_table[] = {
1b466c09 1085 { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
71d0b9d4
LP
1086 { "imagePath", JSON_VARIANT_STRING, json_dispatch_image_path, offsetof(UserRecord, image_path), 0 },
1087 { "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
1088 { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
1089 { "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
1090 { "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
1091 { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
1092 { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
19f32829 1093 { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
71d0b9d4
LP
1094 { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
1095 { "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
1096 { "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
9942f855 1097 { "luksVolumeKeySize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
71d0b9d4
LP
1098 {},
1099 };
1100
71d0b9d4
LP
1101 JsonVariant *m;
1102 sd_id128_t mid;
1103 int r;
1104
1105 if (!variant)
1106 return 0;
1107
1108 if (!json_variant_is_object(variant))
1109 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
1110
1111 r = sd_id128_get_machine(&mid);
1112 if (r < 0)
1113 return json_log(variant, flags, r, "Failed to determine machine ID: %m");
1114
85b55869 1115 m = json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
71d0b9d4
LP
1116 if (!m)
1117 return 0;
1118
f1b622a0 1119 return json_dispatch(m, binding_dispatch_table, flags, userdata);
71d0b9d4
LP
1120}
1121
1b466c09
AV
1122static int dispatch_blob_manifest(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
1123 _cleanup_hashmap_free_ Hashmap *manifest = NULL;
1124 Hashmap **ret = ASSERT_PTR(userdata);
1125 JsonVariant *value;
1126 const char *key;
1127 int r;
1128
1129 if (!variant)
1130 return 0;
1131
1132 if (!json_variant_is_object(variant))
1133 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
1134
1135 JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
1136 _cleanup_free_ char *filename = NULL;
1137 _cleanup_free_ uint8_t *hash = NULL;
1138
1139 if (!json_variant_is_string(value))
1140 return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key);
1141
1142 if (!suitable_blob_filename(key))
1143 return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key);
1144
1145 filename = strdup(key);
1146 if (!filename)
1147 return json_log_oom(value, flags);
1148
1149 hash = malloc(SHA256_DIGEST_SIZE);
1150 if (!hash)
1151 return json_log_oom(value, flags);
1152
1153 r = parse_sha256(json_variant_string(value), hash);
1154 if (r < 0)
1155 return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, json_variant_string(value));
1156
1157 r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash);
1158 if (r < 0)
1159 return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename);
1160 TAKE_PTR(filename); /* Ownership transfers to hashmap */
1161 TAKE_PTR(hash);
1162 }
1163
1164 hashmap_free_and_replace(*ret, manifest);
1165 return 0;
1166}
1167
71d0b9d4
LP
1168int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags) {
1169 sd_id128_t mid;
1170 int r;
1171
1172 r = sd_id128_get_machine(&mid);
1173 if (r < 0)
1174 return json_log(ids, flags, r, "Failed to acquire machine ID: %m");
1175
1176 if (json_variant_is_string(ids)) {
1177 sd_id128_t k;
1178
1179 r = sd_id128_from_string(json_variant_string(ids), &k);
1180 if (r < 0) {
1181 json_log(ids, flags, r, "%s is not a valid machine ID, ignoring: %m", json_variant_string(ids));
1182 return 0;
1183 }
1184
1185 return sd_id128_equal(mid, k);
1186 }
1187
1188 if (json_variant_is_array(ids)) {
1189 JsonVariant *e;
1190
1191 JSON_VARIANT_ARRAY_FOREACH(e, ids) {
1192 sd_id128_t k;
1193
1194 if (!json_variant_is_string(e)) {
1195 json_log(e, flags, 0, "Machine ID is not a string, ignoring: %m");
1196 continue;
1197 }
1198
1199 r = sd_id128_from_string(json_variant_string(e), &k);
1200 if (r < 0) {
1201 json_log(e, flags, r, "%s is not a valid machine ID, ignoring: %m", json_variant_string(e));
1202 continue;
1203 }
1204
1205 if (sd_id128_equal(mid, k))
1206 return true;
1207 }
1208
1209 return false;
1210 }
1211
1212 json_log(ids, flags, 0, "Machine ID is not a string or array of strings, ignoring: %m");
1213 return false;
1214}
1215
1216int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) {
1217 _cleanup_free_ char *hn = NULL;
1218 int r;
1219
1220 r = gethostname_strict(&hn);
1221 if (r == -ENXIO) {
1222 json_log(hns, flags, r, "No hostname set, not matching perMachine hostname record: %m");
1223 return false;
1224 }
1225 if (r < 0)
1226 return json_log(hns, flags, r, "Failed to acquire hostname: %m");
1227
1228 if (json_variant_is_string(hns))
1229 return streq(json_variant_string(hns), hn);
1230
1231 if (json_variant_is_array(hns)) {
1232 JsonVariant *e;
1233
1234 JSON_VARIANT_ARRAY_FOREACH(e, hns) {
1235
1236 if (!json_variant_is_string(e)) {
1237 json_log(e, flags, 0, "Hostname is not a string, ignoring: %m");
1238 continue;
1239 }
1240
1241 if (streq(json_variant_string(hns), hn))
1242 return true;
1243 }
1244
1245 return false;
1246 }
1247
1248 json_log(hns, flags, 0, "Hostname is not a string or array of strings, ignoring: %m");
1249 return false;
1250}
1251
f0409e7b
AV
1252int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags) {
1253 JsonVariant *m;
1254 int r;
1255
1256 assert(json_variant_is_object(entry));
1257
1258 m = json_variant_by_key(entry, "matchMachineId");
1259 if (m) {
1260 r = per_machine_id_match(m, flags);
1261 if (r < 0)
1262 return r;
1263 if (r > 0)
1264 return true;
1265 }
1266
1267 m = json_variant_by_key(entry, "matchHostname");
1268 if (m) {
1269 r = per_machine_hostname_match(m, flags);
1270 if (r < 0)
1271 return r;
1272 if (r > 0)
1273 return true;
1274 }
1275
1276 return false;
1277}
1278
71d0b9d4
LP
1279static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
1280
1281 static const JsonDispatch per_machine_dispatch_table[] = {
5e4fa456
LP
1282 { "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
1283 { "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
1b466c09
AV
1284 { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
1285 { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
5e4fa456
LP
1286 { "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
1287 { "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
1288 { "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
1289 { "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
1290 { "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
1291 { "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
49e55abb
AV
1292 { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 },
1293 { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 },
5e4fa456
LP
1294 { "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
1295 { "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
1296 { "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
9942f855
LP
1297 { "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
1298 { "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
19f32829 1299 { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
9942f855
LP
1300 { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
1301 { "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
5e4fa456
LP
1302 { "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
1303 { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
1304 { "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
1305 { "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
1306 { "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
1307 { "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
1308 { "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
1309 { "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
1310 { "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
1311 { "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
1312 { "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
1313 { "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
1314 { "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
4c2ee5c7 1315 { "cifsExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_extra_mount_options), 0 },
5e4fa456
LP
1316 { "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
1317 { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
1318 { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
1319 { "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
8e1bc689
LP
1320 { "capabilityBoundingSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_bounding_set), JSON_SAFE },
1321 { "capabilityAmbientSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_ambient_set), JSON_SAFE },
5e4fa456
LP
1322 { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
1323 { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
1324 { "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
1325 { "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
1326 { "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0, },
1327 { "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0, },
1328 { "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
1329 { "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
9942f855 1330 { "luksVolumeKeySize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
5e4fa456
LP
1331 { "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
1332 { "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
9942f855
LP
1333 { "luksPbkdfForceIterations", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_force_iterations), 0 },
1334 { "luksPbkdfTimeCostUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
1335 { "luksPbkdfMemoryCost", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
1336 { "luksPbkdfParallelThreads", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
1337 { "luksSectorSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_sector_size), 0 },
2e0001c2 1338 { "luksExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_extra_mount_options), 0 },
86019efa 1339 { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
8bec643c 1340 { "autoResizeMode", _JSON_VARIANT_TYPE_INVALID, dispatch_auto_resize_mode, offsetof(UserRecord, auto_resize_mode), 0 },
9aa3e5eb 1341 { "rebalanceWeight", _JSON_VARIANT_TYPE_INVALID, dispatch_rebalance_weight, offsetof(UserRecord, rebalance_weight), 0 },
9942f855
LP
1342 { "rateLimitIntervalUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
1343 { "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
5e4fa456
LP
1344 { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
1345 { "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
793ceda1
AV
1346 { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE },
1347 { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE },
9942f855 1348 { "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
5e4fa456 1349 { "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
9942f855
LP
1350 { "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
1351 { "passwordChangeMaxUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
1352 { "passwordChangeWarnUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
1353 { "passwordChangeInactiveUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
5e4fa456
LP
1354 { "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
1355 { "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
1356 { "fido2HmacCredential", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
71d0b9d4
LP
1357 {},
1358 };
1359
1360 JsonVariant *e;
1361 int r;
1362
1363 if (!variant)
1364 return 0;
1365
1366 if (!json_variant_is_array(variant))
1367 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
1368
1369 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
71d0b9d4
LP
1370 if (!json_variant_is_object(e))
1371 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
1372
f0409e7b
AV
1373 r = per_machine_match(e, flags);
1374 if (r < 0)
1375 return r;
1376 if (r == 0)
71d0b9d4
LP
1377 continue;
1378
f1b622a0 1379 r = json_dispatch(e, per_machine_dispatch_table, flags, userdata);
71d0b9d4
LP
1380 if (r < 0)
1381 return r;
1382 }
1383
1384 return 0;
1385}
1386
1387static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
1388
1389 static const JsonDispatch status_dispatch_table[] = {
46c60f72
LP
1390 { "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
1391 { "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
1392 { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
1393 { "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
1394 { "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 },
1395 { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE },
1396 { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
1397 { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 },
1398 { "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
1399 { "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
1400 { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
1401 { "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
1402 { "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
1403 { "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 },
61ab5ddc 1404 { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, removable), 0 },
46c60f72
LP
1405 { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
1406 { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
1407 { "fallbackShell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell), 0 },
1408 { "fallbackHomeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, fallback_home_directory), 0 },
1409 { "useFallback", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, use_fallback), 0 },
71d0b9d4
LP
1410 {},
1411 };
1412
71d0b9d4
LP
1413 JsonVariant *m;
1414 sd_id128_t mid;
1415 int r;
1416
1417 if (!variant)
1418 return 0;
1419
1420 if (!json_variant_is_object(variant))
1421 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
1422
1423 r = sd_id128_get_machine(&mid);
1424 if (r < 0)
1425 return json_log(variant, flags, r, "Failed to determine machine ID: %m");
1426
85b55869 1427 m = json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
71d0b9d4
LP
1428 if (!m)
1429 return 0;
1430
f1b622a0 1431 return json_dispatch(m, status_dispatch_table, flags, userdata);
71d0b9d4
LP
1432}
1433
a43eddbd
LP
1434int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret) {
1435 const char *suffix;
1436 char *z;
1437
1438 assert(storage >= 0);
1439 assert(user_name_and_realm);
1440 assert(ret);
1441
1442 if (storage == USER_LUKS)
1443 suffix = ".home";
1444 else if (IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
1445 suffix = ".homedir";
1446 else {
1447 *ret = NULL;
1448 return 0;
1449 }
1450
2700fecd 1451 z = strjoin(get_home_root(), "/", user_name_and_realm, suffix);
a43eddbd
LP
1452 if (!z)
1453 return -ENOMEM;
1454
2700fecd 1455 *ret = path_simplify(z);
a43eddbd
LP
1456 return 1;
1457}
1458
71d0b9d4 1459static int user_record_augment(UserRecord *h, JsonDispatchFlags json_flags) {
a43eddbd
LP
1460 int r;
1461
71d0b9d4
LP
1462 assert(h);
1463
1464 if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
1465 return 0;
1466
1467 assert(h->user_name);
1468
1469 if (!h->user_name_and_realm_auto && h->realm) {
1470 h->user_name_and_realm_auto = strjoin(h->user_name, "@", h->realm);
1471 if (!h->user_name_and_realm_auto)
1472 return json_log_oom(h->json, json_flags);
1473 }
1474
162392b7 1475 /* Let's add in the following automatisms only for regular users, they don't make sense for any others */
71d0b9d4
LP
1476 if (user_record_disposition(h) != USER_REGULAR)
1477 return 0;
1478
1479 if (!h->home_directory && !h->home_directory_auto) {
2700fecd 1480 h->home_directory_auto = path_join(get_home_root(), h->user_name);
71d0b9d4
LP
1481 if (!h->home_directory_auto)
1482 return json_log_oom(h->json, json_flags);
1483 }
1484
1485 if (!h->image_path && !h->image_path_auto) {
a43eddbd
LP
1486 r = user_record_build_image_path(user_record_storage(h), user_record_user_name_and_realm(h), &h->image_path_auto);
1487 if (r < 0)
1488 return json_log(h->json, json_flags, r, "Failed to determine default image path: %m");
71d0b9d4
LP
1489 }
1490
1491 return 0;
1492}
1493
1494int user_group_record_mangle(
1495 JsonVariant *v,
1496 UserRecordLoadFlags load_flags,
1497 JsonVariant **ret_variant,
1498 UserRecordMask *ret_mask) {
1499
1500 static const struct {
1501 UserRecordMask mask;
1502 const char *name;
1503 } mask_field[] = {
1504 { USER_RECORD_PRIVILEGED, "privileged" },
1505 { USER_RECORD_SECRET, "secret" },
1506 { USER_RECORD_BINDING, "binding" },
1507 { USER_RECORD_PER_MACHINE, "perMachine" },
1508 { USER_RECORD_STATUS, "status" },
1509 { USER_RECORD_SIGNATURE, "signature" },
1510 };
1511
1512 JsonDispatchFlags json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
1513 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
1514 JsonVariant *array[ELEMENTSOF(mask_field) * 2];
6f8f8688 1515 size_t n_retain = 0;
71d0b9d4
LP
1516 UserRecordMask m = 0;
1517 int r;
1518
1519 assert((load_flags & _USER_RECORD_MASK_MAX) == 0); /* detect mistakes when accidentally passing
1520 * UserRecordMask bit masks as UserRecordLoadFlags
1521 * value */
1522
1523 assert(v);
1524 assert(ret_variant);
1525 assert(ret_mask);
1526
1527 /* Note that this function is shared with the group record parser, hence we try to be generic in our
1528 * log message wording here, to cover both cases. */
1529
1530 if (!json_variant_is_object(v))
1531 return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is not a JSON object, refusing.");
1532
1533 if (USER_RECORD_ALLOW_MASK(load_flags) == 0) /* allow nothing? */
1534 return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Nothing allowed in record, refusing.");
1535
1536 if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */
1537 return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing.");
1538
1539 /* Check if we have the special sections and if they match our flags set */
6f8f8688 1540 for (size_t i = 0; i < ELEMENTSOF(mask_field); i++) {
71d0b9d4
LP
1541 JsonVariant *e, *k;
1542
1543 if (FLAGS_SET(USER_RECORD_STRIP_MASK(load_flags), mask_field[i].mask)) {
1544 if (!w)
1545 w = json_variant_ref(v);
1546
1547 r = json_variant_filter(&w, STRV_MAKE(mask_field[i].name));
1548 if (r < 0)
1549 return json_log(w, json_flags, r, "Failed to remove field from variant: %m");
1550
1551 continue;
1552 }
1553
1554 e = json_variant_by_key_full(v, mask_field[i].name, &k);
1555 if (e) {
1556 if (!FLAGS_SET(USER_RECORD_ALLOW_MASK(load_flags), mask_field[i].mask))
1557 return json_log(e, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", mask_field[i].name);
1558
1559 if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
1560 array[n_retain++] = k;
1561 array[n_retain++] = e;
1562 }
1563
1564 m |= mask_field[i].mask;
1565 } else {
1566 if (FLAGS_SET(USER_RECORD_REQUIRE_MASK(load_flags), mask_field[i].mask))
1567 return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks '%s' field, which is required.", mask_field[i].name);
1568 }
1569 }
1570
1571 if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
1572 /* If we are supposed to strip regular items, then let's instead just allocate a new object
1573 * with just the stuff we need. */
1574
1575 w = json_variant_unref(w);
1576 r = json_variant_new_object(&w, array, n_retain);
1577 if (r < 0)
1578 return json_log(v, json_flags, r, "Failed to allocate new object: %m");
6f8f8688 1579 } else
71d0b9d4 1580 /* And now check if there's anything else in the record */
6f8f8688 1581 for (size_t i = 0; i < json_variant_elements(v); i += 2) {
71d0b9d4
LP
1582 const char *f;
1583 bool special = false;
71d0b9d4
LP
1584
1585 assert_se(f = json_variant_string(json_variant_by_index(v, i)));
1586
6f8f8688 1587 for (size_t j = 0; j < ELEMENTSOF(mask_field); j++)
71d0b9d4
LP
1588 if (streq(f, mask_field[j].name)) { /* already covered in the loop above */
1589 special = true;
1590 continue;
1591 }
1592
1593 if (!special) {
1594 if ((load_flags & (USER_RECORD_ALLOW_REGULAR|USER_RECORD_REQUIRE_REGULAR)) == 0)
1595 return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", f);
1596
1597 m |= USER_RECORD_REGULAR;
1598 break;
1599 }
1600 }
71d0b9d4
LP
1601
1602 if (FLAGS_SET(load_flags, USER_RECORD_REQUIRE_REGULAR) && !FLAGS_SET(m, USER_RECORD_REGULAR))
1603 return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks basic identity fields, which are required.");
1604
1a298a20 1605 if (!FLAGS_SET(load_flags, USER_RECORD_EMPTY_OK) && m == 0)
71d0b9d4
LP
1606 return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is empty.");
1607
1608 if (w)
1609 *ret_variant = TAKE_PTR(w);
1610 else
1611 *ret_variant = json_variant_ref(v);
1612
1613 *ret_mask = m;
1614 return 0;
1615}
1616
1617int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_flags) {
1618
1619 static const JsonDispatch user_dispatch_table[] = {
5e4fa456
LP
1620 { "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
1621 { "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
1b466c09
AV
1622 { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
1623 { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
5e4fa456
LP
1624 { "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
1625 { "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
1626 { "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
1627 { "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
1628 { "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 },
9942f855
LP
1629 { "lastChangeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 },
1630 { "lastPasswordChangeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 },
5e4fa456
LP
1631 { "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
1632 { "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
1633 { "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
1634 { "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
49e55abb
AV
1635 { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 },
1636 { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 },
5e4fa456
LP
1637 { "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
1638 { "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
1639 { "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
9942f855
LP
1640 { "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
1641 { "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
19f32829 1642 { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
9942f855
LP
1643 { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
1644 { "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
5e4fa456
LP
1645 { "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
1646 { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
1647 { "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
1648 { "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
1649 { "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
1650 { "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
1651 { "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
1652 { "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
1653 { "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
1654 { "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
1655 { "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
1656 { "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
1657 { "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
4c2ee5c7 1658 { "cifsExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_extra_mount_options), 0 },
5e4fa456
LP
1659 { "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
1660 { "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
1661 { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
1662 { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
1663 { "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
8e1bc689
LP
1664 { "capabilityBoundingSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_bounding_set), JSON_SAFE },
1665 { "capabilityAmbientSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_ambient_set), JSON_SAFE },
5e4fa456
LP
1666 { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
1667 { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
1668 { "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
1669 { "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
1670 { "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
86019efa 1671 { "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 },
5e4fa456
LP
1672 { "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
1673 { "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
9942f855 1674 { "luksVolumeKeySize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
5e4fa456
LP
1675 { "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
1676 { "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
9942f855
LP
1677 { "luksPbkdfForceIterations", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_force_iterations), 0 },
1678 { "luksPbkdfTimeCostUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
1679 { "luksPbkdfMemoryCost", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
1680 { "luksPbkdfParallelThreads", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
1681 { "luksSectorSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, luks_sector_size), 0 },
2e0001c2 1682 { "luksExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_extra_mount_options), 0 },
86019efa 1683 { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
8bec643c 1684 { "autoResizeMode", _JSON_VARIANT_TYPE_INVALID, dispatch_auto_resize_mode, offsetof(UserRecord, auto_resize_mode), 0 },
9aa3e5eb 1685 { "rebalanceWeight", _JSON_VARIANT_TYPE_INVALID, dispatch_rebalance_weight, offsetof(UserRecord, rebalance_weight), 0 },
5e4fa456 1686 { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
9942f855
LP
1687 { "rateLimitIntervalUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
1688 { "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
5e4fa456
LP
1689 { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
1690 { "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
793ceda1
AV
1691 { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE },
1692 { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE },
9942f855 1693 { "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
5e4fa456 1694 { "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
9942f855
LP
1695 { "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
1696 { "passwordChangeMaxUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
1697 { "passwordChangeWarnUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
1698 { "passwordChangeInactiveUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
5e4fa456
LP
1699 { "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
1700 { "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
1701 { "fido2HmacCredential", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
b3a97fd3 1702 { "recoveryKeyType", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, recovery_key_type), 0 },
5e4fa456
LP
1703
1704 { "secret", JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
1705 { "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
71d0b9d4
LP
1706
1707 /* Ignore the perMachine, binding, status stuff here, and process it later, so that it overrides whatever is set above */
5e4fa456
LP
1708 { "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
1709 { "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
1710 { "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
71d0b9d4
LP
1711
1712 /* Ignore 'signature', we check it with explicit accessors instead */
5e4fa456 1713 { "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
71d0b9d4
LP
1714 {},
1715 };
1716
1717 JsonDispatchFlags json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
1718 int r;
1719
1720 assert(h);
1721 assert(!h->json);
1722
1723 /* Note that this call will leave a half-initialized record around on failure! */
1724
1725 r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
1726 if (r < 0)
1727 return r;
1728
f0e4244b 1729 r = json_dispatch(h->json, user_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h);
71d0b9d4
LP
1730 if (r < 0)
1731 return r;
1732
1733 /* During the parsing operation above we ignored the 'perMachine', 'binding' and 'status' fields,
1734 * since we want them to override the global options. Let's process them now. */
1735
1736 r = dispatch_per_machine("perMachine", json_variant_by_key(h->json, "perMachine"), json_flags, h);
1737 if (r < 0)
1738 return r;
1739
1740 r = dispatch_binding("binding", json_variant_by_key(h->json, "binding"), json_flags, h);
1741 if (r < 0)
1742 return r;
1743
1744 r = dispatch_status("status", json_variant_by_key(h->json, "status"), json_flags, h);
1745 if (r < 0)
1746 return r;
1747
1748 if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->user_name)
1749 return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "User name field missing, refusing.");
1750
1751 r = user_record_augment(h, json_flags);
1752 if (r < 0)
1753 return r;
1754
1755 return 0;
1756}
1757
1758int user_record_build(UserRecord **ret, ...) {
1759 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1760 _cleanup_(user_record_unrefp) UserRecord *u = NULL;
1761 va_list ap;
1762 int r;
1763
1764 assert(ret);
1765
1766 va_start(ap, ret);
1767 r = json_buildv(&v, ap);
1768 va_end(ap);
1769
1770 if (r < 0)
1771 return r;
1772
1773 u = user_record_new();
1774 if (!u)
1775 return -ENOMEM;
1776
1777 r = user_record_load(u, v, USER_RECORD_LOAD_FULL);
1778 if (r < 0)
1779 return r;
1780
1781 *ret = TAKE_PTR(u);
1782 return 0;
1783}
1784
1785const char *user_record_user_name_and_realm(UserRecord *h) {
1786 assert(h);
1787
1788 /* Return the pre-initialized joined string if it is defined */
1789 if (h->user_name_and_realm_auto)
1790 return h->user_name_and_realm_auto;
1791
1792 /* If it's not defined then we cannot have a realm */
1793 assert(!h->realm);
1794 return h->user_name;
1795}
1796
1797UserStorage user_record_storage(UserRecord *h) {
1798 assert(h);
1799
1800 if (h->storage >= 0)
1801 return h->storage;
1802
1803 return USER_CLASSIC;
1804}
1805
1806const char *user_record_file_system_type(UserRecord *h) {
1807 assert(h);
1808
caf6bd16 1809 return h->file_system_type ?: "btrfs";
71d0b9d4
LP
1810}
1811
1812const char *user_record_skeleton_directory(UserRecord *h) {
1813 assert(h);
1814
1815 return h->skeleton_directory ?: "/etc/skel";
1816}
1817
1818mode_t user_record_access_mode(UserRecord *h) {
1819 assert(h);
1820
f5fbe71d 1821 return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
71d0b9d4
LP
1822}
1823
46c60f72 1824static const char *user_record_home_directory_real(UserRecord *h) {
71d0b9d4
LP
1825 assert(h);
1826
1827 if (h->home_directory)
1828 return h->home_directory;
1829 if (h->home_directory_auto)
1830 return h->home_directory_auto;
1831
1832 /* The root user is special, hence be special about it */
1833 if (streq_ptr(h->user_name, "root"))
1834 return "/root";
1835
1836 return "/";
1837}
1838
46c60f72
LP
1839const char* user_record_home_directory(UserRecord *h) {
1840 assert(h);
1841
1842 if (h->use_fallback && h->fallback_home_directory)
1843 return h->fallback_home_directory;
1844
1845 return user_record_home_directory_real(h);
1846}
1847
71d0b9d4
LP
1848const char *user_record_image_path(UserRecord *h) {
1849 assert(h);
1850
1851 if (h->image_path)
1852 return h->image_path;
1853 if (h->image_path_auto)
1854 return h->image_path_auto;
1855
46c60f72
LP
1856 /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */
1857 return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ?
1858 user_record_home_directory_real(h) : NULL;
71d0b9d4
LP
1859}
1860
1861const char *user_record_cifs_user_name(UserRecord *h) {
1862 assert(h);
1863
1864 return h->cifs_user_name ?: h->user_name;
1865}
1866
1867unsigned long user_record_mount_flags(UserRecord *h) {
1868 assert(h);
1869
1870 return (h->nosuid ? MS_NOSUID : 0) |
1871 (h->noexec ? MS_NOEXEC : 0) |
1872 (h->nodev ? MS_NODEV : 0);
1873}
1874
46c60f72 1875static const char *user_record_shell_real(UserRecord *h) {
71d0b9d4
LP
1876 assert(h);
1877
1878 if (h->shell)
1879 return h->shell;
1880
1881 if (streq_ptr(h->user_name, "root"))
1882 return "/bin/sh";
1883
1884 if (user_record_disposition(h) == USER_REGULAR)
53350c7b 1885 return DEFAULT_USER_SHELL;
71d0b9d4
LP
1886
1887 return NOLOGIN;
1888}
1889
46c60f72
LP
1890const char *user_record_shell(UserRecord *h) {
1891 const char *shell;
1892
1893 assert(h);
1894
1895 shell = user_record_shell_real(h);
1896
1897 /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
1898 * then let's not risk anything. */
1899 if (h->use_fallback && h->fallback_shell)
1900 return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
1901
1902 return shell;
1903}
1904
71d0b9d4
LP
1905const char *user_record_real_name(UserRecord *h) {
1906 assert(h);
1907
1908 return h->real_name ?: h->user_name;
1909}
1910
1911bool user_record_luks_discard(UserRecord *h) {
1912 const char *ip;
1913
1914 assert(h);
1915
1916 if (h->luks_discard >= 0)
1917 return h->luks_discard;
1918
1919 ip = user_record_image_path(h);
1920 if (!ip)
1921 return false;
1922
1923 /* Use discard by default if we are referring to a real block device, but not when operating on a
1924 * loopback device. We want to optimize for SSD and flash storage after all, but we should be careful
1925 * when storing stuff on top of regular file systems in loopback files as doing discard then would
1926 * mean thin provisioning and we should not do that willy-nilly since it means we'll risk EIO later
1927 * on should the disk space to back our file systems not be available. */
1928
1929 return path_startswith(ip, "/dev/");
1930}
1931
5e86c82a
LP
1932bool user_record_luks_offline_discard(UserRecord *h) {
1933 const char *ip;
1934
1935 assert(h);
1936
1937 if (h->luks_offline_discard >= 0)
1938 return h->luks_offline_discard;
1939
1940 /* Discard while we are logged out should generally be a good idea, except when operating directly on
1941 * physical media, where we should just bind it to the online discard mode. */
1942
1943 ip = user_record_image_path(h);
1944 if (!ip)
1945 return false;
1946
1947 if (path_startswith(ip, "/dev/"))
1948 return user_record_luks_discard(h);
1949
1950 return true;
1951}
1952
71d0b9d4
LP
1953const char *user_record_luks_cipher(UserRecord *h) {
1954 assert(h);
1955
1956 return h->luks_cipher ?: "aes";
1957}
1958
1959const char *user_record_luks_cipher_mode(UserRecord *h) {
1960 assert(h);
1961
1962 return h->luks_cipher_mode ?: "xts-plain64";
1963}
1964
1965uint64_t user_record_luks_volume_key_size(UserRecord *h) {
1966 assert(h);
1967
1968 /* We return a value here that can be cast without loss into size_t which is what libcrypsetup expects */
1969
1970 if (h->luks_volume_key_size == UINT64_MAX)
1971 return 256 / 8;
1972
1973 return MIN(h->luks_volume_key_size, SIZE_MAX);
1974}
1975
1976const char* user_record_luks_pbkdf_type(UserRecord *h) {
1977 assert(h);
1978
3f67dccc 1979 return h->luks_pbkdf_type ?: "argon2id";
71d0b9d4
LP
1980}
1981
b04ff66b
AD
1982uint64_t user_record_luks_pbkdf_force_iterations(UserRecord *h) {
1983 assert(h);
1984
1985 /* propagate default "benchmark" mode as itself */
1986 if (h->luks_pbkdf_force_iterations == UINT64_MAX)
1987 return UINT64_MAX;
1988
1989 /* clamp everything else to actually accepted number of iterations of libcryptsetup */
1990 return CLAMP(h->luks_pbkdf_force_iterations, 1U, UINT32_MAX);
1991}
1992
71d0b9d4
LP
1993uint64_t user_record_luks_pbkdf_time_cost_usec(UserRecord *h) {
1994 assert(h);
1995
1996 /* Returns a value with ms granularity, since that's what libcryptsetup expects */
1997
1998 if (h->luks_pbkdf_time_cost_usec == UINT64_MAX)
1999 return 500 * USEC_PER_MSEC; /* We default to 500ms, in contrast to libcryptsetup's 2s, which is just awfully slow on every login */
2000
2001 return MIN(DIV_ROUND_UP(h->luks_pbkdf_time_cost_usec, USEC_PER_MSEC), UINT32_MAX) * USEC_PER_MSEC;
2002}
2003
2004uint64_t user_record_luks_pbkdf_memory_cost(UserRecord *h) {
2005 assert(h);
2006
2007 /* Returns a value with kb granularity, since that's what libcryptsetup expects */
71d0b9d4 2008 if (h->luks_pbkdf_memory_cost == UINT64_MAX)
8b4f88d1
LP
2009 return streq(user_record_luks_pbkdf_type(h), "pbkdf2") ? 0 : /* doesn't apply for simple pbkdf2 */
2010 64*1024*1024; /* We default to 64M, since this should work on smaller systems too */
71d0b9d4
LP
2011
2012 return MIN(DIV_ROUND_UP(h->luks_pbkdf_memory_cost, 1024), UINT32_MAX) * 1024;
2013}
2014
2015uint64_t user_record_luks_pbkdf_parallel_threads(UserRecord *h) {
2016 assert(h);
2017
8b4f88d1
LP
2018 if (h->luks_pbkdf_parallel_threads == UINT64_MAX)
2019 return streq(user_record_luks_pbkdf_type(h), "pbkdf2") ? 0 : /* doesn't apply for simple pbkdf2 */
2020 1; /* We default to 1, since this should work on smaller systems too */
71d0b9d4
LP
2021
2022 return MIN(h->luks_pbkdf_parallel_threads, UINT32_MAX);
2023}
2024
fd83c98e
AD
2025uint64_t user_record_luks_sector_size(UserRecord *h) {
2026 assert(h);
2027
2028 if (h->luks_sector_size == UINT64_MAX)
2029 return 512;
2030
2031 /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */
2032 return CLAMP(UINT64_C(1) << (63 - __builtin_clzl(h->luks_sector_size)), 512U, 4096U);
2033}
2034
71d0b9d4
LP
2035const char *user_record_luks_pbkdf_hash_algorithm(UserRecord *h) {
2036 assert(h);
2037
2038 return h->luks_pbkdf_hash_algorithm ?: "sha512";
2039}
2040
2041gid_t user_record_gid(UserRecord *h) {
2042 assert(h);
2043
2044 if (gid_is_valid(h->gid))
2045 return h->gid;
2046
2047 return (gid_t) h->uid;
2048}
2049
2050UserDisposition user_record_disposition(UserRecord *h) {
2051 assert(h);
2052
2053 if (h->disposition >= 0)
2054 return h->disposition;
2055
2056 /* If not declared, derive from UID */
2057
2058 if (!uid_is_valid(h->uid))
2059 return _USER_DISPOSITION_INVALID;
2060
2061 if (h->uid == 0 || h->uid == UID_NOBODY)
2062 return USER_INTRINSIC;
2063
2064 if (uid_is_system(h->uid))
2065 return USER_SYSTEM;
2066
2067 if (uid_is_dynamic(h->uid))
2068 return USER_DYNAMIC;
2069
2070 if (uid_is_container(h->uid))
2071 return USER_CONTAINER;
2072
2073 if (h->uid > INT32_MAX)
2074 return USER_RESERVED;
2075
2076 return USER_REGULAR;
2077}
2078
2079int user_record_removable(UserRecord *h) {
2080 UserStorage storage;
2081 assert(h);
2082
2083 if (h->removable >= 0)
2084 return h->removable;
2085
2086 /* Refuse to decide for classic records */
2087 storage = user_record_storage(h);
2088 if (h->storage < 0 || h->storage == USER_CLASSIC)
2089 return -1;
2090
2091 /* For now consider only LUKS home directories with a reference by path as removable */
2092 return storage == USER_LUKS && path_startswith(user_record_image_path(h), "/dev/");
2093}
2094
2095uint64_t user_record_ratelimit_interval_usec(UserRecord *h) {
2096 assert(h);
2097
2098 if (h->ratelimit_interval_usec == UINT64_MAX)
2099 return DEFAULT_RATELIMIT_INTERVAL_USEC;
2100
2101 return h->ratelimit_interval_usec;
2102}
2103
2104uint64_t user_record_ratelimit_burst(UserRecord *h) {
2105 assert(h);
2106
2107 if (h->ratelimit_burst == UINT64_MAX)
2108 return DEFAULT_RATELIMIT_BURST;
2109
2110 return h->ratelimit_burst;
2111}
2112
2113bool user_record_can_authenticate(UserRecord *h) {
2114 assert(h);
2115
2116 /* Returns true if there's some form of property configured that the user can authenticate against */
2117
2118 if (h->n_pkcs11_encrypted_key > 0)
2119 return true;
2120
5e4fa456
LP
2121 if (h->n_fido2_hmac_salt > 0)
2122 return true;
2123
71d0b9d4
LP
2124 return !strv_isempty(h->hashed_password);
2125}
2126
86019efa
LP
2127bool user_record_drop_caches(UserRecord *h) {
2128 assert(h);
2129
2130 if (h->drop_caches >= 0)
2131 return h->drop_caches;
2132
2133 /* By default drop caches on fscrypt, not otherwise. */
2134 return user_record_storage(h) == USER_FSCRYPT;
2135}
2136
8bec643c
LP
2137AutoResizeMode user_record_auto_resize_mode(UserRecord *h) {
2138 assert(h);
2139
2140 if (h->auto_resize_mode >= 0)
2141 return h->auto_resize_mode;
2142
2143 return user_record_storage(h) == USER_LUKS ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
2144}
2145
9aa3e5eb
LP
2146uint64_t user_record_rebalance_weight(UserRecord *h) {
2147 assert(h);
2148
2149 if (h->rebalance_weight == REBALANCE_WEIGHT_UNSET)
2150 return REBALANCE_WEIGHT_DEFAULT;
2151
2152 return h->rebalance_weight;
2153}
2154
8e1bc689
LP
2155static uint64_t parse_caps_strv(char **l) {
2156 uint64_t c = 0;
2157 int r;
2158
2159 STRV_FOREACH(i, l) {
2160 r = capability_from_name(*i);
2161 if (r < 0)
2162 log_debug_errno(r, "Don't know capability '%s', ignoring: %m", *i);
2163 else
2164 c |= UINT64_C(1) << r;
2165 }
2166
2167 return c;
2168}
2169
2170uint64_t user_record_capability_bounding_set(UserRecord *h) {
2171 assert(h);
2172
2173 /* Returns UINT64_MAX if no bounding set is configured (!) */
2174
2175 if (!h->capability_bounding_set)
2176 return UINT64_MAX;
2177
2178 return parse_caps_strv(h->capability_bounding_set);
2179}
2180
2181uint64_t user_record_capability_ambient_set(UserRecord *h) {
2182 assert(h);
2183
2184 /* Returns UINT64_MAX if no ambient set is configured (!) */
2185
2186 if (!h->capability_ambient_set)
2187 return UINT64_MAX;
2188
2189 return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
2190}
2191
49e55abb
AV
2192int user_record_languages(UserRecord *h, char ***ret) {
2193 _cleanup_strv_free_ char **l = NULL;
2194 int r;
2195
2196 assert(h);
2197 assert(ret);
2198
2199 if (h->preferred_language) {
2200 l = strv_new(h->preferred_language);
2201 if (!l)
2202 return -ENOMEM;
2203 }
2204
2205 r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true);
2206 if (r < 0)
2207 return r;
2208
2209 *ret = TAKE_PTR(l);
2210 return 0;
2211}
2212
71d0b9d4
LP
2213uint64_t user_record_ratelimit_next_try(UserRecord *h) {
2214 assert(h);
2215
2216 /* Calculates when the it's possible to login next. Returns:
2217 *
2218 * UINT64_MAX → Nothing known
2219 * 0 → Right away
2220 * Any other → Next time in CLOCK_REALTIME in usec (which could be in the past)
2221 */
2222
2223 if (h->ratelimit_begin_usec == UINT64_MAX ||
2224 h->ratelimit_count == UINT64_MAX)
2225 return UINT64_MAX;
2226
61a29a02
LP
2227 if (h->ratelimit_begin_usec > now(CLOCK_REALTIME)) /* If the ratelimit time is in the future, then
2228 * the local clock is probably incorrect. Let's
2229 * not refuse login then. */
2230 return UINT64_MAX;
2231
71d0b9d4
LP
2232 if (h->ratelimit_count < user_record_ratelimit_burst(h))
2233 return 0;
2234
2235 return usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h));
2236}
2237
2238bool user_record_equal(UserRecord *a, UserRecord *b) {
2239 assert(a);
2240 assert(b);
2241
2242 /* We assume that when a record is modified its JSON data is updated at the same time, hence it's
2243 * sufficient to compare the JSON data. */
2244
2245 return json_variant_equal(a->json, b->json);
2246}
2247
2248bool user_record_compatible(UserRecord *a, UserRecord *b) {
2249 assert(a);
2250 assert(b);
2251
d51c4fca 2252 /* If either lacks the regular section, we can't really decide, let's hence say they are
71d0b9d4
LP
2253 * incompatible. */
2254 if (!(a->mask & b->mask & USER_RECORD_REGULAR))
2255 return false;
2256
2257 return streq_ptr(a->user_name, b->user_name) &&
2258 streq_ptr(a->realm, b->realm);
2259}
2260
2261int user_record_compare_last_change(UserRecord *a, UserRecord *b) {
2262 assert(a);
2263 assert(b);
2264
2265 if (a->last_change_usec == b->last_change_usec)
2266 return 0;
2267
2268 /* Always consider a record with a timestamp newer than one without */
2269 if (a->last_change_usec == UINT64_MAX)
2270 return -1;
2271 if (b->last_change_usec == UINT64_MAX)
2272 return 1;
2273
2274 return CMP(a->last_change_usec, b->last_change_usec);
2275}
2276
2277int user_record_clone(UserRecord *h, UserRecordLoadFlags flags, UserRecord **ret) {
2278 _cleanup_(user_record_unrefp) UserRecord *c = NULL;
2279 int r;
2280
2281 assert(h);
2282 assert(ret);
2283
2284 c = user_record_new();
2285 if (!c)
2286 return -ENOMEM;
2287
2288 r = user_record_load(c, h->json, flags);
2289 if (r < 0)
2290 return r;
2291
2292 *ret = TAKE_PTR(c);
2293 return 0;
2294}
2295
2296int user_record_masked_equal(UserRecord *a, UserRecord *b, UserRecordMask mask) {
2297 _cleanup_(user_record_unrefp) UserRecord *x = NULL, *y = NULL;
2298 int r;
2299
2300 assert(a);
2301 assert(b);
2302
2303 /* Compares the two records, but ignores anything not listed in the specified mask */
2304
2305 if ((a->mask & ~mask) != 0) {
bfc0cc1a 2306 r = user_record_clone(a, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &x);
71d0b9d4
LP
2307 if (r < 0)
2308 return r;
2309
2310 a = x;
2311 }
2312
2313 if ((b->mask & ~mask) != 0) {
bfc0cc1a 2314 r = user_record_clone(b, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &y);
71d0b9d4
LP
2315 if (r < 0)
2316 return r;
2317
2318 b = y;
2319 }
2320
2321 return user_record_equal(a, b);
2322}
2323
2324int user_record_test_blocked(UserRecord *h) {
2325 usec_t n;
2326
2327 /* Checks whether access to the specified user shall be allowed at the moment. Returns:
2328 *
2329 * -ESTALE: Record is from the future
2330 * -ENOLCK: Record is blocked
2331 * -EL2HLT: Record is not valid yet
2332 * -EL3HLT: Record is not valid anymore
2333 *
2334 */
2335
2336 assert(h);
2337
71d0b9d4
LP
2338 if (h->locked > 0)
2339 return -ENOLCK;
2340
51a95db6
LP
2341 n = now(CLOCK_REALTIME);
2342
71d0b9d4
LP
2343 if (h->not_before_usec != UINT64_MAX && n < h->not_before_usec)
2344 return -EL2HLT;
2345 if (h->not_after_usec != UINT64_MAX && n > h->not_after_usec)
2346 return -EL3HLT;
2347
51a95db6
LP
2348 if (h->last_change_usec != UINT64_MAX &&
2349 h->last_change_usec > n) /* Complain during log-ins when the record is from the future */
2350 return -ESTALE;
2351
71d0b9d4
LP
2352 return 0;
2353}
2354
2355int user_record_test_password_change_required(UserRecord *h) {
2356 bool change_permitted;
2357 usec_t n;
2358
2359 assert(h);
2360
2361 /* Checks whether the user must change the password when logging in
2362
2363 -EKEYREVOKED: Change password now because admin said so
2364 -EOWNERDEAD: Change password now because it expired
2365 -EKEYREJECTED: Password is expired, no changing is allowed
2366 -EKEYEXPIRED: Password is about to expire, warn user
2367 -ENETDOWN: Record has expiration info but no password change timestamp
2368 -EROFS: No password change required nor permitted
3e0b5486 2369 -ESTALE: RTC likely incorrect, last password change is in the future
71d0b9d4
LP
2370 0: No password change required, but permitted
2371 */
2372
162392b7 2373 /* If a password change request has been set explicitly, it overrides everything */
71d0b9d4
LP
2374 if (h->password_change_now > 0)
2375 return -EKEYREVOKED;
2376
2377 n = now(CLOCK_REALTIME);
2378
3e0b5486
LP
2379 /* Password change in the future? Then our RTC is likely incorrect */
2380 if (h->last_password_change_usec != UINT64_MAX &&
2381 h->last_password_change_usec > n &&
2382 (h->password_change_min_usec != UINT64_MAX ||
2383 h->password_change_max_usec != UINT64_MAX ||
2384 h->password_change_inactive_usec != UINT64_MAX))
2385 return -ESTALE;
2386
71d0b9d4
LP
2387 /* Then, let's check if password changing is currently allowed at all */
2388 if (h->password_change_min_usec != UINT64_MAX) {
2389
2390 /* Expiry configured but no password change timestamp known? */
2391 if (h->last_password_change_usec == UINT64_MAX)
2392 return -ENETDOWN;
2393
2394 if (h->password_change_min_usec >= UINT64_MAX - h->last_password_change_usec)
2395 change_permitted = false;
2396 else
2397 change_permitted = n >= h->last_password_change_usec + h->password_change_min_usec;
2398
2399 } else
2400 change_permitted = true;
2401
2402 /* Let's check whether the password has expired. */
2403 if (!(h->password_change_max_usec == UINT64_MAX ||
2404 h->password_change_max_usec >= UINT64_MAX - h->last_password_change_usec)) {
2405
2406 uint64_t change_before;
2407
2408 /* Expiry configured but no password change timestamp known? */
2409 if (h->last_password_change_usec == UINT64_MAX)
2410 return -ENETDOWN;
2411
2412 /* Password is in inactive phase? */
2413 if (h->password_change_inactive_usec != UINT64_MAX &&
2414 h->password_change_inactive_usec < UINT64_MAX - h->password_change_max_usec) {
2415 usec_t added;
2416
2417 added = h->password_change_inactive_usec + h->password_change_max_usec;
2418 if (added < UINT64_MAX - h->last_password_change_usec &&
2419 n >= h->last_password_change_usec + added)
2420 return -EKEYREJECTED;
2421 }
2422
2423 /* Password needs to be changed now? */
2424 change_before = h->last_password_change_usec + h->password_change_max_usec;
2425 if (n >= change_before)
2426 return change_permitted ? -EOWNERDEAD : -EKEYREJECTED;
2427
2428 /* Warn user? */
2429 if (h->password_change_warn_usec != UINT64_MAX &&
2430 (change_before < h->password_change_warn_usec ||
2431 n >= change_before - h->password_change_warn_usec))
2432 return change_permitted ? -EKEYEXPIRED : -EROFS;
2433 }
2434
2435 /* No password changing necessary */
2436 return change_permitted ? 0 : -EROFS;
2437}
2438
1b466c09
AV
2439int suitable_blob_filename(const char *name) {
2440 /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
2441 return filename_is_valid(name) &&
2442 in_charset(name, URI_UNRESERVED) &&
2443 name[0] != '.';
2444}
2445
71d0b9d4
LP
2446static const char* const user_storage_table[_USER_STORAGE_MAX] = {
2447 [USER_CLASSIC] = "classic",
2448 [USER_LUKS] = "luks",
2449 [USER_DIRECTORY] = "directory",
2450 [USER_SUBVOLUME] = "subvolume",
2451 [USER_FSCRYPT] = "fscrypt",
2452 [USER_CIFS] = "cifs",
2453};
2454
2455DEFINE_STRING_TABLE_LOOKUP(user_storage, UserStorage);
2456
2457static const char* const user_disposition_table[_USER_DISPOSITION_MAX] = {
2458 [USER_INTRINSIC] = "intrinsic",
2459 [USER_SYSTEM] = "system",
2460 [USER_DYNAMIC] = "dynamic",
2461 [USER_REGULAR] = "regular",
2462 [USER_CONTAINER] = "container",
2463 [USER_RESERVED] = "reserved",
2464};
2465
2466DEFINE_STRING_TABLE_LOOKUP(user_disposition, UserDisposition);
8bec643c
LP
2467
2468static const char* const auto_resize_mode_table[_AUTO_RESIZE_MODE_MAX] = {
2469 [AUTO_RESIZE_OFF] = "off",
2470 [AUTO_RESIZE_GROW] = "grow",
2471 [AUTO_RESIZE_SHRINK_AND_GROW] = "shrink-and-grow",
2472};
2473
2474DEFINE_STRING_TABLE_LOOKUP(auto_resize_mode, AutoResizeMode);