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