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