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