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

systemd / systemd / 21192089104

20 Jan 2026 11:35PM UTC coverage: 72.524% (-0.3%) from 72.818%
21192089104

push

github

yuwata
mkdir: reset mtime *after* fchown()

Follow-up for 34c3d5747

Also, drop pointless shortcut.

1 of 2 new or added lines in 1 file covered. (50.0%)

2960 existing lines in 48 files now uncovered.

309808 of 427181 relevant lines covered (72.52%)

1236537.64 hits per line

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

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

3
#include <sys/mount.h>
4

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

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

33
UserRecord* user_record_new(void) {
15,395✔
34
        UserRecord *h;
15,395✔
35

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

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

104
        return h;
15,395✔
105
}
106

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

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

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

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

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

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

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

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

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

145
static UserRecord* user_record_free(UserRecord *h) {
15,360✔
146
        if (!h)
15,360✔
147
                return NULL;
148

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

159
        free(h->blob_directory);
15,360✔
160
        hashmap_free(h->blob_manifest);
15,360✔
161

162
        free(h->shell);
15,360✔
163

164
        strv_free(h->environment);
15,360✔
165
        free(h->time_zone);
15,360✔
166
        free(h->preferred_language);
15,360✔
167
        strv_free(h->additional_languages);
15,360✔
168
        rlimit_free_all(h->rlimits);
15,360✔
169

170
        free(h->skeleton_directory);
15,360✔
171

172
        strv_free_erase(h->hashed_password);
15,360✔
173
        strv_free_erase(h->ssh_authorized_keys);
15,360✔
174
        strv_free_erase(h->password);
15,360✔
175
        strv_free_erase(h->token_pin);
15,360✔
176

177
        free(h->cifs_service);
15,360✔
178
        free(h->cifs_user_name);
15,360✔
179
        free(h->cifs_domain);
15,360✔
180
        free(h->cifs_extra_mount_options);
15,360✔
181

182
        free(h->image_path);
15,360✔
183
        free(h->image_path_auto);
15,360✔
184
        free(h->home_directory);
15,360✔
185
        free(h->home_directory_auto);
15,360✔
186

187
        free(h->fallback_shell);
15,360✔
188
        free(h->fallback_home_directory);
15,360✔
189

190
        strv_free(h->member_of);
15,360✔
191
        strv_free(h->capability_bounding_set);
15,360✔
192
        strv_free(h->capability_ambient_set);
15,360✔
193

194
        free(h->file_system_type);
15,360✔
195
        free(h->luks_cipher);
15,360✔
196
        free(h->luks_cipher_mode);
15,360✔
197
        free(h->luks_pbkdf_hash_algorithm);
15,360✔
198
        free(h->luks_pbkdf_type);
15,360✔
199
        free(h->luks_extra_mount_options);
15,360✔
200

201
        free(h->state);
15,360✔
202
        free(h->service);
15,360✔
203

204
        free(h->preferred_session_type);
15,360✔
205
        free(h->preferred_session_launcher);
15,360✔
206

207
        strv_free(h->pkcs11_token_uri);
15,360✔
208
        for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++)
15,360✔
209
                pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
×
210
        free(h->pkcs11_encrypted_key);
15,360✔
211

212
        for (size_t i = 0; i < h->n_fido2_hmac_credential; i++)
15,360✔
213
                fido2_hmac_credential_done(h->fido2_hmac_credential + i);
×
214
        for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
15,360✔
215
                fido2_hmac_salt_done(h->fido2_hmac_salt + i);
×
216

217
        strv_free(h->recovery_key_type);
15,360✔
218
        for (size_t i = 0; i < h->n_recovery_key; i++)
15,360✔
219
                recovery_key_done(h->recovery_key + i);
×
220

221
        strv_free(h->self_modifiable_fields);
15,360✔
222
        strv_free(h->self_modifiable_blobs);
15,360✔
223
        strv_free(h->self_modifiable_privileged);
15,360✔
224

225
        free(h->default_area);
15,360✔
226

227
        sd_json_variant_unref(h->json);
15,360✔
228

229
        return mfree(h);
15,360✔
230
}
231

232
DEFINE_TRIVIAL_REF_UNREF_FUNC(UserRecord, user_record, user_record_free);
29,781✔
233

234
int json_dispatch_realm(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
94✔
235
        char **s = userdata;
94✔
236
        const char *n;
94✔
237
        int r;
94✔
238

239
        if (sd_json_variant_is_null(variant)) {
94✔
240
                *s = mfree(*s);
×
241
                return 0;
×
242
        }
243

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

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

254
        r = free_and_strdup(s, n);
94✔
255
        if (r < 0)
94✔
256
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
257

258
        return 0;
259
}
260

261
int json_dispatch_gecos(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
7,334✔
262
        char **s = userdata;
7,334✔
263
        const char *n;
7,334✔
264

265
        if (sd_json_variant_is_null(variant)) {
7,334✔
266
                *s = mfree(*s);
×
267
                return 0;
×
268
        }
269

270
        if (!sd_json_variant_is_string(variant))
7,334✔
271
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
272

273
        n = sd_json_variant_string(variant);
7,334✔
274
        if (valid_gecos(n)) {
7,334✔
275
                if (free_and_strdup(s, n) < 0)
7,334✔
276
                        return json_log_oom(variant, flags);
×
277
        } else {
278
                _cleanup_free_ char *m = NULL;
×
279

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

282
                m = mangle_gecos(n);
×
283
                if (!m)
×
284
                        return json_log_oom(variant, flags);
×
285

286
                free_and_replace(*s, m);
×
287
        }
288

289
        return 0;
290
}
291

292
static int json_dispatch_nice(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
169✔
293
        int *nl = userdata;
169✔
294
        int64_t m;
169✔
295

296
        if (sd_json_variant_is_null(variant)) {
169✔
297
                *nl = INT_MAX;
×
298
                return 0;
×
299
        }
300

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

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

308
        *nl = m;
169✔
309
        return 0;
169✔
310
}
311

312
static int json_dispatch_rlimit_value(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
313
        rlim_t *ret = userdata;
×
314

315
        if (sd_json_variant_is_null(variant))
×
316
                *ret = RLIM_INFINITY;
×
317
        else if (sd_json_variant_is_unsigned(variant)) {
×
318
                uint64_t w;
×
319

320
                w = sd_json_variant_unsigned(variant);
×
321
                if (w == RLIM_INFINITY || w != sd_json_variant_unsigned(variant))
×
322
                        return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "Resource limit value '%s' is out of range.", name);
×
323

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

328
        return 0;
329
}
330

331
static int json_dispatch_rlimits(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
332
        struct rlimit** limits = userdata;
×
333
        sd_json_variant *value;
×
334
        const char *key;
×
335
        int r;
×
336

337
        assert_se(limits);
×
338

339
        if (sd_json_variant_is_null(variant)) {
×
340
                rlimit_free_all(limits);
×
341
                return 0;
×
342
        }
343

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

347
        JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
×
348
                sd_json_variant *jcur, *jmax;
×
349
                struct rlimit rl;
×
350
                const char *p;
×
351
                int l;
×
352

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

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

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

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

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

381
                if (limits[l])
×
382
                        *(limits[l]) = rl;
×
383
                else {
384
                        limits[l] = newdup(struct rlimit, &rl, 1);
×
385
                        if (!limits[l])
×
386
                                return log_oom();
×
387
                }
388
        }
389

390
        return 0;
×
391
}
392

393
static int json_dispatch_filename_or_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
11,072✔
394
        char **s = ASSERT_PTR(userdata);
11,072✔
395
        const char *n;
11,072✔
396
        int r;
11,072✔
397

398
        if (sd_json_variant_is_null(variant)) {
11,072✔
399
                *s = mfree(*s);
×
400
                return 0;
×
401
        }
402

403
        if (!sd_json_variant_is_string(variant))
11,072✔
404
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
405

406
        n = sd_json_variant_string(variant);
11,072✔
407
        if (!filename_is_valid(n) && !path_is_normalized(n))
11,072✔
408
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid file name or normalized path.", strna(name));
×
409

410
        r = free_and_strdup(s, n);
11,072✔
411
        if (r < 0)
11,072✔
412
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
413

414
        return 0;
415
}
416

417
static int json_dispatch_home_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
4,823✔
418
        char **s = userdata;
4,823✔
419
        const char *n;
4,823✔
420
        int r;
4,823✔
421

422
        if (sd_json_variant_is_null(variant)) {
4,823✔
423
                *s = mfree(*s);
×
424
                return 0;
×
425
        }
426

427
        if (!sd_json_variant_is_string(variant))
4,823✔
428
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
429

430
        n = sd_json_variant_string(variant);
4,823✔
431
        if (!valid_home(n))
4,823✔
432
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid home directory path.", strna(name));
×
433

434
        r = free_and_strdup(s, n);
4,823✔
435
        if (r < 0)
4,823✔
436
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
437

438
        return 0;
439
}
440

441
static int json_dispatch_image_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
1,698✔
442
        char **s = userdata;
1,698✔
443
        const char *n;
1,698✔
444
        int r;
1,698✔
445

446
        if (sd_json_variant_is_null(variant)) {
1,698✔
447
                *s = mfree(*s);
×
448
                return 0;
×
449
        }
450

451
        if (!sd_json_variant_is_string(variant))
1,698✔
452
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
×
453

454
        n = sd_json_variant_string(variant);
1,698✔
455
        if (empty_or_root(n) || !path_is_valid(n) || !path_is_absolute(n))
5,094✔
456
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid image path.", strna(name));
×
457

458
        r = free_and_strdup(s, n);
1,698✔
459
        if (r < 0)
1,698✔
460
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
461

462
        return 0;
463
}
464

465
static int json_dispatch_locale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
466
        char **s = userdata;
×
467
        const char *n;
×
468
        int r;
×
469

470
        if (sd_json_variant_is_null(variant)) {
×
471
                *s = mfree(*s);
×
472
                return 0;
×
473
        }
474

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

478
        n = sd_json_variant_string(variant);
×
479

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

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

487
        return 0;
488
}
489

490
static int json_dispatch_locales(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
491
        _cleanup_strv_free_ char **n = NULL;
×
492
        char ***l = userdata;
×
493
        const char *locale;
×
494
        sd_json_variant *e;
×
495
        int r;
×
496

497
        if (sd_json_variant_is_null(variant)) {
×
498
                *l = strv_free(*l);
×
499
                return 0;
×
500
        }
501

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

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

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

513
                r = strv_extend(&n, locale);
×
514
                if (r < 0)
×
515
                        return json_log_oom(variant, flags);
×
516
        }
517

518
        return strv_free_and_replace(*l, n);
×
519
}
520

521
JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
16,840✔
522
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
2,910✔
523

524
static int json_dispatch_tasks_or_memory_max(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
525
        uint64_t *limit = userdata, k;
×
526

527
        if (sd_json_variant_is_null(variant)) {
×
528
                *limit = UINT64_MAX;
×
529
                return 0;
×
530
        }
531

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

535
        k = sd_json_variant_unsigned(variant);
×
536
        if (k <= 0 || k >= UINT64_MAX)
×
537
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
538
                                "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
539
                                strna(name), (uint64_t) 1, glyph(GLYPH_ELLIPSIS), UINT64_MAX-1);
540

541
        *limit = k;
×
542
        return 0;
×
543
}
544

