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

systemd / systemd / 25409762285

05 May 2026 08:45PM UTC coverage: 72.658% (-0.02%) from 72.674%
25409762285

push

github

web-flow
Couple of coverity fixes (#41951)

0 of 11 new or added lines in 2 files covered. (0.0%)

2705 existing lines in 63 files now uncovered.

326249 of 449021 relevant lines covered (72.66%)

1212712.0 hits per line

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

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

3
#include <sys/mount.h>
4

5
#include "alloc-util.h"
6
#include "bitfield.h"
7
#include "capability-list.h"
8
#include "cgroup-util.h"
9
#include "dns-domain.h"
10
#include "glyph-util.h"
11
#include "hashmap.h"
12
#include "hostname-setup.h"
13
#include "json-util.h"
14
#include "locale-util.h"
15
#include "log.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 "string-util.h"
24
#include "strv.h"
25
#include "time-util.h"
26
#include "uid-classification.h"
27
#include "user-record.h"
28
#include "user-util.h"
29

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

33
UserRecord* user_record_new(void) {
19,625✔
34
        UserRecord *h;
19,625✔
35

36
        h = new(UserRecord, 1);
19,625✔
37
        if (!h)
19,625✔
38
                return NULL;
39

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

105
        return h;
19,625✔
106
}
107

108
sd_json_dispatch_flags_t USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(UserRecordLoadFlags flags) {
57,456✔
109
        return (FLAGS_SET(flags, USER_RECORD_LOG) ? SD_JSON_LOG : 0) |
57,456✔
110
                (FLAGS_SET(flags, USER_RECORD_PERMISSIVE) ? SD_JSON_PERMISSIVE : 0);
57,456✔
111
}
112

113
static void pkcs11_encrypted_key_done(Pkcs11EncryptedKey *k) {
×
114
        if (!k)
×
115
                return;
116

117
        free(k->uri);
×
118
        erase_and_free(k->data);
×
119
        erase_and_free(k->hashed_password);
×
120
}
121

122
static void fido2_hmac_credential_done(Fido2HmacCredential *c) {
×
123
        if (!c)
×
124
                return;
125

126
        free(c->id);
×
127
}
128

129
static void fido2_hmac_salt_done(Fido2HmacSalt *s) {
×
130
        if (!s)
×
131
                return;
132

133
        fido2_hmac_credential_done(&s->credential);
×
134
        erase_and_free(s->salt);
×
135
        erase_and_free(s->hashed_password);
×
136
}
137

138
static void recovery_key_done(RecoveryKey *k) {
×
139
        if (!k)
×
140
                return;
141

142
        free(k->type);
×
143
        erase_and_free(k->hashed_password);
×
144
}
145

146
static UserRecord* user_record_free(UserRecord *h) {
19,590✔
147
        if (!h)
19,590✔
148
                return NULL;
149

150
        free(h->user_name);
19,590✔
151
        free(h->realm);
19,590✔
152
        free(h->user_name_and_realm_auto);
19,590✔
153
        strv_free(h->aliases);
19,590✔
154
        free(h->real_name);
19,590✔
155
        free(h->email_address);
19,590✔
156
        erase_and_free(h->password_hint);
19,590✔
157
        free(h->location);
19,590✔
158
        free(h->icon_name);
19,590✔
159

160
        free(h->blob_directory);
19,590✔
161
        hashmap_free(h->blob_manifest);
19,590✔
162

163
        free(h->shell);
19,590✔
164

165
        strv_free(h->environment);
19,590✔
166
        free(h->time_zone);
19,590✔
167
        free(h->preferred_language);
19,590✔
168
        strv_free(h->additional_languages);
19,590✔
169
        rlimit_free_all(h->rlimits);
19,590✔
170

171
        free(h->skeleton_directory);
19,590✔
172

173
        strv_free_erase(h->hashed_password);
19,590✔
174
        strv_free_erase(h->ssh_authorized_keys);
19,590✔
175
        strv_free_erase(h->password);
19,590✔
176
        strv_free_erase(h->token_pin);
19,590✔
177

178
        free(h->cifs_service);
19,590✔
179
        free(h->cifs_user_name);
19,590✔
180
        free(h->cifs_domain);
19,590✔
181
        free(h->cifs_extra_mount_options);
19,590✔
182

183
        free(h->image_path);
19,590✔
184
        free(h->image_path_auto);
19,590✔
185
        free(h->home_directory);
19,590✔
186
        free(h->home_directory_auto);
19,590✔
187

188
        free(h->fallback_shell);
19,590✔
189
        free(h->fallback_home_directory);
19,590✔
190

191
        strv_free(h->member_of);
19,590✔
192
        strv_free(h->capability_bounding_set);
19,590✔
193
        strv_free(h->capability_ambient_set);
19,590✔
194

195
        free(h->file_system_type);
19,590✔
196
        free(h->luks_cipher);
19,590✔
197
        free(h->luks_cipher_mode);
19,590✔
198
        free(h->luks_pbkdf_hash_algorithm);
19,590✔
199
        free(h->luks_pbkdf_type);
19,590✔
200
        free(h->luks_extra_mount_options);
19,590✔
201

202
        free(h->state);
19,590✔
203
        free(h->service);
19,590✔
204

205
        free(h->preferred_session_type);
19,590✔
206
        free(h->preferred_session_launcher);
19,590✔
207

208
        strv_free(h->pkcs11_token_uri);
19,590✔
209
        for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++)
39,180✔
210
                pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
×
211
        free(h->pkcs11_encrypted_key);
19,590✔
212

213
        for (size_t i = 0; i < h->n_fido2_hmac_credential; i++)
19,590✔
214
                fido2_hmac_credential_done(h->fido2_hmac_credential + i);
×
215
        free(h->fido2_hmac_credential);
19,590✔
216
        for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
19,590✔
217
                fido2_hmac_salt_done(h->fido2_hmac_salt + i);
×
218
        free(h->fido2_hmac_salt);
19,590✔
219

220
        strv_free(h->recovery_key_type);
19,590✔
221
        for (size_t i = 0; i < h->n_recovery_key; i++)
39,180✔
222
                recovery_key_done(h->recovery_key + i);
×
223
        free(h->recovery_key);
19,590✔
224

225
        strv_free(h->self_modifiable_fields);
19,590✔
226
        strv_free(h->self_modifiable_blobs);
19,590✔
227
        strv_free(h->self_modifiable_privileged);
19,590✔
228

229
        free(h->default_area);
19,590✔
230

231
        sd_json_variant_unref(h->json);
19,590✔
232

233
        return mfree(h);
19,590✔
234
}
235

236
DEFINE_TRIVIAL_REF_UNREF_FUNC(UserRecord, user_record, user_record_free);
36,697✔
237

238
int json_dispatch_realm(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
94✔
239
        char **s = userdata;
94✔
240
        const char *n;
94✔
241
        int r;
94✔
242

243
        if (sd_json_variant_is_null(variant)) {
94✔
244
                *s = mfree(*s);
×
245
                return 0;
×
246
        }
247

248
        if (!sd_json_variant_is_string(variant))
94✔
249
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
250

251
        n = sd_json_variant_string(variant);
94✔
252
        r = dns_name_is_valid(n);
94✔
253
        if (r < 0)
94✔
254
                return json_log(variant, flags, r, "Failed to check if JSON field '%s' is a valid DNS domain.", strna(name));
×
255
        if (r == 0)
94✔
256
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid DNS domain.", strna(name));
×
257

258
        r = free_and_strdup(s, n);
94✔
259
        if (r < 0)
94✔
260
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
261

262
        return 0;
263
}
264

265
int json_dispatch_gecos(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
7,595✔
266
        char **s = userdata;
7,595✔
267
        const char *n;
7,595✔
268

269
        if (sd_json_variant_is_null(variant)) {
7,595✔
270
                *s = mfree(*s);
×
271
                return 0;
×
272
        }
273

274
        if (!sd_json_variant_is_string(variant))
7,595✔
275
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
276

277
        n = sd_json_variant_string(variant);
7,595✔
278
        if (valid_gecos(n)) {
7,595✔
279
                if (free_and_strdup(s, n) < 0)
7,595✔
280
                        return json_log_oom(variant, flags);
×
281
        } else {
282
                _cleanup_free_ char *m = NULL;
×
283

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

286
                m = mangle_gecos(n);
×
287
                if (!m)
×
288
                        return json_log_oom(variant, flags);
×
289

290
                free_and_replace(*s, m);
×
291
        }
292

293
        return 0;
294
}
295

296
static int json_dispatch_nice(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
169✔
297
        int *nl = userdata;
169✔
298
        int64_t m;
169✔
299

300
        if (sd_json_variant_is_null(variant)) {
169✔
301
                *nl = INT_MAX;
×
302
                return 0;
×
303
        }
304

305
        if (!sd_json_variant_is_integer(variant))
169✔
306
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
307

308
        m = sd_json_variant_integer(variant);
169✔
309
        if (m < PRIO_MIN || m >= PRIO_MAX)
169✔
310
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not a valid nice level.", strna(name));
×
311

312
        *nl = m;
169✔
313
        return 0;
169✔
314
}
315

316
static int json_dispatch_rlimit_value(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
317
        rlim_t *ret = userdata;
×
318

319
        if (sd_json_variant_is_null(variant))
×
320
                *ret = RLIM_INFINITY;
×
321
        else if (sd_json_variant_is_unsigned(variant)) {
×
322
                uint64_t w;
×
323

324
                w = sd_json_variant_unsigned(variant);
×
325
                if (w == RLIM_INFINITY || w != sd_json_variant_unsigned(variant))
×
326
                        return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "Resource limit value '%s' is out of range.", name);
×
327

328
                *ret = (rlim_t) w;
×
329
        } else
330
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit value '%s' is not an unsigned integer.", name);
×
331

332
        return 0;
333
}
334

335
static int json_dispatch_rlimits(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
336
        struct rlimit** limits = userdata;
×
337
        sd_json_variant *value;
×
338
        const char *key;
×
339
        int r;
×
340

341
        assert_se(limits);
×
342

343
        if (sd_json_variant_is_null(variant)) {
×
344
                rlimit_free_all(limits);
×
345
                return 0;
×
346
        }
347

348
        if (!sd_json_variant_is_object(variant))
×
349
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
350

351
        JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
×
352
                sd_json_variant *jcur, *jmax;
×
353
                struct rlimit rl;
×
354
                const char *p;
×
355
                int l;
×
356

357
                p = startswith(key, "RLIMIT_");
×
358
                if (!p)
×
359
                        l = -SYNTHETIC_ERRNO(EINVAL);
360
                else
361
                        l = rlimit_from_string(p);
×
362
                if (l < 0)
×
363
                        return json_log(variant, flags, l, "Resource limit '%s' not known.", key);
×
364

365
                if (!sd_json_variant_is_object(value))
×
366
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' has invalid value.", key);
×
367

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

371
                jcur = sd_json_variant_by_key(value, "cur");
×
372
                if (!jcur)
×
373
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'cur' field.", key);
×
374
                r = json_dispatch_rlimit_value("cur", jcur, flags, &rl.rlim_cur);
×
375
                if (r < 0)
×
376
                        return r;
377

378
                jmax = sd_json_variant_by_key(value, "max");
×
379
                if (!jmax)
×
380
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'max' field.", key);
×
381
                r = json_dispatch_rlimit_value("max", jmax, flags, &rl.rlim_max);
×
382
                if (r < 0)
×
383
                        return r;
384

385
                if (limits[l])
×
386
                        *(limits[l]) = rl;
×
387
                else {
388
                        limits[l] = newdup(struct rlimit, &rl, 1);
×
389
                        if (!limits[l])
×
390
                                return log_oom();
×
391
                }
392
        }
393

394
        return 0;
×
395
}
396

397
static int json_dispatch_filename_or_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
15,019✔
398
        char **s = ASSERT_PTR(userdata);
15,019✔
399
        const char *n;
15,019✔
400
        int r;
15,019✔
401

402
        if (sd_json_variant_is_null(variant)) {
15,019✔
403
                *s = mfree(*s);
×
404
                return 0;
×
405
        }
406

407
        if (!sd_json_variant_is_string(variant))
15,019✔
408
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
409

410
        n = sd_json_variant_string(variant);
15,019✔
411
        if (!filename_is_valid(n) && !path_is_normalized(n))
15,019✔
412
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid file name or normalized path.", strna(name));
×
413

414
        r = free_and_strdup(s, n);
15,019✔
415
        if (r < 0)
15,019✔
416
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
417

418
        return 0;
419
}
420

