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

systemd / systemd / 14554080340

19 Apr 2025 11:46AM UTC coverage: 72.101% (-0.03%) from 72.13%
14554080340

push

github

web-flow
Add two new paragraphs to coding style about header files (#37188)

296880 of 411754 relevant lines covered (72.1%)

687547.52 hits per line

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

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

3
#include <sys/mount.h>
4

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

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

34
UserRecord* user_record_new(void) {
8,278✔
35
        UserRecord *h;
8,278✔
36

37
        h = new(UserRecord, 1);
8,278✔
38
        if (!h)
8,278✔
39
                return NULL;
40

41
        *h = (UserRecord) {
8,278✔
42
                .n_ref = 1,
43
                .disposition = _USER_DISPOSITION_INVALID,
44
                .last_change_usec = UINT64_MAX,
45
                .last_password_change_usec = UINT64_MAX,
46
                .umask = MODE_INVALID,
47
                .nice_level = INT_MAX,
48
                .not_before_usec = UINT64_MAX,
49
                .not_after_usec = UINT64_MAX,
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;
8,278✔
106
}
107

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

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

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

121
        free(c->id);
×
122
}
123

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

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

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

137
        free(k->type);
×
138
        erase_and_free(k->hashed_password);
×
139
}
140

141
static UserRecord* user_record_free(UserRecord *h) {
8,263✔
142
        if (!h)
8,263✔
143
                return NULL;
144

145
        free(h->user_name);
8,263✔
146
        free(h->realm);
8,263✔
147
        free(h->user_name_and_realm_auto);
8,263✔
148
        strv_free(h->aliases);
8,263✔
149
        free(h->real_name);
8,263✔
150
        free(h->email_address);
8,263✔
151
        erase_and_free(h->password_hint);
8,263✔
152
        free(h->location);
8,263✔
153
        free(h->icon_name);
8,263✔
154

155
        free(h->blob_directory);
8,263✔
156
        hashmap_free(h->blob_manifest);
8,263✔
157

158
        free(h->shell);
8,263✔
159

160
        strv_free(h->environment);
8,263✔
161
        free(h->time_zone);
8,263✔
162
        free(h->preferred_language);
8,263✔
163
        strv_free(h->additional_languages);
8,263✔
164
        rlimit_free_all(h->rlimits);
8,263✔
165

166
        free(h->skeleton_directory);
8,263✔
167

168
        strv_free_erase(h->hashed_password);
8,263✔
169
        strv_free_erase(h->ssh_authorized_keys);
8,263✔
170
        strv_free_erase(h->password);
8,263✔
171
        strv_free_erase(h->token_pin);
8,263✔
172

173
        free(h->cifs_service);
8,263✔
174
        free(h->cifs_user_name);
8,263✔
175
        free(h->cifs_domain);
8,263✔
176
        free(h->cifs_extra_mount_options);
8,263✔
177

178
        free(h->image_path);
8,263✔
179
        free(h->image_path_auto);
8,263✔
180
        free(h->home_directory);
8,263✔
181
        free(h->home_directory_auto);
8,263✔
182

183
        free(h->fallback_shell);
8,263✔
184
        free(h->fallback_home_directory);
8,263✔
185

186
        strv_free(h->member_of);
8,263✔
187
        strv_free(h->capability_bounding_set);
8,263✔
188
        strv_free(h->capability_ambient_set);
8,263✔
189

190
        free(h->file_system_type);
8,263✔
191
        free(h->luks_cipher);
8,263✔
192
        free(h->luks_cipher_mode);
8,263✔
193
        free(h->luks_pbkdf_hash_algorithm);
8,263✔
194
        free(h->luks_pbkdf_type);
8,263✔
195
        free(h->luks_extra_mount_options);
8,263✔
196

197
        free(h->state);
8,263✔
198
        free(h->service);
8,263✔
199

200
        free(h->preferred_session_type);
8,263✔
201
        free(h->preferred_session_launcher);
8,263✔
202

203
        strv_free(h->pkcs11_token_uri);
8,263✔
204
        for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++)
8,263✔
205
                pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
×
206
        free(h->pkcs11_encrypted_key);
8,263✔
207

208
        for (size_t i = 0; i < h->n_fido2_hmac_credential; i++)
8,263✔
209
                fido2_hmac_credential_done(h->fido2_hmac_credential + i);
×
210
        for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
8,263✔
211
                fido2_hmac_salt_done(h->fido2_hmac_salt + i);
×
212

213
        strv_free(h->recovery_key_type);
8,263✔
214
        for (size_t i = 0; i < h->n_recovery_key; i++)
8,263✔
215
                recovery_key_done(h->recovery_key + i);
×
216

217
        strv_free(h->self_modifiable_fields);
8,263✔
218
        strv_free(h->self_modifiable_blobs);
8,263✔
219
        strv_free(h->self_modifiable_privileged);
8,263✔
220

221
        free(h->default_area);
8,263✔
222

223
        sd_json_variant_unref(h->json);
8,263✔
224

225
        return mfree(h);
8,263✔
226
}
227

228
DEFINE_TRIVIAL_REF_UNREF_FUNC(UserRecord, user_record, user_record_free);
13,653✔
229

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

235
        if (sd_json_variant_is_null(variant)) {
94✔
236
                *s = mfree(*s);
×
237
                return 0;
×
238
        }
239

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

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

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

254
        return 0;
255
}
256

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

261
        if (sd_json_variant_is_null(variant)) {
1,166✔
262
                *s = mfree(*s);
×
263
                return 0;
×
264
        }
265

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

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

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

278
                m = mangle_gecos(n);
×
279
                if (!m)
×
280
                        return json_log_oom(variant, flags);
×
281

282
                free_and_replace(*s, m);
×
283
        }
284

285
        return 0;
286
}
287

288
static int json_dispatch_nice(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
169✔
289
        int *nl = userdata;
169✔
290
        int64_t m;
169✔
291

292
        if (sd_json_variant_is_null(variant)) {
169✔
293
                *nl = INT_MAX;
×
294
                return 0;
×
295
        }
296

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

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

304
        *nl = m;
169✔
305
        return 0;
169✔
306
}
307

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

311
        if (sd_json_variant_is_null(variant))
×
312
                *ret = RLIM_INFINITY;
×
313
        else if (sd_json_variant_is_unsigned(variant)) {
×
314
                uint64_t w;
×
315

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

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

324
        return 0;
325
}
326

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

333
        assert_se(limits);
×
334

335
        if (sd_json_variant_is_null(variant)) {
×
336
                rlimit_free_all(limits);
×
337
                return 0;
×
338
        }
339

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

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

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

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

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

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

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

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

386
        return 0;
×
387
}
388

389
static int json_dispatch_filename_or_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
6,780✔
390
        char **s = ASSERT_PTR(userdata);
6,780✔
391
        const char *n;
6,780✔
392
        int r;
6,780✔
393

394
        if (sd_json_variant_is_null(variant)) {
6,780✔
395
                *s = mfree(*s);
×
396
                return 0;
×
397
        }
398

399
        if (!sd_json_variant_is_string(variant))
6,780✔
400
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
401

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

406
        r = free_and_strdup(s, n);
6,780✔
407
        if (r < 0)
6,780✔
408
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
409

410
        return 0;
411
}
412

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

418
        if (sd_json_variant_is_null(variant)) {
3,993✔
419
                *s = mfree(*s);
×
420
                return 0;
×
421
        }
422

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

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

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

434
        return 0;
435
}
436

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

442
        if (sd_json_variant_is_null(variant)) {
1,464✔
443
                *s = mfree(*s);
×
444
                return 0;
×
445
        }
446

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

450
        n = sd_json_variant_string(variant);
1,464✔
451
        if (empty_or_root(n) || !path_is_valid(n) || !path_is_absolute(n))
4,392✔
452
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid image path.", strna(name));
×
453

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

458
        return 0;
459
}
460

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

465
        if (sd_json_variant_is_null(variant)) {
×
466
                *m = MODE_INVALID;
×
467
                return 0;
×
468
        }
469

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

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

479
        *m = (mode_t) k;
×
480
        return 0;
×
481
}
482

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

487
        if (sd_json_variant_is_null(variant)) {
×
488
                *m = MODE_INVALID;
×
489
                return 0;
×
490
        }
491

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

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

501
        *m = (mode_t) k;
×
502
        return 0;
×
503
}
504

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

510
        if (sd_json_variant_is_null(variant)) {
×
511
                *s = mfree(*s);
×
512
                return 0;
×
513
        }
514

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

518
        n = sd_json_variant_string(variant);
×
519

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

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

527
        return 0;
528
}
529

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

537
        if (sd_json_variant_is_null(variant)) {
×
538
                *l = strv_free(*l);
×
539
                return 0;
×
540
        }
541

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

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

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

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

558
        return strv_free_and_replace(*l, n);
×
559
}
560

561
JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
6,641✔
562
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
2,457✔
563

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

567
        if (sd_json_variant_is_null(variant)) {
×
568
                *limit = UINT64_MAX;
×
569
                return 0;
×
570
        }
571

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

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

581
        *limit = k;
×
582
        return 0;
×
583
}
584

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