545
static int json_dispatch_weight(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
546
        uint64_t *weight = userdata, k;
×
547

548
        if (sd_json_variant_is_null(variant)) {
×
549
                *weight = UINT64_MAX;
×
550
                return 0;
×
551
        }
552

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

556
        k = sd_json_variant_unsigned(variant);
×
557
        if (k <= CGROUP_WEIGHT_MIN || k >= CGROUP_WEIGHT_MAX)
×
558
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
559
                                "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".",
560
                                strna(name), (uint64_t) CGROUP_WEIGHT_MIN,
561
                                glyph(GLYPH_ELLIPSIS), (uint64_t) CGROUP_WEIGHT_MAX);
562

563
        *weight = k;
×
564
        return 0;
×
565
}
566

567
int json_dispatch_user_group_list(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
234✔
568
        char ***list = ASSERT_PTR(userdata);
234✔
569
        _cleanup_strv_free_ char **l = NULL;
234✔
570
        int r;
234✔
571

572
        if (!sd_json_variant_is_array(variant))
234✔
573
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
×
574

575
        sd_json_variant *e;
234✔
576
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
464✔
577
                if (!sd_json_variant_is_string(e))
230✔
578
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
×
579

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

583
                r = strv_extend(&l, sd_json_variant_string(e));
230✔
584
                if (r < 0)
230✔
585
                        return json_log(e, flags, r, "Failed to append array element: %m");
×
586
        }
587

588
        r = strv_extend_strv_consume(list, TAKE_PTR(l), /* filter_duplicates= */ true);
234✔
589
        if (r < 0)
234✔
590
                return json_log(variant, flags, r, "Failed to merge user/group arrays: %m");
×
591

592
        return 0;
593
}
594

595
static int dispatch_secret(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
149✔
596

597
        static const sd_json_dispatch_field secret_dispatch_table[] = {
149✔
598
                { "password",                                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, password),                                       0 },
599
                { "tokenPin",                                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, token_pin),                                      0 },
600
                { "pkcs11Pin",   /* legacy alias */             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,     offsetof(UserRecord, token_pin),                                      0 },
601
                { "pkcs11ProtectedAuthenticationPathPermitted", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
602
                { "fido2UserPresencePermitted",                 SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, fido2_user_presence_permitted),                  0 },
603
                { "fido2UserVerificationPermitted",             SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate, offsetof(UserRecord, fido2_user_verification_permitted),              0 },
604
                {},
605
        };
606

607
        return sd_json_dispatch(variant, secret_dispatch_table, flags, userdata);
149✔
608
}
609

610
static int dispatch_pkcs11_uri(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
611
        char **s = userdata;
×
612
        const char *n;
×
613
        int r;
×
614

615
        if (sd_json_variant_is_null(variant)) {
×
616
                *s = mfree(*s);
×
617
                return 0;
×
618
        }
619

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

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

627
        r = free_and_strdup(s, n);
×
628
        if (r < 0)
×
629
                return json_log(variant, flags, r, "Failed to allocate string: %m");
×
630

631
        return 0;
632
}
633

634
static int dispatch_pkcs11_uri_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
635
        _cleanup_strv_free_ char **z = NULL;
×
636
        char ***l = userdata;
×
637
        sd_json_variant *e;
×
638
        int r;
×
639

640
        if (sd_json_variant_is_null(variant)) {
×
641
                *l = strv_free(*l);
×
642
                return 0;
×
643
        }
644

645
        if (sd_json_variant_is_string(variant)) {
×
646
                const char *n;
×
647

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

652
                z = strv_new(n);
×
653
                if (!z)
×
654
                        return log_oom();
×
655

656
        } else {
657

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

661
                JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
662
                        const char *n;
×
663

664
                        if (!sd_json_variant_is_string(e))
×
665
                                return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
×
666

667
                        n = sd_json_variant_string(e);
×
668
                        if (!pkcs11_uri_valid(n))
×
669
                                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);
×
670

671
                        r = strv_extend(&z, n);
×
672
                        if (r < 0)
×
673
                                return log_oom();
×
674
                }
675
        }
676

677
        strv_free_and_replace(*l, z);
×
678
        return 0;
×
679
}
680

681
static int dispatch_pkcs11_key_data(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
682
        Pkcs11EncryptedKey *k = userdata;
×
683
        size_t l;
×
684
        void *b;
×
685
        int r;
×
686

687
        if (sd_json_variant_is_null(variant)) {
×
688
                k->data = erase_and_free(k->data);
×
689
                k->size = 0;
×
690
                return 0;
×
691
        }
692

693
        r = sd_json_variant_unbase64(variant, &b, &l);
×
694
        if (r < 0)
×
695
                return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
×
696

697
        erase_and_free(k->data);
×
698
        k->data = b;
×
699
        k->size = l;
×
700

701
        return 0;
×
702
}
703

704
static int dispatch_pkcs11_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
705
        UserRecord *h = userdata;
×
706
        sd_json_variant *e;
×
707
        int r;
×
708

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

712
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
713
                static const sd_json_dispatch_field pkcs11_key_dispatch_table[] = {
×
714
                        { "uri",            SD_JSON_VARIANT_STRING, dispatch_pkcs11_uri,      offsetof(Pkcs11EncryptedKey, uri),             SD_JSON_MANDATORY },
715
                        { "data",           SD_JSON_VARIANT_STRING, dispatch_pkcs11_key_data, 0,                                             SD_JSON_MANDATORY },
716
                        { "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string,  offsetof(Pkcs11EncryptedKey, hashed_password), SD_JSON_MANDATORY },
717
                        {},
718
                };
719

720
                if (!sd_json_variant_is_object(e))
×
721
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
722

723
                if (!GREEDY_REALLOC(h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key + 1))
×
724
                        return log_oom();
×
725

726
                Pkcs11EncryptedKey *k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key;
×
727
                *k = (Pkcs11EncryptedKey) {};
×
728

729
                r = sd_json_dispatch(e, pkcs11_key_dispatch_table, flags, k);
×
730
                if (r < 0) {
×
731
                        pkcs11_encrypted_key_done(k);
×
732
                        return r;
×
733
                }
734

735
                h->n_pkcs11_encrypted_key++;
×
736
        }
737

738
        return 0;
×
739
}
740

741
static int dispatch_fido2_hmac_credential(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
742
        Fido2HmacCredential *k = userdata;
×
743
        size_t l;
×
744
        void *b;
×
745
        int r;
×
746

747
        if (sd_json_variant_is_null(variant)) {
×
748
                k->id = mfree(k->id);
×
749
                k->size = 0;
×
750
                return 0;
×
751
        }
752

753
        r = sd_json_variant_unbase64(variant, &b, &l);
×
754
        if (r < 0)
×
755
                return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
×
756

757
        free_and_replace(k->id, b);
×
758
        k->size = l;
×
759

760
        return 0;
×
761
}
762

763
static int dispatch_fido2_hmac_credential_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
764
        UserRecord *h = userdata;
×
765
        sd_json_variant *e;
×
766
        int r;
×
767

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

771
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
772
                size_t l;
×
773
                void *b;
×
774

775
                if (!GREEDY_REALLOC(h->fido2_hmac_credential, h->n_fido2_hmac_credential + 1))
×
776
                        return log_oom();
×
777

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

782
                h->fido2_hmac_credential[h->n_fido2_hmac_credential++] = (Fido2HmacCredential) {
×
783
                        .id = b,
784
                        .size = l,
785
                };
786
        }
787

788
        return 0;
×
789
}
790

791
static int dispatch_fido2_hmac_salt_value(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
792
        Fido2HmacSalt *k = userdata;
×
793
        size_t l;
×
794
        void *b;
×
795
        int r;
×
796

797
        if (sd_json_variant_is_null(variant)) {
×
798
                k->salt = erase_and_free(k->salt);
×
799
                k->salt_size = 0;
×
800
                return 0;
×
801
        }
802

803
        r = sd_json_variant_unbase64(variant, &b, &l);
×
804
        if (r < 0)
×
805
                return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
×
806

807
        erase_and_free(k->salt);
×
808
        k->salt = b;
×
809
        k->salt_size = l;
×
810

811
        return 0;
×
812
}
813

814
static int dispatch_fido2_hmac_salt(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
815
        UserRecord *h = userdata;
×
816
        sd_json_variant *e;
×
817
        int r;
×
818

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

822
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
823
                static const sd_json_dispatch_field fido2_hmac_salt_dispatch_table[] = {
×
824
                        { "credential",     SD_JSON_VARIANT_STRING,  dispatch_fido2_hmac_credential, offsetof(Fido2HmacSalt, credential),      SD_JSON_MANDATORY },
825
                        { "salt",           SD_JSON_VARIANT_STRING,  dispatch_fido2_hmac_salt_value, 0,                                        SD_JSON_MANDATORY },
826
                        { "hashedPassword", SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,        offsetof(Fido2HmacSalt, hashed_password), SD_JSON_MANDATORY },
827
                        { "up",             SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, up),              0                 },
828
                        { "uv",             SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, uv),              0                 },
829
                        { "clientPin",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate,      offsetof(Fido2HmacSalt, client_pin),      0                 },
830
                        {},
831
                };
832

833
                if (!sd_json_variant_is_object(e))
×
834
                        return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
×
835

836
                if (!GREEDY_REALLOC(h->fido2_hmac_salt, h->n_fido2_hmac_salt + 1))
×
837
                        return log_oom();
×
838

839
                Fido2HmacSalt *k = h->fido2_hmac_salt + h->n_fido2_hmac_salt;
×
840
                *k = (Fido2HmacSalt) {
×
841
                        .uv = -1,
842
                        .up = -1,
843
                        .client_pin = -1,
844
                };
845

846
                r = sd_json_dispatch(e, fido2_hmac_salt_dispatch_table, flags, k);
×
847
                if (r < 0) {
×
848
                        fido2_hmac_salt_done(k);
×
849
                        return r;
×
850
                }
851

852
                h->n_fido2_hmac_salt++;
×
853
        }
854

855
        return 0;
×
856
}
857

858
static int dispatch_recovery_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
859
        UserRecord *h = userdata;
×
860
        sd_json_variant *e;
×
861
        int r;
×
862

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

866
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
×
867
                static const sd_json_dispatch_field recovery_key_dispatch_table[] = {
×
868
                        { "type",           SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0,                                      SD_JSON_MANDATORY },
869
                        { "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(RecoveryKey, hashed_password), SD_JSON_MANDATORY },
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->recovery_key, h->n_recovery_key + 1))
×
877
                        return log_oom();
×
878

879
                RecoveryKey *k = h->recovery_key + h->n_recovery_key;
×
880
                *k = (RecoveryKey) {};
×
881

882
                r = sd_json_dispatch(e, recovery_key_dispatch_table, flags, k);
×
883
                if (r < 0) {
×
884
                        recovery_key_done(k);
×
885
                        return r;
×
886
                }
887

888
                h->n_recovery_key++;
×
889
        }
890

891
        return 0;
×
892
}
893

894
static int dispatch_auto_resize_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
895
        AutoResizeMode *mode = userdata, m;
×
896

897
        assert_se(mode);
×
898

899
        if (sd_json_variant_is_null(variant)) {
×
900
                *mode = _AUTO_RESIZE_MODE_INVALID;
×
901
                return 0;
×
902
        }
903

904
        if (sd_json_variant_is_boolean(variant)) {
×
905
                *mode = sd_json_variant_boolean(variant) ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
×
906
                return 0;
×
907
        }
908

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

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

916
        *mode = m;
×
917
        return 0;
×
918
}
919

