• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 13210122091

07 Feb 2025 10:56PM UTC coverage: 71.748% (-0.07%) from 71.813%
13210122091

push

github

web-flow
network: allow to configure routing policy rule even if requesting interface is not activated yet (#36257)

Fixes a regression caused by 4f8b153d9
(v257).
Fixes #36244.

16 of 16 new or added lines in 2 files covered. (100.0%)

3079 existing lines in 54 files now uncovered.

292917 of 408258 relevant lines covered (71.75%)

714241.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.34
/src/shared/user-record.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/mount.h>
4

5
#include "bitfield.h"
6
#include "cap-list.h"
7
#include "cgroup-util.h"
8
#include "dns-domain.h"
9
#include "env-util.h"
10
#include "fs-util.h"
11
#include "glyph-util.h"
12
#include "hexdecoct.h"
13
#include "hostname-util.h"
14
#include "json-util.h"
15
#include "locale-util.h"
16
#include "memory-util.h"
17
#include "path-util.h"
18
#include "percent-util.h"
19
#include "pkcs11-util.h"
20
#include "rlimit-util.h"
21
#include "sha256.h"
22
#include "string-table.h"
23
#include "strv.h"
24
#include "uid-classification.h"
25
#include "user-record.h"
26
#include "user-util.h"
27
#include "utf8.h"
28

29
#define DEFAULT_RATELIMIT_BURST 30
30
#define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE)
31

32
UserRecord* user_record_new(void) {
3,680✔
33
        UserRecord *h;
3,680✔
34

35
        h = new(UserRecord, 1);
3,680✔
36
        if (!h)
3,680✔
37
                return NULL;
38

39
        *h = (UserRecord) {
3,680✔
40
                .n_ref = 1,
41
                .disposition = _USER_DISPOSITION_INVALID,
42
                .last_change_usec = UINT64_MAX,
43
                .last_password_change_usec = UINT64_MAX,
44
                .umask = MODE_INVALID,
45
                .nice_level = INT_MAX,
46
                .not_before_usec = UINT64_MAX,
47
                .not_after_usec = UINT64_MAX,
48
                .locked = -1,
49
                .storage = _USER_STORAGE_INVALID,
50
                .access_mode = MODE_INVALID,
51
                .disk_size = UINT64_MAX,
52
                .disk_size_relative = UINT64_MAX,
53
                .tasks_max = UINT64_MAX,
54
                .memory_high = UINT64_MAX,
55
                .memory_max = UINT64_MAX,
56
                .cpu_weight = UINT64_MAX,
57
                .io_weight = UINT64_MAX,
58
                .uid = UID_INVALID,
59
                .gid = GID_INVALID,
60
                .nodev = true,
61
                .nosuid = true,
62
                .luks_discard = -1,
63
                .luks_offline_discard = -1,
64
                .luks_volume_key_size = UINT64_MAX,
65
                .luks_pbkdf_force_iterations = UINT64_MAX,
66
                .luks_pbkdf_time_cost_usec = UINT64_MAX,
67
                .luks_pbkdf_memory_cost = UINT64_MAX,
68
                .luks_pbkdf_parallel_threads = UINT64_MAX,
69
                .luks_sector_size = UINT64_MAX,
70
                .disk_usage = UINT64_MAX,
71
                .disk_free = UINT64_MAX,
72
                .disk_ceiling = UINT64_MAX,
73
                .disk_floor = UINT64_MAX,
74
                .signed_locally = -1,
75
                .good_authentication_counter = UINT64_MAX,
76
                .bad_authentication_counter = UINT64_MAX,
77
                .last_good_authentication_usec = UINT64_MAX,
78
                .last_bad_authentication_usec = UINT64_MAX,
79
                .ratelimit_begin_usec = UINT64_MAX,
80
                .ratelimit_count = UINT64_MAX,
81
                .ratelimit_interval_usec = UINT64_MAX,
82
                .ratelimit_burst = UINT64_MAX,
83
                .removable = -1,
84
                .enforce_password_policy = -1,
85
                .auto_login = -1,
86
                .stop_delay_usec = UINT64_MAX,
87
                .kill_processes = -1,
88
                .password_change_min_usec = UINT64_MAX,
89
                .password_change_max_usec = UINT64_MAX,
90
                .password_change_warn_usec = UINT64_MAX,
91
                .password_change_inactive_usec = UINT64_MAX,
92
                .password_change_now = -1,
93
                .pkcs11_protected_authentication_path_permitted = -1,
94
                .fido2_user_presence_permitted = -1,
95
                .fido2_user_verification_permitted = -1,
96
                .drop_caches = -1,
97
                .auto_resize_mode = _AUTO_RESIZE_MODE_INVALID,
98
                .rebalance_weight = REBALANCE_WEIGHT_UNSET,
99
                .tmp_limit = TMPFS_LIMIT_NULL,
100
                .dev_shm_limit = TMPFS_LIMIT_NULL,
101
        };
102

103
        return h;
3,680✔
104
}
105

106
static void pkcs11_encrypted_key_done(Pkcs11EncryptedKey *k) {
×
107
        if (!k)
×
108
                return;
109

110
        free(k->uri);
×
111
        erase_and_free(k->data);
×
112
        erase_and_free(k->hashed_password);
×
113
}
114

115
static void fido2_hmac_credential_done(Fido2HmacCredential *c) {
×
116
        if (!c)
×
117
                return;
118

119
        free(c->id);
×
120
}
121

122
static void fido2_hmac_salt_done(Fido2HmacSalt *s) {
×
123
        if (!s)
×
124
                return;
125

126
        fido2_hmac_credential_done(&s->credential);
×
127
        erase_and_free(s->salt);
×
128
        erase_and_free(s->hashed_password);
×
129
}
130

131
static void recovery_key_done(RecoveryKey *k) {
×
132
        if (!k)
×
133
                return;
134

135
        free(k->type);
×
136
        erase_and_free(k->hashed_password);
×
137
}
138

139
static UserRecord* user_record_free(UserRecord *h) {
3,665✔
140
        if (!h)
3,665✔
141
                return NULL;
142

143
        free(h->user_name);
3,665✔
144
        free(h->realm);
3,665✔
145
        free(h->user_name_and_realm_auto);
3,665✔
146
        strv_free(h->aliases);
3,665✔
147
        free(h->real_name);
3,665✔
148
        free(h->email_address);
3,665✔
149
        erase_and_free(h->password_hint);
3,665✔
150
        free(h->location);
3,665✔
151
        free(h->icon_name);
3,665✔
152

153
        free(h->blob_directory);
3,665✔
154
        hashmap_free(h->blob_manifest);
3,665✔
155

156
        free(h->shell);
3,665✔
157

158
        strv_free(h->environment);
3,665✔
159
        free(h->time_zone);
3,665✔
160
        free(h->preferred_language);
3,665✔
161
        strv_free(h->additional_languages);
3,665✔
162
        rlimit_free_all(h->rlimits);
3,665✔
163

164
        free(h->skeleton_directory);
3,665✔
165

166
        strv_free_erase(h->hashed_password);
3,665✔
167
        strv_free_erase(h->ssh_authorized_keys);
3,665✔
168
        strv_free_erase(h->password);
3,665✔
169
        strv_free_erase(h->token_pin);
3,665✔
170

171
        free(h->cifs_service);
3,665✔
172
        free(h->cifs_user_name);
3,665✔
173
        free(h->cifs_domain);
3,665✔
174
        free(h->cifs_extra_mount_options);
3,665✔
175

176
        free(h->image_path);
3,665✔
177
        free(h->image_path_auto);
3,665✔
178
        free(h->home_directory);
3,665✔
179
        free(h->home_directory_auto);
3,665✔
180

181
        free(h->fallback_shell);
3,665✔
182
        free(h->fallback_home_directory);
3,665✔
183

184
        strv_free(h->member_of);
3,665✔
185
        strv_free(h->capability_bounding_set);
3,665✔
186
        strv_free(h->capability_ambient_set);
3,665✔
187

188
        free(h->file_system_type);
3,665✔
189
        free(h->luks_cipher);
3,665✔
190
        free(h->luks_cipher_mode);
3,665✔
191
        free(h->luks_pbkdf_hash_algorithm);
3,665✔
192
        free(h->luks_pbkdf_type);
3,665✔
193
        free(h->luks_extra_mount_options);
3,665✔
194

195
        free(h->state);
3,665✔
196
        free(h->service);
3,665✔
197

198
        free(h->preferred_session_type);
3,665✔
199
        free(h->preferred_session_launcher);
3,665✔
200

201
        strv_free(h->pkcs11_token_uri);
3,665✔
202
        for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++)
3,665✔
203
                pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
×
204
        free(h->pkcs11_encrypted_key);
3,665✔
205

206
        for (size_t i = 0; i < h->n_fido2_hmac_credential; i++)
3,665✔
207
                fido2_hmac_credential_done(h->fido2_hmac_credential + i);
×
208
        for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
3,665✔
209
                fido2_hmac_salt_done(h->fido2_hmac_salt + i);
×
210

211
        strv_free(h->recovery_key_type);
3,665✔
212
        for (size_t i = 0; i < h->n_recovery_key; i++)
3,665✔
213
                recovery_key_done(h->recovery_key + i);
×
214

215
        strv_free(h->self_modifiable_fields);
3,665✔
216
        strv_free(h->self_modifiable_blobs);
3,665✔
217
        strv_free(h->self_modifiable_privileged);
3,665✔
218

219
        free(h->default_area);
3,665✔
220

221
        sd_json_variant_unref(h->json);
3,665✔
222

223
        return mfree(h);
3,665✔
224
}
225

226
DEFINE_TRIVIAL_REF_UNREF_FUNC(UserRecord, user_record, user_record_free);
5,749✔
227

228
int json_dispatch_realm(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
93✔
229
        char **s = userdata;
93✔
230
        const char *n;
93✔
231
        int r;
93✔
232

233
        if (sd_json_variant_is_null(variant)) {
93✔
UNCOV
234
                *s = mfree(*s);
×
UNCOV
235
                return 0;
×
236
        }
237

238
        if (!sd_json_variant_is_string(variant))
93✔
UNCOV
239
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
240

241
        n = sd_json_variant_string(variant);
93✔
242
        r = dns_name_is_valid(n);
93✔
243
        if (r < 0)
93✔
244
                return json_log(variant, flags, r, "Failed to check if JSON field '%s' is a valid DNS domain.", strna(name));
×
245
        if (r == 0)
93✔
UNCOV
246
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid DNS domain.", strna(name));
×
247

248
        r = free_and_strdup(s, n);
93✔
249
        if (r < 0)
93✔
UNCOV
250
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
251

252
        return 0;
253
}
254

255
int json_dispatch_gecos(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
1,093✔
256
        char **s = userdata;
1,093✔
257
        const char *n;
1,093✔
258

259
        if (sd_json_variant_is_null(variant)) {
1,093✔
UNCOV
260
                *s = mfree(*s);
×
UNCOV
261
                return 0;
×
262
        }
263

264
        if (!sd_json_variant_is_string(variant))
1,093✔
UNCOV
265
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
266

267
        n = sd_json_variant_string(variant);
1,093✔
268
        if (valid_gecos(n)) {
1,093✔
269
                if (free_and_strdup(s, n) < 0)
1,093✔
270
                        return json_log_oom(variant, flags);
×
271
        } else {
272
                _cleanup_free_ char *m = NULL;
×
273

274
                json_log(variant, flags|SD_JSON_DEBUG, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid GECOS compatible string, mangling.", strna(name));
×
275

276
                m = mangle_gecos(n);
×
UNCOV
277
                if (!m)
×
278
                        return json_log_oom(variant, flags);
×
279

UNCOV
280
                free_and_replace(*s, m);
×
281
        }
282

283
        return 0;
284
}
285

286
static int json_dispatch_nice(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
287
        int *nl = userdata;
×
288
        int64_t m;
×
289

290
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
291
                *nl = INT_MAX;
×
UNCOV
292
                return 0;
×
293
        }
294

UNCOV
295
        if (!sd_json_variant_is_integer(variant))
×
296
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
297

298
        m = sd_json_variant_integer(variant);
×
UNCOV
299
        if (m < PRIO_MIN || m >= PRIO_MAX)
×
300
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not a valid nice level.", strna(name));
×
301

UNCOV
302
        *nl = m;
×
UNCOV
303
        return 0;
×
304
}
305

UNCOV
306
static int json_dispatch_rlimit_value(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
307
        rlim_t *ret = userdata;
×
308

309
        if (sd_json_variant_is_null(variant))
×
310
                *ret = RLIM_INFINITY;
×
UNCOV
311
        else if (sd_json_variant_is_unsigned(variant)) {
×
312
                uint64_t w;
×
313

314
                w = sd_json_variant_unsigned(variant);
×
UNCOV
315
                if (w == RLIM_INFINITY || (uint64_t) w != sd_json_variant_unsigned(variant))
×
316
                        return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "Resource limit value '%s' is out of range.", name);
×
317

318
                *ret = (rlim_t) w;
×
319
        } else
UNCOV
320
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit value '%s' is not an unsigned integer.", name);
×
321

322
        return 0;
323
}
324

325
static int json_dispatch_rlimits(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
326
        struct rlimit** limits = userdata;
×
327
        sd_json_variant *value;
×
UNCOV
328
        const char *key;
×
329
        int r;
×
330

331
        assert_se(limits);
×
332

333
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
334
                rlimit_free_all(limits);
×
UNCOV
335
                return 0;
×
336
        }
337

UNCOV
338
        if (!sd_json_variant_is_object(variant))
×
339
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
340

341
        JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
×
342
                sd_json_variant *jcur, *jmax;
×
343
                struct rlimit rl;
×
UNCOV
344
                const char *p;
×
345
                int l;
×
346

UNCOV
347
                p = startswith(key, "RLIMIT_");
×
UNCOV
348
                if (!p)
×
349
                        l = -SYNTHETIC_ERRNO(EINVAL);
350
                else
351
                        l = rlimit_from_string(p);
×
UNCOV
352
                if (l < 0)
×
353
                        return json_log(variant, flags, l, "Resource limit '%s' not known.", key);
×
354

UNCOV
355
                if (!sd_json_variant_is_object(value))
×
356
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' has invalid value.", key);
×
357

UNCOV
358
                if (sd_json_variant_elements(value) != 4)
×
359
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' value is does not have two fields as expected.", key);
×
360

361
                jcur = sd_json_variant_by_key(value, "cur");
×
362
                if (!jcur)
×
363
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'cur' field.", key);
×
UNCOV
364
                r = json_dispatch_rlimit_value("cur", jcur, flags, &rl.rlim_cur);
×
UNCOV
365
                if (r < 0)
×
366
                        return r;
367

368
                jmax = sd_json_variant_by_key(value, "max");
×
369
                if (!jmax)
×
370
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'max' field.", key);
×
UNCOV
371
                r = json_dispatch_rlimit_value("max", jmax, flags, &rl.rlim_max);
×
UNCOV
372
                if (r < 0)
×
373
                        return r;
374

UNCOV
375
                if (limits[l])
×
376
                        *(limits[l]) = rl;
×
377
                else {
378
                        limits[l] = newdup(struct rlimit, &rl, 1);
×
UNCOV
379
                        if (!limits[l])
×
UNCOV
380
                                return log_oom();
×
381
                }
382
        }
383

UNCOV
384
        return 0;
×
385
}
386

387
static int json_dispatch_filename_or_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
2,481✔
388
        char **s = ASSERT_PTR(userdata);
2,481✔
389
        const char *n;
2,481✔
390
        int r;
2,481✔
391

392
        if (sd_json_variant_is_null(variant)) {
2,481✔
UNCOV
393
                *s = mfree(*s);
×
UNCOV
394
                return 0;
×
395
        }
396

397
        if (!sd_json_variant_is_string(variant))
2,481✔
UNCOV
398
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
399

400
        n = sd_json_variant_string(variant);