588
        if (sd_json_variant_is_null(variant)) {
×
589
                *weight = UINT64_MAX;
×
590
                return 0;
×
591
        }
592

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

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

603
        *weight = k;
×
604
        return 0;
×
605
}
606

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

612
        if (!sd_json_variant_is_array(variant))
4,278✔
613
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
614

615
        sd_json_variant *e;
4,278✔
616
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
12,698✔
617
                if (!sd_json_variant_is_string(e))
8,420✔
618
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
×
619

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

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

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

632
        return 0;
633
}
634

635
static int dispatch_secret(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
141✔
636

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

647
        return sd_json_dispatch(variant, secret_dispatch_table, flags, userdata);
141✔
648
}
649

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

655
        if (sd_json_variant_is_null(variant)) {
×
656
                *s = mfree(*s);
×
657
                return 0;
×
658
        }
659

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

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

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

671
        return 0;
672
}
673

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

680
        if (sd_json_variant_is_null(variant)) {
×
681
                *l = strv_free(*l);
×
682
                return 0;
×
683
        }
684

685
        if (sd_json_variant_is_string(variant)) {
×
686
                const char *n;
×
687

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

692
                z = strv_new(n);
×
693
                if (!z)
×
694
                        return log_oom();
×
695

696
        } else {
697

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

701
                JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
702
                        const char *n;
×
703

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

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

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

717
        strv_free_and_replace(*l, z);
×
718
        return 0;
×
719
}
720

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

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

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

737
        erase_and_free(k->data);
×
738
        k->data = b;
×
739
        k->size = l;
×
740

741
        return 0;
×
742
}
743

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

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

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

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

763
                if (!GREEDY_REALLOC(h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key + 1))
×
764
                        return log_oom();
×
765

766
                Pkcs11EncryptedKey *k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key;
×
767
                *k = (Pkcs11EncryptedKey) {};
×
768

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

775
                h->n_pkcs11_encrypted_key++;
×
776
        }
777

778
        return 0;
×
779
}
780

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

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

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

797
        free_and_replace(k->id, b);
×
798
        k->size = l;
×
799

800
        return 0;
×
801
}
802

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

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

811
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
812
                size_t l;
×
813
                void *b;
×
814

815
                if (!GREEDY_REALLOC(h->fido2_hmac_credential, h->n_fido2_hmac_credential + 1))
×
816
                        return log_oom();
×
817

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

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

828
        return 0;
×
829
}
830

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

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

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

847
        erase_and_free(k->salt);
×
848
        k->salt = b;
×
849
        k->salt_size = l;
×
850

851
        return 0;
×
852
}
853

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

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

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

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

876
                if (!GREEDY_REALLOC(h->fido2_hmac_salt, h->n_fido2_hmac_salt + 1))
×
877
                        return log_oom();
×
878

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

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

892
                h->n_fido2_hmac_salt++;
×
893
        }
894

895
        return 0;
×
896
}
897

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

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

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

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

916
                if (!GREEDY_REALLOC(h->recovery_key, h->n_recovery_key + 1))
×
917
                        return log_oom();
×
918

919
                RecoveryKey *k = h->recovery_key + h->n_recovery_key;
×
920
                *k = (RecoveryKey) {};
×
921

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

928
                h->n_recovery_key++;
×
929
        }
930

931
        return 0;
×
932
}
933

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

937
        assert_se(mode);
×
938

939
        if (sd_json_variant_is_null(variant)) {
×
940
                *mode = _AUTO_RESIZE_MODE_INVALID;
×
941
                return 0;
×
942
        }
943

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

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

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

956
        *mode = m;
×
957
        return 0;
×
958
}
959

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

964
        assert_se(rebalance_weight);
749✔
965

966
        if (sd_json_variant_is_null(variant)) {
749✔
967
                *rebalance_weight = REBALANCE_WEIGHT_UNSET;
×
968
                return 0;
×
969
        }
970

971
        if (sd_json_variant_is_boolean(variant)) {
749✔
972
                *rebalance_weight = sd_json_variant_boolean(variant) ? REBALANCE_WEIGHT_DEFAULT : REBALANCE_WEIGHT_OFF;
×
973
                return 0;
×
974
        }
975

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

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

989
        return 0;
990
}
991

992
static int dispatch_tmpfs_limit(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
32✔
993
        TmpfsLimit *limit = ASSERT_PTR(userdata);
32✔
994
        int r;
32✔
995

996
        if (sd_json_variant_is_null(variant)) {
32✔
997
                *limit = TMPFS_LIMIT_NULL;
×
998
                return 0;
×
999
        }
1000

1001
        r = sd_json_dispatch_uint64(name, variant, flags, &limit->limit);
32✔
1002
        if (r < 0)
32✔
1003
                return r;
1004

1005
        limit->is_set = true;
32✔
1006
        return 0;
32✔
1007
}
1008

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

1013
        if (sd_json_variant_is_null(variant)) {
×
1014
                *limit = TMPFS_LIMIT_NULL;
×
1015
                return 0;
×
1016
        }
1017

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

1022
        limit->is_set = true;
×
1023
        return 0;
×
1024
}
1025

1026
static int dispatch_privileged(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
5,867✔
1027

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

1038
        return sd_json_dispatch(variant, privileged_dispatch_table, flags, userdata);
5,867✔
1039
}
1040

1041
static int dispatch_binding(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
7,541✔
1042

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

1060
        sd_json_variant *m;
7,541✔
1061
        sd_id128_t mid;
7,541✔
1062
        int r;
7,541✔
1063

1064
        if (!variant)
7,541✔
1065
                return 0;
7,541✔
1066

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

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

1074
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
1,493✔
1075
        if (!m)
1,493✔
1076
                return 0;
1077

1078
        return sd_json_dispatch(m, binding_dispatch_table, flags, userdata);
1,493✔
1079
}
1080

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

1088
        if (!variant)
166✔
1089
                return 0;
1090

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

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

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

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

1104
                filename = strdup(key);
362✔
1105
                if (!filename)
362✔
1106
                        return json_log_oom(value, flags);
×
1107

1108
                hash = malloc(SHA256_DIGEST_SIZE);
362✔
1109
                if (!hash)
362✔
1110
                        return json_log_oom(value, flags);
×
1111

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

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

1123
        hashmap_free_and_replace(*ret, manifest);
166✔
1124
        return 0;
166✔
1125
}
1126

1127
int per_machine_id_match(sd_json_variant *ids, sd_json_dispatch_flags_t flags) {
1,750✔
1128
        sd_id128_t mid;
1,750✔
1129
        int r;
1,750✔
1130

1131
        assert(ids);
1,750✔
1132

1133
        r = sd_id128_get_machine(&mid);
1,750✔
1134
        if (r < 0)
1,750✔
1135
                return json_log(ids, flags, r, "Failed to acquire machine ID: %m");
×
1136

1137
        if (sd_json_variant_is_string(ids)) {
1,750✔
1138
                sd_id128_t k;
1,750✔
1139

1140
                r = sd_id128_from_string(sd_json_variant_string(ids), &k);
1,750✔
1141
                if (r < 0) {
1,750✔
1142
                        json_log(ids, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(ids));
×
1143
                        return 0;
1,750✔
1144
                }
1145

1146
                return sd_id128_equal(mid, k);
3,500✔
1147
        }
1148

1149
        if (sd_json_variant_is_array(ids)) {
×
1150
                sd_json_variant *e;
×
1151

1152
                JSON_VARIANT_ARRAY_FOREACH(e, ids) {
×
1153
                        sd_id128_t k;
×
1154

1155
                        if (!sd_json_variant_is_string(e)) {
×
1156
                                json_log(e, flags, 0, "Machine ID is not a string, ignoring: %m");
×
1157
                                continue;
×
1158
                        }
1159

1160
                        r = sd_id128_from_string(sd_json_variant_string(e), &k);
×
1161
                        if (r < 0) {
×
1162
                                json_log(e, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(e));
×
1163
                                continue;
×
1164
                        }
1165

1166
                        if (sd_id128_equal(mid, k))
×
1167
                                return true;
×
1168
                }
1169

1170
                return false;
×
1171
        }
1172

1173
        json_log(ids, flags, 0, "Machine ID is not a string or array of strings, ignoring: %m");
×
1174
        return false;
1175
}
1176

1177
int per_machine_hostname_match(sd_json_variant *hns, sd_json_dispatch_flags_t flags) {
×
1178
        _cleanup_free_ char *hn = NULL;
×
1179
        int r;
×
1180

1181
        assert(hns);
×
1182

1183
        r = gethostname_strict(&hn);
×
1184
        if (r == -ENXIO) {
×
1185
                json_log(hns, flags, r, "No hostname set, not matching perMachine hostname record: %m");
×
1186
                return false;
×
1187
        }
1188
        if (r < 0)
×
1189
                return json_log(hns, flags, r, "Failed to acquire hostname: %m");
×
1190

1191
        if (sd_json_variant_is_string(hns))
×
1192
                return streq(sd_json_variant_string(hns), hn);
×
1193

1194
        if (sd_json_variant_is_array(hns)) {
×
1195
                sd_json_variant *e;
×
1196

1197
                JSON_VARIANT_ARRAY_FOREACH(e, hns) {
×
1198

1199
                        if (!sd_json_variant_is_string(e)) {
×
1200
                                json_log(e, flags, 0, "Hostname is not a string, ignoring: %m");
×
1201
                                continue;
×
1202
                        }
1203

1204
                        if (streq(sd_json_variant_string(hns), hn))
×
1205
                                return true;
×
1206
                }
1207

1208
                return false;
×
1209
        }
1210

1211
        json_log(hns, flags, 0, "Hostname is not a string or array of strings, ignoring: %m");
×
1212
        return false;
1213
}
1214