920
static int dispatch_rebalance_weight(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
783✔
921
        uint64_t *rebalance_weight = userdata;
783✔
922
        uintmax_t u;
783✔
923

924
        assert_se(rebalance_weight);
783✔
925

926
        if (sd_json_variant_is_null(variant)) {
783✔
927
                *rebalance_weight = REBALANCE_WEIGHT_UNSET;
×
928
                return 0;
×
929
        }
930

931
        if (sd_json_variant_is_boolean(variant)) {
783✔
932
                *rebalance_weight = sd_json_variant_boolean(variant) ? REBALANCE_WEIGHT_DEFAULT : REBALANCE_WEIGHT_OFF;
×
933
                return 0;
×
934
        }
935

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

939
        u = sd_json_variant_unsigned(variant);
783✔
940
        if (u >= REBALANCE_WEIGHT_MIN && u <= REBALANCE_WEIGHT_MAX)
783✔
941
                *rebalance_weight = (uint64_t) u;
×
942
        else if (u == 0)
783✔
943
                *rebalance_weight = REBALANCE_WEIGHT_OFF;
783✔
944
        else
945
                return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
×
946
                                "Rebalance weight is out of valid range %" PRIu64 "%s%" PRIu64 ".",
947
                                REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX);
948

949
        return 0;
950
}
951

952
static int dispatch_tmpfs_limit(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
442✔
953
        TmpfsLimit *limit = ASSERT_PTR(userdata);
442✔
954
        int r;
442✔
955

956
        if (sd_json_variant_is_null(variant)) {
442✔
957
                *limit = TMPFS_LIMIT_NULL;
×
958
                return 0;
×
959
        }
960

961
        r = sd_json_dispatch_uint64(name, variant, flags, &limit->limit);
442✔
962
        if (r < 0)
442✔
963
                return r;
964

965
        limit->is_set = true;
442✔
966
        return 0;
442✔
967
}
968

969
static int dispatch_tmpfs_limit_scale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
970
        TmpfsLimit *limit = ASSERT_PTR(userdata);
×
971
        int r;
×
972

973
        if (sd_json_variant_is_null(variant)) {
×
974
                *limit = TMPFS_LIMIT_NULL;
×
975
                return 0;
×
976
        }
977

978
        r = sd_json_dispatch_uint32(name, variant, flags, &limit->limit_scale);
×
979
        if (r < 0)
×
980
                return r;
981

982
        limit->is_set = true;
×
983
        return 0;
×
984
}
985

986
static int dispatch_privileged(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
9,542✔
987

988
        static const sd_json_dispatch_field privileged_dispatch_table[] = {
9,542✔
989
                { "passwordHint",       SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,  offsetof(UserRecord, password_hint),        0              },
990
                { "hashedPassword",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,    offsetof(UserRecord, hashed_password),      SD_JSON_STRICT },
991
                { "sshAuthorizedKeys",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv,    offsetof(UserRecord, ssh_authorized_keys),  0              },
992
                { "pkcs11EncryptedKey", SD_JSON_VARIANT_ARRAY,         dispatch_pkcs11_key,      0,                                          0              },
993
                { "fido2HmacSalt",      SD_JSON_VARIANT_ARRAY,         dispatch_fido2_hmac_salt, 0,                                          0              },
994
                { "recoveryKey",        SD_JSON_VARIANT_ARRAY,         dispatch_recovery_key,    0,                                          0              },
995
                {},
996
        };
997

998
        return sd_json_dispatch(variant, privileged_dispatch_table, flags, userdata);
9,542✔
999
}
1000

1001
static int dispatch_binding(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
14,799✔
1002

1003
        static const sd_json_dispatch_field binding_dispatch_table[] = {
14,799✔
1004
                { "blobDirectory",     SD_JSON_VARIANT_STRING,        json_dispatch_path,           offsetof(UserRecord, blob_directory),       SD_JSON_STRICT },
1005
                { "imagePath",         SD_JSON_VARIANT_STRING,        json_dispatch_image_path,     offsetof(UserRecord, image_path),           0              },
1006
                { "homeDirectory",     SD_JSON_VARIANT_STRING,        json_dispatch_home_directory, offsetof(UserRecord, home_directory),       0              },
1007
                { "partitionUuid",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, partition_uuid),       0              },
1008
                { "luksUuid",          SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, luks_uuid),            0              },
1009
                { "fileSystemUuid",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_id128,       offsetof(UserRecord, file_system_uuid),     0              },
1010
                { "uid",               SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,     offsetof(UserRecord, uid),                  0              },
1011
                { "gid",               SD_JSON_VARIANT_UNSIGNED,      sd_json_dispatch_uid_gid,     offsetof(UserRecord, gid),                  0              },
1012
                { "storage",           SD_JSON_VARIANT_STRING,        json_dispatch_user_storage,   offsetof(UserRecord, storage),              0              },
1013
                { "fileSystemType",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, file_system_type),     SD_JSON_STRICT },
1014
                { "luksCipher",        SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, luks_cipher),          SD_JSON_STRICT },
1015
                { "luksCipherMode",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,      offsetof(UserRecord, luks_cipher_mode),     SD_JSON_STRICT },
1016
                { "luksVolumeKeySize", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,      offsetof(UserRecord, luks_volume_key_size), 0              },
1017
                {},
1018
        };
1019

1020
        sd_json_variant *m;
14,799✔
1021
        sd_id128_t mid;
14,799✔
1022
        int r;
14,799✔
1023

1024
        if (!variant)
14,799✔
1025
                return 0;
14,799✔
1026

1027
        if (!sd_json_variant_is_object(variant))
1,731✔
1028
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1029

1030
        r = sd_id128_get_machine(&mid);
1,731✔
1031
        if (r < 0)
1,731✔
1032
                return json_log(variant, flags, r, "Failed to determine machine ID: %m");
×
1033

1034
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
1,731✔
1035
        if (!m)
1,731✔
1036
                return 0;
1037

1038
        return sd_json_dispatch(m, binding_dispatch_table, flags, userdata);
1,731✔
1039
}
1040

1041
static int dispatch_blob_manifest(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
166✔
1042
        _cleanup_hashmap_free_ Hashmap *manifest = NULL;
166✔
1043
        Hashmap **ret = ASSERT_PTR(userdata);
166✔
1044
        sd_json_variant *value;
166✔
1045
        const char *key;
166✔
1046
        int r;
166✔
1047

1048
        if (!variant)
166✔
1049
                return 0;
1050

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

1054
        JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
528✔
1055
                _cleanup_free_ char *filename = NULL;
×
1056
                _cleanup_free_ uint8_t *hash = NULL;
362✔
1057

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

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

1064
                filename = strdup(key);
362✔
1065
                if (!filename)
362✔
1066
                        return json_log_oom(value, flags);
×
1067

1068
                hash = malloc(SHA256_DIGEST_SIZE);
362✔
1069
                if (!hash)
362✔
1070
                        return json_log_oom(value, flags);
×
1071

1072
                r = parse_sha256(sd_json_variant_string(value), hash);
362✔
1073
                if (r < 0)
362✔
1074
                        return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, sd_json_variant_string(value));
×
1075

1076
                r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash);
362✔
1077
                if (r < 0)
362✔
1078
                        return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename);
×
1079
                TAKE_PTR(filename); /* Ownership transfers to hashmap */
362✔
1080
                TAKE_PTR(hash);
362✔
1081
        }
1082

1083
        hashmap_free_and_replace(*ret, manifest);
166✔
1084
        return 0;
166✔
1085
}
1086

1087
int per_machine_id_match(sd_json_variant *ids, sd_json_dispatch_flags_t flags) {
1,989✔
1088
        sd_id128_t mid;
1,989✔
1089
        int r;
1,989✔
1090

1091
        assert(ids);
1,989✔
1092

1093
        r = sd_id128_get_machine(&mid);
1,989✔
1094
        if (r < 0)
1,989✔
1095
                return json_log(ids, flags, r, "Failed to acquire machine ID: %m");
×
1096

1097
        if (sd_json_variant_is_string(ids)) {
1,989✔
1098
                sd_id128_t k;
1,989✔
1099

1100
                r = sd_id128_from_string(sd_json_variant_string(ids), &k);
1,989✔
1101
                if (r < 0) {
1,989✔
1102
                        json_log(ids, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(ids));
×
1103
                        return 0;
1,989✔
1104
                }
1105

1106
                return sd_id128_equal(mid, k);
3,978✔
1107
        }
1108

1109
        if (sd_json_variant_is_array(ids)) {
×
1110
                sd_json_variant *e;
×
1111

1112
                JSON_VARIANT_ARRAY_FOREACH(e, ids) {
×
1113
                        sd_id128_t k;
×
1114

1115
                        if (!sd_json_variant_is_string(e)) {
×
1116
                                json_log(e, flags, 0, "Machine ID is not a string, ignoring: %m");
×
1117
                                continue;
×
1118
                        }
1119

1120
                        r = sd_id128_from_string(sd_json_variant_string(e), &k);
×
1121
                        if (r < 0) {
×
1122
                                json_log(e, flags, r, "%s is not a valid machine ID, ignoring: %m", sd_json_variant_string(e));
×
1123
                                continue;
×
1124
                        }
1125

1126
                        if (sd_id128_equal(mid, k))
×
1127
                                return true;
×
1128
                }
1129

1130
                return false;
×
1131
        }
1132

1133
        json_log(ids, flags, 0, "Machine ID is not a string or array of strings, ignoring: %m");
×
1134
        return false;
1135
}
1136

1137
int per_machine_hostname_match(sd_json_variant *hns, sd_json_dispatch_flags_t flags) {
×
1138
        _cleanup_free_ char *hn = NULL;
×
1139
        int r;
×
1140

1141
        assert(hns);
×
1142

1143
        r = gethostname_strict(&hn);
×
1144
        if (r == -ENXIO) {
×
1145
                json_log(hns, flags, r, "No hostname set, not matching perMachine hostname record: %m");
×
1146
                return false;
×
1147
        }
1148
        if (r < 0)
×
1149
                return json_log(hns, flags, r, "Failed to acquire hostname: %m");
×
1150

1151
        if (sd_json_variant_is_string(hns))
×
1152
                return streq(sd_json_variant_string(hns), hn);
×
1153

1154
        if (sd_json_variant_is_array(hns)) {
×
1155
                sd_json_variant *e;
×
1156

1157
                JSON_VARIANT_ARRAY_FOREACH(e, hns) {
×
1158

1159
                        if (!sd_json_variant_is_string(e)) {
×
1160
                                json_log(e, flags, 0, "Hostname is not a string, ignoring: %m");
×
1161
                                continue;
×
1162
                        }
1163

1164
                        if (streq(sd_json_variant_string(hns), hn))
×
1165
                                return true;
×
1166
                }
1167

1168
                return false;
×
1169
        }
1170

1171
        json_log(hns, flags, 0, "Hostname is not a string or array of strings, ignoring: %m");
×
1172
        return false;
1173
}
1174