2,481✔
401
        if (!filename_is_valid(n) && !path_is_normalized(n))
2,481✔
UNCOV
402
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid file name or normalized path.", strna(name));
×
403

404
        r = free_and_strdup(s, n);
2,481✔
405
        if (r < 0)
2,481✔
UNCOV
406
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
407

408
        return 0;
409
}
410

411
static int json_dispatch_home_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
3,439✔
412
        char **s = userdata;
3,439✔
413
        const char *n;
3,439✔
414
        int r;
3,439✔
415

416
        if (sd_json_variant_is_null(variant)) {
3,439✔
UNCOV
417
                *s = mfree(*s);
×
UNCOV
418
                return 0;
×
419
        }
420

421
        if (!sd_json_variant_is_string(variant))
3,439✔
UNCOV
422
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
423

424
        n = sd_json_variant_string(variant);
3,439✔
425
        if (!valid_home(n))
3,439✔
UNCOV
426
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid home directory path.", strna(name));
×
427

428
        r = free_and_strdup(s, n);
3,439✔
429
        if (r < 0)
3,439✔
UNCOV
430
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
431

432
        return 0;
433
}
434

435
static int json_dispatch_image_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
1,074✔
436
        char **s = userdata;
1,074✔
437
        const char *n;
1,074✔
438
        int r;
1,074✔
439

440
        if (sd_json_variant_is_null(variant)) {
1,074✔
UNCOV
441
                *s = mfree(*s);
×
UNCOV
442
                return 0;
×
443
        }
444

445
        if (!sd_json_variant_is_string(variant))
1,074✔
UNCOV
446
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
447

448
        n = sd_json_variant_string(variant);
1,074✔
449
        if (empty_or_root(n) || !path_is_valid(n) || !path_is_absolute(n))
3,222✔
UNCOV
450
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid image path.", strna(name));
×
451

452
        r = free_and_strdup(s, n);
1,074✔
453
        if (r < 0)
1,074✔
UNCOV
454
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
455

456
        return 0;
457
}
458

459
static int json_dispatch_umask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
460
        mode_t *m = userdata;
×
461
        uint64_t k;
×
462

463
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
464
                *m = MODE_INVALID;
×
UNCOV
465
                return 0;
×
466
        }
467

UNCOV
468
        if (!sd_json_variant_is_unsigned(variant))
×
469
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
×
470

471
        k = sd_json_variant_unsigned(variant);
×
UNCOV
472
        if (k > 0777)
×
UNCOV
473
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
×
474
                                "JSON field '%s' outside of valid range 0%s0777.",
475
                                strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
476

UNCOV
477
        *m = (mode_t) k;
×
UNCOV
478
        return 0;
×
479
}
480

481
static int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
482
        mode_t *m = userdata;
×
483
        uint64_t k;
×
484

485
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
486
                *m = MODE_INVALID;
×
UNCOV
487
                return 0;
×
488
        }
489

UNCOV
490
        if (!sd_json_variant_is_unsigned(variant))
×
491
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
×
492

493
        k = sd_json_variant_unsigned(variant);
×
UNCOV
494
        if (k > 07777)
×
UNCOV
495
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
×
496
                                "JSON field '%s' outside of valid range 0%s07777.",
497
                                strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
498

UNCOV
499
        *m = (mode_t) k;
×
UNCOV
500
        return 0;
×
501
}
502

503
static int json_dispatch_locale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
504
        char **s = userdata;
×
UNCOV
505
        const char *n;
×
506
        int r;
×
507

508
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
509
                *s = mfree(*s);
×
UNCOV
510
                return 0;
×
511
        }
512

UNCOV
513
        if (!sd_json_variant_is_string(variant))
×
514
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
515

516
        n = sd_json_variant_string(variant);
×
517

UNCOV
518
        if (!locale_is_valid(n))
×
519
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid locale.", strna(name));
×
520

521
        r = free_and_strdup(s, n);
×
UNCOV
522
        if (r < 0)
×
UNCOV
523
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
524

525
        return 0;
526
}
527

528
static int json_dispatch_locales(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
529
        _cleanup_strv_free_ char **n = NULL;
×
530
        char ***l = userdata;
×
531
        const char *locale;
×
UNCOV
532
        sd_json_variant *e;
×
533
        int r;
×
534

535
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
536
                *l = strv_free(*l);
×
UNCOV
537
                return 0;
×
538
        }
539

UNCOV
540
        if (!sd_json_variant_is_array(variant))
×
541
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
542

543
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
UNCOV
544
                if (!sd_json_variant_is_string(e))
×
545
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
546

547
                locale = sd_json_variant_string(e);
×
UNCOV
548
                if (!locale_is_valid(locale))
×
549
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name));
×
550

551
                r = strv_extend(&n, locale);
×
UNCOV
552
                if (r < 0)
×
UNCOV
553
                        return json_log_oom(variant, flags);
×
554
        }
555

UNCOV
556
        return strv_free_and_replace(*l, n);
×
557
}
558

559
JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
1,571✔
560
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
1,598✔
561

UNCOV
562
static int json_dispatch_tasks_or_memory_max(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
563
        uint64_t *limit = userdata, k;
×
564

565
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
566
                *limit = UINT64_MAX;
×
UNCOV
567
                return 0;
×
568
        }
569

UNCOV
570
        if (!sd_json_variant_is_unsigned(variant))
×
571
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
×
572

573
        k = sd_json_variant_unsigned(variant);
×
UNCOV
574
        if (k <= 0 || k >= UINT64_MAX)
×
UNCOV
575
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
576
                                "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
577
                                strna(name), (uint64_t) 1, special_glyph(SPECIAL_GLYPH_ELLIPSIS), UINT64_MAX-1);
578

UNCOV
579
        *limit = k;
×
UNCOV
580
        return 0;
×
581
}
582

UNCOV
583
static int json_dispatch_weight(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
584
        uint64_t *weight = userdata, k;
×
585

586
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
587
                *weight = UINT64_MAX;
×
UNCOV
588
                return 0;
×
589
        }
590

UNCOV
591
        if (!sd_json_variant_is_unsigned(variant))
×
592
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
×
593

594
        k = sd_json_variant_unsigned(variant);
×
UNCOV
595
        if (k <= CGROUP_WEIGHT_MIN || k >= CGROUP_WEIGHT_MAX)
×
UNCOV
596
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
597
                                "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
598
                                strna(name), (uint64_t) CGROUP_WEIGHT_MIN,
599
                                special_glyph(SPECIAL_GLYPH_ELLIPSIS), (uint64_t) CGROUP_WEIGHT_MAX);
600

UNCOV
601
        *weight = k;
×
UNCOV
602
        return 0;
×
603
}
604

605
int json_dispatch_user_group_list(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
183✔
606
        char ***list = ASSERT_PTR(userdata);
183✔
607
        _cleanup_strv_free_ char **l = NULL;
183✔
608
        int r;
183✔
609

610
        if (!sd_json_variant_is_array(variant))
183✔
UNCOV
611
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
612

613
        sd_json_variant *e;
183✔
614
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
423✔
615
                if (!sd_json_variant_is_string(e))
240✔
UNCOV
616
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
×
617

618
                if (!valid_user_group_name(sd_json_variant_string(e), FLAGS_SET(flags, SD_JSON_RELAX) ? VALID_USER_RELAX : 0))
240✔
UNCOV
619
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", sd_json_variant_string(e));
×
620

621
                r = strv_extend(&l, sd_json_variant_string(e));
240✔
622
                if (r < 0)
240✔
UNCOV
623
                        return json_log(e, flags, r, "Failed to append array element: %m");
×
624
        }
625

626
        r = strv_extend_strv_consume(list, TAKE_PTR(l), /* filter_duplicates = */ true);
183✔
627
        if (r < 0)
183✔
UNCOV
628
                return json_log(variant, flags, r, "Failed to merge user/group arrays: %m");
×
629

630
        return 0;
631
}
632

633
static int dispatch_secret(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
100✔
634

635
        static const sd_json_dispatch_field secret_dispatch_table[] = {
100✔
636
                { "password",                                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, password),                                       0 },
637
                { "tokenPin",                                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, token_pin),                                      0 },
638
                { "pkcs11Pin",   /* legacy alias */             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, token_pin),                                      0 },
639
                { "pkcs11ProtectedAuthenticationPathPermitted", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
640
                { "fido2UserPresencePermitted",                 SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, fido2_user_presence_permitted),                  0 },
641
                { "fido2UserVerificationPermitted",             SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, fido2_user_verification_permitted),              0 },
642
                {},
643
        };
644

645
        return sd_json_dispatch(variant, secret_dispatch_table, flags, userdata);
100✔
646
}
647

648
static int dispatch_pkcs11_uri(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
649
        char **s = userdata;
×
UNCOV
650
        const char *n;
×
651
        int r;
×
652

653
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
654
                *s = mfree(*s);
×
UNCOV
655
                return 0;
×
656
        }
657

UNCOV
658
        if (!sd_json_variant_is_string(variant))
×
659
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
660

661
        n = sd_json_variant_string(variant);
×
UNCOV
662
        if (!pkcs11_uri_valid(n))
×
663
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
×
664

665
        r = free_and_strdup(s, n);
×
UNCOV
666
        if (r < 0)
×
UNCOV
667
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
668

669
        return 0;
670
}
671

672
static int dispatch_pkcs11_uri_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
673
        _cleanup_strv_free_ char **z = NULL;
×
674
        char ***l = userdata;
×
UNCOV
675
        sd_json_variant *e;
×
676
        int r;
×
677

678
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
679
                *l = strv_free(*l);
×
UNCOV
680
                return 0;
×
681
        }
682

UNCOV
683
        if (sd_json_variant_is_string(variant)) {
×
684
                const char *n;
×
685

686
                n = sd_json_variant_string(variant);
×
UNCOV
687
                if (!pkcs11_uri_valid(n))
×
688
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
×
689

690
                z = strv_new(n);
×
UNCOV
691
                if (!z)
×
UNCOV
692
                        return log_oom();
×
693

694
        } else {
695

UNCOV
696
                if (!sd_json_variant_is_array(variant))
×
697
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string or array of strings.", strna(name));
×
698

UNCOV
699
                JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
700
                        const char *n;
×
701

UNCOV
702
                        if (!sd_json_variant_is_string(e))
×
703
                                return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
×
704

705
                        n = sd_json_variant_string(e);
×
UNCOV
706
                        if (!pkcs11_uri_valid(n))
×
707
                                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);
×
708

709
                        r = strv_extend(&z, n);
×
UNCOV
710
                        if (r < 0)
×
UNCOV
711
                                return log_oom();
×
712
                }
713
        }
714

UNCOV
715
        strv_free_and_replace(*l, z);
×
UNCOV
716
        return 0;
×
717
}
718

719
static int dispatch_pkcs11_key_data(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
720
        Pkcs11EncryptedKey *k = userdata;
×
721
        size_t l;
×
UNCOV
722
        void *b;
×
723
        int r;
×
724

725
        if (sd_json_variant_is_null(variant)) {
×
726
                k->data = erase_and_free(k->data);
×
UNCOV
727
                k->size = 0;
×
UNCOV
728
                return 0;
×
729
        }
730

731
        r = sd_json_variant_unbase64(variant, &b, &l);
×
UNCOV
732
        if (r < 0)
×
733
                return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
×
734

735
        erase_and_free(k->data);
×
UNCOV
736
        k->data = b;
×
737
        k->size = l;
×
738

UNCOV
739
        return 0;
×
740
}
741

742
static int dispatch_pkcs11_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
743
        UserRecord *h = userdata;
×
UNCOV
744
        sd_json_variant *e;
×
745
        int r;
×
746

UNCOV
747
        if (!sd_json_variant_is_array(variant))
×
748
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
×
749

UNCOV
750
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
UNCOV
751
                static const sd_json_dispatch_field pkcs11_key_dispatch_table[] = {
×
752
                        { "uri",            SD_JSON_VARIANT_STRING, dispatch_pkcs11_uri,      offsetof(Pkcs11EncryptedKey, uri),             SD_JSON_MANDATORY },
753
                        { "data",           SD_JSON_VARIANT_STRING, dispatch_pkcs11_key_data, 0,                                             SD_JSON_MANDATORY },
754
                        { "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string,  offsetof(Pkcs11EncryptedKey, hashed_password), SD_JSON_MANDATORY },
755
                        {},
756
                };
757

UNCOV
758
                if (!sd_json_variant_is_object(e))
×
759
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
760

UNCOV
761
                if (!GREEDY_REALLOC(h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key + 1))
×
762
                        return log_oom();
×
763

UNCOV
764
                Pkcs11EncryptedKey *k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key;
×
765
                *k = (Pkcs11EncryptedKey) {};
×
766

767
                r = sd_json_dispatch(e, pkcs11_key_dispatch_table, flags, k);
×
768
                if (r < 0) {
×
UNCOV
769
                        pkcs11_encrypted_key_done(k);
×
UNCOV
770
                        return r;
×
771
                }
772

UNCOV
773
                h->n_pkcs11_encrypted_key++;
×
774
        }
775

UNCOV
776
        return 0;
×
777
}
778

779
static int dispatch_fido2_hmac_credential(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
780
        Fido2HmacCredential *k = userdata;
×
781
        size_t l;
×
UNCOV
782
        void *b;
×
783
        int r;
×
784

785
        if (sd_json_variant_is_null(variant)) {
×
786
                k->id = mfree(k->id);
×
UNCOV
787
                k->size = 0;
×
UNCOV
788
                return 0;
×
789
        }
790

791
        r = sd_json_variant_unbase64(variant, &b, &l);
×
UNCOV
792
        if (r < 0)
×
793
                return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
×
794

UNCOV
795
        free_and_replace(k->id, b);
×
796
        k->size = l;
×
797

UNCOV
798
        return 0;
×
799
}
800

801
static int dispatch_fido2_hmac_credential_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
802
        UserRecord *h = userdata;
×
UNCOV
803
        sd_json_variant *e;
×
804
        int r;
×
805

UNCOV
806
        if (!sd_json_variant_is_array(variant))
×
807
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
808

809
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
UNCOV
810
                size_t l;
×
811
                void *b;
×
812

UNCOV
813
                if (!GREEDY_REALLOC(h->fido2_hmac_credential, h->n_fido2_hmac_credential + 1))
×
814
                        return log_oom();
×
815

816
                r = sd_json_variant_unbase64(e, &b, &l);
×
UNCOV
817
                if (r < 0)
×
818
                        return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
×
819

UNCOV
820
                h->fido2_hmac_credential[h->n_fido2_hmac_credential++] = (Fido2HmacCredential) {
×
821
                        .id = b,
822
                        .size = l,
823
                };
824
        }
825

UNCOV
826
        return 0;
×
827
}
828

829
static int dispatch_fido2_hmac_salt_value(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
830
        Fido2HmacSalt *k = userdata;
×
831
        size_t l;
×
UNCOV
832
        void *b;
×
833
        int r;
×
834

835
        if (sd_json_variant_is_null(variant)) {
×
836
                k->salt = erase_and_free(k->salt);
×
UNCOV
837
                k->salt_size = 0;
×
UNCOV
838
                return 0;
×
839
        }
840

841
        r = sd_json_variant_unbase64(variant, &b, &l);
×
UNCOV
842
        if (r < 0)
×
843
                return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
×
844

845
        erase_and_free(k->salt);
×
UNCOV
846
        k->salt = b;
×
847
        k->salt_size = l;
×
848

UNCOV
849
        return 0;
×
850
}
851

