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