1175
int per_machine_match(sd_json_variant *entry, sd_json_dispatch_flags_t flags) {
1,989✔
1176
        sd_json_variant *m;
1,989✔
1177
        int r;
1,989✔
1178

1179
        assert(sd_json_variant_is_object(entry));
1,989✔
1180

1181
        m = sd_json_variant_by_key(entry, "matchMachineId");
1,989✔
1182
        if (m) {
1,989✔
1183
                r = per_machine_id_match(m, flags);
1,907✔
1184
                if (r < 0)
1,907✔
1185
                        return r;
1186
                if (r > 0)
1,907✔
1187
                        return true;
1188
        }
1189

1190
        m = sd_json_variant_by_key(entry, "matchNotMachineId");
82✔
1191
        if (m) {
82✔
1192
                r = per_machine_id_match(m, flags);
82✔
1193
                if (r < 0)
82✔
1194
                        return r;
1195
                if (r == 0)
82✔
1196
                        return true;
1197
        }
1198

1199
        m = sd_json_variant_by_key(entry, "matchHostname");
82✔
1200
        if (m) {
82✔
1201
                r = per_machine_hostname_match(m, flags);
×
1202
                if (r < 0)
×
1203
                        return r;
1204
                if (r > 0)
×
1205
                        return true;
1206
        }
1207

1208
        m = sd_json_variant_by_key(entry, "matchNotHostname");
82✔
1209
        if (m) {
82✔
1210
                r = per_machine_hostname_match(m, flags);
×
1211
                if (r < 0)
×
1212
                        return r;
1213
                if (r == 0)
×
1214
                        return true;
×
1215
        }
1216

1217
        return false;
1218
}
1219

1220
static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
14,799✔
1221

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

1311
        sd_json_variant *e;
14,799✔
1312
        int r;
14,799✔
1313

1314
        if (!variant)
14,799✔
1315
                return 0;
1316

1317
        if (!sd_json_variant_is_array(variant))
1,879✔
1318
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
×
1319

1320
        JSON_VARIANT_ARRAY_FOREACH(e, variant) {
3,833✔
1321
                if (!sd_json_variant_is_object(e))
1,954✔
1322
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
×
1323

1324
                r = per_machine_match(e, flags);
1,954✔
1325
                if (r < 0)
1,954✔
1326
                        return r;
1327
                if (r == 0)
1,954✔
1328
                        continue;
75✔
1329

1330
                r = sd_json_dispatch(e, per_machine_dispatch_table, flags, userdata);
1,879✔
1331
                if (r < 0)
1,879✔
1332
                        return r;
1333
        }
1334

1335
        return 0;
1,879✔
1336
}
1337

1338
static int dispatch_status(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
14,799✔
1339

1340
        static const sd_json_dispatch_field status_dispatch_table[] = {
14,799✔
1341
                { "diskUsage",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_usage),                    0              },
1342
                { "diskFree",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_free),                     0              },
1343
                { "diskSize",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_size),                     0              },
1344
                { "diskCeiling",                _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_ceiling),                  0              },
1345
                { "diskFloor",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, disk_floor),                    0              },
1346
                { "state",                      SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, state),                         SD_JSON_STRICT },
1347
                { "service",                    SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, service),                       SD_JSON_STRICT },
1348
                { "signedLocally",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_tristate,      offsetof(UserRecord, signed_locally),                0              },
1349
                { "goodAuthenticationCounter",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, good_authentication_counter),   0              },
1350
                { "badAuthenticationCounter",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, bad_authentication_counter),    0              },
1351
                { "lastGoodAuthenticationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_good_authentication_usec), 0              },
1352
                { "lastBadAuthenticationUSec",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, last_bad_authentication_usec),  0              },
1353
                { "rateLimitBeginUSec",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_begin_usec),          0              },
1354
                { "rateLimitCount",             _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,        offsetof(UserRecord, ratelimit_count),               0              },
1355
                { "removable",                  SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_tristate,      offsetof(UserRecord, removable),                     0              },
1356
                { "accessMode",                 _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_access_mode,      offsetof(UserRecord, access_mode),                   0              },
1357
                { "fileSystemType",             SD_JSON_VARIANT_STRING,        sd_json_dispatch_string,        offsetof(UserRecord, file_system_type),              SD_JSON_STRICT },
1358
                { "fallbackShell",              SD_JSON_VARIANT_STRING,        json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell),                0              },
1359
                { "fallbackHomeDirectory",      SD_JSON_VARIANT_STRING,        json_dispatch_home_directory,   offsetof(UserRecord, fallback_home_directory),       0              },
1360
                { "useFallback",                SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,       offsetof(UserRecord, use_fallback),                  0              },
1361
                { "defaultArea",                SD_JSON_VARIANT_STRING,        json_dispatch_filename,         offsetof(UserRecord, default_area),                  0              },
1362
                { "aliases",                    SD_JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,  offsetof(UserRecord, aliases),                       SD_JSON_RELAX  },
1363
                {},
1364
        };
1365

1366
        sd_json_variant *m;
14,799✔
1367
        sd_id128_t mid;
14,799✔
1368
        int r;
14,799✔
1369

1370
        if (!variant)
14,799✔
1371
                return 0;
14,799✔
1372

1373
        if (!sd_json_variant_is_object(variant))
4,662✔
1374
                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
×
1375

1376
        r = sd_id128_get_machine(&mid);
4,662✔
1377
        if (r < 0)
4,662✔
1378
                return json_log(variant, flags, r, "Failed to determine machine ID: %m");
×
1379

1380
        m = sd_json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
4,662✔
1381
        if (!m)
4,662✔
1382
                return 0;
1383

1384
        return sd_json_dispatch(m, status_dispatch_table, flags, userdata);
4,662✔
1385
}
1386

1387
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret) {
8,169✔
1388
        const char *suffix;
8,169✔
1389
        char *z;
8,169✔
1390

1391
        assert(storage >= 0);
8,169✔
1392
        assert(user_name_and_realm);
8,169✔
1393
        assert(ret);
8,169✔
1394

1395
        if (storage == USER_LUKS)
8,169✔
1396
                suffix = ".home";
1397
        else if (IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
8,169✔
1398
                suffix = ".homedir";
1399
        else {
1400
                *ret = NULL;
8,031✔
1401
                return 0;
8,031✔
1402
        }
1403

1404
        z = strjoin(get_home_root(), "/", user_name_and_realm, suffix);
138✔
1405
        if (!z)
138✔
1406
                return -ENOMEM;
1407

1408
        *ret = path_simplify(z);
138✔
1409
        return 1;
138✔
1410
}
1411

1412
static int user_record_augment(UserRecord *h, sd_json_dispatch_flags_t json_flags) {
14,799✔
1413
        int r;
14,799✔
1414

1415
        assert(h);
14,799✔
1416

1417
        if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
14,799✔
1418
                return 0;
1419

1420
        assert(h->user_name);
14,616✔
1421

1422
        if (!h->user_name_and_realm_auto && h->realm) {
14,616✔
1423
                h->user_name_and_realm_auto = strjoin(h->user_name, "@", h->realm);
58✔
1424
                if (!h->user_name_and_realm_auto)
58✔
1425
                        return json_log_oom(h->json, json_flags);
×
1426
        }
1427

1428
        /* Let's add in the following automatisms only for regular users, they don't make sense for any others */
1429
        if (user_record_disposition(h) != USER_REGULAR)
14,616✔
1430
                return 0;
1431

1432
        if (!h->home_directory && !h->home_directory_auto) {
9,973✔
1433
                h->home_directory_auto = path_join(get_home_root(), h->user_name);
8,289✔
1434
                if (!h->home_directory_auto)
8,289✔
1435
                        return json_log_oom(h->json, json_flags);
×
1436
        }
1437

1438
        if (!h->image_path && !h->image_path_auto) {
9,973✔
1439
                r = user_record_build_image_path(user_record_storage(h), user_record_user_name_and_realm(h), &h->image_path_auto);
8,167✔
1440
                if (r < 0)
8,167✔
1441
                        return json_log(h->json, json_flags, r, "Failed to determine default image path: %m");
×
1442
        }
1443

1444
        return 0;
1445
}
1446

1447
int user_group_record_mangle(
23,608✔
1448
                sd_json_variant *v,
1449
                UserRecordLoadFlags load_flags,
1450
                sd_json_variant **ret_variant,
1451
                UserRecordMask *ret_mask) {
1452

1453
        static const struct {
23,608✔
1454
                UserRecordMask mask;
1455
                const char *name;
1456
        } mask_field[] = {
1457
                { USER_RECORD_PRIVILEGED,  "privileged" },
1458
                { USER_RECORD_SECRET,      "secret"     },
1459
                { USER_RECORD_BINDING,     "binding"    },
1460
                { USER_RECORD_PER_MACHINE, "perMachine" },
1461
                { USER_RECORD_STATUS,      "status"     },
1462
                { USER_RECORD_SIGNATURE,   "signature"  },
1463
        };
1464

1465
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
23,608✔
1466
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
23,608✔
1467
        sd_json_variant *array[ELEMENTSOF(mask_field) * 2];
23,608✔
1468
        size_t n_retain = 0;
23,608✔
1469
        UserRecordMask m = 0;
23,608✔
1470
        int r;
23,608✔
1471

1472
        assert((load_flags & _USER_RECORD_MASK_MAX) == 0); /* detect mistakes when accidentally passing
23,608✔
1473
                                                            * UserRecordMask bit masks as UserRecordLoadFlags
1474
                                                            * value */
1475

1476
        assert(v);
23,608✔
1477
        assert(ret_variant);
23,608✔
1478

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

1482
        if (!sd_json_variant_is_object(v))
23,608✔
1483
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is not a JSON object, refusing.");
×
1484

1485
        if (USER_RECORD_ALLOW_MASK(load_flags) == 0) /* allow nothing? */
23,608✔
1486
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Nothing allowed in record, refusing.");
×
1487

1488
        if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */
23,608✔
1489
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing.");
×
1490

1491
        /* Extra safety: mark the "secret" part (that contains literal passwords and such) as sensitive, so
1492
         * that it is not included in debug output and erased from memory when we are done. We do this for
1493
         * any record that passes through here. */
1494
        sd_json_variant_sensitive(sd_json_variant_by_key(v, "secret"));
23,608✔
1495

1496
        /* Check if we have the special sections and if they match our flags set */
1497
        FOREACH_ELEMENT(i, mask_field) {
165,256✔
1498
                sd_json_variant *e, *k;
141,648✔
1499

1500
                if (FLAGS_SET(USER_RECORD_STRIP_MASK(load_flags), i->mask)) {
141,648✔
1501
                        if (!w)
9,599✔
1502
                                w = sd_json_variant_ref(v);
7,845✔
1503

1504
                        r = sd_json_variant_filter(&w, STRV_MAKE(i->name));
9,599✔
1505
                        if (r < 0)
9,599✔
1506
                                return json_log(w, json_flags, r, "Failed to remove field from variant: %m");
×
1507

1508
                        continue;
9,599✔
1509
                }
1510

1511
                e = sd_json_variant_by_key_full(v, i->name, &k);
132,049✔
1512
                if (e) {
132,049✔
1513
                        if (!FLAGS_SET(USER_RECORD_ALLOW_MASK(load_flags), i->mask))
23,412✔
1514
                                return json_log(e, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", i->name);
×
1515

1516
                        if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
23,412✔
1517
                                array[n_retain++] = k;
148✔
1518
                                array[n_retain++] = e;
148✔
1519
                        }
1520

1521
                        m |= i->mask;
23,412✔
1522
                } else {
1523
                        if (FLAGS_SET(USER_RECORD_REQUIRE_MASK(load_flags), i->mask))
108,637✔
1524
                                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks '%s' field, which is required.", i->name);
×
1525
                }
1526
        }
1527

1528
        if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
23,608✔
1529
                /* If we are supposed to strip regular items, then let's instead just allocate a new object
1530
                 * with just the stuff we need. */
1531

1532
                w = sd_json_variant_unref(w);
284✔
1533
                r = sd_json_variant_new_object(&w, array, n_retain);
284✔
1534
                if (r < 0)
284✔
1535
                        return json_log(v, json_flags, r, "Failed to allocate new object: %m");
×
1536
        } else
1537
                /* And now check if there's anything else in the record */
1538
                for (size_t i = 0; i < sd_json_variant_elements(v); i += 2) {
23,359✔
1539
                        const char *f;
23,324✔
1540
                        bool special = false;
23,324✔
1541

1542
                        assert_se(f = sd_json_variant_string(sd_json_variant_by_index(v, i)));
23,324✔
1543

1544
                        FOREACH_ELEMENT(j, mask_field)
163,268✔
1545
                                if (streq(f, j->name)) { /* already covered in the loop above */
139,944✔
1546
                                        special = true;
35✔
1547
                                        continue;
35✔
1548
                                }
1549

1550
                        if (!special) {
23,324✔
1551
                                if ((load_flags & (USER_RECORD_ALLOW_REGULAR|USER_RECORD_REQUIRE_REGULAR)) == 0)
23,289✔
1552
                                        return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", f);
×
1553

1554
                                m |= USER_RECORD_REGULAR;
23,289✔
1555
                                break;
23,289✔
1556
                        }
1557
                }
1558

1559
        if (FLAGS_SET(load_flags, USER_RECORD_REQUIRE_REGULAR) && !FLAGS_SET(m, USER_RECORD_REGULAR))
23,608✔
1560
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks basic identity fields, which are required.");
×
1561

1562
        if (!FLAGS_SET(load_flags, USER_RECORD_EMPTY_OK) && m == 0)
23,608✔
1563
                return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is empty.");
×
1564

1565
        if (w)
23,608✔
1566
                *ret_variant = TAKE_PTR(w);
7,845✔
1567
        else
1568
                *ret_variant = sd_json_variant_ref(v);
15,763✔
1569

1570
        if (ret_mask)
23,608✔
1571
                *ret_mask = m;
23,564✔
1572
        return 0;
1573
}
1574