852
static int dispatch_fido2_hmac_salt(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
853
        UserRecord *h = userdata;
×
UNCOV
854
        sd_json_variant *e;
×
855
        int r;
×
856

UNCOV
857
        if (!sd_json_variant_is_array(variant))
×
858
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
×
859

UNCOV
860
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
UNCOV
861
                static const sd_json_dispatch_field fido2_hmac_salt_dispatch_table[] = {
×
862
                        { "credential",     SD_JSON_VARIANT_STRING,  dispatch_fido2_hmac_credential, offsetof(Fido2HmacSalt, credential),      SD_JSON_MANDATORY },
863
                        { "salt",           SD_JSON_VARIANT_STRING,  dispatch_fido2_hmac_salt_value, 0,                                        SD_JSON_MANDATORY },
864
                        { "hashedPassword", SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,        offsetof(Fido2HmacSalt, hashed_password), SD_JSON_MANDATORY },
865
                        { "up",             SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, up),              0                 },
866
                        { "uv",             SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, uv),              0                 },
867
                        { "clientPin",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, client_pin),      0                 },
868
                        {},
869
                };
870

UNCOV
871
                if (!sd_json_variant_is_object(e))
×
872
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
873

UNCOV
874
                if (!GREEDY_REALLOC(h->fido2_hmac_salt, h->n_fido2_hmac_salt + 1))
×
875
                        return log_oom();
×
876

UNCOV
877
                Fido2HmacSalt *k = h->fido2_hmac_salt + h->n_fido2_hmac_salt;
×
UNCOV
878
                *k = (Fido2HmacSalt) {
×
879
                        .uv = -1,
880
                        .up = -1,
881
                        .client_pin = -1,
882
                };
883

884
                r = sd_json_dispatch(e, fido2_hmac_salt_dispatch_table, flags, k);
×
885
                if (r < 0) {
×
UNCOV
886
                        fido2_hmac_salt_done(k);
×
UNCOV
887
                        return r;
×
888
                }
889

UNCOV
890
                h->n_fido2_hmac_salt++;
×
891
        }
892

UNCOV
893
        return 0;
×
894
}
895

896
static int dispatch_recovery_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
897
        UserRecord *h = userdata;
×
UNCOV
898
        sd_json_variant *e;
×
899
        int r;
×
900

UNCOV
901
        if (!sd_json_variant_is_array(variant))
×
902
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
×
903

UNCOV
904
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
UNCOV
905
                static const sd_json_dispatch_field recovery_key_dispatch_table[] = {
×
906
                        { "type",           SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0,                                      SD_JSON_MANDATORY },
907
                        { "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(RecoveryKey, hashed_password), SD_JSON_MANDATORY },
908
                        {},
909
                };
910

UNCOV
911
                if (!sd_json_variant_is_object(e))
×
912
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
913

UNCOV
914
                if (!GREEDY_REALLOC(h->recovery_key, h->n_recovery_key + 1))
×
915
                        return log_oom();
×
916

UNCOV
917
                RecoveryKey *k = h->recovery_key + h->n_recovery_key;
×
918
                *k = (RecoveryKey) {};
×
919

920
                r = sd_json_dispatch(e, recovery_key_dispatch_table, flags, k);
×
921
                if (r < 0) {
×
UNCOV
922
                        recovery_key_done(k);
×
UNCOV
923
                        return r;
×
924
                }
925

UNCOV
926
                h->n_recovery_key++;
×
927
        }
928

UNCOV
929
        return 0;
×
930
}
931

UNCOV
932
static int dispatch_auto_resize_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
933
        AutoResizeMode *mode = userdata, m;
×
934

935
        assert_se(mode);
×
936

937
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
938
                *mode = _AUTO_RESIZE_MODE_INVALID;
×
UNCOV
939
                return 0;
×
940
        }
941

942
        if (sd_json_variant_is_boolean(variant)) {
×
UNCOV
943
                *mode = sd_json_variant_boolean(variant) ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
×
UNCOV
944
                return 0;
×
945
        }
946

UNCOV
947
        if (!sd_json_variant_is_string(variant))
×
948
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string, boolean or null.", strna(name));
×
949

950
        m = auto_resize_mode_from_string(sd_json_variant_string(variant));
×
UNCOV
951
        if (m < 0)
×
952
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid automatic resize mode.", strna(name));
×
953

UNCOV
954
        *mode = m;
×
UNCOV
955
        return 0;
×
956
}
957

958
static int dispatch_rebalance_weight(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
748✔
959
        uint64_t *rebalance_weight = userdata;
748✔
960
        uintmax_t u;
748✔
961

962
        assert_se(rebalance_weight);
748✔
963

964
        if (sd_json_variant_is_null(variant)) {
748✔
UNCOV
965
                *rebalance_weight = REBALANCE_WEIGHT_UNSET;
×
UNCOV
966
                return 0;
×
967
        }
968

969
        if (sd_json_variant_is_boolean(variant)) {
748✔
UNCOV
970
                *rebalance_weight = sd_json_variant_boolean(variant) ? REBALANCE_WEIGHT_DEFAULT : REBALANCE_WEIGHT_OFF;
×
UNCOV
971
                return 0;
×
972
        }
973

974
        if (!sd_json_variant_is_unsigned(variant))
748✔
UNCOV
975
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer, boolean or null.", strna(name));
×
976

977
        u = sd_json_variant_unsigned(variant);
748✔
978
        if (u >= REBALANCE_WEIGHT_MIN && u <= REBALANCE_WEIGHT_MAX)
748✔
UNCOV
979
                *rebalance_weight = (uint64_t) u;
×
980
        else if (u == 0)
748✔
981
                *rebalance_weight = REBALANCE_WEIGHT_OFF;
748✔
982
        else
UNCOV
983
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
984
                                "Rebalance weight is out of valid range %" PRIu64 "%s%" PRIu64 ".",
985
                                REBALANCE_WEIGHT_MIN, special_glyph(SPECIAL_GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX);
986

987
        return 0;
988
}
989

990
static int dispatch_tmpfs_limit(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
991
        TmpfsLimit *limit = ASSERT_PTR(userdata);
×
992
        int r;
×
993

994
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
995
                *limit = TMPFS_LIMIT_NULL;
×
UNCOV
996
                return 0;
×
997
        }
998

UNCOV
999
        r = sd_json_dispatch_uint64(name, variant, flags, &limit->limit);
×
UNCOV
1000
        if (r < 0)
×
1001
                return r;
1002

UNCOV
1003
        limit->is_set = true;
×
UNCOV
1004
        return 0;
×
1005
}
1006

1007
static int dispatch_tmpfs_limit_scale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
1008
        TmpfsLimit *limit = ASSERT_PTR(userdata);
×
1009
        int r;
×
1010

1011
        if (sd_json_variant_is_null(variant)) {
×
UNCOV
1012
                *limit = TMPFS_LIMIT_NULL;
×
UNCOV
1013
                return 0;
×
1014
        }
1015

UNCOV
1016
        r = sd_json_dispatch_uint32(name, variant, flags, &limit->limit_scale);
×
UNCOV
1017
        if (r < 0)
×
1018
                return r;
1019

UNCOV
1020
        limit->is_set = true;
×
UNCOV
1021
        return 0;
×
1022
}
1023

1024
static int dispatch_privileged(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
1,866✔
1025

1026
        static const sd_json_dispatch_field privileged_dispatch_table[] = {
1,866✔
1027
                { "passwordHint",       SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,  offsetof(UserRecord, password_hint),        0              },
1028
                { "hashedPassword",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,    offsetof(UserRecord, hashed_password),      SD_JSON_STRICT },
1029
                { "sshAuthorizedKeys",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,    offsetof(UserRecord, ssh_authorized_keys),  0              },
1030
                { "pkcs11EncryptedKey", SD_JSON_VARIANT_ARRAY,         dispatch_pkcs11_key,      0,                                          0              },
1031
                { "fido2HmacSalt",      SD_JSON_VARIANT_ARRAY,         dispatch_fido2_hmac_salt, 0,                                          0              },
1032
                { "recoveryKey",        SD_JSON_VARIANT_ARRAY,         dispatch_recovery_key,    0,                                          0              },
1033
                {},
1034
        };
1035

1036
        return sd_json_dispatch(variant, privileged_dispatch_table, flags, userdata);
1,866✔
1037
}
1038

1039
static int dispatch_binding(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
2,982✔
1040

1041
        static const sd_json_dispatch_field binding_dispatch_table[] = {
2,982✔
1042
                { "blobDirectory",     SD_JSON_VARIANT_STRING,        json_dispatch_path,           offsetof(UserRecord, blob_directory),       SD_JSON_STRICT },
1043
                { "imagePath",         SD_JSON_VARIANT_STRING,        json_dispatch_image_path,     offsetof(UserRecord, image_path),           0              },
1044
                { "homeDirectory",     SD_JSON_VARIANT_STRING,        json_dispatch_home_directory, offsetof(UserRecord, home_directory),       0              },
1045
                { "partitionUuid",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, partition_uuid),       0              },
1046
                { "luksUuid",          SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, luks_uuid),            0              },
1047
                { "fileSystemUuid",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, file_system_uuid),     0              },
1048
                { "uid",               SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,     offsetof(UserRecord, uid),                  0              },
1049
                { "gid",               SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,     offsetof(UserRecord, gid),                  0              },
1050
                { "storage",           SD_JSON_VARIANT_STRING,        json_dispatch_user_storage,   offsetof(UserRecord, storage),              0              },
1051
                { "fileSystemType",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, file_system_type),     SD_JSON_STRICT },
1052
                { "luksCipher",        SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, luks_cipher),          SD_JSON_STRICT },
1053
                { "luksCipherMode",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, luks_cipher_mode),     SD_JSON_STRICT },
1054
                { "luksVolumeKeySize", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,      offsetof(UserRecord, luks_volume_key_size), 0              },
1055
                {},
1056
        };
1057

1058
        sd_json_variant *m;
2,982✔
1059
        sd_id128_t mid;
2,982✔
1060
        int r;
2,982✔
1061

1062
        if (!variant)
2,982✔
1063
                return 0;
2,982✔
1064

1065
        if (!sd_json_variant_is_object(variant))
1,087✔
UNCOV
1066
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1067

1068
        r = sd_id128_get_machine(&mid);
1,087✔
1069
        if (r < 0)
1,087✔
UNCOV
1070
                return json_log(variant, flags, r, "Failed to determine machine ID: %m");
×
1071

1072
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
1,087✔
1073
        if (!m)
1,087✔
1074
                return 0;
1075

1076
        return sd_json_dispatch(m, binding_dispatch_table, flags, userdata);
1,087✔
1077
}
1078

1079
static int dispatch_blob_manifest(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
166✔
1080
        _cleanup_hashmap_free_ Hashmap *manifest = NULL;
166✔
1081
        Hashmap **ret = ASSERT_PTR(userdata);
166✔
1082
        sd_json_variant *value;
166✔
1083
        const char *key;
166✔
1084
        int r;
166✔
1085

1086
        if (!variant)
166✔
1087
                return 0;
1088

1089
        if (!sd_json_variant_is_object(variant))
166✔
UNCOV
1090
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1091

1092
        JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
528✔
UNCOV
1093
                _cleanup_free_ char *filename = NULL;
×
1094
                _cleanup_free_ uint8_t *hash = NULL;
362✔
1095

1096
                if (!sd_json_variant_is_string(value))
362✔
UNCOV
1097
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key);
×
1098

1099
                if (!suitable_blob_filename(key))
362✔
UNCOV
1100
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key);
×
1101

1102
                filename = strdup(key);
362✔
1103
                if (!filename)
362✔
UNCOV
1104
                        return json_log_oom(value, flags);
×
1105

1106
                hash = malloc(SHA256_DIGEST_SIZE);
362✔
1107
                if (!hash)
362✔
UNCOV
1108
                        return json_log_oom(value, flags);
×
1109

1110
                r = parse_sha256(sd_json_variant_string(value), hash);
362✔
1111
                if (r < 0)
362✔
UNCOV
1112
                        return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, sd_json_variant_string(value));
×
1113

1114
                r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash);
362✔
1115
                if (r < 0)
362✔
UNCOV
1116
                        return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename);
×
1117
                TAKE_PTR(filename); /* Ownership transfers to hashmap */
362✔
1118
                TAKE_PTR(hash);
362✔
1119
        }
1120

1121
        hashmap_free_and_replace(*ret, manifest);
166✔
1122
        return 0;
166✔
1123
}
1124

1125
int per_machine_id_match(sd_json_variant *ids, sd_json_dispatch_flags_t flags) {
1,289✔
1126
        sd_id128_t mid;
1,289✔
1127
        int r;
1,289✔
1128

1129
        r = sd_id128_get_machine(&mid);
1,289✔
1130
        if (r < 0)
1,289✔
UNCOV
1131
                return json_log(ids, flags, r, "Failed to acquire machine ID: %m");
×
1132

1133
        if (sd_json_variant_is_string(ids)) {
1,289✔
1134
                sd_id128_t k;
1,289✔
1135

1136
                r = sd_id128_from_string(sd_json_variant_string(ids), &k);
1,289✔
1137
                if (r < 0) {
1,289✔
UNCOV
1138
                        json_log(ids, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(ids));
×
1139
                        return 0;
1,289✔
1140
                }
1141

1142
                return sd_id128_equal(mid, k);
2,578✔
1143
        }
1144

UNCOV
1145
        if (sd_json_variant_is_array(ids)) {
×
1146
                sd_json_variant *e;
×
1147

UNCOV
1148
                JSON_VARIANT_ARRAY_FOREACH(e, ids) {
×
1149
                        sd_id128_t k;
×
1150

1151
                        if (!sd_json_variant_is_string(e)) {
×
UNCOV
1152
                                json_log(e, flags, 0, "Machine ID is not a string, ignoring: %m");
×
UNCOV
1153
                                continue;
×
1154
                        }
1155

1156
                        r = sd_id128_from_string(sd_json_variant_string(e), &k);
×
1157
                        if (r < 0) {
×
UNCOV
1158
                                json_log(e, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(e));
×
UNCOV
1159
                                continue;
×
1160
                        }
1161

UNCOV
1162
                        if (sd_id128_equal(mid, k))
×
UNCOV
1163
                                return true;
×
1164
                }
1165

UNCOV
1166
                return false;
×
1167
        }
1168

UNCOV
1169
        json_log(ids, flags, 0, "Machine ID is not a string or array of strings, ignoring: %m");
×
1170
        return false;
1171
}
1172

1173
int per_machine_hostname_match(sd_json_variant *hns, sd_json_dispatch_flags_t flags) {
×
UNCOV
1174
        _cleanup_free_ char *hn = NULL;
×
1175
        int r;
×
1176

1177
        r = gethostname_strict(&hn);
×
1178
        if (r == -ENXIO) {
×
UNCOV
1179
                json_log(hns, flags, r, "No hostname set, not matching perMachine hostname record: %m");
×
1180
                return false;
×
1181
        }
UNCOV
1182
        if (r < 0)
×
1183
                return json_log(hns, flags, r, "Failed to acquire hostname: %m");
×
1184

UNCOV
1185
        if (sd_json_variant_is_string(hns))
×
1186
                return streq(sd_json_variant_string(hns), hn);
×
1187

UNCOV
1188
        if (sd_json_variant_is_array(hns)) {
×
1189
                sd_json_variant *e;
×
1190

1191
                JSON_VARIANT_ARRAY_FOREACH(e, hns) {
×
1192

1193
                        if (!sd_json_variant_is_string(e)) {
×
UNCOV
1194
                                json_log(e, flags, 0, "Hostname is not a string, ignoring: %m");
×
UNCOV
1195
                                continue;
×
1196
                        }
1197

UNCOV
1198
                        if (streq(sd_json_variant_string(hns), hn))
×
UNCOV
1199
                                return true;
×
1200
                }
1201

UNCOV
1202
                return false;
×
1203
        }
1204

UNCOV
1205
        json_log(hns, flags, 0, "Hostname is not a string or array of strings, ignoring: %m");
×
1206
        return false;
1207
}
1208