1215
int per_machine_match(sd_json_variant *entry, sd_json_dispatch_flags_t flags) {
1,750✔
1216
        sd_json_variant *m;
1,750✔
1217
        int r;
1,750✔
1218

1219
        assert(sd_json_variant_is_object(entry));
1,750✔
1220

1221
        m = sd_json_variant_by_key(entry, "matchMachineId");
1,750✔
1222
        if (m) {
1,750✔
1223
                r = per_machine_id_match(m, flags);
1,668✔
1224
                if (r < 0)
1,668✔
1225
                        return r;
1226
                if (r > 0)
1,668✔
1227
                        return true;
1228
        }
1229

1230
        m = sd_json_variant_by_key(entry, "matchNotMachineId");
82✔
1231
        if (m) {
82✔
1232
                r = per_machine_id_match(m, flags);
82✔
1233
                if (r < 0)
82✔
1234
                        return r;
1235
                if (r == 0)
82✔
1236
                        return true;
1237
        }
1238

1239
        m = sd_json_variant_by_key(entry, "matchHostname");
82✔
1240
        if (m) {
82✔
1241
                r = per_machine_hostname_match(m, flags);
×
1242
                if (r < 0)
×
1243
                        return r;
1244
                if (r > 0)
×
1245
                        return true;
1246
        }
1247

1248
        m = sd_json_variant_by_key(entry, "matchNotHostname");
82✔
1249
        if (m) {
82✔
1250
                r = per_machine_hostname_match(m, flags);
×
1251
                if (r < 0)
×
1252
                        return r;
1253
                if (r == 0)
×
1254
                        return true;
×
1255
        }
1256

1257
        return false;
1258
}
1259

1260
static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
7,541✔
1261

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

1351
        sd_json_variant *e;
7,541✔
1352
        int r;
7,541✔
1353

1354
        if (!variant)
7,541✔
1355
                return 0;
1356

1357
        if (!sd_json_variant_is_array(variant))
1,640✔
1358
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
1359

1360
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
3,355✔
1361
                if (!sd_json_variant_is_object(e))
1,715✔
1362
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
×
1363

1364
                r = per_machine_match(e, flags);
1,715✔
1365
                if (r < 0)
1,715✔
1366
                        return r;
1367
                if (r == 0)
1,715✔
1368
                        continue;
75✔
1369

1370
                r = sd_json_dispatch(e, per_machine_dispatch_table, flags, userdata);
1,640✔
1371
                if (r < 0)
1,640✔
1372
                        return r;
1373
        }
1374

1375
        return 0;
1,640✔
1376
}
1377

1378
static int dispatch_status(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
7,541✔
1379

1380
        static const sd_json_dispatch_field status_dispatch_table[] = {
7,541✔
1381
                { "diskUsage",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_usage),                    0              },
1382
                { "diskFree",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_free),                     0              },
1383
                { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_size),                     0              },
1384
                { "diskCeiling",                _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_ceiling),                  0              },
1385
                { "diskFloor",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_floor),                    0              },
1386
                { "state",                      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, state),                         SD_JSON_STRICT },
1387
                { "service",                    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, service),                       SD_JSON_STRICT },
1388
                { "signedLocally",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,      offsetof(UserRecord, signed_locally),                0              },
1389
                { "goodAuthenticationCounter",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, good_authentication_counter),   0              },
1390
                { "badAuthenticationCounter",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, bad_authentication_counter),    0              },
1391
                { "lastGoodAuthenticationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_good_authentication_usec), 0              },
1392
                { "lastBadAuthenticationUSec",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_bad_authentication_usec),  0              },
1393
                { "rateLimitBeginUSec",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_begin_usec),          0              },
1394
                { "rateLimitCount",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_count),               0              },
1395
                { "removable",                  SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,      offsetof(UserRecord, removable),                     0              },
1396
                { "accessMode",                 SD_JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,      offsetof(UserRecord, access_mode),                   0              },
1397
                { "fileSystemType",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, file_system_type),              SD_JSON_STRICT },
1398
                { "fallbackShell",              SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell),                0              },
1399
                { "fallbackHomeDirectory",      SD_JSON_VARIANT_STRING,        json_dispatch_home_directory,   offsetof(UserRecord, fallback_home_directory),       0              },
1400
                { "useFallback",                SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,       offsetof(UserRecord, use_fallback),                  0              },
1401
                { "defaultArea",                SD_JSON_VARIANT_STRING,        json_dispatch_filename,         offsetof(UserRecord, default_area),                  0              },
1402
                {},
1403
        };
1404

1405
        sd_json_variant *m;
7,541✔
1406
        sd_id128_t mid;
7,541✔
1407
        int r;
7,541✔
1408

1409
        if (!variant)
7,541✔
1410
                return 0;
7,541✔
1411

1412
        if (!sd_json_variant_is_object(variant))
3,332✔
1413
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1414

1415
        r = sd_id128_get_machine(&mid);
3,332✔
1416
        if (r < 0)
3,332✔
1417
                return json_log(variant, flags, r, "Failed to determine machine ID: %m");
×
1418

1419
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
3,332✔
1420
        if (!m)
3,332✔
1421
                return 0;
1422

1423
        return sd_json_dispatch(m, status_dispatch_table, flags, userdata);
3,332✔
1424
}
1425