1575
int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load_flags) {
14,799✔
1576

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

1672
                { "secret",                     SD_JSON_VARIANT_OBJECT,        dispatch_secret,                      0,                                                   0              },
1673
                { "privileged",                 SD_JSON_VARIANT_OBJECT,        dispatch_privileged,                  0,                                                   0              },
1674

1675
                /* Ignore the perMachine, binding, status stuff here, and process it later, so that it overrides whatever is set above */
1676
                { "perMachine",                 SD_JSON_VARIANT_ARRAY,         NULL,                                 0,                                                   0              },
1677
                { "binding",                    SD_JSON_VARIANT_OBJECT,        NULL,                                 0,                                                   0              },
1678
                { "status",                     SD_JSON_VARIANT_OBJECT,        NULL,                                 0,                                                   0              },
1679

1680
                /* Ignore 'signature', we check it with explicit accessors instead */
1681
                { "signature",                  SD_JSON_VARIANT_ARRAY,         NULL,                                 0,                                                   0              },
1682
                {},
1683
        };
1684

1685
        sd_json_dispatch_flags_t json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
14,799✔
1686
        int r;
14,799✔
1687

1688
        assert(h);
14,799✔
1689
        assert(!h->json);
14,799✔
1690

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

1693
        r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
14,799✔
1694
        if (r < 0)
14,799✔
1695
                return r;
1696

1697
        r = sd_json_dispatch(h->json, user_dispatch_table, json_flags | SD_JSON_ALLOW_EXTENSIONS, h);
14,799✔
1698
        if (r < 0)
14,799✔
1699
                return r;
1700

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

1704
        r = dispatch_per_machine("perMachine", sd_json_variant_by_key(h->json, "perMachine"), json_flags, h);
14,799✔
1705
        if (r < 0)
14,799✔
1706
                return r;
1707

1708
        r = dispatch_binding("binding", sd_json_variant_by_key(h->json, "binding"), json_flags, h);
14,799✔
1709
        if (r < 0)
14,799✔
1710
                return r;
1711

1712
        r = dispatch_status("status", sd_json_variant_by_key(h->json, "status"), json_flags, h);
14,799✔
1713
        if (r < 0)
14,799✔
1714
                return r;
1715

1716
        if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->user_name)
14,799✔
1717
                return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "User name field missing, refusing.");
×
1718

1719
        r = user_record_augment(h, json_flags);
14,799✔
1720
        if (r < 0)
14,799✔
1721
                return r;
×
1722

1723
        return 0;
1724
}
1725

1726
int user_record_build(UserRecord **ret, ...) {
2,998✔
1727
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
2,998✔
1728
        _cleanup_(user_record_unrefp) UserRecord *u = NULL;
2,998✔
1729
        va_list ap;
2,998✔
1730
        int r;
2,998✔
1731

1732
        assert(ret);
2,998✔
1733

1734
        va_start(ap, ret);
2,998✔
1735
        r = sd_json_buildv(&v, ap);
2,998✔
1736
        va_end(ap);
2,998✔
1737

1738
        if (r < 0)
2,998✔
1739
                return r;
1740

1741
        u = user_record_new();
2,998✔
1742
        if (!u)
2,998✔
1743
                return -ENOMEM;
1744

1745
        r = user_record_load(u, v, USER_RECORD_LOAD_FULL);
2,998✔
1746
        if (r < 0)
2,998✔
1747
                return r;
1748

1749
        *ret = TAKE_PTR(u);
2,998✔
1750
        return 0;
2,998✔
1751
}
1752

1753
const char* user_record_user_name_and_realm(UserRecord *h) {
8,319✔
1754
        assert(h);
8,319✔
1755

1756
        /* Return the pre-initialized joined string if it is defined */
1757
        if (h->user_name_and_realm_auto)
8,319✔
1758
                return h->user_name_and_realm_auto;
1759

1760
        /* If it's not defined then we cannot have a realm */
1761
        assert(!h->realm);
8,298✔
1762
        return h->user_name;
8,298✔
1763
}
1764

1765
UserStorage user_record_storage(UserRecord *h) {
21,496✔
1766
        assert(h);
21,496✔
1767

1768
        if (h->storage >= 0)
21,496✔
1769
                return h->storage;
2,422✔
1770

1771
        return USER_CLASSIC;
1772
}
1773

1774
const char* user_record_file_system_type(UserRecord *h) {
×
1775
        assert(h);
×
1776

1777
        return h->file_system_type ?: "btrfs";
×
1778
}
1779

1780
const char* user_record_skeleton_directory(UserRecord *h) {
146✔
1781
        assert(h);
146✔
1782

1783
        return h->skeleton_directory ?: "/etc/skel";
146✔
1784
}
1785

1786
mode_t user_record_access_mode(UserRecord *h) {
9✔
1787
        assert(h);
9✔
1788

1789
        return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
9✔
1790
}
1791

1792
static const char *user_record_home_directory_real(UserRecord *h) {
4,622✔
1793
        assert(h);
4,622✔
1794

1795
        if (h->home_directory)
4,622✔
1796
                return h->home_directory;
1797
        if (h->home_directory_auto)
1,607✔
1798
                return h->home_directory_auto;
1799

1800
        /* The root user is special, hence be special about it */
1801
        if (user_record_is_root(h))
46✔
1802
                return "/root";
×
1803

1804
        return "/";
1805
}
1806

1807
const char* user_record_home_directory(UserRecord *h) {
4,703✔
1808
        assert(h);
4,703✔
1809

1810
        if (h->use_fallback && h->fallback_home_directory)
4,703✔
1811
                return h->fallback_home_directory;
1812

1813
        return user_record_home_directory_real(h);
4,563✔
1814
}
1815

1816
const char* user_record_image_path(UserRecord *h) {
1,169✔
1817
        assert(h);
1,169✔
1818

1819
        if (h->image_path)
1,169✔
1820
                return h->image_path;
1821
        if (h->image_path_auto)
112✔
1822
                return h->image_path_auto;
1823

1824
        /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */
1825
        return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ?
59✔
1826
                user_record_home_directory_real(h) : NULL;
59✔
1827
}
1828

1829
const char* user_record_cifs_user_name(UserRecord *h) {
×
1830
        assert(h);
×
1831

1832
        return h->cifs_user_name ?: h->user_name;
×
1833
}
1834

1835
unsigned long user_record_mount_flags(UserRecord *h) {
42✔
1836
        assert(h);
42✔
1837

1838
        return (h->nosuid ? MS_NOSUID : 0) |
42✔
1839
                (h->noexec ? MS_NOEXEC : 0) |
42✔
1840
                (h->nodev ? MS_NODEV : 0);
42✔
1841
}
1842

1843
static const char *user_record_shell_real(UserRecord *h) {
1,662✔
1844
        assert(h);
1,662✔
1845

1846
        if (h->shell)
1,662✔
1847
                return h->shell;
1848

1849
        if (user_record_is_root(h))
402✔
1850
                return "/bin/sh";
1851

1852
        if (user_record_disposition(h) == USER_REGULAR)
397✔
1853
                return DEFAULT_USER_SHELL;
379✔
1854

1855
        return NOLOGIN;
1856
}
1857

1858
const char* user_record_shell(UserRecord *h) {
1,662✔
1859
        const char *shell;
1,662✔
1860

1861
        assert(h);
1,662✔
1862

1863
        shell = user_record_shell_real(h);
1,662✔
1864

1865
        /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
1866
         * then let's not risk anything. */
1867
        if (h->use_fallback && h->fallback_shell)
1,662✔
1868
                return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
404✔
1869

1870
        return shell;
1871
}
1872

1873
const char* user_record_real_name(UserRecord *h) {
1,154✔
1874
        assert(h);
1,154✔
1875

1876
        return h->real_name ?: h->user_name;
1,154✔
1877
}
1878

1879
bool user_record_luks_discard(UserRecord *h) {
×
1880
        const char *ip;
×
1881

1882
        assert(h);
×
1883

1884
        if (h->luks_discard >= 0)
×
1885
                return h->luks_discard;
×
1886

1887
        ip = user_record_image_path(h);
×
1888
        if (!ip)
×
1889
                return false;
1890

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

1897
        return path_startswith(ip, "/dev/");
×
1898
}
1899

1900
bool user_record_luks_offline_discard(UserRecord *h) {
×
1901
        const char *ip;
×
1902

1903
        assert(h);
×
1904

1905
        if (h->luks_offline_discard >= 0)
×
1906
                return h->luks_offline_discard;
×
1907

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

1911
        ip = user_record_image_path(h);
×
1912
        if (!ip)
×
1913
                return false;
1914

1915
        if (path_startswith(ip, "/dev/"))
×
1916
                return user_record_luks_discard(h);
×
1917

1918
        return true;
1919
}
1920

1921
const char* user_record_luks_cipher(UserRecord *h) {
×
1922
        assert(h);
×
1923

1924
        return h->luks_cipher ?: "aes";
×
1925
}
1926

1927
const char* user_record_luks_cipher_mode(UserRecord *h) {
×
1928
        assert(h);
×
1929

1930
        return h->luks_cipher_mode ?: "xts-plain64";
×
1931
}
1932

1933
uint64_t user_record_luks_volume_key_size(UserRecord *h) {
×
1934
        assert(h);
×
1935

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

1938
        if (h->luks_volume_key_size == UINT64_MAX)
×
1939
                return 256 / 8;
×
1940

1941
        return MIN(h->luks_volume_key_size, SIZE_MAX);
1942
}
1943