1209
int per_machine_match(sd_json_variant *entry, sd_json_dispatch_flags_t flags) {
1,289✔
1210
        sd_json_variant *m;
1,289✔
1211
        int r;
1,289✔
1212

1213
        assert(sd_json_variant_is_object(entry));
1,289✔
1214

1215
        m = sd_json_variant_by_key(entry, "matchMachineId");
1,289✔
1216
        if (m) {
1,289✔
1217
                r = per_machine_id_match(m, flags);
1,289✔
1218
                if (r < 0)
1,289✔
1219
                        return r;
1220
                if (r > 0)
1,289✔
1221
                        return true;
1222
        }
1223

1224
        m = sd_json_variant_by_key(entry, "matchHostname");
×
1225
        if (m) {
×
UNCOV
1226
                r = per_machine_hostname_match(m, flags);
×
1227
                if (r < 0)
×
1228
                        return r;
UNCOV
1229
                if (r > 0)
×
UNCOV
1230
                        return true;
×
1231
        }
1232

1233
        return false;
1234
}
1235

1236
static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
2,982✔
1237

1238
        static const sd_json_dispatch_field per_machine_dispatch_table[] = {
2,982✔
1239
                { "matchMachineId",             _SD_JSON_VARIANT_TYPE_INVALID, NULL,                                 0,                                                   0              },
1240
                { "matchHostname",              _SD_JSON_VARIANT_TYPE_INVALID, NULL,                                 0,                                                   0              },
1241
                { "blobDirectory",              SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, blob_directory),                SD_JSON_STRICT },
1242
                { "blobManifest",               SD_JSON_VARIANT_OBJECT,        dispatch_blob_manifest,               offsetof(UserRecord, blob_manifest),                 0              },
1243
                { "iconName",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, icon_name),                     SD_JSON_STRICT },
1244
                { "location",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, location),                      0              },
1245
                { "shell",                      SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path,       offsetof(UserRecord, shell),                         0              },
1246
                { "umask",                      SD_JSON_VARIANT_UNSIGNED,      json_dispatch_umask,                  offsetof(UserRecord, umask),                         0              },
1247
                { "environment",                SD_JSON_VARIANT_ARRAY,         json_dispatch_strv_environment,       offsetof(UserRecord, environment),                   0              },
1248
                { "timeZone",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, time_zone),                     SD_JSON_STRICT },
1249
                { "preferredLanguage",          SD_JSON_VARIANT_STRING,        json_dispatch_locale,                 offsetof(UserRecord, preferred_language),            0              },
1250
                { "additionalLanguages",        SD_JSON_VARIANT_ARRAY,         json_dispatch_locales,                offsetof(UserRecord, additional_languages),          0              },
1251
                { "niceLevel",                  _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_nice,                   offsetof(UserRecord, nice_level),                    0              },
1252
                { "resourceLimits",             _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits,                offsetof(UserRecord, rlimits),                       0              },
1253
                { "locked",                     SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, locked),                        0              },
1254
                { "notBeforeUSec",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, not_before_usec),               0              },
1255
                { "notAfterUSec",               _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, not_after_usec),                0              },
1256
                { "storage",                    SD_JSON_VARIANT_STRING,        json_dispatch_user_storage,           offsetof(UserRecord, storage),                       0              },
1257
                { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size),                     0              },
1258
                { "diskSizeRelative",           _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size_relative),            0              },
1259
                { "skeletonDirectory",          SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, skeleton_directory),            SD_JSON_STRICT },
1260
                { "accessMode",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,            offsetof(UserRecord, access_mode),                   0              },
1261
                { "tasksMax",                   SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, tasks_max),                     0              },
1262
                { "memoryHigh",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_high),                   0              },
1263
                { "memoryMax",                  SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_max),                    0              },
1264
                { "cpuWeight",                  SD_JSON_VARIANT_UNSIGNED,      json_dispatch_weight,                 offsetof(UserRecord, cpu_weight),                    0              },
1265
                { "ioWeight",                   SD_JSON_VARIANT_UNSIGNED,      json_dispatch_weight,                 offsetof(UserRecord, io_weight),                     0              },
1266
                { "mountNoDevices",             SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,             offsetof(UserRecord, nodev),                         0              },
1267
                { "mountNoSuid",                SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,             offsetof(UserRecord, nosuid),                        0              },
1268
                { "mountNoExecute",             SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,             offsetof(UserRecord, noexec),                        0              },
1269
                { "cifsDomain",                 SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_domain),                   SD_JSON_STRICT },
1270
                { "cifsUserName",               SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_user_name),                SD_JSON_STRICT },
1271
                { "cifsService",                SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_service),                  SD_JSON_STRICT },
1272
                { "cifsExtraMountOptions",      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_extra_mount_options),      0              },
1273
                { "imagePath",                  SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, image_path),                    SD_JSON_STRICT },
1274
                { "uid",                        SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,             offsetof(UserRecord, uid),                           0              },
1275
                { "gid",                        SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,             offsetof(UserRecord, gid),                           0              },
1276
                { "memberOf",                   SD_JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,        offsetof(UserRecord, member_of),                     SD_JSON_RELAX  },
1277
                { "capabilityBoundingSet",      SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, capability_bounding_set),       SD_JSON_STRICT },
1278
                { "capabilityAmbientSet",       SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, capability_ambient_set),        SD_JSON_STRICT },
1279
                { "fileSystemType",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, file_system_type),              SD_JSON_STRICT },
1280
                { "partitionUuid",              SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,               offsetof(UserRecord, partition_uuid),                0              },
1281
                { "luksUuid",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,               offsetof(UserRecord, luks_uuid),                     0              },
1282
                { "fileSystemUuid",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,               offsetof(UserRecord, file_system_uuid),              0              },
1283
                { "luksDiscard",                _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,            offsetof(UserRecord, luks_discard),                  0,             },
1284
                { "luksOfflineDiscard",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,            offsetof(UserRecord, luks_offline_discard),          0,             },
1285
                { "luksCipher",                 SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_cipher),                   SD_JSON_STRICT },
1286
                { "luksCipherMode",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_cipher_mode),              SD_JSON_STRICT },
1287
                { "luksVolumeKeySize",          _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_volume_key_size),          0              },
1288
                { "luksPbkdfHashAlgorithm",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_pbkdf_hash_algorithm),     SD_JSON_STRICT },
1289
                { "luksPbkdfType",              SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_pbkdf_type),               SD_JSON_STRICT },
1290
                { "luksPbkdfForceIterations",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_force_iterations),   0              },
1291
                { "luksPbkdfTimeCostUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_time_cost_usec),     0              },
1292
                { "luksPbkdfMemoryCost",        _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_memory_cost),        0              },
1293
                { "luksPbkdfParallelThreads",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_parallel_threads),   0              },
1294
                { "luksSectorSize",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_sector_size),              0              },
1295
                { "luksExtraMountOptions",      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_extra_mount_options),      0              },
1296
                { "dropCaches",                 SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, drop_caches),                   0              },
1297
                { "autoResizeMode",             _SD_JSON_VARIANT_TYPE_INVALID, dispatch_auto_resize_mode,            offsetof(UserRecord, auto_resize_mode),              0              },
1298
                { "rebalanceWeight",            _SD_JSON_VARIANT_TYPE_INVALID, dispatch_rebalance_weight,            offsetof(UserRecord, rebalance_weight),              0              },
1299
                { "rateLimitIntervalUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, ratelimit_interval_usec),       0              },
1300
                { "rateLimitBurst",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, ratelimit_burst),               0              },
1301
                { "enforcePasswordPolicy",      SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, enforce_password_policy),       0              },
1302
                { "autoLogin",                  SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, auto_login),                    0              },
1303
                { "preferredSessionType",       SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, preferred_session_type),        SD_JSON_STRICT },
1304
                { "preferredSessionLauncher",   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, preferred_session_launcher),    SD_JSON_STRICT },
1305
                { "stopDelayUSec",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, stop_delay_usec),               0              },
1306
                { "killProcesses",              SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, kill_processes),                0              },
1307
                { "passwordChangeMinUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_min_usec),      0              },
1308
                { "passwordChangeMaxUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_max_usec),      0              },
1309
                { "passwordChangeWarnUSec",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_warn_usec),     0              },
1310
                { "passwordChangeInactiveUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_inactive_usec), 0              },
1311
                { "passwordChangeNow",          SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, password_change_now),           0              },
1312
                { "pkcs11TokenUri",             SD_JSON_VARIANT_ARRAY,         dispatch_pkcs11_uri_array,            offsetof(UserRecord, pkcs11_token_uri),              0              },
1313
                { "fido2HmacCredential",        SD_JSON_VARIANT_ARRAY,         dispatch_fido2_hmac_credential_array, 0,                                                   0              },
1314
                { "selfModifiableFields",       SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, self_modifiable_fields),        SD_JSON_STRICT },
1315
                { "selfModifiableBlobs",        SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, self_modifiable_blobs),         SD_JSON_STRICT },
1316
                { "selfModifiablePrivileged",   SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, self_modifiable_privileged),    SD_JSON_STRICT },
1317
                { "tmpLimit",                   _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit,                 offsetof(UserRecord, tmp_limit),                     0,             },
1318
                { "tmpLimitScale",              _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale,           offsetof(UserRecord, tmp_limit),                     0,             },
1319
                { "devShmLimit",                _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit,                 offsetof(UserRecord, dev_shm_limit),                 0,             },
1320
                { "devShmLimitScale",           _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale,           offsetof(UserRecord, dev_shm_limit),                 0,             },
1321
                { "defaultArea",                SD_JSON_VARIANT_STRING,        json_dispatch_filename,               offsetof(UserRecord, default_area),                  0              },
1322
                {},
1323
        };
1324

1325
        sd_json_variant *e;
2,982✔
1326
        int r;
2,982✔
1327

1328
        if (!variant)
2,982✔
1329
                return 0;
1330

1331
        if (!sd_json_variant_is_array(variant))
1,269✔
UNCOV
1332
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
1333

1334
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
2,538✔
1335
                if (!sd_json_variant_is_object(e))
1,269✔
UNCOV
1336
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
×
1337

1338
                r = per_machine_match(e, flags);
1,269✔
1339
                if (r < 0)
1,269✔
1340
                        return r;
1341
                if (r == 0)
1,269✔
UNCOV
1342
                        continue;
×
1343

1344
                r = sd_json_dispatch(e, per_machine_dispatch_table, flags, userdata);
1,269✔
1345
                if (r < 0)
1,269✔
1346
                        return r;
1347
        }
1348

1349
        return 0;
1,269✔
1350
}
1351

1352
static int dispatch_status(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
2,982✔
1353

1354
        static const sd_json_dispatch_field status_dispatch_table[] = {
2,982✔
1355
                { "diskUsage",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_usage),                    0              },
1356
                { "diskFree",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_free),                     0              },
1357
                { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_size),                     0              },
1358
                { "diskCeiling",                _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_ceiling),                  0              },
1359
                { "diskFloor",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_floor),                    0              },
1360
                { "state",                      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, state),                         SD_JSON_STRICT },
1361
                { "service",                    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, service),                       SD_JSON_STRICT },
1362
                { "signedLocally",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,      offsetof(UserRecord, signed_locally),                0              },
1363
                { "goodAuthenticationCounter",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, good_authentication_counter),   0              },
1364
                { "badAuthenticationCounter",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, bad_authentication_counter),    0              },
1365
                { "lastGoodAuthenticationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_good_authentication_usec), 0              },
1366
                { "lastBadAuthenticationUSec",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_bad_authentication_usec),  0              },
1367
                { "rateLimitBeginUSec",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_begin_usec),          0              },
1368
                { "rateLimitCount",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_count),               0              },
1369
                { "removable",                  SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,      offsetof(UserRecord, removable),                     0              },
1370
                { "accessMode",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,      offsetof(UserRecord, access_mode),                   0              },
1371
                { "fileSystemType",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, file_system_type),              SD_JSON_STRICT },
1372
                { "fallbackShell",              SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell),                0              },
1373
                { "fallbackHomeDirectory",      SD_JSON_VARIANT_STRING,        json_dispatch_home_directory,   offsetof(UserRecord, fallback_home_directory),       0              },
1374
                { "useFallback",                SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,       offsetof(UserRecord, use_fallback),                  0              },
1375
                { "defaultArea",                SD_JSON_VARIANT_STRING,        json_dispatch_filename,         offsetof(UserRecord, default_area),                  0              },
1376
                {},
1377
        };
1378

1379
        sd_json_variant *m;
2,982✔
1380
        sd_id128_t mid;
2,982✔
1381
        int r;
2,982✔
1382

1383
        if (!variant)
2,982✔
1384
                return 0;
2,982✔
1385

1386
        if (!sd_json_variant_is_object(variant))
2,046✔
1387
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1388

1389
        r = sd_id128_get_machine(&mid);
2,046✔
1390
        if (r < 0)
2,046✔
UNCOV
1391
                return json_log(variant, flags, r, "Failed to determine machine ID: %m");
×
1392

1393
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
2,046✔
1394
        if (!m)
2,046✔
1395
                return 0;
1396

1397
        return sd_json_dispatch(m, status_dispatch_table, flags, userdata);
2,046✔
1398
}
1399

