1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Lennart Poettering
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.
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.
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/>.
28 #include "alloc-util.h"
29 #include "conf-files.h"
34 #include "fileio-label.h"
35 #include "format-util.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"
43 #include "uid-range.h"
44 #include "user-util.h"
48 typedef enum ItemType
{
67 bool gid_must_exist
:1;
74 static char *arg_root
= NULL
;
76 static const char conf_file_dirs
[] = CONF_PATHS_NULSTR("sysusers.d");
78 static OrderedHashmap
*users
= NULL
, *groups
= NULL
;
79 static OrderedHashmap
*todo_uids
= NULL
, *todo_gids
= NULL
;
80 static OrderedHashmap
*members
= NULL
;
82 static Hashmap
*database_uid
= NULL
, *database_user
= NULL
;
83 static Hashmap
*database_gid
= NULL
, *database_group
= NULL
;
85 static uid_t search_uid
= UID_INVALID
;
86 static UidRange
*uid_range
= NULL
;
87 static unsigned n_uid_range
= 0;
89 static int load_user_database(void) {
90 _cleanup_fclose_
FILE *f
= NULL
;
91 const char *passwd_path
;
95 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
96 f
= fopen(passwd_path
, "re");
98 return errno
== ENOENT
? 0 : -errno
;
100 r
= hashmap_ensure_allocated(&database_user
, &string_hash_ops
);
104 r
= hashmap_ensure_allocated(&database_uid
, NULL
);
109 while ((pw
= fgetpwent(f
))) {
113 n
= strdup(pw
->pw_name
);
117 k
= hashmap_put(database_user
, n
, UID_TO_PTR(pw
->pw_uid
));
118 if (k
< 0 && k
!= -EEXIST
) {
123 q
= hashmap_put(database_uid
, UID_TO_PTR(pw
->pw_uid
), n
);
124 if (q
< 0 && q
!= -EEXIST
) {
135 if (!IN_SET(errno
, 0, ENOENT
))
141 static int load_group_database(void) {
142 _cleanup_fclose_
FILE *f
= NULL
;
143 const char *group_path
;
147 group_path
= prefix_roota(arg_root
, "/etc/group");
148 f
= fopen(group_path
, "re");
150 return errno
== ENOENT
? 0 : -errno
;
152 r
= hashmap_ensure_allocated(&database_group
, &string_hash_ops
);
156 r
= hashmap_ensure_allocated(&database_gid
, NULL
);
161 while ((gr
= fgetgrent(f
))) {
165 n
= strdup(gr
->gr_name
);
169 k
= hashmap_put(database_group
, n
, GID_TO_PTR(gr
->gr_gid
));
170 if (k
< 0 && k
!= -EEXIST
) {
175 q
= hashmap_put(database_gid
, GID_TO_PTR(gr
->gr_gid
), n
);
176 if (q
< 0 && q
!= -EEXIST
) {
187 if (!IN_SET(errno
, 0, ENOENT
))
193 static int make_backup(const char *target
, const char *x
) {
194 _cleanup_close_
int src
= -1;
195 _cleanup_fclose_
FILE *dst
= NULL
;
196 _cleanup_free_
char *temp
= NULL
;
198 struct timespec ts
[2];
202 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
204 if (errno
== ENOENT
) /* No backup necessary... */
210 if (fstat(src
, &st
) < 0)
213 r
= fopen_temporary_label(target
, x
, &dst
, &temp
);
217 r
= copy_bytes(src
, fileno(dst
), (uint64_t) -1, COPY_REFLINK
);
221 /* Don't fail on chmod() or chown(). If it stays owned by us
222 * and/or unreadable by others, then it isn't too bad... */
224 backup
= strjoina(x
, "-");
226 /* Copy over the access mask */
227 if (fchmod(fileno(dst
), st
.st_mode
& 07777) < 0)
228 log_warning_errno(errno
, "Failed to change mode on %s: %m", backup
);
230 if (fchown(fileno(dst
), st
.st_uid
, st
.st_gid
)< 0)
231 log_warning_errno(errno
, "Failed to change ownership of %s: %m", backup
);
235 if (futimens(fileno(dst
), ts
) < 0)
236 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
238 r
= fflush_sync_and_check(dst
);
242 if (rename(temp
, backup
) < 0) {
254 static int putgrent_with_members(const struct group
*gr
, FILE *group
) {
260 a
= ordered_hashmap_get(members
, gr
->gr_name
);
262 _cleanup_strv_free_
char **l
= NULL
;
266 l
= strv_copy(gr
->gr_mem
);
271 if (strv_find(l
, *i
))
274 if (strv_extend(&l
, *i
) < 0)
290 if (putgrent(&t
, group
) != 0)
291 return errno
> 0 ? -errno
: -EIO
;
298 if (putgrent(gr
, group
) != 0)
299 return errno
> 0 ? -errno
: -EIO
;
305 static int putsgent_with_members(const struct sgrp
*sg
, FILE *gshadow
) {
311 a
= ordered_hashmap_get(members
, sg
->sg_namp
);
313 _cleanup_strv_free_
char **l
= NULL
;
317 l
= strv_copy(sg
->sg_mem
);
322 if (strv_find(l
, *i
))
325 if (strv_extend(&l
, *i
) < 0)
341 if (putsgent(&t
, gshadow
) != 0)
342 return errno
> 0 ? -errno
: -EIO
;
349 if (putsgent(sg
, gshadow
) != 0)
350 return errno
> 0 ? -errno
: -EIO
;
356 static int sync_rights(FILE *from
, FILE *to
) {
359 if (fstat(fileno(from
), &st
) < 0)
362 if (fchmod(fileno(to
), st
.st_mode
& 07777) < 0)
365 if (fchown(fileno(to
), st
.st_uid
, st
.st_gid
) < 0)
371 static int rename_and_apply_smack(const char *temp_path
, const char *dest_path
) {
373 if (rename(temp_path
, dest_path
) < 0)
376 #ifdef SMACK_RUN_LABEL
377 r
= mac_smack_apply(dest_path
, SMACK_ATTR_ACCESS
, SMACK_FLOOR_LABEL
);
384 static int write_temporary_passwd(const char *passwd_path
, FILE **tmpfile
, char **tmpfile_path
) {
385 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
386 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
391 if (ordered_hashmap_size(todo_uids
) == 0)
394 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
398 original
= fopen(passwd_path
, "re");
402 r
= sync_rights(original
, passwd
);
407 while ((pw
= fgetpwent(original
))) {
409 i
= ordered_hashmap_get(users
, pw
->pw_name
);
410 if (i
&& i
->todo_user
) {
411 log_error("%s: User \"%s\" already exists.", passwd_path
, pw
->pw_name
);
415 if (ordered_hashmap_contains(todo_uids
, UID_TO_PTR(pw
->pw_uid
))) {
416 log_error("%s: Detected collision for UID " UID_FMT
".", passwd_path
, pw
->pw_uid
);
421 if (putpwent(pw
, passwd
) < 0)
422 return errno
? -errno
: -EIO
;
426 if (!IN_SET(errno
, 0, ENOENT
))
432 if (fchmod(fileno(passwd
), 0644) < 0)
436 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
441 .pw_gecos
= i
->description
,
443 /* "x" means the password is stored in the shadow file */
444 .pw_passwd
= (char*) "x",
446 /* We default to the root directory as home */
447 .pw_dir
= i
->home
? i
->home
: (char*) "/",
449 /* Initialize the shell to nologin, with one exception:
450 * for root we patch in something special */
451 .pw_shell
= i
->uid
== 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
455 if (putpwent(&n
, passwd
) != 0)
456 return errno
? -errno
: -EIO
;
459 r
= fflush_and_check(passwd
);
464 *tmpfile_path
= passwd_tmp
;
470 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
471 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
472 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
478 if (ordered_hashmap_size(todo_uids
) == 0)
481 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
485 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
487 original
= fopen(shadow_path
, "re");
491 r
= sync_rights(original
, shadow
);
496 while ((sp
= fgetspent(original
))) {
498 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
499 if (i
&& i
->todo_user
) {
500 /* we will update the existing entry */
501 sp
->sp_lstchg
= lstchg
;
503 /* only the /etc/shadow stage is left, so we can
504 * safely remove the item from the todo set */
505 i
->todo_user
= false;
506 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
510 if (putspent(sp
, shadow
) < 0)
511 return errno
? -errno
: -EIO
;
515 if (!IN_SET(errno
, 0, ENOENT
))
521 if (fchmod(fileno(shadow
), 0000) < 0)
525 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
528 .sp_pwdp
= (char*) "!!",
535 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
539 if (putspent(&n
, shadow
) != 0)
540 return errno
? -errno
: -EIO
;
543 r
= fflush_sync_and_check(shadow
);
548 *tmpfile_path
= shadow_tmp
;
554 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
555 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
556 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
557 bool group_changed
= false;
562 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
565 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
569 original
= fopen(group_path
, "re");
573 r
= sync_rights(original
, group
);
578 while ((gr
= fgetgrent(original
))) {
579 /* Safety checks against name and GID collisions. Normally,
580 * this should be unnecessary, but given that we look at the
581 * entries anyway here, let's make an extra verification
582 * step that we don't generate duplicate entries. */
584 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
585 if (i
&& i
->todo_group
) {
586 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
590 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
591 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
595 r
= putgrent_with_members(gr
, group
);
599 group_changed
= true;
603 if (!IN_SET(errno
, 0, ENOENT
))
609 if (fchmod(fileno(group
), 0644) < 0)
613 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
617 .gr_passwd
= (char*) "x",
620 r
= putgrent_with_members(&n
, group
);
624 group_changed
= true;
627 r
= fflush_sync_and_check(group
);
633 *tmpfile_path
= group_tmp
;
640 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
642 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
643 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
644 bool group_changed
= false;
649 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
652 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
656 original
= fopen(gshadow_path
, "re");
660 r
= sync_rights(original
, gshadow
);
665 while ((sg
= fgetsgent(original
))) {
667 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
668 if (i
&& i
->todo_group
) {
669 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
673 r
= putsgent_with_members(sg
, gshadow
);
677 group_changed
= true;
681 if (!IN_SET(errno
, 0, ENOENT
))
687 if (fchmod(fileno(gshadow
), 0000) < 0)
691 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
694 .sg_passwd
= (char*) "!!",
697 r
= putsgent_with_members(&n
, gshadow
);
701 group_changed
= true;
704 r
= fflush_sync_and_check(gshadow
);
710 *tmpfile_path
= gshadow_tmp
;
720 static int write_files(void) {
721 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
722 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
723 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
726 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
727 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
728 group_path
= prefix_roota(arg_root
, "/etc/group");
729 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
731 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
735 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
739 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
743 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
747 /* Make a backup of the old files */
749 r
= make_backup("/etc/group", group_path
);
754 r
= make_backup("/etc/gshadow", gshadow_path
);
760 r
= make_backup("/etc/passwd", passwd_path
);
765 r
= make_backup("/etc/shadow", shadow_path
);
770 /* And make the new files count */
772 r
= rename_and_apply_smack(group_tmp
, group_path
);
776 group_tmp
= mfree(group_tmp
);
779 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
783 gshadow_tmp
= mfree(gshadow_tmp
);
787 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
791 passwd_tmp
= mfree(passwd_tmp
);
794 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
798 shadow_tmp
= mfree(shadow_tmp
);
804 static int uid_is_ok(uid_t uid
, const char *name
) {
810 /* Let's see if we already have assigned the UID a second time */
811 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
814 /* Try to avoid using uids that are already used by a group
815 * that doesn't have the same name as our new user. */
816 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
817 if (i
&& !streq(i
->name
, name
))
820 /* Let's check the files directly */
821 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
824 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
825 if (n
&& !streq(n
, name
))
828 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
834 if (!IN_SET(errno
, 0, ENOENT
))
838 g
= getgrgid((gid_t
) uid
);
840 if (!streq(g
->gr_name
, name
))
842 } else if (!IN_SET(errno
, 0, ENOENT
))
849 static int root_stat(const char *p
, struct stat
*st
) {
852 fix
= prefix_roota(arg_root
, p
);
853 if (stat(fix
, st
) < 0)
859 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
861 bool found_uid
= false, found_gid
= false;
867 /* First, try to get the gid directly */
868 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
873 /* Then, try to get the uid directly */
874 if ((_uid
|| (_gid
&& !found_gid
))
876 && root_stat(i
->uid_path
, &st
) >= 0) {
881 /* If we need the gid, but had no success yet, also derive it from the uid path */
882 if (_gid
&& !found_gid
) {
888 /* If that didn't work yet, then let's reuse the gid as uid */
889 if (_uid
&& !found_uid
&& i
->gid_path
) {
894 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
895 uid
= (uid_t
) st
.st_gid
;
917 static int add_user(Item
*i
) {
923 /* Check the database directly */
924 z
= hashmap_get(database_user
, i
->name
);
926 log_debug("User %s already exists.", i
->name
);
927 i
->uid
= PTR_TO_UID(z
);
937 p
= getpwnam(i
->name
);
939 log_debug("User %s already exists.", i
->name
);
943 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
949 if (!IN_SET(errno
, 0, ENOENT
))
950 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
953 /* Try to use the suggested numeric uid */
955 r
= uid_is_ok(i
->uid
, i
->name
);
957 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
959 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
964 /* If that didn't work, try to read it from the specified path */
968 if (read_id_from_file(i
, &c
, NULL
) > 0) {
970 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
971 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
973 r
= uid_is_ok(c
, i
->name
);
975 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
980 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
985 /* Otherwise, try to reuse the group ID */
986 if (!i
->uid_set
&& i
->gid_set
) {
987 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
);
989 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
991 i
->uid
= (uid_t
) i
->gid
;
996 /* And if that didn't work either, let's try to find a free one */
999 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1001 log_error("No free user ID available for %s.", i
->name
);
1005 r
= uid_is_ok(search_uid
, i
->name
);
1007 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1013 i
->uid
= search_uid
;
1016 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1020 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1024 i
->todo_user
= true;
1025 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1030 static int gid_is_ok(gid_t gid
) {
1034 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1037 /* Avoid reusing gids that are already used by a different user */
1038 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1041 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1044 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1052 if (!IN_SET(errno
, 0, ENOENT
))
1056 p
= getpwuid((uid_t
) gid
);
1059 if (!IN_SET(errno
, 0, ENOENT
))
1066 static int add_group(Item
*i
) {
1072 /* Check the database directly */
1073 z
= hashmap_get(database_group
, i
->name
);
1075 log_debug("Group %s already exists.", i
->name
);
1076 i
->gid
= PTR_TO_GID(z
);
1081 /* Also check NSS */
1086 g
= getgrnam(i
->name
);
1088 log_debug("Group %s already exists.", i
->name
);
1093 if (!IN_SET(errno
, 0, ENOENT
))
1094 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1097 /* Try to use the suggested numeric gid */
1099 r
= gid_is_ok(i
->gid
);
1101 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1102 if (i
->gid_must_exist
) {
1103 /* If we require the gid to already exist we can return here:
1104 * r > 0: means the gid does not exist -> fail
1105 * r == 0: means the gid exists -> nothing more to do.
1108 log_error("Failed to create %s: please create GID %d", i
->name
, i
->gid
);
1115 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1120 /* Try to reuse the numeric uid, if there's one */
1121 if (!i
->gid_set
&& i
->uid_set
) {
1122 r
= gid_is_ok((gid_t
) i
->uid
);
1124 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1126 i
->gid
= (gid_t
) i
->uid
;
1131 /* If that didn't work, try to read it from the specified path */
1135 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1137 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1138 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1142 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1147 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1152 /* And if that didn't work either, let's try to find a free one */
1155 /* We look for new GIDs in the UID pool! */
1156 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1158 log_error("No free group ID available for %s.", i
->name
);
1162 r
= gid_is_ok(search_uid
);
1164 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1170 i
->gid
= search_uid
;
1173 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1177 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1181 i
->todo_group
= true;
1182 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1187 static int process_item(Item
*i
) {
1202 return add_group(i
);
1205 assert_not_reached("Unknown item type");
1209 static void item_free(Item
*i
) {
1217 free(i
->description
);
1222 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1224 static int add_implicit(void) {
1229 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1231 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1235 i
= ordered_hashmap_get(groups
, g
);
1237 _cleanup_(item_freep
) Item
*j
= NULL
;
1239 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1247 j
->type
= ADD_GROUP
;
1248 j
->name
= strdup(g
);
1252 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1256 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1260 STRV_FOREACH(m
, l
) {
1262 i
= ordered_hashmap_get(users
, *m
);
1264 _cleanup_(item_freep
) Item
*j
= NULL
;
1266 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1275 j
->name
= strdup(*m
);
1279 r
= ordered_hashmap_put(users
, j
->name
, j
);
1283 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1292 static bool item_equal(Item
*a
, Item
*b
) {
1296 if (a
->type
!= b
->type
)
1299 if (!streq_ptr(a
->name
, b
->name
))
1302 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1305 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1308 if (!streq_ptr(a
->description
, b
->description
))
1311 if (a
->uid_set
!= b
->uid_set
)
1314 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1317 if (a
->gid_set
!= b
->gid_set
)
1320 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1323 if (!streq_ptr(a
->home
, b
->home
))
1329 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1331 static const Specifier specifier_table
[] = {
1332 { 'm', specifier_machine_id
, NULL
},
1333 { 'b', specifier_boot_id
, NULL
},
1334 { 'H', specifier_host_name
, NULL
},
1335 { 'v', specifier_kernel_release
, NULL
},
1339 _cleanup_free_
char *action
= NULL
, *name
= NULL
, *id
= NULL
, *resolved_name
= NULL
, *resolved_id
= NULL
, *description
= NULL
, *home
= NULL
;
1340 _cleanup_(item_freep
) Item
*i
= NULL
;
1352 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
, &action
, &name
, &id
, &description
, &home
, NULL
);
1354 log_error("[%s:%u] Syntax error.", fname
, line
);
1358 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1362 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1367 if (strlen(action
) != 1) {
1368 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1372 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1373 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1378 if (isempty(name
) || streq(name
, "-"))
1382 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1384 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1388 if (!valid_user_group_name(resolved_name
)) {
1389 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1395 if (isempty(id
) || streq(id
, "-"))
1399 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1401 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1406 /* Verify description */
1407 if (isempty(description
) || streq(description
, "-"))
1408 description
= mfree(description
);
1411 if (!valid_gecos(description
)) {
1412 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, description
);
1418 if (isempty(home
) || streq(home
, "-"))
1422 if (!valid_home(home
)) {
1423 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, home
);
1428 switch (action
[0]) {
1431 if (resolved_name
) {
1432 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1437 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1442 log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname
, line
);
1447 log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname
, line
);
1451 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1453 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1462 /* Try to extend an existing member or group item */
1464 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1469 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1473 if (!valid_user_group_name(resolved_id
)) {
1474 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1479 log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname
, line
);
1484 log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname
, line
);
1488 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1492 l
= ordered_hashmap_get(members
, resolved_id
);
1494 /* A list for this group name already exists, let's append to it */
1495 r
= strv_push(&l
, resolved_name
);
1499 resolved_name
= NULL
;
1501 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1503 /* No list for this group name exists yet, create one */
1505 l
= new0(char *, 2);
1509 l
[0] = resolved_name
;
1512 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1518 resolved_id
= resolved_name
= NULL
;
1526 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1530 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1539 if (path_is_absolute(resolved_id
)) {
1540 i
->uid_path
= resolved_id
;
1543 path_kill_slashes(i
->uid_path
);
1545 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1546 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1547 r
= parse_gid(gid
, &i
->gid
);
1549 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1551 i
->gid_must_exist
= true;
1552 free_and_replace(resolved_id
, uid
);
1554 r
= parse_uid(resolved_id
, &i
->uid
);
1556 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1562 i
->description
= description
;
1573 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1578 log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname
, line
);
1583 log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname
, line
);
1587 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1596 if (path_is_absolute(resolved_id
)) {
1597 i
->gid_path
= resolved_id
;
1600 path_kill_slashes(i
->gid_path
);
1602 r
= parse_gid(resolved_id
, &i
->gid
);
1604 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1617 i
->type
= action
[0];
1618 i
->name
= resolved_name
;
1619 resolved_name
= NULL
;
1621 existing
= ordered_hashmap_get(h
, i
->name
);
1624 /* Two identical items are fine */
1625 if (!item_equal(existing
, i
))
1626 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1631 r
= ordered_hashmap_put(h
, i
->name
, i
);
1639 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1640 _cleanup_fclose_
FILE *rf
= NULL
;
1642 char line
[LINE_MAX
];
1651 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1653 if (ignore_enoent
&& r
== -ENOENT
)
1656 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1662 FOREACH_LINE(line
, f
, break) {
1669 if (IN_SET(*l
, 0, '#'))
1672 k
= parse_line(fn
, v
, l
);
1673 if (k
< 0 && r
== 0)
1678 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1686 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1690 name
= hashmap_first(by_id
);
1694 hashmap_remove(by_name
, name
);
1696 hashmap_steal_first_key(by_id
);
1700 while ((name
= hashmap_steal_first_key(by_name
)))
1703 hashmap_free(by_name
);
1704 hashmap_free(by_id
);
1707 static void help(void) {
1708 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1709 "Creates system user accounts.\n\n"
1710 " -h --help Show this help\n"
1711 " --version Show package version\n"
1712 " --root=PATH Operate on an alternate filesystem root\n"
1713 , program_invocation_short_name
);
1716 static int parse_argv(int argc
, char *argv
[]) {
1719 ARG_VERSION
= 0x100,
1723 static const struct option options
[] = {
1724 { "help", no_argument
, NULL
, 'h' },
1725 { "version", no_argument
, NULL
, ARG_VERSION
},
1726 { "root", required_argument
, NULL
, ARG_ROOT
},
1735 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1747 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1756 assert_not_reached("Unhandled option");
1762 int main(int argc
, char *argv
[]) {
1764 _cleanup_close_
int lock
= -1;
1770 r
= parse_argv(argc
, argv
);
1774 log_set_target(LOG_TARGET_AUTO
);
1775 log_parse_environment();
1780 r
= mac_selinux_init();
1782 log_error_errno(r
, "SELinux setup failed: %m");
1786 if (optind
< argc
) {
1789 for (j
= optind
; j
< argc
; j
++) {
1790 k
= read_config_file(argv
[j
], false);
1791 if (k
< 0 && r
== 0)
1795 _cleanup_strv_free_
char **files
= NULL
;
1798 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, 0, conf_file_dirs
);
1800 log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1804 STRV_FOREACH(f
, files
) {
1805 k
= read_config_file(*f
, true);
1806 if (k
< 0 && r
== 0)
1811 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1812 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1813 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1814 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1816 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1817 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1822 /* Default to default range of 1..SYSTEMD_UID_MAX */
1823 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1834 lock
= take_etc_passwd_lock(arg_root
);
1836 log_error_errno(lock
, "Failed to take lock: %m");
1840 r
= load_user_database();
1842 log_error_errno(r
, "Failed to load user database: %m");
1846 r
= load_group_database();
1848 log_error_errno(r
, "Failed to read group database: %m");
1852 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
1855 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
1860 log_error_errno(r
, "Failed to write files: %m");
1863 ordered_hashmap_free_with_destructor(groups
, item_free
);
1864 ordered_hashmap_free_with_destructor(users
, item_free
);
1866 while ((n
= ordered_hashmap_first_key(members
))) {
1867 strv_free(ordered_hashmap_steal_first(members
));
1870 ordered_hashmap_free(members
);
1872 ordered_hashmap_free(todo_uids
);
1873 ordered_hashmap_free(todo_gids
);
1875 free_database(database_user
, database_uid
);
1876 free_database(database_group
, database_gid
);
1880 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;