1944
const char* user_record_luks_pbkdf_type(UserRecord *h) {
×
1945
        assert(h);
×
1946

1947
        return h->luks_pbkdf_type ?: "argon2id";
×
1948
}
1949

1950
uint64_t user_record_luks_pbkdf_force_iterations(UserRecord *h) {
×
1951
        assert(h);
×
1952

1953
        /* propagate default "benchmark" mode as itself */
1954
        if (h->luks_pbkdf_force_iterations == UINT64_MAX)
×
1955
                return UINT64_MAX;
1956

1957
        /* clamp everything else to actually accepted number of iterations of libcryptsetup */
1958
        return CLAMP(h->luks_pbkdf_force_iterations, 1U, UINT32_MAX);
×
1959
}
1960

1961
uint64_t user_record_luks_pbkdf_time_cost_usec(UserRecord *h) {
×
1962
        assert(h);
×
1963

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

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

1969
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_time_cost_usec, USEC_PER_MSEC), UINT32_MAX) * USEC_PER_MSEC;
×
1970
}
1971

1972
uint64_t user_record_luks_pbkdf_memory_cost(UserRecord *h) {
×
1973
        assert(h);
×
1974

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

1980
        return MIN(DIV_ROUND_UP(h->luks_pbkdf_memory_cost, 1024), UINT32_MAX) * 1024;
×
1981
}
1982

1983
uint64_t user_record_luks_pbkdf_parallel_threads(UserRecord *h) {
×
1984
        assert(h);
×
1985

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

1990
        return MIN(h->luks_pbkdf_parallel_threads, UINT32_MAX);
×
1991
}
1992

1993
uint64_t user_record_luks_sector_size(UserRecord *h) {
×
1994
        assert(h);
×
1995

1996
        if (h->luks_sector_size == UINT64_MAX)
×
1997
                return 512;
1998

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

2003
const char* user_record_luks_pbkdf_hash_algorithm(UserRecord *h) {
×
2004
        assert(h);
×
2005

2006
        return h->luks_pbkdf_hash_algorithm ?: "sha512";
×
2007
}
2008

2009
gid_t user_record_gid(UserRecord *h) {
3,893✔
2010
        assert(h);
3,893✔
2011

2012
        if (gid_is_valid(h->gid))
3,893✔
2013
                return h->gid;
2014

2015
        return (gid_t) h->uid;
1,630✔
2016
}
2017

2018
UserDisposition user_record_disposition(UserRecord *h) {
24,739✔
2019
        assert(h);
24,739✔
2020

2021
        if (h->disposition >= 0)
24,739✔
2022
                return h->disposition;
2023

2024
        /* If not declared, derive from UID */
2025

2026
        if (!uid_is_valid(h->uid))
3,864✔
2027
                return _USER_DISPOSITION_INVALID;
2028

2029
        if (user_record_is_root(h) || user_record_is_nobody(h))
3,864✔
2030
                return USER_INTRINSIC;
1,570✔
2031

2032
        if (uid_is_system(h->uid))
2,294✔
2033
                return USER_SYSTEM;
2034

2035
        if (uid_is_dynamic(h->uid) || uid_is_greeter(h->uid))
277✔
2036
                return USER_DYNAMIC;
2037

2038
        if (uid_is_container(h->uid))
277✔
2039
                return USER_CONTAINER;
2040

2041
        if (uid_is_foreign(h->uid))
177✔
2042
                return USER_FOREIGN;
2043

2044
        if (h->uid > INT32_MAX)
177✔
2045
                return USER_RESERVED;
×
2046

2047
        return USER_REGULAR;
2048
}
2049

2050
int user_record_removable(UserRecord *h) {
11,244✔
2051
        UserStorage storage;
11,244✔
2052
        assert(h);
11,244✔
2053

2054
        if (h->removable >= 0)
11,244✔
2055
                return h->removable;
2056

2057
        /* Refuse to decide for classic records */
2058
        storage = user_record_storage(h);
11,244✔
2059
        if (h->storage < 0 || h->storage == USER_CLASSIC)
11,244✔
2060
                return -1;
2061

2062
        /* For now consider only LUKS home directories with a reference by path as removable */
2063
        return storage == USER_LUKS && path_startswith(user_record_image_path(h), "/dev/");
381✔
2064
}
2065

2066
uint64_t user_record_ratelimit_interval_usec(UserRecord *h) {
130✔
2067
        assert(h);
130✔
2068

2069
        if (h->ratelimit_interval_usec == UINT64_MAX)
130✔
2070
                return DEFAULT_RATELIMIT_INTERVAL_USEC;
44✔
2071

2072
        return h->ratelimit_interval_usec;
2073
}
2074

2075
uint64_t user_record_ratelimit_burst(UserRecord *h) {
306✔
2076
        assert(h);
306✔
2077

2078
        if (h->ratelimit_burst == UINT64_MAX)
306✔
2079
                return DEFAULT_RATELIMIT_BURST;
126✔
2080

2081
        return h->ratelimit_burst;
2082
}
2083

2084
bool user_record_can_authenticate(UserRecord *h) {
×
2085
        assert(h);
×
2086

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

2089
        if (h->n_pkcs11_encrypted_key > 0)
×
2090
                return true;
2091

2092
        if (h->n_fido2_hmac_salt > 0)
×
2093
                return true;
2094

2095
        return !strv_isempty(h->hashed_password);
×
2096
}
2097

2098
bool user_record_drop_caches(UserRecord *h) {
215✔
2099
        assert(h);
215✔
2100

2101
        if (h->drop_caches >= 0)
215✔
2102
                return h->drop_caches;
×
2103

2104
        /* By default drop caches on fscrypt, not otherwise. */
2105
        return user_record_storage(h) == USER_FSCRYPT;
215✔
2106
}
2107

2108
AutoResizeMode user_record_auto_resize_mode(UserRecord *h) {
×
2109
        assert(h);
×
2110

2111
        if (h->auto_resize_mode >= 0)
×
2112
                return h->auto_resize_mode;
2113

2114
        return user_record_storage(h) == USER_LUKS ? AUTO_RESIZE_SHRINK_AND_GROW : AUTO_RESIZE_OFF;
×
2115
}
2116

2117
uint64_t user_record_rebalance_weight(UserRecord *h) {
143✔
2118
        assert(h);
143✔
2119

2120
        if (h->rebalance_weight == REBALANCE_WEIGHT_UNSET)
143✔
2121
                return REBALANCE_WEIGHT_DEFAULT;
33✔
2122

2123
        return h->rebalance_weight;
2124
}
2125

2126
static uint64_t parse_caps_strv(char **l) {
×
2127
        uint64_t c = 0;
×
2128
        int r;
×
2129

2130
        STRV_FOREACH(i, l) {
×
2131
                r = capability_from_name(*i);
×
2132
                if (r < 0)
×
2133
                        log_debug_errno(r, "Don't know capability '%s', ignoring: %m", *i);
×
2134
                else
2135
                        c |= UINT64_C(1) << r;
×
2136
        }
2137

2138
        return c;
×
2139
}
2140

2141
uint64_t user_record_capability_bounding_set(UserRecord *h) {
604✔
2142
        assert(h);
604✔
2143

2144
        /* Returns UINT64_MAX if no bounding set is configured (!) */
2145

2146
        if (!h->capability_bounding_set)
604✔
2147
                return UINT64_MAX;
2148

2149
        return parse_caps_strv(h->capability_bounding_set);
×
2150
}
2151

2152
uint64_t user_record_capability_ambient_set(UserRecord *h) {
604✔
2153
        assert(h);
604✔
2154

2155
        /* Returns UINT64_MAX if no ambient set is configured (!) */
2156

2157
        if (!h->capability_ambient_set)
604✔
2158
                return UINT64_MAX;
2159

2160
        return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
×
2161
}
2162

2163
int user_record_languages(UserRecord *h, char ***ret) {
604✔
2164
        _cleanup_strv_free_ char **l = NULL;
604✔
2165
        int r;
604✔
2166

2167
        assert(h);
604✔
2168
        assert(ret);
604✔
2169

2170
        if (h->preferred_language) {
604✔
2171
                l = strv_new(h->preferred_language);
×
2172
                if (!l)
×
2173
                        return -ENOMEM;
2174
        }
2175

2176
        r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true);
604✔
2177
        if (r < 0)
604✔
2178
                return r;
2179

2180
        *ret = TAKE_PTR(l);
604✔
2181
        return 0;
604✔
2182
}
2183

2184
uint32_t user_record_tmp_limit_scale(UserRecord *h) {
265✔
2185
        assert(h);
265✔
2186

2187
        if (h->tmp_limit.is_set)
265✔
2188
                return h->tmp_limit.limit_scale;
3✔
2189

2190
        /* By default grant regular users only 80% quota */
2191
        if (user_record_disposition(h) == USER_REGULAR)
262✔
2192
                return UINT32_SCALE_FROM_PERCENT(80);
210✔
2193

2194
        return UINT32_MAX;
2195
}
2196

2197
uint32_t user_record_dev_shm_limit_scale(UserRecord *h) {
265✔
2198
        assert(h);
265✔
2199

2200
        if (h->dev_shm_limit.is_set)
265✔
2201
                return h->dev_shm_limit.limit_scale;
3✔
2202

2203
        /* By default grant regular users only 80% quota */
2204
        if (user_record_disposition(h) == USER_REGULAR)
262✔
2205
                return UINT32_SCALE_FROM_PERCENT(80);
210✔
2206

2207
        return UINT32_MAX;
2208
}
2209

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

2221
                /* Basic account settings */
2222
                "shell",
2223
                "umask",
2224
                "environment",
2225
                "timeZone",
2226
                "preferredLanguage",
2227
                "additionalLanguages",
2228
                "preferredSessionLauncher",
2229
                "preferredSessionType",
2230
                "defaultArea",
2231

2232
                /* Authentication methods */
2233
                "pkcs11TokenUri",
2234
                "fido2HmacCredential",
2235
                "recoveryKeyType",
2236

2237
                "lastChangeUSec", /* Necessary to be able to change record at all */
2238
                "lastPasswordChangeUSec", /* Ditto, but for authentication methods */
2239
                NULL
2240
        };
2241

2242
        assert(h);
219✔
2243

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

2248
        /* Note that we intentionally distinguish between NULL and an empty array here */
2249
        if (h->self_modifiable_fields)
219✔
2250
                return (const char**) h->self_modifiable_fields;
2251

2252
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_fields : NULL;
217✔
2253
}
2254

2255
const char** user_record_self_modifiable_blobs(UserRecord *h) {
157✔
2256
        static const char *const default_blobs[] = {
157✔
2257
                /* For display purposes */
2258
                "avatar",
2259
                "login-background",
2260
                NULL
2261
        };
2262

2263
        assert(h);
157✔
2264

2265
        /* Note that we intentionally distinguish between NULL and an empty array here */
2266
        if (h->self_modifiable_blobs)
157✔
2267
                return (const char**) h->self_modifiable_blobs;
2268

2269
        return user_record_disposition(h) == USER_REGULAR ? (const char**) default_blobs : NULL;
157✔
2270
}
2271

