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

systemd / systemd / 14526975710

17 Apr 2025 09:06PM UTC coverage: 72.13% (+0.01%) from 72.117%
14526975710

push

github

yuwata
rules: Make ADB and fastboot work out-of-the-box

https://android.googlesource.com/platform/packages/modules/adb/+/d0db47dcd/adb.h#199
https://android.googlesource.com/platform/system/core/+/7199051aa/fastboot/fastboot.cpp#244

297093 of 411885 relevant lines covered (72.13%)

687643.53 hits per line

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

72.47
/src/sysusers/sysusers.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <getopt.h>
4

5
#include "alloc-util.h"
6
#include "audit-util.h"
7
#include "build.h"
8
#include "chase.h"
9
#include "conf-files.h"
10
#include "constants.h"
11
#include "copy.h"
12
#include "creds-util.h"
13
#include "dissect-image.h"
14
#include "env-util.h"
15
#include "fd-util.h"
16
#include "fileio.h"
17
#include "format-util.h"
18
#include "fs-util.h"
19
#include "hashmap.h"
20
#include "libcrypt-util.h"
21
#include "main-func.h"
22
#include "memory-util.h"
23
#include "mount-util.h"
24
#include "pager.h"
25
#include "parse-argument.h"
26
#include "path-util.h"
27
#include "pretty-print.h"
28
#include "selinux-util.h"
29
#include "set.h"
30
#include "smack-util.h"
31
#include "specifier.h"
32
#include "stat-util.h"
33
#include "string-util.h"
34
#include "strv.h"
35
#include "sync-util.h"
36
#include "tmpfile-util-label.h"
37
#include "uid-classification.h"
38
#include "uid-range.h"
39
#include "user-util.h"
40
#include "utf8.h"
41
#include "verbs.h"
42

43
typedef enum ItemType {
44
        ADD_USER =   'u',
45
        ADD_GROUP =  'g',
46
        ADD_MEMBER = 'm',
47
        ADD_RANGE =  'r',
48
} ItemType;
49

50
static const char* item_type_to_string(ItemType t) {
×
51
        switch (t) {
×
52
        case ADD_USER:
53
                return "user";
54
        case ADD_GROUP:
×
55
                return "group";
×
56
        case ADD_MEMBER:
×
57
                return "member";
×
58
        case ADD_RANGE:
×
59
                return "range";
×
60
        default:
×
61
                assert_not_reached();
×
62
        }
63
}
64

65
typedef struct Item {
66
        ItemType type;
67

68
        char *name;
69
        char *group_name;
70
        char *uid_path;
71
        char *gid_path;
72
        char *description;
73
        char *home;
74
        char *shell;
75

76
        gid_t gid;
77
        uid_t uid;
78

79
        char *filename;
80
        unsigned line;
81

82
        bool gid_set;
83

84
        /* When set the group with the specified GID must exist
85
         * and the check if a UID clashes with the GID is skipped.
86
         */
87
        bool id_set_strict;
88

89
        bool uid_set;
90

91
        bool locked;
92

93
        bool todo_user;
94
        bool todo_group;
95
} Item;
96

97
static char *arg_root = NULL;
98
static char *arg_image = NULL;
99
static CatFlags arg_cat_flags = CAT_CONFIG_OFF;
100
static const char *arg_replace = NULL;
101
static bool arg_dry_run = false;
102
static bool arg_inline = false;
103
static PagerFlags arg_pager_flags = 0;
104
static ImagePolicy *arg_image_policy = NULL;
105

106
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
113✔
107
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
113✔
108
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
113✔
109

110
typedef struct Context {
111
        int audit_fd;
112

113
        OrderedHashmap *users, *groups;
114
        OrderedHashmap *todo_uids, *todo_gids;
115
        OrderedHashmap *members;
116

117
        Hashmap *database_by_uid, *database_by_username;
118
        Hashmap *database_by_gid, *database_by_groupname;
119

120
        /* A helper set to hold names that are used by database_by_{uid,gid,username,groupname} above. */
121
        Set *names;
122

123
        uid_t search_uid;
124
        UIDRange *uid_range;
125

126
        UGIDAllocationRange login_defs;
127
        bool login_defs_need_warning;
128
} Context;
129

130
static void context_done(Context *c) {
113✔
131
        assert(c);
113✔
132

133
        c->audit_fd = close_audit_fd(c->audit_fd);
113✔
134

135
        ordered_hashmap_free(c->groups);
113✔
136
        ordered_hashmap_free(c->users);
113✔
137
        ordered_hashmap_free(c->members);
113✔
138
        ordered_hashmap_free(c->todo_uids);
113✔
139
        ordered_hashmap_free(c->todo_gids);
113✔
140

141
        hashmap_free(c->database_by_uid);
113✔
142
        hashmap_free(c->database_by_username);
113✔
143
        hashmap_free(c->database_by_gid);
113✔
144
        hashmap_free(c->database_by_groupname);
113✔
145

146
        set_free(c->names);
113✔
147
        uid_range_free(c->uid_range);
113✔
148
}
113✔
149

150
static void maybe_emit_login_defs_warning(Context *c) {
45✔
151
        assert(c);
45✔
152

153
        if (!c->login_defs_need_warning)
45✔
154
                return;
155

156
        if (c->login_defs.system_alloc_uid_min != SYSTEM_ALLOC_UID_MIN ||
44✔
157
            c->login_defs.system_uid_max != SYSTEM_UID_MAX)
158
                log_warning("login.defs specifies UID allocation range "UID_FMT"–"UID_FMT
×
159
                            " that is different than the built-in defaults ("UID_FMT"–"UID_FMT")",
160
                            c->login_defs.system_alloc_uid_min, c->login_defs.system_uid_max,
161
                            (uid_t) SYSTEM_ALLOC_UID_MIN, (uid_t) SYSTEM_UID_MAX);
162
        if (c->login_defs.system_alloc_gid_min != SYSTEM_ALLOC_GID_MIN ||
44✔
163
            c->login_defs.system_gid_max != SYSTEM_GID_MAX)
164
                log_warning("login.defs specifies GID allocation range "GID_FMT"–"GID_FMT
×
165
                            " that is different than the built-in defaults ("GID_FMT"–"GID_FMT")",
166
                            c->login_defs.system_alloc_gid_min, c->login_defs.system_gid_max,
167
                            (gid_t) SYSTEM_ALLOC_GID_MIN, (gid_t) SYSTEM_GID_MAX);
168

169
        c->login_defs_need_warning = false;
44✔
170
}
171

172
static void log_audit_accounts(Context *c, ItemType what) {
226✔
173
#if HAVE_AUDIT
174
        assert(c);
226✔
175
        assert(IN_SET(what, ADD_USER, ADD_GROUP));
226✔
176

177
        if (arg_dry_run || c->audit_fd < 0)
226✔
178
                return;
226✔
179

180
        Item *i;
×
181
        int type = what == ADD_USER ? AUDIT_ADD_USER : AUDIT_ADD_GROUP;
×
182
        const char *op = what == ADD_USER ? "adding-user" : "adding-group";
×
183

184
        /* Notes:
185
         *
186
         * The op must not contain whitespace. The format with a dash matches what Fedora shadow-utils uses.
187
         *
188
         * We send id == -1, even though we know the number, in particular on success. This is because if we
189
         * send the id, the generated audit message will not contain the name. The name seems more useful
190
         * than the number, hence send just the name:
191
         *
192
         * type=ADD_USER msg=audit(01/10/2025 16:02:00.639:3854) :
193
         *   pid=3846380 uid=root auid=zbyszek ses=2 msg='op=adding-user id=unknown(952) exe=systemd-sysusers ... res=success'
194
         * vs.
195
         * type=ADD_USER msg=audit(01/10/2025 16:03:15.457:3908) :
196
         *   pid=3846607 uid=root auid=zbyszek ses=2 msg='op=adding-user acct=foo5 exe=systemd-sysusers ... res=success'
197
         */
198

199
        ORDERED_HASHMAP_FOREACH(i, what == ADD_USER ? c->todo_uids : c->todo_gids)
×
200
                audit_log_acct_message(
×
201
                                c->audit_fd,
202
                                type,
203
                                program_invocation_short_name,
204
                                op,
205
                                i->name,
×
206
                                /* id= */ (unsigned) -1,
207
                                /* host= */ NULL,
208
                                /* addr= */ NULL,
209
                                /* tty= */ NULL,
210
                                /* success= */ 1);
211
#endif
212
}
213

214
static int load_user_database(Context *c) {
113✔
215
        _cleanup_free_ char *passwd_path = NULL;
113✔
216
        _cleanup_fclose_ FILE *f = NULL;
113✔
217
        struct passwd *pw;
113✔
218
        int r;
113✔
219

220
        assert(c);
113✔
221

222
        r = chase_and_fopen_unlocked("/etc/passwd", arg_root, CHASE_PREFIX_ROOT, "re", &passwd_path, &f);
113✔
223
        if (r == -ENOENT)
113✔
224
                return 0;
225
        if (r < 0)
37✔
226
                return r;
227

228
        while ((r = fgetpwent_sane(f, &pw)) > 0) {
254✔
229

230
                char *n = strdup(pw->pw_name);
217✔
231
                if (!n)
217✔
232
                        return -ENOMEM;
233

234
                /* Note that we use trivial_hash_ops_free here, so identical strings can exist in the set. */
235
                r = set_ensure_consume(&c->names, &trivial_hash_ops_free, n);
217✔
236
                if (r < 0)
217✔
237
                        return r;
238
                assert(r > 0);  /* The set uses pointer comparisons, so n must not be in the set. */
217✔
239

240
                r = hashmap_ensure_put(&c->database_by_username, &string_hash_ops, n, UID_TO_PTR(pw->pw_uid));
217✔
241
                if (r == -EEXIST)
217✔
242
                        log_debug_errno(r, "%s: user '%s' is listed twice, ignoring duplicate uid.",
×
243
                                        passwd_path, n);
244
                else if (r < 0)
217✔
245
                        return r;
246

247
                r = hashmap_ensure_put(&c->database_by_uid, /* hash_ops= */ NULL, UID_TO_PTR(pw->pw_uid), n);
217✔
248
                if (r == -EEXIST)
217✔
249
                        log_debug_errno(r, "%s: uid "UID_FMT" is listed twice, ignoring duplicate name.",
260✔
250
                                        passwd_path, pw->pw_uid);
251
                else if (r < 0)
211✔
252
                        return r;
253
        }
254
        return r;
255
}
256

257
static int load_group_database(Context *c) {
113✔
258
        _cleanup_free_ char *group_path = NULL;
113✔
259
        _cleanup_fclose_ FILE *f = NULL;
113✔
260
        struct group *gr;
113✔
261
        int r;
113✔
262

263
        assert(c);
113✔
264

265
        r = chase_and_fopen_unlocked("/etc/group", arg_root, CHASE_PREFIX_ROOT, "re", &group_path, &f);
113✔
266
        if (r == -ENOENT)
113✔
267
                return 0;
268
        if (r < 0)
37✔
269
                return r;
270

271
        while ((r = fgetgrent_sane(f, &gr)) > 0) {
457✔
272
                char *n = strdup(gr->gr_name);
420✔
273
                if (!n)
420✔
274
                        return -ENOMEM;
275

276
                /* Note that we use trivial_hash_ops_free here, so identical strings can exist in the set. */
277
                r = set_ensure_consume(&c->names, &trivial_hash_ops_free, n);
420✔
278
                if (r < 0)
420✔
279
                        return r;
280
                assert(r > 0);  /* The set uses pointer comparisons, so n must not be in the set. */
420✔
281

282
                r = hashmap_ensure_put(&c->database_by_groupname, &string_hash_ops, n, GID_TO_PTR(gr->gr_gid));
420✔
283
                if (r == -EEXIST)
420✔
284
                        log_debug_errno(r, "%s: group '%s' is listed twice, ignoring duplicate gid.",
×
285
                                        group_path, n);
286
                else if (r < 0)
420✔
287
                        return r;
288

289
                r = hashmap_ensure_put(&c->database_by_gid, /* hash_ops= */ NULL, GID_TO_PTR(gr->gr_gid), n);
420✔
290
                if (r == -EEXIST)
420✔
291
                        log_debug_errno(r, "%s: gid "GID_FMT" is listed twice, ignoring duplicate name.",
469✔
292
                                        group_path, gr->gr_gid);
293
                else if (r < 0)
408✔
294
                        return r;
295
        }
296
        return r;
297
}
298