1426
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret) {
4,363✔
1427
        const char *suffix;
4,363✔
1428
        char *z;
4,363✔
1429

1430
        assert(storage >= 0);
4,363✔
1431
        assert(user_name_and_realm);
4,363✔
1432
        assert(ret);
4,363✔
1433

1434
        if (storage == USER_LUKS)
4,363✔
1435
                suffix = ".home";
1436
        else if (IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
4,363✔
1437
                suffix = ".homedir";
1438
        else {
1439
                *ret = NULL;
4,234✔
1440
                return 0;
4,234✔
1441
        }
1442

1443
        z = strjoin(get_home_root(), "/", user_name_and_realm, suffix);
129✔
1444
        if (!z)
129✔
1445
                return -ENOMEM;
1446

1447
        *ret = path_simplify(z);
129✔
1448
        return 1;
129✔
1449
}
1450

1451
static int user_record_augment(UserRecord *h, sd_json_dispatch_flags_t json_flags) {
7,541✔
1452
        int r;
7,541✔
1453

1454
        assert(h);
7,541✔
1455

1456
        if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
7,541✔
1457
                return 0;
1458

1459
        assert(h->user_name);
7,388✔
1460

1461
        if (!h->user_name_and_realm_auto && h->realm) {
7,388✔
1462
                h->user_name_and_realm_auto = strjoin(h->user_name, "@", h->realm);
58✔
1463
                if (!h->user_name_and_realm_auto)
58✔
1464
                        return json_log_oom(h->json, json_flags);
×
1465
        }
1466

1467
        /* Let's add in the following automatisms only for regular users, they don't make sense for any others */
1468
        if (user_record_disposition(h) != USER_REGULAR)
7,388✔
1469
                return 0;
1470

1471
        if (!h->home_directory && !h->home_directory_auto) {
5,923✔
1472
                h->home_directory_auto = path_join(get_home_root(), h->user_name);
4,549✔
1473
                if (!h->home_directory_auto)
4,549✔
1474
                        return json_log_oom(h->json, json_flags);
×
1475
        }
1476

1477
        if (!h->image_path && !h->image_path_auto) {
5,923✔
1478
                r = user_record_build_image_path(user_record_storage(h), user_record_user_name_and_realm(h), &h->image_path_auto);
4,361✔
1479
                if (r < 0)
4,361✔
1480
                        return json_log(h->json, json_flags, r, "Failed to determine default image path: %m");
×
1481
        }
1482

1483
        return 0;
1484
}
1485

1486
int user_group_record_mangle(
9,003✔
1487
                sd_json_variant *v,
1488
                UserRecordLoadFlags load_flags,
1489
                sd_json_variant **ret_variant,
1490
                UserRecordMask *ret_mask) {
1491

1492
        static const struct {
9,003✔
1493
                UserRecordMask mask;
1494
                const char *name;
1495
        } mask_field[] = {
1496
                { USER_RECORD_PRIVILEGED,  "privileged" },
1497
                { USER_RECORD_SECRET,      "secret"     },
1498
                { USER_RECORD_BINDING,     "binding"    },
1499
                { USER_RECORD_PER_MACHINE, "perMachine" },
1500
                { USER_RECORD_STATUS,      "status"     },
1501
                { USER_RECORD_SIGNATURE,   "signature"  },
1502
        };
1503

1504
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
9,003✔
1505
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
9,003✔
1506
        sd_json_variant *array[ELEMENTSOF(mask_field) * 2];
9,003✔
1507
        size_t n_retain = 0;
9,003✔
1508
        UserRecordMask m = 0;
9,003✔
1509
        int r;
9,003✔
1510

1511
        assert((load_flags & _USER_RECORD_MASK_MAX) == 0); /* detect mistakes when accidentally passing
9,003✔
1512
                                                            * UserRecordMask bit masks as UserRecordLoadFlags
1513
                                                            * value */
1514

1515
        assert(v);
9,003✔
1516
        assert(ret_variant);
9,003✔
1517

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

1521
        if (!sd_json_variant_is_object(v))
9,003✔
1522
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is not a JSON object, refusing.");
×
1523

1524
        if (USER_RECORD_ALLOW_MASK(load_flags) == 0) /* allow nothing? */
9,003✔
1525
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Nothing allowed in record, refusing.");
×
1526

1527
        if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */
9,003✔
1528
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing.");
×
1529

1530
        /* Check if we have the special sections and if they match our flags set */
1531
        FOREACH_ELEMENT(i, mask_field) {
63,021✔
1532
                sd_json_variant *e, *k;
54,018✔
1533

1534
                if (FLAGS_SET(USER_RECORD_STRIP_MASK(load_flags), i->mask)) {
54,018✔
1535
                        if (!w)
5,436✔
1536
                                w = sd_json_variant_ref(v);
4,134✔
1537

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

1542
                        continue;
5,436✔
1543
                }
1544

1545
                e = sd_json_variant_by_key_full(v, i->name, &k);
48,582✔
1546
                if (e) {
48,582✔
1547
                        if (!FLAGS_SET(USER_RECORD_ALLOW_MASK(load_flags), i->mask))
15,059✔
1548
                                return json_log(e, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", i->name);
×
1549

1550
                        if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
15,059✔
1551
                                array[n_retain++] = k;
122✔
1552
                                array[n_retain++] = e;
122✔
1553
                        }
1554

1555
                        m |= i->mask;
15,059✔
1556
                } else {
1557
                        if (FLAGS_SET(USER_RECORD_REQUIRE_MASK(load_flags), i->mask))
33,523✔
1558
                                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks '%s' field, which is required.", i->name);
×
1559
                }
1560
        }
1561

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

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

1576
                        assert_se(f = sd_json_variant_string(sd_json_variant_by_index(v, i)));
8,771✔
1577

1578
                        FOREACH_ELEMENT(j, mask_field)
61,397✔
1579
                                if (streq(f, j->name)) { /* already covered in the loop above */
52,626✔
1580
                                        special = true;
31✔
1581
                                        continue;
31✔
1582
                                }
1583

1584
                        if (!special) {
8,771✔
1585
                                if ((load_flags & (USER_RECORD_ALLOW_REGULAR|USER_RECORD_REQUIRE_REGULAR)) == 0)
8,740✔
1586
                                        return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", f);
×
1587

1588
                                m |= USER_RECORD_REGULAR;
8,740✔
1589
                                break;
8,740✔
1590
                        }
1591
                }
1592

1593
        if (FLAGS_SET(load_flags, USER_RECORD_REQUIRE_REGULAR) && !FLAGS_SET(m, USER_RECORD_REGULAR))
9,003✔
1594
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks basic identity fields, which are required.");
×
1595

1596
        if (!FLAGS_SET(load_flags, USER_RECORD_EMPTY_OK) && m == 0)
9,003✔
1597
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is empty.");
×
1598

1599
        if (w)
9,003✔
1600
                *ret_variant = TAKE_PTR(w);
4,134✔
1601
        else
1602
                *ret_variant = sd_json_variant_ref(v);
4,869✔
1603

1604
        if (ret_mask)
9,003✔
1605
                *ret_mask = m;
8,959✔
1606
        return 0;
1607
}
1608

1609
int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load_flags) {
7,541✔
1610

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

1706
                { "secret",                     SD_JSON_VARIANT_OBJECT,        dispatch_secret,                      0,                                                   0              },
1707
                { "privileged",                 SD_JSON_VARIANT_OBJECT,        dispatch_privileged,                  0,                                                   0              },
1708

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

1714
                /* Ignore 'signature', we check it with explicit accessors instead */
1715
                { "signature",                  SD_JSON_VARIANT_ARRAY,         NULL,                                 0,                                                   0              },
1716
                {},
1717
        };
1718

1719
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
7,541✔
1720
        int r;
7,541✔
1721

1722
        assert(h);
7,541✔
1723
        assert(!h->json);
7,541✔
1724

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

1727
        r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
7,541✔
1728
        if (r < 0)
7,541✔
1729
                return r;
1730

1731
        r = sd_json_dispatch(h->json, user_dispatch_table, json_flags | SD_JSON_ALLOW_EXTENSIONS, h);
7,541✔
1732
        if (r < 0)
7,541✔
1733
                return r;
1734

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

1738
        r = dispatch_per_machine("perMachine", sd_json_variant_by_key(h->json, "perMachine"), json_flags, h);
7,541✔
1739
        if (r < 0)
7,541✔
1740
                return r;
1741

1742
        r = dispatch_binding("binding", sd_json_variant_by_key(h->json, "binding"), json_flags, h);
7,541✔
1743
        if (r < 0)
7,541✔
1744
                return r;
1745

1746
        r = dispatch_status("status", sd_json_variant_by_key(h->json, "status"), json_flags, h);
7,541✔
1747
        if (r < 0)
7,541✔
1748
                return r;
1749

1750
        if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->user_name)
7,541✔
1751
                return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "User name field missing, refusing.");
×
1752

1753
        r = user_record_augment(h, json_flags);
7,541✔
1754
        if (r < 0)
7,541✔
1755
                return r;
×
1756

1757
        return 0;
1758
}
1759

1760
int user_record_build(UserRecord **ret, ...) {
53✔
1761
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
53✔
1762
        _cleanup_(user_record_unrefp) UserRecord *u = NULL;
53✔
1763
        va_list ap;
53✔
1764
        int r;
53✔
1765

1766
        assert(ret);
53✔
1767

1768
        va_start(ap, ret);
53✔
1769
        r = sd_json_buildv(&v, ap);
53✔
1770
        va_end(ap);
53✔
1771

1772
        if (r < 0)
53✔
1773
                return r;
1774

1775
        u = user_record_new();
53✔
1776
        if (!u)
53✔
1777
                return -ENOMEM;
1778

1779
        r = user_record_load(u, v, USER_RECORD_LOAD_FULL);
53✔
1780
        if (r < 0)
53✔
1781
                return r;
1782

1783
        *ret = TAKE_PTR(u);
53✔
1784
        return 0;
53✔
1785
}
1786

1787
const char* user_record_user_name_and_realm(UserRecord *h) {
4,531✔
1788
        assert(h);
4,531✔
1789

1790
        /* Return the pre-initialized joined string if it is defined */
1791
        if (h->user_name_and_realm_auto)
4,531✔
1792
                return h->user_name_and_realm_auto;
1793

1794
        /* If it's not defined then we cannot have a realm */
1795
        assert(!h->realm);
4,510✔
1796
        return h->user_name;
4,510✔
1797
}
1798

1799
UserStorage user_record_storage(UserRecord *h) {
16,232✔
1800
        assert(h);
16,232✔
1801

1802
        if (h->storage >= 0)
16,232✔
1803
                return h->storage;
2,280✔
1804

1805
        return USER_CLASSIC;
1806
}
1807

1808
const char* user_record_file_system_type(UserRecord *h) {
×
1809
        assert(h);
×
1810

1811
        return h->file_system_type ?: "btrfs";
×
1812
}
1813

1814
const char* user_record_skeleton_directory(UserRecord *h) {
140✔
1815
        assert(h);
140✔
1816

1817
        return h->skeleton_directory ?: "/etc/skel";
140✔
1818
}
1819

1820
mode_t user_record_access_mode(UserRecord *h) {
9✔
1821
        assert(h);
9✔
1822

1823
        return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
9✔
1824
}
1825

1826
static const char *user_record_home_directory_real(UserRecord *h) {
3,564✔
1827
        assert(h);
3,564✔
1828

1829
        if (h->home_directory)
3,564✔
1830
                return h->home_directory;
1831
        if (h->home_directory_auto)
1,003✔
1832
                return h->home_directory_auto;
1833

1834
        /* The root user is special, hence be special about it */
1835
        if (user_record_is_root(h))
35✔
1836
                return "/root";
×
1837

1838
        return "/";
1839
}
1840

1841
const char* user_record_home_directory(UserRecord *h) {
3,634✔
1842
        assert(h);
3,634✔
1843

1844
        if (h->use_fallback && h->fallback_home_directory)
3,634✔
1845
                return h->fallback_home_directory;
1846

1847
        return user_record_home_directory_real(h);
3,506✔
1848
}
1849

