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/>.
24 #include "alloc-util.h"
25 #include "conf-files.h"
30 #include "fileio-label.h"
31 #include "format-util.h"
33 #include "path-util.h"
34 #include "selinux-util.h"
35 #include "smack-util.h"
36 #include "specifier.h"
37 #include "string-util.h"
39 #include "uid-range.h"
40 #include "user-util.h"
44 typedef enum ItemType
{
65 /* When set the group with the specified gid must exist
66 * and the check if a uid clashes with the gid is skipped.
76 static char *arg_root
= NULL
;
77 static const char *arg_replace
= NULL
;
78 static bool arg_inline
= false;
80 static const char conf_file_dirs
[] = CONF_PATHS_NULSTR("sysusers.d");
82 static OrderedHashmap
*users
= NULL
, *groups
= NULL
;
83 static OrderedHashmap
*todo_uids
= NULL
, *todo_gids
= NULL
;
84 static OrderedHashmap
*members
= NULL
;
86 static Hashmap
*database_uid
= NULL
, *database_user
= NULL
;
87 static Hashmap
*database_gid
= NULL
, *database_group
= NULL
;
89 static uid_t search_uid
= UID_INVALID
;
90 static UidRange
*uid_range
= NULL
;
91 static unsigned n_uid_range
= 0;
93 static int load_user_database(void) {
94 _cleanup_fclose_
FILE *f
= NULL
;
95 const char *passwd_path
;
99 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
100 f
= fopen(passwd_path
, "re");
102 return errno
== ENOENT
? 0 : -errno
;
104 r
= hashmap_ensure_allocated(&database_user
, &string_hash_ops
);
108 r
= hashmap_ensure_allocated(&database_uid
, NULL
);
112 while ((r
= fgetpwent_sane(f
, &pw
)) > 0) {
116 n
= strdup(pw
->pw_name
);
120 k
= hashmap_put(database_user
, n
, UID_TO_PTR(pw
->pw_uid
));
121 if (k
< 0 && k
!= -EEXIST
) {
126 q
= hashmap_put(database_uid
, UID_TO_PTR(pw
->pw_uid
), n
);
127 if (q
< 0 && q
!= -EEXIST
) {
139 static int load_group_database(void) {
140 _cleanup_fclose_
FILE *f
= NULL
;
141 const char *group_path
;
145 group_path
= prefix_roota(arg_root
, "/etc/group");
146 f
= fopen(group_path
, "re");
148 return errno
== ENOENT
? 0 : -errno
;
150 r
= hashmap_ensure_allocated(&database_group
, &string_hash_ops
);
154 r
= hashmap_ensure_allocated(&database_gid
, NULL
);
159 while ((gr
= fgetgrent(f
))) {
163 n
= strdup(gr
->gr_name
);
167 k
= hashmap_put(database_group
, n
, GID_TO_PTR(gr
->gr_gid
));
168 if (k
< 0 && k
!= -EEXIST
) {
173 q
= hashmap_put(database_gid
, GID_TO_PTR(gr
->gr_gid
), n
);
174 if (q
< 0 && q
!= -EEXIST
) {
185 if (!IN_SET(errno
, 0, ENOENT
))
191 static int make_backup(const char *target
, const char *x
) {
192 _cleanup_close_
int src
= -1;
193 _cleanup_fclose_
FILE *dst
= NULL
;
194 _cleanup_free_
char *temp
= NULL
;
196 struct timespec ts
[2];
200 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
202 if (errno
== ENOENT
) /* No backup necessary... */
208 if (fstat(src
, &st
) < 0)
211 r
= fopen_temporary_label(target
, x
, &dst
, &temp
);
215 r
= copy_bytes(src
, fileno(dst
), (uint64_t) -1, COPY_REFLINK
);
219 /* Don't fail on chmod() or chown(). If it stays owned by us
220 * and/or unreadable by others, then it isn't too bad... */
222 backup
= strjoina(x
, "-");
224 /* Copy over the access mask */
225 if (fchmod(fileno(dst
), st
.st_mode
& 07777) < 0)
226 log_warning_errno(errno
, "Failed to change mode on %s: %m", backup
);
228 if (fchown(fileno(dst
), st
.st_uid
, st
.st_gid
)< 0)
229 log_warning_errno(errno
, "Failed to change ownership of %s: %m", backup
);
233 if (futimens(fileno(dst
), ts
) < 0)
234 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
236 r
= fflush_sync_and_check(dst
);
240 if (rename(temp
, backup
) < 0) {
252 static int putgrent_with_members(const struct group
*gr
, FILE *group
) {
258 a
= ordered_hashmap_get(members
, gr
->gr_name
);
260 _cleanup_strv_free_
char **l
= NULL
;
264 l
= strv_copy(gr
->gr_mem
);
269 if (strv_find(l
, *i
))
272 if (strv_extend(&l
, *i
) < 0)
288 r
= putgrent_sane(&t
, group
);
289 return r
< 0 ? r
: 1;
293 return putgrent_sane(gr
, group
);
297 static int putsgent_with_members(const struct sgrp
*sg
, FILE *gshadow
) {
303 a
= ordered_hashmap_get(members
, sg
->sg_namp
);
305 _cleanup_strv_free_
char **l
= NULL
;
309 l
= strv_copy(sg
->sg_mem
);
314 if (strv_find(l
, *i
))
317 if (strv_extend(&l
, *i
) < 0)
333 r
= putsgent_sane(&t
, gshadow
);
334 return r
< 0 ? r
: 1;
338 return putsgent_sane(sg
, gshadow
);
342 static int sync_rights(FILE *from
, FILE *to
) {
345 if (fstat(fileno(from
), &st
) < 0)
348 if (fchmod(fileno(to
), st
.st_mode
& 07777) < 0)
351 if (fchown(fileno(to
), st
.st_uid
, st
.st_gid
) < 0)
357 static int rename_and_apply_smack(const char *temp_path
, const char *dest_path
) {
359 if (rename(temp_path
, dest_path
) < 0)
362 #ifdef SMACK_RUN_LABEL
363 r
= mac_smack_apply(dest_path
, SMACK_ATTR_ACCESS
, SMACK_FLOOR_LABEL
);
370 static const char* default_shell(uid_t uid
) {
371 return uid
== 0 ? "/bin/sh" : "/sbin/nologin";
374 static int write_temporary_passwd(const char *passwd_path
, FILE **tmpfile
, char **tmpfile_path
) {
375 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
376 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
377 struct passwd
*pw
= NULL
;
382 if (ordered_hashmap_size(todo_uids
) == 0)
385 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
389 original
= fopen(passwd_path
, "re");
392 r
= sync_rights(original
, passwd
);
396 while ((r
= fgetpwent_sane(original
, &pw
)) > 0) {
398 i
= ordered_hashmap_get(users
, pw
->pw_name
);
399 if (i
&& i
->todo_user
) {
400 log_error("%s: User \"%s\" already exists.", passwd_path
, pw
->pw_name
);
404 if (ordered_hashmap_contains(todo_uids
, UID_TO_PTR(pw
->pw_uid
))) {
405 log_error("%s: Detected collision for UID " UID_FMT
".", passwd_path
, pw
->pw_uid
);
409 /* Make sure we keep the NIS entries (if any) at the end. */
410 if (IN_SET(pw
->pw_name
[0], '+', '-'))
413 r
= putpwent_sane(pw
, passwd
);
423 if (fchmod(fileno(passwd
), 0644) < 0)
427 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
432 .pw_gecos
= i
->description
,
434 /* "x" means the password is stored in the shadow file */
435 .pw_passwd
= (char*) "x",
437 /* We default to the root directory as home */
438 .pw_dir
= i
->home
? i
->home
: (char*) "/",
440 /* Initialize the shell to nologin, with one exception:
441 * for root we patch in something special */
442 .pw_shell
= i
->shell
?: (char*) default_shell(i
->uid
),
445 r
= putpwent_sane(&n
, passwd
);
450 /* Append the remaining NIS entries if any */
452 r
= putpwent_sane(pw
, passwd
);
456 r
= fgetpwent_sane(original
, &pw
);
463 r
= fflush_and_check(passwd
);
468 *tmpfile_path
= passwd_tmp
;
474 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
475 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
476 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
477 struct spwd
*sp
= NULL
;
483 if (ordered_hashmap_size(todo_uids
) == 0)
486 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
490 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
492 original
= fopen(shadow_path
, "re");
495 r
= sync_rights(original
, shadow
);
499 while ((r
= fgetspent_sane(original
, &sp
)) > 0) {
501 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
502 if (i
&& i
->todo_user
) {
503 /* we will update the existing entry */
504 sp
->sp_lstchg
= lstchg
;
506 /* only the /etc/shadow stage is left, so we can
507 * safely remove the item from the todo set */
508 i
->todo_user
= false;
509 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
512 /* Make sure we keep the NIS entries (if any) at the end. */
513 if (IN_SET(sp
->sp_namp
[0], '+', '-'))
516 r
= putspent_sane(sp
, shadow
);
526 if (fchmod(fileno(shadow
), 0000) < 0)
530 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
533 .sp_pwdp
= (char*) "!!",
540 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
543 r
= putspent_sane(&n
, shadow
);
548 /* Append the remaining NIS entries if any */
550 r
= putspent_sane(sp
, shadow
);
554 r
= fgetspent_sane(original
, &sp
);
560 if (!IN_SET(errno
, 0, ENOENT
))
563 r
= fflush_sync_and_check(shadow
);
568 *tmpfile_path
= shadow_tmp
;
574 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
575 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
576 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
577 bool group_changed
= false;
578 struct group
*gr
= NULL
;
583 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
586 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
590 original
= fopen(group_path
, "re");
593 r
= sync_rights(original
, group
);
597 while ((r
= fgetgrent_sane(original
, &gr
)) > 0) {
598 /* Safety checks against name and GID collisions. Normally,
599 * this should be unnecessary, but given that we look at the
600 * entries anyway here, let's make an extra verification
601 * step that we don't generate duplicate entries. */
603 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
604 if (i
&& i
->todo_group
) {
605 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
609 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
610 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
614 /* Make sure we keep the NIS entries (if any) at the end. */
615 if (IN_SET(gr
->gr_name
[0], '+', '-'))
618 r
= putgrent_with_members(gr
, group
);
622 group_changed
= true;
630 if (fchmod(fileno(group
), 0644) < 0)
634 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
638 .gr_passwd
= (char*) "x",
641 r
= putgrent_with_members(&n
, group
);
645 group_changed
= true;
648 /* Append the remaining NIS entries if any */
650 r
= putgrent_sane(gr
, group
);
654 r
= fgetgrent_sane(original
, &gr
);
661 r
= fflush_sync_and_check(group
);
667 *tmpfile_path
= group_tmp
;
674 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
676 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
677 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
678 bool group_changed
= false;
683 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
686 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
690 original
= fopen(gshadow_path
, "re");
694 r
= sync_rights(original
, gshadow
);
698 while ((r
= fgetsgent_sane(original
, &sg
)) > 0) {
700 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
701 if (i
&& i
->todo_group
) {
702 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
706 r
= putsgent_with_members(sg
, gshadow
);
710 group_changed
= true;
718 if (fchmod(fileno(gshadow
), 0000) < 0)
722 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
725 .sg_passwd
= (char*) "!!",
728 r
= putsgent_with_members(&n
, gshadow
);
732 group_changed
= true;
735 r
= fflush_sync_and_check(gshadow
);
741 *tmpfile_path
= gshadow_tmp
;
751 static int write_files(void) {
752 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
753 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
754 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
757 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
758 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
759 group_path
= prefix_roota(arg_root
, "/etc/group");
760 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
762 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
766 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
770 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
774 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
778 /* Make a backup of the old files */
780 r
= make_backup("/etc/group", group_path
);
785 r
= make_backup("/etc/gshadow", gshadow_path
);
791 r
= make_backup("/etc/passwd", passwd_path
);
796 r
= make_backup("/etc/shadow", shadow_path
);
801 /* And make the new files count */
803 r
= rename_and_apply_smack(group_tmp
, group_path
);
807 group_tmp
= mfree(group_tmp
);
810 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
814 gshadow_tmp
= mfree(gshadow_tmp
);
818 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
822 passwd_tmp
= mfree(passwd_tmp
);
825 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
829 shadow_tmp
= mfree(shadow_tmp
);
835 static int uid_is_ok(uid_t uid
, const char *name
, bool check_with_gid
) {
841 /* Let's see if we already have assigned the UID a second time */
842 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
845 /* Try to avoid using uids that are already used by a group
846 * that doesn't have the same name as our new user. */
847 if (check_with_gid
) {
848 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
849 if (i
&& !streq(i
->name
, name
))
853 /* Let's check the files directly */
854 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
857 if (check_with_gid
) {
858 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
859 if (n
&& !streq(n
, name
))
863 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
869 if (!IN_SET(errno
, 0, ENOENT
))
872 if (check_with_gid
) {
874 g
= getgrgid((gid_t
) uid
);
876 if (!streq(g
->gr_name
, name
))
878 } else if (!IN_SET(errno
, 0, ENOENT
))
886 static int root_stat(const char *p
, struct stat
*st
) {
889 fix
= prefix_roota(arg_root
, p
);
890 if (stat(fix
, st
) < 0)
896 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
898 bool found_uid
= false, found_gid
= false;
904 /* First, try to get the gid directly */
905 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
910 /* Then, try to get the uid directly */
911 if ((_uid
|| (_gid
&& !found_gid
))
913 && root_stat(i
->uid_path
, &st
) >= 0) {
918 /* If we need the gid, but had no success yet, also derive it from the uid path */
919 if (_gid
&& !found_gid
) {
925 /* If that didn't work yet, then let's reuse the gid as uid */
926 if (_uid
&& !found_uid
&& i
->gid_path
) {
931 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
932 uid
= (uid_t
) st
.st_gid
;
954 static int add_user(Item
*i
) {
960 /* Check the database directly */
961 z
= hashmap_get(database_user
, i
->name
);
963 log_debug("User %s already exists.", i
->name
);
964 i
->uid
= PTR_TO_UID(z
);
974 p
= getpwnam(i
->name
);
976 log_debug("User %s already exists.", i
->name
);
980 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
986 if (!IN_SET(errno
, 0, ENOENT
))
987 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
990 /* Try to use the suggested numeric uid */
992 r
= uid_is_ok(i
->uid
, i
->name
, !i
->id_set_strict
);
994 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
996 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
1001 /* If that didn't work, try to read it from the specified path */
1005 if (read_id_from_file(i
, &c
, NULL
) > 0) {
1007 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1008 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
1010 r
= uid_is_ok(c
, i
->name
, true);
1012 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1017 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
1022 /* Otherwise, try to reuse the group ID */
1023 if (!i
->uid_set
&& i
->gid_set
) {
1024 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
, true);
1026 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1028 i
->uid
= (uid_t
) i
->gid
;
1033 /* And if that didn't work either, let's try to find a free one */
1036 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1038 log_error("No free user ID available for %s.", i
->name
);
1042 r
= uid_is_ok(search_uid
, i
->name
, true);
1044 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1050 i
->uid
= search_uid
;
1053 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1057 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1061 i
->todo_user
= true;
1062 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1067 static int gid_is_ok(gid_t gid
) {
1071 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1074 /* Avoid reusing gids that are already used by a different user */
1075 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1078 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1081 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1089 if (!IN_SET(errno
, 0, ENOENT
))
1093 p
= getpwuid((uid_t
) gid
);
1096 if (!IN_SET(errno
, 0, ENOENT
))
1103 static int add_group(Item
*i
) {
1109 /* Check the database directly */
1110 z
= hashmap_get(database_group
, i
->name
);
1112 log_debug("Group %s already exists.", i
->name
);
1113 i
->gid
= PTR_TO_GID(z
);
1118 /* Also check NSS */
1123 g
= getgrnam(i
->name
);
1125 log_debug("Group %s already exists.", i
->name
);
1130 if (!IN_SET(errno
, 0, ENOENT
))
1131 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1134 /* Try to use the suggested numeric gid */
1136 r
= gid_is_ok(i
->gid
);
1138 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1139 if (i
->id_set_strict
) {
1140 /* If we require the gid to already exist we can return here:
1141 * r > 0: means the gid does not exist -> fail
1142 * r == 0: means the gid exists -> nothing more to do.
1145 log_error("Failed to create %s: please create GID %d", i
->name
, i
->gid
);
1152 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1157 /* Try to reuse the numeric uid, if there's one */
1158 if (!i
->gid_set
&& i
->uid_set
) {
1159 r
= gid_is_ok((gid_t
) i
->uid
);
1161 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1163 i
->gid
= (gid_t
) i
->uid
;
1168 /* If that didn't work, try to read it from the specified path */
1172 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1174 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1175 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1179 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1184 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1189 /* And if that didn't work either, let's try to find a free one */
1192 /* We look for new GIDs in the UID pool! */
1193 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1195 log_error("No free group ID available for %s.", i
->name
);
1199 r
= gid_is_ok(search_uid
);
1201 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1207 i
->gid
= search_uid
;
1210 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1214 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1218 i
->todo_group
= true;
1219 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1224 static int process_item(Item
*i
) {
1234 j
= ordered_hashmap_get(groups
, i
->name
);
1235 if (j
&& j
->todo_group
) {
1236 /* When the group with the same name is already in queue,
1237 * use the information about the group and do not create
1238 * duplicated group entry. */
1239 i
->gid_set
= j
->gid_set
;
1241 i
->id_set_strict
= true;
1252 return add_group(i
);
1255 assert_not_reached("Unknown item type");
1259 static void item_free(Item
*i
) {
1267 free(i
->description
);
1273 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1275 static int add_implicit(void) {
1280 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1281 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1285 if (!ordered_hashmap_get(users
, *m
)) {
1286 _cleanup_(item_freep
) Item
*j
= NULL
;
1288 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1297 j
->name
= strdup(*m
);
1301 r
= ordered_hashmap_put(users
, j
->name
, j
);
1305 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1309 if (!(ordered_hashmap_get(users
, g
) ||
1310 ordered_hashmap_get(groups
, g
))) {
1311 _cleanup_(item_freep
) Item
*j
= NULL
;
1313 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1321 j
->type
= ADD_GROUP
;
1322 j
->name
= strdup(g
);
1326 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1330 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1338 static bool item_equal(Item
*a
, Item
*b
) {
1342 if (a
->type
!= b
->type
)
1345 if (!streq_ptr(a
->name
, b
->name
))
1348 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1351 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1354 if (!streq_ptr(a
->description
, b
->description
))
1357 if (a
->uid_set
!= b
->uid_set
)
1360 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1363 if (a
->gid_set
!= b
->gid_set
)
1366 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1369 if (!streq_ptr(a
->home
, b
->home
))
1372 if (!streq_ptr(a
->shell
, b
->shell
))
1378 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1380 static const Specifier specifier_table
[] = {
1381 { 'm', specifier_machine_id
, NULL
},
1382 { 'b', specifier_boot_id
, NULL
},
1383 { 'H', specifier_host_name
, NULL
},
1384 { 'v', specifier_kernel_release
, NULL
},
1388 _cleanup_free_
char *action
= NULL
,
1389 *name
= NULL
, *resolved_name
= NULL
,
1390 *id
= NULL
, *resolved_id
= NULL
,
1391 *description
= NULL
,
1393 *shell
, *resolved_shell
= NULL
;
1394 _cleanup_(item_freep
) Item
*i
= NULL
;
1406 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
,
1407 &action
, &name
, &id
, &description
, &home
, &shell
, NULL
);
1409 log_error("[%s:%u] Syntax error.", fname
, line
);
1413 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1417 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1422 if (strlen(action
) != 1) {
1423 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1427 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1428 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1433 if (isempty(name
) || streq(name
, "-"))
1437 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1439 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1443 if (!valid_user_group_name(resolved_name
)) {
1444 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1450 if (isempty(id
) || streq(id
, "-"))
1454 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1456 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1461 /* Verify description */
1462 if (isempty(description
) || streq(description
, "-"))
1463 description
= mfree(description
);
1466 if (!valid_gecos(description
)) {
1467 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, description
);
1473 if (isempty(home
) || streq(home
, "-"))
1477 if (!valid_home(home
)) {
1478 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, home
);
1484 if (isempty(shell
) || streq(shell
, "-"))
1485 shell
= mfree(shell
);
1488 r
= specifier_printf(shell
, specifier_table
, NULL
, &resolved_shell
);
1490 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, shell
);
1494 if (!valid_shell(resolved_shell
)) {
1495 log_error("[%s:%u] '%s' is not a valid login shell field.", fname
, line
, resolved_shell
);
1501 switch (action
[0]) {
1504 if (resolved_name
) {
1505 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1510 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1514 if (description
|| home
|| shell
) {
1515 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1516 fname
, line
, action
[0],
1517 description
? "GECOS" : home
? "home directory" : "login shell");
1521 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1523 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1532 /* Try to extend an existing member or group item */
1534 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1539 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1543 if (!valid_user_group_name(resolved_id
)) {
1544 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1548 if (description
|| home
|| shell
) {
1549 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1550 fname
, line
, action
[0],
1551 description
? "GECOS" : home
? "home directory" : "login shell");
1555 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1559 l
= ordered_hashmap_get(members
, resolved_id
);
1561 /* A list for this group name already exists, let's append to it */
1562 r
= strv_push(&l
, resolved_name
);
1566 resolved_name
= NULL
;
1568 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1570 /* No list for this group name exists yet, create one */
1572 l
= new0(char *, 2);
1576 l
[0] = resolved_name
;
1579 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1585 resolved_id
= resolved_name
= NULL
;
1593 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1597 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1606 if (path_is_absolute(resolved_id
)) {
1607 i
->uid_path
= resolved_id
;
1610 path_kill_slashes(i
->uid_path
);
1612 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1613 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1614 r
= parse_gid(gid
, &i
->gid
);
1616 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1618 i
->id_set_strict
= true;
1619 free_and_replace(resolved_id
, uid
);
1621 if (!streq(resolved_id
, "-")) {
1622 r
= parse_uid(resolved_id
, &i
->uid
);
1624 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1630 i
->description
= description
;
1636 i
->shell
= resolved_shell
;
1637 resolved_shell
= NULL
;
1644 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1648 if (description
|| home
|| shell
) {
1649 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1650 fname
, line
, action
[0],
1651 description
? "GECOS" : home
? "home directory" : "login shell");
1655 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1664 if (path_is_absolute(resolved_id
)) {
1665 i
->gid_path
= resolved_id
;
1668 path_kill_slashes(i
->gid_path
);
1670 r
= parse_gid(resolved_id
, &i
->gid
);
1672 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1685 i
->type
= action
[0];
1686 i
->name
= resolved_name
;
1687 resolved_name
= NULL
;
1689 existing
= ordered_hashmap_get(h
, i
->name
);
1692 /* Two identical items are fine */
1693 if (!item_equal(existing
, i
))
1694 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1699 r
= ordered_hashmap_put(h
, i
->name
, i
);
1707 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1708 _cleanup_fclose_
FILE *rf
= NULL
;
1710 char line
[LINE_MAX
];
1719 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1721 if (ignore_enoent
&& r
== -ENOENT
)
1724 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1730 FOREACH_LINE(line
, f
, break) {
1737 if (IN_SET(*l
, 0, '#'))
1740 k
= parse_line(fn
, v
, l
);
1741 if (k
< 0 && r
== 0)
1746 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1754 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1758 name
= hashmap_first(by_id
);
1762 hashmap_remove(by_name
, name
);
1764 hashmap_steal_first_key(by_id
);
1768 while ((name
= hashmap_steal_first_key(by_name
)))
1771 hashmap_free(by_name
);
1772 hashmap_free(by_id
);
1775 static void help(void) {
1776 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1777 "Creates system user accounts.\n\n"
1778 " -h --help Show this help\n"
1779 " --version Show package version\n"
1780 " --root=PATH Operate on an alternate filesystem root\n"
1781 " --replace=PATH Treat arguments as replacement for PATH\n"
1782 " --inline Treat arguments as configuration lines\n"
1783 , program_invocation_short_name
);
1786 static int parse_argv(int argc
, char *argv
[]) {
1789 ARG_VERSION
= 0x100,
1795 static const struct option options
[] = {
1796 { "help", no_argument
, NULL
, 'h' },
1797 { "version", no_argument
, NULL
, ARG_VERSION
},
1798 { "root", required_argument
, NULL
, ARG_ROOT
},
1799 { "replace", required_argument
, NULL
, ARG_REPLACE
},
1800 { "inline", no_argument
, NULL
, ARG_INLINE
},
1809 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1821 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1827 if (!path_is_absolute(optarg
) ||
1828 !endswith(optarg
, ".conf")) {
1829 log_error("The argument to --replace= must an absolute path to a config file");
1833 arg_replace
= optarg
;
1844 assert_not_reached("Unhandled option");
1847 if (arg_replace
&& optind
>= argc
) {
1848 log_error("When --replace= is given, some configuration items must be specified");
1855 static int parse_arguments(char **args
) {
1860 STRV_FOREACH(arg
, args
) {
1862 /* Use (argument):n, where n==1 for the first positional arg */
1863 r
= parse_line("(argument)", pos
, *arg
);
1865 r
= read_config_file(*arg
, false);
1875 static int read_config_files(const char* dirs
, char **args
) {
1876 _cleanup_strv_free_
char **files
= NULL
;
1877 _cleanup_free_
char *p
= NULL
;
1881 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, 0, dirs
);
1883 return log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1886 r
= conf_files_insert_nulstr(&files
, arg_root
, dirs
, arg_replace
);
1888 return log_error_errno(r
, "Failed to extend sysusers.d file list: %m");
1890 p
= path_join(arg_root
, arg_replace
, NULL
);
1895 STRV_FOREACH(f
, files
)
1896 if (p
&& path_equal(*f
, p
)) {
1897 log_debug("Parsing arguments at position \"%s\"…", *f
);
1899 r
= parse_arguments(args
);
1903 log_debug("Reading config file \"%s\"…", *f
);
1905 /* Just warn, ignore result otherwise */
1906 (void) read_config_file(*f
, true);
1912 int main(int argc
, char *argv
[]) {
1914 _cleanup_close_
int lock
= -1;
1920 r
= parse_argv(argc
, argv
);
1924 log_set_target(LOG_TARGET_AUTO
);
1925 log_parse_environment();
1930 r
= mac_selinux_init();
1932 log_error_errno(r
, "SELinux setup failed: %m");
1936 /* If command line arguments are specified along with --replace, read all
1937 * configuration files and insert the positional arguments at the specified
1938 * place. Otherwise, if command line arguments are specified, execute just
1939 * them, and finally, without --replace= or any positional arguments, just
1940 * read configuration and execute it.
1942 if (arg_replace
|| optind
>= argc
)
1943 r
= read_config_files(conf_file_dirs
, argv
+ optind
);
1945 r
= parse_arguments(argv
+ optind
);
1949 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1950 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1951 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1952 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1954 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1955 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1960 /* Default to default range of 1..SYSTEM_UID_MAX */
1961 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1972 lock
= take_etc_passwd_lock(arg_root
);
1974 log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
1978 r
= load_user_database();
1980 log_error_errno(r
, "Failed to load user database: %m");
1984 r
= load_group_database();
1986 log_error_errno(r
, "Failed to read group database: %m");
1990 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
1993 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
1998 log_error_errno(r
, "Failed to write files: %m");
2001 ordered_hashmap_free_with_destructor(groups
, item_free
);
2002 ordered_hashmap_free_with_destructor(users
, item_free
);
2004 while ((n
= ordered_hashmap_first_key(members
))) {
2005 strv_free(ordered_hashmap_steal_first(members
));
2008 ordered_hashmap_free(members
);
2010 ordered_hashmap_free(todo_uids
);
2011 ordered_hashmap_free(todo_gids
);
2013 free_database(database_user
, database_uid
);
2014 free_database(database_group
, database_gid
);
2018 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;