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