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