1850
const char* user_record_image_path(UserRecord *h) {
1,077✔
1851
        assert(h);
1,077✔
1852

1853
        if (h->image_path)
1,077✔
1854
                return h->image_path;
1855
        if (h->image_path_auto)
105✔
1856
                return h->image_path_auto;
1857

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

1863
const char* user_record_cifs_user_name(UserRecord *h) {
×
1864
        assert(h);
×
1865

1866
        return h->cifs_user_name ?: h->user_name;
×
1867
}
1868

1869
unsigned long user_record_mount_flags(UserRecord *h) {
39✔
1870
        assert(h);
39✔
1871

1872
        return (h->nosuid ? MS_NOSUID : 0) |
39✔
1873
                (h->noexec ? MS_NOEXEC : 0) |
39✔
1874
                (h->nodev ? MS_NODEV : 0);
39✔
1875
}
1876

1877
static const char *user_record_shell_real(UserRecord *h) {
1,220✔
1878
        assert(h);
1,220✔
1879

1880
        if (h->shell)
1,220✔
1881
                return h->shell;
1882

1883
        if (user_record_is_root(h))
378✔
1884
                return "/bin/sh";
1885

1886
        if (user_record_disposition(h) == USER_REGULAR)
373✔
1887
                return DEFAULT_USER_SHELL;
355✔
1888

1889
        return NOLOGIN;
1890
}
1891

1892
const char* user_record_shell(UserRecord *h) {
1,220✔
1893
        const char *shell;
1,220✔
1894

1895
        assert(h);
1,220✔
1896

1897
        shell = user_record_shell_real(h);
1,220✔
1898

1899
        /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
1900
         * then let's not risk anything. */
1901
        if (h->use_fallback && h->fallback_shell)
1,220✔
1902
                return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
182✔
1903

1904
        return shell;
1905
}
1906

1907
const char* user_record_real_name(UserRecord *h) {
753✔
1908
        assert(h);
753✔
1909

1910
        return h->real_name ?: h->user_name;
753✔
1911
}
1912

1913
bool user_record_luks_discard(UserRecord *h) {
×
1914
        const char *ip;
×
1915

1916
        assert(h);
×
1917

1918
        if (h->luks_discard >= 0)
×
1919
                return h->luks_discard;
×
1920

1921
        ip = user_record_image_path(h);
×
1922
        if (!ip)
×
1923
                return false;
1924

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

1931
        return path_startswith(ip, "/dev/");
×
1932
}
1933

1934
bool user_record_luks_offline_discard(UserRecord *h) {
×
1935
        const char *ip;
×
1936

1937
        assert(h);
×
1938

1939
        if (h->luks_offline_discard >= 0)
×
1940
                return h->luks_offline_discard;
×
1941

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

1945
        ip = user_record_image_path(h);
×
1946
        if (!ip)
×
1947
                return false;
1948

1949
        if (path_startswith(ip, "/dev/"))
×
1950
                return user_record_luks_discard(h);
×
1951

1952
        return true;
1953
}
1954

1955
const char* user_record_luks_cipher(UserRecord *h) {
×
1956
        assert(h);
×
1957

1958
        return h->luks_cipher ?: "aes";
×
1959
}
1960

1961
const char* user_record_luks_cipher_mode(UserRecord *h) {
×
1962
        assert(h);
×
1963

1964
        return h->luks_cipher_mode ?: "xts-plain64";
×
1965
}
1966

1967
uint64_t user_record_luks_volume_key_size(UserRecord *h) {
×
1968
        assert(h);
×
1969

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

1972
        if (h->luks_volume_key_size == UINT64_MAX)
×
1973
                return 256 / 8;
×
1974

1975
        return MIN(h->luks_volume_key_size, SIZE_MAX);
1976
}
1977

1978
const char* user_record_luks_pbkdf_type(UserRecord *h) {
×
1979
        assert(h);
×
1980

1981
        return h->luks_pbkdf_type ?: "argon2id";
×
1982
}
1983

1984
uint64_t user_record_luks_pbkdf_force_iterations(UserRecord *h) {
×
1985
        assert(h);
×
1986

1987
        /* propagate default "benchmark" mode as itself */
1988
        if (h->luks_pbkdf_force_iterations == UINT64_MAX)
×
1989
                return UINT64_MAX;
1990

1991
        /* clamp everything else to actually accepted number of iterations of libcryptsetup */
1992
        return CLAMP(h->luks_pbkdf_force_iterations, 1U, UINT32_MAX);
×
1993
}
1994

1995
uint64_t user_record_luks_pbkdf_time_cost_usec(UserRecord *h) {
×
1996
        assert(h);
×
1997

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

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

2003
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_time_cost_usec, USEC_PER_MSEC), UINT32_MAX) * USEC_PER_MSEC;
×
2004
}
2005

2006
uint64_t user_record_luks_pbkdf_memory_cost(UserRecord *h) {
×
2007
        assert(h);
×
2008

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

2014
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_memory_cost, 1024), UINT32_MAX) * 1024;
×
2015
}
2016

2017
uint64_t user_record_luks_pbkdf_parallel_threads(UserRecord *h) {
×
2018
        assert(h);
×
2019

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

2024
        return MIN(h->luks_pbkdf_parallel_threads, UINT32_MAX);
×
2025
}
2026

2027
uint64_t user_record_luks_sector_size(UserRecord *h) {
×
2028
        assert(h);
×
2029

2030
        if (h->luks_sector_size == UINT64_MAX)
×
2031
                return 512;
2032

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

2037
const char* user_record_luks_pbkdf_hash_algorithm(UserRecord *h) {
×
2038
        assert(h);
×
2039

2040
        return h->luks_pbkdf_hash_algorithm ?: "sha512";
×
2041
}
2042

2043
gid_t user_record_gid(UserRecord *h) {
3,323✔
2044
        assert(h);
3,323✔
2045

2046
        if (gid_is_valid(h->gid))
3,323✔
2047
                return h->gid;
2,104✔
2048

2049
        return (gid_t) h->uid;
1,219✔
2050
}
2051

2052
UserDisposition user_record_disposition(UserRecord *h) {
12,457✔
2053
        assert(h);
12,457✔
2054

2055
        if (h->disposition >= 0)
12,457✔
2056
                return h->disposition;
2057

2058
        /* If not declared, derive from UID */
2059

2060
        if (!uid_is_valid(h->uid))
3,299✔
2061
                return _USER_DISPOSITION_INVALID;
2062

2063
        if (user_record_is_root(h) || user_record_is_nobody(h))
3,299✔
2064
                return USER_INTRINSIC;
1,536✔
2065

2066
        if (uid_is_system(h->uid))
1,763✔
2067
                return USER_SYSTEM;
2068

2069
        if (uid_is_dynamic(h->uid))
229✔
2070
                return USER_DYNAMIC;
2071

2072
        if (uid_is_container(h->uid))
229✔
2073
                return USER_CONTAINER;
2074

2075
        if (uid_is_foreign(h->uid))
75✔
2076
                return USER_FOREIGN;
2077

2078
        if (h->uid > INT32_MAX)
75✔
2079
                return USER_RESERVED;
×
2080

2081
        return USER_REGULAR;
2082
}
2083

2084
int user_record_removable(UserRecord *h) {
9,807✔
2085
        UserStorage storage;
9,807✔
2086
        assert(h);
9,807✔
2087

2088
        if (h->removable >= 0)
9,807✔
2089
                return h->removable;
2090

2091
        /* Refuse to decide for classic records */
2092
        storage = user_record_storage(h);
9,807✔
2093
        if (h->storage < 0 || h->storage == USER_CLASSIC)
9,807✔
2094
                return -1;
2095

2096
        /* For now consider only LUKS home directories with a reference by path as removable */
2097
        return storage == USER_LUKS && path_startswith(user_record_image_path(h), "/dev/");
266✔
2098
}
2099

2100
uint64_t user_record_ratelimit_interval_usec(UserRecord *h) {
120✔
2101
        assert(h);
120✔
2102

2103
        if (h->ratelimit_interval_usec == UINT64_MAX)
120✔
2104
                return DEFAULT_RATELIMIT_INTERVAL_USEC;
36✔
2105

2106
        return h->ratelimit_interval_usec;
2107
}
2108

2109
uint64_t user_record_ratelimit_burst(UserRecord *h) {
276✔
2110
        assert(h);
276✔
2111

2112
        if (h->ratelimit_burst == UINT64_MAX)
276✔
2113
                return DEFAULT_RATELIMIT_BURST;
98✔
2114

2115
        return h->ratelimit_burst;
2116
}
2117

2118
bool user_record_can_authenticate(UserRecord *h) {
×
2119
        assert(h);
×
2120

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

2123
        if (h->n_pkcs11_encrypted_key > 0)
×
2124
                return true;
2125

2126
        if (h->n_fido2_hmac_salt > 0)
×
2127
                return true;
2128

2129
        return !strv_isempty(h->hashed_password);
×
2130
}
2131

2132
bool user_record_drop_caches(UserRecord *h) {
200✔
2133
        assert(h);
200✔
2134

2135
        if (h->drop_caches >= 0)
200✔
2136
                return h->drop_caches;
×
2137

2138
        /* By default drop caches on fscrypt, not otherwise. */
2139
        return user_record_storage(h) == USER_FSCRYPT;
200✔
2140
}
2141

2142
AutoResizeMode user_record_auto_resize_mode(UserRecord *h) {
×
2143
        assert(h);
×
2144

2145
        if (h->auto_resize_mode >= 0)
×
2146
                return h->auto_resize_mode;
2147

2148
        return user_record_storage(h) == USER_LUKS ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
×
2149
}
2150

2151
uint64_t user_record_rebalance_weight(UserRecord *h) {
229✔
2152
        assert(h);
229✔
2153

2154
        if (h->rebalance_weight == REBALANCE_WEIGHT_UNSET)
229✔
2155
                return REBALANCE_WEIGHT_DEFAULT;
77✔
2156

2157
        return h->rebalance_weight;
2158
}
2159

2160
static uint64_t parse_caps_strv(char **l) {
×
2161
        uint64_t c = 0;
×
2162
        int r;
×
2163

2164
        STRV_FOREACH(i, l) {
×
2165
                r = capability_from_name(*i);
×
2166
                if (r < 0)
×
2167
                        log_debug_errno(r, "Don't know capability '%s', ignoring: %m", *i);
×
2168
                else
2169
                        c |= UINT64_C(1) << r;
×
2170
        }
2171

2172
        return c;
×
2173
}
2174

2175
uint64_t user_record_capability_bounding_set(UserRecord *h) {
458✔
2176
        assert(h);
458✔
2177

2178
        /* Returns UINT64_MAX if no bounding set is configured (!) */
2179

2180
        if (!h->capability_bounding_set)
458✔
2181
                return UINT64_MAX;
2182

2183
        return parse_caps_strv(h->capability_bounding_set);
×
2184
}
2185

2186
uint64_t user_record_capability_ambient_set(UserRecord *h) {
458✔
2187
        assert(h);
458✔
2188

2189
        /* Returns UINT64_MAX if no ambient set is configured (!) */
2190

2191
        if (!h->capability_ambient_set)
458✔
2192
                return UINT64_MAX;
2193

2194
        return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
×
2195
}
2196

2197
int user_record_languages(UserRecord *h, char ***ret) {
458✔
2198
        _cleanup_strv_free_ char **l = NULL;
458✔
2199
        int r;
458✔
2200

2201
        assert(h);
458✔
2202
        assert(ret);
458✔
2203

2204
        if (h->preferred_language) {
458✔
2205
                l = strv_new(h->preferred_language);
×
2206
                if (!l)
×
2207
                        return -ENOMEM;
2208
        }
2209

2210
        r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true);
458✔
2211
        if (r < 0)
458✔
2212
                return r;
2213

2214
        *ret = TAKE_PTR(l);
458✔
2215
        return 0;
458✔
2216
}
2217

