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