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
);
432 /* Make sure we keep the NIS entries (if any) at the end. */
433 if (IN_SET(pw
->pw_name
[0], '+', '-'))
437 if (putpwent(pw
, passwd
) < 0)
438 return errno
? -errno
: -EIO
;
442 if (!IN_SET(errno
, 0, ENOENT
))
448 if (fchmod(fileno(passwd
), 0644) < 0)
452 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
457 .pw_gecos
= i
->description
,
459 /* "x" means the password is stored in the shadow file */
460 .pw_passwd
= (char*) "x",
462 /* We default to the root directory as home */
463 .pw_dir
= i
->home
? i
->home
: (char*) "/",
465 /* Initialize the shell to nologin, with one exception:
466 * for root we patch in something special */
467 .pw_shell
= i
->shell
?: (char*) default_shell(i
->uid
),
471 if (putpwent(&n
, passwd
) != 0)
472 return errno
? -errno
: -EIO
;
475 /* Append the remaining NIS entries if any */
478 if (putpwent(pw
, passwd
) < 0)
479 return errno
? -errno
: -EIO
;
481 pw
= fgetpwent(original
);
483 if (!IN_SET(errno
, 0, ENOENT
))
486 r
= fflush_and_check(passwd
);
491 *tmpfile_path
= passwd_tmp
;
497 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
498 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
499 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
500 struct spwd
*sp
= NULL
;
506 if (ordered_hashmap_size(todo_uids
) == 0)
509 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
513 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
515 original
= fopen(shadow_path
, "re");
518 r
= sync_rights(original
, shadow
);
523 while ((sp
= fgetspent(original
))) {
525 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
526 if (i
&& i
->todo_user
) {
527 /* we will update the existing entry */
528 sp
->sp_lstchg
= lstchg
;
530 /* only the /etc/shadow stage is left, so we can
531 * safely remove the item from the todo set */
532 i
->todo_user
= false;
533 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
538 /* Make sure we keep the NIS entries (if any) at the end. */
539 if (IN_SET(sp
->sp_namp
[0], '+', '-'))
542 if (putspent(sp
, shadow
) < 0)
543 return errno
? -errno
: -EIO
;
547 if (!IN_SET(errno
, 0, ENOENT
))
553 if (fchmod(fileno(shadow
), 0000) < 0)
557 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
560 .sp_pwdp
= (char*) "!!",
567 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
571 if (putspent(&n
, shadow
) != 0)
572 return errno
? -errno
: -EIO
;
576 /* Append the remaining NIS entries if any */
579 if (putspent(sp
, shadow
) < 0)
580 return errno
? -errno
: -EIO
;
583 sp
= fgetspent(original
);
585 if (!IN_SET(errno
, 0, ENOENT
))
588 r
= fflush_sync_and_check(shadow
);
593 *tmpfile_path
= shadow_tmp
;
599 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
600 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
601 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
602 bool group_changed
= false;
603 struct group
*gr
= NULL
;
608 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
611 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
615 original
= fopen(group_path
, "re");
618 r
= sync_rights(original
, group
);
623 while ((gr
= fgetgrent(original
))) {
624 /* Safety checks against name and GID collisions. Normally,
625 * this should be unnecessary, but given that we look at the
626 * entries anyway here, let's make an extra verification
627 * step that we don't generate duplicate entries. */
629 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
630 if (i
&& i
->todo_group
) {
631 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
635 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
636 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
640 /* Make sure we keep the NIS entries (if any) at the end. */
641 if (IN_SET(gr
->gr_name
[0], '+', '-'))
644 r
= putgrent_with_members(gr
, group
);
648 group_changed
= true;
652 if (!IN_SET(errno
, 0, ENOENT
))
658 if (fchmod(fileno(group
), 0644) < 0)
662 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
666 .gr_passwd
= (char*) "x",
669 r
= putgrent_with_members(&n
, group
);
673 group_changed
= true;
676 /* Append the remaining NIS entries if any */
679 if (putgrent(gr
, group
) != 0)
680 return errno
> 0 ? -errno
: -EIO
;
682 gr
= fgetgrent(original
);
684 if (!IN_SET(errno
, 0, ENOENT
))
687 r
= fflush_sync_and_check(group
);
693 *tmpfile_path
= group_tmp
;
700 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
702 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
703 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
704 bool group_changed
= false;
709 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
712 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
716 original
= fopen(gshadow_path
, "re");
720 r
= sync_rights(original
, gshadow
);
725 while ((sg
= fgetsgent(original
))) {
727 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
728 if (i
&& i
->todo_group
) {
729 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
733 r
= putsgent_with_members(sg
, gshadow
);
737 group_changed
= true;
741 if (!IN_SET(errno
, 0, ENOENT
))
747 if (fchmod(fileno(gshadow
), 0000) < 0)
751 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
754 .sg_passwd
= (char*) "!!",
757 r
= putsgent_with_members(&n
, gshadow
);
761 group_changed
= true;
764 r
= fflush_sync_and_check(gshadow
);
770 *tmpfile_path
= gshadow_tmp
;
780 static int write_files(void) {
781 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
782 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
783 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
786 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
787 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
788 group_path
= prefix_roota(arg_root
, "/etc/group");
789 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
791 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
795 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
799 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
803 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
807 /* Make a backup of the old files */
809 r
= make_backup("/etc/group", group_path
);
814 r
= make_backup("/etc/gshadow", gshadow_path
);
820 r
= make_backup("/etc/passwd", passwd_path
);
825 r
= make_backup("/etc/shadow", shadow_path
);
830 /* And make the new files count */
832 r
= rename_and_apply_smack(group_tmp
, group_path
);
836 group_tmp
= mfree(group_tmp
);
839 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
843 gshadow_tmp
= mfree(gshadow_tmp
);
847 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
851 passwd_tmp
= mfree(passwd_tmp
);
854 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
858 shadow_tmp
= mfree(shadow_tmp
);
864 static int uid_is_ok(uid_t uid
, const char *name
, bool check_with_gid
) {
870 /* Let's see if we already have assigned the UID a second time */
871 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
874 /* Try to avoid using uids that are already used by a group
875 * that doesn't have the same name as our new user. */
876 if (check_with_gid
) {
877 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
878 if (i
&& !streq(i
->name
, name
))
882 /* Let's check the files directly */
883 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
886 if (check_with_gid
) {
887 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
888 if (n
&& !streq(n
, name
))
892 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
898 if (!IN_SET(errno
, 0, ENOENT
))
901 if (check_with_gid
) {
903 g
= getgrgid((gid_t
) uid
);
905 if (!streq(g
->gr_name
, name
))
907 } else if (!IN_SET(errno
, 0, ENOENT
))
915 static int root_stat(const char *p
, struct stat
*st
) {
918 fix
= prefix_roota(arg_root
, p
);
919 if (stat(fix
, st
) < 0)
925 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
927 bool found_uid
= false, found_gid
= false;
933 /* First, try to get the gid directly */
934 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
939 /* Then, try to get the uid directly */
940 if ((_uid
|| (_gid
&& !found_gid
))
942 && root_stat(i
->uid_path
, &st
) >= 0) {
947 /* If we need the gid, but had no success yet, also derive it from the uid path */
948 if (_gid
&& !found_gid
) {
954 /* If that didn't work yet, then let's reuse the gid as uid */
955 if (_uid
&& !found_uid
&& i
->gid_path
) {
960 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
961 uid
= (uid_t
) st
.st_gid
;
983 static int add_user(Item
*i
) {
989 /* Check the database directly */
990 z
= hashmap_get(database_user
, i
->name
);
992 log_debug("User %s already exists.", i
->name
);
993 i
->uid
= PTR_TO_UID(z
);
1001 /* Also check NSS */
1003 p
= getpwnam(i
->name
);
1005 log_debug("User %s already exists.", i
->name
);
1009 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
1015 if (!IN_SET(errno
, 0, ENOENT
))
1016 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
1019 /* Try to use the suggested numeric uid */
1021 r
= uid_is_ok(i
->uid
, i
->name
, !i
->id_set_strict
);
1023 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1025 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
1030 /* If that didn't work, try to read it from the specified path */
1034 if (read_id_from_file(i
, &c
, NULL
) > 0) {
1036 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1037 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
1039 r
= uid_is_ok(c
, i
->name
, true);
1041 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1046 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
1051 /* Otherwise, try to reuse the group ID */
1052 if (!i
->uid_set
&& i
->gid_set
) {
1053 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
, true);
1055 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1057 i
->uid
= (uid_t
) i
->gid
;
1062 /* And if that didn't work either, let's try to find a free one */
1065 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1067 log_error("No free user ID available for %s.", i
->name
);
1071 r
= uid_is_ok(search_uid
, i
->name
, true);
1073 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1079 i
->uid
= search_uid
;
1082 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1086 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1090 i
->todo_user
= true;
1091 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1096 static int gid_is_ok(gid_t gid
) {
1100 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1103 /* Avoid reusing gids that are already used by a different user */
1104 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1107 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1110 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1118 if (!IN_SET(errno
, 0, ENOENT
))
1122 p
= getpwuid((uid_t
) gid
);
1125 if (!IN_SET(errno
, 0, ENOENT
))
1132 static int add_group(Item
*i
) {
1138 /* Check the database directly */
1139 z
= hashmap_get(database_group
, i
->name
);
1141 log_debug("Group %s already exists.", i
->name
);
1142 i
->gid
= PTR_TO_GID(z
);
1147 /* Also check NSS */
1152 g
= getgrnam(i
->name
);
1154 log_debug("Group %s already exists.", i
->name
);
1159 if (!IN_SET(errno
, 0, ENOENT
))
1160 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1163 /* Try to use the suggested numeric gid */
1165 r
= gid_is_ok(i
->gid
);
1167 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1168 if (i
->id_set_strict
) {
1169 /* If we require the gid to already exist we can return here:
1170 * r > 0: means the gid does not exist -> fail
1171 * r == 0: means the gid exists -> nothing more to do.
1174 log_error("Failed to create %s: please create GID %d", i
->name
, i
->gid
);
1181 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1186 /* Try to reuse the numeric uid, if there's one */
1187 if (!i
->gid_set
&& i
->uid_set
) {
1188 r
= gid_is_ok((gid_t
) i
->uid
);
1190 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1192 i
->gid
= (gid_t
) i
->uid
;
1197 /* If that didn't work, try to read it from the specified path */
1201 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1203 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1204 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1208 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1213 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1218 /* And if that didn't work either, let's try to find a free one */
1221 /* We look for new GIDs in the UID pool! */
1222 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1224 log_error("No free group ID available for %s.", i
->name
);
1228 r
= gid_is_ok(search_uid
);
1230 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1236 i
->gid
= search_uid
;
1239 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1243 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1247 i
->todo_group
= true;
1248 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1253 static int process_item(Item
*i
) {
1263 j
= ordered_hashmap_get(groups
, i
->name
);
1264 if (j
&& j
->todo_group
) {
1265 /* When the group with the same name is already in queue,
1266 * use the information about the group and do not create
1267 * duplicated group entry. */
1268 i
->gid_set
= j
->gid_set
;
1270 i
->id_set_strict
= true;
1281 return add_group(i
);
1284 assert_not_reached("Unknown item type");
1288 static void item_free(Item
*i
) {
1296 free(i
->description
);
1302 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1304 static int add_implicit(void) {
1309 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1310 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1314 if (!ordered_hashmap_get(users
, *m
)) {
1315 _cleanup_(item_freep
) Item
*j
= NULL
;
1317 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1326 j
->name
= strdup(*m
);
1330 r
= ordered_hashmap_put(users
, j
->name
, j
);
1334 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1338 if (!(ordered_hashmap_get(users
, g
) ||
1339 ordered_hashmap_get(groups
, g
))) {
1340 _cleanup_(item_freep
) Item
*j
= NULL
;
1342 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1350 j
->type
= ADD_GROUP
;
1351 j
->name
= strdup(g
);
1355 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1359 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1367 static bool item_equal(Item
*a
, Item
*b
) {
1371 if (a
->type
!= b
->type
)
1374 if (!streq_ptr(a
->name
, b
->name
))
1377 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1380 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1383 if (!streq_ptr(a
->description
, b
->description
))
1386 if (a
->uid_set
!= b
->uid_set
)
1389 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1392 if (a
->gid_set
!= b
->gid_set
)
1395 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1398 if (!streq_ptr(a
->home
, b
->home
))
1401 if (!streq_ptr(a
->shell
, b
->shell
))
1407 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1409 static const Specifier specifier_table
[] = {
1410 { 'm', specifier_machine_id
, NULL
},
1411 { 'b', specifier_boot_id
, NULL
},
1412 { 'H', specifier_host_name
, NULL
},
1413 { 'v', specifier_kernel_release
, NULL
},
1417 _cleanup_free_
char *action
= NULL
,
1418 *name
= NULL
, *resolved_name
= NULL
,
1419 *id
= NULL
, *resolved_id
= NULL
,
1420 *description
= NULL
,
1422 *shell
, *resolved_shell
= NULL
;
1423 _cleanup_(item_freep
) Item
*i
= NULL
;
1435 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
,
1436 &action
, &name
, &id
, &description
, &home
, &shell
, NULL
);
1438 log_error("[%s:%u] Syntax error.", fname
, line
);
1442 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1446 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1451 if (strlen(action
) != 1) {
1452 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1456 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1457 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1462 if (isempty(name
) || streq(name
, "-"))
1466 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1468 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1472 if (!valid_user_group_name(resolved_name
)) {
1473 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1479 if (isempty(id
) || streq(id
, "-"))
1483 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1485 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1490 /* Verify description */
1491 if (isempty(description
) || streq(description
, "-"))
1492 description
= mfree(description
);
1495 if (!valid_gecos(description
)) {
1496 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, description
);
1502 if (isempty(home
) || streq(home
, "-"))
1506 if (!valid_home(home
)) {
1507 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, home
);
1513 if (isempty(shell
) || streq(shell
, "-"))
1514 shell
= mfree(shell
);
1517 r
= specifier_printf(shell
, specifier_table
, NULL
, &resolved_shell
);
1519 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, shell
);
1523 if (!valid_shell(resolved_shell
)) {
1524 log_error("[%s:%u] '%s' is not a valid login shell field.", fname
, line
, resolved_shell
);
1530 switch (action
[0]) {
1533 if (resolved_name
) {
1534 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1539 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1543 if (description
|| home
|| shell
) {
1544 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1545 fname
, line
, action
[0],
1546 description
? "GECOS" : home
? "home directory" : "login shell");
1550 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1552 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1561 /* Try to extend an existing member or group item */
1563 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1568 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1572 if (!valid_user_group_name(resolved_id
)) {
1573 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1577 if (description
|| home
|| shell
) {
1578 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1579 fname
, line
, action
[0],
1580 description
? "GECOS" : home
? "home directory" : "login shell");
1584 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1588 l
= ordered_hashmap_get(members
, resolved_id
);
1590 /* A list for this group name already exists, let's append to it */
1591 r
= strv_push(&l
, resolved_name
);
1595 resolved_name
= NULL
;
1597 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1599 /* No list for this group name exists yet, create one */
1601 l
= new0(char *, 2);
1605 l
[0] = resolved_name
;
1608 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1614 resolved_id
= resolved_name
= NULL
;
1622 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1626 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1635 if (path_is_absolute(resolved_id
)) {
1636 i
->uid_path
= resolved_id
;
1639 path_kill_slashes(i
->uid_path
);
1641 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1642 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1643 r
= parse_gid(gid
, &i
->gid
);
1645 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1647 i
->id_set_strict
= true;
1648 free_and_replace(resolved_id
, uid
);
1650 if (!streq(resolved_id
, "-")) {
1651 r
= parse_uid(resolved_id
, &i
->uid
);
1653 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1659 i
->description
= description
;
1665 i
->shell
= resolved_shell
;
1666 resolved_shell
= NULL
;
1673 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1677 if (description
|| home
|| shell
) {
1678 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1679 fname
, line
, action
[0],
1680 description
? "GECOS" : home
? "home directory" : "login shell");
1684 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1693 if (path_is_absolute(resolved_id
)) {
1694 i
->gid_path
= resolved_id
;
1697 path_kill_slashes(i
->gid_path
);
1699 r
= parse_gid(resolved_id
, &i
->gid
);
1701 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1714 i
->type
= action
[0];
1715 i
->name
= resolved_name
;
1716 resolved_name
= NULL
;
1718 existing
= ordered_hashmap_get(h
, i
->name
);
1721 /* Two identical items are fine */
1722 if (!item_equal(existing
, i
))
1723 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1728 r
= ordered_hashmap_put(h
, i
->name
, i
);
1736 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1737 _cleanup_fclose_
FILE *rf
= NULL
;
1739 char line
[LINE_MAX
];
1748 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1750 if (ignore_enoent
&& r
== -ENOENT
)
1753 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1759 FOREACH_LINE(line
, f
, break) {
1766 if (IN_SET(*l
, 0, '#'))
1769 k
= parse_line(fn
, v
, l
);
1770 if (k
< 0 && r
== 0)
1775 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1783 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1787 name
= hashmap_first(by_id
);
1791 hashmap_remove(by_name
, name
);
1793 hashmap_steal_first_key(by_id
);
1797 while ((name
= hashmap_steal_first_key(by_name
)))
1800 hashmap_free(by_name
);
1801 hashmap_free(by_id
);
1804 static void help(void) {
1805 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1806 "Creates system user accounts.\n\n"
1807 " -h --help Show this help\n"
1808 " --version Show package version\n"
1809 " --root=PATH Operate on an alternate filesystem root\n"
1810 " --replace=PATH Treat arguments as replacement for PATH\n"
1811 " --inline Treat arguments as configuration lines\n"
1812 , program_invocation_short_name
);
1815 static int parse_argv(int argc
, char *argv
[]) {
1818 ARG_VERSION
= 0x100,
1824 static const struct option options
[] = {
1825 { "help", no_argument
, NULL
, 'h' },
1826 { "version", no_argument
, NULL
, ARG_VERSION
},
1827 { "root", required_argument
, NULL
, ARG_ROOT
},
1828 { "replace", required_argument
, NULL
, ARG_REPLACE
},
1829 { "inline", no_argument
, NULL
, ARG_INLINE
},
1838 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1850 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1856 if (!path_is_absolute(optarg
) ||
1857 !endswith(optarg
, ".conf")) {
1858 log_error("The argument to --replace= must an absolute path to a config file");
1862 arg_replace
= optarg
;
1873 assert_not_reached("Unhandled option");
1876 if (arg_replace
&& optind
>= argc
) {
1877 log_error("When --replace= is given, some configuration items must be specified");
1884 static int parse_arguments(char **args
) {
1889 STRV_FOREACH(arg
, args
) {
1891 /* Use (argument):n, where n==1 for the first positional arg */
1892 r
= parse_line("(argument)", pos
, *arg
);
1894 r
= read_config_file(*arg
, false);
1904 static int read_config_files(const char* dirs
, char **args
) {
1905 _cleanup_strv_free_
char **files
= NULL
;
1906 _cleanup_free_
char *p
= NULL
;
1910 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, 0, dirs
);
1912 return log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1915 r
= conf_files_insert_nulstr(&files
, arg_root
, dirs
, arg_replace
);
1917 return log_error_errno(r
, "Failed to extend sysusers.d file list: %m");
1919 p
= path_join(arg_root
, arg_replace
, NULL
);
1924 STRV_FOREACH(f
, files
)
1925 if (p
&& path_equal(*f
, p
)) {
1926 log_debug("Parsing arguments at position \"%s\"…", *f
);
1928 r
= parse_arguments(args
);
1932 log_debug("Reading config file \"%s\"…", *f
);
1934 /* Just warn, ignore result otherwise */
1935 (void) read_config_file(*f
, true);
1941 int main(int argc
, char *argv
[]) {
1943 _cleanup_close_
int lock
= -1;
1949 r
= parse_argv(argc
, argv
);
1953 log_set_target(LOG_TARGET_AUTO
);
1954 log_parse_environment();
1959 r
= mac_selinux_init();
1961 log_error_errno(r
, "SELinux setup failed: %m");
1965 /* If command line arguments are specified along with --replace, read all
1966 * configuration files and insert the positional arguments at the specified
1967 * place. Otherwise, if command line arguments are specified, execute just
1968 * them, and finally, without --replace= or any positional arguments, just
1969 * read configuration and execute it.
1971 if (arg_replace
|| optind
>= argc
)
1972 r
= read_config_files(conf_file_dirs
, argv
+ optind
);
1974 r
= parse_arguments(argv
+ optind
);
1978 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1979 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1980 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1981 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1983 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1984 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1989 /* Default to default range of 1..SYSTEM_UID_MAX */
1990 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
2001 lock
= take_etc_passwd_lock(arg_root
);
2003 log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
2007 r
= load_user_database();
2009 log_error_errno(r
, "Failed to load user database: %m");
2013 r
= load_group_database();
2015 log_error_errno(r
, "Failed to read group database: %m");
2019 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
2022 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
2027 log_error_errno(r
, "Failed to write files: %m");
2030 ordered_hashmap_free_with_destructor(groups
, item_free
);
2031 ordered_hashmap_free_with_destructor(users
, item_free
);
2033 while ((n
= ordered_hashmap_first_key(members
))) {
2034 strv_free(ordered_hashmap_steal_first(members
));
2037 ordered_hashmap_free(members
);
2039 ordered_hashmap_free(todo_uids
);
2040 ordered_hashmap_free(todo_gids
);
2042 free_database(database_user
, database_uid
);
2043 free_database(database_group
, database_gid
);
2047 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;