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