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