421
static int json_dispatch_birth_date(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
2✔
422
        struct tm *ret = ASSERT_PTR(userdata);
2✔
423
        const char *s;
2✔
424
        int r;
2✔
425

426
        if (sd_json_variant_is_null(variant)) {
2✔
427
                *ret = BIRTH_DATE_UNSET;
×
428
                return 0;
×
429
        }
430

431
        if (!sd_json_variant_is_string(variant))
2✔
432
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
433

434
        s = sd_json_variant_string(variant);
2✔
435

436
        r = parse_birth_date(s, ret);
2✔
437
        if (r < 0)
2✔
438
                return json_log(variant, flags, r, "JSON field '%s' is not a valid ISO 8601 date (expected YYYY-MM-DD).", strna(name));
×
439

440
        return 0;
441
}
442

443
static int json_dispatch_home_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
5,177✔
444
        char **s = userdata;
5,177✔
445
        const char *n;
5,177✔
446
        int r;
5,177✔
447

448
        if (sd_json_variant_is_null(variant)) {
5,177✔
449
                *s = mfree(*s);
×
450
                return 0;
×
451
        }
452

453
        if (!sd_json_variant_is_string(variant))
5,177✔
454
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
455

456
        n = sd_json_variant_string(variant);
5,177✔
457
        if (!valid_home(n))
5,177✔
458
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid home directory path.", strna(name));
×
459

460
        r = free_and_strdup(s, n);
5,177✔
461
        if (r < 0)
5,177✔
462
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
463

464
        return 0;
465
}
466

467
static int json_dispatch_image_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
1,774✔
468
        char **s = userdata;
1,774✔
469
        const char *n;
1,774✔
470
        int r;
1,774✔
471

472
        if (sd_json_variant_is_null(variant)) {
1,774✔
473
                *s = mfree(*s);
×
474
                return 0;
×
475
        }
476

477
        if (!sd_json_variant_is_string(variant))
1,774✔
478
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
479

480
        n = sd_json_variant_string(variant);
1,774✔
481
        if (empty_or_root(n) || !path_is_valid(n) || !path_is_absolute(n))
5,322✔
482
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid image path.", strna(name));
×
483

484
        r = free_and_strdup(s, n);
1,774✔
485
        if (r < 0)
1,774✔
486
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
487

488
        return 0;
489
}
490

491
static int json_dispatch_locale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
492
        char **s = userdata;
×
493
        const char *n;
×
494
        int r;
×
495

496
        if (sd_json_variant_is_null(variant)) {
×
497
                *s = mfree(*s);
×
498
                return 0;
×
499
        }
500

501
        if (!sd_json_variant_is_string(variant))
×
502
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
503

504
        n = sd_json_variant_string(variant);
×
505

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

509
        r = free_and_strdup(s, n);
×
510
        if (r < 0)
×
511
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
512

513
        return 0;
514
}
515

516
static int json_dispatch_locales(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
517
        _cleanup_strv_free_ char **n = NULL;
×
518
        char ***l = userdata;
×
519
        const char *locale;
×
520
        sd_json_variant *e;
×
521
        size_t s = 0;
×
522
        int r;
×
523

524
        if (sd_json_variant_is_null(variant)) {
×
525
                *l = strv_free(*l);
×
526
                return 0;
×
527
        }
528

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

532
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
533
                if (!sd_json_variant_is_string(e))
×
534
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
535

536
                locale = sd_json_variant_string(e);
×
537
                if (!locale_is_valid(locale))
×
538
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name));
×
539

540
                r = strv_extend_with_size(&n, &s, locale);
×
541
                if (r < 0)
×
542
                        return json_log_oom(variant, flags);
×
543
        }
544

545
        return strv_free_and_replace(*l, n);
×
546
}
547

548
JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
21,216✔
549
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
3,002✔
550

551
static int json_dispatch_tasks_or_memory_max(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
552
        uint64_t *limit = userdata, k;
×
553

554
        if (sd_json_variant_is_null(variant)) {
×
555
                *limit = UINT64_MAX;
×
556
                return 0;
×
557
        }
558

559
        if (!sd_json_variant_is_unsigned(variant))
×
560
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
×
561

562
        k = sd_json_variant_unsigned(variant);
×
563
        if (k <= 0 || k >= UINT64_MAX)
×
564
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
565
                                "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
566
                                strna(name), (uint64_t) 1, glyph(GLYPH_ELLIPSIS), UINT64_MAX-1);
567

568
        *limit = k;
×
569
        return 0;
×
570
}
571

572
static int json_dispatch_weight(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
4✔
573
        uint64_t *weight = userdata, k;
4✔
574

575
        if (sd_json_variant_is_null(variant)) {
4✔
576
                *weight = UINT64_MAX;
×
577
                return 0;
×
578
        }
579

580
        if (!sd_json_variant_is_unsigned(variant))
4✔
581
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
×
582

583
        k = sd_json_variant_unsigned(variant);
4✔
584
        if (k < CGROUP_WEIGHT_MIN || k > CGROUP_WEIGHT_MAX)
4✔
585
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
586
                                "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
587
                                strna(name), CGROUP_WEIGHT_MIN,
588
                                glyph(GLYPH_ELLIPSIS), CGROUP_WEIGHT_MAX);
589

590
        *weight = k;
4✔
591
        return 0;
4✔
592
}
593

594
int json_dispatch_user_group_list(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
663✔
595
        char ***list = ASSERT_PTR(userdata);
663✔
596
        _cleanup_strv_free_ char **l = NULL;
663✔
597
        size_t s = 0;
663✔
598
        int r;
663✔
599

600
        if (!sd_json_variant_is_array(variant))
663✔
601
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
602

603
        sd_json_variant *e;
663✔
604
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
1,717✔
605
                if (!sd_json_variant_is_string(e))
1,054✔
606
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
×
607

608
                if (!valid_user_group_name(sd_json_variant_string(e), FLAGS_SET(flags, SD_JSON_RELAX) ? VALID_USER_RELAX : 0))
1,054✔
609
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", sd_json_variant_string(e));
×
610

611
                r = strv_extend_with_size(&l, &s, sd_json_variant_string(e));
1,054✔
612
                if (r < 0)
1,054✔
613
                        return json_log(e, flags, r, "Failed to append array element: %m");
×
614
        }
615

616
        r = strv_extend_strv_consume(list, TAKE_PTR(l), /* filter_duplicates= */ true);
663✔
617
        if (r < 0)
663✔
618
                return json_log(variant, flags, r, "Failed to merge user/group arrays: %m");
×
619

620
        return 0;
621
}
622

623
static int dispatch_secret(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
169✔
624

625
        static const sd_json_dispatch_field secret_dispatch_table[] = {
169✔
626
                { "password",                                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, password),                                       0 },
627
                { "tokenPin",                                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, token_pin),                                      0 },
628
                { "pkcs11Pin",   /* legacy alias */             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, token_pin),                                      0 },
629
                { "pkcs11ProtectedAuthenticationPathPermitted", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
630
                { "fido2UserPresencePermitted",                 SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, fido2_user_presence_permitted),                  0 },
631
                { "fido2UserVerificationPermitted",             SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, fido2_user_verification_permitted),              0 },
632
                {},
633
        };
634

635
        return sd_json_dispatch(variant, secret_dispatch_table, flags, userdata);
169✔
636
}
637

638
static int dispatch_pkcs11_uri(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
639
        char **s = userdata;
×
640
        const char *n;
×
641
        int r;
×
642

643
        if (sd_json_variant_is_null(variant)) {
×
644
                *s = mfree(*s);
×
645
                return 0;
×
646
        }
647

648
        if (!sd_json_variant_is_string(variant))
×
649
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
650

651
        n = sd_json_variant_string(variant);
×
652
        if (!pkcs11_uri_valid(n))
×
653
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
×
654

655
        r = free_and_strdup(s, n);
×
656
        if (r < 0)
×
657
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
658

659
        return 0;
660
}
661

662
static int dispatch_pkcs11_uri_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
663
        _cleanup_strv_free_ char **z = NULL;
×
664
        char ***l = userdata;
×
665
        sd_json_variant *e;
×
666
        int r;
×
667

668
        if (sd_json_variant_is_null(variant)) {
×
669
                *l = strv_free(*l);
×
670
                return 0;
×
671
        }
672

673
        if (sd_json_variant_is_string(variant)) {
×
674
                const char *n;
×
675

676
                n = sd_json_variant_string(variant);
×
677
                if (!pkcs11_uri_valid(n))
×
678
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
×
679

680
                z = strv_new(n);
×
681
                if (!z)
×
682
                        return log_oom();
×
683

684
        } else {
685

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

689
                JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
690
                        const char *n;
×
691

692
                        if (!sd_json_variant_is_string(e))
×
693
                                return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
×
694

695
                        n = sd_json_variant_string(e);
×
696
                        if (!pkcs11_uri_valid(n))
×
697
                                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);
×
698

699
                        r = strv_extend(&z, n);
×
700
                        if (r < 0)
×
701
                                return log_oom();
×
702
                }
703
        }
704

705
        strv_free_and_replace(*l, z);
×
706
        return 0;
×
707
}
708

709
static int dispatch_pkcs11_key_data(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
710
        Pkcs11EncryptedKey *k = userdata;
×
711
        size_t l;
×
712
        void *b;
×
713
        int r;
×
714

715
        if (sd_json_variant_is_null(variant)) {
×
716
                k->data = erase_and_free(k->data);
×
717
                k->size = 0;
×
718
                return 0;
×
719
        }
720

721
        r = sd_json_variant_unbase64(variant, &b, &l);
×
722
        if (r < 0)
×
723
                return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
×
724

725
        erase_and_free(k->data);
×
726
        k->data = b;
×
727
        k->size = l;
×
728

729
        return 0;
×
730
}
731

732
static int dispatch_pkcs11_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
733
        UserRecord *h = userdata;
×
734
        sd_json_variant *e;
×
735
        int r;
×
736

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

740
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
741
                static const sd_json_dispatch_field pkcs11_key_dispatch_table[] = {
×
742
                        { "uri",            SD_JSON_VARIANT_STRING, dispatch_pkcs11_uri,      offsetof(Pkcs11EncryptedKey, uri),             SD_JSON_MANDATORY },
743
                        { "data",           SD_JSON_VARIANT_STRING, dispatch_pkcs11_key_data, 0,                                             SD_JSON_MANDATORY },
744
                        { "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string,  offsetof(Pkcs11EncryptedKey, hashed_password), SD_JSON_MANDATORY },
745
                        {},
746
                };
747

748
                if (!sd_json_variant_is_object(e))
×
749
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
750

751
                if (!GREEDY_REALLOC(h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key + 1))
×
752
                        return log_oom();
×
753

754
                Pkcs11EncryptedKey *k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key;
×
755
                *k = (Pkcs11EncryptedKey) {};
×
756

757
                r = sd_json_dispatch(e, pkcs11_key_dispatch_table, flags, k);
×
758
                if (r < 0) {
×
759
                        pkcs11_encrypted_key_done(k);
×
760
                        return r;
×
761
                }
762

763
                h->n_pkcs11_encrypted_key++;
×
764
        }
765

766
        return 0;
×
767
}
768

769
static int dispatch_fido2_hmac_credential(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
770
        Fido2HmacCredential *k = userdata;
×
771
        size_t l;
×
772
        void *b;
×
773
        int r;
×
774

775
        if (sd_json_variant_is_null(variant)) {
×
776
                k->id = mfree(k->id);
×
777
                k->size = 0;
×
778
                return 0;
×
779
        }
780

781
        r = sd_json_variant_unbase64(variant, &b, &l);
×
782
        if (r < 0)
×
783
                return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
×
784

785
        free_and_replace(k->id, b);
×
786
        k->size = l;
×
787

788
        return 0;
×
789
}
790

791
static int dispatch_fido2_hmac_credential_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
792
        UserRecord *h = userdata;
×
793
        sd_json_variant *e;
×
794
        int r;
×
795

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

799
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
800
                size_t l;
×
801
                void *b;
×
802

803
                if (!GREEDY_REALLOC(h->fido2_hmac_credential, h->n_fido2_hmac_credential + 1))
×
804
                        return log_oom();
×
805

806
                r = sd_json_variant_unbase64(e, &b, &l);
×
807
                if (r < 0)
×
808
                        return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
×
809

810
                h->fido2_hmac_credential[h->n_fido2_hmac_credential++] = (Fido2HmacCredential) {
×
811
                        .id = b,
812
                        .size = l,
813
                };
814
        }
815

816
        return 0;
×
817
}
818

