]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysusers/sysusers.c
treewide: no need to negate errno for log_*_errno()
[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);
922 if (r < 0) {
da927ba9 923 log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
924 return r;
925 }
926 if (r == 0) {
927 log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
928 i->uid_set = false;
929 }
930 }
931
932 /* If that didn't work, try to read it from the specified path */
933 if (!i->uid_set) {
934 uid_t c;
935
936 if (read_id_from_file(i, &c, NULL) > 0) {
937
8530dc44 938 if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
1b992147
LP
939 log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name);
940 else {
941 r = uid_is_ok(c, i->name);
942 if (r < 0) {
da927ba9 943 log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
944 return r;
945 } else if (r > 0) {
946 i->uid = c;
947 i->uid_set = true;
948 } else
949 log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name);
950 }
951 }
952 }
953
954 /* Otherwise try to reuse the group ID */
955 if (!i->uid_set && i->gid_set) {
956 r = uid_is_ok((uid_t) i->gid, i->name);
957 if (r < 0) {
da927ba9 958 log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
959 return r;
960 }
961 if (r > 0) {
962 i->uid = (uid_t) i->gid;
963 i->uid_set = true;
964 }
965 }
966
967 /* And if that didn't work either, let's try to find a free one */
968 if (!i->uid_set) {
8530dc44
LP
969 for (;;) {
970 r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
971 if (r < 0) {
972 log_error("No free user ID available for %s.", i->name);
973 return r;
974 }
1b992147
LP
975
976 r = uid_is_ok(search_uid, i->name);
977 if (r < 0) {
da927ba9 978 log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
979 return r;
980 } else if (r > 0)
981 break;
982 }
983
1b992147
LP
984 i->uid_set = true;
985 i->uid = search_uid;
1b992147
LP
986 }
987
d5099efc 988 r = hashmap_ensure_allocated(&todo_uids, NULL);
1b992147
LP
989 if (r < 0)
990 return log_oom();
991
992 r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
993 if (r < 0)
994 return log_oom();
995
c1b6b04f 996 i->todo_user = true;
1b992147
LP
997 log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
998
999 return 0;
1000}
1001
1002static int gid_is_ok(gid_t gid) {
1003 struct group *g;
1004 struct passwd *p;
1005
1006 if (hashmap_get(todo_gids, GID_TO_PTR(gid)))
1007 return 0;
1008
1009 /* Avoid reusing gids that are already used by a different user */
1010 if (hashmap_get(todo_uids, UID_TO_PTR(gid)))
1011 return 0;
1012
1013 if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
1014 return 0;
1015
1016 if (hashmap_contains(database_uid, UID_TO_PTR(gid)))
1017 return 0;
1018
1019 if (!arg_root) {
1020 errno = 0;
1021 g = getgrgid(gid);
1022 if (g)
1023 return 0;
b0284aba 1024 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
1025 return -errno;
1026
1027 errno = 0;
1028 p = getpwuid((uid_t) gid);
1029 if (p)
1030 return 0;
b0284aba 1031 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
1032 return -errno;
1033 }
1034
1035 return 1;
1036}
1037
1038static int add_group(Item *i) {
1039 void *z;
1040 int r;
1041
1042 assert(i);
1043
1044 /* Check the database directly */
1045 z = hashmap_get(database_group, i->name);
1046 if (z) {
1047 log_debug("Group %s already exists.", i->name);
1048 i->gid = PTR_TO_GID(z);
1049 i->gid_set = true;
1050 return 0;
1051 }
1052
1053 /* Also check NSS */
1054 if (!arg_root) {
1055 struct group *g;
1056
1057 errno = 0;
1058 g = getgrnam(i->name);
1059 if (g) {
1060 log_debug("Group %s already exists.", i->name);
1061 i->gid = g->gr_gid;
1062 i->gid_set = true;
1063 return 0;
1064 }
b0284aba 1065 if (!IN_SET(errno, 0, ENOENT)) {
1b992147
LP
1066 log_error("Failed to check if group %s already exists: %m", i->name);
1067 return -errno;
1068 }
1069 }
1070
1071 /* Try to use the suggested numeric gid */
1072 if (i->gid_set) {
1073 r = gid_is_ok(i->gid);
1074 if (r < 0) {
da927ba9 1075 log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1b992147
LP
1076 return r;
1077 }
1078 if (r == 0) {
1079 log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
1080 i->gid_set = false;
1081 }
1082 }
1083
1084 /* Try to reuse the numeric uid, if there's one */
1085 if (!i->gid_set && i->uid_set) {
1086 r = gid_is_ok((gid_t) i->uid);
1087 if (r < 0) {
da927ba9 1088 log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1b992147
LP
1089 return r;
1090 }
1091 if (r > 0) {
1092 i->gid = (gid_t) i->uid;
1093 i->gid_set = true;
1094 }
1095 }
1096
1097 /* If that didn't work, try to read it from the specified path */
1098 if (!i->gid_set) {
1099 gid_t c;
1100
1101 if (read_id_from_file(i, NULL, &c) > 0) {
1102
8530dc44 1103 if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
1b992147
LP
1104 log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name);
1105 else {
1106 r = gid_is_ok(c);
1107 if (r < 0) {
da927ba9 1108 log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1b992147
LP
1109 return r;
1110 } else if (r > 0) {
1111 i->gid = c;
1112 i->gid_set = true;
1113 } else
1114 log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name);
1115 }
1116 }
1117 }
1118
1119 /* And if that didn't work either, let's try to find a free one */
1120 if (!i->gid_set) {
8530dc44
LP
1121 for (;;) {
1122 /* We look for new GIDs in the UID pool! */
1123 r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
1124 if (r < 0) {
1125 log_error("No free group ID available for %s.", i->name);
1126 return r;
1127 }
1128
1129 r = gid_is_ok(search_uid);
1b992147 1130 if (r < 0) {
da927ba9 1131 log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1b992147
LP
1132 return r;
1133 } else if (r > 0)
1134 break;
1135 }
1136
1b992147 1137 i->gid_set = true;
8530dc44 1138 i->gid = search_uid;
1b992147
LP
1139 }
1140
d5099efc 1141 r = hashmap_ensure_allocated(&todo_gids, NULL);
1b992147
LP
1142 if (r < 0)
1143 return log_oom();
1144
1145 r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
1146 if (r < 0)
1147 return log_oom();
1148
c1b6b04f 1149 i->todo_group = true;
1b992147
LP
1150 log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
1151
1152 return 0;
1153}
1154
1155static int process_item(Item *i) {
1156 int r;
1157
1158 assert(i);
1159
1160 switch (i->type) {
1161
1162 case ADD_USER:
1163 r = add_group(i);
1164 if (r < 0)
1165 return r;
1166
1167 return add_user(i);
1168
1169 case ADD_GROUP: {
1170 Item *j;
1171
1172 j = hashmap_get(users, i->name);
1173 if (j) {
1174 /* There's already user to be created for this
1175 * name, let's process that in one step */
1176
1177 if (i->gid_set) {
1178 j->gid = i->gid;
1179 j->gid_set = true;
1180 }
1181
1182 if (i->gid_path) {
1183 free(j->gid_path);
1184 j->gid_path = strdup(i->gid_path);
1185 if (!j->gid_path)
1186 return log_oom();
1187 }
1188
1189 return 0;
1190 }
1191
1192 return add_group(i);
1193 }
1b992147 1194
a12b0cc3
LP
1195 default:
1196 assert_not_reached("Unknown item type");
1197 }
1b992147
LP
1198}
1199
1200static void item_free(Item *i) {
1201
1202 if (!i)
1203 return;
1204
1205 free(i->name);
1206 free(i->uid_path);
1207 free(i->gid_path);
1208 free(i->description);
1209 free(i);
1210}
1211
1212DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
1213
a12b0cc3
LP
1214static int add_implicit(void) {
1215 char *g, **l;
1216 Iterator iterator;
1217 int r;
1218
1219 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1220
1221 HASHMAP_FOREACH_KEY(l, g, members, iterator) {
1222 Item *i;
1223 char **m;
1224
1225 i = hashmap_get(groups, g);
1226 if (!i) {
1227 _cleanup_(item_freep) Item *j = NULL;
1228
d5099efc 1229 r = hashmap_ensure_allocated(&groups, &string_hash_ops);
a12b0cc3
LP
1230 if (r < 0)
1231 return log_oom();
1232
1233 j = new0(Item, 1);
1234 if (!j)
1235 return log_oom();
1236
1237 j->type = ADD_GROUP;
1238 j->name = strdup(g);
1239 if (!j->name)
1240 return log_oom();
1241
1242 r = hashmap_put(groups, j->name, j);
1243 if (r < 0)
1244 return log_oom();
1245
1246 log_debug("Adding implicit group '%s' due to m line", j->name);
1247 j = NULL;
1248 }
1249
1250 STRV_FOREACH(m, l) {
1251
1252 i = hashmap_get(users, *m);
1253 if (!i) {
1254 _cleanup_(item_freep) Item *j = NULL;
1255
d5099efc 1256 r = hashmap_ensure_allocated(&users, &string_hash_ops);
a12b0cc3
LP
1257 if (r < 0)
1258 return log_oom();
1259
1260 j = new0(Item, 1);
1261 if (!j)
1262 return log_oom();
1263
1264 j->type = ADD_USER;
1265 j->name = strdup(*m);
1266 if (!j->name)
1267 return log_oom();
1268
1269 r = hashmap_put(users, j->name, j);
1270 if (r < 0)
1271 return log_oom();
1272
1273 log_debug("Adding implicit user '%s' due to m line", j->name);
1274 j = NULL;
1275 }
1276 }
1277 }
1278
1279 return 0;
1280}
1281
1b992147
LP
1282static bool item_equal(Item *a, Item *b) {
1283 assert(a);
1284 assert(b);
1285
1286 if (a->type != b->type)
1287 return false;
1288
1289 if (!streq_ptr(a->name, b->name))
1290 return false;
1291
1292 if (!streq_ptr(a->uid_path, b->uid_path))
1293 return false;
1294
1295 if (!streq_ptr(a->gid_path, b->gid_path))
1296 return false;
1297
1298 if (!streq_ptr(a->description, b->description))
1299 return false;
1300
1301 if (a->uid_set != b->uid_set)
1302 return false;
1303
1304 if (a->uid_set && a->uid != b->uid)
1305 return false;
1306
1307 if (a->gid_set != b->gid_set)
1308 return false;
1309
1310 if (a->gid_set && a->gid != b->gid)
1311 return false;
1312
7629889c
LP
1313 if (!streq_ptr(a->home, b->home))
1314 return false;
1315
1b992147
LP
1316 return true;
1317}
1318
1319static bool valid_user_group_name(const char *u) {
1320 const char *i;
1321 long sz;
1322
24fb7c1f 1323 if (isempty(u))
1b992147
LP
1324 return false;
1325
1326 if (!(u[0] >= 'a' && u[0] <= 'z') &&
1327 !(u[0] >= 'A' && u[0] <= 'Z') &&
1328 u[0] != '_')
1329 return false;
1330
1331 for (i = u+1; *i; i++) {
1332 if (!(*i >= 'a' && *i <= 'z') &&
1333 !(*i >= 'A' && *i <= 'Z') &&
1334 !(*i >= '0' && *i <= '9') &&
1335 *i != '_' &&
1336 *i != '-')
1337 return false;
1338 }
1339
1340 sz = sysconf(_SC_LOGIN_NAME_MAX);
1341 assert_se(sz > 0);
1342
1343 if ((size_t) (i-u) > (size_t) sz)
1344 return false;
1345
932ad62b
LP
1346 if ((size_t) (i-u) > UT_NAMESIZE - 1)
1347 return false;
1348
1b992147
LP
1349 return true;
1350}
1351
1352static bool valid_gecos(const char *d) {
1353
7629889c
LP
1354 if (!d)
1355 return false;
1356
1b992147
LP
1357 if (!utf8_is_valid(d))
1358 return false;
1359
38c74dad
LP
1360 if (string_has_cc(d, NULL))
1361 return false;
1362
1363 /* Colons are used as field separators, and hence not OK */
1364 if (strchr(d, ':'))
1b992147
LP
1365 return false;
1366
1367 return true;
1368}
1369
7629889c
LP
1370static bool valid_home(const char *p) {
1371
1372 if (isempty(p))
1373 return false;
1374
1375 if (!utf8_is_valid(p))
1376 return false;
1377
1378 if (string_has_cc(p, NULL))
1379 return false;
1380
1381 if (!path_is_absolute(p))
1382 return false;
1383
1384 if (!path_is_safe(p))
1385 return false;
1386
1387 /* Colons are used as field separators, and hence not OK */
1388 if (strchr(p, ':'))
1389 return false;
1390
1391 return true;
1392}
1393
1b992147
LP
1394static int parse_line(const char *fname, unsigned line, const char *buffer) {
1395
1396 static const Specifier specifier_table[] = {
1397 { 'm', specifier_machine_id, NULL },
1398 { 'b', specifier_boot_id, NULL },
1399 { 'H', specifier_host_name, NULL },
1400 { 'v', specifier_kernel_release, NULL },
1401 {}
1402 };
1403
8530dc44 1404 _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
1b992147
LP
1405 _cleanup_(item_freep) Item *i = NULL;
1406 Item *existing;
1407 Hashmap *h;
7629889c
LP
1408 int r;
1409 const char *p;
1b992147
LP
1410
1411 assert(fname);
1412 assert(line >= 1);
1413 assert(buffer);
1414
7629889c
LP
1415 /* Parse columns */
1416 p = buffer;
1417 r = unquote_many_words(&p, &action, &name, &id, &description, &home, NULL);
1418 if (r < 0) {
1b992147 1419 log_error("[%s:%u] Syntax error.", fname, line);
7629889c
LP
1420 return r;
1421 }
1422 if (r < 2) {
1423 log_error("[%s:%u] Missing action and name columns.", fname, line);
1424 return -EINVAL;
1425 }
1426 if (*p != 0) {
1427 log_error("[%s:%u] Trailing garbage.", fname, line);
1428 return -EINVAL;
1b992147
LP
1429 }
1430
7629889c 1431 /* Verify action */
1b992147
LP
1432 if (strlen(action) != 1) {
1433 log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
1434 return -EINVAL;
1435 }
1436
8530dc44 1437 if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) {
1b992147
LP
1438 log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
1439 return -EBADMSG;
1440 }
1441
7629889c 1442 /* Verify name */
8530dc44
LP
1443 if (isempty(name) || streq(name, "-")) {
1444 free(name);
1445 name = NULL;
1b992147
LP
1446 }
1447
8530dc44
LP
1448 if (name) {
1449 r = specifier_printf(name, specifier_table, NULL, &resolved_name);
1450 if (r < 0) {
1451 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1452 return r;
1453 }
1454
1455 if (!valid_user_group_name(resolved_name)) {
1456 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
1457 return -EINVAL;
1458 }
1b992147
LP
1459 }
1460
8530dc44 1461 /* Verify id */
7629889c
LP
1462 if (isempty(id) || streq(id, "-")) {
1463 free(id);
1464 id = NULL;
1465 }
1466
8530dc44
LP
1467 if (id) {
1468 r = specifier_printf(id, specifier_table, NULL, &resolved_id);
1469 if (r < 0) {
1470 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1471 return r;
1472 }
1473 }
1474
1475 /* Verify description */
7629889c
LP
1476 if (isempty(description) || streq(description, "-")) {
1477 free(description);
1478 description = NULL;
1479 }
1b992147 1480
8530dc44
LP
1481 if (description) {
1482 if (!valid_gecos(description)) {
1483 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description);
1484 return -EINVAL;
1485 }
1486 }
1487
1488 /* Verify home */
7629889c
LP
1489 if (isempty(home) || streq(home, "-")) {
1490 free(home);
1491 home = NULL;
1b992147
LP
1492 }
1493
8530dc44
LP
1494 if (home) {
1495 if (!valid_home(home)) {
1496 log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home);
1497 return -EINVAL;
1498 }
1499 }
1500
a12b0cc3 1501 switch (action[0]) {
1b992147 1502
8530dc44
LP
1503 case ADD_RANGE:
1504 if (resolved_name) {
1505 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname, line);
1506 return -EINVAL;
1507 }
1b992147 1508
8530dc44
LP
1509 if (!resolved_id) {
1510 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname, line);
1511 return -EINVAL;
1512 }
1b992147 1513
7629889c 1514 if (description) {
8530dc44 1515 log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line);
7629889c
LP
1516 return -EINVAL;
1517 }
1518
1519 if (home) {
8530dc44 1520 log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line);
7629889c
LP
1521 return -EINVAL;
1522 }
1523
8530dc44
LP
1524 r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id);
1525 if (r < 0) {
1526 log_error("[%s:%u] Invalid UID range %s.", fname, line, resolved_id);
a12b0cc3
LP
1527 return -EINVAL;
1528 }
1b992147 1529
8530dc44
LP
1530 return 0;
1531
1532 case ADD_MEMBER: {
1533 char **l;
1534
1535 /* Try to extend an existing member or group item */
1536 if (!name) {
1537 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname, line);
1538 return -EINVAL;
1539 }
1540
1541 if (!resolved_id) {
1542 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
1543 return -EINVAL;
a12b0cc3 1544 }
1b992147 1545
a12b0cc3
LP
1546 if (!valid_user_group_name(resolved_id)) {
1547 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
1548 return -EINVAL;
1549 }
1b992147 1550
8530dc44
LP
1551 if (description) {
1552 log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
1553 return -EINVAL;
1554 }
1555
1556 if (home) {
1557 log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line);
1558 return -EINVAL;
1559 }
1560
d5099efc 1561 r = hashmap_ensure_allocated(&members, &string_hash_ops);
7629889c
LP
1562 if (r < 0)
1563 return log_oom();
a12b0cc3
LP
1564
1565 l = hashmap_get(members, resolved_id);
1566 if (l) {
1567 /* A list for this group name already exists, let's append to it */
1568 r = strv_push(&l, resolved_name);
1569 if (r < 0)
1570 return log_oom();
1571
1572 resolved_name = NULL;
1573
1574 assert_se(hashmap_update(members, resolved_id, l) >= 0);
1b992147 1575 } else {
a12b0cc3
LP
1576 /* No list for this group name exists yet, create one */
1577
1578 l = new0(char *, 2);
1579 if (!l)
1580 return -ENOMEM;
1581
1582 l[0] = resolved_name;
1583 l[1] = NULL;
1b992147 1584
a12b0cc3 1585 r = hashmap_put(members, resolved_id, l);
1b992147 1586 if (r < 0) {
a12b0cc3
LP
1587 free(l);
1588 return log_oom();
1b992147
LP
1589 }
1590
a12b0cc3 1591 resolved_id = resolved_name = NULL;
1b992147 1592 }
a12b0cc3
LP
1593
1594 return 0;
1b992147
LP
1595 }
1596
a12b0cc3 1597 case ADD_USER:
8530dc44
LP
1598 if (!name) {
1599 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname, line);
1600 return -EINVAL;
1601 }
1602
d5099efc 1603 r = hashmap_ensure_allocated(&users, &string_hash_ops);
a12b0cc3
LP
1604 if (r < 0)
1605 return log_oom();
1606
1607 i = new0(Item, 1);
1608 if (!i)
1609 return log_oom();
1610
8530dc44
LP
1611 if (resolved_id) {
1612 if (path_is_absolute(resolved_id)) {
1613 i->uid_path = resolved_id;
1614 resolved_id = NULL;
a12b0cc3
LP
1615
1616 path_kill_slashes(i->uid_path);
a12b0cc3 1617 } else {
8530dc44 1618 r = parse_uid(resolved_id, &i->uid);
a12b0cc3
LP
1619 if (r < 0) {
1620 log_error("Failed to parse UID: %s", id);
1621 return -EBADMSG;
1622 }
1623
1624 i->uid_set = true;
1625 }
1626 }
1627
8530dc44
LP
1628 i->description = description;
1629 description = NULL;
7629889c 1630
8530dc44
LP
1631 i->home = home;
1632 home = NULL;
a12b0cc3 1633
1b992147 1634 h = users;
a12b0cc3
LP
1635 break;
1636
1637 case ADD_GROUP:
8530dc44
LP
1638 if (!name) {
1639 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname, line);
1640 return -EINVAL;
1641 }
a12b0cc3 1642
7629889c 1643 if (description) {
a12b0cc3
LP
1644 log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
1645 return -EINVAL;
1646 }
1647
7629889c
LP
1648 if (home) {
1649 log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line);
1650 return -EINVAL;
1651 }
1652
d5099efc 1653 r = hashmap_ensure_allocated(&groups, &string_hash_ops);
7629889c
LP
1654 if (r < 0)
1655 return log_oom();
1656
a12b0cc3
LP
1657 i = new0(Item, 1);
1658 if (!i)
1659 return log_oom();
1660
8530dc44
LP
1661 if (resolved_id) {
1662 if (path_is_absolute(resolved_id)) {
1663 i->gid_path = resolved_id;
1664 resolved_id = NULL;
a12b0cc3
LP
1665
1666 path_kill_slashes(i->gid_path);
1667 } else {
8530dc44 1668 r = parse_gid(resolved_id, &i->gid);
a12b0cc3
LP
1669 if (r < 0) {
1670 log_error("Failed to parse GID: %s", id);
1671 return -EBADMSG;
1672 }
1673
1674 i->gid_set = true;
1675 }
1676 }
1677
1b992147 1678 h = groups;
a12b0cc3 1679 break;
8530dc44 1680
bce415ed
RC
1681 default:
1682 return -EBADMSG;
1b992147 1683 }
a12b0cc3
LP
1684
1685 i->type = action[0];
1686 i->name = resolved_name;
1687 resolved_name = NULL;
1b992147
LP
1688
1689 existing = hashmap_get(h, i->name);
1690 if (existing) {
1691
1692 /* Two identical items are fine */
1693 if (!item_equal(existing, i))
1694 log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name);
1695
1696 return 0;
1697 }
1698
1699 r = hashmap_put(h, i->name, i);
a12b0cc3
LP
1700 if (r < 0)
1701 return log_oom();
1b992147
LP
1702
1703 i = NULL;
1704 return 0;
1705}
1706
1707static int read_config_file(const char *fn, bool ignore_enoent) {
dfc87cbf
LP
1708 _cleanup_fclose_ FILE *rf = NULL;
1709 FILE *f = NULL;
1b992147
LP
1710 char line[LINE_MAX];
1711 unsigned v = 0;
c4640902 1712 int r = 0;
1b992147
LP
1713
1714 assert(fn);
1715
dfc87cbf
LP
1716 if (streq(fn, "-"))
1717 f = stdin;
1718 else {
1719 r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &rf);
1720 if (r < 0) {
1721 if (ignore_enoent && r == -ENOENT)
1722 return 0;
1b992147 1723
da927ba9 1724 log_error_errno(r, "Failed to open '%s', ignoring: %m", fn);
dfc87cbf
LP
1725 return r;
1726 }
1727
1728 f = rf;
1b992147
LP
1729 }
1730
1731 FOREACH_LINE(line, f, break) {
1732 char *l;
1733 int k;
1734
1735 v++;
1736
1737 l = strstrip(line);
1738 if (*l == '#' || *l == 0)
1739 continue;
1740
1741 k = parse_line(fn, v, l);
1742 if (k < 0 && r == 0)
1743 r = k;
1744 }
1745
1746 if (ferror(f)) {
1747 log_error("Failed to read from file %s: %m", fn);
1748 if (r == 0)
1749 r = -EIO;
1750 }
1751
1752 return r;
1753}
1754
1b992147
LP
1755static void free_database(Hashmap *by_name, Hashmap *by_id) {
1756 char *name;
1757
1758 for (;;) {
1759 name = hashmap_first(by_id);
1760 if (!name)
1761 break;
1762
1763 hashmap_remove(by_name, name);
1764
1765 hashmap_steal_first_key(by_id);
1766 free(name);
1767 }
1768
1769 while ((name = hashmap_steal_first_key(by_name)))
1770 free(name);
1771
1772 hashmap_free(by_name);
1773 hashmap_free(by_id);
1774}
1775
601185b4 1776static void help(void) {
1b992147
LP
1777 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1778 "Creates system user accounts.\n\n"
1779 " -h --help Show this help\n"
1780 " --version Show package version\n"
601185b4
ZJS
1781 " --root=PATH Operate on an alternate filesystem root\n"
1782 , program_invocation_short_name);
1b992147
LP
1783}
1784
1785static int parse_argv(int argc, char *argv[]) {
1786
1787 enum {
1788 ARG_VERSION = 0x100,
1789 ARG_ROOT,
1790 };
1791
1792 static const struct option options[] = {
1793 { "help", no_argument, NULL, 'h' },
1794 { "version", no_argument, NULL, ARG_VERSION },
1795 { "root", required_argument, NULL, ARG_ROOT },
1796 {}
1797 };
1798
1799 int c;
1800
1801 assert(argc >= 0);
1802 assert(argv);
1803
601185b4 1804 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
1b992147
LP
1805
1806 switch (c) {
1807
1808 case 'h':
601185b4
ZJS
1809 help();
1810 return 0;
1b992147
LP
1811
1812 case ARG_VERSION:
1813 puts(PACKAGE_STRING);
1814 puts(SYSTEMD_FEATURES);
1815 return 0;
1816
1817 case ARG_ROOT:
1818 free(arg_root);
1819 arg_root = path_make_absolute_cwd(optarg);
1820 if (!arg_root)
1821 return log_oom();
1822
1823 path_kill_slashes(arg_root);
1824 break;
1825
1826 case '?':
1827 return -EINVAL;
1828
1829 default:
1830 assert_not_reached("Unhandled option");
1831 }
1b992147
LP
1832
1833 return 1;
1834}
1835
1836int main(int argc, char *argv[]) {
1837
1838 _cleanup_close_ int lock = -1;
1839 Iterator iterator;
1840 int r, k;
1841 Item *i;
a12b0cc3 1842 char *n;
1b992147
LP
1843
1844 r = parse_argv(argc, argv);
1845 if (r <= 0)
1846 goto finish;
1847
1848 log_set_target(LOG_TARGET_AUTO);
1849 log_parse_environment();
1850 log_open();
1851
1852 umask(0022);
1853
cc56fafe 1854 r = mac_selinux_init(NULL);
9f1c1940 1855 if (r < 0) {
da927ba9 1856 log_error_errno(r, "SELinux setup failed: %m");
9f1c1940
ZJS
1857 goto finish;
1858 }
1b992147
LP
1859
1860 if (optind < argc) {
1861 int j;
1862
1863 for (j = optind; j < argc; j++) {
1864 k = read_config_file(argv[j], false);
1865 if (k < 0 && r == 0)
1866 r = k;
1867 }
1868 } else {
1869 _cleanup_strv_free_ char **files = NULL;
1870 char **f;
1871
1872 r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
1873 if (r < 0) {
da927ba9 1874 log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
1b992147
LP
1875 goto finish;
1876 }
1877
1878 STRV_FOREACH(f, files) {
1879 k = read_config_file(*f, true);
1880 if (k < 0 && r == 0)
1881 r = k;
1882 }
1883 }
1884
8530dc44
LP
1885 if (!uid_range) {
1886 /* Default to default range of 1..SYSTEMD_UID_MAX */
1887 r = uid_range_add(&uid_range, &n_uid_range, 1, SYSTEM_UID_MAX);
1888 if (r < 0) {
1889 log_oom();
1890 goto finish;
1891 }
1892 }
1893
a12b0cc3
LP
1894 r = add_implicit();
1895 if (r < 0)
1896 goto finish;
1897
45035609 1898 lock = take_password_lock(arg_root);
1b992147 1899 if (lock < 0) {
da927ba9 1900 log_error_errno(lock, "Failed to take lock: %m");
1b992147
LP
1901 goto finish;
1902 }
1903
1904 r = load_user_database();
1905 if (r < 0) {
da927ba9 1906 log_error_errno(r, "Failed to load user database: %m");
1b992147
LP
1907 goto finish;
1908 }
1909
1910 r = load_group_database();
1911 if (r < 0) {
da927ba9 1912 log_error_errno(r, "Failed to read group database: %m");
1b992147
LP
1913 goto finish;
1914 }
1915
1916 HASHMAP_FOREACH(i, groups, iterator)
1917 process_item(i);
1918
1919 HASHMAP_FOREACH(i, users, iterator)
1920 process_item(i);
1921
1922 r = write_files();
1923 if (r < 0)
da927ba9 1924 log_error_errno(r, "Failed to write files: %m");
1b992147
LP
1925
1926finish:
1927 while ((i = hashmap_steal_first(groups)))
1928 item_free(i);
1929
1930 while ((i = hashmap_steal_first(users)))
1931 item_free(i);
1932
a12b0cc3
LP
1933 while ((n = hashmap_first_key(members))) {
1934 strv_free(hashmap_steal_first(members));
1935 free(n);
1936 }
1937
1b992147
LP
1938 hashmap_free(groups);
1939 hashmap_free(users);
a12b0cc3 1940 hashmap_free(members);
1b992147
LP
1941 hashmap_free(todo_uids);
1942 hashmap_free(todo_gids);
1943
1944 free_database(database_user, database_uid);
1945 free_database(database_group, database_gid);
1946
1947 free(arg_root);
1948
1949 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1950}