2218
uint32_t user_record_tmp_limit_scale(UserRecord *h) {
249✔
2219
        assert(h);
249✔
2220

2221
        if (h->tmp_limit.is_set)
249✔
2222
                return h->tmp_limit.limit_scale;
1✔
2223

2224
        /* By default grant regular users only 80% quota */
2225
        if (user_record_disposition(h) == USER_REGULAR)
248✔
2226
                return UINT32_SCALE_FROM_PERCENT(80);
197✔
2227

2228
        return UINT32_MAX;
2229
}
2230

2231
uint32_t user_record_dev_shm_limit_scale(UserRecord *h) {
249✔
2232
        assert(h);
249✔
2233

2234
        if (h->dev_shm_limit.is_set)
249✔
2235
                return h->dev_shm_limit.limit_scale;
1✔
2236

2237
        /* By default grant regular users only 80% quota */
2238
        if (user_record_disposition(h) == USER_REGULAR)
248✔
2239
                return UINT32_SCALE_FROM_PERCENT(80);
197✔
2240

2241
        return UINT32_MAX;
2242
}
2243

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

2255
                /* Basic account settings */
2256
                "shell",
2257
                "umask",
2258
                "environment",
2259
                "timeZone",
2260
                "preferredLanguage",
2261
                "additionalLanguages",
2262
                "preferredSessionLauncher",
2263
                "preferredSessionType",
2264
                "defaultArea",
2265

2266
                /* Authentication methods */
2267
                "pkcs11TokenUri",
2268
                "fido2HmacCredential",
2269
                "recoveryKeyType",
2270

2271
                "lastChangeUSec", /* Necessary to be able to change record at all */
2272
                "lastPasswordChangeUSec", /* Ditto, but for authentication methods */
2273
                NULL
2274
        };
2275

2276
        assert(h);
211✔
2277

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

2282
        /* Note that we intentionally distinguish between NULL and an empty array here */
2283
        if (h->self_modifiable_fields)
211✔
2284
                return (const char**) h->self_modifiable_fields;
2285

2286
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_fields : NULL;
209✔
2287
}
2288

2289
const char** user_record_self_modifiable_blobs(UserRecord *h) {
149✔
2290
        static const char *const default_blobs[] = {
149✔
2291
                /* For display purposes */
2292
                "avatar",
2293
                "login-background",
2294
                NULL
2295
        };
2296

2297
        assert(h);
149✔
2298

2299
        /* Note that we intentionally distinguish between NULL and an empty array here */
2300
        if (h->self_modifiable_blobs)
149✔
2301
                return (const char**) h->self_modifiable_blobs;
2302

2303
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_blobs : NULL;
149✔
2304
}
2305

2306
const char** user_record_self_modifiable_privileged(UserRecord *h) {
173✔
2307
        static const char *const default_fields[] = {
173✔
2308
                /* For display purposes */
2309
                "passwordHint",
2310

2311
                /* Authentication methods */
2312
                "hashedPassword",
2313
                "pkcs11EncryptedKey",
2314
                "fido2HmacSalt",
2315
                "recoveryKey",
2316

2317
                "sshAuthorizedKeys", /* Basically just ~/.ssh/authorized_keys */
2318
                NULL
2319
        };
2320

2321
        assert(h);
173✔
2322

2323
        /* Note that we intentionally distinguish between NULL and an empty array here */
2324
        if (h->self_modifiable_privileged)
173✔
2325
                return (const char**) h->self_modifiable_privileged;
2326

2327
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_fields : NULL;
171✔
2328
}
2329

2330
static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_json_variant **target) {
72✔
2331
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *blobs = NULL;
72✔
2332
        char **allowed;
72✔
2333
        int r;
72✔
2334

2335
        assert(current);
72✔
2336
        assert(target);
72✔
2337

2338
        if (!sd_json_variant_is_object(*target))
72✔
2339
                return -EINVAL;
2340

2341
        v = sd_json_variant_ref(*target);
72✔
2342

2343
        /* Handle basic fields */
2344
        allowed = (char**) user_record_self_modifiable_fields(current);
72✔
2345
        r = sd_json_variant_filter(&v, allowed);
72✔
2346
        if (r < 0)
72✔
2347
                return r;
2348

2349
        /* Handle blobs */
2350
        blobs = sd_json_variant_ref(sd_json_variant_by_key(v, "blobManifest"));
72✔
2351
        if (blobs) {
72✔
2352
                /* The blobManifest contains the sha256 hashes of the blobs,
2353
                 * which are enforced by the service managing the user. So, by
2354
                 * comparing the blob manifests like this, we're actually comparing
2355
                 * the contents of the blob directories & files */
2356

2357
                allowed = (char**) user_record_self_modifiable_blobs(current);
10✔
2358
                r = sd_json_variant_filter(&blobs, allowed);
10✔
2359
                if (r < 0)
10✔
2360
                        return r;
2361

2362
                if (sd_json_variant_is_blank_object(blobs))
10✔
2363
                        r = sd_json_variant_filter(&v, STRV_MAKE("blobManifest"));
×
2364
                else
2365
                        r = sd_json_variant_set_field(&v, "blobManifest", blobs);
10✔
2366
                if (r < 0)
10✔
2367
                        return r;
2368
        }
2369

2370
        JSON_VARIANT_REPLACE(*target, TAKE_PTR(v));
72✔
2371
        return 0;
72✔
2372
}
2373

2374
static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) {
44✔
2375
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *privileged = NULL;
44✔
2376
        sd_json_variant *per_machine;
44✔
2377
        char **allowed;
44✔
2378
        int r;
44✔
2379

2380
        assert(current);
44✔
2381
        assert(h);
44✔
2382
        assert(ret);
44✔
2383

2384
        r = user_group_record_mangle(h->json, USER_RECORD_EXTRACT_SIGNABLE|USER_RECORD_PERMISSIVE, &v, NULL);
44✔
2385
        if (r < 0)
44✔
2386
                return r;
2387

2388
        /* Handle the regular section */
2389
        r = remove_self_modifiable_json_fields_common(current, &v);
44✔
2390
        if (r < 0)
44✔
2391
                return r;
2392

2393
        /* Handle the perMachine section */
2394
        per_machine = sd_json_variant_by_key(v, "perMachine");
44✔
2395
        if (per_machine) {
44✔
2396
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_per_machine = NULL;
28✔
2397
                sd_json_variant *e;
28✔
2398

2399
                if (!sd_json_variant_is_array(per_machine))
28✔
2400
                        return -EINVAL;
2401

2402
                JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
63✔
2403
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *z = NULL;
35✔
2404

2405
                        if (!sd_json_variant_is_object(e))
35✔
2406
                                return -EINVAL;
2407

2408
                        r = per_machine_match(e, 0);
35✔
2409
                        if (r < 0)
35✔
2410
                                return r;
2411
                        if (r == 0) {
35✔
2412
                                /* It's only permissible to change anything inside of matching perMachine sections */
2413
                                r = sd_json_variant_append_array(&new_per_machine, e);
7✔
2414
                                if (r < 0)
7✔
2415
                                        return r;
2416
                                continue;
7✔
2417
                        }
2418

2419
                        z = sd_json_variant_ref(e);
28✔
2420

2421
                        r = remove_self_modifiable_json_fields_common(current, &z);
28✔
2422
                        if (r < 0)
28✔
2423
                                return r;
2424

2425
                        if (!sd_json_variant_is_blank_object(z)) {
28✔
2426
                                r = sd_json_variant_append_array(&new_per_machine, z);
28✔
2427
                                if (r < 0)
28✔
2428
                                        return r;
2429
                        }
2430
                }
2431

2432
                if (sd_json_variant_is_blank_array(new_per_machine))
28✔
2433
                        r = sd_json_variant_filter(&v, STRV_MAKE("perMachine"));
×
2434
                else
2435
                        r = sd_json_variant_set_field(&v, "perMachine", new_per_machine);
28✔
2436
                if (r < 0)
28✔
2437
                        return r;
2438
        }