2272
const char** user_record_self_modifiable_privileged(UserRecord *h) {
181✔
2273
        static const char *const default_fields[] = {
181✔
2274
                /* For display purposes */
2275
                "passwordHint",
2276

2277
                /* Authentication methods */
2278
                "hashedPassword",
2279
                "pkcs11EncryptedKey",
2280
                "fido2HmacSalt",
2281
                "recoveryKey",
2282

2283
                "sshAuthorizedKeys", /* Basically just ~/.ssh/authorized_keys */
2284
                NULL
2285
        };
2286

2287
        assert(h);
181✔
2288

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

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

2296
static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_json_variant **target) {
72✔
2297
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *blobs = NULL;
72✔
2298
        char **allowed;
72✔
2299
        int r;
72✔
2300

2301
        assert(current);
72✔
2302
        assert(target);
72✔
2303

2304
        if (!sd_json_variant_is_object(*target))
72✔
2305
                return -EINVAL;
2306

2307
        v = sd_json_variant_ref(*target);
72✔
2308

2309
        /* Handle basic fields */
2310
        allowed = (char**) user_record_self_modifiable_fields(current);
72✔
2311
        r = sd_json_variant_filter(&v, allowed);
72✔
2312
        if (r < 0)
72✔
2313
                return r;
2314

2315
        /* Handle blobs */
2316
        blobs = sd_json_variant_ref(sd_json_variant_by_key(v, "blobManifest"));
72✔
2317
        if (blobs) {
72✔
2318
                /* The blobManifest contains the sha256 hashes of the blobs,
2319
                 * which are enforced by the service managing the user. So, by
2320
                 * comparing the blob manifests like this, we're actually comparing
2321
                 * the contents of the blob directories & files */
2322

2323
                allowed = (char**) user_record_self_modifiable_blobs(current);
10✔
2324
                r = sd_json_variant_filter(&blobs, allowed);
10✔
2325
                if (r < 0)
10✔
2326
                        return r;
2327

2328
                if (sd_json_variant_is_blank_object(blobs))
10✔
2329
                        r = sd_json_variant_filter(&v, STRV_MAKE("blobManifest"));
×
2330
                else
2331
                        r = sd_json_variant_set_field(&v, "blobManifest", blobs);
10✔
2332
                if (r < 0)
10✔
2333
                        return r;
2334
        }
2335

2336
        JSON_VARIANT_REPLACE(*target, TAKE_PTR(v));
72✔
2337
        return 0;
72✔
2338
}
2339

2340
static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) {
44✔
2341
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *privileged = NULL;
44✔
2342
        sd_json_variant *per_machine;
44✔
2343
        char **allowed;
44✔
2344
        int r;
44✔
2345

2346
        assert(current);
44✔
2347
        assert(h);
44✔
2348
        assert(ret);
44✔
2349

2350
        r = user_group_record_mangle(h->json, USER_RECORD_EXTRACT_SIGNABLE|USER_RECORD_PERMISSIVE, &v, NULL);
44✔
2351
        if (r < 0)
44✔
2352
                return r;
2353

2354
        /* Handle the regular section */
2355
        r = remove_self_modifiable_json_fields_common(current, &v);
44✔
2356
        if (r < 0)
44✔
2357
                return r;
2358

2359
        /* Handle the perMachine section */
2360
        per_machine = sd_json_variant_by_key(v, "perMachine");
44✔
2361
        if (per_machine) {
44✔
2362
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_per_machine = NULL;
28✔
2363
                sd_json_variant *e;
28✔
2364

2365
                if (!sd_json_variant_is_array(per_machine))
28✔
2366
                        return -EINVAL;
2367

2368
                JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
63✔
2369
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *z = NULL;
35✔
2370

2371
                        if (!sd_json_variant_is_object(e))
35✔
2372
                                return -EINVAL;
2373

2374
                        r = per_machine_match(e, 0);
35✔
2375
                        if (r < 0)
35✔
2376
                                return r;
2377
                        if (r == 0) {
35✔
2378
                                /* It's only permissible to change anything inside of matching perMachine sections */
2379
                                r = sd_json_variant_append_array(&new_per_machine, e);
7✔
2380
                                if (r < 0)
7✔
2381
                                        return r;
2382
                                continue;
7✔
2383
                        }
2384

2385
                        z = sd_json_variant_ref(e);
28✔
2386

2387
                        r = remove_self_modifiable_json_fields_common(current, &z);
28✔
2388
                        if (r < 0)
28✔
2389
                                return r;
2390

2391
                        if (!sd_json_variant_is_blank_object(z)) {
28✔
2392
                                r = sd_json_variant_append_array(&new_per_machine, z);
28✔
2393
                                if (r < 0)
28✔
2394
                                        return r;
2395
                        }
2396
                }
2397

2398
                if (sd_json_variant_is_blank_array(new_per_machine))
28✔
2399
                        r = sd_json_variant_filter(&v, STRV_MAKE("perMachine"));
×
2400
                else
2401
                        r = sd_json_variant_set_field(&v, "perMachine", new_per_machine);
28✔
2402
                if (r < 0)
28✔
2403
                        return r;
2404
        }
2405

2406
        /* Handle the privileged section */
2407
        privileged = sd_json_variant_ref(sd_json_variant_by_key(v, "privileged"));
44✔
2408
        if (privileged) {
44✔
2409
                allowed = (char**) user_record_self_modifiable_privileged(current);
34✔
2410
                r = sd_json_variant_filter(&privileged, allowed);
34✔
2411
                if (r < 0)
34✔
2412
                        return r;
2413

2414
                if (sd_json_variant_is_blank_object(privileged))
34✔
2415
                        r = sd_json_variant_filter(&v, STRV_MAKE("privileged"));
32✔
2416
                else
2417
                        r = sd_json_variant_set_field(&v, "privileged", privileged);
2✔
2418
                if (r < 0)
34✔
2419
                        return r;
2420
        }
2421

2422
        JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
44✔
2423
        return 0;
44✔
2424
}
2425

2426
int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) {
22✔
2427
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *vc = NULL, *vi = NULL;
44✔
2428
        int r;
22✔
2429

2430
        assert(current);
22✔
2431
        assert(incoming);
22✔
2432

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

2438
        r = remove_self_modifiable_json_fields(current, current, &vc);
22✔
2439
        if (r < 0)
22✔
2440
                return r;
2441

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

2459
        return sd_json_variant_equal(vc, vi);
22✔
2460
}
2461

2462
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
304✔
2463
        assert(h);
304✔
2464

2465
        /* Calculates when the it's possible to login next. Returns:
2466
         *
2467
         * UINT64_MAX → Nothing known
2468
         * 0          → Right away
2469
         * Any other  → Next time in CLOCK_REALTIME in usec (which could be in the past)
2470
         */
2471

2472
        if (h->ratelimit_begin_usec == UINT64_MAX ||
304✔
2473
            h->ratelimit_count == UINT64_MAX)
183✔
2474
                return UINT64_MAX;
2475

2476
        if (h->ratelimit_begin_usec > now(CLOCK_REALTIME)) /* If the ratelimit time is in the future, then
183✔
2477
                                                            * the local clock is probably incorrect. Let's
2478
                                                            * not refuse login then. */
2479
                return UINT64_MAX;
2480

2481
        if (h->ratelimit_count < user_record_ratelimit_burst(h))
183✔
2482
                return 0;
2483

2484
        return usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h));
×
2485
}
2486

2487
bool user_record_equal(UserRecord *a, UserRecord *b) {
149✔
2488
        assert(a);
149✔
2489
        assert(b);
149✔
2490

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

2494
        return sd_json_variant_equal(a->json, b->json);
149✔
2495
}
2496

2497
bool user_record_compatible(UserRecord *a, UserRecord *b) {
133✔
2498
        assert(a);
133✔
2499
        assert(b);
133✔
2500

2501
        /* If either lacks the regular section, we can't really decide, let's hence say they are
2502
         * incompatible. */
2503
        if (!(a->mask & b->mask & USER_RECORD_REGULAR))
133✔
2504
                return false;
2505

2506
        return streq_ptr(a->user_name, b->user_name) &&
133✔
2507
                streq_ptr(a->realm, b->realm);
133✔
2508
}
2509

2510
int user_record_compare_last_change(UserRecord *a, UserRecord *b) {
16✔
2511
        assert(a);
16✔
2512
        assert(b);
16✔
2513

2514
        if (a->last_change_usec == b->last_change_usec)
16✔
2515
                return 0;
2516

2517
        /* Always consider a record with a timestamp newer than one without */
2518
        if (a->last_change_usec == UINT64_MAX)
16✔
2519
                return -1;
2520
        if (b->last_change_usec == UINT64_MAX)
16✔
2521
                return 1;
2522

2523
        return CMP(a->last_change_usec, b->last_change_usec);
16✔
2524
}
2525

2526
int user_record_clone(UserRecord *h, UserRecordLoadFlags flags, UserRecord **ret) {
3,997✔
2527
        _cleanup_(user_record_unrefp) UserRecord *c = NULL;
3,997✔
2528
        int r;
3,997✔
2529

2530
        assert(h);
3,997✔
2531
        assert(ret);
3,997✔
2532

2533
        c = user_record_new();
3,997✔
2534
        if (!c)
3,997✔
2535
                return -ENOMEM;
2536

2537
        r = user_record_load(c, h->json, flags);
3,997✔
2538
        if (r < 0)
3,997✔
2539
                return r;
2540

2541
        *ret = TAKE_PTR(c);
3,997✔
2542
        return 0;
3,997✔
2543
}
2544

2545
int user_record_masked_equal(UserRecord *a, UserRecord *b, UserRecordMask mask) {
24✔
2546
        _cleanup_(user_record_unrefp) UserRecord *x = NULL, *y = NULL;
24✔
2547
        int r;
24✔
2548

2549
        assert(a);
24✔
2550
        assert(b);
24✔
2551

2552
        /* Compares the two records, but ignores anything not listed in the specified mask */
2553

2554
        if ((a->mask & ~mask) != 0) {
24✔
2555
                r = user_record_clone(a, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &x);
24✔
2556
                if (r < 0)
24✔
2557
                        return r;
2558

2559
                a = x;
24✔
2560
        }
2561

2562
        if ((b->mask & ~mask) != 0) {
24✔
2563
                r = user_record_clone(b, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX) | USER_RECORD_PERMISSIVE, &y);
24✔
2564
                if (r < 0)
24✔
2565
                        return r;
2566

2567
                b = y;
24✔
2568
        }
2569

2570
        return user_record_equal(a, b);
24✔
2571
}
2572

2573
int user_record_test_blocked(UserRecord *h) {
197✔
2574
        usec_t n;
197✔
2575

2576
        /* Checks whether access to the specified user shall be allowed at the moment. Returns:
2577
         *
2578
         *          -ESTALE: Record is from the future
2579
         *          -ENOLCK: Record is blocked
2580
         *          -EL2HLT: Record is not valid yet
2581
         *          -EL3HLT: Record is not valid anymore
2582
         *
2583
         */
2584

2585
        assert(h);
197✔
2586

2587
        if (h->locked > 0)
197✔
2588
                return -ENOLCK;
2589

2590
        n = now(CLOCK_REALTIME);
168✔
2591

2592
        if (h->not_before_usec != UINT64_MAX && n < h->not_before_usec)
168✔
2593
                return -EL2HLT;
2594
        if (h->not_after_usec != UINT64_MAX && n > h->not_after_usec)
168✔
2595
                return -EL3HLT;
2596

2597
        if (h->last_change_usec != UINT64_MAX &&
168✔
2598
            h->last_change_usec > n) /* Complain during log-ins when the record is from the future */
2599
                return -ESTALE;
×
2600

2601
        return 0;
2602
}
2603