819
static int dispatch_fido2_hmac_salt_value(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
820
        Fido2HmacSalt *k = userdata;
×
821
        size_t l;
×
822
        void *b;
×
823
        int r;
×
824

825
        if (sd_json_variant_is_null(variant)) {
×
826
                k->salt = erase_and_free(k->salt);
×
827
                k->salt_size = 0;
×
828
                return 0;
×
829
        }
830

831
        r = sd_json_variant_unbase64(variant, &b, &l);
×
832
        if (r < 0)
×
833
                return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
×
834

835
        erase_and_free(k->salt);
×
836
        k->salt = b;
×
837
        k->salt_size = l;
×
838

839
        return 0;
×
840
}
841

842
static int dispatch_fido2_hmac_salt(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
843
        UserRecord *h = userdata;
×
844
        sd_json_variant *e;
×
845
        int r;
×
846

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

850
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
851
                static const sd_json_dispatch_field fido2_hmac_salt_dispatch_table[] = {
×
852
                        { "credential",     SD_JSON_VARIANT_STRING,  dispatch_fido2_hmac_credential, offsetof(Fido2HmacSalt, credential),      SD_JSON_MANDATORY },
853
                        { "salt",           SD_JSON_VARIANT_STRING,  dispatch_fido2_hmac_salt_value, 0,                                        SD_JSON_MANDATORY },
854
                        { "hashedPassword", SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,        offsetof(Fido2HmacSalt, hashed_password), SD_JSON_MANDATORY },
855
                        { "up",             SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, up),              0                 },
856
                        { "uv",             SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, uv),              0                 },
857
                        { "clientPin",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, client_pin),      0                 },
858
                        {},
859
                };
860

861
                if (!sd_json_variant_is_object(e))
×
862
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
863

864
                if (!GREEDY_REALLOC(h->fido2_hmac_salt, h->n_fido2_hmac_salt + 1))
×
865
                        return log_oom();
×
866

867
                Fido2HmacSalt *k = h->fido2_hmac_salt + h->n_fido2_hmac_salt;
×
868
                *k = (Fido2HmacSalt) {
×
869
                        .uv = -1,
870
                        .up = -1,
871
                        .client_pin = -1,
872
                };
873

874
                r = sd_json_dispatch(e, fido2_hmac_salt_dispatch_table, flags, k);
×
875
                if (r < 0) {
×
876
                        fido2_hmac_salt_done(k);
×
877
                        return r;
×
878
                }
879

880
                h->n_fido2_hmac_salt++;
×
881
        }
882

883
        return 0;
×
884
}
885

886
static int dispatch_recovery_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
887
        UserRecord *h = userdata;
×
888
        sd_json_variant *e;
×
889
        int r;
×
890

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

894
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
895
                static const sd_json_dispatch_field recovery_key_dispatch_table[] = {
×
896
                        { "type",           SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0,                                      SD_JSON_MANDATORY },
897
                        { "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(RecoveryKey, hashed_password), SD_JSON_MANDATORY },
898
                        {},
899
                };
900

901
                if (!sd_json_variant_is_object(e))
×
902
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
903

904
                if (!GREEDY_REALLOC(h->recovery_key, h->n_recovery_key + 1))
×
905
                        return log_oom();
×
906

907
                RecoveryKey *k = h->recovery_key + h->n_recovery_key;
×
908
                *k = (RecoveryKey) {};
×
909

910
                r = sd_json_dispatch(e, recovery_key_dispatch_table, flags, k);
×
911
                if (r < 0) {
×
912
                        recovery_key_done(k);
×
913
                        return r;
×
914
                }
915

916
                h->n_recovery_key++;
×
917
        }
918

919
        return 0;
×
920
}
921

922
static int dispatch_auto_resize_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
923
        AutoResizeMode *mode = userdata, m;
×
924

925
        assert_se(mode);
×
926

927
        if (sd_json_variant_is_null(variant)) {
×
928
                *mode = _AUTO_RESIZE_MODE_INVALID;
×
929
                return 0;
×
930
        }
931

932
        if (sd_json_variant_is_boolean(variant)) {
×
933
                *mode = sd_json_variant_boolean(variant) ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
×
934
                return 0;
×
935
        }
936

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

940
        m = auto_resize_mode_from_string(sd_json_variant_string(variant));
×
941
        if (m < 0)
×
942
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid automatic resize mode.", strna(name));
×
943

944
        *mode = m;
×
945
        return 0;
×
946
}
947

948
static int dispatch_rebalance_weight(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
873✔
949
        uint64_t *rebalance_weight = userdata;
873✔
950
        uintmax_t u;
873✔
951

952
        assert_se(rebalance_weight);
873✔
953

954
        if (sd_json_variant_is_null(variant)) {
873✔
955
                *rebalance_weight = REBALANCE_WEIGHT_UNSET;
×
956
                return 0;
×
957
        }
958

959
        if (sd_json_variant_is_boolean(variant)) {
873✔
960
                *rebalance_weight = sd_json_variant_boolean(variant) ? REBALANCE_WEIGHT_DEFAULT : REBALANCE_WEIGHT_OFF;
×
961
                return 0;
×
962
        }
963

964
        if (!sd_json_variant_is_unsigned(variant))
873✔
965
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer, boolean or null.", strna(name));
×
966

967
        u = sd_json_variant_unsigned(variant);
873✔
968
        if (u >= REBALANCE_WEIGHT_MIN && u <= REBALANCE_WEIGHT_MAX)
873✔
969
                *rebalance_weight = (uint64_t) u;
×
970
        else if (u == 0)
873✔
971
                *rebalance_weight = REBALANCE_WEIGHT_OFF;
873✔
972
        else
973
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
974
                                "Rebalance weight is out of valid range %" PRIu64 "%s%" PRIu64 ".",
975
                                REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX);
976

977
        return 0;
978
}
979

980
static int dispatch_tmpfs_limit(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
438✔
981
        TmpfsLimit *limit = ASSERT_PTR(userdata);
438✔
982
        int r;
438✔
983

984
        if (sd_json_variant_is_null(variant)) {
438✔
985
                *limit = TMPFS_LIMIT_NULL;
×
986
                return 0;
×
987
        }
988

989
        r = sd_json_dispatch_uint64(name, variant, flags, &limit->limit);
438✔
990
        if (r < 0)
438✔
991
                return r;
992

993
        limit->is_set = true;
438✔
994
        return 0;
438✔
995
}
996

997
static int dispatch_tmpfs_limit_scale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
998
        TmpfsLimit *limit = ASSERT_PTR(userdata);
×
999
        int r;
×
1000

1001
        if (sd_json_variant_is_null(variant)) {
×
1002
                *limit = TMPFS_LIMIT_NULL;
×
1003
                return 0;
×
1004
        }
1005

1006
        r = sd_json_dispatch_uint32(name, variant, flags, &limit->limit_scale);
×
1007
        if (r < 0)
×
1008
                return r;
1009

1010
        limit->is_set = true;
×
1011
        return 0;
×
1012
}
1013

1014
static int dispatch_privileged(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
12,548✔
1015

1016
        static const sd_json_dispatch_field privileged_dispatch_table[] = {
12,548✔
1017
                { "passwordHint",       SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,  offsetof(UserRecord, password_hint),        0              },
1018
                { "hashedPassword",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,    offsetof(UserRecord, hashed_password),      SD_JSON_STRICT },
1019
                { "sshAuthorizedKeys",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,    offsetof(UserRecord, ssh_authorized_keys),  0              },
1020
                { "pkcs11EncryptedKey", SD_JSON_VARIANT_ARRAY,         dispatch_pkcs11_key,      0,                                          0              },
1021
                { "fido2HmacSalt",      SD_JSON_VARIANT_ARRAY,         dispatch_fido2_hmac_salt, 0,                                          0              },
1022
                { "recoveryKey",        SD_JSON_VARIANT_ARRAY,         dispatch_recovery_key,    0,                                          0              },
1023
                {},
1024
        };
1025

1026
        return sd_json_dispatch(variant, privileged_dispatch_table, flags, userdata);
12,548✔
1027
}
1028

1029
static int dispatch_binding(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
18,821✔
1030

1031
        static const sd_json_dispatch_field binding_dispatch_table[] = {
18,821✔
1032
                { "blobDirectory",     SD_JSON_VARIANT_STRING,        json_dispatch_path,           offsetof(UserRecord, blob_directory),       SD_JSON_STRICT },
1033
                { "imagePath",         SD_JSON_VARIANT_STRING,        json_dispatch_image_path,     offsetof(UserRecord, image_path),           0              },
1034
                { "homeDirectory",     SD_JSON_VARIANT_STRING,        json_dispatch_home_directory, offsetof(UserRecord, home_directory),       0              },
1035
                { "partitionUuid",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, partition_uuid),       0              },
1036
                { "luksUuid",          SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, luks_uuid),            0              },
1037
                { "fileSystemUuid",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, file_system_uuid),     0              },
1038
                { "uid",               SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,     offsetof(UserRecord, uid),                  0              },
1039
                { "gid",               SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,     offsetof(UserRecord, gid),                  0              },
1040
                { "storage",           SD_JSON_VARIANT_STRING,        json_dispatch_user_storage,   offsetof(UserRecord, storage),              0              },
1041
                { "fileSystemType",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, file_system_type),     SD_JSON_STRICT },
1042
                { "luksCipher",        SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, luks_cipher),          SD_JSON_STRICT },
1043
                { "luksCipherMode",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, luks_cipher_mode),     SD_JSON_STRICT },
1044
                { "luksVolumeKeySize", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,      offsetof(UserRecord, luks_volume_key_size), 0              },
1045
                {},
1046
        };
1047

1048
        sd_json_variant *m;
18,821✔
1049
        sd_id128_t mid;
18,821✔
1050
        int r;
18,821✔
1051

1052
        if (!variant)
18,821✔
1053
                return 0;
18,821✔
1054

1055
        if (!sd_json_variant_is_object(variant))
1,807✔
1056
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1057

1058
        r = sd_id128_get_machine(&mid);
1,807✔
1059
        if (r < 0)
1,807✔
1060
                return json_log(variant, flags, r, "Failed to determine machine ID: %m");
×
1061

1062
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
1,807✔
1063
        if (!m)
1,807✔
1064
                return 0;
1065

1066
        return sd_json_dispatch(m, binding_dispatch_table, flags, userdata);
1,807✔
1067
}
1068

1069
static int dispatch_blob_manifest(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
166✔
1070
        _cleanup_hashmap_free_ Hashmap *manifest = NULL;
166✔
1071
        Hashmap **ret = ASSERT_PTR(userdata);
166✔
1072
        sd_json_variant *value;
166✔
1073
        const char *key;
166✔
1074
        int r;
166✔
1075

1076
        if (!variant)
166✔
1077
                return 0;
1078

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

1082
        JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
528✔
1083
                _cleanup_free_ char *filename = NULL;
×
1084
                _cleanup_free_ uint8_t *hash = NULL;
362✔
1085

1086
                if (!sd_json_variant_is_string(value))
362✔
1087
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key);
×
1088

1089
                if (!suitable_blob_filename(key))
362✔
1090
                        return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key);
×
1091

1092
                filename = strdup(key);
362✔
1093
                if (!filename)
362✔
1094
                        return json_log_oom(value, flags);
×
1095

1096
                hash = malloc(SHA256_DIGEST_SIZE);
362✔
1097
                if (!hash)
362✔
1098
                        return json_log_oom(value, flags);
×
1099

1100
                r = parse_sha256(sd_json_variant_string(value), hash);
362✔
1101
                if (r < 0)
362✔
1102
                        return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, sd_json_variant_string(value));
×
1103

1104
                r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash);
362✔
1105
                if (r < 0)
362✔
1106
                        return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename);
×
1107
                TAKE_PTR(filename); /* Ownership transfers to hashmap */
362✔
1108
                TAKE_PTR(hash);
362✔
1109
        }
1110

1111
        hashmap_free_and_replace(*ret, manifest);
166✔
1112
        return 0;
166✔
1113
}
1114

