1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright 2014 Lennart Poettering
9 #include "alloc-util.h"
10 #include "conf-files.h"
14 #include "fileio-label.h"
15 #include "format-util.h"
19 #include "path-util.h"
20 #include "selinux-util.h"
21 #include "smack-util.h"
22 #include "specifier.h"
23 #include "string-util.h"
25 #include "terminal-util.h"
26 #include "uid-range.h"
27 #include "user-util.h"
31 typedef enum ItemType
{
53 /* When set the group with the specified gid must exist
54 * and the check if a uid clashes with the gid is skipped.
64 static char *arg_root
= NULL
;
65 static bool arg_cat_config
= false;
66 static const char *arg_replace
= NULL
;
67 static bool arg_inline
= false;
68 static bool arg_no_pager
= false;
70 static OrderedHashmap
*users
= NULL
, *groups
= NULL
;
71 static OrderedHashmap
*todo_uids
= NULL
, *todo_gids
= NULL
;
72 static OrderedHashmap
*members
= NULL
;
74 static Hashmap
*database_uid
= NULL
, *database_user
= NULL
;
75 static Hashmap
*database_gid
= NULL
, *database_group
= NULL
;
77 static uid_t search_uid
= UID_INVALID
;
78 static UidRange
*uid_range
= NULL
;
79 static unsigned n_uid_range
= 0;
81 static int load_user_database(void) {
82 _cleanup_fclose_
FILE *f
= NULL
;
83 const char *passwd_path
;
87 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
88 f
= fopen(passwd_path
, "re");
90 return errno
== ENOENT
? 0 : -errno
;
92 r
= hashmap_ensure_allocated(&database_user
, &string_hash_ops
);
96 r
= hashmap_ensure_allocated(&database_uid
, NULL
);
100 while ((r
= fgetpwent_sane(f
, &pw
)) > 0) {
104 n
= strdup(pw
->pw_name
);
108 k
= hashmap_put(database_user
, n
, UID_TO_PTR(pw
->pw_uid
));
109 if (k
< 0 && k
!= -EEXIST
) {
114 q
= hashmap_put(database_uid
, UID_TO_PTR(pw
->pw_uid
), n
);
115 if (q
< 0 && q
!= -EEXIST
) {
121 if (k
<= 0 && q
<= 0)
127 static int load_group_database(void) {
128 _cleanup_fclose_
FILE *f
= NULL
;
129 const char *group_path
;
133 group_path
= prefix_roota(arg_root
, "/etc/group");
134 f
= fopen(group_path
, "re");
136 return errno
== ENOENT
? 0 : -errno
;
138 r
= hashmap_ensure_allocated(&database_group
, &string_hash_ops
);
142 r
= hashmap_ensure_allocated(&database_gid
, NULL
);
147 while ((gr
= fgetgrent(f
))) {
151 n
= strdup(gr
->gr_name
);
155 k
= hashmap_put(database_group
, n
, GID_TO_PTR(gr
->gr_gid
));
156 if (k
< 0 && k
!= -EEXIST
) {
161 q
= hashmap_put(database_gid
, GID_TO_PTR(gr
->gr_gid
), n
);
162 if (q
< 0 && q
!= -EEXIST
) {
168 if (k
<= 0 && q
<= 0)
173 if (!IN_SET(errno
, 0, ENOENT
))
179 static int make_backup(const char *target
, const char *x
) {
180 _cleanup_close_
int src
= -1;
181 _cleanup_fclose_
FILE *dst
= NULL
;
182 _cleanup_free_
char *temp
= NULL
;
184 struct timespec ts
[2];
188 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
190 if (errno
== ENOENT
) /* No backup necessary... */
196 if (fstat(src
, &st
) < 0)
199 r
= fopen_temporary_label(target
, x
, &dst
, &temp
);
203 r
= copy_bytes(src
, fileno(dst
), (uint64_t) -1, COPY_REFLINK
);
207 /* Don't fail on chmod() or chown(). If it stays owned by us
208 * and/or unreadable by others, then it isn't too bad... */
210 backup
= strjoina(x
, "-");
212 /* Copy over the access mask */
213 if (fchmod(fileno(dst
), st
.st_mode
& 07777) < 0)
214 log_warning_errno(errno
, "Failed to change mode on %s: %m", backup
);
216 if (fchown(fileno(dst
), st
.st_uid
, st
.st_gid
)< 0)
217 log_warning_errno(errno
, "Failed to change ownership of %s: %m", backup
);
221 if (futimens(fileno(dst
), ts
) < 0)
222 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
224 r
= fflush_sync_and_check(dst
);
228 if (rename(temp
, backup
) < 0) {
240 static int putgrent_with_members(const struct group
*gr
, FILE *group
) {
246 a
= ordered_hashmap_get(members
, gr
->gr_name
);
248 _cleanup_strv_free_
char **l
= NULL
;
252 l
= strv_copy(gr
->gr_mem
);
257 if (strv_find(l
, *i
))
260 if (strv_extend(&l
, *i
) < 0)
276 r
= putgrent_sane(&t
, group
);
277 return r
< 0 ? r
: 1;
281 return putgrent_sane(gr
, group
);
285 static int putsgent_with_members(const struct sgrp
*sg
, FILE *gshadow
) {
291 a
= ordered_hashmap_get(members
, sg
->sg_namp
);
293 _cleanup_strv_free_
char **l
= NULL
;
297 l
= strv_copy(sg
->sg_mem
);
302 if (strv_find(l
, *i
))
305 if (strv_extend(&l
, *i
) < 0)
321 r
= putsgent_sane(&t
, gshadow
);
322 return r
< 0 ? r
: 1;
326 return putsgent_sane(sg
, gshadow
);
330 static int sync_rights(FILE *from
, FILE *to
) {
333 if (fstat(fileno(from
), &st
) < 0)
336 if (fchmod(fileno(to
), st
.st_mode
& 07777) < 0)
339 if (fchown(fileno(to
), st
.st_uid
, st
.st_gid
) < 0)
345 static int rename_and_apply_smack(const char *temp_path
, const char *dest_path
) {
347 if (rename(temp_path
, dest_path
) < 0)
350 #ifdef SMACK_RUN_LABEL
351 r
= mac_smack_apply(dest_path
, SMACK_ATTR_ACCESS
, SMACK_FLOOR_LABEL
);
358 static const char* default_shell(uid_t uid
) {
359 return uid
== 0 ? "/bin/sh" : "/sbin/nologin";
362 static int write_temporary_passwd(const char *passwd_path
, FILE **tmpfile
, char **tmpfile_path
) {
363 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
364 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
365 struct passwd
*pw
= NULL
;
370 if (ordered_hashmap_size(todo_uids
) == 0)
373 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
377 original
= fopen(passwd_path
, "re");
380 r
= sync_rights(original
, passwd
);
384 while ((r
= fgetpwent_sane(original
, &pw
)) > 0) {
386 i
= ordered_hashmap_get(users
, pw
->pw_name
);
387 if (i
&& i
->todo_user
) {
388 log_error("%s: User \"%s\" already exists.", passwd_path
, pw
->pw_name
);
392 if (ordered_hashmap_contains(todo_uids
, UID_TO_PTR(pw
->pw_uid
))) {
393 log_error("%s: Detected collision for UID " UID_FMT
".", passwd_path
, pw
->pw_uid
);
397 /* Make sure we keep the NIS entries (if any) at the end. */
398 if (IN_SET(pw
->pw_name
[0], '+', '-'))
401 r
= putpwent_sane(pw
, passwd
);
411 if (fchmod(fileno(passwd
), 0644) < 0)
415 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
420 .pw_gecos
= i
->description
,
422 /* "x" means the password is stored in the shadow file */
423 .pw_passwd
= (char*) "x",
425 /* We default to the root directory as home */
426 .pw_dir
= i
->home
? i
->home
: (char*) "/",
428 /* Initialize the shell to nologin, with one exception:
429 * for root we patch in something special */
430 .pw_shell
= i
->shell
?: (char*) default_shell(i
->uid
),
433 r
= putpwent_sane(&n
, passwd
);
438 /* Append the remaining NIS entries if any */
440 r
= putpwent_sane(pw
, passwd
);
444 r
= fgetpwent_sane(original
, &pw
);
451 r
= fflush_and_check(passwd
);
455 *tmpfile
= TAKE_PTR(passwd
);
456 *tmpfile_path
= TAKE_PTR(passwd_tmp
);
461 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
462 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
463 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
464 struct spwd
*sp
= NULL
;
470 if (ordered_hashmap_size(todo_uids
) == 0)
473 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
477 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
479 original
= fopen(shadow_path
, "re");
482 r
= sync_rights(original
, shadow
);
486 while ((r
= fgetspent_sane(original
, &sp
)) > 0) {
488 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
489 if (i
&& i
->todo_user
) {
490 /* we will update the existing entry */
491 sp
->sp_lstchg
= lstchg
;
493 /* only the /etc/shadow stage is left, so we can
494 * safely remove the item from the todo set */
495 i
->todo_user
= false;
496 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
499 /* Make sure we keep the NIS entries (if any) at the end. */
500 if (IN_SET(sp
->sp_namp
[0], '+', '-'))
503 r
= putspent_sane(sp
, shadow
);
513 if (fchmod(fileno(shadow
), 0000) < 0)
517 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
520 .sp_pwdp
= (char*) "!!",
527 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
530 r
= putspent_sane(&n
, shadow
);
535 /* Append the remaining NIS entries if any */
537 r
= putspent_sane(sp
, shadow
);
541 r
= fgetspent_sane(original
, &sp
);
547 if (!IN_SET(errno
, 0, ENOENT
))
550 r
= fflush_sync_and_check(shadow
);
554 *tmpfile
= TAKE_PTR(shadow
);
555 *tmpfile_path
= TAKE_PTR(shadow_tmp
);
560 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
561 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
562 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
563 bool group_changed
= false;
564 struct group
*gr
= NULL
;
569 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
572 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
576 original
= fopen(group_path
, "re");
579 r
= sync_rights(original
, group
);
583 while ((r
= fgetgrent_sane(original
, &gr
)) > 0) {
584 /* Safety checks against name and GID collisions. Normally,
585 * this should be unnecessary, but given that we look at the
586 * entries anyway here, let's make an extra verification
587 * step that we don't generate duplicate entries. */
589 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
590 if (i
&& i
->todo_group
) {
591 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
595 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
596 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
600 /* Make sure we keep the NIS entries (if any) at the end. */
601 if (IN_SET(gr
->gr_name
[0], '+', '-'))
604 r
= putgrent_with_members(gr
, group
);
608 group_changed
= true;
616 if (fchmod(fileno(group
), 0644) < 0)
620 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
624 .gr_passwd
= (char*) "x",
627 r
= putgrent_with_members(&n
, group
);
631 group_changed
= true;
634 /* Append the remaining NIS entries if any */
636 r
= putgrent_sane(gr
, group
);
640 r
= fgetgrent_sane(original
, &gr
);
647 r
= fflush_sync_and_check(group
);
652 *tmpfile
= TAKE_PTR(group
);
653 *tmpfile_path
= TAKE_PTR(group_tmp
);
658 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
660 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
661 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
662 bool group_changed
= false;
667 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
670 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
674 original
= fopen(gshadow_path
, "re");
678 r
= sync_rights(original
, gshadow
);
682 while ((r
= fgetsgent_sane(original
, &sg
)) > 0) {
684 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
685 if (i
&& i
->todo_group
) {
686 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
690 r
= putsgent_with_members(sg
, gshadow
);
694 group_changed
= true;
702 if (fchmod(fileno(gshadow
), 0000) < 0)
706 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
709 .sg_passwd
= (char*) "!!",
712 r
= putsgent_with_members(&n
, gshadow
);
716 group_changed
= true;
719 r
= fflush_sync_and_check(gshadow
);
724 *tmpfile
= TAKE_PTR(gshadow
);
725 *tmpfile_path
= TAKE_PTR(gshadow_tmp
);
733 static int write_files(void) {
734 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
735 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
736 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
739 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
740 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
741 group_path
= prefix_roota(arg_root
, "/etc/group");
742 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
744 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
748 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
752 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
756 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
760 /* Make a backup of the old files */
762 r
= make_backup("/etc/group", group_path
);
767 r
= make_backup("/etc/gshadow", gshadow_path
);
773 r
= make_backup("/etc/passwd", passwd_path
);
778 r
= make_backup("/etc/shadow", shadow_path
);
783 /* And make the new files count */
785 r
= rename_and_apply_smack(group_tmp
, group_path
);
789 group_tmp
= mfree(group_tmp
);
792 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
796 gshadow_tmp
= mfree(gshadow_tmp
);
800 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
804 passwd_tmp
= mfree(passwd_tmp
);
807 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
811 shadow_tmp
= mfree(shadow_tmp
);
817 static int uid_is_ok(uid_t uid
, const char *name
, bool check_with_gid
) {
823 /* Let's see if we already have assigned the UID a second time */
824 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
827 /* Try to avoid using uids that are already used by a group
828 * that doesn't have the same name as our new user. */
829 if (check_with_gid
) {
830 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
831 if (i
&& !streq(i
->name
, name
))
835 /* Let's check the files directly */
836 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
839 if (check_with_gid
) {
840 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
841 if (n
&& !streq(n
, name
))
845 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
851 if (!IN_SET(errno
, 0, ENOENT
))
854 if (check_with_gid
) {
856 g
= getgrgid((gid_t
) uid
);
858 if (!streq(g
->gr_name
, name
))
860 } else if (!IN_SET(errno
, 0, ENOENT
))
868 static int root_stat(const char *p
, struct stat
*st
) {
871 fix
= prefix_roota(arg_root
, p
);
872 if (stat(fix
, st
) < 0)
878 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
880 bool found_uid
= false, found_gid
= false;
886 /* First, try to get the gid directly */
887 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
892 /* Then, try to get the uid directly */
893 if ((_uid
|| (_gid
&& !found_gid
))
895 && root_stat(i
->uid_path
, &st
) >= 0) {
900 /* If we need the gid, but had no success yet, also derive it from the uid path */
901 if (_gid
&& !found_gid
) {
907 /* If that didn't work yet, then let's reuse the gid as uid */
908 if (_uid
&& !found_uid
&& i
->gid_path
) {
913 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
914 uid
= (uid_t
) st
.st_gid
;
936 static int add_user(Item
*i
) {
942 /* Check the database directly */
943 z
= hashmap_get(database_user
, i
->name
);
945 log_debug("User %s already exists.", i
->name
);
946 i
->uid
= PTR_TO_UID(z
);
956 p
= getpwnam(i
->name
);
958 log_debug("User %s already exists.", i
->name
);
962 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
968 if (!IN_SET(errno
, 0, ENOENT
))
969 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
972 /* Try to use the suggested numeric uid */
974 r
= uid_is_ok(i
->uid
, i
->name
, !i
->id_set_strict
);
976 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
978 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
983 /* If that didn't work, try to read it from the specified path */
987 if (read_id_from_file(i
, &c
, NULL
) > 0) {
989 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
990 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
992 r
= uid_is_ok(c
, i
->name
, true);
994 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
999 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
1004 /* Otherwise, try to reuse the group ID */
1005 if (!i
->uid_set
&& i
->gid_set
) {
1006 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
, true);
1008 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1010 i
->uid
= (uid_t
) i
->gid
;
1015 /* And if that didn't work either, let's try to find a free one */
1018 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1020 log_error("No free user ID available for %s.", i
->name
);
1024 r
= uid_is_ok(search_uid
, i
->name
, true);
1026 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1032 i
->uid
= search_uid
;
1035 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1039 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1043 i
->todo_user
= true;
1044 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1049 static int gid_is_ok(gid_t gid
) {
1053 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1056 /* Avoid reusing gids that are already used by a different user */
1057 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1060 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1063 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1071 if (!IN_SET(errno
, 0, ENOENT
))
1075 p
= getpwuid((uid_t
) gid
);
1078 if (!IN_SET(errno
, 0, ENOENT
))
1085 static int add_group(Item
*i
) {
1091 /* Check the database directly */
1092 z
= hashmap_get(database_group
, i
->name
);
1094 log_debug("Group %s already exists.", i
->name
);
1095 i
->gid
= PTR_TO_GID(z
);
1100 /* Also check NSS */
1105 g
= getgrnam(i
->name
);
1107 log_debug("Group %s already exists.", i
->name
);
1112 if (!IN_SET(errno
, 0, ENOENT
))
1113 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1116 /* Try to use the suggested numeric gid */
1118 r
= gid_is_ok(i
->gid
);
1120 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1121 if (i
->id_set_strict
) {
1122 /* If we require the gid to already exist we can return here:
1123 * r > 0: means the gid does not exist -> fail
1124 * r == 0: means the gid exists -> nothing more to do.
1127 log_error("Failed to create %s: please create GID %d", i
->name
, i
->gid
);
1134 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1139 /* Try to reuse the numeric uid, if there's one */
1140 if (!i
->gid_set
&& i
->uid_set
) {
1141 r
= gid_is_ok((gid_t
) i
->uid
);
1143 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1145 i
->gid
= (gid_t
) i
->uid
;
1150 /* If that didn't work, try to read it from the specified path */
1154 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1156 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1157 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1161 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1166 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1171 /* And if that didn't work either, let's try to find a free one */
1174 /* We look for new GIDs in the UID pool! */
1175 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1177 log_error("No free group ID available for %s.", i
->name
);
1181 r
= gid_is_ok(search_uid
);
1183 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1189 i
->gid
= search_uid
;
1192 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1196 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1200 i
->todo_group
= true;
1201 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1206 static int process_item(Item
*i
) {
1216 j
= ordered_hashmap_get(groups
, i
->name
);
1217 if (j
&& j
->todo_group
) {
1218 /* When the group with the same name is already in queue,
1219 * use the information about the group and do not create
1220 * duplicated group entry. */
1221 i
->gid_set
= j
->gid_set
;
1223 i
->id_set_strict
= true;
1234 return add_group(i
);
1237 assert_not_reached("Unknown item type");
1241 static void item_free(Item
*i
) {
1249 free(i
->description
);
1255 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1257 static int add_implicit(void) {
1262 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1263 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1267 if (!ordered_hashmap_get(users
, *m
)) {
1268 _cleanup_(item_freep
) Item
*j
= NULL
;
1270 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1279 j
->name
= strdup(*m
);
1283 r
= ordered_hashmap_put(users
, j
->name
, j
);
1287 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1291 if (!(ordered_hashmap_get(users
, g
) ||
1292 ordered_hashmap_get(groups
, g
))) {
1293 _cleanup_(item_freep
) Item
*j
= NULL
;
1295 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1303 j
->type
= ADD_GROUP
;
1304 j
->name
= strdup(g
);
1308 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1312 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1320 static bool item_equal(Item
*a
, Item
*b
) {
1324 if (a
->type
!= b
->type
)
1327 if (!streq_ptr(a
->name
, b
->name
))
1330 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1333 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1336 if (!streq_ptr(a
->description
, b
->description
))
1339 if (a
->uid_set
!= b
->uid_set
)
1342 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1345 if (a
->gid_set
!= b
->gid_set
)
1348 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1351 if (!streq_ptr(a
->home
, b
->home
))
1354 if (!streq_ptr(a
->shell
, b
->shell
))
1360 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1362 static const Specifier specifier_table
[] = {
1363 { 'm', specifier_machine_id
, NULL
},
1364 { 'b', specifier_boot_id
, NULL
},
1365 { 'H', specifier_host_name
, NULL
},
1366 { 'v', specifier_kernel_release
, NULL
},
1367 { 'T', specifier_tmp_dir
, NULL
},
1368 { 'V', specifier_var_tmp_dir
, NULL
},
1372 _cleanup_free_
char *action
= NULL
,
1373 *name
= NULL
, *resolved_name
= NULL
,
1374 *id
= NULL
, *resolved_id
= NULL
,
1375 *description
= NULL
, *resolved_description
= NULL
,
1376 *home
= NULL
, *resolved_home
= NULL
,
1377 *shell
, *resolved_shell
= NULL
;
1378 _cleanup_(item_freep
) Item
*i
= NULL
;
1390 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
,
1391 &action
, &name
, &id
, &description
, &home
, &shell
, NULL
);
1393 log_error("[%s:%u] Syntax error.", fname
, line
);
1397 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1401 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1406 if (strlen(action
) != 1) {
1407 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1411 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1412 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1417 if (isempty(name
) || streq(name
, "-"))
1421 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1423 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1427 if (!valid_user_group_name(resolved_name
)) {
1428 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1434 if (isempty(id
) || streq(id
, "-"))
1438 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1440 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1445 /* Verify description */
1446 if (isempty(description
) || streq(description
, "-"))
1447 description
= mfree(description
);
1450 r
= specifier_printf(description
, specifier_table
, NULL
, &resolved_description
);
1452 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, description
);
1456 if (!valid_gecos(resolved_description
)) {
1457 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, resolved_description
);
1463 if (isempty(home
) || streq(home
, "-"))
1467 r
= specifier_printf(home
, specifier_table
, NULL
, &resolved_home
);
1469 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, home
);
1473 if (!valid_home(resolved_home
)) {
1474 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, resolved_home
);
1480 if (isempty(shell
) || streq(shell
, "-"))
1481 shell
= mfree(shell
);
1484 r
= specifier_printf(shell
, specifier_table
, NULL
, &resolved_shell
);
1486 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, shell
);
1490 if (!valid_shell(resolved_shell
)) {
1491 log_error("[%s:%u] '%s' is not a valid login shell field.", fname
, line
, resolved_shell
);
1496 switch (action
[0]) {
1499 if (resolved_name
) {
1500 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1505 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1509 if (description
|| home
|| shell
) {
1510 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1511 fname
, line
, action
[0],
1512 description
? "GECOS" : home
? "home directory" : "login shell");
1516 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1518 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1527 /* Try to extend an existing member or group item */
1529 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1534 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1538 if (!valid_user_group_name(resolved_id
)) {
1539 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1543 if (description
|| home
|| shell
) {
1544 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1545 fname
, line
, action
[0],
1546 description
? "GECOS" : home
? "home directory" : "login shell");
1550 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1554 l
= ordered_hashmap_get(members
, resolved_id
);
1556 /* A list for this group name already exists, let's append to it */
1557 r
= strv_push(&l
, resolved_name
);
1561 resolved_name
= NULL
;
1563 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1565 /* No list for this group name exists yet, create one */
1567 l
= new0(char *, 2);
1571 l
[0] = resolved_name
;
1574 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1580 resolved_id
= resolved_name
= NULL
;
1588 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1592 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1601 if (path_is_absolute(resolved_id
)) {
1602 i
->uid_path
= TAKE_PTR(resolved_id
);
1603 path_simplify(i
->uid_path
, false);
1605 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1606 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1607 r
= parse_gid(gid
, &i
->gid
);
1609 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1611 i
->id_set_strict
= true;
1612 free_and_replace(resolved_id
, uid
);
1614 if (!streq(resolved_id
, "-")) {
1615 r
= parse_uid(resolved_id
, &i
->uid
);
1617 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1623 i
->description
= TAKE_PTR(resolved_description
);
1624 i
->home
= TAKE_PTR(resolved_home
);
1625 i
->shell
= TAKE_PTR(resolved_shell
);
1632 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1636 if (description
|| home
|| shell
) {
1637 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1638 fname
, line
, action
[0],
1639 description
? "GECOS" : home
? "home directory" : "login shell");
1643 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1652 if (path_is_absolute(resolved_id
)) {
1653 i
->gid_path
= TAKE_PTR(resolved_id
);
1654 path_simplify(i
->gid_path
, false);
1656 r
= parse_gid(resolved_id
, &i
->gid
);
1658 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1671 i
->type
= action
[0];
1672 i
->name
= TAKE_PTR(resolved_name
);
1674 existing
= ordered_hashmap_get(h
, i
->name
);
1677 /* Two identical items are fine */
1678 if (!item_equal(existing
, i
))
1679 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1684 r
= ordered_hashmap_put(h
, i
->name
, i
);
1692 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1693 _cleanup_fclose_
FILE *rf
= NULL
;
1695 char line
[LINE_MAX
];
1704 r
= search_and_fopen(fn
, "re", arg_root
, (const char**) CONF_PATHS_STRV("sysusers.d"), &rf
);
1706 if (ignore_enoent
&& r
== -ENOENT
)
1709 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1715 FOREACH_LINE(line
, f
, break) {
1722 if (IN_SET(*l
, 0, '#'))
1725 k
= parse_line(fn
, v
, l
);
1726 if (k
< 0 && r
== 0)
1731 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1739 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1743 name
= hashmap_first(by_id
);
1747 hashmap_remove(by_name
, name
);
1749 hashmap_steal_first_key(by_id
);
1753 while ((name
= hashmap_steal_first_key(by_name
)))
1756 hashmap_free(by_name
);
1757 hashmap_free(by_id
);
1760 static int cat_config(void) {
1761 _cleanup_strv_free_
char **files
= NULL
;
1764 r
= conf_files_list_with_replacement(arg_root
, CONF_PATHS_STRV("sysusers.d"), arg_replace
, &files
, NULL
);
1768 (void) pager_open(arg_no_pager
, false);
1770 return cat_files(NULL
, files
, 0);
1773 static void help(void) {
1774 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1775 "Creates system user accounts.\n\n"
1776 " -h --help Show this help\n"
1777 " --version Show package version\n"
1778 " --cat-config Show configuration files\n"
1779 " --root=PATH Operate on an alternate filesystem root\n"
1780 " --replace=PATH Treat arguments as replacement for PATH\n"
1781 " --inline Treat arguments as configuration lines\n"
1782 " --no-pager Do not pipe output into a pager\n"
1783 , program_invocation_short_name
);
1786 static int parse_argv(int argc
, char *argv
[]) {
1789 ARG_VERSION
= 0x100,
1797 static const struct option options
[] = {
1798 { "help", no_argument
, NULL
, 'h' },
1799 { "version", no_argument
, NULL
, ARG_VERSION
},
1800 { "cat-config", no_argument
, NULL
, ARG_CAT_CONFIG
},
1801 { "root", required_argument
, NULL
, ARG_ROOT
},
1802 { "replace", required_argument
, NULL
, ARG_REPLACE
},
1803 { "inline", no_argument
, NULL
, ARG_INLINE
},
1804 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1813 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1824 case ARG_CAT_CONFIG
:
1825 arg_cat_config
= true;
1829 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1835 if (!path_is_absolute(optarg
) ||
1836 !endswith(optarg
, ".conf")) {
1837 log_error("The argument to --replace= must an absolute path to a config file");
1841 arg_replace
= optarg
;
1849 arg_no_pager
= true;
1856 assert_not_reached("Unhandled option");
1859 if (arg_replace
&& arg_cat_config
) {
1860 log_error("Option --replace= is not supported with --cat-config");
1864 if (arg_replace
&& optind
>= argc
) {
1865 log_error("When --replace= is given, some configuration items must be specified");
1872 static int parse_arguments(char **args
) {
1877 STRV_FOREACH(arg
, args
) {
1879 /* Use (argument):n, where n==1 for the first positional arg */
1880 r
= parse_line("(argument)", pos
, *arg
);
1882 r
= read_config_file(*arg
, false);
1892 static int read_config_files(char **args
) {
1893 _cleanup_strv_free_
char **files
= NULL
;
1894 _cleanup_free_
char *p
= NULL
;
1898 r
= conf_files_list_with_replacement(arg_root
, CONF_PATHS_STRV("sysusers.d"), arg_replace
, &files
, &p
);
1902 STRV_FOREACH(f
, files
)
1903 if (p
&& path_equal(*f
, p
)) {
1904 log_debug("Parsing arguments at position \"%s\"…", *f
);
1906 r
= parse_arguments(args
);
1910 log_debug("Reading config file \"%s\"…", *f
);
1912 /* Just warn, ignore result otherwise */
1913 (void) read_config_file(*f
, true);
1919 int main(int argc
, char *argv
[]) {
1920 _cleanup_close_
int lock
= -1;
1926 r
= parse_argv(argc
, argv
);
1930 log_set_target(LOG_TARGET_AUTO
);
1931 log_parse_environment();
1934 if (arg_cat_config
) {
1941 r
= mac_selinux_init();
1943 log_error_errno(r
, "SELinux setup failed: %m");
1947 /* If command line arguments are specified along with --replace, read all
1948 * configuration files and insert the positional arguments at the specified
1949 * place. Otherwise, if command line arguments are specified, execute just
1950 * them, and finally, without --replace= or any positional arguments, just
1951 * read configuration and execute it.
1953 if (arg_replace
|| optind
>= argc
)
1954 r
= read_config_files(argv
+ optind
);
1956 r
= parse_arguments(argv
+ optind
);
1960 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1961 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1962 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1963 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1965 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1966 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1971 /* Default to default range of 1..SYSTEM_UID_MAX */
1972 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1983 lock
= take_etc_passwd_lock(arg_root
);
1985 log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
1989 r
= load_user_database();
1991 log_error_errno(r
, "Failed to load user database: %m");
1995 r
= load_group_database();
1997 log_error_errno(r
, "Failed to read group database: %m");
2001 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
2002 (void) process_item(i
);
2004 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
2005 (void) process_item(i
);
2009 log_error_errno(r
, "Failed to write files: %m");
2014 ordered_hashmap_free_with_destructor(groups
, item_free
);
2015 ordered_hashmap_free_with_destructor(users
, item_free
);
2017 while ((n
= ordered_hashmap_first_key(members
))) {
2018 strv_free(ordered_hashmap_steal_first(members
));
2021 ordered_hashmap_free(members
);
2023 ordered_hashmap_free(todo_uids
);
2024 ordered_hashmap_free(todo_gids
);
2026 free_database(database_user
, database_uid
);
2027 free_database(database_group
, database_gid
);
2033 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;