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