1115
int per_machine_id_match(sd_json_variant *ids, sd_json_dispatch_flags_t flags) {
2,105✔
1116
        sd_id128_t mid;
2,105✔
1117
        int r;
2,105✔
1118

1119
        assert(ids);
2,105✔
1120

1121
        r = sd_id128_get_machine(&mid);
2,105✔
1122
        if (r < 0)
2,105✔
1123
                return json_log(ids, flags, r, "Failed to acquire machine ID: %m");
×
1124

1125
        if (sd_json_variant_is_string(ids)) {
2,105✔
1126
                sd_id128_t k;
2,105✔
1127

1128
                r = sd_id128_from_string(sd_json_variant_string(ids), &k);
2,105✔
1129
                if (r < 0) {
2,105✔
1130
                        json_log(ids, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(ids));
×
1131
                        return 0;
2,105✔
1132
                }
1133

1134
                return sd_id128_equal(mid, k);
4,210✔
1135
        }
1136

1137
        if (sd_json_variant_is_array(ids)) {
×
1138
                sd_json_variant *e;
×
1139

1140
                JSON_VARIANT_ARRAY_FOREACH(e, ids) {
×
1141
                        sd_id128_t k;
×
1142

1143
                        if (!sd_json_variant_is_string(e)) {
×
1144
                                json_log(e, flags, 0, "Machine ID is not a string, ignoring: %m");
×
1145
                                continue;
×
1146
                        }
1147

1148
                        r = sd_id128_from_string(sd_json_variant_string(e), &k);
×
1149
                        if (r < 0) {
×
1150
                                json_log(e, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(e));
×
1151
                                continue;
×
1152
                        }
1153

1154
                        if (sd_id128_equal(mid, k))
×
1155
                                return true;
×
1156
                }
1157

1158
                return false;
×
1159
        }
1160

1161
        json_log(ids, flags, 0, "Machine ID is not a string or array of strings, ignoring: %m");
×
1162
        return false;
1163
}
1164

1165
int per_machine_hostname_match(sd_json_variant *hns, sd_json_dispatch_flags_t flags) {
×
1166
        _cleanup_free_ char *hn = NULL;
×
1167
        int r;
×
1168

1169
        assert(hns);
×
1170

1171
        r = gethostname_strict(&hn);
×
1172
        if (r == -ENXIO) {
×
1173
                json_log(hns, flags, r, "No hostname set, not matching perMachine hostname record: %m");
×
1174
                return false;
1175
        }
1176
        if (r < 0)
×
1177
                return json_log(hns, flags, r, "Failed to acquire hostname: %m");
×
1178

1179
        if (sd_json_variant_is_string(hns))
×
1180
                return streq(sd_json_variant_string(hns), hn);
×
1181

1182
        if (sd_json_variant_is_array(hns)) {
×
1183
                sd_json_variant *e;
×
1184

1185
                JSON_VARIANT_ARRAY_FOREACH(e, hns) {
×
1186

1187
                        if (!sd_json_variant_is_string(e)) {
×
1188
                                json_log(e, flags, 0, "Hostname is not a string, ignoring: %m");
×
1189
                                continue;
×
1190
                        }
1191

1192
                        if (streq(sd_json_variant_string(e), hn))
×
1193
                                return true;
×
1194
                }
1195

1196
                return false;
×
1197
        }
1198

1199
        json_log(hns, flags, 0, "Hostname is not a string or array of strings, ignoring: %m");
×
1200
        return false;
1201
}
1202

1203
int per_machine_match(sd_json_variant *entry, sd_json_dispatch_flags_t flags) {
2,105✔
1204
        sd_json_variant *m;
2,105✔
1205
        int r;
2,105✔
1206

1207
        assert(sd_json_variant_is_object(entry));
2,105✔
1208

1209
        m = sd_json_variant_by_key(entry, "matchMachineId");
2,105✔
1210
        if (m) {
2,105✔
1211
                r = per_machine_id_match(m, flags);
2,023✔
1212
                if (r < 0)
2,023✔
1213
                        return r;
1214
                if (r > 0)
2,023✔
1215
                        return true;
1216
        }
1217

1218
        m = sd_json_variant_by_key(entry, "matchNotMachineId");
82✔
1219
        if (m) {
82✔
1220
                r = per_machine_id_match(m, flags);
82✔
1221
                if (r < 0)
82✔
1222
                        return r;
1223
                if (r == 0)
82✔
1224
                        return true;
1225
        }
1226

1227
        m = sd_json_variant_by_key(entry, "matchHostname");
82✔
1228
        if (m) {
82✔
1229
                r = per_machine_hostname_match(m, flags);
×
1230
                if (r < 0)
×
1231
                        return r;
1232
                if (r > 0)
×
1233
                        return true;
1234
        }
1235

1236
        m = sd_json_variant_by_key(entry, "matchNotHostname");
82✔
1237
        if (m) {
82✔
1238
                r = per_machine_hostname_match(m, flags);
×
1239
                if (r < 0)
×
1240
                        return r;
1241
                if (r == 0)
×
1242
                        return true;
×
1243
        }
1244

1245
        return false;
1246
}
1247

1248
static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
18,821✔
1249

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

1339
        sd_json_variant *e;
18,821✔
1340
        int r;
18,821✔
1341

1342
        if (!variant)
18,821✔
1343
                return 0;
1344

1345
        if (!sd_json_variant_is_array(variant))
1,985✔
1346
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
1347

1348
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
4,045✔
1349
                if (!sd_json_variant_is_object(e))
2,060✔
1350
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
×
1351

1352
                r = per_machine_match(e, flags);
2,060✔
1353
                if (r < 0)
2,060✔
1354
                        return r;
1355
                if (r == 0)
2,060✔
1356
                        continue;
75✔
1357

1358
                r = sd_json_dispatch(e, per_machine_dispatch_table, flags, userdata);
1,985✔
1359
                if (r < 0)
1,985✔
1360
                        return r;
1361
        }
1362

1363
        return 0;
1,985✔
1364
}
1365

1366
static int dispatch_status(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
18,821✔
1367

1368
        static const sd_json_dispatch_field status_dispatch_table[] = {
18,821✔
1369
                { "diskUsage",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_usage),                    0              },
1370
                { "diskFree",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_free),                     0              },
1371
                { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_size),                     0              },
1372
                { "diskCeiling",                _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_ceiling),                  0              },
1373
                { "diskFloor",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_floor),                    0              },
1374
                { "state",                      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, state),                         SD_JSON_STRICT },
1375
                { "service",                    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, service),                       SD_JSON_STRICT },
1376
                { "signedLocally",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,      offsetof(UserRecord, signed_locally),                0              },
1377
                { "goodAuthenticationCounter",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, good_authentication_counter),   0              },
1378
                { "badAuthenticationCounter",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, bad_authentication_counter),    0              },
1379
                { "lastGoodAuthenticationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_good_authentication_usec), 0              },
1380
                { "lastBadAuthenticationUSec",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_bad_authentication_usec),  0              },
1381
                { "rateLimitBeginUSec",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_begin_usec),          0              },
1382
                { "rateLimitCount",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_count),               0              },
1383
                { "removable",                  SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,      offsetof(UserRecord, removable),                     0              },
1384
                { "accessMode",                 _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode,      offsetof(UserRecord, access_mode),                   0              },
1385
                { "fileSystemType",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, file_system_type),              SD_JSON_STRICT },
1386
                { "fallbackShell",              SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell),                0              },
1387
                { "fallbackHomeDirectory",      SD_JSON_VARIANT_STRING,        json_dispatch_home_directory,   offsetof(UserRecord, fallback_home_directory),       0              },
1388
                { "useFallback",                SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,       offsetof(UserRecord, use_fallback),                  0              },
1389
                { "defaultArea",                SD_JSON_VARIANT_STRING,        json_dispatch_filename,         offsetof(UserRecord, default_area),                  0              },
1390
                { "aliases",                    SD_JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,  offsetof(UserRecord, aliases),                       SD_JSON_RELAX  },
1391
                {},
1392
        };
1393

1394
        sd_json_variant *m;
18,821✔
1395
        sd_id128_t mid;
18,821✔
1396
        int r;
18,821✔
1397

1398
        if (!variant)
18,821✔
1399
                return 0;
18,821✔
1400

1401
        if (!sd_json_variant_is_object(variant))
5,514✔
1402
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1403

1404
        r = sd_id128_get_machine(&mid);
5,514✔
1405
        if (r < 0)
5,514✔
1406
                return json_log(variant, flags, r, "Failed to determine machine ID: %m");
×
1407

1408
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
5,514✔
1409
        if (!m)
5,514✔
1410
                return 0;
1411

1412
        return sd_json_dispatch(m, status_dispatch_table, flags, userdata);
5,514✔
1413
}
1414

1415
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret) {
11,837✔
1416
        const char *suffix;
11,837✔
1417
        char *z;
11,837✔
1418

1419
        assert(storage >= 0);
11,837✔
1420
        assert(user_name_and_realm);
11,837✔
1421
        assert(ret);
11,837✔
1422

1423
        if (storage == USER_LUKS)
11,837✔
1424
                suffix = ".home";
1425
        else if (IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
11,837✔
1426
                suffix = ".homedir";
1427
        else {
1428
                *ret = NULL;
11,699✔
1429
                return 0;
11,699✔
1430
        }
1431

1432
        z = strjoin(get_home_root(), "/", user_name_and_realm, suffix);
138✔
1433
        if (!z)
138✔
1434
                return -ENOMEM;
1435

1436
        *ret = path_simplify(z);
138✔
1437
        return 1;
138✔
1438
}
1439

1440
static int user_record_augment(UserRecord *h, sd_json_dispatch_flags_t json_flags) {
18,821✔
1441
        int r;
18,821✔
1442

1443
        assert(h);
18,821✔
1444

1445
        if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
18,821✔
1446
                return 0;
1447

1448
        assert(h->user_name);
18,634✔
1449

1450
        if (!h->user_name_and_realm_auto && h->realm) {
18,634✔
1451
                h->user_name_and_realm_auto = strjoin(h->user_name, "@", h->realm);
58✔
1452
                if (!h->user_name_and_realm_auto)
58✔
1453
                        return json_log_oom(h->json, json_flags);
×
1454
        }
1455

1456
        /* Let's add in the following automatisms only for regular users, they don't make sense for any others */
1457
        if (user_record_disposition(h) != USER_REGULAR)
18,634✔
1458
                return 0;
1459

1460
        if (!h->home_directory && !h->home_directory_auto) {
13,747✔
1461
                h->home_directory_auto = path_join(get_home_root(), h->user_name);
11,987✔
1462
                if (!h->home_directory_auto)
11,987✔
1463
                        return json_log_oom(h->json, json_flags);
×
1464
        }
1465

1466
        if (!h->image_path && !h->image_path_auto) {
13,747✔
1467
                r = user_record_build_image_path(user_record_storage(h), user_record_user_name_and_realm(h), &h->image_path_auto);
11,835✔
1468
                if (r < 0)
11,835✔
1469
                        return json_log(h->json, json_flags, r, "Failed to determine default image path: %m");
×
1470
        }
1471

1472
        return 0;
1473
}
1474

1475
int user_group_record_mangle(
28,756✔
1476
                sd_json_variant *v,
1477
                UserRecordLoadFlags load_flags,
1478
                sd_json_variant **ret_variant,
1479
                UserRecordMask *ret_mask) {
1480

1481
        static const struct {
28,756✔
1482
                UserRecordMask mask;
1483
                const char *name;
1484
        } mask_field[] = {
1485
                { USER_RECORD_PRIVILEGED,  "privileged" },
1486
                { USER_RECORD_SECRET,      "secret"     },
1487
                { USER_RECORD_BINDING,     "binding"    },
1488
                { USER_RECORD_PER_MACHINE, "perMachine" },
1489
                { USER_RECORD_STATUS,      "status"     },
1490
                { USER_RECORD_SIGNATURE,   "signature"  },
1491
        };
1492

1493
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
28,756✔
1494
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
28,756✔
1495
        sd_json_variant *array[ELEMENTSOF(mask_field) * 2];
28,756✔
1496
        size_t n_retain = 0;
28,756✔
1497
        UserRecordMask m = 0;
28,756✔
1498
        int r;
28,756✔
1499

1500
        assert((load_flags & _USER_RECORD_MASK_MAX) == 0); /* detect mistakes when accidentally passing
28,756✔
1501
                                                            * UserRecordMask bit masks as UserRecordLoadFlags
1502
                                                            * value */
1503

1504
        assert(v);
28,756✔
1505
        assert(ret_variant);
28,756✔
1506

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

1510
        if (!sd_json_variant_is_object(v))
28,756✔
1511
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is not a JSON object, refusing.");
×
1512

1513
        if (USER_RECORD_ALLOW_MASK(load_flags) == 0) /* allow nothing? */
28,756✔
1514
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Nothing allowed in record, refusing.");
×
1515

1516
        if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */
28,756✔
1517
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing.");
×
1518

1519
        /* Extra safety: mark sensitive parts of the JSON as such, so that they are not included in debug
1520
         * output and erased from memory when we are done. We do this for any record that passes through here. */
1521
        FOREACH_STRING(key,
172,536✔
1522
                       /* This section contains literal passwords and such in plain text */
1523
                       "secret",
1524

1525
                       /* Personally Identifiable Information (PII) — avoid leaking in logs */
1526
                       "realName",
1527
                       "location",
1528
                       "emailAddress",
1529
                       "birthDate")
1530
                sd_json_variant_sensitive(sd_json_variant_by_key(v, key));
143,780✔
1531

1532
        /* Check if we have the special sections and if they match our flags set */
1533
        FOREACH_ELEMENT(i, mask_field) {
201,292✔
1534
                sd_json_variant *e, *k;
172,536✔
1535

1536
                if (FLAGS_SET(USER_RECORD_STRIP_MASK(load_flags), i->mask)) {
172,536✔
1537
                        if (!w)
12,508✔
1538
                                w = sd_json_variant_ref(v);
10,104✔
1539

1540
                        r = sd_json_variant_filter(&w, STRV_MAKE(i->name));
12,508✔
1541
                        if (r < 0)
12,508✔
1542
                                return json_log(w, json_flags, r, "Failed to remove field from variant: %m");
×
1543

1544
                        continue;
12,508✔
1545
                }
1546

1547
                e = sd_json_variant_by_key_full(v, i->name, &k);
160,028✔
1548
                if (e) {
160,028✔
1549
                        if (!FLAGS_SET(USER_RECORD_ALLOW_MASK(load_flags), i->mask))
27,980✔
1550
                                return json_log(e, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", i->name);
×
1551

1552
                        if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
27,980✔
1553
                                array[n_retain++] = k;
152✔
1554
                                array[n_retain++] = e;
152✔
1555
                        }
1556

1557
                        m |= i->mask;
27,980✔
1558
                } else {
1559
                        if (FLAGS_SET(USER_RECORD_REQUIRE_MASK(load_flags), i->mask))
132,048✔
1560
                                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks '%s' field, which is required.", i->name);
×
1561
                }
1562
        }
1563

1564
        if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
28,756✔
1565
                /* If we are supposed to strip regular items, then let's instead just allocate a new object
1566
                 * with just the stuff we need. */
1567

1568
                w = sd_json_variant_unref(w);
287✔
1569
                r = sd_json_variant_new_object(&w, array, n_retain);
287✔
1570
                if (r < 0)
287✔
1571
                        return json_log(v, json_flags, r, "Failed to allocate new object: %m");
×
1572
        } else
1573
                /* And now check if there's anything else in the record */
1574
                for (size_t i = 0; i < sd_json_variant_elements(v); i += 2) {
28,504✔
1575
                        const char *f;
28,469✔
1576
                        bool special = false;
28,469✔
1577

1578
                        assert_se(f = sd_json_variant_string(sd_json_variant_by_index(v, i)));
28,469✔
1579

1580
                        FOREACH_ELEMENT(j, mask_field)
199,283✔
1581
                                if (streq(f, j->name)) { /* already covered in the loop above */
170,814✔
1582
                                        special = true;
35✔
1583
                                        continue;
35✔
1584
                                }
1585

1586
                        if (!special) {
28,469✔
1587
                                if ((load_flags & (USER_RECORD_ALLOW_REGULAR|USER_RECORD_REQUIRE_REGULAR)) == 0)
28,434✔
1588
                                        return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", f);
×
1589

1590
                                m |= USER_RECORD_REGULAR;
28,434✔
1591
                                break;
28,434✔
1592
                        }
1593
                }
1594

1595
        if (FLAGS_SET(load_flags, USER_RECORD_REQUIRE_REGULAR) && !FLAGS_SET(m, USER_RECORD_REGULAR))
28,756✔
1596
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks basic identity fields, which are required.");
×
1597

1598
        if (!FLAGS_SET(load_flags, USER_RECORD_EMPTY_OK) && m == 0)
28,756✔
1599
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is empty.");
×
1600

1601
        if (w)
28,756✔
1602
                *ret_variant = TAKE_PTR(w);
10,104✔
1603
        else
1604
                *ret_variant = sd_json_variant_ref(v);
18,652✔
1605

1606
        if (ret_mask)
28,756✔
1607
                *ret_mask = m;
28,700✔
1608
        return 0;
1609
}
1610