1400
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret) {
664✔
1401
        const char *suffix;
664✔
1402
        char *z;
664✔
1403

1404
        assert(storage >= 0);
664✔
1405
        assert(user_name_and_realm);
664✔
1406
        assert(ret);
664✔
1407

1408
        if (storage == USER_LUKS)
664✔
1409
                suffix = ".home";
1410
        else if (IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
664✔
1411
                suffix = ".homedir";
1412
        else {
1413
                *ret = NULL;
633✔
1414
                return 0;
633✔
1415
        }
1416

1417
        z = strjoin(get_home_root(), "/", user_name_and_realm, suffix);
31✔
1418
        if (!z)
31✔
1419
                return -ENOMEM;
1420

1421
        *ret = path_simplify(z);
31✔
1422
        return 1;
31✔
1423
}
1424

1425
static int user_record_augment(UserRecord *h, sd_json_dispatch_flags_t json_flags) {
2,982✔
1426
        int r;
2,982✔
1427

1428
        assert(h);
2,982✔
1429

1430
        if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
2,982✔
1431
                return 0;
1432

1433
        assert(h->user_name);
2,937✔
1434

1435
        if (!h->user_name_and_realm_auto && h->realm) {
2,937✔
1436
                h->user_name_and_realm_auto = strjoin(h->user_name, "@", h->realm);
57✔
1437
                if (!h->user_name_and_realm_auto)
57✔
UNCOV
1438
                        return json_log_oom(h->json, json_flags);
×
1439
        }
1440

1441
        /* Let's add in the following automatisms only for regular users, they don't make sense for any others */
1442
        if (user_record_disposition(h) != USER_REGULAR)
2,937✔
1443
                return 0;
1444

1445
        if (!h->home_directory && !h->home_directory_auto) {
1,834✔
1446
                h->home_directory_auto = path_join(get_home_root(), h->user_name);
336✔
1447
                if (!h->home_directory_auto)
336✔
UNCOV
1448
                        return json_log_oom(h->json, json_flags);
×
1449
        }
1450

1451
        if (!h->image_path && !h->image_path_auto) {
1,834✔
1452
                r = user_record_build_image_path(user_record_storage(h), user_record_user_name_and_realm(h), &h->image_path_auto);
662✔
1453
                if (r < 0)
662✔
UNCOV
1454
                        return json_log(h->json, json_flags, r, "Failed to determine default image path: %m");
×
1455
        }
1456

1457
        return 0;
1458
}
1459

1460
int user_group_record_mangle(
3,872✔
1461
                sd_json_variant *v,
1462
                UserRecordLoadFlags load_flags,
1463
                sd_json_variant **ret_variant,
1464
                UserRecordMask *ret_mask) {
1465

1466
        static const struct {
3,872✔
1467
                UserRecordMask mask;
1468
                const char *name;
1469
        } mask_field[] = {
1470
                { USER_RECORD_PRIVILEGED,  "privileged" },
1471
                { USER_RECORD_SECRET,      "secret"     },
1472
                { USER_RECORD_BINDING,     "binding"    },
1473
                { USER_RECORD_PER_MACHINE, "perMachine" },
1474
                { USER_RECORD_STATUS,      "status"     },
1475
                { USER_RECORD_SIGNATURE,   "signature"  },
1476
        };
1477

1478
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
3,872✔
1479
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
3,872✔
1480
        sd_json_variant *array[ELEMENTSOF(mask_field) * 2];
3,872✔
1481
        size_t n_retain = 0;
3,872✔
1482
        UserRecordMask m = 0;
3,872✔
1483
        int r;
3,872✔
1484

1485
        assert((load_flags & _USER_RECORD_MASK_MAX) == 0); /* detect mistakes when accidentally passing
3,872✔
1486
                                                            * UserRecordMask bit masks as UserRecordLoadFlags
1487
                                                            * value */
1488

1489
        assert(v);
3,872✔
1490
        assert(ret_variant);
3,872✔
1491

1492
        /* Note that this function is shared with the group record parser, hence we try to be generic in our
1493
         * log message wording here, to cover both cases. */
1494

1495
        if (!sd_json_variant_is_object(v))
3,872✔
UNCOV
1496
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is not a JSON object, refusing.");
×
1497

1498
        if (USER_RECORD_ALLOW_MASK(load_flags) == 0) /* allow nothing? */
3,872✔
UNCOV
1499
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Nothing allowed in record, refusing.");
×
1500

1501
        if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */
3,872✔
UNCOV
1502
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing.");
×
1503

1504
        /* Check if we have the special sections and if they match our flags set */
1505
        FOREACH_ELEMENT(i, mask_field) {
27,104✔
1506
                sd_json_variant *e, *k;
23,232✔
1507

1508
                if (FLAGS_SET(USER_RECORD_STRIP_MASK(load_flags), i->mask)) {
23,232✔
1509
                        if (!w)
2,190✔
1510
                                w = sd_json_variant_ref(v);
1,709✔
1511

1512
                        r = sd_json_variant_filter(&w, STRV_MAKE(i->name));
2,190✔
1513
                        if (r < 0)
2,190✔
UNCOV
1514
                                return json_log(w, json_flags, r, "Failed to remove field from variant: %m");
×
1515

1516
                        continue;
2,190✔
1517
                }
1518

1519
                e = sd_json_variant_by_key_full(v, i->name, &k);
21,042✔
1520
                if (e) {
21,042✔
1521
                        if (!FLAGS_SET(USER_RECORD_ALLOW_MASK(load_flags), i->mask))
8,379✔
UNCOV
1522
                                return json_log(e, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", i->name);
×
1523

1524
                        if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
8,379✔
1525
                                array[n_retain++] = k;
18✔
1526
                                array[n_retain++] = e;
18✔
1527
                        }
1528

1529
                        m |= i->mask;
8,379✔
1530
                } else {
1531
                        if (FLAGS_SET(USER_RECORD_REQUIRE_MASK(load_flags), i->mask))
12,663✔
UNCOV
1532
                                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks '%s' field, which is required.", i->name);
×
1533
                }
1534
        }
1535

1536
        if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
3,872✔
1537
                /* If we are supposed to strip regular items, then let's instead just allocate a new object
1538
                 * with just the stuff we need. */
1539

1540
                w = sd_json_variant_unref(w);
28✔
1541
                r = sd_json_variant_new_object(&w, array, n_retain);
28✔
1542
                if (r < 0)
28✔
UNCOV
1543
                        return json_log(v, json_flags, r, "Failed to allocate new object: %m");
×
1544
        } else
1545
                /* And now check if there's anything else in the record */
1546
                for (size_t i = 0; i < sd_json_variant_elements(v); i += 2) {
3,871✔
1547
                        const char *f;
3,844✔
1548
                        bool special = false;
3,844✔
1549

1550
                        assert_se(f = sd_json_variant_string(sd_json_variant_by_index(v, i)));
3,844✔
1551

1552
                        FOREACH_ELEMENT(j, mask_field)
26,908✔
1553
                                if (streq(f, j->name)) { /* already covered in the loop above */
23,064✔
1554
                                        special = true;
27✔
1555
                                        continue;
27✔
1556
                                }
1557

1558
                        if (!special) {
3,844✔
1559
                                if ((load_flags & (USER_RECORD_ALLOW_REGULAR|USER_RECORD_REQUIRE_REGULAR)) == 0)
3,817✔
UNCOV
1560
                                        return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", f);
×
1561

1562
                                m |= USER_RECORD_REGULAR;
3,817✔
1563
                                break;
3,817✔
1564
                        }
1565
                }
1566

1567
        if (FLAGS_SET(load_flags, USER_RECORD_REQUIRE_REGULAR) && !FLAGS_SET(m, USER_RECORD_REGULAR))
3,872✔
UNCOV
1568
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks basic identity fields, which are required.");
×
1569

1570
        if (!FLAGS_SET(load_flags, USER_RECORD_EMPTY_OK) && m == 0)
3,872✔
UNCOV
1571
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is empty.");
×
1572

1573
        if (w)
3,872✔
1574
                *ret_variant = TAKE_PTR(w);
1,709✔
1575
        else
1576
                *ret_variant = sd_json_variant_ref(v);
2,163✔
1577

1578
        if (ret_mask)
3,872✔
1579
                *ret_mask = m;
3,836✔
1580
        return 0;
1581
}
1582

1583
int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load_flags) {
2,982✔
1584

1585
        static const sd_json_dispatch_field user_dispatch_table[] = {
2,982✔
1586
                { "userName",                   SD_JSON_VARIANT_STRING,        json_dispatch_user_group_name,        offsetof(UserRecord, user_name),                     SD_JSON_RELAX  },
1587
                { "aliases",                    SD_JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,        offsetof(UserRecord, aliases),                       SD_JSON_RELAX  },
1588
                { "realm",                      SD_JSON_VARIANT_STRING,        json_dispatch_realm,                  offsetof(UserRecord, realm),                         0              },
1589
                { "blobDirectory",              SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, blob_directory),                SD_JSON_STRICT },
1590
                { "blobManifest",               SD_JSON_VARIANT_OBJECT,        dispatch_blob_manifest,               offsetof(UserRecord, blob_manifest),                 0              },
1591
                { "realName",                   SD_JSON_VARIANT_STRING,        json_dispatch_gecos,                  offsetof(UserRecord, real_name),                     0              },
1592
                { "emailAddress",               SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, email_address),                 SD_JSON_STRICT },
1593
                { "iconName",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, icon_name),                     SD_JSON_STRICT },
1594
                { "location",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, location),                      0              },
1595
                { "disposition",                SD_JSON_VARIANT_STRING,        json_dispatch_user_disposition,       offsetof(UserRecord, disposition),                   0              },
1596
                { "lastChangeUSec",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, last_change_usec),              0              },
1597
                { "lastPasswordChangeUSec",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, last_password_change_usec),     0              },
1598
                { "shell",                      SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path,       offsetof(UserRecord, shell),                         0              },
1599
                { "umask",                      SD_JSON_VARIANT_UNSIGNED,      json_dispatch_umask,                  offsetof(UserRecord, umask),                         0              },
1600
                { "environment",                SD_JSON_VARIANT_ARRAY,         json_dispatch_strv_environment,       offsetof(UserRecord, environment),                   0              },
1601
                { "timeZone",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, time_zone),                     SD_JSON_STRICT },
1602
                { "preferredLanguage",          SD_JSON_VARIANT_STRING,        json_dispatch_locale,                 offsetof(UserRecord, preferred_language),            0              },
1603
                { "additionalLanguages",        SD_JSON_VARIANT_ARRAY,         json_dispatch_locales,                offsetof(UserRecord, additional_languages),          0              },
1604
                { "niceLevel",                  _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_nice,                   offsetof(UserRecord, nice_level),                    0              },
1605
                { "resourceLimits",             _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits,                offsetof(UserRecord, rlimits),                       0              },
1606
                { "locked",                     SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, locked),                        0              },
1607
                { "notBeforeUSec",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, not_before_usec),               0              },
1608
                { "notAfterUSec",               _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, not_after_usec),                0              },
1609
                { "storage",                    SD_JSON_VARIANT_STRING,        json_dispatch_user_storage,           offsetof(UserRecord, storage),                       0              },
1610
                { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size),                     0              },
1611
                { "diskSizeRelative",           _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, disk_size_relative),            0              },
1612
                { "skeletonDirectory",          SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, skeleton_directory),            SD_JSON_STRICT },
1613
                { "accessMode",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,            offsetof(UserRecord, access_mode),                   0              },
1614
                { "tasksMax",                   SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, tasks_max),                     0              },
1615
                { "memoryHigh",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_high),                   0              },
1616
                { "memoryMax",                  SD_JSON_VARIANT_UNSIGNED,      json_dispatch_tasks_or_memory_max,    offsetof(UserRecord, memory_max),                    0              },
1617
                { "cpuWeight",                  SD_JSON_VARIANT_UNSIGNED,      json_dispatch_weight,                 offsetof(UserRecord, cpu_weight),                    0              },
1618
                { "ioWeight",                   SD_JSON_VARIANT_UNSIGNED,      json_dispatch_weight,                 offsetof(UserRecord, io_weight),                     0              },
1619
                { "mountNoDevices",             SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,             offsetof(UserRecord, nodev),                         0              },
1620
                { "mountNoSuid",                SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,             offsetof(UserRecord, nosuid),                        0              },
1621
                { "mountNoExecute",             SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,             offsetof(UserRecord, noexec),                        0              },
1622
                { "cifsDomain",                 SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_domain),                   SD_JSON_STRICT },
1623
                { "cifsUserName",               SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_user_name),                SD_JSON_STRICT },
1624
                { "cifsService",                SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_service),                  SD_JSON_STRICT },
1625
                { "cifsExtraMountOptions",      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, cifs_extra_mount_options),      0              },
1626
                { "imagePath",                  SD_JSON_VARIANT_STRING,        json_dispatch_path,                   offsetof(UserRecord, image_path),                    SD_JSON_STRICT },
1627
                { "homeDirectory",              SD_JSON_VARIANT_STRING,        json_dispatch_home_directory,         offsetof(UserRecord, home_directory),                0              },
1628
                { "uid",                        SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,             offsetof(UserRecord, uid),                           0              },
1629
                { "gid",                        SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,             offsetof(UserRecord, gid),                           0              },
1630
                { "memberOf",                   SD_JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,        offsetof(UserRecord, member_of),                     SD_JSON_RELAX  },
1631
                { "capabilityBoundingSet",      SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, capability_bounding_set),       SD_JSON_STRICT },
1632
                { "capabilityAmbientSet",       SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, capability_ambient_set),        SD_JSON_STRICT },
1633
                { "fileSystemType",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, file_system_type),              SD_JSON_STRICT },
1634
                { "partitionUuid",              SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,               offsetof(UserRecord, partition_uuid),                0              },
1635
                { "luksUuid",                   SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,               offsetof(UserRecord, luks_uuid),                     0              },
1636
                { "fileSystemUuid",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,               offsetof(UserRecord, file_system_uuid),              0              },
1637
                { "luksDiscard",                _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,            offsetof(UserRecord, luks_discard),                  0              },
1638
                { "luksOfflineDiscard",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,            offsetof(UserRecord, luks_offline_discard),          0              },
1639
                { "luksCipher",                 SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_cipher),                   SD_JSON_STRICT },
1640
                { "luksCipherMode",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_cipher_mode),              SD_JSON_STRICT },
1641
                { "luksVolumeKeySize",          _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_volume_key_size),          0              },
1642
                { "luksPbkdfHashAlgorithm",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_pbkdf_hash_algorithm),     SD_JSON_STRICT },
1643
                { "luksPbkdfType",              SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_pbkdf_type),               SD_JSON_STRICT },
1644
                { "luksPbkdfForceIterations",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_force_iterations),   0              },
1645
                { "luksPbkdfTimeCostUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_time_cost_usec),     0              },
1646
                { "luksPbkdfMemoryCost",        _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_memory_cost),        0              },
1647
                { "luksPbkdfParallelThreads",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_pbkdf_parallel_threads),   0              },
1648
                { "luksSectorSize",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, luks_sector_size),              0              },
1649
                { "luksExtraMountOptions",      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, luks_extra_mount_options),      0              },
1650
                { "dropCaches",                 SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, drop_caches),                   0              },
1651
                { "autoResizeMode",             _SD_JSON_VARIANT_TYPE_INVALID, dispatch_auto_resize_mode,            offsetof(UserRecord, auto_resize_mode),              0              },
1652
                { "rebalanceWeight",            _SD_JSON_VARIANT_TYPE_INVALID, dispatch_rebalance_weight,            offsetof(UserRecord, rebalance_weight),              0              },
1653
                { "service",                    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, service),                       SD_JSON_STRICT },
1654
                { "rateLimitIntervalUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, ratelimit_interval_usec),       0              },
1655
                { "rateLimitBurst",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, ratelimit_burst),               0              },
1656
                { "enforcePasswordPolicy",      SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, enforce_password_policy),       0              },
1657
                { "autoLogin",                  SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, auto_login),                    0              },
1658
                { "preferredSessionType",       SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, preferred_session_type),        SD_JSON_STRICT },
1659
                { "preferredSessionLauncher",   SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,              offsetof(UserRecord, preferred_session_launcher),    SD_JSON_STRICT },
1660
                { "stopDelayUSec",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, stop_delay_usec),               0              },
1661
                { "killProcesses",              SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, kill_processes),                0              },
1662
                { "passwordChangeMinUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_min_usec),      0              },
1663
                { "passwordChangeMaxUSec",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_max_usec),      0              },
1664
                { "passwordChangeWarnUSec",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_warn_usec),     0              },
1665
                { "passwordChangeInactiveUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,              offsetof(UserRecord, password_change_inactive_usec), 0              },
1666
                { "passwordChangeNow",          SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,            offsetof(UserRecord, password_change_now),           0              },
1667
                { "pkcs11TokenUri",             SD_JSON_VARIANT_ARRAY,         dispatch_pkcs11_uri_array,            offsetof(UserRecord, pkcs11_token_uri),              0              },
1668
                { "fido2HmacCredential",        SD_JSON_VARIANT_ARRAY,         dispatch_fido2_hmac_credential_array, 0,                                                   0              },
1669
                { "recoveryKeyType",            SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, recovery_key_type),             0              },
1670
                { "selfModifiableFields",       SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, self_modifiable_fields),        SD_JSON_STRICT },
1671
                { "selfModifiableBlobs",        SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, self_modifiable_blobs),         SD_JSON_STRICT },
1672
                { "selfModifiablePrivileged",   SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,                offsetof(UserRecord, self_modifiable_privileged),    SD_JSON_STRICT },
1673
                { "tmpLimit",                   _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit,                 offsetof(UserRecord, tmp_limit),                     0,             },
1674
                { "tmpLimitScale",              _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale,           offsetof(UserRecord, tmp_limit),                     0,             },
1675
                { "devShmLimit",                _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit,                 offsetof(UserRecord, dev_shm_limit),                 0,             },
1676
                { "devShmLimitScale",           _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale,           offsetof(UserRecord, dev_shm_limit),                 0,             },
1677
                { "defaultArea",                SD_JSON_VARIANT_STRING,        json_dispatch_filename,               offsetof(UserRecord, default_area),                  0              },
1678

1679
                { "secret",                     SD_JSON_VARIANT_OBJECT,        dispatch_secret,                      0,                                                   0              },
1680
                { "privileged",                 SD_JSON_VARIANT_OBJECT,        dispatch_privileged,                  0,                                                   0              },
1681

1682
                /* Ignore the perMachine, binding, status stuff here, and process it later, so that it overrides whatever is set above */
1683
                { "perMachine",                 SD_JSON_VARIANT_ARRAY,         NULL,                                 0,                                                   0              },