299
static int make_backup(const char *target, const char *x) {
348✔
300
        _cleanup_(unlink_and_freep) char *dst_tmp = NULL;
×
301
        _cleanup_fclose_ FILE *dst = NULL;
348✔
302
        _cleanup_close_ int src = -EBADF;
348✔
303
        const char *backup;
348✔
304
        struct stat st;
348✔
305
        int r;
348✔
306

307
        assert(target);
348✔
308
        assert(x);
348✔
309

310
        src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
348✔
311
        if (src < 0) {
348✔
312
                if (errno == ENOENT) /* No backup necessary... */
312✔
313
                        return 0;
314

315
                return -errno;
×
316
        }
317

318
        if (fstat(src, &st) < 0)
36✔
319
                return -errno;
×
320

321
        r = fopen_temporary_label(
36✔
322
                        target,   /* The path for which to the look up the label */
323
                        x,        /* Where we want the file actually to end up */
324
                        &dst,     /* The temporary file we write to */
325
                        &dst_tmp);
326
        if (r < 0)
36✔
327
                return r;
328

329
        r = copy_bytes(src, fileno(dst), UINT64_MAX, COPY_REFLINK);
36✔
330
        if (r < 0)
36✔
331
                return r;
332

333
        backup = strjoina(x, "-");
180✔
334

335
        /* Copy over the access mask. Don't fail on chmod() or chown(). If it stays owned by us and/or
336
         * unreadable by others, then it isn't too bad... */
337
        r = fchmod_and_chown_with_fallback(fileno(dst), dst_tmp, st.st_mode & 07777, st.st_uid, st.st_gid);
36✔
338
        if (r < 0)
36✔
339
                log_warning_errno(r, "Failed to change access mode or ownership of %s: %m", backup);
×
340

341
        if (futimens(fileno(dst), (const struct timespec[2]) { st.st_atim, st.st_mtim }) < 0)
36✔
342
                log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup);
×
343

344
        r = fsync_full(fileno(dst));
36✔
345
        if (r < 0)
36✔
346
                return r;
347

348
        if (rename(dst_tmp, backup) < 0)
36✔
349
                return errno;
×
350

351
        dst_tmp = mfree(dst_tmp); /* disable the unlink_and_freep() hook now that the file has been renamed */
36✔
352
        return 0;
36✔
353
}
354

355
static int putgrent_with_members(
753✔
356
                Context *c,
357
                const struct group *gr,
358
                FILE *group) {
359

360
        char **a;
753✔
361
        int r;
753✔
362

363
        assert(c);
753✔
364
        assert(gr);
753✔
365
        assert(group);
753✔
366

367
        a = ordered_hashmap_get(c->members, gr->gr_name);
753✔
368
        if (a) {
753✔
369
                _cleanup_strv_free_ char **l = NULL;
10✔
370
                bool added = false;
10✔
371

372
                l = strv_copy(gr->gr_mem);
10✔
373
                if (!l)
10✔
374
                        return -ENOMEM;
375

376
                STRV_FOREACH(i, a) {
20✔
377
                        if (strv_contains(l, *i))
10✔
378
                                continue;
5✔
379

380
                        r = strv_extend(&l, *i);
5✔
381
                        if (r < 0)
5✔
382
                                return r;
383

384
                        added = true;
385
                }
386

387
                if (added) {
10✔
388
                        struct group t;
5✔
389

390
                        strv_sort_uniq(l);
5✔
391

392
                        t = *gr;
5✔
393
                        t.gr_mem = l;
5✔
394

395
                        r = putgrent_sane(&t, group);
5✔
396
                        return r < 0 ? r : 1;
10✔
397
                }
398
        }
399

400
        return putgrent_sane(gr, group);
748✔
401
}
402

403
#if ENABLE_GSHADOW
404
static int putsgent_with_members(
743✔
405
                Context *c,
406
                const struct sgrp *sg,
407
                FILE *gshadow) {
408

409
        char **a;
743✔
410
        int r;
743✔
411

412
        assert(sg);
743✔
413
        assert(gshadow);
743✔
414

415
        a = ordered_hashmap_get(c->members, sg->sg_namp);
743✔
416
        if (a) {
743✔
417
                _cleanup_strv_free_ char **l = NULL;
10✔
418
                bool added = false;
10✔
419

420
                l = strv_copy(sg->sg_mem);
10✔
421
                if (!l)
10✔
422
                        return -ENOMEM;
423

424
                STRV_FOREACH(i, a) {
20✔
425
                        if (strv_contains(l, *i))
10✔
426
                                continue;
5✔
427

428
                        r = strv_extend(&l, *i);
5✔
429
                        if (r < 0)
5✔
430
                                return r;
431

432
                        added = true;
433
                }
434

435
                if (added) {
10✔
436
                        struct sgrp t;
5✔
437

438
                        strv_sort_uniq(l);
5✔
439

440
                        t = *sg;
5✔
441
                        t.sg_mem = l;
5✔
442

443
                        r = putsgent_sane(&t, gshadow);
5✔
444
                        return r < 0 ? r : 1;
10✔
445
                }
446
        }
447

448
        return putsgent_sane(sg, gshadow);
738✔
449
}
450
#endif
451

452
static const char* pick_shell(const Item *i) {
270✔
453
        assert(i);
270✔
454

455
        if (i->type != ADD_USER)
270✔
456
                return NULL;
457
        if (i->shell)
268✔
458
                return i->shell;
459
        if (i->uid_set && i->uid == 0)
248✔
460
                return default_root_shell(arg_root);
10✔
461
        return NOLOGIN;
462
}
463

464
static int write_temporary_passwd(
113✔
465
                Context *c,
466
                const char *passwd_path,
467
                FILE **ret_tmpfile,
468
                char **ret_tmpfile_path) {
469

470
        _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
113✔
471
        _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
113✔
472
        struct passwd *pw = NULL;
113✔
473
        Item *i;
113✔
474
        int r;
113✔
475

476
        assert(c);
113✔
477

478
        if (ordered_hashmap_isempty(c->todo_uids))
113✔
479
                return 0;
480

481
        if (arg_dry_run) {
87✔
482
                log_info("Would write /etc/passwd%s", glyph(GLYPH_ELLIPSIS));
×
483
                return 0;
×
484
        }
485

486
        r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
87✔
487
        if (r < 0)
87✔
488
                return log_debug_errno(r, "Failed to open temporary copy of %s: %m", passwd_path);
×
489

490
        original = fopen(passwd_path, "re");
87✔
491
        if (original) {
87✔
492

493
                /* Allow fallback path for when /proc is not mounted. On any normal system /proc will be
494
                 * mounted, but e.g. when 'dnf --installroot' is used, it might not be. There is no security
495
                 * relevance here, since the environment is ultimately trusted, and not requiring /proc makes
496
                 * it easier to depend on sysusers in packaging scripts and suchlike. */
497
                r = copy_rights_with_fallback(fileno(original), fileno(passwd), passwd_tmp);
14✔
498
                if (r < 0)
14✔
499
                        return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
×
500
                                               passwd_path, passwd_tmp);
501

502
                while ((r = fgetpwent_sane(original, &pw)) > 0) {
138✔
503
                        i = ordered_hashmap_get(c->users, pw->pw_name);
129✔
504
                        if (i && i->todo_user)
129✔
505
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
506
                                                       "%s: User \"%s\" already exists.",
507
                                                       passwd_path, pw->pw_name);
508

509
                        if (ordered_hashmap_contains(c->todo_uids, UID_TO_PTR(pw->pw_uid)))
129✔
510
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
511
                                                       "%s: Detected collision for UID " UID_FMT ".",
512
                                                       passwd_path, pw->pw_uid);
513

514
                        /* Make sure we keep the NIS entries (if any) at the end. */
515
                        if (IN_SET(pw->pw_name[0], '+', '-'))
129✔
516
                                break;
517

518
                        r = putpwent_sane(pw, passwd);
124✔
519
                        if (r < 0)
124✔
520
                                return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary passwd file: %m",
×
521
                                                       pw->pw_name);
522
                }
523
                if (r < 0)
14✔
524
                        return log_debug_errno(r, "Failed to read %s: %m", passwd_path);
×
525

526
        } else {
527
                if (errno != ENOENT)
73✔
528
                        return log_debug_errno(errno, "Failed to open %s: %m", passwd_path);
×
529
                if (fchmod(fileno(passwd), 0644) < 0)
73✔
530
                        return log_debug_errno(errno, "Failed to fchmod %s: %m", passwd_tmp);
×
531
        }
532

533
        ORDERED_HASHMAP_FOREACH(i, c->todo_uids) {
355✔
534
                _cleanup_free_ char *creds_shell = NULL, *cn = NULL;
268✔
535

536
                struct passwd n = {
1,072✔
537
                        .pw_name = i->name,
268✔
538
                        .pw_uid = i->uid,
268✔
539
                        .pw_gid = i->gid,
268✔
540
                        .pw_gecos = (char*) strempty(i->description),
268✔
541

542
                        /* "x" means the password is stored in the shadow file */
543
                        .pw_passwd = (char*) PASSWORD_SEE_SHADOW,
544

545
                        /* We default to the root directory as home */
546
                        .pw_dir = i->home ?: (char*) "/",
268✔
547

548
                        /* Initialize the shell to nologin, with one exception:
549
                         * for root we patch in something special */
550
                        .pw_shell = (char*) pick_shell(i),
268✔
551
                };
552

553
                /* Try to pick up the shell for this account via the credentials logic */
554
                cn = strjoin("passwd.shell.", i->name);
268✔
555
                if (!cn)
268✔
556
                        return -ENOMEM;
557

558
                r = read_credential(cn, (void**) &creds_shell, NULL);
268✔
559
                if (r < 0)
268✔
560
                        log_debug_errno(r, "Couldn't read credential '%s', ignoring: %m", cn);
268✔
561
                else
562
                        n.pw_shell = creds_shell;
×
563

564
                r = putpwent_sane(&n, passwd);
268✔
565
                if (r < 0)
268✔
566
                        return log_debug_errno(r, "Failed to add new user \"%s\" to temporary passwd file: %m",
×
567
                                               i->name);
568
        }
569

570
        /* Append the remaining NIS entries if any */
571
        while (pw) {
87✔
572
                r = putpwent_sane(pw, passwd);
5✔
573
                if (r < 0)
5✔
574
                        return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary passwd file: %m",
×
575
                                               pw->pw_name);
576

577
                r = fgetpwent_sane(original, &pw);
5✔
578
                if (r < 0)
5✔
579
                        return log_debug_errno(r, "Failed to read %s: %m", passwd_path);
×
580
                if (r == 0)
5✔
581
                        break;
582
        }
583

584
        r = fflush_sync_and_check(passwd);
87✔
585
        if (r < 0)
87✔
586
                return log_debug_errno(r, "Failed to flush %s: %m", passwd_tmp);
×
587

588
        *ret_tmpfile = TAKE_PTR(passwd);
87✔
589
        *ret_tmpfile_path = TAKE_PTR(passwd_tmp);
87✔
590

591
        return 0;
87✔
592
}
593

594
static usec_t epoch_or_now(void) {
87✔
595
        uint64_t epoch;
87✔
596

597
        if (secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch) >= 0) {
87✔
598
                if (epoch > UINT64_MAX/USEC_PER_SEC) /* Overflow check */
×
599
                        return USEC_INFINITY;
87✔
600
                return (usec_t) epoch * USEC_PER_SEC;
×
601
        }
602

603
        return now(CLOCK_REALTIME);
87✔
604
}
605

606
static int write_temporary_shadow(
113✔
607
                Context *c,
608
                const char *shadow_path,
609
                FILE **ret_tmpfile,
610
                char **ret_tmpfile_path) {
611

612
        _cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
113✔
613
        _cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
113✔
614
        struct spwd *sp = NULL;
113✔
615
        long lstchg;
113✔
616
        Item *i;
113✔
617
        int r;
113✔
618

619
        assert(c);
113✔
620

621
        if (ordered_hashmap_isempty(c->todo_uids))
113✔
622
                return 0;
623

624
        if (arg_dry_run) {
87✔
625
                log_info("Would write /etc/shadow%s", glyph(GLYPH_ELLIPSIS));
×
626
                return 0;
×
627
        }
628

629
        r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
87✔
630
        if (r < 0)
87✔
631
                return log_debug_errno(r, "Failed to open temporary copy of %s: %m", shadow_path);
×
632

633
        lstchg = (long) (epoch_or_now() / USEC_PER_DAY);
87✔
634

635
        original = fopen(shadow_path, "re");
87✔
636
        if (original) {
87✔
637

638
                r = copy_rights_with_fallback(fileno(original), fileno(shadow), shadow_tmp);
4✔
639
                if (r < 0)
4✔
640
                        return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
×
641
                                               shadow_path, shadow_tmp);
642

643
                while ((r = fgetspent_sane(original, &sp)) > 0) {
103✔
644
                        i = ordered_hashmap_get(c->users, sp->sp_namp);
99✔
645
                        if (i && i->todo_user) {
99✔
646
                                /* we will update the existing entry */
647
                                sp->sp_lstchg = lstchg;
×
648

649
                                /* only the /etc/shadow stage is left, so we can
650
                                 * safely remove the item from the todo set */
651
                                i->todo_user = false;
×
652
                                ordered_hashmap_remove(c->todo_uids, UID_TO_PTR(i->uid));
×
653
                        }
654

655
                        /* Make sure we keep the NIS entries (if any) at the end. */
656
                        if (IN_SET(sp->sp_namp[0], '+', '-'))
99✔
657
                                break;
658

659
                        r = putspent_sane(sp, shadow);
99✔
660
                        if (r < 0)
99✔
661
                                return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary shadow file: %m",
×
662
                                                       sp->sp_namp);
663

664
                }
665
                if (r < 0)
4✔
666
                        return log_debug_errno(r, "Failed to read %s: %m", shadow_path);
×
667

668
        } else {
669
                if (errno != ENOENT)
83✔
670
                        return log_debug_errno(errno, "Failed to open %s: %m", shadow_path);
×
671
                if (fchmod(fileno(shadow), 0000) < 0)
83✔
672
                        return log_debug_errno(errno, "Failed to fchmod %s: %m", shadow_tmp);
×
673
        }
674

675
        ORDERED_HASHMAP_FOREACH(i, c->todo_uids) {
355✔
676
                _cleanup_(erase_and_freep) char *creds_password = NULL;
268✔
677
                bool is_hashed;
268✔
678

679
                struct spwd n = {
536✔
680
                        .sp_namp = i->name,
268✔
681
                        .sp_lstchg = lstchg,
682
                        .sp_min = -1,
683
                        .sp_max = -1,
684
                        .sp_warn = -1,
685
                        .sp_inact = -1,
686
                        .sp_expire = i->locked ? 1 : -1, /* Negative expiration means "unset". Expiration 0 or 1 means "locked" */
268✔
687
                        .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
688
                };
689

690
                r = get_credential_user_password(i->name, &creds_password, &is_hashed);
268✔
691
                if (r < 0)
268✔
692
                        log_debug_errno(r, "Couldn't read password credential for user '%s', ignoring: %m", i->name);
268✔
693

694
                if (creds_password && !is_hashed) {
268✔
695
                        _cleanup_(erase_and_freep) char* plaintext_password = TAKE_PTR(creds_password);
×
696
                        r = hash_password(plaintext_password, &creds_password);
×
697
                        if (r < 0)
×
698
                                return log_debug_errno(r, "Failed to hash password: %m");
×
699
                }
700

701
                if (creds_password)
268✔
702
                        n.sp_pwdp = creds_password;
×
703
                else if (streq(i->name, "root"))
268✔
704
                        /* Let firstboot set the password later */
705
                        n.sp_pwdp = (char*) PASSWORD_UNPROVISIONED;
10✔
706
                else
707
                        n.sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID;
258✔
708

709
                r = putspent_sane(&n, shadow);
268✔
710
                if (r < 0)
268✔
711
                        return log_debug_errno(r, "Failed to add new user \"%s\" to temporary shadow file: %m",
×
712
                                               i->name);
713
        }
714

715
        /* Append the remaining NIS entries if any */
716
        while (sp) {
87✔
717
                r = putspent_sane(sp, shadow);
×
718
                if (r < 0)
×
719
                        return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary shadow file: %m",
×
720
                                               sp->sp_namp);
721

722
                r = fgetspent_sane(original, &sp);
×
723
                if (r < 0)
×
724
                        return log_debug_errno(r, "Failed to read %s: %m", shadow_path);
×
725
                if (r == 0)
×
726
                        break;
727
        }
728
        if (!IN_SET(errno, 0, ENOENT))
87✔
729
                return -errno;
×
730

731
        r = fflush_sync_and_check(shadow);
87✔
732
        if (r < 0)
87✔
733
                return log_debug_errno(r, "Failed to flush %s: %m", shadow_tmp);
×
734

735
        *ret_tmpfile = TAKE_PTR(shadow);
87✔
736
        *ret_tmpfile_path = TAKE_PTR(shadow_tmp);
87✔
737

738
        return 0;
87✔
739
}
740

741
static int write_temporary_group(
113✔
742
                Context *c,
743
                const char *group_path,
744
                FILE **ret_tmpfile,
745
                char **ret_tmpfile_path) {
746

747
        _cleanup_fclose_ FILE *original = NULL, *group = NULL;
113✔
748
        _cleanup_(unlink_and_freep) char *group_tmp = NULL;
113✔
749
        bool group_changed = false;
113✔
750
        struct group *gr = NULL;
113✔
751
        Item *i;
113✔
752
        int r;
113✔
753

754
        assert(c);
113✔
755

756
        if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members))
113✔
757
                return 0;
758

759
        if (arg_dry_run) {
87✔
760
                log_info("Would write /etc/group%s", glyph(GLYPH_ELLIPSIS));
×
761
                return 0;
×
762
        }
763

764
        r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
87✔
765
        if (r < 0)
87✔
766
                return log_error_errno(r, "Failed to open temporary copy of %s: %m", group_path);
×
767

768
        original = fopen(group_path, "re");
87✔
769
        if (original) {
87✔
770

771
                r = copy_rights_with_fallback(fileno(original), fileno(group), group_tmp);
14✔
772
                if (r < 0)
14✔
773
                        return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
×
774
                                               group_path, group_tmp);
775

776
                while ((r = fgetgrent_sane(original, &gr)) > 0) {
251✔
777
                        /* Safety checks against name and GID collisions. Normally,
778
                         * this should be unnecessary, but given that we look at the
779
                         * entries anyway here, let's make an extra verification
780
                         * step that we don't generate duplicate entries. */
781

782
                        i = ordered_hashmap_get(c->groups, gr->gr_name);
242✔
783
                        if (i && i->todo_group)
242✔
784
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
785
                                                       "%s: Group \"%s\" already exists.",
786
                                                       group_path, gr->gr_name);
787

788
                        if (ordered_hashmap_contains(c->todo_gids, GID_TO_PTR(gr->gr_gid)))
242✔
789
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
790
                                                       "%s: Detected collision for GID " GID_FMT ".",
791
                                                       group_path, gr->gr_gid);
792

793
                        /* Make sure we keep the NIS entries (if any) at the end. */
794
                        if (IN_SET(gr->gr_name[0], '+', '-'))
242✔
795
                                break;
796

797
                        r = putgrent_with_members(c, gr, group);
237✔
798
                        if (r < 0)
237✔
799
                                return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
×
800
                                                       gr->gr_name);
801
                        if (r > 0)
237✔
802
                                group_changed = true;
×
803
                }
804
                if (r < 0)
14✔
805
                        return log_error_errno(r, "Failed to read %s: %m", group_path);
×
806

807
        } else {
808
                if (errno != ENOENT)
73✔
809
                        return log_error_errno(errno, "Failed to open %s: %m", group_path);
×
810
                if (fchmod(fileno(group), 0644) < 0)
73✔
811
                        return log_error_errno(errno, "Failed to fchmod %s: %m", group_tmp);
×
812
        }
813

814
        ORDERED_HASHMAP_FOREACH(i, c->todo_gids) {
603✔
815
                struct group n = {
516✔
816
                        .gr_name = i->name,
516✔
817
                        .gr_gid = i->gid,
516✔
818
                        .gr_passwd = (char*) PASSWORD_SEE_SHADOW,
819
                };
820

821
                r = putgrent_with_members(c, &n, group);
516✔
822
                if (r < 0)
516✔
823
                        return log_error_errno(r, "Failed to add new group \"%s\" to temporary group file: %m",
×
824
                                               gr->gr_name);
825

826
                group_changed = true;
516✔
827
        }
828

829
        /* Append the remaining NIS entries if any */
830
        while (gr) {
97✔
831
                r = putgrent_sane(gr, group);
15✔
832
                if (r < 0)
15✔
833
                        return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
×
834
                                               gr->gr_name);
835

836
                r = fgetgrent_sane(original, &gr);
15✔
837
                if (r < 0)
15✔
838
                        return log_error_errno(r, "Failed to read %s: %m", group_path);
×
839
                if (r == 0)
15✔
840
                        break;
841
        }
842

843
        r = fflush_sync_and_check(group);
87✔
844
        if (r < 0)
87✔
845
                return log_error_errno(r, "Failed to flush %s: %m", group_tmp);
×
846

847
        if (group_changed) {
87✔
848
                *ret_tmpfile = TAKE_PTR(group);
87✔
849
                *ret_tmpfile_path = TAKE_PTR(group_tmp);
87✔
850
        }
851
        return 0;
852
}
853

854
static int write_temporary_gshadow(
113✔
855
                Context *c,
856
                const char * gshadow_path,
857
                FILE **ret_tmpfile,
858
                char **ret_tmpfile_path) {
859

860
#if ENABLE_GSHADOW
861
        _cleanup_fclose_ FILE *original = NULL, *gshadow = NULL;
113✔
862
        _cleanup_(unlink_and_freep) char *gshadow_tmp = NULL;
113✔
863
        bool group_changed = false;
113✔
864
        Item *i;
113✔
865
        int r;
113✔
866

867
        assert(c);
113✔
868

869
        if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members))
113✔
870
                return 0;
871

872
        if (arg_dry_run) {
87✔
873
                log_info("Would write /etc/gshadow%s", glyph(GLYPH_ELLIPSIS));
×
874
                return 0;
×
875
        }
876

877
        r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
87✔
878
        if (r < 0)
87✔
879
                return log_error_errno(r, "Failed to open temporary copy of %s: %m", gshadow_path);
×
880

881
        original = fopen(gshadow_path, "re");
87✔
882
        if (original) {
87✔
883
                struct sgrp *sg;
4✔
884

885
                r = copy_rights_with_fallback(fileno(original), fileno(gshadow), gshadow_tmp);
4✔
886
                if (r < 0)
4✔
887
                        return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
×
888
                                               gshadow_path, gshadow_tmp);
889

890
                while ((r = fgetsgent_sane(original, &sg)) > 0) {
231✔
891

892
                        i = ordered_hashmap_get(c->groups, sg->sg_namp);
227✔
893
                        if (i && i->todo_group)
227✔
894
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
895
                                                       "%s: Group \"%s\" already exists.",
896
                                                       gshadow_path, sg->sg_namp);
897

898
                        r = putsgent_with_members(c, sg, gshadow);
227✔
899
                        if (r < 0)
227✔
900
                                return log_error_errno(r, "Failed to add existing group \"%s\" to temporary gshadow file: %m",
×
901
                                                       sg->sg_namp);
902
                        if (r > 0)
227✔
903
                                group_changed = true;
×
904
                }
905
                if (r < 0)
4✔
906
                        return r;
907

908
        } else {
909
                if (errno != ENOENT)
83✔
910
                        return log_error_errno(errno, "Failed to open %s: %m", gshadow_path);
×
911
                if (fchmod(fileno(gshadow), 0000) < 0)
83✔
912
                        return log_error_errno(errno, "Failed to fchmod %s: %m", gshadow_tmp);
×
913
        }
914

915
        ORDERED_HASHMAP_FOREACH(i, c->todo_gids) {
603✔
916
                struct sgrp n = {
516✔
917
                        .sg_namp = i->name,
516✔
918
                        .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
919
                };
920

921
                r = putsgent_with_members(c, &n, gshadow);
516✔
922
                if (r < 0)
516✔
923
                        return log_error_errno(r, "Failed to add new group \"%s\" to temporary gshadow file: %m",
×
924
                                               n.sg_namp);
925

926
                group_changed = true;
516✔
927
        }
928

929
        r = fflush_sync_and_check(gshadow);
87✔
930
        if (r < 0)
87✔
931
                return log_error_errno(r, "Failed to flush %s: %m", gshadow_tmp);
×
932

933
        if (group_changed) {
87✔
934
                *ret_tmpfile = TAKE_PTR(gshadow);
87✔
935
                *ret_tmpfile_path = TAKE_PTR(gshadow_tmp);
87✔
936
        }
937
#endif
938
        return 0;
939
}
940

941
static int write_files(Context *c) {
113✔
942
        _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL;
339✔
943
        _cleanup_(unlink_and_freep) char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL;
113✔
944
        int r;
113✔
945

946
        const char
113✔
947
                *passwd_path = prefix_roota(arg_root, "/etc/passwd"),
221✔
948
                *shadow_path = prefix_roota(arg_root, "/etc/shadow"),
221✔
949
                *group_path = prefix_roota(arg_root, "/etc/group"),
221✔
950
                *gshadow_path = prefix_roota(arg_root, "/etc/gshadow");
221✔
951

952
        assert(c);
113✔
953

954
        r = write_temporary_group(c, group_path, &group, &group_tmp);
113✔
955
        if (r < 0)
113✔
956
                return r;
957

958
        r = write_temporary_gshadow(c, gshadow_path, &gshadow, &gshadow_tmp);
113✔
959
        if (r < 0)
113✔
960
                return r;
961

962
        r = write_temporary_passwd(c, passwd_path, &passwd, &passwd_tmp);
113✔
963
        if (r < 0)
113✔
964
                return r;
965

966
        r = write_temporary_shadow(c, shadow_path, &shadow, &shadow_tmp);
113✔
967
        if (r < 0)
113✔
968
                return r;
969

970
        /* Make a backup of the old files */
971
        if (group) {
113✔
972
                r = make_backup("/etc/group", group_path);
87✔
973
                if (r < 0)
87✔
974
                        return log_error_errno(r, "Failed to backup %s: %m", group_path);
×
975
        }
976
        if (gshadow) {
113✔
977
                r = make_backup("/etc/gshadow", gshadow_path);
87✔
978
                if (r < 0)
87✔
979
                        return log_error_errno(r, "Failed to backup %s: %m", gshadow_path);
×
980
        }
981

982
        if (passwd) {
113✔
983
                r = make_backup("/etc/passwd", passwd_path);
87✔
984
                if (r < 0)
87✔
985
                        return log_error_errno(r, "Failed to backup %s: %m", passwd_path);
×
986
        }
987
        if (shadow) {
113✔
988
                r = make_backup("/etc/shadow", shadow_path);
87✔
989
                if (r < 0)
87✔
990
                        return log_error_errno(r, "Failed to backup %s: %m", shadow_path);
×
991
        }
992

993
        /* And make the new files count */
994
        if (group) {
113✔
995
                r = rename_and_apply_smack_floor_label(group_tmp, group_path);
87✔
996
                if (r < 0)
87✔
997
                        return log_error_errno(r, "Failed to rename %s to %s: %m",
×
998
                                               group_tmp, group_path);
999
                group_tmp = mfree(group_tmp);
87✔
1000
        }
1001
        /* OK, we have written the group entries successfully */
1002
        log_audit_accounts(c, ADD_GROUP);
113✔
1003
        if (gshadow) {
113✔
1004
                r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path);
87✔
1005
                if (r < 0)
87✔
1006
                        return log_error_errno(r, "Failed to rename %s to %s: %m",
×
1007
                                               gshadow_tmp, gshadow_path);
1008

1009
                gshadow_tmp = mfree(gshadow_tmp);
87✔
1010
        }
1011

1012
        if (passwd) {
113✔
1013
                r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
87✔
1014
                if (r < 0)
87✔
1015
                        return log_error_errno(r, "Failed to rename %s to %s: %m",
×
1016
                                               passwd_tmp, passwd_path);
1017

1018
                passwd_tmp = mfree(passwd_tmp);
87✔
1019
        }
1020
        /* OK, we have written the user entries successfully */
1021
        log_audit_accounts(c, ADD_USER);
113✔
1022
        if (shadow) {
113✔
1023
                r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
87✔
1024
                if (r < 0)
87✔
1025
                        return log_error_errno(r, "Failed to rename %s to %s: %m",
×
1026
                                               shadow_tmp, shadow_path);
1027

1028
                shadow_tmp = mfree(shadow_tmp);
87✔
1029
        }
1030

1031
        return 0;
1032
}
1033

1034
static int uid_is_ok(
288✔
1035
                Context *c,
1036
                uid_t uid,
1037
                const char *name,
1038
                bool check_with_gid) {
1039

1040
        int r;
288✔
1041
        assert(c);
288✔
1042

1043
        /* Let's see if we already have assigned the UID a second time */
1044
        if (ordered_hashmap_get(c->todo_uids, UID_TO_PTR(uid)))
288✔
1045
                return 0;
1046

1047
        /* Try to avoid using uids that are already used by a group
1048
         * that doesn't have the same name as our new user. */
1049
        if (check_with_gid) {
283✔
1050
                Item *i;
203✔
1051

1052
                i = ordered_hashmap_get(c->todo_gids, GID_TO_PTR(uid));
203✔
1053
                if (i && !streq(i->name, name))
203✔
1054
                        return 0;
1055
        }
1056

1057
        /* Let's check the files directly */
1058
        if (hashmap_contains(c->database_by_uid, UID_TO_PTR(uid)))
273✔
1059
                return 0;
1060

1061
        if (check_with_gid) {
273✔
1062
                const char *n;
193✔
1063

1064
                n = hashmap_get(c->database_by_gid, GID_TO_PTR(uid));
193✔
1065
                if (n && !streq(n, name))
193✔
1066
                        return 0;
1067
        }
1068

1069
        /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
1070
        if (!arg_root) {
268✔
1071
                _cleanup_free_ struct group *g = NULL;
5✔
1072

1073
                r = getpwuid_malloc(uid, /* ret= */ NULL);
5✔
1074
                if (r >= 0)
5✔
1075
                        return 0;
1076
                if (r != -ESRCH)
5✔
1077
                        log_warning_errno(r, "Unexpected failure while looking up UID '" UID_FMT "' via NSS, assuming it doesn't exist: %m", uid);
×
1078

1079
                if (check_with_gid) {
5✔
1080
                        r = getgrgid_malloc((gid_t) uid, &g);
5✔
1081
                        if (r >= 0) {
5✔
1082
                                if (!streq(g->gr_name, name))
×
1083
                                        return 0;
1084
                        } else if (r != -ESRCH)
5✔
1085
                                log_warning_errno(r, "Unexpected failure while looking up GID '" GID_FMT "' via NSS, assuming it doesn't exist: %m", uid);
5✔
1086
                }
1087
        }
1088

1089
        return 1;
1090
}
1091

1092
static int root_stat(const char *p, struct stat *ret_st) {
×
1093
        return chase_and_stat(p, arg_root, CHASE_PREFIX_ROOT, /* ret_path= */ NULL, ret_st);
×
1094
}
1095

1096
static int read_id_from_file(Item *i, uid_t *ret_uid, gid_t *ret_gid) {
70✔
1097
        struct stat st;
70✔
1098
        bool found_uid = false, found_gid = false;
70✔
1099
        uid_t uid = 0;
70✔
1100
        gid_t gid = 0;
70✔
1101

1102
        assert(i);
70✔
1103

1104
        /* First, try to get the GID directly */
1105
        if (ret_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
70✔
1106
                gid = st.st_gid;
×
1107
                found_gid = true;
×
1108
        }
1109

1110
        /* Then, try to get the UID directly */
1111
        if ((ret_uid || (ret_gid && !found_gid))
70✔
1112
            && i->uid_path
70✔
1113
            && root_stat(i->uid_path, &st) >= 0) {
×
1114

1115
                uid = st.st_uid;
×
1116
                found_uid = true;
×
1117

1118
                /* If we need the gid, but had no success yet, also derive it from the UID path */
1119
                if (ret_gid && !found_gid) {
×
1120
                        gid = st.st_gid;
×
1121
                        found_gid = true;
×
1122
                }
1123
        }
1124

1125
        /* If that didn't work yet, then let's reuse the GID as UID */
1126
        if (ret_uid && !found_uid && i->gid_path) {
70✔
1127

1128
                if (found_gid) {
×
1129
                        uid = (uid_t) gid;
1130
                        found_uid = true;
1131
                } else if (root_stat(i->gid_path, &st) >= 0) {
×
1132
                        uid = (uid_t) st.st_gid;
×
1133
                        found_uid = true;
×
1134
                }
1135
        }
1136

1137
        if (ret_uid) {
70✔
1138
                if (!found_uid)
40✔
1139
                        return 0;
70✔
1140

1141
                *ret_uid = uid;
×
1142
        }
1143

1144
        if (ret_gid) {
30✔
1145
                if (!found_gid)
30✔
1146
                        return 0;
1147

1148
                *ret_gid = gid;
×
1149
        }
1150

1151
        return 1;
1152
}
1153

1154
static int add_user(Context *c, Item *i) {
297✔
1155
        void *z;
297✔
1156
        int r;
297✔
1157

1158
        assert(c);
297✔
1159
        assert(i);
297✔
1160

1161
        /* Check the database directly */
1162
        z = hashmap_get(c->database_by_username, i->name);
297✔
1163
        if (z) {
297✔
1164
                log_debug("User %s already exists.", i->name);
29✔
1165
                i->uid = PTR_TO_UID(z);
29✔
1166
                i->uid_set = true;
29✔
1167
                return 0;
29✔
1168
        }
1169

1170
        if (!arg_root) {
268✔
1171
                _cleanup_free_ struct passwd *p = NULL;
5✔
1172

1173
                /* Also check NSS */
1174
                r = getpwnam_malloc(i->name, &p);
5✔
1175
                if (r >= 0) {
5✔
1176
                        log_debug("User %s already exists.", i->name);
×
1177
                        i->uid = p->pw_uid;
×
1178
                        i->uid_set = true;
×
1179

1180
                        r = free_and_strdup(&i->description, p->pw_gecos);
×
1181
                        if (r < 0)
×
1182
                                return log_oom();
×
1183

1184
                        return 0;
1185
                }
1186
                if (r != -ESRCH)
5✔
1187
                        log_warning_errno(r, "Unexpected failure while looking up user '%s' via NSS, assuming it doesn't exist: %m", i->name);
5✔
1188
        }
1189

1190
        /* Try to use the suggested numeric UID */
1191
        if (i->uid_set) {
268✔
1192
                r = uid_is_ok(c, i->uid, i->name, !i->id_set_strict);
233✔
1193
                if (r < 0)
233✔
1194
                        return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
×
1195
                if (r == 0) {
233✔
1196
                        log_info("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
5✔
1197
                        i->uid_set = false;
5✔
1198
                }
1199
        }
1200

1201
        /* If that didn't work, try to read it from the specified path */
1202
        if (!i->uid_set) {
268✔
1203
                uid_t candidate;
40✔
1204

1205
                if (read_id_from_file(i, &candidate, NULL) > 0) {
40✔
1206

1207
                        if (candidate <= 0 || !uid_range_contains(c->uid_range, candidate))
×
1208
                                log_debug("User ID " UID_FMT " of file not suitable for %s.", candidate, i->name);
×
1209
                        else {
1210
                                r = uid_is_ok(c, candidate, i->name, true);
×
1211
                                if (r < 0)
×
1212
                                        return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
×
1213
                                else if (r > 0) {
×
1214
                                        i->uid = candidate;
×
1215
                                        i->uid_set = true;
×
1216
                                } else
1217
                                        log_debug("User ID " UID_FMT " of file for %s is already used.", candidate, i->name);
40✔
1218
                        }
1219
                }
1220
        }
1221

1222
        /* Otherwise, try to reuse the group ID */
1223
        if (!i->uid_set && i->gid_set) {
268✔
1224
                r = uid_is_ok(c, (uid_t) i->gid, i->name, true);
40✔
1225
                if (r < 0)
40✔
1226
                        return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
×
1227
                if (r > 0) {
40✔
1228
                        i->uid = (uid_t) i->gid;
25✔
1229
                        i->uid_set = true;
25✔
1230
                }
1231
        }
1232

1233
        /* And if that didn't work either, let's try to find a free one */
1234
        if (!i->uid_set) {
268✔
1235
                maybe_emit_login_defs_warning(c);
15✔
1236

1237
                for (;;) {
15✔
1238
                        r = uid_range_next_lower(c->uid_range, &c->search_uid);
15✔
1239
                        if (r < 0)
15✔
1240
                                return log_error_errno(r, "No free user ID available for %s.", i->name);
×
1241

1242
                        r = uid_is_ok(c, c->search_uid, i->name, true);
15✔
1243
                        if (r < 0)
15✔
1244
                                return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
×
1245
                        else if (r > 0)
15✔
1246
                                break;
1247
                }
1248

1249
                i->uid_set = true;
15✔
1250
                i->uid = c->search_uid;
15✔
1251
        }
1252

1253
        r = ordered_hashmap_ensure_put(&c->todo_uids, NULL, UID_TO_PTR(i->uid), i);
268✔
1254
        if (r == -EEXIST)
268✔
1255
                return log_error_errno(r, "Requested user %s with UID " UID_FMT " and gid" GID_FMT " to be created is duplicated "
×
1256
                                       "or conflicts with another user.", i->name, i->uid, i->gid);
1257
        if (r == -ENOMEM)
268✔
1258
                return log_oom();
×
1259
        if (r < 0)
268✔
1260
                return log_error_errno(r, "Failed to store user %s with UID " UID_FMT " and GID " GID_FMT " to be created: %m",
×
1261
                                       i->name, i->uid, i->gid);
1262

1263
        i->todo_user = true;
268✔
1264
        log_info("Creating user '%s' (%s) with UID " UID_FMT " and GID " GID_FMT ".",
538✔
1265
                 i->name, strna(i->description), i->uid, i->gid);
1266

1267
        return 0;
1268
}
1269

1270
static int gid_is_ok(
720✔
1271
                Context *c,
1272
                gid_t gid,
1273
                const char *groupname,
1274
                bool check_with_uid) {
1275

1276
        Item *user;
720✔
1277
        char *username;
720✔
1278
        int r;
720✔
1279

1280
        assert(c);
720✔
1281
        assert(groupname);
720✔
1282

1283
        if (ordered_hashmap_get(c->todo_gids, GID_TO_PTR(gid)))
720✔
1284
                return 0;
1285

1286
        /* Avoid reusing gids that are already used by a different user */
1287
        if (check_with_uid) {
655✔
1288
                user = ordered_hashmap_get(c->todo_uids, UID_TO_PTR(gid));
321✔
1289
                if (user && !streq(user->name, groupname))
321✔
1290
                        return 0;
1291
        }
1292

1293
        if (hashmap_contains(c->database_by_gid, GID_TO_PTR(gid)))
655✔
1294
                return 0;
1295

1296
        if (check_with_uid) {
517✔
1297
                username = hashmap_get(c->database_by_uid, UID_TO_PTR(gid));
183✔
1298
                if (username && !streq(username, groupname))
183✔
1299
                        return 0;
1300
        }
1301

1302
        if (!arg_root) {
517✔
1303
                r = getgrgid_malloc(gid, /* ret= */ NULL);
5✔
1304
                if (r >= 0)
5✔
1305
                        return 0;
1306
                if (r != -ESRCH)
5✔
1307
                        log_warning_errno(r, "Unexpected failure while looking up GID '" GID_FMT "' via NSS, assuming it doesn't exist: %m", gid);
×
1308

1309
                if (check_with_uid) {
5✔
1310
                        r = getpwuid_malloc(gid, /* ret= */ NULL);
5✔
1311
                        if (r >= 0)
5✔
1312
                                return 0;
1313
                        if (r != -ESRCH)
5✔
1314
                                log_warning_errno(r, "Unexpected failure while looking up GID '" GID_FMT "' via NSS, assuming it doesn't exist: %m", gid);
×
1315
                }
1316
        }
1317

1318
        return 1;
1319
}
1320

1321
static int get_gid_by_name(
645✔
1322
                Context *c,
1323
                const char *name,
1324
                gid_t *ret_gid) {
1325

1326
        void *z;
645✔
1327
        int r;
645✔
1328

1329
        assert(c);
645✔
1330
        assert(ret_gid);
645✔
1331

1332
        /* Check the database directly */
1333
        z = hashmap_get(c->database_by_groupname, name);
645✔
1334
        if (z) {
645✔
1335
                *ret_gid = PTR_TO_GID(z);
67✔
1336
                return 0;
67✔
1337
        }
1338

1339
        /* Also check NSS */
1340
        if (!arg_root) {
578✔
1341
                _cleanup_free_ struct group *g = NULL;
5✔
1342

1343
                r = getgrnam_malloc(name, &g);
5✔
1344
                if (r >= 0) {
5✔
1345
                        *ret_gid = g->gr_gid;
×
1346
                        return 0;
×
1347
                }
1348
                if (r != -ESRCH)
5✔
1349
                        log_warning_errno(r, "Unexpected failure while looking up group '%s' via NSS, assuming it doesn't exist: %m", name);
5✔
1350
        }
1351

1352
        return -ENOENT;
1353
}
1354

1355
static int add_group(Context *c, Item *i) {
639✔
1356
        int r;
639✔
1357

1358
        assert(c);
639✔
1359
        assert(i);
639✔
1360

1361
        r = get_gid_by_name(c, i->name, &i->gid);
639✔
1362
        if (r != -ENOENT) {
639✔
1363
                if (r < 0)
62✔
1364
                        return r;
1365
                log_debug("Group %s already exists.", i->name);
62✔
1366
                i->gid_set = true;
62✔
1367
                return 0;
62✔
1368
        }
1369

1370
        /* Try to use the suggested numeric GID */
1371
        if (i->gid_set) {
577✔
1372
                r = gid_is_ok(c, i->gid, i->name, false);
394✔
1373
                if (r < 0)
394✔
1374
                        return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
×
1375
                if (i->id_set_strict) {
394✔
1376
                        /* If we require the GID to already exist we can return here:
1377
                         * r > 0: means the GID does not exist -> fail
1378
                         * r == 0: means the GID exists -> nothing more to do.
1379
                         */
1380
                        if (r > 0)
61✔
1381
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1✔
1382
                                                       "Failed to create %s: please create GID " GID_FMT,
1383
                                                       i->name, i->gid);
1384
                        if (r == 0)
60✔
1385
                                return 0;
1386
                }
1387
                if (r == 0) {
333✔
1388
                        log_info("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
×
1389
                        i->gid_set = false;
×
1390
                }
1391
        }
1392

1393
        /* Try to reuse the numeric uid, if there's one */
1394
        if (!i->gid_set && i->uid_set) {
516✔
1395
                r = gid_is_ok(c, (gid_t) i->uid, i->name, true);
158✔
1396
                if (r < 0)
158✔
1397
                        return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
×
1398
                if (r > 0) {
158✔
1399
                        i->gid = (gid_t) i->uid;
153✔
1400
                        i->gid_set = true;
153✔
1401
                }
1402
        }
1403

1404
        /* If that didn't work, try to read it from the specified path */
1405
        if (!i->gid_set) {
516✔
1406
                gid_t candidate;
30✔
1407

1408
                if (read_id_from_file(i, NULL, &candidate) > 0) {
30✔
1409

1410
                        if (candidate <= 0 || !uid_range_contains(c->uid_range, candidate))
×
1411
                                log_debug("Group ID " GID_FMT " of file not suitable for %s.", candidate, i->name);
×
1412
                        else {
1413
                                r = gid_is_ok(c, candidate, i->name, true);
×
1414
                                if (r < 0)
×
1415
                                        return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
×
1416
                                else if (r > 0) {
×
1417
                                        i->gid = candidate;
×
1418
                                        i->gid_set = true;
×
1419
                                } else
1420
                                        log_debug("Group ID " GID_FMT " of file for %s already used.", candidate, i->name);
30✔
1421
                        }
1422
                }
1423
        }
1424

1425
        /* And if that didn't work either, let's try to find a free one */
1426
        if (!i->gid_set) {
516✔
1427
                maybe_emit_login_defs_warning(c);
30✔
1428

1429
                for (;;) {
168✔
1430
                        /* We look for new GIDs in the UID pool! */
1431
                        r = uid_range_next_lower(c->uid_range, &c->search_uid);
168✔
1432
                        if (r < 0)
168✔
1433
                                return log_error_errno(r, "No free group ID available for %s.", i->name);
×
1434

1435
                        r = gid_is_ok(c, c->search_uid, i->name, true);
168✔
1436
                        if (r < 0)
168✔
1437
                                return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
×
1438
                        else if (r > 0)
168✔
1439
                                break;
1440
                }
1441

1442
                i->gid_set = true;
30✔
1443
                i->gid = c->search_uid;
30✔
1444
        }
1445

1446
        r = ordered_hashmap_ensure_put(&c->todo_gids, NULL, GID_TO_PTR(i->gid), i);
516✔
1447
        if (r == -EEXIST)
516✔
1448
                return log_error_errno(r, "Requested group %s with GID "GID_FMT " to be created is duplicated or conflicts with another user.", i->name, i->gid);
×
1449
        if (r == -ENOMEM)
516✔
1450
                return log_oom();
×
1451
        if (r < 0)
516✔
1452
                return log_error_errno(r, "Failed to store group %s with GID " GID_FMT " to be created: %m", i->name, i->gid);
×
1453

1454
        i->todo_group = true;
516✔
1455
        log_info("Creating group '%s' with GID " GID_FMT ".", i->name, i->gid);
516✔
1456

1457
        return 0;
1458
}
1459

1460
static int process_item(Context *c, Item *i) {
675✔
1461
        int r;
675✔
1462

1463
        assert(c);
675✔
1464
        assert(i);
675✔
1465

1466
        switch (i->type) {
675✔
1467

1468
        case ADD_USER: {
299✔
1469
                Item *j = NULL;
299✔
1470

1471
                if (!i->gid_set) {
299✔
1472
                        j = ordered_hashmap_get(c->groups, i->group_name ?: i->name);
235✔
1473

1474
                        /* If that's not a match, also check if the group name
1475
                         * matches a user name in the queue. */
1476
                        if (!j && i->group_name)
235✔
1477
                                j = ordered_hashmap_get(c->users, i->group_name);
11✔
1478
                }
1479

1480
                if (j && j->todo_group) {
235✔
1481
                        /* When a group with the target name is already in queue,
1482
                         * use the information about the group and do not create
1483
                         * duplicated group entry. */
1484
                        i->gid_set = j->gid_set;
30✔
1485
                        i->gid = j->gid;
30✔
1486
                        i->id_set_strict = true;
30✔
1487
                } else if (i->group_name) {
269✔
1488
                        /* When a group name was given instead of a GID and it's
1489
                         * not in queue, then it must already exist. */
1490
                        r = get_gid_by_name(c, i->group_name, &i->gid);
6✔
1491
                        if (r < 0)
6✔
1492
                                return log_error_errno(r, "Group %s not found.", i->group_name);
1✔
1493
                        i->gid_set = true;
5✔
1494
                        i->id_set_strict = true;
5✔
1495
                } else {
1496
                        r = add_group(c, i);
263✔
1497
                        if (r < 0)
263✔
1498
                                return r;
1499
                }
1500

1501
                return add_user(c, i);
297✔
1502
        }
1503

1504
        case ADD_GROUP:
376✔
1505
                return add_group(c, i);
376✔
1506

1507
        default:
×
1508
                assert_not_reached();
×
1509
        }
1510
}
1511

1512
static Item* item_free(Item *i) {
677✔
1513
        if (!i)
677✔
1514
                return NULL;
1515

1516
        free(i->name);
677✔
1517
        free(i->group_name);
677✔
1518
        free(i->uid_path);
677✔
1519
        free(i->gid_path);
677✔
1520
        free(i->description);
677✔
1521
        free(i->home);
677✔
1522
        free(i->shell);
677✔
1523
        free(i->filename);
677✔
1524
        return mfree(i);
677✔
1525
}
1526

1527
DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
1,364✔
1528
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_hash_ops, char, string_hash_func, string_compare_func, Item, item_free);
675✔
1529

1530
static Item* item_new(ItemType type, const char *name, const char *filename, unsigned line) {
677✔
1531
        assert(name);
677✔
1532
        assert(!!filename == (line > 0));
677✔
1533

1534
        _cleanup_(item_freep) Item *new = new(Item, 1);
1,354✔
1535
        if (!new)
677✔
1536
                return NULL;
1537

1538
        *new = (Item) {
677✔
1539
                .type = type,
1540
                .line = line,
1541
        };
1542

1543
        if (free_and_strdup(&new->name, name) < 0 ||
1,354✔
1544
            free_and_strdup(&new->filename, filename) < 0)
677✔
1545
                return NULL;
×
1546

1547
        return TAKE_PTR(new);
677✔
1548
}
1549

1550
static int add_implicit(Context *c) {
113✔
1551
        char *g, **l;
113✔
1552
        int r;
113✔
1553

1554
        assert(c);
113✔
1555

1556
        /* Implicitly create additional users and groups, if they were listed in "m" lines */
1557
        ORDERED_HASHMAP_FOREACH_KEY(l, g, c->members) {
236✔
1558
                STRV_FOREACH(m, l)
20✔
1559
                        if (!ordered_hashmap_get(c->users, *m)) {
10✔
1560
                                _cleanup_(item_freep) Item *j =
×
1561
                                        item_new(ADD_USER, *m, /* filename= */ NULL, /* line= */ 0);
5✔
1562
                                if (!j)
5✔
1563
                                        return log_oom();
×
1564

1565
                                r = ordered_hashmap_ensure_put(&c->users, &item_hash_ops, j->name, j);
5✔
1566
                                if (r == -ENOMEM)
5✔
1567
                                        return log_oom();
×
1568
                                if (r < 0)
5✔
1569
                                        return log_error_errno(r, "Failed to add implicit user '%s': %m", j->name);
×
1570

1571
                                log_debug("Adding implicit user '%s' due to m line", j->name);
5✔
1572
                                TAKE_PTR(j);
5✔
1573
                        }
1574

1575
                if (!(ordered_hashmap_get(c->users, g) ||
10✔
1576
                      ordered_hashmap_get(c->groups, g))) {
2✔
1577
                        _cleanup_(item_freep) Item *j =
×
1578
                                item_new(ADD_GROUP, g, /* filename= */ NULL, /* line= */ 0);
×
1579
                        if (!j)
×
1580
                                return log_oom();
×
1581

1582
                        r = ordered_hashmap_ensure_put(&c->groups, &item_hash_ops, j->name, j);
×
1583
                        if (r == -ENOMEM)
×
1584
                                return log_oom();
×
1585
                        if (r < 0)
×
1586
                                return log_error_errno(r, "Failed to add implicit group '%s': %m", j->name);
×
1587

1588
                        log_debug("Adding implicit group '%s' due to m line", j->name);
×
1589
                        TAKE_PTR(j);
×
1590
                }
1591
        }
1592

1593
        return 0;
113✔
1594
}
1595

1596
static int item_equivalent(Item *a, Item *b) {
1✔
1597
        int r;
1✔
1598

1599
        assert(a);
1✔
1600
        assert(b);
1✔
1601

1602
        if (a->type != b->type) {
1✔
1603
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1604
                           "Item not equivalent because types differ");
1605
                return false;
×
1606
        }
1607

1608
        if (!streq_ptr(a->name, b->name)) {
1✔
1609
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1610
                           "Item not equivalent because names differ ('%s' vs. '%s')",
1611
                           a->name, b->name);
1612
                return false;
×
1613
        }
1614

1615
        /* Paths were simplified previously, so we can use streq. */
1616
        if (!streq_ptr(a->uid_path, b->uid_path)) {
1✔
1617
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1618
                           "Item not equivalent because UID paths differ (%s vs. %s)",
1619
                           a->uid_path ?: "(unset)", b->uid_path ?: "(unset)");
1620
                return false;
×
1621
        }
1622

1623
        if (!streq_ptr(a->gid_path, b->gid_path)) {
1✔
1624
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1625
                           "Item not equivalent because GID paths differ (%s vs. %s)",
1626
                           a->gid_path ?: "(unset)", b->gid_path ?: "(unset)");
1627
                return false;
×
1628
        }
1629

1630
        if (!streq_ptr(a->description, b->description))  {
1✔
1631
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1632
                           "Item not equivalent because descriptions differ ('%s' vs. '%s')",
1633
                           strempty(a->description), strempty(b->description));
1634
                return false;
×
1635
        }
1636

1637
        if ((a->uid_set != b->uid_set) ||
1✔
1638
            (a->uid_set && a->uid != b->uid)) {
×
1639
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1640
                           "Item not equivalent because UIDs differ (%s vs. %s)",
1641
                           a->uid_set ? FORMAT_UID(a->uid) : "(unset)",
1642
                           b->uid_set ? FORMAT_UID(b->uid) : "(unset)");
1643
                return false;
×
1644
        }
1645

1646
        if ((a->gid_set != b->gid_set) ||
1✔
1647
            (a->gid_set && a->gid != b->gid)) {
1✔
1648
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1649
                           "Item not equivalent because GIDs differ (%s vs. %s)",
1650
                           a->gid_set ? FORMAT_GID(a->gid) : "(unset)",
1651
                           b->gid_set ? FORMAT_GID(b->gid) : "(unset)");
1652
                return false;
×
1653
        }
1654

1655
        if (!streq_ptr(a->home, b->home)) {
1✔
1656
                log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1657
                           "Item not equivalent because home directories differ ('%s' vs. '%s')",
1658
                           strempty(a->description), strempty(b->description));
1659
                return false;
×
1660
        }
1661

1662
        /* Check if the two paths refer to the same file.
1663
         * If the paths are equal (after normalization), it's obviously the same file.
1664
         * If both paths specify a nologin shell, treat them as the same (e.g. /bin/true and /bin/false).
1665
         * Otherwise, try to resolve the paths, and see if we get the same result, (e.g. /sbin/nologin and
1666
         * /usr/sbin/nologin).
1667
         * If we can't resolve something, treat different paths as different. */
1668

1669
        const char *a_shell = pick_shell(a),
1✔
1670
                   *b_shell = pick_shell(b);
1✔
1671
        if (!path_equal(a_shell, b_shell) &&
1✔
1672
            !(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) {
×
1673
                _cleanup_free_ char *pa = NULL, *pb = NULL;
×
1674

1675
                r = chase(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
×
1676
                if (r < 0) {
×
1677
                        log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
×
1678
                                       r, "Failed to look up path '%s%s%s': %m",
1679
                                       strempty(arg_root), arg_root ? "/" : "", a_shell);
1680
                        return ERRNO_IS_RESOURCE(r) ? r : false;
×
1681
                }
1682

1683
                r = chase(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
×
1684
                if (r < 0) {
×
1685
                        log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
×
1686
                                       r, "Failed to look up path '%s%s%s': %m",
1687
                                       strempty(arg_root), arg_root ? "/" : "", b_shell);
1688
                        return ERRNO_IS_RESOURCE(r) ? r : false;
×
1689
                }
1690

1691
                if (!path_equal(pa, pb)) {
×
1692
                        log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
×
1693
                                   "Item not equivalent because shells differ ('%s' vs. '%s')",
1694
                                   pa, pb);
1695
                        return false;
×
1696
                }
1697
        }
1698

1699
        return true;
1700
}
1701

1702
static int parse_line(
682✔
1703
                const char *fname,
1704
                unsigned line,
1705
                const char *buffer,
1706
                bool *invalid_config,
1707
                void *context) {
1708

1709
        Context *c = ASSERT_PTR(context);
682✔
1710
        _cleanup_free_ char *action = NULL,
1,364✔
1711
                *name = NULL, *resolved_name = NULL,
×
1712
                *id = NULL, *resolved_id = NULL,
×
1713
                *description = NULL, *resolved_description = NULL,
×
1714
                *home = NULL, *resolved_home = NULL,
×
1715
                *shell = NULL, *resolved_shell = NULL;
682✔
1716
        _cleanup_(item_freep) Item *i = NULL;
682✔
1717
        Item *existing;
682✔
1718
        OrderedHashmap *h;
682✔
1719
        int r;
682✔
1720
        const char *p;
682✔
1721

1722
        assert(fname);
682✔
1723
        assert(line >= 1);
682✔
1724
        assert(buffer);
682✔
1725
        assert(!invalid_config); /* We don't support invalid_config yet. */
682✔
1726

1727
        /* Parse columns */
1728
        p = buffer;
682✔
1729
        r = extract_many_words(&p, NULL, EXTRACT_UNQUOTE,
682✔
1730
                               &action, &name, &id, &description, &home, &shell);
1731
        if (r < 0)
682✔
1732
                return log_syntax(NULL, LOG_ERR, fname, line, r, "Syntax error.");
×
1733
        if (r < 2)
682✔
1734
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1735
                                  "Missing action and name columns.");
1736
        if (!isempty(p))
682✔
1737
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1738
                                  "Trailing garbage.");
1739

1740
        if (isempty(action))
682✔
1741
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
1742
                                  "Empty command specification.");
1743

1744
        bool locked = false;
1745
        for (int pos = 1; action[pos]; pos++)
694✔
1746
                if (action[pos] == '!' && !locked)
12✔
1747
                        locked = true;
12✔
1748
                else
1749
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
1750
                                          "Unknown modifiers in command '%s'.", action);
1751

1752
        if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE))
682✔
1753
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
1754
                                  "Unknown command type '%c'.", action[0]);
1755

1756
        /* Verify name */
1757
        if (empty_or_dash(name))
682✔
1758
                name = mfree(name);
×
1759

1760
        if (name) {
682✔
1761
                r = specifier_printf(name, NAME_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_name);
682✔
1762
                if (r < 0)
682✔
1763
                        return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to replace specifiers in '%s': %m", name);
×
1764

1765
                if (!valid_user_group_name(resolved_name, 0))
682✔
1766
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1767
                                          "'%s' is not a valid user or group name.", resolved_name);
1768
        }
1769

1770
        /* Verify id */
1771
        if (empty_or_dash(id))
682✔
1772
                id = mfree(id);
55✔
1773

1774
        if (id) {
682✔
1775
                r = specifier_printf(id, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_id);
627✔
1776
                if (r < 0)
627✔
1777
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
1778
                                          "Failed to replace specifiers in '%s': %m", name);
1779
        }
1780

1781
        /* Verify description */
1782
        if (empty_or_dash(description))
682✔
1783
                description = mfree(description);
631✔
1784

1785
        if (description) {
682✔
1786
                r = specifier_printf(description, LONG_LINE_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_description);
51✔
1787
                if (r < 0)
51✔
1788
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
1789
                                          "Failed to replace specifiers in '%s': %m", description);
1790

1791
                if (!valid_gecos(resolved_description))
51✔
1792
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1793
                                          "'%s' is not a valid GECOS field.", resolved_description);
1794
        }
1795

1796
        /* Verify home */
1797
        if (empty_or_dash(home))
682✔
1798
                home = mfree(home);
542✔
1799

1800
        if (home) {
682✔
1801
                r = specifier_printf(home, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_home);
140✔
1802
                if (r < 0)
140✔
1803
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
1804
                                          "Failed to replace specifiers in '%s': %m", home);
1805

1806
                path_simplify(resolved_home);
140✔
1807

1808
                if (!valid_home(resolved_home))
140✔
1809
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1810
                                          "'%s' is not a valid home directory field.", resolved_home);
1811
        }
1812

1813
        /* Verify shell */
1814
        if (empty_or_dash(shell))
682✔
1815
                shell = mfree(shell);
661✔
1816

1817
        if (shell) {
682✔
1818
                r = specifier_printf(shell, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_shell);
21✔
1819
                if (r < 0)
21✔
1820
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
1821
                                          "Failed to replace specifiers in '%s': %m", shell);
1822

1823
                path_simplify(resolved_shell);
21✔
1824

1825
                if (!valid_shell(resolved_shell))
21✔
1826
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1827
                                          "'%s' is not a valid login shell field.", resolved_shell);
1828
        }
1829

1830
        switch (action[0]) {
682✔
1831

1832
        case ADD_RANGE:
×
1833
                if (locked)
×
1834
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1835
                                          "Flag '!' not permitted on lines of type 'r'.");
1836

1837
                if (resolved_name)
×
1838
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1839
                                          "Lines of type 'r' don't take a name field.");
1840

1841
                if (!resolved_id)
×
1842
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1843
                                          "Lines of type 'r' require an ID range in the third field.");
1844

1845
                if (description || home || shell)
×
1846
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1847
                                          "Lines of type '%c' don't take a %s field.",
1848
                                          action[0],
1849
                                          description ? "GECOS" : home ? "home directory" : "login shell");
1850

1851
                r = uid_range_add_str(&c->uid_range, resolved_id);
×
1852
                if (r < 0)
×
1853
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1854
                                          "Invalid UID range %s.", resolved_id);
1855

1856
                return 0;
1857

1858
        case ADD_MEMBER: {
10✔
1859
                /* Try to extend an existing member or group item */
1860
                if (!name)
10✔
1861
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1862
                                          "Lines of type 'm' require a user name in the second field.");
1863

1864
                if (locked)
10✔
1865
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1866
                                          "Flag '!' not permitted on lines of type 'm'.");
1867

1868
                if (!resolved_id)
10✔
1869
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1870
                                          "Lines of type 'm' require a group name in the third field.");
1871

1872
                if (!valid_user_group_name(resolved_id, 0))
10✔
1873
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1874
                                               "'%s' is not a valid user or group name.", resolved_id);
1875

1876
                if (description || home || shell)
10✔
1877
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1878
                                          "Lines of type '%c' don't take a %s field.",
1879
                                          action[0],
1880
                                          description ? "GECOS" : home ? "home directory" : "login shell");
1881

1882
                r = string_strv_ordered_hashmap_put(&c->members, resolved_id, resolved_name);
10✔
1883
                if (r < 0)
10✔
1884
                        return log_error_errno(r, "Failed to store mapping for %s: %m", resolved_id);
×
1885

1886
                return 0;
1887
        }
1888

1889
        case ADD_USER:
295✔
1890
                if (!name)
295✔
1891
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1892
                                          "Lines of type 'u' require a user name in the second field.");
1893

1894
                r = ordered_hashmap_ensure_allocated(&c->users, &item_hash_ops);
295✔
1895
                if (r < 0)
295✔
1896
                        return log_oom();
×
1897

1898
                i = item_new(ADD_USER, resolved_name, fname, line);
295✔
1899
                if (!i)
295✔
1900
                        return log_oom();
×
1901

1902
                if (resolved_id) {
295✔
1903
                        if (path_is_absolute(resolved_id))
266✔
1904
                                i->uid_path = path_simplify(TAKE_PTR(resolved_id));
×
1905
                        else {
1906
                                _cleanup_free_ char *uid = NULL, *gid = NULL;
266✔
1907
                                if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
266✔
1908
                                        r = parse_gid(gid, &i->gid);
90✔
1909
                                        if (r < 0) {
90✔
1910
                                                if (valid_user_group_name(gid, 0))
26✔
1911
                                                        i->group_name = TAKE_PTR(gid);
26✔
1912
                                                else
1913
                                                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
1914
                                                                          "Failed to parse GID: '%s': %m", id);
1915
                                        } else {
1916
                                                i->gid_set = true;
64✔
1917
                                                i->id_set_strict = true;
64✔
1918
                                        }
1919
                                        free_and_replace(resolved_id, uid);
90✔
1920
                                }
1921
                                if (!streq(resolved_id, "-")) {
266✔
1922
                                        r = parse_uid(resolved_id, &i->uid);
251✔
1923
                                        if (r < 0)
251✔
1924
                                                return log_syntax(NULL, LOG_ERR, fname, line, r,
1✔
1925
                                                                  "Failed to parse UID: '%s': %m", id);
1926
                                        i->uid_set = true;
250✔
1927
                                }
1928
                        }
1929
                }
1930

1931
                i->description = TAKE_PTR(resolved_description);
294✔
1932
                i->home = TAKE_PTR(resolved_home);
294✔
1933
                i->shell = TAKE_PTR(resolved_shell);
294✔
1934
                i->locked = locked;
294✔
1935

1936
                h = c->users;
294✔
1937
                break;
294✔
1938

1939
        case ADD_GROUP:
377✔
1940
                if (!name)
377✔
1941
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1942
                                          "Lines of type 'g' require a user name in the second field.");
1943

1944
                if (locked)
377✔
1945
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1946
                                          "Flag '!' not permitted on lines of type 'g'.");
1947

1948
                if (description || home || shell)
377✔
1949
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
1950
                                          "Lines of type '%c' don't take a %s field.",
1951
                                          action[0],
1952
                                          description ? "GECOS" : home ? "home directory" : "login shell");
1953

1954
                r = ordered_hashmap_ensure_allocated(&c->groups, &item_hash_ops);
377✔
1955
                if (r < 0)
377✔
1956
                        return log_oom();
×
1957

1958
                i = item_new(ADD_GROUP, resolved_name, fname, line);
377✔
1959
                if (!i)
377✔
1960
                        return log_oom();
×
1961

1962
                if (resolved_id) {
377✔
1963
                        if (path_is_absolute(resolved_id))
351✔
1964
                                i->gid_path = path_simplify(TAKE_PTR(resolved_id));
×
1965
                        else {
1966
                                r = parse_gid(resolved_id, &i->gid);
351✔
1967
                                if (r < 0)
351✔
1968
                                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
1969
                                                          "Failed to parse GID: '%s': %m", id);
1970

1971
                                i->gid_set = true;
351✔
1972
                        }
1973
                }
1974

1975
                h = c->groups;
377✔
1976
                break;
377✔
1977

1978
        default:
×
1979
                assert_not_reached();
×
1980
        }
1981

1982
        existing = ordered_hashmap_get(h, i->name);
671✔
1983
        if (existing) {
671✔
1984
                /* Two functionally-equivalent items are fine */
1985
                r = item_equivalent(i, existing);
1✔
1986
                if (r < 0)
1✔
1987
                        return r;
1988
                if (r == 0) {
1✔
1989
                        if (existing->filename)
×
1990
                                log_syntax(NULL, LOG_WARNING, fname, line, 0,
×
1991
                                           "Conflict with earlier configuration for %s '%s' in %s:%u, ignoring line.",
1992
                                           item_type_to_string(i->type),
1993
                                           i->name,
1994
                                           existing->filename, existing->line);
1995
                        else
1996
                                log_syntax(NULL, LOG_WARNING, fname, line, 0,
×
1997
                                           "Conflict with earlier configuration for %s '%s', ignoring line.",
1998
                                           item_type_to_string(i->type),
1999
                                           i->name);
2000
                }
2001

2002
                return 0;
1✔
2003
        }
2004

2005
        r = ordered_hashmap_put(h, i->name, i);
670✔
2006
        if (r < 0)
670✔
2007
                return log_oom();
×
2008

2009
        i = NULL;
670✔
2010
        return 0;
670✔
2011
}
2012

2013
static int read_config_file(Context *c, const char *fn, bool ignore_enoent) {
132✔
2014
        return conf_file_read(
396✔
2015
                        arg_root,
2016
                        (const char**) CONF_PATHS_STRV("sysusers.d"),
132✔
2017
                        ASSERT_PTR(fn),
132✔
2018
                        parse_line,
2019
                        ASSERT_PTR(c),
132✔
2020
                        ignore_enoent,
2021
                        /* invalid_config= */ NULL);
2022
}
2023

2024
static int cat_config(void) {
×
2025
        _cleanup_strv_free_ char **files = NULL;
×
2026
        int r;
×
2027

2028
        r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, NULL);
×
2029
        if (r < 0)
×
2030
                return r;
2031

2032
        pager_open(arg_pager_flags);
×
2033

2034
        return cat_files(NULL, files, arg_cat_flags);
×
2035
}
2036

2037
static int help(void) {
×
2038
        _cleanup_free_ char *link = NULL;
×
2039
        int r;
×
2040

2041
        r = terminal_urlify_man("systemd-sysusers.service", "8", &link);
×
2042
        if (r < 0)
×
2043
                return log_oom();
×
2044

2045
        printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n"
×
2046
               "\n%2$sCreates system user and group accounts.%4$s\n"
2047
               "\n%3$sCommands:%4$s\n"
2048
               "     --cat-config           Show configuration files\n"
2049
               "     --tldr                 Show non-comment parts of configuration\n"
2050
               "  -h --help                 Show this help\n"
2051
               "     --version              Show package version\n"
2052
               "\n%3$sOptions:%4$s\n"
2053
               "     --root=PATH            Operate on an alternate filesystem root\n"
2054
               "     --image=PATH           Operate on disk image as filesystem root\n"
2055
               "     --image-policy=POLICY  Specify disk image dissection policy\n"
2056
               "     --replace=PATH         Treat arguments as replacement for PATH\n"
2057
               "     --dry-run              Just print what would be done\n"
2058
               "     --inline               Treat arguments as configuration lines\n"
2059
               "     --no-pager             Do not pipe output into a pager\n"
2060
               "\nSee the %5$s for details.\n",
2061
               program_invocation_short_name,
2062
               ansi_highlight(),
2063
               ansi_underline(),
2064
               ansi_normal(),
2065
               link);
2066

2067
        return 0;
2068
}
2069

2070
static int parse_argv(int argc, char *argv[]) {
113✔
2071

2072
        enum {
113✔
2073
                ARG_VERSION = 0x100,
2074
                ARG_CAT_CONFIG,
2075
                ARG_TLDR,
2076
                ARG_ROOT,
2077
                ARG_IMAGE,
2078
                ARG_IMAGE_POLICY,
2079
                ARG_REPLACE,
2080
                ARG_DRY_RUN,
2081
                ARG_INLINE,
2082
                ARG_NO_PAGER,
2083
        };
2084

2085
        static const struct option options[] = {
113✔
2086
                { "help",         no_argument,       NULL, 'h'              },
2087
                { "version",      no_argument,       NULL, ARG_VERSION      },
2088
                { "cat-config",   no_argument,       NULL, ARG_CAT_CONFIG   },
2089
                { "tldr",         no_argument,       NULL, ARG_TLDR         },
2090
                { "root",         required_argument, NULL, ARG_ROOT         },
2091
                { "image",        required_argument, NULL, ARG_IMAGE        },
2092
                { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
2093
                { "replace",      required_argument, NULL, ARG_REPLACE      },
2094
                { "dry-run",      no_argument,       NULL, ARG_DRY_RUN      },
2095
                { "inline",       no_argument,       NULL, ARG_INLINE       },
2096
                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
2097
                {}
2098
        };
2099

2100
        int c, r;
113✔
2101

2102
        assert(argc >= 0);
113✔
2103
        assert(argv);
113✔
2104

2105
        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
260✔
2106

2107
                switch (c) {
147✔
2108

2109
                case 'h':
×
2110
                        return help();
×
2111

2112
                case ARG_VERSION:
×
2113
                        return version();
×
2114

2115
                case ARG_CAT_CONFIG:
×
2116
                        arg_cat_flags = CAT_CONFIG_ON;
×
2117
                        break;
×
2118

2119
                case ARG_TLDR:
×
2120
                        arg_cat_flags = CAT_TLDR;
×
2121
                        break;
×
2122

2123
                case ARG_ROOT:
108✔
2124
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
108✔
2125
                        if (r < 0)
108✔
2126
                                return r;
2127
                        break;
2128

2129
                case ARG_IMAGE:
×
2130
#ifdef STANDALONE
2131
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
2132
                                               "This systemd-sysusers version is compiled without support for --image=.");
2133
#else
2134
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
×
2135
                        if (r < 0)
×
2136
                                return r;
2137
                        break;
2138
#endif
2139

2140
                case ARG_IMAGE_POLICY:
×
2141
                        r = parse_image_policy_argument(optarg, &arg_image_policy);
×
2142
                        if (r < 0)
×
2143
                                return r;
2144
                        break;
2145

2146
                case ARG_REPLACE:
35✔
2147
                        if (!path_is_absolute(optarg))
35✔
2148
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2149
                                                       "The argument to --replace= must be an absolute path.");
2150
                        if (!endswith(optarg, ".conf"))
35✔
2151
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2152
                                                       "The argument to --replace= must have the extension '.conf'.");
2153

2154
                        arg_replace = optarg;
35✔
2155
                        break;
35✔
2156

2157
                case ARG_DRY_RUN:
×
2158
                        arg_dry_run = true;
×
2159
                        break;
×
2160

2161
                case ARG_INLINE:
4✔
2162
                        arg_inline = true;
4✔
2163
                        break;
4✔
2164

2165
                case ARG_NO_PAGER:
×
2166
                        arg_pager_flags |= PAGER_DISABLE;
×
2167
                        break;
×
2168

2169
                case '?':
2170
                        return -EINVAL;
2171

2172
                default:
×
2173
                        assert_not_reached();
×
2174
                }
2175

2176
        if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF)
113✔
2177
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2178
                                       "Option --replace= is not supported with --cat-config/--tldr.");
2179

2180
        if (arg_replace && optind >= argc)
113✔
2181
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2182
                                       "When --replace= is given, some configuration items must be specified.");
2183

2184
        if (arg_image && arg_root)
113✔
2185
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2186
                                       "Use either --root= or --image=, the combination of both is not supported.");
2187

2188
        return 1;
2189
}
2190

2191
static int parse_arguments(Context *c, char **args) {
41✔
2192
        unsigned pos = 1;
41✔
2193
        int r;
41✔
2194

2195
        assert(c);
41✔
2196

2197
        STRV_FOREACH(arg, args) {
85✔
2198
                if (arg_inline)
44✔
2199
                        /* Use (argument):n, where n==1 for the first positional arg */
2200
                        r = parse_line("(argument)", pos, *arg, /* invalid_config= */ NULL, c);
7✔
2201
                else
2202
                        r = read_config_file(c, *arg, /* ignore_enoent= */ false);
37✔
2203
                if (r < 0)
44✔
2204
                        return r;
2205

2206
                pos++;
44✔
2207
        }
2208

2209
        return 0;
2210
}
2211

2212
static int read_config_files(Context *c, char **args) {
90✔
2213
        _cleanup_strv_free_ char **files = NULL;
×
2214
        _cleanup_free_ char *p = NULL;
90✔
2215
        int r;
90✔
2216

2217
        assert(c);
90✔
2218

2219
        r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, &p);
90✔
2220
        if (r < 0)
90✔
2221
                return r;
2222

2223
        STRV_FOREACH(f, files)
202✔
2224
                if (p && path_equal(*f, p)) {
112✔
2225
                        log_debug("Parsing arguments at position \"%s\"%s", *f, glyph(GLYPH_ELLIPSIS));
18✔
2226

2227
                        r = parse_arguments(c, args);
18✔
2228
                        if (r < 0)
18✔
2229
                                return r;
2230
                } else {
2231
                        log_debug("Reading config file \"%s\"%s", *f, glyph(GLYPH_ELLIPSIS));
116✔
2232

2233
                        /* Just warn, ignore result otherwise */
2234
                        (void) read_config_file(c, *f, /* ignore_enoent= */ true);
94✔
2235
                }
2236

2237
        return 0;
2238
}
2239

2240
static int read_credential_lines(Context *c) {
113✔
2241
        _cleanup_free_ char *j = NULL;
113✔
2242
        const char *d;
113✔
2243
        int r;
113✔
2244

2245
        assert(c);
113✔
2246

2247
        r = get_credentials_dir(&d);
113✔
2248
        if (r == -ENXIO)
113✔
2249
                return 0;
2250
        if (r < 0)
1✔
2251
                return log_error_errno(r, "Failed to get credentials directory: %m");
×
2252

2253
        j = path_join(d, "sysusers.extra");
1✔
2254
        if (!j)
1✔
2255
                return log_oom();
×
2256

2257
        (void) read_config_file(c, j, /* ignore_enoent= */ true);
1✔
2258
        return 0;
2259
}
2260

2261
static int run(int argc, char *argv[]) {
113✔
2262
#ifndef STANDALONE
2263
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
113✔
2264
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
×
2265
#endif
2266
        _cleanup_close_ int lock = -EBADF;
113✔
2267
        _cleanup_(context_done) Context c = {
113✔
2268
                .audit_fd = -EBADF,
2269
                .search_uid = UID_INVALID,
2270
        };
2271

2272
        Item *i;
113✔
2273
        int r;
113✔
2274

2275
        r = parse_argv(argc, argv);
113✔
2276
        if (r <= 0)
113✔
2277
                return r;
2278

2279
        log_setup();
113✔
2280

2281
        if (arg_cat_flags != CAT_CONFIG_OFF)
113✔
2282
                return cat_config();
×
2283

2284
        if (should_bypass("SYSTEMD_SYSUSERS"))
113✔
2285
                return 0;
2286

2287
        umask(0022);
113✔
2288

2289
        r = mac_init();
113✔
2290
        if (r < 0)
113✔
2291
                return r;
2292

2293
#ifndef STANDALONE
2294
        if (arg_image) {
113✔
2295
                assert(!arg_root);
×
2296

2297
                r = mount_image_privately_interactively(
×
2298
                                arg_image,
2299
                                arg_image_policy,
2300
                                DISSECT_IMAGE_GENERIC_ROOT |
2301
                                DISSECT_IMAGE_REQUIRE_ROOT |
2302
                                DISSECT_IMAGE_VALIDATE_OS |
2303
                                DISSECT_IMAGE_RELAX_VAR_CHECK |
2304
                                DISSECT_IMAGE_FSCK |
2305
                                DISSECT_IMAGE_GROWFS |
2306
                                DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
2307
                                &mounted_dir,
2308
                                /* ret_dir_fd= */ NULL,
2309
                                &loop_device);
2310
                if (r < 0)
×
2311
                        return r;
2312

2313
                arg_root = strdup(mounted_dir);
×
2314
                if (!arg_root)
×
2315
                        return log_oom();
×
2316
        }
2317
#else
2318
        assert(!arg_image);
2319
#endif
2320

2321
        /* Prepare to emit audit events, but only if we're operating on the host system. */
2322
        if (!arg_root)
113✔
2323
                c.audit_fd = open_audit_fd_or_warn();
5✔
2324

2325
        /* If command line arguments are specified along with --replace, read all configuration files and
2326
         * insert the positional arguments at the specified place. Otherwise, if command line arguments are
2327
         * specified, execute just them, and finally, without --replace= or any positional arguments, just
2328
         * read configuration and execute it. */
2329
        if (arg_replace || optind >= argc)
113✔
2330
                r = read_config_files(&c, argv + optind);
90✔
2331
        else
2332
                r = parse_arguments(&c, argv + optind);
23✔
2333
        if (r < 0)
113✔
2334
                return r;
2335

2336
        r = read_credential_lines(&c);
113✔
2337
        if (r < 0)
113✔
2338
                return r;
2339

2340
        /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our
2341
         * detection whether the names or UID/GID area already used otherwise doesn't get confused. After
2342
         * all, even though nss-systemd synthesizes these users/groups, they should still appear in
2343
         * /etc/passwd and /etc/group, as the synthesizing logic is merely supposed to be fallback for cases
2344
         * where we run with a completely unpopulated /etc. */
2345
        if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0)
113✔
2346
                return log_error_errno(errno, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
×
2347

2348
        if (!c.uid_range) {
113✔
2349
                /* Default to default range of SYSTEMD_UID_MIN..SYSTEM_UID_MAX. */
2350
                r = read_login_defs(&c.login_defs, NULL, arg_root);
113✔
2351
                if (r < 0)
113✔
2352
                        return log_error_errno(r, "Failed to read %s%s: %m",
×
2353
                                               strempty(arg_root), "/etc/login.defs");
2354

2355
                c.login_defs_need_warning = true;
113✔
2356

2357
                /* We pick a range that very conservative: we look at compiled-in maximum and the value in
2358
                 * /etc/login.defs. That way the UIDs/GIDs which we allocate will be interpreted correctly,
2359
                 * even if /etc/login.defs is removed later. (The bottom bound doesn't matter much, since
2360
                 * it's only used during allocation, so we use the configured value directly). */
2361
                uid_t begin = c.login_defs.system_alloc_uid_min,
113✔
2362
                      end = MIN3((uid_t) SYSTEM_UID_MAX, c.login_defs.system_uid_max, c.login_defs.system_gid_max);
113✔
2363
                if (begin < end) {
113✔
2364
                        r = uid_range_add(&c.uid_range, begin, end - begin + 1);
113✔
2365
                        if (r < 0)
113✔
2366
                                return log_oom();
×
2367
                }
2368
        }
2369

2370
        r = add_implicit(&c);
113✔
2371
        if (r < 0)
113✔
2372
                return r;
2373

2374
        if (!arg_dry_run) {
113✔
2375
                lock = take_etc_passwd_lock(arg_root);
113✔
2376
                if (lock < 0)
113✔
2377
                        return log_error_errno(lock, "Failed to take /etc/passwd lock: %m");
×
2378
        }
2379

2380
        r = load_user_database(&c);
113✔
2381
        if (r < 0)
113✔
2382
                return log_error_errno(r, "Failed to load user database: %m");
×
2383

2384
        r = load_group_database(&c);
113✔
2385
        if (r < 0)
113✔
2386
                return log_error_errno(r, "Failed to read group database: %m");
×
2387

2388
        ORDERED_HASHMAP_FOREACH(i, c.groups)
489✔
2389
                (void) process_item(&c, i);
376✔
2390

2391
        ORDERED_HASHMAP_FOREACH(i, c.users)
412✔
2392
                (void) process_item(&c, i);
299✔
2393

2394
        return write_files(&c);
113✔
2395
}
2396

2397
DEFINE_MAIN_FUNCTION(run);
226✔
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