1611
int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load_flags) {
18,821✔
1612

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

1709
                { "secret",                     SD_JSON_VARIANT_OBJECT,        dispatch_secret,                      0,                                                   0              },
1710
                { "privileged",                 SD_JSON_VARIANT_OBJECT,        dispatch_privileged,                  0,                                                   0              },
1711

1712
                /* Ignore the perMachine, binding, status stuff here, and process it later, so that it overrides whatever is set above */
1713
                { "perMachine",                 SD_JSON_VARIANT_ARRAY,         NULL,                                 0,                                                   0              },
1714
                { "binding",                    SD_JSON_VARIANT_OBJECT,        NULL,                                 0,                                                   0              },
1715
                { "status",                     SD_JSON_VARIANT_OBJECT,        NULL,                                 0,                                                   0              },
1716

1717
                /* Ignore 'signature', we check it with explicit accessors instead */
1718
                { "signature",                  SD_JSON_VARIANT_ARRAY,         NULL,                                 0,                                                   0              },
1719
                {},
1720
        };
1721

1722
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
18,821✔
1723
        int r;
18,821✔
1724

1725
        assert(h);
18,821✔
1726
        assert(!h->json);
18,821✔
1727

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

1730
        r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
18,821✔
1731
        if (r < 0)
18,821✔
1732
                return r;
1733

1734
        r = sd_json_dispatch(h->json, user_dispatch_table, json_flags | SD_JSON_ALLOW_EXTENSIONS, h);
18,821✔
1735
        if (r < 0)
18,821✔
1736
                return r;
1737

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

1741
        r = dispatch_per_machine("perMachine", sd_json_variant_by_key(h->json, "perMachine"), json_flags, h);
18,821✔
1742
        if (r < 0)
18,821✔
1743
                return r;
1744

1745
        r = dispatch_binding("binding", sd_json_variant_by_key(h->json, "binding"), json_flags, h);
18,821✔
1746
        if (r < 0)
18,821✔
1747
                return r;
1748

1749
        r = dispatch_status("status", sd_json_variant_by_key(h->json, "status"), json_flags, h);
18,821✔
1750
        if (r < 0)
18,821✔
1751
                return r;
1752

1753
        if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->user_name)
18,821✔
1754
                return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "User name field missing, refusing.");
×
1755

1756
        r = user_record_augment(h, json_flags);
18,821✔
1757
        if (r < 0)
18,821✔
1758
                return r;
×
1759

1760
        return 0;
1761
}
1762

1763
int user_record_build(UserRecord **ret, ...) {
3,003✔
1764
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
3,003✔
1765
        _cleanup_(user_record_unrefp) UserRecord *u = NULL;
3,003✔
1766
        va_list ap;
3,003✔
1767
        int r;
3,003✔
1768

1769
        assert(ret);
3,003✔
1770

1771
        va_start(ap, ret);
3,003✔
1772
        r = sd_json_buildv(&v, ap);
3,003✔
1773
        va_end(ap);
3,003✔
1774

1775
        if (r < 0)
3,003✔
1776
                return r;
1777

1778
        u = user_record_new();
3,003✔
1779
        if (!u)
3,003✔
1780
                return -ENOMEM;
1781

1782
        r = user_record_load(u, v, USER_RECORD_LOAD_FULL);
3,003✔
1783
        if (r < 0)
3,003✔
1784
                return r;
1785

1786
        *ret = TAKE_PTR(u);
3,003✔
1787
        return 0;
3,003✔
1788
}
1789

1790
const char* user_record_user_name_and_realm(UserRecord *h) {
11,988✔
1791
        assert(h);
11,988✔
1792

1793
        /* Return the pre-initialized joined string if it is defined */
1794
        if (h->user_name_and_realm_auto)
11,988✔
1795
                return h->user_name_and_realm_auto;
1796

1797
        /* If it's not defined then we cannot have a realm */
1798
        assert(!h->realm);
11,967✔
1799
        return h->user_name;
11,967✔
1800
}
1801

1802
UserStorage user_record_storage(UserRecord *h) {
25,682✔
1803
        assert(h);
25,682✔
1804

1805
        if (h->storage >= 0)
25,682✔
1806
                return h->storage;
2,467✔
1807

1808
        return USER_CLASSIC;
1809
}
1810

1811
const char* user_record_file_system_type(UserRecord *h) {
×
1812
        assert(h);
×
1813

1814
        return h->file_system_type ?: "btrfs";
×
1815
}
1816

1817
const char* user_record_skeleton_directory(UserRecord *h) {
145✔
1818
        assert(h);
145✔
1819

1820
        return h->skeleton_directory ?: "/etc/skel";
145✔
1821
}
1822

1823
mode_t user_record_access_mode(UserRecord *h) {
9✔
1824
        assert(h);
9✔
1825

1826
        return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
9✔
1827
}
1828

1829
static const char *user_record_home_directory_real(UserRecord *h) {
5,462✔
1830
        assert(h);
5,462✔
1831

1832
        if (h->home_directory)
5,462✔
1833
                return h->home_directory;
1834
        if (h->home_directory_auto)
2,349✔
1835
                return h->home_directory_auto;
1836

1837
        /* The root user is special, hence be special about it */
1838
        if (user_record_is_root(h))
53✔
1839
                return "/root";
×
1840

1841
        return "/";
1842
}
1843

1844
const char* user_record_home_directory(UserRecord *h) {
5,540✔
1845
        assert(h);
5,540✔
1846

1847
        if (h->use_fallback && h->fallback_home_directory)
5,540✔
1848
                return h->fallback_home_directory;
1849

1850
        return user_record_home_directory_real(h);
5,401✔
1851
}
1852

1853
const char* user_record_image_path(UserRecord *h) {
1,190✔
1854
        assert(h);
1,190✔
1855

1856
        if (h->image_path)
1,190✔
1857
                return h->image_path;
1858
        if (h->image_path_auto)
114✔
1859
                return h->image_path_auto;
1860

1861
        /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */
1862
        return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ?
61✔
1863
                user_record_home_directory_real(h) : NULL;
61✔
1864
}
1865

1866
static bool user_record_image_is_blockdev(UserRecord *h) {
×
1867
        assert(h);
×
1868

1869
        const char *p = user_record_image_path(h);
×
1870
        if (!p)
×
1871
                return false;
1872

1873
        return path_startswith(p, "/dev/");
×
1874
}
1875

1876
const char* user_record_cifs_user_name(UserRecord *h) {
×
1877
        assert(h);
×
1878

1879
        return h->cifs_user_name ?: h->user_name;
×
1880
}
1881

1882
unsigned long user_record_mount_flags(UserRecord *h) {
47✔
1883
        assert(h);
47✔
1884

1885
        return (h->nosuid ? MS_NOSUID : 0) |
47✔
1886
                (h->noexec ? MS_NOEXEC : 0) |
47✔
1887
                (h->nodev ? MS_NODEV : 0);
47✔
1888
}
1889

1890
static const char *user_record_shell_real(UserRecord *h) {
2,352✔
1891
        assert(h);
2,352✔
1892

1893
        if (h->shell)
2,352✔
1894
                return h->shell;
1895

1896
        if (user_record_is_root(h))
419✔
1897
                return "/bin/sh";
1898

1899
        if (user_record_disposition(h) == USER_REGULAR)
414✔
1900
                return DEFAULT_USER_SHELL;
392✔
1901

1902
        return NOLOGIN;
1903
}
1904

1905
const char* user_record_shell(UserRecord *h) {
2,352✔
1906
        const char *shell;
2,352✔
1907

1908
        assert(h);
2,352✔
1909

1910
        shell = user_record_shell_real(h);
2,352✔
1911

1912
        /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
1913
         * then let's not risk anything. */
1914
        if (h->use_fallback && h->fallback_shell)
2,352✔
1915
                return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
200✔
1916

1917
        return shell;
1918
}
1919

1920
const char* user_record_real_name(UserRecord *h) {
1,843✔
1921
        assert(h);
1,843✔
1922

1923
        return h->real_name ?: h->user_name;
1,843✔
1924
}
1925

1926
bool user_record_luks_discard(UserRecord *h) {
×
1927
        assert(h);
×
1928

1929
        if (h->luks_discard >= 0)
×
1930
                return h->luks_discard;
×
1931

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

1938
        return user_record_image_is_blockdev(h);
×
1939
}
1940