1684
                { "binding",                    SD_JSON_VARIANT_OBJECT,        NULL,                                 0,                                                   0              },
1685
                { "status",                     SD_JSON_VARIANT_OBJECT,        NULL,                                 0,                                                   0              },
1686

1687
                /* Ignore 'signature', we check it with explicit accessors instead */
1688
                { "signature",                  SD_JSON_VARIANT_ARRAY,         NULL,                                 0,                                                   0              },
1689
                {},
1690
        };
1691

1692
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
2,982✔
1693
        int r;
2,982✔
1694

1695
        assert(h);
2,982✔
1696
        assert(!h->json);
2,982✔
1697

1698
        /* Note that this call will leave a half-initialized record around on failure! */
1699

1700
        r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
2,982✔
1701
        if (r < 0)
2,982✔
1702
                return r;
1703

1704
        r = sd_json_dispatch(h->json, user_dispatch_table, json_flags | SD_JSON_ALLOW_EXTENSIONS, h);
2,982✔
1705
        if (r < 0)
2,982✔
1706
                return r;
1707

1708
        /* During the parsing operation above we ignored the 'perMachine', 'binding' and 'status' fields,
1709
         * since we want them to override the global options. Let's process them now. */
1710

1711
        r = dispatch_per_machine("perMachine", sd_json_variant_by_key(h->json, "perMachine"), json_flags, h);
2,982✔
1712
        if (r < 0)
2,982✔
1713
                return r;
1714

1715
        r = dispatch_binding("binding", sd_json_variant_by_key(h->json, "binding"), json_flags, h);
2,982✔
1716
        if (r < 0)
2,982✔
1717
                return r;
1718

1719
        r = dispatch_status("status", sd_json_variant_by_key(h->json, "status"), json_flags, h);
2,982✔
1720
        if (r < 0)
2,982✔
1721
                return r;
1722

1723
        if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->user_name)
2,982✔
UNCOV
1724
                return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "User name field missing, refusing.");
×
1725

1726
        r = user_record_augment(h, json_flags);
2,982✔
1727
        if (r < 0)
2,982✔
UNCOV
1728
                return r;
×
1729

1730
        return 0;
1731
}
1732

1733
int user_record_build(UserRecord **ret, ...) {
43✔
1734
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
43✔
1735
        _cleanup_(user_record_unrefp) UserRecord *u = NULL;
43✔
1736
        va_list ap;
43✔
1737
        int r;
43✔
1738

1739
        assert(ret);
43✔
1740

1741
        va_start(ap, ret);
43✔
1742
        r = sd_json_buildv(&v, ap);
43✔
1743
        va_end(ap);
43✔
1744

1745
        if (r < 0)
43✔
1746
                return r;
1747

1748
        u = user_record_new();
43✔
1749
        if (!u)
43✔
1750
                return -ENOMEM;
1751

1752
        r = user_record_load(u, v, USER_RECORD_LOAD_FULL);
43✔
1753
        if (r < 0)
43✔
1754
                return r;
1755

1756
        *ret = TAKE_PTR(u);
43✔
1757
        return 0;
43✔
1758
}
1759

1760
const char* user_record_user_name_and_realm(UserRecord *h) {
810✔
1761
        assert(h);
810✔
1762

1763
        /* Return the pre-initialized joined string if it is defined */
1764
        if (h->user_name_and_realm_auto)
810✔
1765
                return h->user_name_and_realm_auto;
1766

1767
        /* If it's not defined then we cannot have a realm */
1768
        assert(!h->realm);
789✔
1769
        return h->user_name;
789✔
1770
}
1771

1772
UserStorage user_record_storage(UserRecord *h) {
5,506✔
1773
        assert(h);
5,506✔
1774

1775
        if (h->storage >= 0)
5,506✔
1776
                return h->storage;
1,602✔
1777

1778
        return USER_CLASSIC;
1779
}
1780

UNCOV
1781
const char* user_record_file_system_type(UserRecord *h) {
×
UNCOV
1782
        assert(h);
×
1783

UNCOV
1784
        return h->file_system_type ?: "btrfs";
×
1785
}
1786

1787
const char* user_record_skeleton_directory(UserRecord *h) {
36✔
1788
        assert(h);
36✔
1789

1790
        return h->skeleton_directory ?: "/etc/skel";
36✔
1791
}
1792

1793
mode_t user_record_access_mode(UserRecord *h) {
5✔
1794
        assert(h);
5✔
1795

1796
        return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
5✔
1797
}
1798

1799
static const char *user_record_home_directory_real(UserRecord *h) {
2,247✔
1800
        assert(h);
2,247✔
1801

1802
        if (h->home_directory)
2,247✔
1803
                return h->home_directory;
1804
        if (h->home_directory_auto)
151✔
1805
                return h->home_directory_auto;
1806

1807
        /* The root user is special, hence be special about it */
1808
        if (user_record_is_root(h))
35✔
UNCOV
1809
                return "/root";
×
1810

1811
        return "/";
1812
}
1813

1814
const char* user_record_home_directory(UserRecord *h) {
2,289✔
1815
        assert(h);
2,289✔
1816

1817
        if (h->use_fallback && h->fallback_home_directory)
2,289✔
1818
                return h->fallback_home_directory;
1819

1820
        return user_record_home_directory_real(h);
2,195✔
1821
}
1822

1823
const char* user_record_image_path(UserRecord *h) {
851✔
1824
        assert(h);
851✔
1825

1826
        if (h->image_path)
851✔
1827
                return h->image_path;
1828
        if (h->image_path_auto)
66✔
1829
                return h->image_path_auto;
1830

1831
        /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */
1832
        return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ?
52✔
1833
                user_record_home_directory_real(h) : NULL;
52✔
1834
}
1835

UNCOV
1836
const char* user_record_cifs_user_name(UserRecord *h) {
×
UNCOV
1837
        assert(h);
×
1838

UNCOV
1839
        return h->cifs_user_name ?: h->user_name;
×
1840
}
1841

1842
unsigned long user_record_mount_flags(UserRecord *h) {
29✔
1843
        assert(h);
29✔
1844

1845
        return (h->nosuid ? MS_NOSUID : 0) |
29✔
1846
                (h->noexec ? MS_NOEXEC : 0) |
29✔
1847
                (h->nodev ? MS_NODEV : 0);
29✔
1848
}
1849

1850
static const char *user_record_shell_real(UserRecord *h) {
593✔
1851
        assert(h);
593✔
1852

1853
        if (h->shell)
593✔
1854
                return h->shell;
1855

1856
        if (user_record_is_root(h))
283✔
1857
                return "/bin/sh";
1858

1859
        if (user_record_disposition(h) == USER_REGULAR)
278✔
1860
                return DEFAULT_USER_SHELL;
260✔
1861

1862
        return NOLOGIN;
1863
}
1864

1865
const char* user_record_shell(UserRecord *h) {
593✔
1866
        const char *shell;
593✔
1867

1868
        assert(h);
593✔
1869

1870
        shell = user_record_shell_real(h);
593✔
1871

1872
        /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
1873
         * then let's not risk anything. */
1874
        if (h->use_fallback && h->fallback_shell)
593✔
1875
                return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
132✔
1876

1877
        return shell;
1878
}
1879

1880
const char* user_record_real_name(UserRecord *h) {
167✔
1881
        assert(h);
167✔
1882

1883
        return h->real_name ?: h->user_name;
167✔
1884
}
1885

1886
bool user_record_luks_discard(UserRecord *h) {
×
1887
        const char *ip;
×
1888

1889
        assert(h);
×
1890

UNCOV
1891
        if (h->luks_discard >= 0)
×
UNCOV
1892
                return h->luks_discard;
×
1893

UNCOV
1894
        ip = user_record_image_path(h);
×
UNCOV
1895
        if (!ip)
×
1896
                return false;
1897

1898
        /* Use discard by default if we are referring to a real block device, but not when operating on a
1899
         * loopback device. We want to optimize for SSD and flash storage after all, but we should be careful
1900
         * when storing stuff on top of regular file systems in loopback files as doing discard then would
1901
         * mean thin provisioning and we should not do that willy-nilly since it means we'll risk EIO later
1902
         * on should the disk space to back our file systems not be available. */
1903

UNCOV
1904
        return path_startswith(ip, "/dev/");
×
1905
}
1906

1907
bool user_record_luks_offline_discard(UserRecord *h) {
×
1908
        const char *ip;
×
1909

UNCOV
1910
        assert(h);
×
1911

UNCOV
1912
        if (h->luks_offline_discard >= 0)
×
1913
                return h->luks_offline_discard;
×
1914

1915
        /* Discard while we are logged out should generally be a good idea, except when operating directly on
1916
         * physical media, where we should just bind it to the online discard mode. */
1917

1918
        ip = user_record_image_path(h);
×
UNCOV
1919
        if (!ip)
×
1920
                return false;
1921

UNCOV
1922
        if (path_startswith(ip, "/dev/"))
×
1923
                return user_record_luks_discard(h);
×
1924

1925
        return true;
1926
}
1927

UNCOV
1928
const char* user_record_luks_cipher(UserRecord *h) {
×
1929
        assert(h);
×
1930

UNCOV
1931
        return h->luks_cipher ?: "aes";
×
1932
}
1933

UNCOV
1934
const char* user_record_luks_cipher_mode(UserRecord *h) {
×
1935
        assert(h);
×
1936

UNCOV
1937
        return h->luks_cipher_mode ?: "xts-plain64";
×
1938
}
1939

1940
uint64_t user_record_luks_volume_key_size(UserRecord *h) {
×
1941
        assert(h);
×
1942

1943
        /* We return a value here that can be cast without loss into size_t which is what libcrypsetup expects */
1944

UNCOV
1945
        if (h->luks_volume_key_size == UINT64_MAX)
×
1946
                return 256 / 8;
×
1947

1948
        return MIN(h->luks_volume_key_size, SIZE_MAX);
1949
}
1950

UNCOV
1951
const char* user_record_luks_pbkdf_type(UserRecord *h) {
×
1952
        assert(h);
×
1953

UNCOV
1954
        return h->luks_pbkdf_type ?: "argon2id";
×
1955
}
1956

UNCOV
1957
uint64_t user_record_luks_pbkdf_force_iterations(UserRecord *h) {
×
UNCOV
1958
        assert(h);
×
1959

1960
        /* propagate default "benchmark" mode as itself */
UNCOV
1961
        if (h->luks_pbkdf_force_iterations == UINT64_MAX)
×
1962
                return UINT64_MAX;
1963

1964
        /* clamp everything else to actually accepted number of iterations of libcryptsetup */
UNCOV
1965
        return CLAMP(h->luks_pbkdf_force_iterations, 1U, UINT32_MAX);
×
1966
}
1967

1968
uint64_t user_record_luks_pbkdf_time_cost_usec(UserRecord *h) {
×
UNCOV
1969
        assert(h);
×
1970

1971
        /* Returns a value with ms granularity, since that's what libcryptsetup expects */
1972

UNCOV
1973
        if (h->luks_pbkdf_time_cost_usec == UINT64_MAX)
×
1974
                return 500 * USEC_PER_MSEC; /* We default to 500ms, in contrast to libcryptsetup's 2s, which is just awfully slow on every login */
1975

UNCOV
1976
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_time_cost_usec, USEC_PER_MSEC), UINT32_MAX) * USEC_PER_MSEC;
×
1977
}
1978

1979
uint64_t user_record_luks_pbkdf_memory_cost(UserRecord *h) {
×
UNCOV
1980
        assert(h);
×
1981

1982
        /* Returns a value with kb granularity, since that's what libcryptsetup expects */
UNCOV
1983
        if (h->luks_pbkdf_memory_cost == UINT64_MAX)
×
UNCOV
1984
                return streq(user_record_luks_pbkdf_type(h), "pbkdf2") ? 0 : /* doesn't apply for simple pbkdf2 */
×
1985
                        64*1024*1024; /* We default to 64M, since this should work on smaller systems too */
1986

UNCOV
1987
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_memory_cost, 1024), UINT32_MAX) * 1024;
×
1988
}
1989

UNCOV
1990
uint64_t user_record_luks_pbkdf_parallel_threads(UserRecord *h) {
×
UNCOV
1991
        assert(h);
×
1992

UNCOV
1993
        if (h->luks_pbkdf_parallel_threads == UINT64_MAX)
×
UNCOV
1994
                return streq(user_record_luks_pbkdf_type(h), "pbkdf2") ? 0 : /* doesn't apply for simple pbkdf2 */
×
1995
                        1; /* We default to 1, since this should work on smaller systems too */
1996

UNCOV
1997
        return MIN(h->luks_pbkdf_parallel_threads, UINT32_MAX);
×
1998
}
1999

UNCOV
2000
uint64_t user_record_luks_sector_size(UserRecord *h) {
×
UNCOV
2001
        assert(h);
×
2002

UNCOV
2003
        if (h->luks_sector_size == UINT64_MAX)
×
2004
                return 512;
2005

2006
        /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */
UNCOV
2007
        return CLAMP(UINT64_C(1) << (63 - __builtin_clzl(h->luks_sector_size)), 512U, 4096U);
×
2008
}
2009

UNCOV
2010
const char* user_record_luks_pbkdf_hash_algorithm(UserRecord *h) {
×
UNCOV
2011
        assert(h);
×
2012

UNCOV
2013
        return h->luks_pbkdf_hash_algorithm ?: "sha512";
×
2014
}
2015

2016
gid_t user_record_gid(UserRecord *h) {
618✔
2017
        assert(h);
618✔
2018

2019
        if (gid_is_valid(h->gid))
618✔
2020
                return h->gid;
606✔
2021

2022
        return (gid_t) h->uid;
12✔
2023
}
2024

2025
UserDisposition user_record_disposition(UserRecord *h) {
6,000✔
2026
        assert(h);
6,000✔
2027

2028
        if (h->disposition >= 0)
6,000✔
2029
                return h->disposition;
2030

2031
        /* If not declared, derive from UID */
2032

2033
        if (!uid_is_valid(h->uid))
3,617✔
2034
                return _USER_DISPOSITION_INVALID;
2035

2036
        if (user_record_is_root(h) || user_record_is_nobody(h))
3,617✔
2037
                return USER_INTRINSIC;
876✔
2038

2039
        if (uid_is_system(h->uid))
2,741✔
2040
                return USER_SYSTEM;
2041

2042
        if (uid_is_dynamic(h->uid))
1,236✔
2043
                return USER_DYNAMIC;
2044

2045
        if (uid_is_container(h->uid))
1,236✔
2046
                return USER_CONTAINER;
2047

2048
        if (uid_is_foreign(h->uid))
1,082✔
2049
                return USER_FOREIGN;
2050

2051
        if (h->uid > INT32_MAX)
1,082✔
UNCOV
2052
                return USER_RESERVED;
×
2053

2054
        return USER_REGULAR;
2055
}
2056

2057
int user_record_removable(UserRecord *h) {
3,337✔
2058
        UserStorage storage;
3,337✔
2059
        assert(h);
3,337✔
2060

2061
        if (h->removable >= 0)
3,337✔
2062
                return h->removable;
2063

2064
        /* Refuse to decide for classic records */
2065
        storage = user_record_storage(h);
3,337✔
2066
        if (h->storage < 0 || h->storage == USER_CLASSIC)
3,337✔
2067
                return -1;
2068

2069
        /* For now consider only LUKS home directories with a reference by path as removable */
2070
        return storage == USER_LUKS && path_startswith(user_record_image_path(h), "/dev/");
223✔
2071
}
2072

2073
uint64_t user_record_ratelimit_interval_usec(UserRecord *h) {
100✔
2074
        assert(h);
100✔
2075

2076
        if (h->ratelimit_interval_usec == UINT64_MAX)
100✔
2077
                return DEFAULT_RATELIMIT_INTERVAL_USEC;
16✔
2078

2079
        return h->ratelimit_interval_usec;
2080
}
2081

