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
{
69 /* When set the group with the specified gid must exist
70 * and the check if a uid clashes with the gid is skipped.
80 static char *arg_root
= NULL
;
81 static const char *arg_replace
= NULL
;
82 static bool arg_inline
= false;
84 static const char conf_file_dirs
[] = CONF_PATHS_NULSTR("sysusers.d");
86 static OrderedHashmap
*users
= NULL
, *groups
= NULL
;
87 static OrderedHashmap
*todo_uids
= NULL
, *todo_gids
= NULL
;
88 static OrderedHashmap
*members
= NULL
;
90 static Hashmap
*database_uid
= NULL
, *database_user
= NULL
;
91 static Hashmap
*database_gid
= NULL
, *database_group
= NULL
;
93 static uid_t search_uid
= UID_INVALID
;
94 static UidRange
*uid_range
= NULL
;
95 static unsigned n_uid_range
= 0;
97 static int load_user_database(void) {
98 _cleanup_fclose_
FILE *f
= NULL
;
99 const char *passwd_path
;
103 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
104 f
= fopen(passwd_path
, "re");
106 return errno
== ENOENT
? 0 : -errno
;
108 r
= hashmap_ensure_allocated(&database_user
, &string_hash_ops
);
112 r
= hashmap_ensure_allocated(&database_uid
, NULL
);
117 while ((pw
= fgetpwent(f
))) {
121 n
= strdup(pw
->pw_name
);
125 k
= hashmap_put(database_user
, n
, UID_TO_PTR(pw
->pw_uid
));
126 if (k
< 0 && k
!= -EEXIST
) {
131 q
= hashmap_put(database_uid
, UID_TO_PTR(pw
->pw_uid
), n
);
132 if (q
< 0 && q
!= -EEXIST
) {
143 if (!IN_SET(errno
, 0, ENOENT
))
149 static int load_group_database(void) {
150 _cleanup_fclose_
FILE *f
= NULL
;
151 const char *group_path
;
155 group_path
= prefix_roota(arg_root
, "/etc/group");
156 f
= fopen(group_path
, "re");
158 return errno
== ENOENT
? 0 : -errno
;
160 r
= hashmap_ensure_allocated(&database_group
, &string_hash_ops
);
164 r
= hashmap_ensure_allocated(&database_gid
, NULL
);
169 while ((gr
= fgetgrent(f
))) {
173 n
= strdup(gr
->gr_name
);
177 k
= hashmap_put(database_group
, n
, GID_TO_PTR(gr
->gr_gid
));
178 if (k
< 0 && k
!= -EEXIST
) {
183 q
= hashmap_put(database_gid
, GID_TO_PTR(gr
->gr_gid
), n
);
184 if (q
< 0 && q
!= -EEXIST
) {
195 if (!IN_SET(errno
, 0, ENOENT
))
201 static int make_backup(const char *target
, const char *x
) {
202 _cleanup_close_
int src
= -1;
203 _cleanup_fclose_
FILE *dst
= NULL
;
204 _cleanup_free_
char *temp
= NULL
;
206 struct timespec ts
[2];
210 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
212 if (errno
== ENOENT
) /* No backup necessary... */
218 if (fstat(src
, &st
) < 0)
221 r
= fopen_temporary_label(target
, x
, &dst
, &temp
);
225 r
= copy_bytes(src
, fileno(dst
), (uint64_t) -1, COPY_REFLINK
);
229 /* Don't fail on chmod() or chown(). If it stays owned by us
230 * and/or unreadable by others, then it isn't too bad... */
232 backup
= strjoina(x
, "-");
234 /* Copy over the access mask */
235 if (fchmod(fileno(dst
), st
.st_mode
& 07777) < 0)
236 log_warning_errno(errno
, "Failed to change mode on %s: %m", backup
);
238 if (fchown(fileno(dst
), st
.st_uid
, st
.st_gid
)< 0)
239 log_warning_errno(errno
, "Failed to change ownership of %s: %m", backup
);
243 if (futimens(fileno(dst
), ts
) < 0)
244 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
246 r
= fflush_sync_and_check(dst
);
250 if (rename(temp
, backup
) < 0) {
262 static int putgrent_with_members(const struct group
*gr
, FILE *group
) {
268 a
= ordered_hashmap_get(members
, gr
->gr_name
);
270 _cleanup_strv_free_
char **l
= NULL
;
274 l
= strv_copy(gr
->gr_mem
);
279 if (strv_find(l
, *i
))
282 if (strv_extend(&l
, *i
) < 0)
298 if (putgrent(&t
, group
) != 0)
299 return errno
> 0 ? -errno
: -EIO
;
306 if (putgrent(gr
, group
) != 0)
307 return errno
> 0 ? -errno
: -EIO
;
313 static int putsgent_with_members(const struct sgrp
*sg
, FILE *gshadow
) {
319 a
= ordered_hashmap_get(members
, sg
->sg_namp
);
321 _cleanup_strv_free_
char **l
= NULL
;
325 l
= strv_copy(sg
->sg_mem
);
330 if (strv_find(l
, *i
))
333 if (strv_extend(&l
, *i
) < 0)
349 if (putsgent(&t
, gshadow
) != 0)
350 return errno
> 0 ? -errno
: -EIO
;
357 if (putsgent(sg
, gshadow
) != 0)
358 return errno
> 0 ? -errno
: -EIO
;
364 static int sync_rights(FILE *from
, FILE *to
) {
367 if (fstat(fileno(from
), &st
) < 0)
370 if (fchmod(fileno(to
), st
.st_mode
& 07777) < 0)
373 if (fchown(fileno(to
), st
.st_uid
, st
.st_gid
) < 0)
379 static int rename_and_apply_smack(const char *temp_path
, const char *dest_path
) {
381 if (rename(temp_path
, dest_path
) < 0)
384 #ifdef SMACK_RUN_LABEL
385 r
= mac_smack_apply(dest_path
, SMACK_ATTR_ACCESS
, SMACK_FLOOR_LABEL
);
392 static const char* default_shell(uid_t uid
) {
393 return uid
== 0 ? "/bin/sh" : "/sbin/nologin";
396 static int write_temporary_passwd(const char *passwd_path
, FILE **tmpfile
, char **tmpfile_path
) {
397 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
398 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
399 struct passwd
*pw
= NULL
;
404 if (ordered_hashmap_size(todo_uids
) == 0)
407 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
411 original
= fopen(passwd_path
, "re");
414 r
= sync_rights(original
, passwd
);
419 while ((pw
= fgetpwent(original
))) {
421 i
= ordered_hashmap_get(users
, pw
->pw_name
);
422 if (i
&& i
->todo_user
) {
423 log_error("%s: User \"%s\" already exists.", passwd_path
, pw
->pw_name
);
427 if (ordered_hashmap_contains(todo_uids
, UID_TO_PTR(pw
->pw_uid
))) {
428 log_error("%s: Detected collision for UID " UID_FMT
".", passwd_path
, pw
->pw_uid
);
434 /* Make sure we keep the NIS entries (if any) at the end. */
435 if (IN_SET(pw
->pw_name
[0], '+', '-'))
438 if (putpwent(pw
, passwd
) < 0)
439 return errno
? -errno
: -EIO
;
443 if (!IN_SET(errno
, 0, ENOENT
))
449 if (fchmod(fileno(passwd
), 0644) < 0)
453 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
458 .pw_gecos
= i
->description
,
460 /* "x" means the password is stored in the shadow file */
461 .pw_passwd
= (char*) "x",
463 /* We default to the root directory as home */
464 .pw_dir
= i
->home
? i
->home
: (char*) "/",
466 /* Initialize the shell to nologin, with one exception:
467 * for root we patch in something special */
468 .pw_shell
= i
->shell
?: (char*) default_shell(i
->uid
),
472 if (putpwent(&n
, passwd
) != 0)
473 return errno
? -errno
: -EIO
;
477 /* Append the remaining NIS entries if any */
480 if (putpwent(pw
, passwd
) < 0)
481 return errno
? -errno
: -EIO
;
484 pw
= fgetpwent(original
);
486 if (!IN_SET(errno
, 0, ENOENT
))
489 r
= fflush_and_check(passwd
);
494 *tmpfile_path
= passwd_tmp
;
500 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
501 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
502 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
503 struct spwd
*sp
= NULL
;
509 if (ordered_hashmap_size(todo_uids
) == 0)
512 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
516 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
518 original
= fopen(shadow_path
, "re");
521 r
= sync_rights(original
, shadow
);
526 while ((sp
= fgetspent(original
))) {
528 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
529 if (i
&& i
->todo_user
) {
530 /* we will update the existing entry */
531 sp
->sp_lstchg
= lstchg
;
533 /* only the /etc/shadow stage is left, so we can
534 * safely remove the item from the todo set */
535 i
->todo_user
= false;
536 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
541 /* Make sure we keep the NIS entries (if any) at the end. */
542 if (IN_SET(sp
->sp_namp
[0], '+', '-'))
545 if (putspent(sp
, shadow
) < 0)
546 return errno
? -errno
: -EIO
;
550 if (!IN_SET(errno
, 0, ENOENT
))
556 if (fchmod(fileno(shadow
), 0000) < 0)
560 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
563 .sp_pwdp
= (char*) "!!",
570 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
574 if (putspent(&n
, shadow
) != 0)
575 return errno
? -errno
: -EIO
;
579 /* Append the remaining NIS entries if any */
582 if (putspent(sp
, shadow
) < 0)
583 return errno
? -errno
: -EIO
;
586 sp
= fgetspent(original
);
588 if (!IN_SET(errno
, 0, ENOENT
))
591 r
= fflush_sync_and_check(shadow
);
596 *tmpfile_path
= shadow_tmp
;
602 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
603 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
604 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
605 bool group_changed
= false;
606 struct group
*gr
= NULL
;
611 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
614 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
618 original
= fopen(group_path
, "re");
621 r
= sync_rights(original
, group
);
626 while ((gr
= fgetgrent(original
))) {
627 /* Safety checks against name and GID collisions. Normally,
628 * this should be unnecessary, but given that we look at the
629 * entries anyway here, let's make an extra verification
630 * step that we don't generate duplicate entries. */
632 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
633 if (i
&& i
->todo_group
) {
634 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
638 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
639 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
645 /* Make sure we keep the NIS entries (if any) at the end. */
646 if (IN_SET(gr
->gr_name
[0], '+', '-'))
649 r
= putgrent_with_members(gr
, group
);
653 group_changed
= true;
657 if (!IN_SET(errno
, 0, ENOENT
))
663 if (fchmod(fileno(group
), 0644) < 0)
667 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
671 .gr_passwd
= (char*) "x",
674 r
= putgrent_with_members(&n
, group
);
678 group_changed
= true;
682 /* Append the remaining NIS entries if any */
685 if (putgrent(gr
, group
) != 0)
686 return errno
> 0 ? -errno
: -EIO
;
689 gr
= fgetgrent(original
);
691 if (!IN_SET(errno
, 0, ENOENT
))
694 r
= fflush_sync_and_check(group
);
700 *tmpfile_path
= group_tmp
;
707 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
709 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
710 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
711 bool group_changed
= false;
716 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
719 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
723 original
= fopen(gshadow_path
, "re");
727 r
= sync_rights(original
, gshadow
);
732 while ((sg
= fgetsgent(original
))) {
734 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
735 if (i
&& i
->todo_group
) {
736 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
740 r
= putsgent_with_members(sg
, gshadow
);
744 group_changed
= true;
748 if (!IN_SET(errno
, 0, ENOENT
))
754 if (fchmod(fileno(gshadow
), 0000) < 0)
758 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
761 .sg_passwd
= (char*) "!!",
764 r
= putsgent_with_members(&n
, gshadow
);
768 group_changed
= true;
771 r
= fflush_sync_and_check(gshadow
);
777 *tmpfile_path
= gshadow_tmp
;
787 static int write_files(void) {
788 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
789 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
790 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
793 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
794 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
795 group_path
= prefix_roota(arg_root
, "/etc/group");
796 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
798 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
802 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
806 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
810 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
814 /* Make a backup of the old files */
816 r
= make_backup("/etc/group", group_path
);
821 r
= make_backup("/etc/gshadow", gshadow_path
);
827 r
= make_backup("/etc/passwd", passwd_path
);
832 r
= make_backup("/etc/shadow", shadow_path
);
837 /* And make the new files count */
839 r
= rename_and_apply_smack(group_tmp
, group_path
);
843 group_tmp
= mfree(group_tmp
);
846 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
850 gshadow_tmp
= mfree(gshadow_tmp
);
854 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
858 passwd_tmp
= mfree(passwd_tmp
);
861 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
865 shadow_tmp
= mfree(shadow_tmp
);
871 static int uid_is_ok(uid_t uid
, const char *name
, bool check_with_gid
) {
877 /* Let's see if we already have assigned the UID a second time */
878 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
881 /* Try to avoid using uids that are already used by a group
882 * that doesn't have the same name as our new user. */
883 if (check_with_gid
) {
884 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
885 if (i
&& !streq(i
->name
, name
))
889 /* Let's check the files directly */
890 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
893 if (check_with_gid
) {
894 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
895 if (n
&& !streq(n
, name
))
899 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
905 if (!IN_SET(errno
, 0, ENOENT
))
908 if (check_with_gid
) {
910 g
= getgrgid((gid_t
) uid
);
912 if (!streq(g
->gr_name
, name
))
914 } else if (!IN_SET(errno
, 0, ENOENT
))
922 static int root_stat(const char *p
, struct stat
*st
) {
925 fix
= prefix_roota(arg_root
, p
);
926 if (stat(fix
, st
) < 0)
932 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
934 bool found_uid
= false, found_gid
= false;
940 /* First, try to get the gid directly */
941 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
946 /* Then, try to get the uid directly */
947 if ((_uid
|| (_gid
&& !found_gid
))
949 && root_stat(i
->uid_path
, &st
) >= 0) {
954 /* If we need the gid, but had no success yet, also derive it from the uid path */
955 if (_gid
&& !found_gid
) {
961 /* If that didn't work yet, then let's reuse the gid as uid */
962 if (_uid
&& !found_uid
&& i
->gid_path
) {
967 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
968 uid
= (uid_t
) st
.st_gid
;
990 static int add_user(Item
*i
) {
996 /* Check the database directly */
997 z
= hashmap_get(database_user
, i
->name
);
999 log_debug("User %s already exists.", i
->name
);
1000 i
->uid
= PTR_TO_UID(z
);
1008 /* Also check NSS */
1010 p
= getpwnam(i
->name
);
1012 log_debug("User %s already exists.", i
->name
);
1016 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
1022 if (!IN_SET(errno
, 0, ENOENT
))
1023 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
1026 /* Try to use the suggested numeric uid */
1028 r
= uid_is_ok(i
->uid
, i
->name
, !i
->id_set_strict
);
1030 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1032 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
1037 /* If that didn't work, try to read it from the specified path */
1041 if (read_id_from_file(i
, &c
, NULL
) > 0) {
1043 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1044 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
1046 r
= uid_is_ok(c
, i
->name
, true);
1048 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1053 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
1058 /* Otherwise, try to reuse the group ID */
1059 if (!i
->uid_set
&& i
->gid_set
) {
1060 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
, true);
1062 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1064 i
->uid
= (uid_t
) i
->gid
;
1069 /* And if that didn't work either, let's try to find a free one */
1072 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1074 log_error("No free user ID available for %s.", i
->name
);
1078 r
= uid_is_ok(search_uid
, i
->name
, true);
1080 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1086 i
->uid
= search_uid
;
1089 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1093 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1097 i
->todo_user
= true;
1098 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1103 static int gid_is_ok(gid_t gid
) {
1107 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1110 /* Avoid reusing gids that are already used by a different user */
1111 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1114 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1117 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1125 if (!IN_SET(errno
, 0, ENOENT
))
1129 p
= getpwuid((uid_t
) gid
);
1132 if (!IN_SET(errno
, 0, ENOENT
))
1139 static int add_group(Item
*i
) {
1145 /* Check the database directly */
1146 z
= hashmap_get(database_group
, i
->name
);
1148 log_debug("Group %s already exists.", i
->name
);
1149 i
->gid
= PTR_TO_GID(z
);
1154 /* Also check NSS */
1159 g
= getgrnam(i
->name
);
1161 log_debug("Group %s already exists.", i
->name
);
1166 if (!IN_SET(errno
, 0, ENOENT
))
1167 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1170 /* Try to use the suggested numeric gid */
1172 r
= gid_is_ok(i
->gid
);
1174 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1175 if (i
->id_set_strict
) {
1176 /* If we require the gid to already exist we can return here:
1177 * r > 0: means the gid does not exist -> fail
1178 * r == 0: means the gid exists -> nothing more to do.
1181 log_error("Failed to create %s: please create GID %d", i
->name
, i
->gid
);
1188 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1193 /* Try to reuse the numeric uid, if there's one */
1194 if (!i
->gid_set
&& i
->uid_set
) {
1195 r
= gid_is_ok((gid_t
) i
->uid
);
1197 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1199 i
->gid
= (gid_t
) i
->uid
;
1204 /* If that didn't work, try to read it from the specified path */
1208 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1210 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1211 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1215 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1220 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1225 /* And if that didn't work either, let's try to find a free one */
1228 /* We look for new GIDs in the UID pool! */
1229 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1231 log_error("No free group ID available for %s.", i
->name
);
1235 r
= gid_is_ok(search_uid
);
1237 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1243 i
->gid
= search_uid
;
1246 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1250 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1254 i
->todo_group
= true;
1255 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1260 static int process_item(Item
*i
) {
1270 j
= ordered_hashmap_get(groups
, i
->name
);
1271 if (j
&& j
->todo_group
) {
1272 /* When the group with the same name is already in queue,
1273 * use the information about the group and do not create
1274 * duplicated group entry. */
1275 i
->gid_set
= j
->gid_set
;
1277 i
->id_set_strict
= true;
1288 return add_group(i
);
1291 assert_not_reached("Unknown item type");
1295 static void item_free(Item
*i
) {
1303 free(i
->description
);
1309 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1311 static int add_implicit(void) {
1316 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1317 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1321 if (!ordered_hashmap_get(users
, *m
)) {
1322 _cleanup_(item_freep
) Item
*j
= NULL
;
1324 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1333 j
->name
= strdup(*m
);
1337 r
= ordered_hashmap_put(users
, j
->name
, j
);
1341 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1345 if (!(ordered_hashmap_get(users
, g
) ||
1346 ordered_hashmap_get(groups
, g
))) {
1347 _cleanup_(item_freep
) Item
*j
= NULL
;
1349 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1357 j
->type
= ADD_GROUP
;
1358 j
->name
= strdup(g
);
1362 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1366 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1374 static bool item_equal(Item
*a
, Item
*b
) {
1378 if (a
->type
!= b
->type
)
1381 if (!streq_ptr(a
->name
, b
->name
))
1384 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1387 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1390 if (!streq_ptr(a
->description
, b
->description
))
1393 if (a
->uid_set
!= b
->uid_set
)
1396 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1399 if (a
->gid_set
!= b
->gid_set
)
1402 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1405 if (!streq_ptr(a
->home
, b
->home
))
1408 if (!streq_ptr(a
->shell
, b
->shell
))
1414 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1416 static const Specifier specifier_table
[] = {
1417 { 'm', specifier_machine_id
, NULL
},
1418 { 'b', specifier_boot_id
, NULL
},
1419 { 'H', specifier_host_name
, NULL
},
1420 { 'v', specifier_kernel_release
, NULL
},
1424 _cleanup_free_
char *action
= NULL
,
1425 *name
= NULL
, *resolved_name
= NULL
,
1426 *id
= NULL
, *resolved_id
= NULL
,
1427 *description
= NULL
,
1429 *shell
, *resolved_shell
= NULL
;
1430 _cleanup_(item_freep
) Item
*i
= NULL
;
1442 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
,
1443 &action
, &name
, &id
, &description
, &home
, &shell
, NULL
);
1445 log_error("[%s:%u] Syntax error.", fname
, line
);
1449 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1453 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1458 if (strlen(action
) != 1) {
1459 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1463 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1464 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1469 if (isempty(name
) || streq(name
, "-"))
1473 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1475 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1479 if (!valid_user_group_name(resolved_name
)) {
1480 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1486 if (isempty(id
) || streq(id
, "-"))
1490 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1492 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1497 /* Verify description */
1498 if (isempty(description
) || streq(description
, "-"))
1499 description
= mfree(description
);
1502 if (!valid_gecos(description
)) {
1503 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, description
);
1509 if (isempty(home
) || streq(home
, "-"))
1513 if (!valid_home(home
)) {
1514 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, home
);
1520 if (isempty(shell
) || streq(shell
, "-"))
1521 shell
= mfree(shell
);
1524 r
= specifier_printf(shell
, specifier_table
, NULL
, &resolved_shell
);
1526 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, shell
);
1530 if (!valid_shell(resolved_shell
)) {
1531 log_error("[%s:%u] '%s' is not a valid login shell field.", fname
, line
, resolved_shell
);
1537 switch (action
[0]) {
1540 if (resolved_name
) {
1541 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1546 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1550 if (description
|| home
|| shell
) {
1551 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1552 fname
, line
, action
[0],
1553 description
? "GECOS" : home
? "home directory" : "login shell");
1557 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1559 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1568 /* Try to extend an existing member or group item */
1570 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1575 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1579 if (!valid_user_group_name(resolved_id
)) {
1580 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1584 if (description
|| home
|| shell
) {
1585 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1586 fname
, line
, action
[0],
1587 description
? "GECOS" : home
? "home directory" : "login shell");
1591 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1595 l
= ordered_hashmap_get(members
, resolved_id
);
1597 /* A list for this group name already exists, let's append to it */
1598 r
= strv_push(&l
, resolved_name
);
1602 resolved_name
= NULL
;
1604 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1606 /* No list for this group name exists yet, create one */
1608 l
= new0(char *, 2);
1612 l
[0] = resolved_name
;
1615 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1621 resolved_id
= resolved_name
= NULL
;
1629 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1633 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1642 if (path_is_absolute(resolved_id
)) {
1643 i
->uid_path
= resolved_id
;
1646 path_kill_slashes(i
->uid_path
);
1648 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1649 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1650 r
= parse_gid(gid
, &i
->gid
);
1652 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1654 i
->id_set_strict
= true;
1655 free_and_replace(resolved_id
, uid
);
1657 if (!streq(resolved_id
, "-")) {
1658 r
= parse_uid(resolved_id
, &i
->uid
);
1660 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1666 i
->description
= description
;
1672 i
->shell
= resolved_shell
;
1673 resolved_shell
= NULL
;
1680 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1684 if (description
|| home
|| shell
) {
1685 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1686 fname
, line
, action
[0],
1687 description
? "GECOS" : home
? "home directory" : "login shell");
1691 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1700 if (path_is_absolute(resolved_id
)) {
1701 i
->gid_path
= resolved_id
;
1704 path_kill_slashes(i
->gid_path
);
1706 r
= parse_gid(resolved_id
, &i
->gid
);
1708 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1721 i
->type
= action
[0];
1722 i
->name
= resolved_name
;
1723 resolved_name
= NULL
;
1725 existing
= ordered_hashmap_get(h
, i
->name
);
1728 /* Two identical items are fine */
1729 if (!item_equal(existing
, i
))
1730 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1735 r
= ordered_hashmap_put(h
, i
->name
, i
);
1743 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1744 _cleanup_fclose_
FILE *rf
= NULL
;
1746 char line
[LINE_MAX
];
1755 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1757 if (ignore_enoent
&& r
== -ENOENT
)
1760 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1766 FOREACH_LINE(line
, f
, break) {
1773 if (IN_SET(*l
, 0, '#'))
1776 k
= parse_line(fn
, v
, l
);
1777 if (k
< 0 && r
== 0)
1782 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1790 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1794 name
= hashmap_first(by_id
);
1798 hashmap_remove(by_name
, name
);
1800 hashmap_steal_first_key(by_id
);
1804 while ((name
= hashmap_steal_first_key(by_name
)))
1807 hashmap_free(by_name
);
1808 hashmap_free(by_id
);
1811 static void help(void) {
1812 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1813 "Creates system user accounts.\n\n"
1814 " -h --help Show this help\n"
1815 " --version Show package version\n"
1816 " --root=PATH Operate on an alternate filesystem root\n"
1817 " --replace=PATH Treat arguments as replacement for PATH\n"
1818 " --inline Treat arguments as configuration lines\n"
1819 , program_invocation_short_name
);
1822 static int parse_argv(int argc
, char *argv
[]) {
1825 ARG_VERSION
= 0x100,
1831 static const struct option options
[] = {
1832 { "help", no_argument
, NULL
, 'h' },
1833 { "version", no_argument
, NULL
, ARG_VERSION
},
1834 { "root", required_argument
, NULL
, ARG_ROOT
},
1835 { "replace", required_argument
, NULL
, ARG_REPLACE
},
1836 { "inline", no_argument
, NULL
, ARG_INLINE
},
1845 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1857 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1863 if (!path_is_absolute(optarg
) ||
1864 !endswith(optarg
, ".conf")) {
1865 log_error("The argument to --replace= must an absolute path to a config file");
1869 arg_replace
= optarg
;
1880 assert_not_reached("Unhandled option");
1883 if (arg_replace
&& optind
>= argc
) {
1884 log_error("When --replace= is given, some configuration items must be specified");
1891 static int parse_arguments(char **args
) {
1896 STRV_FOREACH(arg
, args
) {
1898 /* Use (argument):n, where n==1 for the first positional arg */
1899 r
= parse_line("(argument)", pos
, *arg
);
1901 r
= read_config_file(*arg
, false);
1911 static int read_config_files(const char* dirs
, char **args
) {
1912 _cleanup_strv_free_
char **files
= NULL
;
1913 _cleanup_free_
char *p
= NULL
;
1917 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, 0, dirs
);
1919 return log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1922 r
= conf_files_insert_nulstr(&files
, arg_root
, dirs
, arg_replace
);
1924 return log_error_errno(r
, "Failed to extend sysusers.d file list: %m");
1926 p
= path_join(arg_root
, arg_replace
, NULL
);
1931 STRV_FOREACH(f
, files
)
1932 if (p
&& path_equal(*f
, p
)) {
1933 log_debug("Parsing arguments at position \"%s\"…", *f
);
1935 r
= parse_arguments(args
);
1939 log_debug("Reading config file \"%s\"…", *f
);
1941 /* Just warn, ignore result otherwise */
1942 (void) read_config_file(*f
, true);
1948 int main(int argc
, char *argv
[]) {
1950 _cleanup_close_
int lock
= -1;
1956 r
= parse_argv(argc
, argv
);
1960 log_set_target(LOG_TARGET_AUTO
);
1961 log_parse_environment();
1966 r
= mac_selinux_init();
1968 log_error_errno(r
, "SELinux setup failed: %m");
1972 /* If command line arguments are specified along with --replace, read all
1973 * configuration files and insert the positional arguments at the specified
1974 * place. Otherwise, if command line arguments are specified, execute just
1975 * them, and finally, without --replace= or any positional arguments, just
1976 * read configuration and execute it.
1978 if (arg_replace
|| optind
>= argc
)
1979 r
= read_config_files(conf_file_dirs
, argv
+ optind
);
1981 r
= parse_arguments(argv
+ optind
);
1985 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1986 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1987 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1988 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1990 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1991 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1996 /* Default to default range of 1..SYSTEM_UID_MAX */
1997 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
2008 lock
= take_etc_passwd_lock(arg_root
);
2010 log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
2014 r
= load_user_database();
2016 log_error_errno(r
, "Failed to load user database: %m");
2020 r
= load_group_database();
2022 log_error_errno(r
, "Failed to read group database: %m");
2026 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
2029 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
2034 log_error_errno(r
, "Failed to write files: %m");
2037 ordered_hashmap_free_with_destructor(groups
, item_free
);
2038 ordered_hashmap_free_with_destructor(users
, item_free
);
2040 while ((n
= ordered_hashmap_first_key(members
))) {
2041 strv_free(ordered_hashmap_steal_first(members
));
2044 ordered_hashmap_free(members
);
2046 ordered_hashmap_free(todo_uids
);
2047 ordered_hashmap_free(todo_gids
);
2049 free_database(database_user
, database_uid
);
2050 free_database(database_group
, database_gid
);
2054 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;