1941
bool user_record_luks_offline_discard(UserRecord *h) {
×
1942
        const char *ip;
×
1943

1944
        assert(h);
×
1945

1946
        if (h->luks_offline_discard >= 0)
×
1947
                return h->luks_offline_discard;
×
1948

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

1952
        ip = user_record_image_path(h);
×
1953
        if (!ip)
×
1954
                return false;
1955

1956
        if (path_startswith(ip, "/dev/"))
×
1957
                return user_record_luks_discard(h);
×
1958

1959
        return true;
1960
}
1961

1962
const char* user_record_luks_cipher(UserRecord *h) {
×
1963
        assert(h);
×
1964

1965
        return h->luks_cipher ?: "aes";
×
1966
}
1967

1968
const char* user_record_luks_cipher_mode(UserRecord *h) {
×
1969
        assert(h);
×
1970

1971
        return h->luks_cipher_mode ?: "xts-plain64";
×
1972
}
1973

1974
uint64_t user_record_luks_volume_key_size(UserRecord *h) {
×
1975
        assert(h);
×
1976

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

1979
        if (h->luks_volume_key_size == UINT64_MAX)
×
1980
                return 256 / 8;
×
1981

1982
        return MIN(h->luks_volume_key_size, SIZE_MAX);
1983
}
1984

1985
const char* user_record_luks_pbkdf_type(UserRecord *h) {
×
1986
        assert(h);
×
1987

1988
        return h->luks_pbkdf_type ?: "argon2id";
×
1989
}
1990

1991
uint64_t user_record_luks_pbkdf_force_iterations(UserRecord *h) {
×
1992
        assert(h);
×
1993

1994
        /* propagate default "benchmark" mode as itself */
1995
        if (h->luks_pbkdf_force_iterations == UINT64_MAX)
×
1996
                return UINT64_MAX;
1997

1998
        /* clamp everything else to actually accepted number of iterations of libcryptsetup */
1999
        return CLAMP(h->luks_pbkdf_force_iterations, 1U, UINT32_MAX);
×
2000
}
2001

2002
uint64_t user_record_luks_pbkdf_time_cost_usec(UserRecord *h) {
×
2003
        assert(h);
×
2004

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

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

2010
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_time_cost_usec, USEC_PER_MSEC), UINT32_MAX) * USEC_PER_MSEC;
×
2011
}
2012

2013
uint64_t user_record_luks_pbkdf_memory_cost(UserRecord *h) {
×
2014
        assert(h);
×
2015

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

2021
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_memory_cost, 1024), UINT32_MAX) * 1024;
×
2022
}
2023

2024
uint64_t user_record_luks_pbkdf_parallel_threads(UserRecord *h) {
×
2025
        assert(h);
×
2026

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

2031
        return MIN(h->luks_pbkdf_parallel_threads, UINT32_MAX);
×
2032
}
2033

2034
uint64_t user_record_luks_sector_size(UserRecord *h) {
×
2035
        assert(h);
×
2036

2037
        if (h->luks_sector_size == UINT64_MAX)
×
2038
                return 512;
2039

2040
        /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */
2041
        return CLAMP(UINT64_C(1) << (63 - __builtin_clzll(h->luks_sector_size)), 512U, 4096U);
×
2042
}
2043

2044
const char* user_record_luks_pbkdf_hash_algorithm(UserRecord *h) {
×
2045
        assert(h);
×
2046

2047
        return h->luks_pbkdf_hash_algorithm ?: "sha512";
×
2048
}
2049

2050
gid_t user_record_gid(UserRecord *h) {
4,803✔
2051
        assert(h);
4,803✔
2052

2053
        if (gid_is_valid(h->gid))
4,803✔
2054
                return h->gid;
2055

2056
        return (gid_t) h->uid;
2,297✔
2057
}
2058

2059
UserDisposition user_record_disposition(UserRecord *h) {
27,443✔
2060
        assert(h);
27,443✔
2061

2062
        if (h->disposition >= 0)
27,443✔
2063
                return h->disposition;
2064

2065
        /* If not declared, derive from UID */
2066

2067
        if (!uid_is_valid(h->uid))
4,143✔
2068
                return _USER_DISPOSITION_INVALID;
2069

2070
        if (user_record_is_root(h) || user_record_is_nobody(h))
4,123✔
2071
                return USER_INTRINSIC;
2072

2073
        if (uid_is_system(h->uid))
2,478✔
2074
                return USER_SYSTEM;
2075

2076
        if (uid_is_dynamic(h->uid) || uid_is_greeter(h->uid))
295✔
2077
                return USER_DYNAMIC;
2078

2079
        if (uid_is_container(h->uid))
295✔
2080
                return USER_CONTAINER;
2081

2082
        if (uid_is_foreign(h->uid))
177✔
2083
                return USER_FOREIGN;
2084

2085
        if (h->uid > INT32_MAX)
177✔
2086
                return USER_RESERVED;
×
2087

2088
        return USER_REGULAR;
2089
}
2090

2091
int user_record_removable(UserRecord *h) {
11,707✔
2092
        UserStorage storage;
11,707✔
2093
        assert(h);
11,707✔
2094

2095
        if (h->removable >= 0)
11,707✔
2096
                return h->removable;
2097

2098
        /* Refuse to decide for classic records */
2099
        storage = user_record_storage(h);
11,707✔
2100
        if (h->storage < 0 || h->storage == USER_CLASSIC)
11,707✔
2101
                return -1;
2102

2103
        /* For now consider only LUKS home directories with a reference by path as removable */
2104
        return storage == USER_LUKS && user_record_image_is_blockdev(h);
377✔
2105
}
2106

2107
uint64_t user_record_ratelimit_interval_usec(UserRecord *h) {
134✔
2108
        assert(h);
134✔
2109

2110
        if (h->ratelimit_interval_usec == UINT64_MAX)
134✔
2111
                return DEFAULT_RATELIMIT_INTERVAL_USEC;
43✔
2112

2113
        return h->ratelimit_interval_usec;
2114
}
2115

2116
uint64_t user_record_ratelimit_burst(UserRecord *h) {
307✔
2117
        assert(h);
307✔
2118

2119
        if (h->ratelimit_burst == UINT64_MAX)
307✔
2120
                return DEFAULT_RATELIMIT_BURST;
123✔
2121

2122
        return h->ratelimit_burst;
2123
}
2124

2125
bool user_record_can_authenticate(UserRecord *h) {
×
2126
        assert(h);
×
2127

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

2130
        if (h->n_pkcs11_encrypted_key > 0)
×
2131
                return true;
2132

2133
        if (h->n_fido2_hmac_salt > 0)
×
2134
                return true;
2135

2136
        return !strv_isempty(h->hashed_password);
×
2137
}
2138

2139
bool user_record_drop_caches(UserRecord *h) {
216✔
2140
        assert(h);
216✔
2141

2142
        if (h->drop_caches >= 0)
216✔
2143
                return h->drop_caches;
×
2144

2145
        /* By default drop caches on fscrypt, not otherwise. */
2146
        return user_record_storage(h) == USER_FSCRYPT;
216✔
2147
}
2148

2149
AutoResizeMode user_record_auto_resize_mode(UserRecord *h) {
×
2150
        assert(h);
×
2151

2152
        if (h->auto_resize_mode >= 0)
×
2153
                return h->auto_resize_mode;
2154

2155
        return user_record_storage(h) == USER_LUKS ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
×
2156
}
2157

2158
uint64_t user_record_rebalance_weight(UserRecord *h) {
148✔
2159
        assert(h);
148✔
2160

2161
        if (h->rebalance_weight == REBALANCE_WEIGHT_UNSET)
148✔
2162
                return REBALANCE_WEIGHT_DEFAULT;
33✔
2163

2164
        return h->rebalance_weight;
2165
}
2166

2167
static uint64_t parse_caps_strv(char **l) {
×
2168
        uint64_t c = 0;
×
2169
        int r;
×
2170

2171
        STRV_FOREACH(i, l) {
×
2172
                r = capability_from_name(*i);
×
2173
                if (r < 0)
×
2174
                        log_debug_errno(r, "Don't know capability '%s', ignoring: %m", *i);
×
2175
                else
2176
                        c |= UINT64_C(1) << r;
×
2177
        }
2178

2179
        return c;
×
2180
}
2181

2182
uint64_t user_record_capability_bounding_set(UserRecord *h) {
644✔
2183
        assert(h);
644✔
2184

2185
        /* Returns UINT64_MAX if no bounding set is configured (!) */
2186

2187
        if (!h->capability_bounding_set)
644✔
2188
                return UINT64_MAX;
2189

2190
        return parse_caps_strv(h->capability_bounding_set);
×
2191
}
2192

2193
uint64_t user_record_capability_ambient_set(UserRecord *h) {
644✔
2194
        assert(h);
644✔
2195

2196
        /* Returns UINT64_MAX if no ambient set is configured (!) */
2197

2198
        if (!h->capability_ambient_set)
644✔
2199
                return UINT64_MAX;
2200

2201
        return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
×
2202
}
2203

2204
int user_record_languages(UserRecord *h, char ***ret) {
644✔
2205
        _cleanup_strv_free_ char **l = NULL;
644✔
2206
        int r;
644✔
2207

2208
        assert(h);
644✔
2209
        assert(ret);
644✔
2210

2211
        if (h->preferred_language) {
644✔
2212
                l = strv_new(h->preferred_language);
×
2213
                if (!l)
×
2214
                        return -ENOMEM;
2215
        }
2216

2217
        r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true);
644✔
2218
        if (r < 0)
644✔
2219
                return r;
2220

2221
        *ret = TAKE_PTR(l);
644✔
2222
        return 0;
644✔
2223
}
2224

2225
uint32_t user_record_tmp_limit_scale(UserRecord *h) {
269✔
2226
        assert(h);
269✔
2227

2228
        if (h->tmp_limit.is_set)
269✔
2229
                return h->tmp_limit.limit_scale;
2✔
2230

2231
        /* By default grant regular users only 80% quota */
2232
        if (user_record_disposition(h) == USER_REGULAR)
267✔
2233
                return UINT32_SCALE_FROM_PERCENT(80);
213✔
2234

2235
        return UINT32_MAX;
2236
}
2237

2238
uint32_t user_record_dev_shm_limit_scale(UserRecord *h) {
269✔
2239
        assert(h);
269✔
2240

2241
        if (h->dev_shm_limit.is_set)
269✔
2242
                return h->dev_shm_limit.limit_scale;
2✔
2243

2244
        /* By default grant regular users only 80% quota */
2245
        if (user_record_disposition(h) == USER_REGULAR)
267✔
2246
                return UINT32_SCALE_FROM_PERCENT(80);
213✔
2247

2248
        return UINT32_MAX;
2249
}
2250

2251
const char** user_record_self_modifiable_fields(UserRecord *h) {
242✔
2252
        /* As a rule of thumb: a setting is safe if it cannot be used by a
2253
         * user to give themselves some unfair advantage over other users on
2254
         * a given system. */
2255
        static const char *const default_fields[] = {
242✔
2256
                /* For display purposes */
2257
                "realName",
2258
                "emailAddress", /* Just the $EMAIL env var */
2259
                "iconName",
2260
                "location",
2261

2262
                /* Basic account settings */
2263
                "shell",
2264
                "umask",
2265
                "environment",
2266
                "timeZone",
2267
                "preferredLanguage",
2268
                "additionalLanguages",
2269
                "preferredSessionLauncher",
2270
                "preferredSessionType",
2271
                "defaultArea",
2272

2273
                /* Authentication methods */
2274
                "pkcs11TokenUri",
2275
                "fido2HmacCredential",
2276
                "recoveryKeyType",
2277

2278
                "lastChangeUSec", /* Necessary to be able to change record at all */
2279
                "lastPasswordChangeUSec", /* Ditto, but for authentication methods */
2280
                NULL
2281
        };
2282

2283
        assert(h);
242✔
2284

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

2289
        /* Note that we intentionally distinguish between NULL and an empty array here */
2290
        if (h->self_modifiable_fields)
242✔
2291
                return (const char**) h->self_modifiable_fields;
2292

2293
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_fields : NULL;
240✔
2294
}
2295

2296
const char** user_record_self_modifiable_blobs(UserRecord *h) {
158✔
2297
        static const char *const default_blobs[] = {
158✔
2298
                /* For display purposes */
2299
                "avatar",
2300
                "login-background",
2301
                NULL
2302
        };
2303

2304
        assert(h);
158✔
2305

2306
        /* Note that we intentionally distinguish between NULL and an empty array here */
2307
        if (h->self_modifiable_blobs)
158✔
2308
                return (const char**) h->self_modifiable_blobs;
2309

2310
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_blobs : NULL;
158✔
2311
}
2312