2082
uint64_t user_record_ratelimit_burst(UserRecord *h) {
224✔
2083
        assert(h);
224✔
2084

2085
        if (h->ratelimit_burst == UINT64_MAX)
224✔
2086
                return DEFAULT_RATELIMIT_BURST;
46✔
2087

2088
        return h->ratelimit_burst;
2089
}
2090

2091
bool user_record_can_authenticate(UserRecord *h) {
×
UNCOV
2092
        assert(h);
×
2093

2094
        /* Returns true if there's some form of property configured that the user can authenticate against */
2095

UNCOV
2096
        if (h->n_pkcs11_encrypted_key > 0)
×
2097
                return true;
2098

UNCOV
2099
        if (h->n_fido2_hmac_salt > 0)
×
2100
                return true;
2101

UNCOV
2102
        return !strv_isempty(h->hashed_password);
×
2103
}
2104

2105
bool user_record_drop_caches(UserRecord *h) {
160✔
2106
        assert(h);
160✔
2107

2108
        if (h->drop_caches >= 0)
160✔
UNCOV
2109
                return h->drop_caches;
×
2110

2111
        /* By default drop caches on fscrypt, not otherwise. */
2112
        return user_record_storage(h) == USER_FSCRYPT;
160✔
2113
}
2114

UNCOV
2115
AutoResizeMode user_record_auto_resize_mode(UserRecord *h) {
×
2116
        assert(h);
×
2117

UNCOV
2118
        if (h->auto_resize_mode >= 0)
×
2119
                return h->auto_resize_mode;
2120

UNCOV
2121
        return user_record_storage(h) == USER_LUKS ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
×
2122
}
2123

2124
uint64_t user_record_rebalance_weight(UserRecord *h) {
131✔
2125
        assert(h);
131✔
2126

2127
        if (h->rebalance_weight == REBALANCE_WEIGHT_UNSET)
131✔
2128
                return REBALANCE_WEIGHT_DEFAULT;
16✔
2129

2130
        return h->rebalance_weight;
2131
}
2132

2133
static uint64_t parse_caps_strv(char **l) {
×
2134
        uint64_t c = 0;
×
2135
        int r;
×
2136

2137
        STRV_FOREACH(i, l) {
×
UNCOV
2138
                r = capability_from_name(*i);
×
UNCOV
2139
                if (r < 0)
×
2140
                        log_debug_errno(r, "Don't know capability '%s', ignoring: %m", *i);
×
2141
                else
UNCOV
2142
                        c |= UINT64_C(1) << r;
×
2143
        }
2144

UNCOV
2145
        return c;
×
2146
}
2147

2148
uint64_t user_record_capability_bounding_set(UserRecord *h) {
345✔
2149
        assert(h);
345✔
2150

2151
        /* Returns UINT64_MAX if no bounding set is configured (!) */
2152

2153
        if (!h->capability_bounding_set)
345✔
2154
                return UINT64_MAX;
2155

UNCOV
2156
        return parse_caps_strv(h->capability_bounding_set);
×
2157
}
2158

2159
uint64_t user_record_capability_ambient_set(UserRecord *h) {
345✔
2160
        assert(h);
345✔
2161

2162
        /* Returns UINT64_MAX if no ambient set is configured (!) */
2163

2164
        if (!h->capability_ambient_set)
345✔
2165
                return UINT64_MAX;
2166

UNCOV
2167
        return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
×
2168
}
2169

2170
int user_record_languages(UserRecord *h, char ***ret) {
345✔
2171
        _cleanup_strv_free_ char **l = NULL;
345✔
2172
        int r;
345✔
2173

2174
        assert(h);
345✔
2175
        assert(ret);
345✔
2176

2177
        if (h->preferred_language) {
345✔
UNCOV
2178
                l = strv_new(h->preferred_language);
×
UNCOV
2179
                if (!l)
×
2180
                        return -ENOMEM;
2181
        }
2182

2183
        r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true);
345✔
2184
        if (r < 0)
345✔
2185
                return r;
2186

2187
        *ret = TAKE_PTR(l);
345✔
2188
        return 0;
345✔
2189
}
2190

2191
uint32_t user_record_tmp_limit_scale(UserRecord *h) {
224✔
2192
        assert(h);
224✔
2193

2194
        if (h->tmp_limit.is_set)
224✔
UNCOV
2195
                return h->tmp_limit.limit_scale;
×
2196

2197
        /* By default grant regular users only 80% quota */
2198
        if (user_record_disposition(h) == USER_REGULAR)
224✔
2199
                return UINT32_SCALE_FROM_PERCENT(80);
179✔
2200

2201
        return UINT32_MAX;
2202
}
2203

2204
uint32_t user_record_dev_shm_limit_scale(UserRecord *h) {
224✔
2205
        assert(h);
224✔
2206

2207
        if (h->dev_shm_limit.is_set)
224✔
UNCOV
2208
                return h->dev_shm_limit.limit_scale;
×
2209

2210
        /* By default grant regular users only 80% quota */
2211
        if (user_record_disposition(h) == USER_REGULAR)
224✔
2212
                return UINT32_SCALE_FROM_PERCENT(80);
179✔
2213

2214
        return UINT32_MAX;
2215
}
2216

2217
const char** user_record_self_modifiable_fields(UserRecord *h) {
173✔
2218
        /* As a rule of thumb: a setting is safe if it cannot be used by a
2219
         * user to give themselves some unfair advantage over other users on
2220
         * a given system. */
2221
        static const char *const default_fields[] = {
173✔
2222
                /* For display purposes */
2223
                "realName",
2224
                "emailAddress", /* Just the $EMAIL env var */
2225
                "iconName",
2226
                "location",
2227

2228
                /* Basic account settings */
2229
                "shell",
2230
                "umask",
2231
                "environment",
2232
                "timeZone",
2233
                "preferredLanguage",
2234
                "additionalLanguages",
2235
                "preferredSessionLauncher",
2236
                "preferredSessionType",
2237
                "defaultArea",
2238

2239
                /* Authentication methods */
2240
                "pkcs11TokenUri",
2241
                "fido2HmacCredential",
2242
                "recoveryKeyType",
2243

2244
                "lastChangeUSec", /* Necessary to be able to change record at all */
2245
                "lastPasswordChangeUSec", /* Ditto, but for authentication methods */
2246
                NULL
2247
        };
2248

2249
        assert(h);
173✔
2250

2251
        /* Note: if the self_modifiable_fields field in UserRecord is NULL we'll apply a default, if we have
2252
         * one. If it is a non-NULL empty strv, we'll report it as explicit empty list. When the field is
2253
         * NULL and we have no default list we'll return NULL. */
2254

2255
        /* Note that we intentionally distinguish between NULL and an empty array here */
2256
        if (h->self_modifiable_fields)
173✔
2257
                return (const char**) h->self_modifiable_fields;
2258

2259
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_fields : NULL;
171✔
2260
}
2261

2262
const char** user_record_self_modifiable_blobs(UserRecord *h) {
127✔
2263
        static const char *const default_blobs[] = {
127✔
2264
                /* For display purposes */
2265
                "avatar",
2266
                "login-background",
2267
                NULL
2268
        };
2269

2270
        assert(h);
127✔
2271

2272
        /* Note that we intentionally distinguish between NULL and an empty array here */
2273
        if (h->self_modifiable_blobs)
127✔
2274
                return (const char**) h->self_modifiable_blobs;
2275

2276
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_blobs : NULL;
127✔
2277
}
2278

2279
const char** user_record_self_modifiable_privileged(UserRecord *h) {
143✔
2280
        static const char *const default_fields[] = {
143✔
2281
                /* For display purposes */
2282
                "passwordHint",
2283

2284
                /* Authentication methods */
2285
                "hashedPassword",
2286
                "pkcs11EncryptedKey",
2287
                "fido2HmacSalt",
2288
                "recoveryKey",
2289

2290
                "sshAuthorizedKeys", /* Basically just ~/.ssh/authorized_keys */
2291
                NULL
2292
        };
2293

2294
        assert(h);
143✔
2295

2296
        /* Note that we intentionally distinguish between NULL and an empty array here */
2297
        if (h->self_modifiable_privileged)
143✔
2298
                return (const char**) h->self_modifiable_privileged;
2299

2300
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_fields : NULL;
141✔
2301
}
2302

2303
static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_json_variant **target) {
56✔
2304
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *blobs = NULL;
56✔
2305
        char **allowed;
56✔
2306
        int r;
56✔
2307

2308
        assert(current);
56✔
2309
        assert(target);
56✔
2310

2311
        if (!sd_json_variant_is_object(*target))
56✔
2312
                return -EINVAL;
2313

2314
        v = sd_json_variant_ref(*target);
56✔
2315

2316
        /* Handle basic fields */
2317
        allowed = (char**) user_record_self_modifiable_fields(current);
56✔
2318
        r = sd_json_variant_filter(&v, allowed);
56✔
2319
        if (r < 0)
56✔
2320
                return r;
2321

2322
        /* Handle blobs */
2323
        blobs = sd_json_variant_ref(sd_json_variant_by_key(v, "blobManifest"));
56✔
2324
        if (blobs) {
56✔
2325
                /* The blobManifest contains the sha256 hashes of the blobs,
2326
                 * which are enforced by the service managing the user. So, by
2327
                 * comparing the blob manifests like this, we're actually comparing
2328
                 * the contents of the blob directories & files */
2329

2330
                allowed = (char**) user_record_self_modifiable_blobs(current);
10✔
2331
                r = sd_json_variant_filter(&blobs, allowed);
10✔
2332
                if (r < 0)
10✔
2333
                        return r;
2334

2335
                if (sd_json_variant_is_blank_object(blobs))
10✔
UNCOV
2336
                        r = sd_json_variant_filter(&v, STRV_MAKE("blobManifest"));
×
2337
                else
2338
                        r = sd_json_variant_set_field(&v, "blobManifest", blobs);
10✔
2339
                if (r < 0)
10✔
2340
                        return r;
2341
        }
2342

2343
        JSON_VARIANT_REPLACE(*target, TAKE_PTR(v));
56✔
2344
        return 0;
56✔
2345
}
2346

2347
static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) {
36✔
2348
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *privileged = NULL;
36✔
2349
        sd_json_variant *per_machine;
36✔
2350
        char **allowed;
36✔
2351
        int r;
36✔
2352

2353
        assert(current);
36✔
2354
        assert(h);
36✔
2355
        assert(ret);
36✔
2356

2357
        r = user_group_record_mangle(h->json, USER_RECORD_EXTRACT_SIGNABLE|USER_RECORD_PERMISSIVE, &v, NULL);
36✔
2358
        if (r < 0)
36✔
2359
                return r;
2360

2361
        /* Handle the regular section */
2362
        r = remove_self_modifiable_json_fields_common(current, &v);
36✔
2363
        if (r < 0)
36✔
2364
                return r;
2365

2366
        /* Handle the perMachine section */
2367
        per_machine = sd_json_variant_by_key(v, "perMachine");
36✔
2368
        if (per_machine) {
36✔
2369
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_per_machine = NULL;
20✔
2370
                sd_json_variant *e;
20✔
2371

2372
                if (!sd_json_variant_is_array(per_machine))
20✔
2373
                        return -EINVAL;
2374

2375
                JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
40✔
2376
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *z = NULL;
20✔
2377

2378
                        if (!sd_json_variant_is_object(e))
20✔
2379
                                return -EINVAL;
2380

2381
                        r = per_machine_match(e, 0);
20✔
2382
                        if (r < 0)
20✔
2383
                                return r;
2384
                        if (r == 0) {
20✔
2385
                                /* It's only permissible to change anything inside of matching perMachine sections */
UNCOV
2386
                                r = sd_json_variant_append_array(&new_per_machine, e);
×
UNCOV
2387
                                if (r < 0)
×
2388
                                        return r;
UNCOV
2389
                                continue;
×
2390
                        }
2391

2392
                        z = sd_json_variant_ref(e);
20✔
2393

2394
                        r = remove_self_modifiable_json_fields_common(current, &z);
20✔
2395
                        if (r < 0)
20✔
2396
                                return r;
2397

2398
                        if (!sd_json_variant_is_blank_object(z)) {
20✔
2399
                                r = sd_json_variant_append_array(&new_per_machine, z);
20✔
2400
                                if (r < 0)
20✔
2401
                                        return r;
2402
                        }
2403
                }
2404

2405
                if (sd_json_variant_is_blank_array(new_per_machine))
20✔
UNCOV
2406
                        r = sd_json_variant_filter(&v, STRV_MAKE("perMachine"));
×
2407
                else
2408
                        r = sd_json_variant_set_field(&v, "perMachine", new_per_machine);
20✔
2409
                if (r < 0)
20✔
2410
                        return r;
2411
        }
2412

2413
        /* Handle the privileged section */
2414
        privileged = sd_json_variant_ref(sd_json_variant_by_key(v, "privileged"));
36✔
2415
        if (privileged) {
36✔
2416
                allowed = (char**) user_record_self_modifiable_privileged(current);
26✔
2417
                r = sd_json_variant_filter(&privileged, allowed);
26✔
2418
                if (r < 0)
26✔
2419
                        return r;
2420

2421
                if (sd_json_variant_is_blank_object(privileged))
26✔
2422
                        r = sd_json_variant_filter(&v, STRV_MAKE("privileged"));
24✔
2423
                else
2424
                        r = sd_json_variant_set_field(&v, "privileged", privileged);
2✔
2425
                if (r < 0)
26✔
2426
                        return r;
2427
        }
2428

2429
        JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
36✔
2430
        return 0;
36✔
2431
}
2432

2433
int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) {
18✔
2434
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *vc = NULL, *vi = NULL;
36✔
2435
        int r;
18✔
2436

2437
        assert(current);
18✔
2438
        assert(incoming);
18✔
2439

2440
        /* We remove the fields that the user is allowed to change and then
2441
         * compare the resulting JSON records. If they are not equal, that
2442
         * means a disallowed field has been changed and thus we should
2443
         * require administrator permission to apply the changes. */
2444

2445
        r = remove_self_modifiable_json_fields(current, current, &vc);
18✔
2446
        if (r < 0)
18✔
2447
                return r;
2448

2449
        /* Note that we use `current` as the source of the allowlist, and not
2450
         * `incoming`. This prevents the user from adding fields. Consider a
2451
         * scenario that would've been possible if we had messed up this check:
2452
         *
2453
         * 1) A user starts out with no group memberships and no custom allowlist.
2454
         *    Thus, this user is not an administrator, and the `memberOf` and
2455
         *    `selfModifiableFields` fields are unset in their record.
2456
         * 2) This user crafts a request to add the following to their record:
2457
         *    { "memberOf": ["wheel"], "selfModifiableFields": ["memberOf", "selfModifiableFields"] }
2458
         * 3) We remove the `mebmerOf` and `selfModifiabileFields` fields from `incoming`
2459
         * 4) `current` and `incoming` compare as equal, so we let the change happen
2460
         * 5) the user has granted themselves administrator privileges
2461
         */
2462
        r = remove_self_modifiable_json_fields(current, incoming, &vi);
18✔
2463
        if (r < 0)
18✔
2464
                return r;
2465

2466
        return sd_json_variant_equal(vc, vi);
18✔
2467
}
2468

2469
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
227✔
2470
        assert(h);
227✔
2471

2472
        /* Calculates when the it's possible to login next. Returns:
2473
         *
2474
         * UINT64_MAX → Nothing known
2475
         * 0          → Right away
2476
         * Any other  → Next time in CLOCK_REALTIME in usec (which could be in the past)
2477
         */
2478

2479
        if (h->ratelimit_begin_usec == UINT64_MAX ||
227✔
2480
            h->ratelimit_count == UINT64_MAX)
131✔
2481
                return UINT64_MAX;
2482

