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