2313
const char** user_record_self_modifiable_privileged(UserRecord *h) {
192✔
2314
        static const char *const default_fields[] = {
192✔
2315
                /* For display purposes */
2316
                "passwordHint",
2317

2318
                /* Authentication methods */
2319
                "hashedPassword",
2320
                "pkcs11EncryptedKey",
2321
                "fido2HmacSalt",
2322
                "recoveryKey",
2323

2324
                "sshAuthorizedKeys", /* Basically just ~/.ssh/authorized_keys */
2325
                NULL
2326
        };
2327

2328
        assert(h);
192✔
2329

2330
        /* Note that we intentionally distinguish between NULL and an empty array here */
2331
        if (h->self_modifiable_privileged)
192✔
2332
                return (const char**) h->self_modifiable_privileged;
2333

2334
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_fields : NULL;
190✔
2335
}
2336

2337
static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_json_variant **target) {
94✔
2338
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *blobs = NULL;
94✔
2339
        char **allowed;
94✔
2340
        int r;
94✔
2341

2342
        assert(current);
94✔
2343
        assert(target);
94✔
2344

2345
        if (!sd_json_variant_is_object(*target))
94✔
2346
                return -EINVAL;
2347

2348
        v = sd_json_variant_ref(*target);
94✔
2349

2350
        /* Handle basic fields */
2351
        allowed = (char**) user_record_self_modifiable_fields(current);
94✔
2352
        r = sd_json_variant_filter(&v, allowed);
94✔
2353
        if (r < 0)
94✔
2354
                return r;
2355

2356
        /* Handle blobs */
2357
        blobs = sd_json_variant_ref(sd_json_variant_by_key(v, "blobManifest"));
94✔
2358
        if (blobs) {
94✔
2359
                /* The blobManifest contains the sha256 hashes of the blobs,
2360
                 * which are enforced by the service managing the user. So, by
2361
                 * comparing the blob manifests like this, we're actually comparing
2362
                 * the contents of the blob directories & files */
2363

2364
                allowed = (char**) user_record_self_modifiable_blobs(current);
10✔
2365
                r = sd_json_variant_filter(&blobs, allowed);
10✔
2366
                if (r < 0)
10✔
2367
                        return r;
2368

2369
                if (sd_json_variant_is_blank_object(blobs))
10✔
2370
                        r = sd_json_variant_filter(&v, STRV_MAKE("blobManifest"));
×
2371
                else
2372
                        r = sd_json_variant_set_field(&v, "blobManifest", blobs);
10✔
2373
                if (r < 0)
10✔
2374
                        return r;
2375
        }
2376

2377
        JSON_VARIANT_REPLACE(*target, TAKE_PTR(v));
94✔
2378
        return 0;
94✔
2379
}
2380

2381
static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) {
56✔
2382
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *privileged = NULL;
56✔
2383
        sd_json_variant *per_machine;
56✔
2384
        char **allowed;
56✔
2385
        int r;
56✔
2386

2387
        assert(current);
56✔
2388
        assert(h);
56✔
2389
        assert(ret);
56✔
2390

2391
        r = user_group_record_mangle(h->json, USER_RECORD_EXTRACT_SIGNABLE|USER_RECORD_PERMISSIVE, &v, NULL);
56✔
2392
        if (r < 0)
56✔
2393
                return r;
2394

2395
        /* Handle the regular section */
2396
        r = remove_self_modifiable_json_fields_common(current, &v);
56✔
2397
        if (r < 0)
56✔
2398
                return r;
2399

2400
        /* Handle the perMachine section */
2401
        per_machine = sd_json_variant_by_key(v, "perMachine");
56✔
2402
        if (per_machine) {
56✔
2403
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_per_machine = NULL;
38✔
2404
                sd_json_variant *e;
38✔
2405

2406
                if (!sd_json_variant_is_array(per_machine))
38✔
2407
                        return -EINVAL;
2408

2409
                JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
83✔
2410
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *z = NULL;
45✔
2411

2412
                        if (!sd_json_variant_is_object(e))
45✔
2413
                                return -EINVAL;
2414

2415
                        r = per_machine_match(e, 0);
45✔
2416
                        if (r < 0)
45✔
2417
                                return r;
2418
                        if (r == 0) {
45✔
2419
                                /* It's only permissible to change anything inside of matching perMachine sections */
2420
                                r = sd_json_variant_append_array(&new_per_machine, e);
7✔
2421
                                if (r < 0)
7✔
2422
                                        return r;
2423
                                continue;
7✔
2424
                        }
2425

2426
                        z = sd_json_variant_ref(e);
38✔
2427

2428
                        r = remove_self_modifiable_json_fields_common(current, &z);
38✔
2429
                        if (r < 0)
38✔
2430
                                return r;
2431

2432
                        if (!sd_json_variant_is_blank_object(z)) {
38✔
2433
                                r = sd_json_variant_append_array(&new_per_machine, z);
38✔
2434
                                if (r < 0)
38✔
2435
                                        return r;
2436
                        }
2437
                }
2438

2439
                if (sd_json_variant_is_blank_array(new_per_machine))
38✔
2440
                        r = sd_json_variant_filter(&v, STRV_MAKE("perMachine"));
×
2441
                else
2442
                        r = sd_json_variant_set_field(&v, "perMachine", new_per_machine);
38✔
2443
                if (r < 0)
38✔
2444
                        return r;
2445
        }
2446

2447
        /* Handle the privileged section */
2448
        privileged = sd_json_variant_ref(sd_json_variant_by_key(v, "privileged"));
56✔
2449
        if (privileged) {
56✔
2450
                allowed = (char**) user_record_self_modifiable_privileged(current);
44✔
2451
                r = sd_json_variant_filter(&privileged, allowed);
44✔
2452
                if (r < 0)
44✔
2453
                        return r;
2454

2455
                if (sd_json_variant_is_blank_object(privileged))
44✔
2456
                        r = sd_json_variant_filter(&v, STRV_MAKE("privileged"));
42✔
2457
                else
2458
                        r = sd_json_variant_set_field(&v, "privileged", privileged);
2✔
2459
                if (r < 0)
44✔
2460
                        return r;
2461
        }
2462

2463
        JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
56✔
2464
        return 0;
56✔
2465
}
2466

2467
int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) {
28✔
2468
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *vc = NULL, *vi = NULL;
56✔
2469
        int r;
28✔
2470

2471
        assert(current);
28✔
2472
        assert(incoming);
28✔
2473

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

2479
        r = remove_self_modifiable_json_fields(current, current, &vc);
28✔
2480
        if (r < 0)
28✔
2481
                return r;
2482

2483
        /* Note that we use `current` as the source of the allowlist, and not
2484
         * `incoming`. This prevents the user from adding fields. Consider a
2485
         * scenario that would've been possible if we had messed up this check:
2486
         *
2487
         * 1) A user starts out with no group memberships and no custom allowlist.
2488
         *    Thus, this user is not an administrator, and the `memberOf` and
2489
         *    `selfModifiableFields` fields are unset in their record.
2490
         * 2) This user crafts a request to add the following to their record:
2491
         *    { "memberOf": ["wheel"], "selfModifiableFields": ["memberOf", "selfModifiableFields"] }
2492
         * 3) We remove the `memberOf` and `selfModifiabileFields` fields from `incoming`
2493
         * 4) `current` and `incoming` compare as equal, so we let the change happen
2494
         * 5) the user has granted themselves administrator privileges
2495
         */
2496
        r = remove_self_modifiable_json_fields(current, incoming, &vi);
28✔
2497
        if (r < 0)
28✔
2498
                return r;
2499

2500
        return sd_json_variant_equal(vc, vi);
28✔
2501
}
2502

2503
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
305✔
2504
        assert(h);
305✔
2505

2506
        /* Calculates when the it's possible to login next. Returns:
2507
         *
2508
         * UINT64_MAX → Nothing known
2509
         * 0          → Right away
2510
         * Any other  → Next time in CLOCK_REALTIME in usec (which could be in the past)
2511
         */
2512

2513
        if (h->ratelimit_begin_usec == UINT64_MAX ||
305✔
2514
            h->ratelimit_count == UINT64_MAX)
181✔
2515
                return UINT64_MAX;
2516

2517
        if (h->ratelimit_begin_usec > now(CLOCK_REALTIME)) /* If the ratelimit time is in the future, then
181✔
2518
                                                            * the local clock is probably incorrect. Let's
2519
                                                            * not refuse login then. */
2520
                return UINT64_MAX;
2521

2522
        if (h->ratelimit_count < user_record_ratelimit_burst(h))
181✔
2523
                return 0;
2524

2525
        return usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h));
×
2526
}
2527

2528
bool user_record_equal(UserRecord *a, UserRecord *b) {
159✔
2529
        assert(a);
159✔
2530
        assert(b);
159✔
2531

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

2535
        return sd_json_variant_equal(a->json, b->json);
159✔
2536
}
2537

2538
bool user_record_compatible(UserRecord *a, UserRecord *b) {
153✔
2539
        assert(a);
153✔
2540
        assert(b);
153✔
2541

2542
        /* If either lacks the regular section, we can't really decide, let's hence say they are
2543
         * incompatible. */
2544
        if (!(a->mask & b->mask & USER_RECORD_REGULAR))
153✔
2545
                return false;
2546

2547
        return streq_ptr(a->user_name, b->user_name) &&
153✔
2548
                streq_ptr(a->realm, b->realm);
153✔
2549
}
2550

2551
int user_record_compare_last_change(UserRecord *a, UserRecord *b) {
21✔
2552
        assert(a);
21✔
2553
        assert(b);
21✔
2554

2555
        if (a->last_change_usec == b->last_change_usec)
21✔
2556
                return 0;
2557

2558
        /* Always consider a record with a timestamp newer than one without */
2559
        if (a->last_change_usec == UINT64_MAX)
21✔
2560
                return -1;
2561
        if (b->last_change_usec == UINT64_MAX)
21✔
2562
                return 1;
2563

2564
        return CMP(a->last_change_usec, b->last_change_usec);
21✔
2565
}
2566

2567
int user_record_clone(UserRecord *h, UserRecordLoadFlags flags, UserRecord **ret) {
5,678✔
2568
        _cleanup_(user_record_unrefp) UserRecord *c = NULL;
5,678✔
2569
        int r;
5,678✔
2570

2571
        assert(h);
5,678✔
2572
        assert(ret);
5,678✔
2573

2574
        c = user_record_new();
5,678✔
2575
        if (!c)
5,678✔
2576
                return -ENOMEM;
2577

2578
        r = user_record_load(c, h->json, flags);
5,678✔
2579
        if (r < 0)
5,678✔
2580
                return r;
2581

2582
        *ret = TAKE_PTR(c);
5,678✔
2583
        return 0;
5,678✔
2584
}
2585

2586
int user_record_masked_equal(UserRecord *a, UserRecord *b, UserRecordMask mask) {
24✔
2587
        _cleanup_(user_record_unrefp) UserRecord *x = NULL, *y = NULL;
24✔
2588
        int r;
24✔
2589

2590
        assert(a);
24✔
2591
        assert(b);
24✔
2592

2593
        /* Compares the two records, but ignores anything not listed in the specified mask */
2594

2595
        if ((a->mask & ~mask) != 0) {
24✔
2596
                r = user_record_clone(a, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &x);
24✔
2597
                if (r < 0)
24✔
2598
                        return r;
2599

2600
                a = x;
24✔
2601
        }
2602

2603
        if ((b->mask & ~mask) != 0) {
24✔
2604
                r = user_record_clone(b, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &y);
24✔
2605
                if (r < 0)
24✔
2606
                        return r;
2607

2608
                b = y;
24✔
2609
        }
2610

2611
        return user_record_equal(a, b);
24✔
2612
}
2613

2614
int user_record_test_blocked(UserRecord *h) {
198✔
2615
        usec_t n;
198✔
2616

2617
        /* Checks whether access to the specified user shall be allowed at the moment. Returns:
2618
         *
2619
         *          -ESTALE: Record is from the future
2620
         *          -ENOLCK: Record is blocked
2621
         *          -EL2HLT: Record is not valid yet
2622
         *          -EL3HLT: Record is not valid anymore
2623
         *
2624
         */
2625

2626
        assert(h);
198✔
2627

2628
        if (h->locked > 0)
198✔
2629
                return -ENOLCK;
2630

2631
        n = now(CLOCK_REALTIME);
168✔
2632

2633
        if (h->not_before_usec != UINT64_MAX && n < h->not_before_usec)
168✔
2634
                return -EL2HLT;
2635
        if (h->not_after_usec != UINT64_MAX && n > h->not_after_usec)
168✔
2636
                return -EL3HLT;
2637

2638
        if (h->last_change_usec != UINT64_MAX &&
168✔
2639
            h->last_change_usec > n) /* Complain during log-ins when the record is from the future */
2640
                return -ESTALE;
×
2641

2642
        return 0;
2643
}
2644