2604
int user_record_test_password_change_required(UserRecord *h) {
199✔
2605
        bool change_permitted;
199✔
2606
        usec_t n;
199✔
2607

2608
        assert(h);
199✔
2609

2610
        /* Checks whether the user must change the password when logging in
2611

2612
            -EKEYREVOKED: Change password now because admin said so
2613
             -EOWNERDEAD: Change password now because it expired
2614
           -EKEYREJECTED: Password is expired, no changing is allowed
2615
            -EKEYEXPIRED: Password is about to expire, warn user
2616
               -ENETDOWN: Record has expiration info but no password change timestamp
2617
                  -EROFS: No password change required nor permitted
2618
                 -ESTALE: RTC likely incorrect, last password change is in the future
2619
                       0: No password change required, but permitted
2620
         */
2621

2622
        /* If a password change request has been set explicitly, it overrides everything */
2623
        if (h->password_change_now > 0)
199✔
2624
                return -EKEYREVOKED;
2625

2626
        n = now(CLOCK_REALTIME);
199✔
2627

2628
        /* Password change in the future? Then our RTC is likely incorrect */
2629
        if (h->last_password_change_usec != UINT64_MAX &&
199✔
2630
            h->last_password_change_usec > n &&
×
2631
            (h->password_change_min_usec != UINT64_MAX ||
×
2632
             h->password_change_max_usec != UINT64_MAX ||
×
2633
             h->password_change_inactive_usec != UINT64_MAX))
×
2634
            return -ESTALE;
2635

2636
        /* Then, let's check if password changing is currently allowed at all */
2637
        if (h->password_change_min_usec != UINT64_MAX) {
199✔
2638

2639
                /* Expiry configured but no password change timestamp known? */
2640
                if (h->last_password_change_usec == UINT64_MAX)
×
2641
                        return -ENETDOWN;
2642

2643
                if (h->password_change_min_usec >= UINT64_MAX - h->last_password_change_usec)
×
2644
                        change_permitted = false;
2645
                else
2646
                        change_permitted = n >= h->last_password_change_usec + h->password_change_min_usec;
×
2647

2648
        } else
2649
                change_permitted = true;
2650

2651
        /* Let's check whether the password has expired.  */
2652
        if (!(h->password_change_max_usec == UINT64_MAX ||
199✔
2653
              h->password_change_max_usec >= UINT64_MAX - h->last_password_change_usec)) {
×
2654

2655
                uint64_t change_before;
×
2656

2657
                /* Expiry configured but no password change timestamp known? */
2658
                if (h->last_password_change_usec == UINT64_MAX)
×
2659
                        return -ENETDOWN;
2660

2661
                /* Password is in inactive phase? */
2662
                if (h->password_change_inactive_usec != UINT64_MAX &&
×
2663
                    h->password_change_inactive_usec < UINT64_MAX - h->password_change_max_usec) {
×
2664
                        usec_t added;
×
2665

2666
                        added = h->password_change_inactive_usec + h->password_change_max_usec;
×
2667
                        if (added < UINT64_MAX - h->last_password_change_usec &&
×
2668
                            n >= h->last_password_change_usec + added)
×
2669
                                return -EKEYREJECTED;
2670
                }
2671

2672
                /* Password needs to be changed now? */
2673
                change_before = h->last_password_change_usec + h->password_change_max_usec;
×
2674
                if (n >= change_before)
×
2675
                        return change_permitted ? -EOWNERDEAD : -EKEYREJECTED;
×
2676

2677
                /* Warn user? */
2678
                if (h->password_change_warn_usec != UINT64_MAX &&
×
2679
                    (change_before < h->password_change_warn_usec ||
×
2680
                     n >= change_before - h->password_change_warn_usec))
×
2681
                        return change_permitted ? -EKEYEXPIRED : -EROFS;
×
2682
        }
2683

2684
        /* No password changing necessary */
2685
        return change_permitted ? 0 : -EROFS;
199✔
2686
}
2687

2688
bool user_record_is_root(const UserRecord *u) {
4,790✔
2689
        assert(u);
4,790✔
2690

2691
        return u->uid == 0 || streq_ptr(u->user_name, "root");
4,790✔
2692
}
2693

2694
bool user_record_is_nobody(const UserRecord *u) {
2,533✔
2695
        assert(u);
2,533✔
2696

2697
        return u->uid == UID_NOBODY || STRPTR_IN_SET(u->user_name, NOBODY_USER_NAME, "nobody");
2,533✔
2698
}
2699

2700
bool user_record_matches_user_name(const UserRecord *u, const char *user_name) {
5,471✔
2701
        assert(u);
5,471✔
2702
        assert(user_name);
5,471✔
2703

2704
        if (streq_ptr(u->user_name, user_name))
5,471✔
2705
                return true;
2706

2707
        if (streq_ptr(u->user_name_and_realm_auto, user_name))
27✔
2708
                return true;
2709

2710
        if (strv_contains(u->aliases, user_name))
21✔
2711
                return true;
2712

2713
        const char *realm = strrchr(user_name, '@');
15✔
2714
        if (realm && streq_ptr(realm+1, u->realm))
15✔
2715
                STRV_FOREACH(a, u->aliases)
18✔
2716
                        if (startswith(user_name, *a) == realm)
18✔
2717
                                return true;
2718

2719
        return false;
2720
}
2721

2722
int suitable_blob_filename(const char *name) {
429✔
2723
        /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
2724
        return filename_is_valid(name) &&
858✔
2725
               in_charset(name, URI_UNRESERVED) &&
429✔
2726
               name[0] != '.';
423✔
2727
}
2728

2729
bool userdb_match_is_set(const UserDBMatch *match) {
52,779✔
2730
        if (!match)
52,779✔
2731
                return false;
2732

2733
        return !strv_isempty(match->fuzzy_names) ||
33,900✔
2734
                !FLAGS_SET(match->disposition_mask, USER_DISPOSITION_MASK_ALL) ||
33,892✔
2735
                match->uid_min > 0 ||
25,499✔
2736
                match->uid_max < UID_INVALID-1 ||
51,412✔
2737
                !sd_id128_is_null(match->uuid);
17,512✔
2738
}
2739

2740
void userdb_match_done(UserDBMatch *match) {
24,913✔
2741
        assert(match);
24,913✔
2742
        strv_free(match->fuzzy_names);
24,913✔
2743
}
24,913✔
2744

2745
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches) {
6✔
2746
        assert(names || n_names == 0);
6✔
2747

2748
        /* Checks if any of the user record strings in the names[] array matches any of the search strings in
2749
         * the matches** strv fuzzily. */
2750

2751
        FOREACH_ARRAY(n, names, n_names) {
22✔
2752
                if (!*n)
16✔
2753
                        continue;
8✔
2754

2755
                _cleanup_free_ char *lcn = strdup(*n);
8✔
2756
                if (!lcn)
8✔
2757
                        return -ENOMEM;
2758

2759
                ascii_strlower(lcn);
8✔
2760

2761
                STRV_FOREACH(i, matches) {
16✔
2762
                        _cleanup_free_ char *lc = strdup(*i);
8✔
2763
                        if (!lc)
8✔
2764
                                return -ENOMEM;
2765

2766
                        ascii_strlower(lc);
8✔
2767

2768
                        /* First do substring check */
2769
                        if (strstr(lcn, lc))
8✔
2770
                                return true;
2771

2772
                        /* Then do some fuzzy string comparison (but only if the needle is non-trivially long) */
2773
                        if (strlen(lc) >= 5 && strlevenshtein(lcn, lc) < 3)
8✔
2774
                                return true;
2775
                }
2776
        }
2777

2778
        return false;
2779
}
2780

2781
bool user_record_match(UserRecord *u, const UserDBMatch *match) {
9,752✔
2782
        assert(u);
9,752✔
2783

2784
        if (!match)
9,752✔
2785
                return true;
2786

2787
        if (!uid_is_valid(u->uid))
7,143✔
2788
                return false;
2789

2790
        if (u->uid < match->uid_min || u->uid > match->uid_max)
7,143✔
2791
                return false;
2792

2793
        if (!BIT_SET(match->disposition_mask, user_record_disposition(u)))
7,141✔
2794
                return false;
2795

2796
        if (!sd_id128_is_null(match->uuid) && !sd_id128_equal(match->uuid, u->uuid))
14,102✔
2797
                return false;
×
2798

2799
        if (!strv_isempty(match->fuzzy_names)) {
7,051✔
2800

2801
                /* Note this array of names is sparse, i.e. various entries listed in it will be
2802
                 * NULL. Because of that we are not using a NULL terminated strv here, but a regular
2803
                 * array. */
2804
                const char* names[] = {
4✔
2805
                        u->user_name,
2✔
2806
                        user_record_user_name_and_realm(u),
2✔
2807
                        u->real_name,
2✔
2808
                        u->email_address,
2✔
2809
                        u->cifs_user_name,
2✔
2810
                };
2811

2812
                if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) &&
4✔
2813
                    !user_name_fuzzy_match((const char**) u->aliases, strv_length(u->aliases), match->fuzzy_names))
2✔
2814
                        return false;
2✔
2815
        }
2816

2817
        return true;
2818
}
2819

UNCOV
2820
int json_dispatch_dispositions_mask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
×
UNCOV
2821
        uint64_t *mask = ASSERT_PTR(userdata);
×
2822

UNCOV
2823
        if (sd_json_variant_is_null(variant)) {
×
2824
                *mask = UINT64_MAX;
×
2825
                return 0;
×
2826
        }
2827

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

2831
        uint64_t m = 0;
UNCOV
2832
        for (size_t i = 0; i < sd_json_variant_elements(variant); i++) {
×
UNCOV
2833
                sd_json_variant *e;
×
UNCOV
2834
                const char *a;
×
2835

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

UNCOV
2840
                assert_se(a = sd_json_variant_string(e));
×
2841

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

UNCOV
2846
                m |= INDEX_TO_MASK(uint64_t, d);
×
2847
        }
2848

UNCOV
2849
        *mask = m;
×
UNCOV
2850
        return 0;
×
2851
}
2852

2853
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
2854
        [USER_CLASSIC]   = "classic",
2855
        [USER_LUKS]      = "luks",
2856
        [USER_DIRECTORY] = "directory",
2857
        [USER_SUBVOLUME] = "subvolume",
2858
        [USER_FSCRYPT]   = "fscrypt",
2859
        [USER_CIFS]      = "cifs",
2860
};
2861

2862
DEFINE_STRING_TABLE_LOOKUP(user_storage, UserStorage);
3,130✔
2863

2864
static const char* const user_disposition_table[_USER_DISPOSITION_MAX] = {
2865
        [USER_INTRINSIC] = "intrinsic",
2866
        [USER_SYSTEM]    = "system",
2867
        [USER_DYNAMIC]   = "dynamic",
2868
        [USER_REGULAR]   = "regular",
2869
        [USER_CONTAINER] = "container",
2870
        [USER_FOREIGN]   = "foreign",
2871
        [USER_RESERVED]  = "reserved",
2872
};
2873

2874
DEFINE_STRING_TABLE_LOOKUP(user_disposition, UserDisposition);
73,837✔
2875

2876
static const char* const auto_resize_mode_table[_AUTO_RESIZE_MODE_MAX] = {
2877
        [AUTO_RESIZE_OFF]             = "off",
2878
        [AUTO_RESIZE_GROW]            = "grow",
2879
        [AUTO_RESIZE_SHRINK_AND_GROW] = "shrink-and-grow",
2880
};
2881

2882
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