2439

2440
        /* Handle the privileged section */
2441
        privileged = sd_json_variant_ref(sd_json_variant_by_key(v, "privileged"));
44✔
2442
        if (privileged) {
44✔
2443
                allowed = (char**) user_record_self_modifiable_privileged(current);
34✔
2444
                r = sd_json_variant_filter(&privileged, allowed);
34✔
2445
                if (r < 0)
34✔
2446
                        return r;
2447

2448
                if (sd_json_variant_is_blank_object(privileged))
34✔
2449
                        r = sd_json_variant_filter(&v, STRV_MAKE("privileged"));
32✔
2450
                else
2451
                        r = sd_json_variant_set_field(&v, "privileged", privileged);
2✔
2452
                if (r < 0)
34✔
2453
                        return r;
2454
        }
2455

2456
        JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
44✔
2457
        return 0;
44✔
2458
}
2459

2460
int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) {
22✔
2461
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *vc = NULL, *vi = NULL;
44✔
2462
        int r;
22✔
2463

2464
        assert(current);
22✔
2465
        assert(incoming);
22✔
2466

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

2472
        r = remove_self_modifiable_json_fields(current, current, &vc);
22✔
2473
        if (r < 0)
22✔
2474
                return r;
2475

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

2493
        return sd_json_variant_equal(vc, vi);
22✔
2494
}
2495

2496
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
279✔
2497
        assert(h);
279✔
2498

2499
        /* Calculates when the it's possible to login next. Returns:
2500
         *
2501
         * UINT64_MAX → Nothing known
2502
         * 0          → Right away
2503
         * Any other  → Next time in CLOCK_REALTIME in usec (which could be in the past)
2504
         */
2505

2506
        if (h->ratelimit_begin_usec == UINT64_MAX ||
279✔
2507
            h->ratelimit_count == UINT64_MAX)
163✔
2508
                return UINT64_MAX;
2509

2510
        if (h->ratelimit_begin_usec > now(CLOCK_REALTIME)) /* If the ratelimit time is in the future, then
163✔
2511
                                                            * the local clock is probably incorrect. Let's
2512
                                                            * not refuse login then. */
2513
                return UINT64_MAX;
2514

2515
        if (h->ratelimit_count < user_record_ratelimit_burst(h))
163✔
2516
                return 0;
2517

2518
        return usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h));
×
2519
}
2520

2521
bool user_record_equal(UserRecord *a, UserRecord *b) {
137✔
2522
        assert(a);
137✔
2523
        assert(b);
137✔
2524

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

2528
        return sd_json_variant_equal(a->json, b->json);
137✔
2529
}
2530

2531
bool user_record_compatible(UserRecord *a, UserRecord *b) {
126✔
2532
        assert(a);
126✔
2533
        assert(b);
126✔
2534

2535
        /* If either lacks the regular section, we can't really decide, let's hence say they are
2536
         * incompatible. */
2537
        if (!(a->mask & b->mask & USER_RECORD_REGULAR))
126✔
2538
                return false;
2539

2540
        return streq_ptr(a->user_name, b->user_name) &&
126✔
2541
                streq_ptr(a->realm, b->realm);
126✔
2542
}
2543

2544
int user_record_compare_last_change(UserRecord *a, UserRecord *b) {
16✔
2545
        assert(a);
16✔
2546
        assert(b);
16✔
2547

2548
        if (a->last_change_usec == b->last_change_usec)
16✔
2549
                return 0;
2550

2551
        /* Always consider a record with a timestamp newer than one without */
2552
        if (a->last_change_usec == UINT64_MAX)
16✔
2553
                return -1;
2554
        if (b->last_change_usec == UINT64_MAX)
16✔
2555
                return 1;
2556

2557
        return CMP(a->last_change_usec, b->last_change_usec);
16✔
2558
}
2559

2560
int user_record_clone(UserRecord *h, UserRecordLoadFlags flags, UserRecord **ret) {
2,494✔
2561
        _cleanup_(user_record_unrefp) UserRecord *c = NULL;
2,494✔
2562
        int r;
2,494✔
2563

2564
        assert(h);
2,494✔
2565
        assert(ret);
2,494✔
2566

2567
        c = user_record_new();
2,494✔
2568
        if (!c)
2,494✔
2569
                return -ENOMEM;
2570

2571
        r = user_record_load(c, h->json, flags);
2,494✔
2572
        if (r < 0)
2,494✔
2573
                return r;
2574

2575
        *ret = TAKE_PTR(c);
2,494✔
2576
        return 0;
2,494✔
2577
}
2578

2579
int user_record_masked_equal(UserRecord *a, UserRecord *b, UserRecordMask mask) {
21✔
2580
        _cleanup_(user_record_unrefp) UserRecord *x = NULL, *y = NULL;
21✔
2581
        int r;
21✔
2582

2583
        assert(a);
21✔
2584
        assert(b);
21✔
2585

2586
        /* Compares the two records, but ignores anything not listed in the specified mask */
2587

2588
        if ((a->mask & ~mask) != 0) {
21✔
2589
                r = user_record_clone(a, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &x);
21✔
2590
                if (r < 0)
21✔
2591
                        return r;
2592

2593
                a = x;
21✔
2594
        }
2595

2596
        if ((b->mask & ~mask) != 0) {
21✔
2597
                r = user_record_clone(b, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &y);
21✔
2598
                if (r < 0)
21✔
2599
                        return r;
2600

2601
                b = y;
21✔
2602
        }
2603

2604
        return user_record_equal(a, b);
21✔
2605
}
2606

2607
int user_record_test_blocked(UserRecord *h) {
179✔
2608
        usec_t n;
179✔
2609

2610
        /* Checks whether access to the specified user shall be allowed at the moment. Returns:
2611
         *
2612
         *          -ESTALE: Record is from the future
2613
         *          -ENOLCK: Record is blocked
2614
         *          -EL2HLT: Record is not valid yet
2615
         *          -EL3HLT: Record is not valid anymore
2616
         *
2617
         */
2618

2619
        assert(h);
179✔
2620

2621
        if (h->locked > 0)
179✔
2622
                return -ENOLCK;
2623

2624
        n = now(CLOCK_REALTIME);
156✔
2625

2626
        if (h->not_before_usec != UINT64_MAX && n < h->not_before_usec)
156✔
2627
                return -EL2HLT;
2628
        if (h->not_after_usec != UINT64_MAX && n > h->not_after_usec)
156✔
2629
                return -EL3HLT;
2630

2631
        if (h->last_change_usec != UINT64_MAX &&
156✔
2632
            h->last_change_usec > n) /* Complain during log-ins when the record is from the future */
2633
                return -ESTALE;
×
2634

2635
        return 0;
2636
}
2637