2645
int user_record_test_password_change_required(UserRecord *h) {
200✔
2646
        bool change_permitted;
200✔
2647
        usec_t n;
200✔
2648

2649
        assert(h);
200✔
2650

2651
        /* Checks whether the user must change the password when logging in
2652

2653
            -EKEYREVOKED: Change password now because admin said so
2654
             -EOWNERDEAD: Change password now because it expired
2655
           -EKEYREJECTED: Password is expired, no changing is allowed
2656
            -EKEYEXPIRED: Password is about to expire, warn user
2657
               -ENETDOWN: Record has expiration info but no password change timestamp
2658
                  -EROFS: No password change required nor permitted
2659
                 -ESTALE: RTC likely incorrect, last password change is in the future
2660
                       0: No password change required, but permitted
2661
         */
2662

2663
        /* If a password change request has been set explicitly, it overrides everything */
2664
        if (h->password_change_now > 0)
200✔
2665
                return -EKEYREVOKED;
2666

2667
        n = now(CLOCK_REALTIME);
200✔
2668

2669
        /* Password change in the future? Then our RTC is likely incorrect */
2670
        if (h->last_password_change_usec != UINT64_MAX &&
200✔
2671
            h->last_password_change_usec > n &&
×
2672
            (h->password_change_min_usec != UINT64_MAX ||
×
2673
             h->password_change_max_usec != UINT64_MAX ||
×
2674
             h->password_change_inactive_usec != UINT64_MAX))
×
2675
            return -ESTALE;
2676

2677
        /* Then, let's check if password changing is currently allowed at all */
2678
        if (h->password_change_min_usec != UINT64_MAX) {
200✔
2679

2680
                /* Expiry configured but no password change timestamp known? */
2681
                if (h->last_password_change_usec == UINT64_MAX)
×
2682
                        return -ENETDOWN;
2683

2684
                if (h->password_change_min_usec >= UINT64_MAX - h->last_password_change_usec)
×
2685
                        change_permitted = false;
2686
                else
2687
                        change_permitted = n >= h->last_password_change_usec + h->password_change_min_usec;
×
2688

2689
        } else
2690
                change_permitted = true;
2691

2692
        /* Let's check whether the password has expired.  */
2693
        if (!(h->password_change_max_usec == UINT64_MAX ||
200✔
2694
              h->password_change_max_usec >= UINT64_MAX - h->last_password_change_usec)) {
×
2695

2696
                uint64_t change_before;
×
2697

2698
                /* Expiry configured but no password change timestamp known? */
2699
                if (h->last_password_change_usec == UINT64_MAX)
×
2700
                        return -ENETDOWN;
2701

2702
                /* Password is in inactive phase? */
2703
                if (h->password_change_inactive_usec != UINT64_MAX &&
×
2704
                    h->password_change_inactive_usec < UINT64_MAX - h->password_change_max_usec) {
×
2705
                        usec_t added;
×
2706

2707
                        added = h->password_change_inactive_usec + h->password_change_max_usec;
×
2708
                        if (added < UINT64_MAX - h->last_password_change_usec &&
×
2709
                            n >= h->last_password_change_usec + added)
×
2710
                                return -EKEYREJECTED;
2711
                }
2712

2713
                /* Password needs to be changed now? */
2714
                change_before = h->last_password_change_usec + h->password_change_max_usec;
×
2715
                if (n >= change_before)
×
2716
                        return change_permitted ? -EOWNERDEAD : -EKEYREJECTED;
×
2717

2718
                /* Warn user? */
2719
                if (h->password_change_warn_usec != UINT64_MAX &&
×
2720
                    (change_before < h->password_change_warn_usec ||
×
2721
                     n >= change_before - h->password_change_warn_usec))
×
2722
                        return change_permitted ? -EKEYEXPIRED : -EROFS;
×
2723
        }
2724

2725
        /* No password changing necessary */
2726
        return change_permitted ? 0 : -EROFS;
200✔
2727
}
2728

2729
bool user_record_is_root(const UserRecord *u) {
5,075✔
2730
        assert(u);
5,075✔
2731

2732
        return u->uid == 0 || streq_ptr(u->user_name, "root");
5,075✔
2733
}
2734

2735
bool user_record_is_nobody(const UserRecord *u) {
2,725✔
2736
        assert(u);
2,725✔
2737

2738
        return u->uid == UID_NOBODY || STRPTR_IN_SET(u->user_name, NOBODY_USER_NAME, "nobody");
2,725✔
2739
}
2740

2741
bool user_record_matches_user_name(const UserRecord *u, const char *user_name) {
7,367✔
2742
        assert(u);
7,367✔
2743
        assert(user_name);
7,367✔
2744

2745
        if (streq_ptr(u->user_name, user_name))
7,367✔
2746
                return true;
2747

2748
        if (streq_ptr(u->user_name_and_realm_auto, user_name))
29✔
2749
                return true;
2750

2751
        if (strv_contains(u->aliases, user_name))
23✔
2752
                return true;
2753

2754
        const char *realm = strrchr(user_name, '@');
17✔
2755
        if (realm && streq_ptr(realm+1, u->realm))
17✔
2756
                STRV_FOREACH(a, u->aliases)
18✔
2757
                        if (startswith(user_name, *a) == realm)
18✔
2758
                                return true;
2759

2760
        return false;
2761
}
2762

2763
int suitable_blob_filename(const char *name) {
429✔
2764
        /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
2765
        return filename_is_valid(name) &&
858✔
2766
               in_charset(name, URI_UNRESERVED) &&
429✔
2767
               name[0] != '.';
423✔
2768
}
2769

2770
bool userdb_match_is_set(const UserDBMatch *match) {
57,001✔
2771
        if (!match)
57,001✔
2772
                return false;
2773

2774
        return !strv_isempty(match->fuzzy_names) ||
29,177✔
2775
                !FLAGS_SET(match->disposition_mask, USER_DISPOSITION_MASK_ALL) ||
29,169✔
2776
                match->uid_min > 0 ||
28,681✔
2777
                match->uid_max < UID_INVALID-1 ||
57,850✔
2778
                !sd_id128_is_null(match->uuid);
28,673✔
2779
}
2780

2781
void userdb_match_done(UserDBMatch *match) {
27,923✔
2782
        assert(match);
27,923✔
2783
        strv_free(match->fuzzy_names);
27,923✔
2784
}
27,923✔
2785

2786
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches) {
6✔
2787
        assert(names || n_names == 0);
6✔
2788

2789
        /* Checks if any of the user record strings in the names[] array matches any of the search strings in
2790
         * the matches** strv fuzzily. */
2791

2792
        FOREACH_ARRAY(n, names, n_names) {
22✔
2793
                if (!*n)
16✔
2794
                        continue;
8✔
2795

2796
                _cleanup_free_ char *lcn = strdup(*n);
8✔
2797
                if (!lcn)
8✔
2798
                        return -ENOMEM;
2799

2800
                ascii_strlower(lcn);
8✔
2801

2802
                STRV_FOREACH(i, matches) {
16✔
2803
                        _cleanup_free_ char *lc = strdup(*i);
8✔
2804
                        if (!lc)
8✔
2805
                                return -ENOMEM;
2806

2807
                        ascii_strlower(lc);
8✔
2808

2809
                        /* First do substring check */
2810
                        if (strstr(lcn, lc))
8✔
2811
                                return true;
2812

2813
                        /* Then do some fuzzy string comparison (but only if the needle is non-trivially long) */
2814
                        if (strlen(lc) >= 5 && strlevenshtein(lcn, lc) < 3)
8✔
2815
                                return true;
2816
                }
2817
        }
2818

2819
        return false;
2820
}
2821

2822
bool user_record_match(UserRecord *u, const UserDBMatch *match) {
12,233✔
2823
        assert(u);
12,233✔
2824

2825
        if (!match)
12,233✔
2826
                return true;
2827

2828
        if (!uid_is_valid(u->uid))
5,688✔
2829
                return false;
2830

2831
        if (u->uid < match->uid_min || u->uid > match->uid_max)
5,688✔
2832
                return false;
2833

2834
        if (!BIT_SET(match->disposition_mask, user_record_disposition(u)))
5,686✔
2835
                return false;
2836

2837
        if (!sd_id128_is_null(match->uuid) && !sd_id128_equal(match->uuid, u->uuid))
11,204✔
2838
                return false;
×
2839

2840
        if (!strv_isempty(match->fuzzy_names)) {
5,602✔
2841

2842
                /* Note this array of names is sparse, i.e. various entries listed in it will be
2843
                 * NULL. Because of that we are not using a NULL terminated strv here, but a regular
2844
                 * array. */
2845
                const char* names[] = {
4✔
2846
                        u->user_name,
2✔
2847
                        user_record_user_name_and_realm(u),
2✔
2848
                        u->real_name,
2✔
2849
                        u->email_address,
2✔
2850
                        u->cifs_user_name,
2✔
2851
                };
2852

2853
                if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) &&
4✔
2854
                    !user_name_fuzzy_match((const char**) u->aliases, strv_length(u->aliases), match->fuzzy_names))
2✔
2855
                        return false;
2✔
2856
        }
2857

2858
        return true;
2859
}
2860

UNCOV
2861
int json_dispatch_dispositions_mask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
2862
        uint64_t *mask = ASSERT_PTR(userdata);
×
2863

UNCOV
2864
        if (sd_json_variant_is_null(variant)) {
×
2865
                *mask = UINT64_MAX;
×
2866
                return 0;
×
2867
        }
2868

UNCOV
2869
        if (!sd_json_variant_is_array(variant))
×
2870
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
2871

2872
        uint64_t m = 0;
UNCOV
2873
        for (size_t i = 0; i < sd_json_variant_elements(variant); i++) {
×
UNCOV
2874
                sd_json_variant *e;
×
UNCOV
2875
                const char *a;
×
2876

UNCOV
2877
                e = sd_json_variant_by_index(variant, i);
×
UNCOV
2878
                if (!sd_json_variant_is_string(e))
×
2879
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
2880

UNCOV
2881
                assert_se(a = sd_json_variant_string(e));
×
2882

UNCOV
2883
                UserDisposition d = user_disposition_from_string(a);
×
UNCOV
2884
                if (d < 0)
×
2885
                        return json_log(e, flags, d, "JSON field '%s' contains an invalid user disposition type: %s", strna(name), a);
×
2886

UNCOV
2887
                m |= INDEX_TO_MASK(uint64_t, d);
×
2888
        }
2889

UNCOV
2890
        *mask = m;
×
UNCOV
2891
        return 0;
×
2892
}
2893

2894
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
2895
        [USER_CLASSIC]   = "classic",
2896
        [USER_LUKS]      = "luks",
2897
        [USER_DIRECTORY] = "directory",
2898
        [USER_SUBVOLUME] = "subvolume",
2899
        [USER_FSCRYPT]   = "fscrypt",
2900
        [USER_CIFS]      = "cifs",
2901
};
2902

2903
DEFINE_STRING_TABLE_LOOKUP(user_storage, UserStorage);
3,228✔
2904

2905
static const char* const user_disposition_table[_USER_DISPOSITION_MAX] = {
2906
        [USER_INTRINSIC] = "intrinsic",
2907
        [USER_SYSTEM]    = "system",
2908
        [USER_DYNAMIC]   = "dynamic",
2909
        [USER_REGULAR]   = "regular",
2910
        [USER_CONTAINER] = "container",
2911
        [USER_FOREIGN]   = "foreign",
2912
        [USER_RESERVED]  = "reserved",
2913
};
2914

2915
DEFINE_STRING_TABLE_LOOKUP(user_disposition, UserDisposition);
22,368✔
2916

2917
static const char* const auto_resize_mode_table[_AUTO_RESIZE_MODE_MAX] = {
2918
        [AUTO_RESIZE_OFF]             = "off",
2919
        [AUTO_RESIZE_GROW]            = "grow",
2920
        [AUTO_RESIZE_SHRINK_AND_GROW] = "shrink-and-grow",
2921
};
2922

2923
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