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