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