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