2638
int user_record_test_password_change_required(UserRecord *h) {
181✔
2639
        bool change_permitted;
181✔
2640
        usec_t n;
181✔
2641

2642
        assert(h);
181✔
2643

2644
        /* Checks whether the user must change the password when logging in
2645

2646
            -EKEYREVOKED: Change password now because admin said so
2647
             -EOWNERDEAD: Change password now because it expired
2648
           -EKEYREJECTED: Password is expired, no changing is allowed
2649
            -EKEYEXPIRED: Password is about to expire, warn user
2650
               -ENETDOWN: Record has expiration info but no password change timestamp
2651
                  -EROFS: No password change required nor permitted
2652
                 -ESTALE: RTC likely incorrect, last password change is in the future
2653
                       0: No password change required, but permitted
2654
         */
2655

2656
        /* If a password change request has been set explicitly, it overrides everything */
2657
        if (h->password_change_now > 0)
181✔
2658
                return -EKEYREVOKED;
2659

2660
        n = now(CLOCK_REALTIME);
181✔
2661

2662
        /* Password change in the future? Then our RTC is likely incorrect */
2663
        if (h->last_password_change_usec != UINT64_MAX &&
181✔
2664
            h->last_password_change_usec > n &&
×
2665
            (h->password_change_min_usec != UINT64_MAX ||
×
2666
             h->password_change_max_usec != UINT64_MAX ||
×
2667
             h->password_change_inactive_usec != UINT64_MAX))
×
2668
            return -ESTALE;
2669

2670
        /* Then, let's check if password changing is currently allowed at all */
2671
        if (h->password_change_min_usec != UINT64_MAX) {
181✔
2672

2673
                /* Expiry configured but no password change timestamp known? */
2674
                if (h->last_password_change_usec == UINT64_MAX)
×
2675
                        return -ENETDOWN;
2676

2677
                if (h->password_change_min_usec >= UINT64_MAX - h->last_password_change_usec)
×
2678
                        change_permitted = false;
2679
                else
2680
                        change_permitted = n >= h->last_password_change_usec + h->password_change_min_usec;
×
2681

2682
        } else
2683
                change_permitted = true;
2684

2685
        /* Let's check whether the password has expired.  */
2686
        if (!(h->password_change_max_usec == UINT64_MAX ||
181✔
2687
              h->password_change_max_usec >= UINT64_MAX - h->last_password_change_usec)) {
×
2688

2689
                uint64_t change_before;
×
2690

2691
                /* Expiry configured but no password change timestamp known? */
2692
                if (h->last_password_change_usec == UINT64_MAX)
×
2693
                        return -ENETDOWN;
2694

2695
                /* Password is in inactive phase? */
2696
                if (h->password_change_inactive_usec != UINT64_MAX &&
×
2697
                    h->password_change_inactive_usec < UINT64_MAX - h->password_change_max_usec) {
×
2698
                        usec_t added;
×
2699

2700
                        added = h->password_change_inactive_usec + h->password_change_max_usec;
×
2701
                        if (added < UINT64_MAX - h->last_password_change_usec &&
×
2702
                            n >= h->last_password_change_usec + added)
×
2703
                                return -EKEYREJECTED;
2704
                }
2705

2706
                /* Password needs to be changed now? */
2707
                change_before = h->last_password_change_usec + h->password_change_max_usec;
×
2708
                if (n >= change_before)
×
2709
                        return change_permitted ? -EOWNERDEAD : -EKEYREJECTED;
×
2710

2711
                /* Warn user? */
2712
                if (h->password_change_warn_usec != UINT64_MAX &&
×
2713
                    (change_before < h->password_change_warn_usec ||
×
2714
                     n >= change_before - h->password_change_warn_usec))
×
2715
                        return change_permitted ? -EKEYEXPIRED : -EROFS;
×
2716
        }
2717

2718
        /* No password changing necessary */
2719
        return change_permitted ? 0 : -EROFS;
181✔
2720
}
2721

2722
bool user_record_is_root(const UserRecord *u) {
4,128✔
2723
        assert(u);
4,128✔
2724

2725
        return u->uid == 0 || streq_ptr(u->user_name, "root");
4,128✔
2726
}
2727

2728
bool user_record_is_nobody(const UserRecord *u) {
1,990✔
2729
        assert(u);
1,990✔
2730

2731
        return u->uid == UID_NOBODY || STRPTR_IN_SET(u->user_name, NOBODY_USER_NAME, "nobody");
1,990✔
2732
}
2733

2734
bool user_record_matches_user_name(const UserRecord *u, const char *user_name) {
2,882✔
2735
        assert(u);
2,882✔
2736
        assert(user_name);
2,882✔
2737

2738
        if (streq_ptr(u->user_name, user_name))
2,882✔
2739
                return true;
2740

2741
        if (streq_ptr(u->user_name_and_realm_auto, user_name))
24✔
2742
                return true;
2743

2744
        if (strv_contains(u->aliases, user_name))
18✔
2745
                return true;
2746

2747
        const char *realm = strrchr(user_name, '@');
12✔
2748
        if (realm && streq_ptr(realm+1, u->realm))
12✔
2749
                STRV_FOREACH(a, u->aliases)
18✔
2750
                        if (startswith(user_name, *a) == realm)
18✔
2751
                                return true;
2752

2753
        return false;
2754
}
2755

2756
int suitable_blob_filename(const char *name) {
429✔
2757
        /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
2758
        return filename_is_valid(name) &&
858✔
2759
               in_charset(name, URI_UNRESERVED) &&
429✔
2760
               name[0] != '.';
423✔
2761
}
2762

2763
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches) {
116✔
2764
        assert(names || n_names == 0);
116✔
2765

2766
        /* Checks if any of the user record strings in the names[] array matches any of the search strings in
2767
         * the matches** strv fuzzily. */
2768

2769
        FOREACH_ARRAY(n, names, n_names) {
436✔
2770
                if (!*n)
320✔
2771
                        continue;
125✔
2772

2773
                _cleanup_free_ char *lcn = strdup(*n);
195✔
2774
                if (!lcn)
195✔
2775
                        return -ENOMEM;
2776

2777
                ascii_strlower(lcn);
195✔
2778

2779
                STRV_FOREACH(i, matches) {
390✔
2780
                        _cleanup_free_ char *lc = strdup(*i);
195✔
2781
                        if (!lc)
195✔
2782
                                return -ENOMEM;
2783

2784
                        ascii_strlower(lc);
195✔
2785

2786
                        /* First do substring check */
2787
                        if (strstr(lcn, lc))
195✔
2788
                                return true;
2789

2790
                        /* Then do some fuzzy string comparison (but only if the needle is non-trivially long) */
2791
                        if (strlen(lc) >= 5 && strlevenshtein(lcn, lc) < 3)
195✔
2792
                                return true;
2793
                }
2794
        }
2795

2796
        return false;
2797
}
2798

2799
bool user_record_match(UserRecord *u, const UserDBMatch *match) {
4,361✔
2800
        assert(u);
4,361✔
2801

2802
        if (!match)
4,361✔
2803
                return true;
2804

2805
        if (!uid_is_valid(u->uid))
2,577✔
2806
                return false;
2807

2808
        if (u->uid < match->uid_min || u->uid > match->uid_max)
2,577✔
2809
                return false;
2810

2811
        if (!BIT_SET(match->disposition_mask, user_record_disposition(u)))
2,549✔
2812
                return false;
2813

2814
        if (!strv_isempty(match->fuzzy_names)) {
2,491✔
2815

2816
                /* Note this array of names is sparse, i.e. various entries listed in it will be
2817
                 * NULL. Because of that we are not using a NULL terminated strv here, but a regular
2818
                 * array. */
2819
                const char* names[] = {
56✔
2820
                        u->user_name,
28✔
2821
                        user_record_user_name_and_realm(u),
28✔
2822
                        u->real_name,
28✔
2823
                        u->email_address,
28✔
2824
                        u->cifs_user_name,
28✔
2825
                };
2826

2827
                if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) &&
56✔
2828
                    !user_name_fuzzy_match((const char**) u->aliases, strv_length(u->aliases), match->fuzzy_names))
28✔
2829
                        return false;
28✔
2830
        }
2831

2832
        return true;
2833
}
2834

2835
int json_dispatch_dispositions_mask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
8✔
2836
        uint64_t *mask = ASSERT_PTR(userdata);
8✔
2837

2838
        if (sd_json_variant_is_null(variant)) {
8✔
2839
                *mask = UINT64_MAX;
×
2840
                return 0;
×
2841
        }
2842

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

2846
        uint64_t m = 0;
2847
        for (size_t i = 0; i < sd_json_variant_elements(variant); i++) {
20✔
2848
                sd_json_variant *e;
12✔
2849
                const char *a;
12✔
2850

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

2855
                assert_se(a = sd_json_variant_string(e));
12✔
2856

2857
                UserDisposition d = user_disposition_from_string(a);
12✔
2858
                if (d < 0)
12✔
2859
                        return json_log(e, flags, d, "JSON field '%s' contains an invalid user disposition type: %s", strna(name), a);
×
2860

2861
                m |= INDEX_TO_MASK(uint64_t, d);
12✔
2862
        }
2863

2864
        *mask = m;
8✔
2865
        return 0;
8✔
2866
}
2867

2868
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
2869
        [USER_CLASSIC]   = "classic",
2870
        [USER_LUKS]      = "luks",
2871
        [USER_DIRECTORY] = "directory",
2872
        [USER_SUBVOLUME] = "subvolume",
2873
        [USER_FSCRYPT]   = "fscrypt",
2874
        [USER_CIFS]      = "cifs",
2875
};
2876

2877
DEFINE_STRING_TABLE_LOOKUP(user_storage, UserStorage);
2,666✔
2878

2879
static const char* const user_disposition_table[_USER_DISPOSITION_MAX] = {
2880
        [USER_INTRINSIC] = "intrinsic",
2881
        [USER_SYSTEM]    = "system",
2882
        [USER_DYNAMIC]   = "dynamic",
2883
        [USER_REGULAR]   = "regular",
2884
        [USER_CONTAINER] = "container",
2885
        [USER_FOREIGN]   = "foreign",
2886
        [USER_RESERVED]  = "reserved",
2887
};
2888

2889
DEFINE_STRING_TABLE_LOOKUP(user_disposition, UserDisposition);
7,548✔
2890

2891
static const char* const auto_resize_mode_table[_AUTO_RESIZE_MODE_MAX] = {
2892
        [AUTO_RESIZE_OFF]             = "off",
2893
        [AUTO_RESIZE_GROW]            = "grow",
2894
        [AUTO_RESIZE_SHRINK_AND_GROW] = "shrink-and-grow",
2895
};
2896

2897
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