2483
        if (h->ratelimit_begin_usec > now(CLOCK_REALTIME)) /* If the ratelimit time is in the future, then
131✔
2484
                                                            * the local clock is probably incorrect. Let's
2485
                                                            * not refuse login then. */
2486
                return UINT64_MAX;
2487

2488
        if (h->ratelimit_count < user_record_ratelimit_burst(h))
131✔
2489
                return 0;
2490

UNCOV
2491
        return usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h));
×
2492
}
2493

2494
bool user_record_equal(UserRecord *a, UserRecord *b) {
104✔
2495
        assert(a);
104✔
2496
        assert(b);
104✔
2497

2498
        /* We assume that when a record is modified its JSON data is updated at the same time, hence it's
2499
         * sufficient to compare the JSON data. */
2500

2501
        return sd_json_variant_equal(a->json, b->json);
104✔
2502
}
2503

2504
bool user_record_compatible(UserRecord *a, UserRecord *b) {
91✔
2505
        assert(a);
91✔
2506
        assert(b);
91✔
2507

2508
        /* If either lacks the regular section, we can't really decide, let's hence say they are
2509
         * incompatible. */
2510
        if (!(a->mask & b->mask & USER_RECORD_REGULAR))
91✔
2511
                return false;
2512

2513
        return streq_ptr(a->user_name, b->user_name) &&
91✔
2514
                streq_ptr(a->realm, b->realm);
91✔
2515
}
2516

2517
int user_record_compare_last_change(UserRecord *a, UserRecord *b) {
12✔
2518
        assert(a);
12✔
2519
        assert(b);
12✔
2520

2521
        if (a->last_change_usec == b->last_change_usec)
12✔
2522
                return 0;
2523

2524
        /* Always consider a record with a timestamp newer than one without */
2525
        if (a->last_change_usec == UINT64_MAX)
12✔
2526
                return -1;
2527
        if (b->last_change_usec == UINT64_MAX)
12✔
2528
                return 1;
2529

2530
        return CMP(a->last_change_usec, b->last_change_usec);
12✔
2531
}
2532

2533
int user_record_clone(UserRecord *h, UserRecordLoadFlags flags, UserRecord **ret) {
774✔
2534
        _cleanup_(user_record_unrefp) UserRecord *c = NULL;
774✔
2535
        int r;
774✔
2536

2537
        assert(h);
774✔
2538
        assert(ret);
774✔
2539

2540
        c = user_record_new();
774✔
2541
        if (!c)
774✔
2542
                return -ENOMEM;
2543

2544
        r = user_record_load(c, h->json, flags);
774✔
2545
        if (r < 0)
774✔
2546
                return r;
2547

2548
        *ret = TAKE_PTR(c);
774✔
2549
        return 0;
774✔
2550
}
2551

2552
int user_record_masked_equal(UserRecord *a, UserRecord *b, UserRecordMask mask) {
17✔
2553
        _cleanup_(user_record_unrefp) UserRecord *x = NULL, *y = NULL;
17✔
2554
        int r;
17✔
2555

2556
        assert(a);
17✔
2557
        assert(b);
17✔
2558

2559
        /* Compares the two records, but ignores anything not listed in the specified mask */
2560

2561
        if ((a->mask & ~mask) != 0) {
17✔
2562
                r = user_record_clone(a, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &x);
17✔
2563
                if (r < 0)
17✔
2564
                        return r;
2565

2566
                a = x;
17✔
2567
        }
2568

2569
        if ((b->mask & ~mask) != 0) {
17✔
2570
                r = user_record_clone(b, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &y);
17✔
2571
                if (r < 0)
17✔
2572
                        return r;
2573

2574
                b = y;
17✔
2575
        }
2576

2577
        return user_record_equal(a, b);
17✔
2578
}
2579

2580
int user_record_test_blocked(UserRecord *h) {
145✔
2581
        usec_t n;
145✔
2582

2583
        /* Checks whether access to the specified user shall be allowed at the moment. Returns:
2584
         *
2585
         *          -ESTALE: Record is from the future
2586
         *          -ENOLCK: Record is blocked
2587
         *          -EL2HLT: Record is not valid yet
2588
         *          -EL3HLT: Record is not valid anymore
2589
         *
2590
         */
2591

2592
        assert(h);
145✔
2593

2594
        if (h->locked > 0)
145✔
2595
                return -ENOLCK;
2596

2597
        n = now(CLOCK_REALTIME);
126✔
2598

2599
        if (h->not_before_usec != UINT64_MAX && n < h->not_before_usec)
126✔
2600
                return -EL2HLT;
2601
        if (h->not_after_usec != UINT64_MAX && n > h->not_after_usec)
126✔
2602
                return -EL3HLT;
2603

2604
        if (h->last_change_usec != UINT64_MAX &&
126✔
2605
            h->last_change_usec > n) /* Complain during log-ins when the record is from the future */
UNCOV
2606
                return -ESTALE;
×
2607

2608
        return 0;
2609
}
2610

2611
int user_record_test_password_change_required(UserRecord *h) {
147✔
2612
        bool change_permitted;
147✔
2613
        usec_t n;
147✔
2614

2615
        assert(h);
147✔
2616

2617
        /* Checks whether the user must change the password when logging in
2618

2619
            -EKEYREVOKED: Change password now because admin said so
2620
             -EOWNERDEAD: Change password now because it expired
2621
           -EKEYREJECTED: Password is expired, no changing is allowed
2622
            -EKEYEXPIRED: Password is about to expire, warn user
2623
               -ENETDOWN: Record has expiration info but no password change timestamp
2624
                  -EROFS: No password change required nor permitted
2625
                 -ESTALE: RTC likely incorrect, last password change is in the future
2626
                       0: No password change required, but permitted
2627
         */
2628

2629
        /* If a password change request has been set explicitly, it overrides everything */
2630
        if (h->password_change_now > 0)
147✔
2631
                return -EKEYREVOKED;
2632

2633
        n = now(CLOCK_REALTIME);
147✔
2634

2635
        /* Password change in the future? Then our RTC is likely incorrect */
2636
        if (h->last_password_change_usec != UINT64_MAX &&
147✔
UNCOV
2637
            h->last_password_change_usec > n &&
×
UNCOV
2638
            (h->password_change_min_usec != UINT64_MAX ||
×
UNCOV
2639
             h->password_change_max_usec != UINT64_MAX ||
×
UNCOV
2640
             h->password_change_inactive_usec != UINT64_MAX))
×
2641
            return -ESTALE;
2642

2643
        /* Then, let's check if password changing is currently allowed at all */
2644
        if (h->password_change_min_usec != UINT64_MAX) {
147✔
2645

2646
                /* Expiry configured but no password change timestamp known? */
2647
                if (h->last_password_change_usec == UINT64_MAX)
×
2648
                        return -ENETDOWN;
2649

UNCOV
2650
                if (h->password_change_min_usec >= UINT64_MAX - h->last_password_change_usec)
×
2651
                        change_permitted = false;
2652
                else
UNCOV
2653
                        change_permitted = n >= h->last_password_change_usec + h->password_change_min_usec;
×
2654

2655
        } else
2656
                change_permitted = true;
2657

2658
        /* Let's check whether the password has expired.  */
2659
        if (!(h->password_change_max_usec == UINT64_MAX ||
147✔
2660
              h->password_change_max_usec >= UINT64_MAX - h->last_password_change_usec)) {
4✔
2661

2662
                uint64_t change_before;
4✔
2663

2664
                /* Expiry configured but no password change timestamp known? */
2665
                if (h->last_password_change_usec == UINT64_MAX)
4✔
2666
                        return -ENETDOWN;
2667

2668
                /* Password is in inactive phase? */
2669
                if (h->password_change_inactive_usec != UINT64_MAX &&
4✔
UNCOV
2670
                    h->password_change_inactive_usec < UINT64_MAX - h->password_change_max_usec) {
×
UNCOV
2671
                        usec_t added;
×
2672

UNCOV
2673
                        added = h->password_change_inactive_usec + h->password_change_max_usec;
×
UNCOV
2674
                        if (added < UINT64_MAX - h->last_password_change_usec &&
×
UNCOV
2675
                            n >= h->last_password_change_usec + added)
×
2676
                                return -EKEYREJECTED;
2677
                }
2678

2679
                /* Password needs to be changed now? */
2680
                change_before = h->last_password_change_usec + h->password_change_max_usec;
4✔
2681
                if (n >= change_before)
4✔
2682
                        return change_permitted ? -EOWNERDEAD : -EKEYREJECTED;
×
2683

2684
                /* Warn user? */
2685
                if (h->password_change_warn_usec != UINT64_MAX &&
4✔
2686
                    (change_before < h->password_change_warn_usec ||
4✔
2687
                     n >= change_before - h->password_change_warn_usec))
4✔
UNCOV
2688
                        return change_permitted ? -EKEYEXPIRED : -EROFS;
×
2689
        }
2690

2691
        /* No password changing necessary */
2692
        return change_permitted ? 0 : -EROFS;
147✔
2693
}
2694

2695
int user_record_is_root(const UserRecord *u) {
4,142✔
2696
        assert(u);
4,142✔
2697

2698
        return u->uid == 0 || streq_ptr(u->user_name, "root");
4,142✔
2699
}
2700

2701
int user_record_is_nobody(const UserRecord *u) {
2,831✔
2702
        assert(u);
2,831✔
2703

2704
        return u->uid == UID_NOBODY || STRPTR_IN_SET(u->user_name, NOBODY_USER_NAME, "nobody");
2,831✔
2705
}
2706

2707
bool user_record_matches_user_name(const UserRecord *u, const char *user_name) {
574✔
2708
        assert(u);
574✔
2709
        assert(user_name);
574✔
2710

2711
        if (streq_ptr(u->user_name, user_name))
574✔
2712
                return true;
2713

2714
        if (streq_ptr(u->user_name_and_realm_auto, user_name))
24✔
2715
                return true;
2716

2717
        if (strv_contains(u->aliases, user_name))
18✔
2718
                return true;
2719

2720
        const char *realm = strrchr(user_name, '@');
12✔
2721
        if (realm && streq_ptr(realm+1, u->realm))
12✔
2722
                STRV_FOREACH(a, u->aliases)
18✔
2723
                        if (startswith(user_name, *a) == realm)
18✔
2724
                                return true;
2725

2726
        return false;
2727
}
2728

2729
int suitable_blob_filename(const char *name) {
429✔
2730
        /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
2731
        return filename_is_valid(name) &&
858✔
2732
               in_charset(name, URI_UNRESERVED) &&
429✔
2733
               name[0] != '.';
423✔
2734
}
2735

2736
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches) {
116✔
2737
        assert(names || n_names == 0);
116✔
2738

2739
        /* Checks if any of the user record strings in the names[] array matches any of the search strings in
2740
         * the matches** strv fuzzily. */
2741

2742
        FOREACH_ARRAY(n, names, n_names) {
436✔
2743
                if (!*n)
320✔
2744
                        continue;
125✔
2745

2746
                _cleanup_free_ char *lcn = strdup(*n);
195✔
2747
                if (!lcn)
195✔
2748
                        return -ENOMEM;
2749

2750
                ascii_strlower(lcn);
195✔
2751

2752
                STRV_FOREACH(i, matches) {
390✔
2753
                        _cleanup_free_ char *lc = strdup(*i);
195✔
2754
                        if (!lc)
195✔
2755
                                return -ENOMEM;
2756

2757
                        ascii_strlower(lc);
195✔
2758

2759
                        /* First do substring check */
2760
                        if (strstr(lcn, lc))
195✔
2761
                                return true;
2762

2763
                        /* Then do some fuzzy string comparison (but only if the needle is non-trivially long) */
2764
                        if (strlen(lc) >= 5 && strlevenshtein(lcn, lc) < 3)
195✔
2765
                                return true;
2766
                }
2767
        }
2768

2769
        return false;
2770
}
2771

2772
int user_record_match(UserRecord *u, const UserDBMatch *match) {
1,937✔
2773
        assert(u);
1,937✔
2774

2775
        if (!match)
1,937✔
2776
                return true;
2777

2778
        if (u->uid < match->uid_min || u->uid > match->uid_max)
1,119✔
2779
                return false;
2780

2781
        if (!BIT_SET(match->disposition_mask, user_record_disposition(u)))
1,091✔
2782
                return false;
2783

2784
        if (!strv_isempty(match->fuzzy_names)) {
1,033✔
2785

2786
                /* Note this array of names is sparse, i.e. various entries listed in it will be
2787
                 * NULL. Because of that we are not using a NULL terminated strv here, but a regular
2788
                 * array. */
2789
                const char* names[] = {
56✔
2790
                        u->user_name,
28✔
2791
                        user_record_user_name_and_realm(u),
28✔
2792
                        u->real_name,
28✔
2793
                        u->email_address,
28✔
2794
                        u->cifs_user_name,
28✔
2795
                };
2796

2797
                if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) &&
56✔
2798
                    !user_name_fuzzy_match((const char**) u->aliases, strv_length(u->aliases), match->fuzzy_names))
28✔
2799
                        return false;
28✔
2800
        }
2801

2802
        return true;
2803
}
2804

2805
int json_dispatch_dispositions_mask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
8✔
2806
        uint64_t *mask = ASSERT_PTR(userdata);
8✔
2807

2808
        if (sd_json_variant_is_null(variant)) {
8✔
UNCOV
2809
                *mask = UINT64_MAX;
×
UNCOV
2810
                return 0;
×
2811
        }
2812

2813
        if (!sd_json_variant_is_array(variant))
8✔
UNCOV
2814
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
2815

2816
        uint64_t m = 0;
2817
        for (size_t i = 0; i < sd_json_variant_elements(variant); i++) {
20✔
2818
                sd_json_variant *e;
12✔
2819
                const char *a;
12✔
2820

2821
                e = sd_json_variant_by_index(variant, i);
12✔
2822
                if (!sd_json_variant_is_string(e))
12✔
2823
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
2824

2825
                assert_se(a = sd_json_variant_string(e));
12✔
2826

2827
                UserDisposition d = user_disposition_from_string(a);
12✔
2828
                if (d < 0)
12✔
UNCOV
2829
                        return json_log(e, flags, d, "JSON field '%s' contains an invalid user disposition type: %s", strna(name), a);
×
2830

2831
                m |= INDEX_TO_MASK(uint64_t, d);
12✔
2832
        }
2833

2834
        *mask = m;
8✔
2835
        return 0;
8✔
2836
}
2837

2838
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
2839
        [USER_CLASSIC]   = "classic",
2840
        [USER_LUKS]      = "luks",
2841
        [USER_DIRECTORY] = "directory",
2842
        [USER_SUBVOLUME] = "subvolume",
2843
        [USER_FSCRYPT]   = "fscrypt",
2844
        [USER_CIFS]      = "cifs",
2845
};
2846

2847
DEFINE_STRING_TABLE_LOOKUP(user_storage, UserStorage);
1,762✔
2848

2849
static const char* const user_disposition_table[_USER_DISPOSITION_MAX] = {
2850
        [USER_INTRINSIC] = "intrinsic",
2851
        [USER_SYSTEM]    = "system",
2852
        [USER_DYNAMIC]   = "dynamic",
2853
        [USER_REGULAR]   = "regular",
2854
        [USER_CONTAINER] = "container",
2855
        [USER_FOREIGN]   = "foreign",
2856
        [USER_RESERVED]  = "reserved",
2857
};
2858

2859
DEFINE_STRING_TABLE_LOOKUP(user_disposition, UserDisposition);
2,425✔
2860

2861
static const char* const auto_resize_mode_table[_AUTO_RESIZE_MODE_MAX] = {
2862
        [AUTO_RESIZE_OFF]             = "off",
2863
        [AUTO_RESIZE_GROW]            = "grow",
2864
        [AUTO_RESIZE_SHRINK_AND_GROW] = "shrink-and-grow",
2865
};
2866

UNCOV
2867
DEFINE_STRING_TABLE_LOOKUP(auto_resize_mode, AutoResizeMode);
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc