]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysusers/sysusers.c
Merge pull request #31524 from poettering/secure-getenv-naming-fix
[thirdparty/systemd.git] / src / sysusers / sysusers.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
1b992147 2
3f6fd1ba 3#include <getopt.h>
932ad62b 4#include <utmp.h>
1b992147 5
b5efdb8a 6#include "alloc-util.h"
d6b4d1c7 7#include "build.h"
f461a28d 8#include "chase.h"
1b992147 9#include "conf-files.h"
28db6fbf 10#include "constants.h"
1b992147 11#include "copy.h"
99e9f896 12#include "creds-util.h"
0c3ee127 13#include "dissect-image.h"
3fa8a114 14#include "env-util.h"
a0f29c76 15#include "fd-util.h"
ee228be1 16#include "fileio.h"
f97b34a6 17#include "format-util.h"
dcd5c891 18#include "fs-util.h"
3f6fd1ba 19#include "hashmap.h"
99e9f896 20#include "libcrypt-util.h"
71da1673 21#include "main-func.h"
99e9f896 22#include "memory-util.h"
0c3ee127 23#include "mount-util.h"
d481b830 24#include "nscd-flush.h"
dcd5c891 25#include "pager.h"
614b022c 26#include "parse-argument.h"
3f6fd1ba 27#include "path-util.h"
294bf0c3 28#include "pretty-print.h"
3f6fd1ba 29#include "selinux-util.h"
0c3ee127 30#include "set.h"
07630cea 31#include "smack-util.h"
3f6fd1ba 32#include "specifier.h"
5f465fda 33#include "stat-util.h"
07630cea 34#include "string-util.h"
3f6fd1ba 35#include "strv.h"
bf819d3a 36#include "sync-util.h"
e4de7287 37#include "tmpfile-util-label.h"
8e1ac16b 38#include "uid-classification.h"
3f6fd1ba 39#include "uid-range.h"
a0f29c76 40#include "user-util.h"
3f6fd1ba 41#include "utf8.h"
1b992147
LP
42
43typedef enum ItemType {
9a5af4b7
ZJS
44 ADD_USER = 'u',
45 ADD_GROUP = 'g',
a12b0cc3 46 ADD_MEMBER = 'm',
9a5af4b7 47 ADD_RANGE = 'r',
1b992147 48} ItemType;
dcd5c891 49
cf1ab844 50static const char* item_type_to_string(ItemType t) {
9a5af4b7
ZJS
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
1b992147
LP
65typedef struct Item {
66 ItemType type;
67
68 char *name;
649916d3 69 char *group_name;
1b992147
LP
70 char *uid_path;
71 char *gid_path;
72 char *description;
7629889c 73 char *home;
7b1aaf66 74 char *shell;
1b992147
LP
75
76 gid_t gid;
77 uid_t uid;
78
9a87bdd7
ZJS
79 char *filename;
80 unsigned line;
81
616c5354 82 bool gid_set;
fb959f14 83
3f316701
ZJS
84 /* When set the group with the specified GID must exist
85 * and the check if a UID clashes with the GID is skipped.
fb959f14 86 */
616c5354 87 bool id_set_strict;
fb959f14 88
616c5354 89 bool uid_set;
1b992147 90
616c5354
ZJS
91 bool todo_user;
92 bool todo_group;
1b992147
LP
93} Item;
94
95static char *arg_root = NULL;
0c3ee127 96static char *arg_image = NULL;
918560d7 97static CatFlags arg_cat_flags = CAT_CONFIG_OFF;
d16a1c1b 98static const char *arg_replace = NULL;
64fe1095 99static bool arg_dry_run = false;
1b600bd5 100static bool arg_inline = false;
0221d68a 101static PagerFlags arg_pager_flags = 0;
84be0c71 102static ImagePolicy *arg_image_policy = NULL;
1b992147 103
71da1673 104STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
0c3ee127 105STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
84be0c71 106STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
71da1673 107
d2a16737
LP
108typedef struct Context {
109 OrderedHashmap *users, *groups;
110 OrderedHashmap *todo_uids, *todo_gids;
111 OrderedHashmap *members;
112
113 Hashmap *database_by_uid, *database_by_username;
114 Hashmap *database_by_gid, *database_by_groupname;
115
116 /* A helper set to hold names that are used by database_by_{uid,gid,username,groupname} above. */
117 Set *names;
118
119 uid_t search_uid;
eb7d7d14 120 UIDRange *uid_range;
d2a16737
LP
121
122 UGIDAllocationRange login_defs;
123 bool login_defs_need_warning;
124} Context;
125
126static void context_done(Context *c) {
127 assert(c);
128
129 ordered_hashmap_free(c->groups);
130 ordered_hashmap_free(c->users);
131 ordered_hashmap_free(c->members);
132 ordered_hashmap_free(c->todo_uids);
133 ordered_hashmap_free(c->todo_gids);
134
135 hashmap_free(c->database_by_uid);
136 hashmap_free(c->database_by_username);
137 hashmap_free(c->database_by_gid);
138 hashmap_free(c->database_by_groupname);
139
140 set_free_free(c->names);
141 uid_range_free(c->uid_range);
142}
143
d2a16737
LP
144static void maybe_emit_login_defs_warning(Context *c) {
145 assert(c);
146
147 if (!c->login_defs_need_warning)
4b6f9b20
ZJS
148 return;
149
d2a16737
LP
150 if (c->login_defs.system_alloc_uid_min != SYSTEM_ALLOC_UID_MIN ||
151 c->login_defs.system_uid_max != SYSTEM_UID_MAX)
4b6f9b20
ZJS
152 log_warning("login.defs specifies UID allocation range "UID_FMT"–"UID_FMT
153 " that is different than the built-in defaults ("UID_FMT"–"UID_FMT")",
d2a16737 154 c->login_defs.system_alloc_uid_min, c->login_defs.system_uid_max,
2676befc 155 (uid_t) SYSTEM_ALLOC_UID_MIN, (uid_t) SYSTEM_UID_MAX);
d2a16737
LP
156 if (c->login_defs.system_alloc_gid_min != SYSTEM_ALLOC_GID_MIN ||
157 c->login_defs.system_gid_max != SYSTEM_GID_MAX)
4b6f9b20
ZJS
158 log_warning("login.defs specifies GID allocation range "GID_FMT"–"GID_FMT
159 " that is different than the built-in defaults ("GID_FMT"–"GID_FMT")",
d2a16737 160 c->login_defs.system_alloc_gid_min, c->login_defs.system_gid_max,
2676befc 161 (gid_t) SYSTEM_ALLOC_GID_MIN, (gid_t) SYSTEM_GID_MAX);
4b6f9b20 162
d2a16737 163 c->login_defs_need_warning = false;
4b6f9b20
ZJS
164}
165
d2a16737 166static int load_user_database(Context *c) {
1b992147
LP
167 _cleanup_fclose_ FILE *f = NULL;
168 const char *passwd_path;
169 struct passwd *pw;
170 int r;
171
d2a16737
LP
172 assert(c);
173
1d13f648 174 passwd_path = prefix_roota(arg_root, "/etc/passwd");
1b992147
LP
175 f = fopen(passwd_path, "re");
176 if (!f)
177 return errno == ENOENT ? 0 : -errno;
178
d2a16737 179 r = hashmap_ensure_allocated(&c->database_by_username, &string_hash_ops);
1b992147
LP
180 if (r < 0)
181 return r;
182
d2a16737 183 r = hashmap_ensure_allocated(&c->database_by_uid, NULL);
1b992147
LP
184 if (r < 0)
185 return r;
186
c8e02e40 187 /* Note that we use NULL, i.e. trivial_hash_ops here, so identical strings can exist in the set. */
d2a16737 188 r = set_ensure_allocated(&c->names, NULL);
1b992147
LP
189 if (r < 0)
190 return r;
191
100d5f6e 192 while ((r = fgetpwent_sane(f, &pw)) > 0) {
1b992147 193
c8e02e40 194 char *n = strdup(pw->pw_name);
1b992147
LP
195 if (!n)
196 return -ENOMEM;
197
d2a16737 198 r = set_consume(c->names, n);
c8e02e40
ZJS
199 if (r < 0)
200 return r;
201 assert(r > 0); /* The set uses pointer comparisons, so n must not be in the set. */
202
d2a16737 203 r = hashmap_put(c->database_by_username, n, UID_TO_PTR(pw->pw_uid));
c8e02e40
ZJS
204 if (r == -EEXIST)
205 log_debug_errno(r, "%s: user '%s' is listed twice, ignoring duplicate uid.",
206 passwd_path, n);
207 else if (r < 0)
208 return r;
209
d2a16737 210 r = hashmap_put(c->database_by_uid, UID_TO_PTR(pw->pw_uid), n);
c8e02e40
ZJS
211 if (r == -EEXIST)
212 log_debug_errno(r, "%s: uid "UID_FMT" is listed twice, ignoring duplicate name.",
213 passwd_path, pw->pw_uid);
214 else if (r < 0)
215 return r;
1b992147 216 }
100d5f6e 217 return r;
1b992147
LP
218}
219
d2a16737 220static int load_group_database(Context *c) {
1b992147
LP
221 _cleanup_fclose_ FILE *f = NULL;
222 const char *group_path;
223 struct group *gr;
224 int r;
225
d2a16737
LP
226 assert(c);
227
1d13f648 228 group_path = prefix_roota(arg_root, "/etc/group");
1b992147
LP
229 f = fopen(group_path, "re");
230 if (!f)
231 return errno == ENOENT ? 0 : -errno;
232
d2a16737 233 r = hashmap_ensure_allocated(&c->database_by_groupname, &string_hash_ops);
1b992147
LP
234 if (r < 0)
235 return r;
236
d2a16737 237 r = hashmap_ensure_allocated(&c->database_by_gid, NULL);
1b992147
LP
238 if (r < 0)
239 return r;
240
c8e02e40 241 /* Note that we use NULL, i.e. trivial_hash_ops here, so identical strings can exist in the set. */
d2a16737 242 r = set_ensure_allocated(&c->names, NULL);
1b992147
LP
243 if (r < 0)
244 return r;
245
f030d36c 246 while ((r = fgetgrent_sane(f, &gr)) > 0) {
1b992147 247
c8e02e40 248 char *n = strdup(gr->gr_name);
1b992147
LP
249 if (!n)
250 return -ENOMEM;
251
d2a16737 252 r = set_consume(c->names, n);
c8e02e40
ZJS
253 if (r < 0)
254 return r;
255 assert(r > 0); /* The set uses pointer comparisons, so n must not be in the set. */
256
d2a16737 257 r = hashmap_put(c->database_by_groupname, n, GID_TO_PTR(gr->gr_gid));
c8e02e40
ZJS
258 if (r == -EEXIST)
259 log_debug_errno(r, "%s: group '%s' is listed twice, ignoring duplicate gid.",
260 group_path, n);
261 else if (r < 0)
262 return r;
263
d2a16737 264 r = hashmap_put(c->database_by_gid, GID_TO_PTR(gr->gr_gid), n);
c8e02e40
ZJS
265 if (r == -EEXIST)
266 log_debug_errno(r, "%s: gid "GID_FMT" is listed twice, ignoring duplicate name.",
267 group_path, gr->gr_gid);
268 else if (r < 0)
269 return r;
1b992147 270 }
f030d36c 271 return r;
1b992147
LP
272}
273
9f1c1940 274static int make_backup(const char *target, const char *x) {
62c03398 275 _cleanup_(unlink_and_freep) char *dst_tmp = NULL;
9f1c1940 276 _cleanup_fclose_ FILE *dst = NULL;
5bb1d7fb 277 _cleanup_close_ int src = -EBADF;
62c03398 278 const char *backup;
1b992147
LP
279 struct stat st;
280 int r;
281
62c03398
LP
282 assert(target);
283 assert(x);
284
1b992147
LP
285 src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
286 if (src < 0) {
287 if (errno == ENOENT) /* No backup necessary... */
288 return 0;
289
290 return -errno;
291 }
292
293 if (fstat(src, &st) < 0)
294 return -errno;
295
62c03398 296 r = fopen_temporary_label(
47fb161e 297 target, /* The path for which to the look up the label */
62c03398 298 x, /* Where we want the file actually to end up */
47fb161e
ZJS
299 &dst, /* The temporary file we write to */
300 &dst_tmp);
9f1c1940
ZJS
301 if (r < 0)
302 return r;
1b992147 303
f5fbe71d 304 r = copy_bytes(src, fileno(dst), UINT64_MAX, COPY_REFLINK);
1b992147 305 if (r < 0)
62c03398 306 return r;
9f1c1940 307
63c372cb 308 backup = strjoina(x, "-");
9f1c1940 309
62c03398
LP
310 /* Copy over the access mask. Don't fail on chmod() or chown(). If it stays owned by us and/or
311 * unreadable by others, then it isn't too bad... */
b78d7f24 312 r = fchmod_and_chown_with_fallback(fileno(dst), dst_tmp, st.st_mode & 07777, st.st_uid, st.st_gid);
c039af23
YW
313 if (r < 0)
314 log_warning_errno(r, "Failed to change access mode or ownership of %s: %m", backup);
1b992147 315
62c03398 316 if (futimens(fileno(dst), (const struct timespec[2]) { st.st_atim, st.st_mtim }) < 0)
56f64d95 317 log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup);
1b992147 318
62c03398 319 r = fsync_full(fileno(dst));
0675e94a 320 if (r < 0)
62c03398 321 return r;
0675e94a 322
62c03398
LP
323 if (rename(dst_tmp, backup) < 0)
324 return errno;
1b992147 325
7802194a 326 dst_tmp = mfree(dst_tmp); /* disable the unlink_and_freep() hook now that the file has been renamed */
1b992147 327 return 0;
1b992147
LP
328}
329
d2a16737
LP
330static int putgrent_with_members(
331 Context *c,
332 const struct group *gr,
333 FILE *group) {
334
a12b0cc3 335 char **a;
010cd1dc 336 int r;
a12b0cc3 337
d2a16737 338 assert(c);
a12b0cc3
LP
339 assert(gr);
340 assert(group);
341
d2a16737 342 a = ordered_hashmap_get(c->members, gr->gr_name);
a12b0cc3
LP
343 if (a) {
344 _cleanup_strv_free_ char **l = NULL;
345 bool added = false;
a12b0cc3
LP
346
347 l = strv_copy(gr->gr_mem);
348 if (!l)
349 return -ENOMEM;
350
351 STRV_FOREACH(i, a) {
d29cc4d6 352 if (strv_contains(l, *i))
a12b0cc3
LP
353 continue;
354
010cd1dc
LP
355 r = strv_extend(&l, *i);
356 if (r < 0)
357 return r;
a12b0cc3
LP
358
359 added = true;
360 }
361
362 if (added) {
363 struct group t;
364
365 strv_uniq(l);
366 strv_sort(l);
367
368 t = *gr;
369 t.gr_mem = l;
370
100d5f6e
FB
371 r = putgrent_sane(&t, group);
372 return r < 0 ? r : 1;
a12b0cc3
LP
373 }
374 }
375
100d5f6e 376 return putgrent_sane(gr, group);
a12b0cc3
LP
377}
378
349cc4a5 379#if ENABLE_GSHADOW
d2a16737
LP
380static int putsgent_with_members(
381 Context *c,
382 const struct sgrp *sg,
383 FILE *gshadow) {
384
9ab315cc 385 char **a;
010cd1dc 386 int r;
9ab315cc
LP
387
388 assert(sg);
389 assert(gshadow);
390
d2a16737 391 a = ordered_hashmap_get(c->members, sg->sg_namp);
9ab315cc
LP
392 if (a) {
393 _cleanup_strv_free_ char **l = NULL;
394 bool added = false;
9ab315cc
LP
395
396 l = strv_copy(sg->sg_mem);
397 if (!l)
398 return -ENOMEM;
399
400 STRV_FOREACH(i, a) {
d29cc4d6 401 if (strv_contains(l, *i))
9ab315cc
LP
402 continue;
403
010cd1dc
LP
404 r = strv_extend(&l, *i);
405 if (r < 0)
406 return r;
9ab315cc
LP
407
408 added = true;
409 }
410
411 if (added) {
412 struct sgrp t;
413
414 strv_uniq(l);
415 strv_sort(l);
416
417 t = *sg;
418 t.sg_mem = l;
419
100d5f6e
FB
420 r = putsgent_sane(&t, gshadow);
421 return r < 0 ? r : 1;
9ab315cc
LP
422 }
423 }
424
100d5f6e 425 return putsgent_sane(sg, gshadow);
9ab315cc 426}
b14e1b43 427#endif
9ab315cc 428
5f465fda
ZJS
429static const char* pick_shell(const Item *i) {
430 if (i->type != ADD_USER)
431 return NULL;
432 if (i->shell)
433 return i->shell;
434 if (i->uid_set && i->uid == 0)
8a7adccb 435 return default_root_shell(arg_root);
5f465fda 436 return NOLOGIN;
7b1aaf66
ZJS
437}
438
d2a16737
LP
439static int write_temporary_passwd(
440 Context *c,
441 const char *passwd_path,
442 FILE **ret_tmpfile,
443 char **ret_tmpfile_path) {
444
b20b0b66 445 _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
1dd98a71 446 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
563dc6f8 447 struct passwd *pw = NULL;
1b992147
LP
448 Item *i;
449 int r;
450
d2a16737
LP
451 assert(c);
452
453 if (ordered_hashmap_isempty(c->todo_uids))
b20b0b66 454 return 0;
1b992147 455
64fe1095 456 if (arg_dry_run) {
28e5e1e9 457 log_info("Would write /etc/passwd%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
64fe1095
ZJS
458 return 0;
459 }
460
b20b0b66
FB
461 r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
462 if (r < 0)
335f6ab4 463 return log_debug_errno(r, "Failed to open temporary copy of %s: %m", passwd_path);
b20b0b66
FB
464
465 original = fopen(passwd_path, "re");
466 if (original) {
b20b0b66 467
82855aa6
ZJS
468 /* Allow fallback path for when /proc is not mounted. On any normal system /proc will be
469 * mounted, but e.g. when 'dnf --installroot' is used, it might not be. There is no security
470 * relevance here, since the environment is ultimately trusted, and not requiring /proc makes
471 * it easier to depend on sysusers in packaging scripts and suchlike. */
472 r = copy_rights_with_fallback(fileno(original), fileno(passwd), passwd_tmp);
1b992147 473 if (r < 0)
335f6ab4
ZJS
474 return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
475 passwd_path, passwd_tmp);
1b992147 476
100d5f6e 477 while ((r = fgetpwent_sane(original, &pw)) > 0) {
d2a16737 478 i = ordered_hashmap_get(c->users, pw->pw_name);
330d1def
ZJS
479 if (i && i->todo_user)
480 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
481 "%s: User \"%s\" already exists.",
482 passwd_path, pw->pw_name);
e3c72c21 483
d2a16737 484 if (ordered_hashmap_contains(c->todo_uids, UID_TO_PTR(pw->pw_uid)))
330d1def
ZJS
485 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
486 "%s: Detected collision for UID " UID_FMT ".",
487 passwd_path, pw->pw_uid);
b20b0b66 488
563dc6f8
FB
489 /* Make sure we keep the NIS entries (if any) at the end. */
490 if (IN_SET(pw->pw_name[0], '+', '-'))
491 break;
492
100d5f6e
FB
493 r = putpwent_sane(pw, passwd);
494 if (r < 0)
335f6ab4
ZJS
495 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary passwd file: %m",
496 pw->pw_name);
b20b0b66 497 }
100d5f6e 498 if (r < 0)
335f6ab4 499 return log_debug_errno(r, "Failed to read %s: %m", passwd_path);
1dd98a71
FB
500
501 } else {
502 if (errno != ENOENT)
335f6ab4 503 return log_debug_errno(errno, "Failed to open %s: %m", passwd_path);
1dd98a71 504 if (fchmod(fileno(passwd), 0644) < 0)
335f6ab4 505 return log_debug_errno(errno, "Failed to fchmod %s: %m", passwd_tmp);
b20b0b66
FB
506 }
507
d2a16737 508 ORDERED_HASHMAP_FOREACH(i, c->todo_uids) {
99e9f896
LP
509 _cleanup_free_ char *creds_shell = NULL, *cn = NULL;
510
b20b0b66
FB
511 struct passwd n = {
512 .pw_name = i->name,
513 .pw_uid = i->uid,
514 .pw_gid = i->gid,
d610e142 515 .pw_gecos = (char*) strempty(i->description),
1b992147 516
b20b0b66 517 /* "x" means the password is stored in the shadow file */
53c25ac9 518 .pw_passwd = (char*) PASSWORD_SEE_SHADOW,
a12b0cc3 519
b20b0b66 520 /* We default to the root directory as home */
636e72bc 521 .pw_dir = i->home ?: (char*) "/",
1b992147 522
b20b0b66
FB
523 /* Initialize the shell to nologin, with one exception:
524 * for root we patch in something special */
5f465fda 525 .pw_shell = (char*) pick_shell(i),
b20b0b66 526 };
9ab315cc 527
99e9f896
LP
528 /* Try to pick up the shell for this account via the credentials logic */
529 cn = strjoin("passwd.shell.", i->name);
530 if (!cn)
531 return -ENOMEM;
532
533 r = read_credential(cn, (void**) &creds_shell, NULL);
534 if (r < 0)
535 log_debug_errno(r, "Couldn't read credential '%s', ignoring: %m", cn);
536 else
537 n.pw_shell = creds_shell;
538
100d5f6e
FB
539 r = putpwent_sane(&n, passwd);
540 if (r < 0)
335f6ab4 541 return log_debug_errno(r, "Failed to add new user \"%s\" to temporary passwd file: %m",
7e4c6149 542 i->name);
b20b0b66
FB
543 }
544
563dc6f8
FB
545 /* Append the remaining NIS entries if any */
546 while (pw) {
100d5f6e
FB
547 r = putpwent_sane(pw, passwd);
548 if (r < 0)
335f6ab4
ZJS
549 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary passwd file: %m",
550 pw->pw_name);
563dc6f8 551
100d5f6e
FB
552 r = fgetpwent_sane(original, &pw);
553 if (r < 0)
335f6ab4 554 return log_debug_errno(r, "Failed to read %s: %m", passwd_path);
100d5f6e
FB
555 if (r == 0)
556 break;
563dc6f8 557 }
563dc6f8 558
19193b48 559 r = fflush_sync_and_check(passwd);
b20b0b66 560 if (r < 0)
335f6ab4 561 return log_debug_errno(r, "Failed to flush %s: %m", passwd_tmp);
b20b0b66 562
9740e677
ZJS
563 *ret_tmpfile = TAKE_PTR(passwd);
564 *ret_tmpfile_path = TAKE_PTR(passwd_tmp);
1cc6c93a 565
b20b0b66 566 return 0;
b20b0b66
FB
567}
568
3fa8a114
JSMR
569static usec_t epoch_or_now(void) {
570 uint64_t epoch;
571
dbe253aa 572 if (secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch) >= 0) {
3fa8a114
JSMR
573 if (epoch > UINT64_MAX/USEC_PER_SEC) /* Overflow check */
574 return USEC_INFINITY;
575 return (usec_t) epoch * USEC_PER_SEC;
576 }
577
578 return now(CLOCK_REALTIME);
579}
580
d2a16737
LP
581static int write_temporary_shadow(
582 Context *c,
583 const char *shadow_path,
584 FILE **ret_tmpfile,
585 char **ret_tmpfile_path) {
586
b20b0b66 587 _cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
1dd98a71 588 _cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
19ec7de2 589 struct spwd *sp = NULL;
b20b0b66
FB
590 long lstchg;
591 Item *i;
592 int r;
593
d2a16737
LP
594 assert(c);
595
596 if (ordered_hashmap_isempty(c->todo_uids))
b20b0b66
FB
597 return 0;
598
64fe1095 599 if (arg_dry_run) {
28e5e1e9 600 log_info("Would write /etc/shadow%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
64fe1095
ZJS
601 return 0;
602 }
603
b20b0b66
FB
604 r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
605 if (r < 0)
335f6ab4 606 return log_debug_errno(r, "Failed to open temporary copy of %s: %m", shadow_path);
9ab315cc 607
3fa8a114 608 lstchg = (long) (epoch_or_now() / USEC_PER_DAY);
b20b0b66
FB
609
610 original = fopen(shadow_path, "re");
611 if (original) {
b20b0b66 612
82855aa6 613 r = copy_rights_with_fallback(fileno(original), fileno(shadow), shadow_tmp);
9ab315cc 614 if (r < 0)
335f6ab4
ZJS
615 return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
616 shadow_path, shadow_tmp);
9ab315cc 617
100d5f6e 618 while ((r = fgetspent_sane(original, &sp)) > 0) {
d2a16737 619 i = ordered_hashmap_get(c->users, sp->sp_namp);
b20b0b66
FB
620 if (i && i->todo_user) {
621 /* we will update the existing entry */
622 sp->sp_lstchg = lstchg;
e3c72c21 623
b20b0b66
FB
624 /* only the /etc/shadow stage is left, so we can
625 * safely remove the item from the todo set */
626 i->todo_user = false;
d2a16737 627 ordered_hashmap_remove(c->todo_uids, UID_TO_PTR(i->uid));
9ab315cc 628 }
b20b0b66 629
19ec7de2
FB
630 /* Make sure we keep the NIS entries (if any) at the end. */
631 if (IN_SET(sp->sp_namp[0], '+', '-'))
632 break;
633
100d5f6e
FB
634 r = putspent_sane(sp, shadow);
635 if (r < 0)
335f6ab4
ZJS
636 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary shadow file: %m",
637 sp->sp_namp);
638
b20b0b66 639 }
100d5f6e 640 if (r < 0)
335f6ab4 641 return log_debug_errno(r, "Failed to read %s: %m", shadow_path);
1dd98a71
FB
642
643 } else {
644 if (errno != ENOENT)
335f6ab4 645 return log_debug_errno(errno, "Failed to open %s: %m", shadow_path);
1dd98a71 646 if (fchmod(fileno(shadow), 0000) < 0)
335f6ab4 647 return log_debug_errno(errno, "Failed to fchmod %s: %m", shadow_tmp);
b20b0b66 648 }
9ab315cc 649
d2a16737 650 ORDERED_HASHMAP_FOREACH(i, c->todo_uids) {
99e9f896 651 _cleanup_(erase_and_freep) char *creds_password = NULL;
ff86850b 652 bool is_hashed;
99e9f896 653
b20b0b66
FB
654 struct spwd n = {
655 .sp_namp = i->name,
b20b0b66
FB
656 .sp_lstchg = lstchg,
657 .sp_min = -1,
658 .sp_max = -1,
659 .sp_warn = -1,
660 .sp_inact = -1,
12c82937 661 .sp_expire = -1,
f5fbe71d 662 .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
b20b0b66 663 };
9ab315cc 664
ff86850b
LN
665 r = get_credential_user_password(i->name, &creds_password, &is_hashed);
666 if (r < 0)
667 log_debug_errno(r, "Couldn't read password credential for user '%s', ignoring: %m", i->name);
99e9f896 668
ff86850b
LN
669 if (creds_password && !is_hashed) {
670 _cleanup_(erase_and_freep) char* plaintext_password = TAKE_PTR(creds_password);
671 r = hash_password(plaintext_password, &creds_password);
99e9f896 672 if (r < 0)
ff86850b
LN
673 return log_debug_errno(r, "Failed to hash password: %m");
674 }
99e9f896
LP
675
676 if (creds_password)
677 n.sp_pwdp = creds_password;
a777a592
ZJS
678 else if (streq(i->name, "root"))
679 /* Let firstboot set the password later */
680 n.sp_pwdp = (char*) PASSWORD_UNPROVISIONED;
681 else
682 n.sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID;
99e9f896 683
100d5f6e
FB
684 r = putspent_sane(&n, shadow);
685 if (r < 0)
335f6ab4 686 return log_debug_errno(r, "Failed to add new user \"%s\" to temporary shadow file: %m",
7e4c6149 687 i->name);
1b992147 688 }
19ec7de2
FB
689
690 /* Append the remaining NIS entries if any */
691 while (sp) {
100d5f6e
FB
692 r = putspent_sane(sp, shadow);
693 if (r < 0)
335f6ab4
ZJS
694 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary shadow file: %m",
695 sp->sp_namp);
19ec7de2 696
100d5f6e
FB
697 r = fgetspent_sane(original, &sp);
698 if (r < 0)
335f6ab4 699 return log_debug_errno(r, "Failed to read %s: %m", shadow_path);
100d5f6e
FB
700 if (r == 0)
701 break;
19ec7de2
FB
702 }
703 if (!IN_SET(errno, 0, ENOENT))
704 return -errno;
1b992147 705
0675e94a 706 r = fflush_sync_and_check(shadow);
b20b0b66 707 if (r < 0)
335f6ab4 708 return log_debug_errno(r, "Failed to flush %s: %m", shadow_tmp);
b20b0b66 709
9740e677
ZJS
710 *ret_tmpfile = TAKE_PTR(shadow);
711 *ret_tmpfile_path = TAKE_PTR(shadow_tmp);
1cc6c93a 712
b20b0b66 713 return 0;
b20b0b66 714}
1b992147 715
d2a16737
LP
716static int write_temporary_group(
717 Context *c,
718 const char *group_path,
719 FILE **ret_tmpfile,
720 char **ret_tmpfile_path) {
721
b20b0b66 722 _cleanup_fclose_ FILE *original = NULL, *group = NULL;
1dd98a71 723 _cleanup_(unlink_and_freep) char *group_tmp = NULL;
b20b0b66 724 bool group_changed = false;
563dc6f8 725 struct group *gr = NULL;
b20b0b66
FB
726 Item *i;
727 int r;
1b992147 728
d2a16737
LP
729 assert(c);
730
731 if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members))
b20b0b66 732 return 0;
e3c72c21 733
64fe1095 734 if (arg_dry_run) {
28e5e1e9 735 log_info("Would write /etc/group%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
64fe1095
ZJS
736 return 0;
737 }
738
b20b0b66
FB
739 r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
740 if (r < 0)
4c795066 741 return log_error_errno(r, "Failed to open temporary copy of %s: %m", group_path);
b20b0b66
FB
742
743 original = fopen(group_path, "re");
744 if (original) {
b20b0b66 745
82855aa6 746 r = copy_rights_with_fallback(fileno(original), fileno(group), group_tmp);
b20b0b66 747 if (r < 0)
4c795066 748 return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
335f6ab4 749 group_path, group_tmp);
b20b0b66 750
100d5f6e 751 while ((r = fgetgrent_sane(original, &gr)) > 0) {
b20b0b66
FB
752 /* Safety checks against name and GID collisions. Normally,
753 * this should be unnecessary, but given that we look at the
754 * entries anyway here, let's make an extra verification
755 * step that we don't generate duplicate entries. */
756
d2a16737 757 i = ordered_hashmap_get(c->groups, gr->gr_name);
330d1def
ZJS
758 if (i && i->todo_group)
759 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
760 "%s: Group \"%s\" already exists.",
761 group_path, gr->gr_name);
b20b0b66 762
d2a16737 763 if (ordered_hashmap_contains(c->todo_gids, GID_TO_PTR(gr->gr_gid)))
330d1def
ZJS
764 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
765 "%s: Detected collision for GID " GID_FMT ".",
766 group_path, gr->gr_gid);
1b992147 767
563dc6f8
FB
768 /* Make sure we keep the NIS entries (if any) at the end. */
769 if (IN_SET(gr->gr_name[0], '+', '-'))
770 break;
771
d2a16737 772 r = putgrent_with_members(c, gr, group);
b20b0b66 773 if (r < 0)
4c795066 774 return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
335f6ab4 775 gr->gr_name);
b20b0b66
FB
776 if (r > 0)
777 group_changed = true;
b20b0b66 778 }
100d5f6e 779 if (r < 0)
4c795066 780 return log_error_errno(r, "Failed to read %s: %m", group_path);
1dd98a71
FB
781
782 } else {
783 if (errno != ENOENT)
4c795066 784 return log_error_errno(errno, "Failed to open %s: %m", group_path);
1dd98a71 785 if (fchmod(fileno(group), 0644) < 0)
4c795066 786 return log_error_errno(errno, "Failed to fchmod %s: %m", group_tmp);
b20b0b66 787 }
7629889c 788
d2a16737 789 ORDERED_HASHMAP_FOREACH(i, c->todo_gids) {
b20b0b66
FB
790 struct group n = {
791 .gr_name = i->name,
792 .gr_gid = i->gid,
53c25ac9 793 .gr_passwd = (char*) PASSWORD_SEE_SHADOW,
b20b0b66 794 };
1b992147 795
d2a16737 796 r = putgrent_with_members(c, &n, group);
b20b0b66 797 if (r < 0)
4c795066 798 return log_error_errno(r, "Failed to add new group \"%s\" to temporary group file: %m",
335f6ab4 799 gr->gr_name);
7629889c 800
b20b0b66
FB
801 group_changed = true;
802 }
1b992147 803
563dc6f8
FB
804 /* Append the remaining NIS entries if any */
805 while (gr) {
100d5f6e
FB
806 r = putgrent_sane(gr, group);
807 if (r < 0)
4c795066 808 return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
335f6ab4 809 gr->gr_name);
563dc6f8 810
100d5f6e
FB
811 r = fgetgrent_sane(original, &gr);
812 if (r < 0)
4c795066 813 return log_error_errno(r, "Failed to read %s: %m", group_path);
100d5f6e
FB
814 if (r == 0)
815 break;
563dc6f8 816 }
563dc6f8 817
0675e94a 818 r = fflush_sync_and_check(group);
b20b0b66 819 if (r < 0)
4c795066 820 return log_error_errno(r, "Failed to flush %s: %m", group_tmp);
1b992147 821
b20b0b66 822 if (group_changed) {
9740e677
ZJS
823 *ret_tmpfile = TAKE_PTR(group);
824 *ret_tmpfile_path = TAKE_PTR(group_tmp);
b20b0b66
FB
825 }
826 return 0;
b20b0b66
FB
827}
828
d2a16737
LP
829static int write_temporary_gshadow(
830 Context *c,
831 const char * gshadow_path,
832 FILE **ret_tmpfile,
833 char **ret_tmpfile_path) {
834
349cc4a5 835#if ENABLE_GSHADOW
b20b0b66 836 _cleanup_fclose_ FILE *original = NULL, *gshadow = NULL;
1dd98a71 837 _cleanup_(unlink_and_freep) char *gshadow_tmp = NULL;
b20b0b66 838 bool group_changed = false;
b20b0b66
FB
839 Item *i;
840 int r;
841
d2a16737
LP
842 assert(c);
843
844 if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members))
b20b0b66
FB
845 return 0;
846
64fe1095 847 if (arg_dry_run) {
28e5e1e9 848 log_info("Would write /etc/gshadow%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
64fe1095
ZJS
849 return 0;
850 }
851
b20b0b66
FB
852 r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
853 if (r < 0)
4c795066 854 return log_error_errno(r, "Failed to open temporary copy of %s: %m", gshadow_path);
9ab315cc 855
b20b0b66
FB
856 original = fopen(gshadow_path, "re");
857 if (original) {
858 struct sgrp *sg;
859
82855aa6 860 r = copy_rights_with_fallback(fileno(original), fileno(gshadow), gshadow_tmp);
1b992147 861 if (r < 0)
4c795066 862 return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
335f6ab4 863 gshadow_path, gshadow_tmp);
9ab315cc 864
100d5f6e 865 while ((r = fgetsgent_sane(original, &sg)) > 0) {
c5abf225 866
d2a16737 867 i = ordered_hashmap_get(c->groups, sg->sg_namp);
330d1def
ZJS
868 if (i && i->todo_group)
869 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
870 "%s: Group \"%s\" already exists.",
871 gshadow_path, sg->sg_namp);
9ab315cc 872
d2a16737 873 r = putsgent_with_members(c, sg, gshadow);
fff19499 874 if (r < 0)
4c795066 875 return log_error_errno(r, "Failed to add existing group \"%s\" to temporary gshadow file: %m",
335f6ab4 876 sg->sg_namp);
b20b0b66
FB
877 if (r > 0)
878 group_changed = true;
b20b0b66 879 }
100d5f6e
FB
880 if (r < 0)
881 return r;
1dd98a71
FB
882
883 } else {
884 if (errno != ENOENT)
4c795066 885 return log_error_errno(errno, "Failed to open %s: %m", gshadow_path);
1dd98a71 886 if (fchmod(fileno(gshadow), 0000) < 0)
4c795066 887 return log_error_errno(errno, "Failed to fchmod %s: %m", gshadow_tmp);
b20b0b66 888 }
9ab315cc 889
d2a16737 890 ORDERED_HASHMAP_FOREACH(i, c->todo_gids) {
b20b0b66
FB
891 struct sgrp n = {
892 .sg_namp = i->name,
53c25ac9 893 .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
b20b0b66 894 };
9ab315cc 895
d2a16737 896 r = putsgent_with_members(c, &n, gshadow);
9ab315cc 897 if (r < 0)
4c795066 898 return log_error_errno(r, "Failed to add new group \"%s\" to temporary gshadow file: %m",
335f6ab4 899 n.sg_namp);
b20b0b66
FB
900
901 group_changed = true;
9ab315cc
LP
902 }
903
0675e94a 904 r = fflush_sync_and_check(gshadow);
b20b0b66 905 if (r < 0)
4c795066 906 return log_error_errno(r, "Failed to flush %s: %m", gshadow_tmp);
b20b0b66 907
9ab315cc 908 if (group_changed) {
9740e677
ZJS
909 *ret_tmpfile = TAKE_PTR(gshadow);
910 *ret_tmpfile_path = TAKE_PTR(gshadow_tmp);
b20b0b66 911 }
b14e1b43 912#endif
62d1c93a 913 return 0;
b20b0b66
FB
914}
915
d2a16737 916static int write_files(Context *c) {
b20b0b66 917 _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL;
1dd98a71 918 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL;
b20b0b66
FB
919 int r;
920
28e014fe
ZJS
921 const char
922 *passwd_path = prefix_roota(arg_root, "/etc/passwd"),
923 *shadow_path = prefix_roota(arg_root, "/etc/shadow"),
924 *group_path = prefix_roota(arg_root, "/etc/group"),
925 *gshadow_path = prefix_roota(arg_root, "/etc/gshadow");
b20b0b66 926
d2a16737
LP
927 assert(c);
928
929 r = write_temporary_group(c, group_path, &group, &group_tmp);
b20b0b66 930 if (r < 0)
1dd98a71 931 return r;
b20b0b66 932
d2a16737 933 r = write_temporary_gshadow(c, gshadow_path, &gshadow, &gshadow_tmp);
b20b0b66 934 if (r < 0)
1dd98a71 935 return r;
b20b0b66 936
d2a16737 937 r = write_temporary_passwd(c, passwd_path, &passwd, &passwd_tmp);
b20b0b66 938 if (r < 0)
1dd98a71 939 return r;
b20b0b66 940
d2a16737 941 r = write_temporary_shadow(c, shadow_path, &shadow, &shadow_tmp);
b20b0b66 942 if (r < 0)
1dd98a71 943 return r;
b20b0b66
FB
944
945 /* Make a backup of the old files */
946 if (group) {
947 r = make_backup("/etc/group", group_path);
948 if (r < 0)
9740e677 949 return log_error_errno(r, "Failed to backup %s: %m", group_path);
b20b0b66
FB
950 }
951 if (gshadow) {
952 r = make_backup("/etc/gshadow", gshadow_path);
953 if (r < 0)
9740e677 954 return log_error_errno(r, "Failed to backup %s: %m", gshadow_path);
1b992147
LP
955 }
956
957 if (passwd) {
9f1c1940 958 r = make_backup("/etc/passwd", passwd_path);
1b992147 959 if (r < 0)
9740e677 960 return log_error_errno(r, "Failed to backup %s: %m", passwd_path);
1b992147 961 }
9ab315cc
LP
962 if (shadow) {
963 r = make_backup("/etc/shadow", shadow_path);
964 if (r < 0)
9740e677 965 return log_error_errno(r, "Failed to backup %s: %m", shadow_path);
9ab315cc 966 }
1b992147
LP
967
968 /* And make the new files count */
b20b0b66 969 if (group) {
2da3dc69 970 r = rename_and_apply_smack_floor_label(group_tmp, group_path);
b20b0b66 971 if (r < 0)
4c795066 972 return log_error_errno(r, "Failed to rename %s to %s: %m",
335f6ab4 973 group_tmp, group_path);
b20b0b66 974 group_tmp = mfree(group_tmp);
d481b830
FB
975
976 if (!arg_root && !arg_image)
977 (void) nscd_flush_cache(STRV_MAKE("group"));
b20b0b66
FB
978 }
979 if (gshadow) {
2da3dc69 980 r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path);
b20b0b66 981 if (r < 0)
4c795066 982 return log_error_errno(r, "Failed to rename %s to %s: %m",
335f6ab4 983 gshadow_tmp, gshadow_path);
1b992147 984
b20b0b66 985 gshadow_tmp = mfree(gshadow_tmp);
1b992147
LP
986 }
987
988 if (passwd) {
2da3dc69 989 r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
c02e7b1e 990 if (r < 0)
4c795066 991 return log_error_errno(r, "Failed to rename %s to %s: %m",
335f6ab4 992 passwd_tmp, passwd_path);
1b992147 993
97b11eed 994 passwd_tmp = mfree(passwd_tmp);
d481b830
FB
995
996 if (!arg_root && !arg_image)
997 (void) nscd_flush_cache(STRV_MAKE("passwd"));
1b992147 998 }
9ab315cc 999 if (shadow) {
2da3dc69 1000 r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
c02e7b1e 1001 if (r < 0)
4c795066 1002 return log_error_errno(r, "Failed to rename %s to %s: %m",
335f6ab4 1003 shadow_tmp, shadow_path);
9ab315cc 1004
97b11eed 1005 shadow_tmp = mfree(shadow_tmp);
9ab315cc 1006 }
1b992147 1007
1dd98a71 1008 return 0;
1b992147
LP
1009}
1010
d2a16737
LP
1011static int uid_is_ok(
1012 Context *c,
1013 uid_t uid,
1014 const char *name,
1015 bool check_with_gid) {
1016
75673cd8 1017 int r;
d2a16737 1018 assert(c);
1b992147
LP
1019
1020 /* Let's see if we already have assigned the UID a second time */
d2a16737 1021 if (ordered_hashmap_get(c->todo_uids, UID_TO_PTR(uid)))
1b992147
LP
1022 return 0;
1023
1024 /* Try to avoid using uids that are already used by a group
1025 * that doesn't have the same name as our new user. */
b9ee05c2 1026 if (check_with_gid) {
62d1c93a
ZJS
1027 Item *i;
1028
d2a16737 1029 i = ordered_hashmap_get(c->todo_gids, GID_TO_PTR(uid));
b9ee05c2
MV
1030 if (i && !streq(i->name, name))
1031 return 0;
1032 }
1b992147
LP
1033
1034 /* Let's check the files directly */
d2a16737 1035 if (hashmap_contains(c->database_by_uid, UID_TO_PTR(uid)))
1b992147
LP
1036 return 0;
1037
b9ee05c2 1038 if (check_with_gid) {
62d1c93a
ZJS
1039 const char *n;
1040
d2a16737 1041 n = hashmap_get(c->database_by_gid, GID_TO_PTR(uid));
b9ee05c2
MV
1042 if (n && !streq(n, name))
1043 return 0;
1044 }
1b992147
LP
1045
1046 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
1047 if (!arg_root) {
75673cd8 1048 _cleanup_free_ struct group *g = NULL;
62d1c93a 1049
75673cd8
LP
1050 r = getpwuid_malloc(uid, /* ret= */ NULL);
1051 if (r >= 0)
1b992147 1052 return 0;
75673cd8
LP
1053 if (r != -ESRCH)
1054 return r;
1b992147 1055
b9ee05c2 1056 if (check_with_gid) {
75673cd8
LP
1057 r = getgrgid_malloc((gid_t) uid, &g);
1058 if (r >= 0) {
b9ee05c2
MV
1059 if (!streq(g->gr_name, name))
1060 return 0;
75673cd8
LP
1061 } else if (r != -ESRCH)
1062 return r;
b9ee05c2 1063 }
1b992147
LP
1064 }
1065
1066 return 1;
1067}
1068
1069static int root_stat(const char *p, struct stat *st) {
1070 const char *fix;
1071
1d13f648 1072 fix = prefix_roota(arg_root, p);
7c248223 1073 return RET_NERRNO(stat(fix, st));
1b992147
LP
1074}
1075
905ec0c0 1076static int read_id_from_file(Item *i, uid_t *ret_uid, gid_t *ret_gid) {
1b992147
LP
1077 struct stat st;
1078 bool found_uid = false, found_gid = false;
56d21cde
PDS
1079 uid_t uid = 0;
1080 gid_t gid = 0;
1b992147
LP
1081
1082 assert(i);
1083
3f316701 1084 /* First, try to get the GID directly */
905ec0c0 1085 if (ret_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
1b992147
LP
1086 gid = st.st_gid;
1087 found_gid = true;
1088 }
1089
3f316701 1090 /* Then, try to get the UID directly */
905ec0c0 1091 if ((ret_uid || (ret_gid && !found_gid))
1b992147
LP
1092 && i->uid_path
1093 && root_stat(i->uid_path, &st) >= 0) {
1094
1095 uid = st.st_uid;
1096 found_uid = true;
1097
3f316701 1098 /* If we need the gid, but had no success yet, also derive it from the UID path */
905ec0c0 1099 if (ret_gid && !found_gid) {
1b992147
LP
1100 gid = st.st_gid;
1101 found_gid = true;
1102 }
1103 }
1104
3f316701 1105 /* If that didn't work yet, then let's reuse the GID as UID */
905ec0c0 1106 if (ret_uid && !found_uid && i->gid_path) {
1b992147
LP
1107
1108 if (found_gid) {
1109 uid = (uid_t) gid;
1110 found_uid = true;
1111 } else if (root_stat(i->gid_path, &st) >= 0) {
1112 uid = (uid_t) st.st_gid;
1113 found_uid = true;
1114 }
1115 }
1116
905ec0c0 1117 if (ret_uid) {
1b992147
LP
1118 if (!found_uid)
1119 return 0;
1120
905ec0c0 1121 *ret_uid = uid;
1b992147
LP
1122 }
1123
905ec0c0 1124 if (ret_gid) {
1b992147
LP
1125 if (!found_gid)
1126 return 0;
1127
905ec0c0 1128 *ret_gid = gid;
1b992147
LP
1129 }
1130
1131 return 1;
1132}
1133
d2a16737 1134static int add_user(Context *c, Item *i) {
1b992147
LP
1135 void *z;
1136 int r;
1137
d2a16737 1138 assert(c);
1b992147
LP
1139 assert(i);
1140
1141 /* Check the database directly */
d2a16737 1142 z = hashmap_get(c->database_by_username, i->name);
1b992147
LP
1143 if (z) {
1144 log_debug("User %s already exists.", i->name);
c1b6b04f 1145 i->uid = PTR_TO_UID(z);
1b992147
LP
1146 i->uid_set = true;
1147 return 0;
1148 }
1149
1150 if (!arg_root) {
75673cd8 1151 _cleanup_free_ struct passwd *p = NULL;
1b992147
LP
1152
1153 /* Also check NSS */
75673cd8
LP
1154 r = getpwnam_malloc(i->name, &p);
1155 if (r >= 0) {
1b992147
LP
1156 log_debug("User %s already exists.", i->name);
1157 i->uid = p->pw_uid;
1158 i->uid_set = true;
1159
2fc09a9c
DM
1160 r = free_and_strdup(&i->description, p->pw_gecos);
1161 if (r < 0)
1162 return log_oom();
1163
1b992147
LP
1164 return 0;
1165 }
75673cd8
LP
1166 if (r != -ESRCH)
1167 return log_error_errno(r, "Failed to check if user %s already exists: %m", i->name);
1b992147
LP
1168 }
1169
3f316701 1170 /* Try to use the suggested numeric UID */
1b992147 1171 if (i->uid_set) {
d2a16737 1172 r = uid_is_ok(c, i->uid, i->name, !i->id_set_strict);
f647962d 1173 if (r < 0)
3f316701 1174 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
1b992147 1175 if (r == 0) {
5374bc7c 1176 log_info("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
1b992147
LP
1177 i->uid_set = false;
1178 }
1179 }
1180
1181 /* If that didn't work, try to read it from the specified path */
1182 if (!i->uid_set) {
d2a16737 1183 uid_t candidate;
1b992147 1184
d2a16737 1185 if (read_id_from_file(i, &candidate, NULL) > 0) {
1b992147 1186
d2a16737
LP
1187 if (candidate <= 0 || !uid_range_contains(c->uid_range, candidate))
1188 log_debug("User ID " UID_FMT " of file not suitable for %s.", candidate, i->name);
1b992147 1189 else {
d2a16737 1190 r = uid_is_ok(c, candidate, i->name, true);
f647962d 1191 if (r < 0)
3f316701 1192 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
f647962d 1193 else if (r > 0) {
d2a16737 1194 i->uid = candidate;
1b992147
LP
1195 i->uid_set = true;
1196 } else
d2a16737 1197 log_debug("User ID " UID_FMT " of file for %s is already used.", candidate, i->name);
1b992147
LP
1198 }
1199 }
1200 }
1201
b938cb90 1202 /* Otherwise, try to reuse the group ID */
1b992147 1203 if (!i->uid_set && i->gid_set) {
d2a16737 1204 r = uid_is_ok(c, (uid_t) i->gid, i->name, true);
f647962d 1205 if (r < 0)
3f316701 1206 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
1b992147
LP
1207 if (r > 0) {
1208 i->uid = (uid_t) i->gid;
1209 i->uid_set = true;
1210 }
1211 }
1212
1213 /* And if that didn't work either, let's try to find a free one */
1214 if (!i->uid_set) {
d2a16737 1215 maybe_emit_login_defs_warning(c);
4b6f9b20 1216
8530dc44 1217 for (;;) {
d2a16737 1218 r = uid_range_next_lower(c->uid_range, &c->search_uid);
330d1def
ZJS
1219 if (r < 0)
1220 return log_error_errno(r, "No free user ID available for %s.", i->name);
1b992147 1221
d2a16737 1222 r = uid_is_ok(c, c->search_uid, i->name, true);
f647962d 1223 if (r < 0)
3f316701 1224 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
f647962d 1225 else if (r > 0)
1b992147
LP
1226 break;
1227 }
1228
1b992147 1229 i->uid_set = true;
d2a16737 1230 i->uid = c->search_uid;
1b992147
LP
1231 }
1232
d2a16737 1233 r = ordered_hashmap_ensure_put(&c->todo_uids, NULL, UID_TO_PTR(i->uid), i);
1dfb9321 1234 if (r == -EEXIST)
3f316701 1235 return log_error_errno(r, "Requested user %s with UID " UID_FMT " and gid" GID_FMT " to be created is duplicated "
1dfb9321
SS
1236 "or conflicts with another user.", i->name, i->uid, i->gid);
1237 if (r == -ENOMEM)
1b992147 1238 return log_oom();
1b992147 1239 if (r < 0)
3f316701 1240 return log_error_errno(r, "Failed to store user %s with UID " UID_FMT " and GID " GID_FMT " to be created: %m",
1dfb9321 1241 i->name, i->uid, i->gid);
1b992147 1242
c1b6b04f 1243 i->todo_user = true;
9a5af4b7 1244 log_info("Creating user '%s' (%s) with UID " UID_FMT " and GID " GID_FMT ".",
62d1c93a 1245 i->name, strna(i->description), i->uid, i->gid);
1b992147
LP
1246
1247 return 0;
1248}
1249
d2a16737
LP
1250static int gid_is_ok(
1251 Context *c,
1252 gid_t gid,
1253 const char *groupname,
1254 bool check_with_uid) {
1255
4ae3e8c9
LB
1256 Item *user;
1257 char *username;
75673cd8 1258 int r;
4ae3e8c9 1259
d2a16737 1260 assert(c);
4ae3e8c9 1261 assert(groupname);
1b992147 1262
d2a16737 1263 if (ordered_hashmap_get(c->todo_gids, GID_TO_PTR(gid)))
1b992147
LP
1264 return 0;
1265
1266 /* Avoid reusing gids that are already used by a different user */
4ae3e8c9 1267 if (check_with_uid) {
d2a16737 1268 user = ordered_hashmap_get(c->todo_uids, UID_TO_PTR(gid));
4ae3e8c9
LB
1269 if (user && !streq(user->name, groupname))
1270 return 0;
1271 }
1b992147 1272
d2a16737 1273 if (hashmap_contains(c->database_by_gid, GID_TO_PTR(gid)))
1b992147
LP
1274 return 0;
1275
4ae3e8c9 1276 if (check_with_uid) {
d2a16737 1277 username = hashmap_get(c->database_by_uid, UID_TO_PTR(gid));
4ae3e8c9
LB
1278 if (username && !streq(username, groupname))
1279 return 0;
1280 }
1b992147
LP
1281
1282 if (!arg_root) {
75673cd8
LP
1283 r = getgrgid_malloc(gid, /* ret= */ NULL);
1284 if (r >= 0)
1b992147 1285 return 0;
75673cd8
LP
1286 if (r != -ESRCH)
1287 return r;
1b992147 1288
6b6e45eb 1289 if (check_with_uid) {
75673cd8
LP
1290 r = getpwuid_malloc(gid, /* ret= */ NULL);
1291 if (r >= 0)
6b6e45eb 1292 return 0;
75673cd8
LP
1293 if (r != -ESRCH)
1294 return r;
6b6e45eb 1295 }
1b992147
LP
1296 }
1297
1298 return 1;
1299}
1300
d2a16737
LP
1301static int get_gid_by_name(
1302 Context *c,
1303 const char *name,
1304 gid_t *ret_gid) {
1305
1b992147 1306 void *z;
75673cd8 1307 int r;
1b992147 1308
d2a16737
LP
1309 assert(c);
1310 assert(ret_gid);
1b992147
LP
1311
1312 /* Check the database directly */
d2a16737 1313 z = hashmap_get(c->database_by_groupname, name);
1b992147 1314 if (z) {
d2a16737 1315 *ret_gid = PTR_TO_GID(z);
1b992147
LP
1316 return 0;
1317 }
1318
1319 /* Also check NSS */
1320 if (!arg_root) {
75673cd8 1321 _cleanup_free_ struct group *g = NULL;
1b992147 1322
75673cd8
LP
1323 r = getgrnam_malloc(name, &g);
1324 if (r >= 0) {
d2a16737 1325 *ret_gid = g->gr_gid;
1b992147
LP
1326 return 0;
1327 }
75673cd8
LP
1328 if (r != -ESRCH)
1329 return log_error_errno(r, "Failed to check if group %s already exists: %m", name);
649916d3
DM
1330 }
1331
1332 return -ENOENT;
1333}
1334
d2a16737 1335static int add_group(Context *c, Item *i) {
649916d3
DM
1336 int r;
1337
d2a16737 1338 assert(c);
649916d3
DM
1339 assert(i);
1340
d2a16737 1341 r = get_gid_by_name(c, i->name, &i->gid);
649916d3
DM
1342 if (r != -ENOENT) {
1343 if (r < 0)
1344 return r;
1345 log_debug("Group %s already exists.", i->name);
1346 i->gid_set = true;
1347 return 0;
1b992147
LP
1348 }
1349
3f316701 1350 /* Try to use the suggested numeric GID */
1b992147 1351 if (i->gid_set) {
d2a16737 1352 r = gid_is_ok(c, i->gid, i->name, false);
f647962d 1353 if (r < 0)
3f316701 1354 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
b9ee05c2 1355 if (i->id_set_strict) {
3f316701
ZJS
1356 /* If we require the GID to already exist we can return here:
1357 * r > 0: means the GID does not exist -> fail
1358 * r == 0: means the GID exists -> nothing more to do.
28e7fad7 1359 */
baaa35ad
ZJS
1360 if (r > 0)
1361 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
c0f86d66 1362 "Failed to create %s: please create GID " GID_FMT,
baaa35ad 1363 i->name, i->gid);
28e7fad7
MV
1364 if (r == 0)
1365 return 0;
1366 }
1b992147 1367 if (r == 0) {
5374bc7c 1368 log_info("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
1b992147
LP
1369 i->gid_set = false;
1370 }
1371 }
1372
1373 /* Try to reuse the numeric uid, if there's one */
1374 if (!i->gid_set && i->uid_set) {
d2a16737 1375 r = gid_is_ok(c, (gid_t) i->uid, i->name, true);
f647962d 1376 if (r < 0)
3f316701 1377 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
1b992147
LP
1378 if (r > 0) {
1379 i->gid = (gid_t) i->uid;
1380 i->gid_set = true;
1381 }
1382 }
1383
1384 /* If that didn't work, try to read it from the specified path */
1385 if (!i->gid_set) {
d2a16737 1386 gid_t candidate;
1b992147 1387
d2a16737 1388 if (read_id_from_file(i, NULL, &candidate) > 0) {
1b992147 1389
d2a16737
LP
1390 if (candidate <= 0 || !uid_range_contains(c->uid_range, candidate))
1391 log_debug("Group ID " GID_FMT " of file not suitable for %s.", candidate, i->name);
1b992147 1392 else {
d2a16737 1393 r = gid_is_ok(c, candidate, i->name, true);
f647962d 1394 if (r < 0)
3f316701 1395 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
f647962d 1396 else if (r > 0) {
d2a16737 1397 i->gid = candidate;
1b992147
LP
1398 i->gid_set = true;
1399 } else
d2a16737 1400 log_debug("Group ID " GID_FMT " of file for %s already used.", candidate, i->name);
1b992147
LP
1401 }
1402 }
1403 }
1404
1405 /* And if that didn't work either, let's try to find a free one */
1406 if (!i->gid_set) {
d2a16737 1407 maybe_emit_login_defs_warning(c);
4b6f9b20 1408
8530dc44
LP
1409 for (;;) {
1410 /* We look for new GIDs in the UID pool! */
d2a16737 1411 r = uid_range_next_lower(c->uid_range, &c->search_uid);
330d1def
ZJS
1412 if (r < 0)
1413 return log_error_errno(r, "No free group ID available for %s.", i->name);
8530dc44 1414
d2a16737 1415 r = gid_is_ok(c, c->search_uid, i->name, true);
f647962d 1416 if (r < 0)
3f316701 1417 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
f647962d 1418 else if (r > 0)
1b992147
LP
1419 break;
1420 }
1421
1b992147 1422 i->gid_set = true;
d2a16737 1423 i->gid = c->search_uid;
1b992147
LP
1424 }
1425
d2a16737 1426 r = ordered_hashmap_ensure_put(&c->todo_gids, NULL, GID_TO_PTR(i->gid), i);
1dfb9321 1427 if (r == -EEXIST)
3f316701 1428 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);
1dfb9321 1429 if (r == -ENOMEM)
1b992147 1430 return log_oom();
1b992147 1431 if (r < 0)
3f316701 1432 return log_error_errno(r, "Failed to store group %s with GID " GID_FMT " to be created: %m", i->name, i->gid);
1b992147 1433
c1b6b04f 1434 i->todo_group = true;
9a5af4b7 1435 log_info("Creating group '%s' with GID " GID_FMT ".", i->name, i->gid);
1b992147
LP
1436
1437 return 0;
1438}
1439
d2a16737 1440static int process_item(Context *c, Item *i) {
1b992147
LP
1441 int r;
1442
d2a16737 1443 assert(c);
1b992147
LP
1444 assert(i);
1445
1446 switch (i->type) {
1447
b5327d0a 1448 case ADD_USER: {
5ed47c4d
LB
1449 Item *j = NULL;
1450
1451 if (!i->gid_set)
d2a16737 1452 j = ordered_hashmap_get(c->groups, i->group_name ?: i->name);
b5327d0a 1453
b5327d0a 1454 if (j && j->todo_group) {
649916d3 1455 /* When a group with the target name is already in queue,
b5327d0a
YW
1456 * use the information about the group and do not create
1457 * duplicated group entry. */
1458 i->gid_set = j->gid_set;
1459 i->gid = j->gid;
1460 i->id_set_strict = true;
649916d3
DM
1461 } else if (i->group_name) {
1462 /* When a group name was given instead of a GID and it's
1463 * not in queue, then it must already exist. */
d2a16737 1464 r = get_gid_by_name(c, i->group_name, &i->gid);
649916d3
DM
1465 if (r < 0)
1466 return log_error_errno(r, "Group %s not found.", i->group_name);
1467 i->gid_set = true;
1468 i->id_set_strict = true;
b5327d0a 1469 } else {
d2a16737 1470 r = add_group(c, i);
b5327d0a
YW
1471 if (r < 0)
1472 return r;
1473 }
1b992147 1474
d2a16737 1475 return add_user(c, i);
b5327d0a 1476 }
1b992147 1477
e2c2060f 1478 case ADD_GROUP:
d2a16737 1479 return add_group(c, i);
1b992147 1480
a12b0cc3 1481 default:
04499a70 1482 assert_not_reached();
a12b0cc3 1483 }
1b992147
LP
1484}
1485
71da1673 1486static Item* item_free(Item *i) {
1b992147 1487 if (!i)
71da1673 1488 return NULL;
1b992147
LP
1489
1490 free(i->name);
649916d3 1491 free(i->group_name);
1b992147
LP
1492 free(i->uid_path);
1493 free(i->gid_path);
1494 free(i->description);
d9b8ea54 1495 free(i->home);
7b1aaf66 1496 free(i->shell);
9a87bdd7 1497 free(i->filename);
71da1673 1498 return mfree(i);
1b992147
LP
1499}
1500
1501DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
71da1673 1502DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_hash_ops, char, string_hash_func, string_compare_func, Item, item_free);
1b992147 1503
9a87bdd7
ZJS
1504static Item* item_new(ItemType type, const char *name, const char *filename, unsigned line) {
1505 assert(name);
1506 assert(!!filename == (line > 0));
1507
1508 _cleanup_(item_freep) Item *new = new(Item, 1);
1509 if (!new)
1510 return NULL;
1511
1512 *new = (Item) {
1513 .type = type,
1514 .line = line,
1515 };
1516
1517 if (free_and_strdup(&new->name, name) < 0 ||
1518 free_and_strdup(&new->filename, filename) < 0)
1519 return NULL;
1520
1521 return TAKE_PTR(new);
1522}
1523
d2a16737 1524static int add_implicit(Context *c) {
a12b0cc3 1525 char *g, **l;
a12b0cc3
LP
1526 int r;
1527
d2a16737
LP
1528 assert(c);
1529
a12b0cc3 1530 /* Implicitly create additional users and groups, if they were listed in "m" lines */
d2a16737 1531 ORDERED_HASHMAP_FOREACH_KEY(l, g, c->members) {
d4f0412d 1532 STRV_FOREACH(m, l)
d2a16737 1533 if (!ordered_hashmap_get(c->users, *m)) {
9a87bdd7
ZJS
1534 _cleanup_(item_freep) Item *j =
1535 item_new(ADD_USER, *m, /* filename= */ NULL, /* line= */ 0);
a12b0cc3
LP
1536 if (!j)
1537 return log_oom();
1538
d2a16737 1539 r = ordered_hashmap_ensure_put(&c->users, &item_hash_ops, j->name, j);
f334deba 1540 if (r == -ENOMEM)
a12b0cc3 1541 return log_oom();
f334deba
SS
1542 if (r < 0)
1543 return log_error_errno(r, "Failed to add implicit user '%s': %m", j->name);
a12b0cc3
LP
1544
1545 log_debug("Adding implicit user '%s' due to m line", j->name);
f334deba 1546 TAKE_PTR(j);
a12b0cc3 1547 }
d4f0412d 1548
d2a16737
LP
1549 if (!(ordered_hashmap_get(c->users, g) ||
1550 ordered_hashmap_get(c->groups, g))) {
9a87bdd7
ZJS
1551 _cleanup_(item_freep) Item *j =
1552 item_new(ADD_GROUP, g, /* filename= */ NULL, /* line= */ 0);
d4f0412d
YW
1553 if (!j)
1554 return log_oom();
1555
d2a16737 1556 r = ordered_hashmap_ensure_put(&c->groups, &item_hash_ops, j->name, j);
f334deba 1557 if (r == -ENOMEM)
d4f0412d 1558 return log_oom();
f334deba
SS
1559 if (r < 0)
1560 return log_error_errno(r, "Failed to add implicit group '%s': %m", j->name);
d4f0412d
YW
1561
1562 log_debug("Adding implicit group '%s' due to m line", j->name);
f334deba 1563 TAKE_PTR(j);
a12b0cc3
LP
1564 }
1565 }
1566
1567 return 0;
1568}
1569
5f465fda
ZJS
1570static int item_equivalent(Item *a, Item *b) {
1571 int r;
1572
1b992147
LP
1573 assert(a);
1574 assert(b);
1575
7519b880
ZJS
1576 if (a->type != b->type) {
1577 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1578 "Item not equivalent because types differ");
1b992147 1579 return false;
7519b880 1580 }
1b992147 1581
7519b880
ZJS
1582 if (!streq_ptr(a->name, b->name)) {
1583 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1584 "Item not equivalent because names differ ('%s' vs. '%s')",
1585 a->name, b->name);
1b992147 1586 return false;
7519b880 1587 }
1b992147 1588
5f465fda 1589 /* Paths were simplified previously, so we can use streq. */
7519b880
ZJS
1590 if (!streq_ptr(a->uid_path, b->uid_path)) {
1591 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1592 "Item not equivalent because UID paths differ (%s vs. %s)",
1593 a->uid_path ?: "(unset)", b->uid_path ?: "(unset)");
1b992147 1594 return false;
7519b880 1595 }
1b992147 1596
7519b880
ZJS
1597 if (!streq_ptr(a->gid_path, b->gid_path)) {
1598 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1599 "Item not equivalent because GID paths differ (%s vs. %s)",
1600 a->gid_path ?: "(unset)", b->gid_path ?: "(unset)");
1b992147 1601 return false;
7519b880 1602 }
1b992147 1603
7519b880
ZJS
1604 if (!streq_ptr(a->description, b->description)) {
1605 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1606 "Item not equivalent because descriptions differ ('%s' vs. '%s')",
1607 strempty(a->description), strempty(b->description));
1b992147 1608 return false;
7519b880 1609 }
1b992147 1610
7519b880
ZJS
1611 if ((a->uid_set != b->uid_set) ||
1612 (a->uid_set && a->uid != b->uid)) {
1613 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1614 "Item not equivalent because UIDs differ (%s vs. %s)",
80d07162
AV
1615 a->uid_set ? FORMAT_UID(a->uid) : "(unset)",
1616 b->uid_set ? FORMAT_UID(b->uid) : "(unset)");
1b992147 1617 return false;
7519b880 1618 }
1b992147 1619
7519b880
ZJS
1620 if ((a->gid_set != b->gid_set) ||
1621 (a->gid_set && a->gid != b->gid)) {
1622 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1623 "Item not equivalent because GIDs differ (%s vs. %s)",
80d07162
AV
1624 a->gid_set ? FORMAT_GID(a->gid) : "(unset)",
1625 b->gid_set ? FORMAT_GID(b->gid) : "(unset)");
1b992147 1626 return false;
7519b880 1627 }
1b992147 1628
7519b880
ZJS
1629 if (!streq_ptr(a->home, b->home)) {
1630 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1631 "Item not equivalent because home directories differ ('%s' vs. '%s')",
1632 strempty(a->description), strempty(b->description));
7629889c 1633 return false;
7519b880 1634 }
7629889c 1635
5f465fda
ZJS
1636 /* Check if the two paths refer to the same file.
1637 * If the paths are equal (after normalization), it's obviously the same file.
1638 * If both paths specify a nologin shell, treat them as the same (e.g. /bin/true and /bin/false).
1639 * Otherwise, try to resolve the paths, and see if we get the same result, (e.g. /sbin/nologin and
1640 * /usr/sbin/nologin).
1641 * If we can't resolve something, treat different paths as different. */
1642
1643 const char *a_shell = pick_shell(a),
1644 *b_shell = pick_shell(b);
1645 if (!path_equal_ptr(a_shell, b_shell) &&
1646 !(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) {
1647 _cleanup_free_ char *pa = NULL, *pb = NULL;
1648
f461a28d 1649 r = chase(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
5f465fda
ZJS
1650 if (r < 0) {
1651 log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
1652 r, "Failed to look up path '%s%s%s': %m",
1653 strempty(arg_root), arg_root ? "/" : "", a_shell);
1654 return ERRNO_IS_RESOURCE(r) ? r : false;
1655 }
1656
f461a28d 1657 r = chase(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
5f465fda
ZJS
1658 if (r < 0) {
1659 log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
1660 r, "Failed to look up path '%s%s%s': %m",
1661 strempty(arg_root), arg_root ? "/" : "", b_shell);
1662 return ERRNO_IS_RESOURCE(r) ? r : false;
1663 }
1664
7519b880
ZJS
1665 if (!path_equal(pa, pb)) {
1666 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1667 "Item not equivalent because shells differ ('%s' vs. '%s')",
1668 pa, pb);
5f465fda 1669 return false;
7519b880 1670 }
5f465fda 1671 }
7b1aaf66 1672
1b992147
LP
1673 return true;
1674}
1675
d2a16737 1676static int parse_line(
d2a16737
LP
1677 const char *fname,
1678 unsigned line,
15d660fb
ZJS
1679 const char *buffer,
1680 bool *invalid_config,
1681 void *context) {
d2a16737 1682
15d660fb 1683 Context *c = ASSERT_PTR(context);
7b1aaf66
ZJS
1684 _cleanup_free_ char *action = NULL,
1685 *name = NULL, *resolved_name = NULL,
1686 *id = NULL, *resolved_id = NULL,
b8bed700
YW
1687 *description = NULL, *resolved_description = NULL,
1688 *home = NULL, *resolved_home = NULL,
71fb1588 1689 *shell = NULL, *resolved_shell = NULL;
1b992147
LP
1690 _cleanup_(item_freep) Item *i = NULL;
1691 Item *existing;
5bc9c980 1692 OrderedHashmap *h;
7629889c
LP
1693 int r;
1694 const char *p;
1b992147
LP
1695
1696 assert(fname);
1697 assert(line >= 1);
1698 assert(buffer);
15d660fb 1699 assert(!invalid_config); /* We don't support invalid_config yet. */
1b992147 1700
7629889c
LP
1701 /* Parse columns */
1702 p = buffer;
4ec85141 1703 r = extract_many_words(&p, NULL, EXTRACT_UNQUOTE,
7b1aaf66 1704 &action, &name, &id, &description, &home, &shell, NULL);
330d1def 1705 if (r < 0)
87c696f2 1706 return log_syntax(NULL, LOG_ERR, fname, line, r, "Syntax error.");
330d1def 1707 if (r < 2)
87c696f2
ZJS
1708 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1709 "Missing action and name columns.");
330d1def 1710 if (!isempty(p))
87c696f2
ZJS
1711 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1712 "Trailing garbage.");
1b992147 1713
7629889c 1714 /* Verify action */
330d1def 1715 if (strlen(action) != 1)
87c696f2
ZJS
1716 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1717 "Unknown modifier '%s'.", action);
1b992147 1718
330d1def 1719 if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE))
87c696f2
ZJS
1720 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
1721 "Unknown command type '%c'.", action[0]);
1b992147 1722
7629889c 1723 /* Verify name */
e7b88b7b 1724 if (empty_or_dash(name))
97b11eed 1725 name = mfree(name);
1b992147 1726
8530dc44 1727 if (name) {
de61a04b 1728 r = specifier_printf(name, NAME_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_name);
330d1def 1729 if (r < 0)
87c696f2 1730 return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to replace specifiers in '%s': %m", name);
8530dc44 1731
7a8867ab 1732 if (!valid_user_group_name(resolved_name, 0))
87c696f2
ZJS
1733 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1734 "'%s' is not a valid user or group name.", resolved_name);
1b992147
LP
1735 }
1736
8530dc44 1737 /* Verify id */
e7b88b7b 1738 if (empty_or_dash(id))
97b11eed 1739 id = mfree(id);
7629889c 1740
8530dc44 1741 if (id) {
de61a04b 1742 r = specifier_printf(id, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_id);
330d1def 1743 if (r < 0)
87c696f2
ZJS
1744 return log_syntax(NULL, LOG_ERR, fname, line, r,
1745 "Failed to replace specifiers in '%s': %m", name);
8530dc44
LP
1746 }
1747
1748 /* Verify description */
e7b88b7b 1749 if (empty_or_dash(description))
97b11eed 1750 description = mfree(description);
1b992147 1751
8530dc44 1752 if (description) {
de61a04b 1753 r = specifier_printf(description, LONG_LINE_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_description);
330d1def 1754 if (r < 0)
87c696f2
ZJS
1755 return log_syntax(NULL, LOG_ERR, fname, line, r,
1756 "Failed to replace specifiers in '%s': %m", description);
b8bed700 1757
330d1def 1758 if (!valid_gecos(resolved_description))
87c696f2
ZJS
1759 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1760 "'%s' is not a valid GECOS field.", resolved_description);
8530dc44
LP
1761 }
1762
1763 /* Verify home */
e7b88b7b 1764 if (empty_or_dash(home))
97b11eed 1765 home = mfree(home);
1b992147 1766
8530dc44 1767 if (home) {
de61a04b 1768 r = specifier_printf(home, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_home);
330d1def 1769 if (r < 0)
87c696f2
ZJS
1770 return log_syntax(NULL, LOG_ERR, fname, line, r,
1771 "Failed to replace specifiers in '%s': %m", home);
b8bed700 1772
eef74f91
ZJS
1773 path_simplify(resolved_home);
1774
330d1def 1775 if (!valid_home(resolved_home))
87c696f2
ZJS
1776 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1777 "'%s' is not a valid home directory field.", resolved_home);
8530dc44
LP
1778 }
1779
7b1aaf66 1780 /* Verify shell */
e7b88b7b 1781 if (empty_or_dash(shell))
7b1aaf66
ZJS
1782 shell = mfree(shell);
1783
1784 if (shell) {
de61a04b 1785 r = specifier_printf(shell, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_shell);
330d1def 1786 if (r < 0)
87c696f2
ZJS
1787 return log_syntax(NULL, LOG_ERR, fname, line, r,
1788 "Failed to replace specifiers in '%s': %m", shell);
7b1aaf66 1789
eef74f91
ZJS
1790 path_simplify(resolved_shell);
1791
330d1def 1792 if (!valid_shell(resolved_shell))
87c696f2
ZJS
1793 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1794 "'%s' is not a valid login shell field.", resolved_shell);
7b1aaf66
ZJS
1795 }
1796
a12b0cc3 1797 switch (action[0]) {
1b992147 1798
8530dc44 1799 case ADD_RANGE:
330d1def 1800 if (resolved_name)
87c696f2
ZJS
1801 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1802 "Lines of type 'r' don't take a name field.");
330d1def
ZJS
1803
1804 if (!resolved_id)
87c696f2
ZJS
1805 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1806 "Lines of type 'r' require an ID range in the third field.");
330d1def
ZJS
1807
1808 if (description || home || shell)
87c696f2
ZJS
1809 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1810 "Lines of type '%c' don't take a %s field.",
1811 action[0],
1812 description ? "GECOS" : home ? "home directory" : "login shell");
7629889c 1813
d2a16737 1814 r = uid_range_add_str(&c->uid_range, resolved_id);
330d1def 1815 if (r < 0)
87c696f2
ZJS
1816 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1817 "Invalid UID range %s.", resolved_id);
1b992147 1818
8530dc44
LP
1819 return 0;
1820
1821 case ADD_MEMBER: {
8530dc44 1822 /* Try to extend an existing member or group item */
330d1def 1823 if (!name)
87c696f2
ZJS
1824 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1825 "Lines of type 'm' require a user name in the second field.");
330d1def
ZJS
1826
1827 if (!resolved_id)
87c696f2
ZJS
1828 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1829 "Lines of type 'm' require a group name in the third field.");
330d1def 1830
7a8867ab 1831 if (!valid_user_group_name(resolved_id, 0))
87c696f2
ZJS
1832 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1833 "'%s' is not a valid user or group name.", resolved_id);
330d1def
ZJS
1834
1835 if (description || home || shell)
87c696f2
ZJS
1836 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1837 "Lines of type '%c' don't take a %s field.",
1838 action[0],
1839 description ? "GECOS" : home ? "home directory" : "login shell");
8530dc44 1840
d2a16737 1841 r = string_strv_ordered_hashmap_put(&c->members, resolved_id, resolved_name);
7629889c 1842 if (r < 0)
cde79109 1843 return log_error_errno(r, "Failed to store mapping for %s: %m", resolved_id);
a12b0cc3
LP
1844
1845 return 0;
1b992147
LP
1846 }
1847
a12b0cc3 1848 case ADD_USER:
330d1def 1849 if (!name)
87c696f2
ZJS
1850 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1851 "Lines of type 'u' require a user name in the second field.");
8530dc44 1852
d2a16737 1853 r = ordered_hashmap_ensure_allocated(&c->users, &item_hash_ops);
a12b0cc3
LP
1854 if (r < 0)
1855 return log_oom();
1856
9a87bdd7 1857 i = item_new(ADD_USER, resolved_name, fname, line);
a12b0cc3
LP
1858 if (!i)
1859 return log_oom();
1860
8530dc44 1861 if (resolved_id) {
9a87bdd7
ZJS
1862 if (path_is_absolute(resolved_id))
1863 i->uid_path = path_simplify(TAKE_PTR(resolved_id));
1864 else {
4cb41413
MV
1865 _cleanup_free_ char *uid = NULL, *gid = NULL;
1866 if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
1867 r = parse_gid(gid, &i->gid);
649916d3 1868 if (r < 0) {
7a8867ab 1869 if (valid_user_group_name(gid, 0))
649916d3
DM
1870 i->group_name = TAKE_PTR(gid);
1871 else
87c696f2
ZJS
1872 return log_syntax(NULL, LOG_ERR, fname, line, r,
1873 "Failed to parse GID: '%s': %m", id);
649916d3
DM
1874 } else {
1875 i->gid_set = true;
1876 i->id_set_strict = true;
1877 }
4cb41413 1878 free_and_replace(resolved_id, uid);
a12b0cc3 1879 }
1825c909
MV
1880 if (!streq(resolved_id, "-")) {
1881 r = parse_uid(resolved_id, &i->uid);
1882 if (r < 0)
87c696f2
ZJS
1883 return log_syntax(NULL, LOG_ERR, fname, line, r,
1884 "Failed to parse UID: '%s': %m", id);
1825c909
MV
1885 i->uid_set = true;
1886 }
a12b0cc3
LP
1887 }
1888 }
1889
b8bed700
YW
1890 i->description = TAKE_PTR(resolved_description);
1891 i->home = TAKE_PTR(resolved_home);
1cc6c93a 1892 i->shell = TAKE_PTR(resolved_shell);
7b1aaf66 1893
d2a16737 1894 h = c->users;
a12b0cc3
LP
1895 break;
1896
1897 case ADD_GROUP:
330d1def 1898 if (!name)
87c696f2
ZJS
1899 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1900 "Lines of type 'g' require a user name in the second field.");
a12b0cc3 1901
330d1def 1902 if (description || home || shell)
87c696f2
ZJS
1903 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1904 "Lines of type '%c' don't take a %s field.",
1905 action[0],
1906 description ? "GECOS" : home ? "home directory" : "login shell");
7629889c 1907
d2a16737 1908 r = ordered_hashmap_ensure_allocated(&c->groups, &item_hash_ops);
7629889c
LP
1909 if (r < 0)
1910 return log_oom();
1911
9a87bdd7 1912 i = item_new(ADD_GROUP, resolved_name, fname, line);
a12b0cc3
LP
1913 if (!i)
1914 return log_oom();
1915
8530dc44 1916 if (resolved_id) {
9a87bdd7
ZJS
1917 if (path_is_absolute(resolved_id))
1918 i->gid_path = path_simplify(TAKE_PTR(resolved_id));
1919 else {
8530dc44 1920 r = parse_gid(resolved_id, &i->gid);
4cb41413 1921 if (r < 0)
87c696f2
ZJS
1922 return log_syntax(NULL, LOG_ERR, fname, line, r,
1923 "Failed to parse GID: '%s': %m", id);
a12b0cc3
LP
1924
1925 i->gid_set = true;
1926 }
1927 }
1928
d2a16737 1929 h = c->groups;
a12b0cc3 1930 break;
8530dc44 1931
bce415ed 1932 default:
9a87bdd7 1933 assert_not_reached();
1b992147 1934 }
a12b0cc3 1935
5bc9c980 1936 existing = ordered_hashmap_get(h, i->name);
1b992147 1937 if (existing) {
5f465fda 1938 /* Two functionally-equivalent items are fine */
7519b880 1939 r = item_equivalent(i, existing);
5f465fda
ZJS
1940 if (r < 0)
1941 return r;
9a87bdd7
ZJS
1942 if (r == 0) {
1943 if (existing->filename)
1944 log_syntax(NULL, LOG_WARNING, fname, line, 0,
1945 "Conflict with earlier configuration for %s '%s' in %s:%u, ignoring line.",
1946 item_type_to_string(i->type),
1947 i->name,
1948 existing->filename, existing->line);
1949 else
1950 log_syntax(NULL, LOG_WARNING, fname, line, 0,
1951 "Conflict with earlier configuration for %s '%s', ignoring line.",
1952 item_type_to_string(i->type),
1953 i->name);
1954 }
1b992147
LP
1955
1956 return 0;
1957 }
1958
5bc9c980 1959 r = ordered_hashmap_put(h, i->name, i);
a12b0cc3
LP
1960 if (r < 0)
1961 return log_oom();
1b992147
LP
1962
1963 i = NULL;
1964 return 0;
1965}
1966
d2a16737 1967static int read_config_file(Context *c, const char *fn, bool ignore_enoent) {
15d660fb
ZJS
1968 return conf_file_read(
1969 arg_root,
1970 (const char**) CONF_PATHS_STRV("sysusers.d"),
1971 ASSERT_PTR(fn),
1972 parse_line,
1973 ASSERT_PTR(c),
1974 ignore_enoent,
1975 /* invalid_config= */ NULL);
1b992147
LP
1976}
1977
ec0327d6
ZJS
1978static int cat_config(void) {
1979 _cleanup_strv_free_ char **files = NULL;
ec0327d6
ZJS
1980 int r;
1981
a826d4f7 1982 r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, NULL);
ec0327d6
ZJS
1983 if (r < 0)
1984 return r;
1985
384c2c32 1986 pager_open(arg_pager_flags);
dcd5c891 1987
918560d7 1988 return cat_files(NULL, files, arg_cat_flags);
ec0327d6
ZJS
1989}
1990
37ec0fdd
LP
1991static int help(void) {
1992 _cleanup_free_ char *link = NULL;
1993 int r;
1994
1995 r = terminal_urlify_man("systemd-sysusers.service", "8", &link);
1996 if (r < 0)
1997 return log_oom();
1998
1b992147
LP
1999 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
2000 "Creates system user accounts.\n\n"
2001 " -h --help Show this help\n"
2002 " --version Show package version\n"
ec0327d6 2003 " --cat-config Show configuration files\n"
918560d7 2004 " --tldr Show non-comment parts of configuration\n"
601185b4 2005 " --root=PATH Operate on an alternate filesystem root\n"
0c3ee127 2006 " --image=PATH Operate on disk image as filesystem root\n"
84be0c71 2007 " --image-policy=POLICY Specify disk image dissection policy\n"
d16a1c1b 2008 " --replace=PATH Treat arguments as replacement for PATH\n"
64fe1095 2009 " --dry-run Just print what would be done\n"
1b600bd5 2010 " --inline Treat arguments as configuration lines\n"
dcd5c891 2011 " --no-pager Do not pipe output into a pager\n"
bc556335
DDM
2012 "\nSee the %s for details.\n",
2013 program_invocation_short_name,
2014 link);
37ec0fdd
LP
2015
2016 return 0;
1b992147
LP
2017}
2018
2019static int parse_argv(int argc, char *argv[]) {
2020
2021 enum {
2022 ARG_VERSION = 0x100,
ec0327d6 2023 ARG_CAT_CONFIG,
918560d7 2024 ARG_TLDR,
1b992147 2025 ARG_ROOT,
0c3ee127 2026 ARG_IMAGE,
06e78680 2027 ARG_IMAGE_POLICY,
d16a1c1b 2028 ARG_REPLACE,
64fe1095 2029 ARG_DRY_RUN,
1b600bd5 2030 ARG_INLINE,
dcd5c891 2031 ARG_NO_PAGER,
1b992147
LP
2032 };
2033
2034 static const struct option options[] = {
84be0c71
LP
2035 { "help", no_argument, NULL, 'h' },
2036 { "version", no_argument, NULL, ARG_VERSION },
2037 { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
918560d7 2038 { "tldr", no_argument, NULL, ARG_TLDR },
84be0c71
LP
2039 { "root", required_argument, NULL, ARG_ROOT },
2040 { "image", required_argument, NULL, ARG_IMAGE },
06e78680 2041 { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
84be0c71
LP
2042 { "replace", required_argument, NULL, ARG_REPLACE },
2043 { "dry-run", no_argument, NULL, ARG_DRY_RUN },
2044 { "inline", no_argument, NULL, ARG_INLINE },
2045 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1b992147
LP
2046 {}
2047 };
2048
0f474365 2049 int c, r;
1b992147
LP
2050
2051 assert(argc >= 0);
2052 assert(argv);
2053
601185b4 2054 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
1b992147
LP
2055
2056 switch (c) {
2057
2058 case 'h':
37ec0fdd 2059 return help();
1b992147
LP
2060
2061 case ARG_VERSION:
3f6fd1ba 2062 return version();
1b992147 2063
ec0327d6 2064 case ARG_CAT_CONFIG:
918560d7
ZJS
2065 arg_cat_flags = CAT_CONFIG_ON;
2066 break;
2067
2068 case ARG_TLDR:
2069 arg_cat_flags = CAT_TLDR;
ec0327d6
ZJS
2070 break;
2071
1b992147 2072 case ARG_ROOT:
614b022c 2073 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
0f474365 2074 if (r < 0)
0f03c2a4 2075 return r;
1b992147
LP
2076 break;
2077
0c3ee127 2078 case ARG_IMAGE:
3537577c
ZJS
2079#ifdef STANDALONE
2080 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
2081 "This systemd-sysusers version is compiled without support for --image=.");
2082#else
614b022c 2083 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
0c3ee127
LP
2084 if (r < 0)
2085 return r;
2086 break;
3537577c 2087#endif
0c3ee127 2088
06e78680
YW
2089 case ARG_IMAGE_POLICY:
2090 r = parse_image_policy_argument(optarg, &arg_image_policy);
2091 if (r < 0)
2092 return r;
2093 break;
2094
d16a1c1b 2095 case ARG_REPLACE:
37ee46f7 2096 if (!path_is_absolute(optarg))
baaa35ad 2097 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
37ee46f7
ZJS
2098 "The argument to --replace= must be an absolute path.");
2099 if (!endswith(optarg, ".conf"))
2100 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2101 "The argument to --replace= must have the extension '.conf'.");
d16a1c1b
ZJS
2102
2103 arg_replace = optarg;
2104 break;
2105
64fe1095
ZJS
2106 case ARG_DRY_RUN:
2107 arg_dry_run = true;
2108 break;
2109
1b600bd5
ZJS
2110 case ARG_INLINE:
2111 arg_inline = true;
2112 break;
2113
dcd5c891 2114 case ARG_NO_PAGER:
0221d68a 2115 arg_pager_flags |= PAGER_DISABLE;
dcd5c891
LP
2116 break;
2117
1b992147
LP
2118 case '?':
2119 return -EINVAL;
2120
2121 default:
04499a70 2122 assert_not_reached();
1b992147 2123 }
1b992147 2124
918560d7 2125 if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF)
baaa35ad 2126 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
918560d7 2127 "Option --replace= is not supported with --cat-config/--tldr.");
ec0327d6 2128
baaa35ad
ZJS
2129 if (arg_replace && optind >= argc)
2130 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
918560d7 2131 "When --replace= is given, some configuration items must be specified.");
d16a1c1b 2132
0c3ee127 2133 if (arg_image && arg_root)
918560d7
ZJS
2134 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2135 "Use either --root= or --image=, the combination of both is not supported.");
0c3ee127 2136
1b992147
LP
2137 return 1;
2138}
2139
d2a16737 2140static int parse_arguments(Context *c, char **args) {
d16a1c1b
ZJS
2141 unsigned pos = 1;
2142 int r;
2143
d2a16737
LP
2144 assert(c);
2145
d16a1c1b
ZJS
2146 STRV_FOREACH(arg, args) {
2147 if (arg_inline)
2148 /* Use (argument):n, where n==1 for the first positional arg */
15d660fb 2149 r = parse_line("(argument)", pos, *arg, /* invalid_config= */ NULL, c);
d16a1c1b 2150 else
d2a16737 2151 r = read_config_file(c, *arg, /* ignore_enoent= */ false);
d16a1c1b
ZJS
2152 if (r < 0)
2153 return r;
2154
2155 pos++;
2156 }
2157
2158 return 0;
2159}
2160
d2a16737 2161static int read_config_files(Context *c, char **args) {
d16a1c1b
ZJS
2162 _cleanup_strv_free_ char **files = NULL;
2163 _cleanup_free_ char *p = NULL;
d16a1c1b
ZJS
2164 int r;
2165
d2a16737
LP
2166 assert(c);
2167
a826d4f7 2168 r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, &p);
d16a1c1b 2169 if (r < 0)
ec0327d6 2170 return r;
d16a1c1b
ZJS
2171
2172 STRV_FOREACH(f, files)
2173 if (p && path_equal(*f, p)) {
28e5e1e9 2174 log_debug("Parsing arguments at position \"%s\"%s", *f, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
d16a1c1b 2175
d2a16737 2176 r = parse_arguments(c, args);
d16a1c1b
ZJS
2177 if (r < 0)
2178 return r;
2179 } else {
28e5e1e9 2180 log_debug("Reading config file \"%s\"%s", *f, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
d16a1c1b
ZJS
2181
2182 /* Just warn, ignore result otherwise */
d2a16737 2183 (void) read_config_file(c, *f, /* ignore_enoent= */ true);
d16a1c1b
ZJS
2184 }
2185
2186 return 0;
2187}
2188
d2a16737 2189static int read_credential_lines(Context *c) {
3acb6ede
LP
2190 _cleanup_free_ char *j = NULL;
2191 const char *d;
2192 int r;
2193
d2a16737
LP
2194 assert(c);
2195
3acb6ede
LP
2196 r = get_credentials_dir(&d);
2197 if (r == -ENXIO)
2198 return 0;
2199 if (r < 0)
2200 return log_error_errno(r, "Failed to get credentials directory: %m");
2201
2202 j = path_join(d, "sysusers.extra");
2203 if (!j)
2204 return log_oom();
2205
d2a16737 2206 (void) read_config_file(c, j, /* ignore_enoent= */ true);
3acb6ede
LP
2207 return 0;
2208}
2209
71da1673 2210static int run(int argc, char *argv[]) {
3537577c 2211#ifndef STANDALONE
0c3ee127 2212 _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
a4b3e942 2213 _cleanup_(umount_and_freep) char *mounted_dir = NULL;
3537577c 2214#endif
5bb1d7fb 2215 _cleanup_close_ int lock = -EBADF;
d2a16737
LP
2216 _cleanup_(context_done) Context c = {
2217 .search_uid = UID_INVALID,
2218 };
2219
1b992147 2220 Item *i;
dd2fd972 2221 int r;
1b992147
LP
2222
2223 r = parse_argv(argc, argv);
2224 if (r <= 0)
71da1673 2225 return r;
1b992147 2226
d2acb93d 2227 log_setup();
1b992147 2228
918560d7 2229 if (arg_cat_flags != CAT_CONFIG_OFF)
71da1673 2230 return cat_config();
ec0327d6 2231
1b992147
LP
2232 umask(0022);
2233
a452c807 2234 r = mac_init();
71da1673 2235 if (r < 0)
a9ba0e32 2236 return r;
1b992147 2237
3537577c 2238#ifndef STANDALONE
0c3ee127
LP
2239 if (arg_image) {
2240 assert(!arg_root);
2241
2242 r = mount_image_privately_interactively(
2243 arg_image,
84be0c71 2244 arg_image_policy,
4b5de5dd
LP
2245 DISSECT_IMAGE_GENERIC_ROOT |
2246 DISSECT_IMAGE_REQUIRE_ROOT |
2247 DISSECT_IMAGE_VALIDATE_OS |
2248 DISSECT_IMAGE_RELAX_VAR_CHECK |
c65f854a
LP
2249 DISSECT_IMAGE_FSCK |
2250 DISSECT_IMAGE_GROWFS,
a4b3e942 2251 &mounted_dir,
a133d2c3 2252 /* ret_dir_fd= */ NULL,
e330f97a 2253 &loop_device);
0c3ee127
LP
2254 if (r < 0)
2255 return r;
2256
a4b3e942 2257 arg_root = strdup(mounted_dir);
0c3ee127
LP
2258 if (!arg_root)
2259 return log_oom();
2260 }
3537577c
ZJS
2261#else
2262 assert(!arg_image);
2263#endif
0c3ee127 2264
3acb6ede
LP
2265 /* If command line arguments are specified along with --replace, read all configuration files and
2266 * insert the positional arguments at the specified place. Otherwise, if command line arguments are
2267 * specified, execute just them, and finally, without --replace= or any positional arguments, just
2268 * read configuration and execute it. */
d16a1c1b 2269 if (arg_replace || optind >= argc)
d2a16737 2270 r = read_config_files(&c, argv + optind);
d16a1c1b 2271 else
d2a16737 2272 r = parse_arguments(&c, argv + optind);
d16a1c1b 2273 if (r < 0)
71da1673 2274 return r;
1b992147 2275
d2a16737 2276 r = read_credential_lines(&c);
3acb6ede
LP
2277 if (r < 0)
2278 return r;
2279
2280 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our
2281 * detection whether the names or UID/GID area already used otherwise doesn't get confused. After
2282 * all, even though nss-systemd synthesizes these users/groups, they should still appear in
2283 * /etc/passwd and /etc/group, as the synthesizing logic is merely supposed to be fallback for cases
2284 * where we run with a completely unpopulated /etc. */
71da1673
YW
2285 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0)
2286 return log_error_errno(errno, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
fe102d6a 2287
d2a16737 2288 if (!c.uid_range) {
aa25270c 2289 /* Default to default range of SYSTEMD_UID_MIN..SYSTEM_UID_MAX. */
d2a16737 2290 r = read_login_defs(&c.login_defs, NULL, arg_root);
71da1673 2291 if (r < 0)
aa25270c
ZJS
2292 return log_error_errno(r, "Failed to read %s%s: %m",
2293 strempty(arg_root), "/etc/login.defs");
2294
d2a16737 2295 c.login_defs_need_warning = true;
4b6f9b20 2296
aa25270c 2297 /* We pick a range that very conservative: we look at compiled-in maximum and the value in
3f316701 2298 * /etc/login.defs. That way the UIDs/GIDs which we allocate will be interpreted correctly,
aa25270c
ZJS
2299 * even if /etc/login.defs is removed later. (The bottom bound doesn't matter much, since
2300 * it's only used during allocation, so we use the configured value directly). */
d2a16737
LP
2301 uid_t begin = c.login_defs.system_alloc_uid_min,
2302 end = MIN3((uid_t) SYSTEM_UID_MAX, c.login_defs.system_uid_max, c.login_defs.system_gid_max);
aa25270c 2303 if (begin < end) {
d2a16737 2304 r = uid_range_add(&c.uid_range, begin, end - begin + 1);
aa25270c
ZJS
2305 if (r < 0)
2306 return log_oom();
2307 }
8530dc44
LP
2308 }
2309
d2a16737 2310 r = add_implicit(&c);
a12b0cc3 2311 if (r < 0)
71da1673 2312 return r;
a12b0cc3 2313
64fe1095
ZJS
2314 if (!arg_dry_run) {
2315 lock = take_etc_passwd_lock(arg_root);
2316 if (lock < 0)
2317 return log_error_errno(lock, "Failed to take /etc/passwd lock: %m");
2318 }
1b992147 2319
d2a16737 2320 r = load_user_database(&c);
71da1673
YW
2321 if (r < 0)
2322 return log_error_errno(r, "Failed to load user database: %m");
1b992147 2323
d2a16737 2324 r = load_group_database(&c);
71da1673
YW
2325 if (r < 0)
2326 return log_error_errno(r, "Failed to read group database: %m");
1b992147 2327
d2a16737
LP
2328 ORDERED_HASHMAP_FOREACH(i, c.groups)
2329 (void) process_item(&c, i);
1b992147 2330
d2a16737
LP
2331 ORDERED_HASHMAP_FOREACH(i, c.users)
2332 (void) process_item(&c, i);
1b992147 2333
d2a16737 2334 return write_files(&c);
1b992147 2335}
71da1673
YW
2336
2337DEFINE_MAIN_FUNCTION(run);