1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "alloc-util.h"
29 #include "conf-files.h"
34 #include "fileio-label.h"
35 #include "format-util.h"
37 #include "path-util.h"
38 #include "selinux-util.h"
39 #include "smack-util.h"
40 #include "specifier.h"
41 #include "string-util.h"
43 #include "uid-range.h"
44 #include "user-util.h"
48 typedef enum ItemType
{
67 // id_set_strict means that the group with the specified gid must
68 // exist and that the check if a uid clashes with a gid is skipped
76 static char *arg_root
= NULL
;
78 static const char conf_file_dirs
[] = CONF_PATHS_NULSTR("sysusers.d");
80 static OrderedHashmap
*users
= NULL
, *groups
= NULL
;
81 static OrderedHashmap
*todo_uids
= NULL
, *todo_gids
= NULL
;
82 static OrderedHashmap
*members
= NULL
;
84 static Hashmap
*database_uid
= NULL
, *database_user
= NULL
;
85 static Hashmap
*database_gid
= NULL
, *database_group
= NULL
;
87 static uid_t search_uid
= UID_INVALID
;
88 static UidRange
*uid_range
= NULL
;
89 static unsigned n_uid_range
= 0;
91 static int load_user_database(void) {
92 _cleanup_fclose_
FILE *f
= NULL
;
93 const char *passwd_path
;
97 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
98 f
= fopen(passwd_path
, "re");
100 return errno
== ENOENT
? 0 : -errno
;
102 r
= hashmap_ensure_allocated(&database_user
, &string_hash_ops
);
106 r
= hashmap_ensure_allocated(&database_uid
, NULL
);
111 while ((pw
= fgetpwent(f
))) {
115 n
= strdup(pw
->pw_name
);
119 k
= hashmap_put(database_user
, n
, UID_TO_PTR(pw
->pw_uid
));
120 if (k
< 0 && k
!= -EEXIST
) {
125 q
= hashmap_put(database_uid
, UID_TO_PTR(pw
->pw_uid
), n
);
126 if (q
< 0 && q
!= -EEXIST
) {
137 if (!IN_SET(errno
, 0, ENOENT
))
143 static int load_group_database(void) {
144 _cleanup_fclose_
FILE *f
= NULL
;
145 const char *group_path
;
149 group_path
= prefix_roota(arg_root
, "/etc/group");
150 f
= fopen(group_path
, "re");
152 return errno
== ENOENT
? 0 : -errno
;
154 r
= hashmap_ensure_allocated(&database_group
, &string_hash_ops
);
158 r
= hashmap_ensure_allocated(&database_gid
, NULL
);
163 while ((gr
= fgetgrent(f
))) {
167 n
= strdup(gr
->gr_name
);
171 k
= hashmap_put(database_group
, n
, GID_TO_PTR(gr
->gr_gid
));
172 if (k
< 0 && k
!= -EEXIST
) {
177 q
= hashmap_put(database_gid
, GID_TO_PTR(gr
->gr_gid
), n
);
178 if (q
< 0 && q
!= -EEXIST
) {
189 if (!IN_SET(errno
, 0, ENOENT
))
195 static int make_backup(const char *target
, const char *x
) {
196 _cleanup_close_
int src
= -1;
197 _cleanup_fclose_
FILE *dst
= NULL
;
198 _cleanup_free_
char *temp
= NULL
;
200 struct timespec ts
[2];
204 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
206 if (errno
== ENOENT
) /* No backup necessary... */
212 if (fstat(src
, &st
) < 0)
215 r
= fopen_temporary_label(target
, x
, &dst
, &temp
);
219 r
= copy_bytes(src
, fileno(dst
), (uint64_t) -1, COPY_REFLINK
);
223 /* Don't fail on chmod() or chown(). If it stays owned by us
224 * and/or unreadable by others, then it isn't too bad... */
226 backup
= strjoina(x
, "-");
228 /* Copy over the access mask */
229 if (fchmod(fileno(dst
), st
.st_mode
& 07777) < 0)
230 log_warning_errno(errno
, "Failed to change mode on %s: %m", backup
);
232 if (fchown(fileno(dst
), st
.st_uid
, st
.st_gid
)< 0)
233 log_warning_errno(errno
, "Failed to change ownership of %s: %m", backup
);
237 if (futimens(fileno(dst
), ts
) < 0)
238 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
240 r
= fflush_sync_and_check(dst
);
244 if (rename(temp
, backup
) < 0) {
256 static int putgrent_with_members(const struct group
*gr
, FILE *group
) {
262 a
= ordered_hashmap_get(members
, gr
->gr_name
);
264 _cleanup_strv_free_
char **l
= NULL
;
268 l
= strv_copy(gr
->gr_mem
);
273 if (strv_find(l
, *i
))
276 if (strv_extend(&l
, *i
) < 0)
292 if (putgrent(&t
, group
) != 0)
293 return errno
> 0 ? -errno
: -EIO
;
300 if (putgrent(gr
, group
) != 0)
301 return errno
> 0 ? -errno
: -EIO
;
307 static int putsgent_with_members(const struct sgrp
*sg
, FILE *gshadow
) {
313 a
= ordered_hashmap_get(members
, sg
->sg_namp
);
315 _cleanup_strv_free_
char **l
= NULL
;
319 l
= strv_copy(sg
->sg_mem
);
324 if (strv_find(l
, *i
))
327 if (strv_extend(&l
, *i
) < 0)
343 if (putsgent(&t
, gshadow
) != 0)
344 return errno
> 0 ? -errno
: -EIO
;
351 if (putsgent(sg
, gshadow
) != 0)
352 return errno
> 0 ? -errno
: -EIO
;
358 static int sync_rights(FILE *from
, FILE *to
) {
361 if (fstat(fileno(from
), &st
) < 0)
364 if (fchmod(fileno(to
), st
.st_mode
& 07777) < 0)
367 if (fchown(fileno(to
), st
.st_uid
, st
.st_gid
) < 0)
373 static int rename_and_apply_smack(const char *temp_path
, const char *dest_path
) {
375 if (rename(temp_path
, dest_path
) < 0)
378 #ifdef SMACK_RUN_LABEL
379 r
= mac_smack_apply(dest_path
, SMACK_ATTR_ACCESS
, SMACK_FLOOR_LABEL
);
386 static int write_temporary_passwd(const char *passwd_path
, FILE **tmpfile
, char **tmpfile_path
) {
387 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
388 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
393 if (ordered_hashmap_size(todo_uids
) == 0)
396 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
400 original
= fopen(passwd_path
, "re");
404 r
= sync_rights(original
, passwd
);
409 while ((pw
= fgetpwent(original
))) {
411 i
= ordered_hashmap_get(users
, pw
->pw_name
);
412 if (i
&& i
->todo_user
) {
413 log_error("%s: User \"%s\" already exists.", passwd_path
, pw
->pw_name
);
417 if (ordered_hashmap_contains(todo_uids
, UID_TO_PTR(pw
->pw_uid
))) {
418 log_error("%s: Detected collision for UID " UID_FMT
".", passwd_path
, pw
->pw_uid
);
423 if (putpwent(pw
, passwd
) < 0)
424 return errno
? -errno
: -EIO
;
428 if (!IN_SET(errno
, 0, ENOENT
))
434 if (fchmod(fileno(passwd
), 0644) < 0)
438 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
443 .pw_gecos
= i
->description
,
445 /* "x" means the password is stored in the shadow file */
446 .pw_passwd
= (char*) "x",
448 /* We default to the root directory as home */
449 .pw_dir
= i
->home
? i
->home
: (char*) "/",
451 /* Initialize the shell to nologin, with one exception:
452 * for root we patch in something special */
453 .pw_shell
= i
->uid
== 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
457 if (putpwent(&n
, passwd
) != 0)
458 return errno
? -errno
: -EIO
;
461 r
= fflush_and_check(passwd
);
466 *tmpfile_path
= passwd_tmp
;
472 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
473 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
474 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
480 if (ordered_hashmap_size(todo_uids
) == 0)
483 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
487 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
489 original
= fopen(shadow_path
, "re");
493 r
= sync_rights(original
, shadow
);
498 while ((sp
= fgetspent(original
))) {
500 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
501 if (i
&& i
->todo_user
) {
502 /* we will update the existing entry */
503 sp
->sp_lstchg
= lstchg
;
505 /* only the /etc/shadow stage is left, so we can
506 * safely remove the item from the todo set */
507 i
->todo_user
= false;
508 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
512 if (putspent(sp
, shadow
) < 0)
513 return errno
? -errno
: -EIO
;
517 if (!IN_SET(errno
, 0, ENOENT
))
523 if (fchmod(fileno(shadow
), 0000) < 0)
527 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
530 .sp_pwdp
= (char*) "!!",
537 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
541 if (putspent(&n
, shadow
) != 0)
542 return errno
? -errno
: -EIO
;
545 r
= fflush_sync_and_check(shadow
);
550 *tmpfile_path
= shadow_tmp
;
556 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
557 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
558 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
559 bool group_changed
= false;
564 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
567 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
571 original
= fopen(group_path
, "re");
575 r
= sync_rights(original
, group
);
580 while ((gr
= fgetgrent(original
))) {
581 /* Safety checks against name and GID collisions. Normally,
582 * this should be unnecessary, but given that we look at the
583 * entries anyway here, let's make an extra verification
584 * step that we don't generate duplicate entries. */
586 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
587 if (i
&& i
->todo_group
) {
588 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
592 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
593 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
597 r
= putgrent_with_members(gr
, group
);
601 group_changed
= true;
605 if (!IN_SET(errno
, 0, ENOENT
))
611 if (fchmod(fileno(group
), 0644) < 0)
615 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
619 .gr_passwd
= (char*) "x",
622 r
= putgrent_with_members(&n
, group
);
626 group_changed
= true;
629 r
= fflush_sync_and_check(group
);
635 *tmpfile_path
= group_tmp
;
642 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
644 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
645 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
646 bool group_changed
= false;
651 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
654 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
658 original
= fopen(gshadow_path
, "re");
662 r
= sync_rights(original
, gshadow
);
667 while ((sg
= fgetsgent(original
))) {
669 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
670 if (i
&& i
->todo_group
) {
671 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
675 r
= putsgent_with_members(sg
, gshadow
);
679 group_changed
= true;
683 if (!IN_SET(errno
, 0, ENOENT
))
689 if (fchmod(fileno(gshadow
), 0000) < 0)
693 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
696 .sg_passwd
= (char*) "!!",
699 r
= putsgent_with_members(&n
, gshadow
);
703 group_changed
= true;
706 r
= fflush_sync_and_check(gshadow
);
712 *tmpfile_path
= gshadow_tmp
;
722 static int write_files(void) {
723 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
724 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
725 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
728 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
729 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
730 group_path
= prefix_roota(arg_root
, "/etc/group");
731 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
733 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
737 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
741 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
745 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
749 /* Make a backup of the old files */
751 r
= make_backup("/etc/group", group_path
);
756 r
= make_backup("/etc/gshadow", gshadow_path
);
762 r
= make_backup("/etc/passwd", passwd_path
);
767 r
= make_backup("/etc/shadow", shadow_path
);
772 /* And make the new files count */
774 r
= rename_and_apply_smack(group_tmp
, group_path
);
778 group_tmp
= mfree(group_tmp
);
781 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
785 gshadow_tmp
= mfree(gshadow_tmp
);
789 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
793 passwd_tmp
= mfree(passwd_tmp
);
796 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
800 shadow_tmp
= mfree(shadow_tmp
);
806 static int uid_is_ok(uid_t uid
, const char *name
, bool check_with_gid
) {
812 /* Let's see if we already have assigned the UID a second time */
813 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
816 /* Try to avoid using uids that are already used by a group
817 * that doesn't have the same name as our new user. */
818 if (check_with_gid
) {
819 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
820 if (i
&& !streq(i
->name
, name
))
824 /* Let's check the files directly */
825 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
828 if (check_with_gid
) {
829 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
830 if (n
&& !streq(n
, name
))
834 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
840 if (!IN_SET(errno
, 0, ENOENT
))
843 if (check_with_gid
) {
845 g
= getgrgid((gid_t
) uid
);
847 if (!streq(g
->gr_name
, name
))
849 } else if (!IN_SET(errno
, 0, ENOENT
))
857 static int root_stat(const char *p
, struct stat
*st
) {
860 fix
= prefix_roota(arg_root
, p
);
861 if (stat(fix
, st
) < 0)
867 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
869 bool found_uid
= false, found_gid
= false;
875 /* First, try to get the gid directly */
876 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
881 /* Then, try to get the uid directly */
882 if ((_uid
|| (_gid
&& !found_gid
))
884 && root_stat(i
->uid_path
, &st
) >= 0) {
889 /* If we need the gid, but had no success yet, also derive it from the uid path */
890 if (_gid
&& !found_gid
) {
896 /* If that didn't work yet, then let's reuse the gid as uid */
897 if (_uid
&& !found_uid
&& i
->gid_path
) {
902 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
903 uid
= (uid_t
) st
.st_gid
;
925 static int add_user(Item
*i
) {
931 /* Check the database directly */
932 z
= hashmap_get(database_user
, i
->name
);
934 log_debug("User %s already exists.", i
->name
);
935 i
->uid
= PTR_TO_UID(z
);
945 p
= getpwnam(i
->name
);
947 log_debug("User %s already exists.", i
->name
);
951 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
957 if (!IN_SET(errno
, 0, ENOENT
))
958 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
961 /* Try to use the suggested numeric uid */
963 r
= uid_is_ok(i
->uid
, i
->name
, !i
->id_set_strict
);
965 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
967 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
972 /* If that didn't work, try to read it from the specified path */
976 if (read_id_from_file(i
, &c
, NULL
) > 0) {
978 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
979 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
981 r
= uid_is_ok(c
, i
->name
, true);
983 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
988 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
993 /* Otherwise, try to reuse the group ID */
994 if (!i
->uid_set
&& i
->gid_set
) {
995 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
, true);
997 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
999 i
->uid
= (uid_t
) i
->gid
;
1004 /* And if that didn't work either, let's try to find a free one */
1007 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1009 log_error("No free user ID available for %s.", i
->name
);
1013 r
= uid_is_ok(search_uid
, i
->name
, true);
1015 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1021 i
->uid
= search_uid
;
1024 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1028 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1032 i
->todo_user
= true;
1033 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1038 static int gid_is_ok(gid_t gid
) {
1042 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1045 /* Avoid reusing gids that are already used by a different user */
1046 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1049 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1052 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1060 if (!IN_SET(errno
, 0, ENOENT
))
1064 p
= getpwuid((uid_t
) gid
);
1067 if (!IN_SET(errno
, 0, ENOENT
))
1074 static int add_group(Item
*i
) {
1080 /* Check the database directly */
1081 z
= hashmap_get(database_group
, i
->name
);
1083 log_debug("Group %s already exists.", i
->name
);
1084 i
->gid
= PTR_TO_GID(z
);
1089 /* Also check NSS */
1094 g
= getgrnam(i
->name
);
1096 log_debug("Group %s already exists.", i
->name
);
1101 if (!IN_SET(errno
, 0, ENOENT
))
1102 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1105 /* Try to use the suggested numeric gid */
1107 r
= gid_is_ok(i
->gid
);
1109 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1110 if (i
->id_set_strict
) {
1111 /* If we require the gid to already exist we can return here:
1112 * r > 0: means the gid does not exist -> fail
1113 * r == 0: means the gid exists -> nothing more to do.
1116 log_error("Failed to create %s: please create GID %d", i
->name
, i
->gid
);
1123 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1128 /* Try to reuse the numeric uid, if there's one */
1129 if (!i
->gid_set
&& i
->uid_set
) {
1130 r
= gid_is_ok((gid_t
) i
->uid
);
1132 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1134 i
->gid
= (gid_t
) i
->uid
;
1139 /* If that didn't work, try to read it from the specified path */
1143 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1145 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1146 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1150 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1155 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1160 /* And if that didn't work either, let's try to find a free one */
1163 /* We look for new GIDs in the UID pool! */
1164 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1166 log_error("No free group ID available for %s.", i
->name
);
1170 r
= gid_is_ok(search_uid
);
1172 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1178 i
->gid
= search_uid
;
1181 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1185 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1189 i
->todo_group
= true;
1190 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1195 static int process_item(Item
*i
) {
1210 return add_group(i
);
1213 assert_not_reached("Unknown item type");
1217 static void item_free(Item
*i
) {
1225 free(i
->description
);
1230 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1232 static int add_implicit(void) {
1237 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1239 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1243 i
= ordered_hashmap_get(groups
, g
);
1245 _cleanup_(item_freep
) Item
*j
= NULL
;
1247 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1255 j
->type
= ADD_GROUP
;
1256 j
->name
= strdup(g
);
1260 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1264 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1268 STRV_FOREACH(m
, l
) {
1270 i
= ordered_hashmap_get(users
, *m
);
1272 _cleanup_(item_freep
) Item
*j
= NULL
;
1274 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1283 j
->name
= strdup(*m
);
1287 r
= ordered_hashmap_put(users
, j
->name
, j
);
1291 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1300 static bool item_equal(Item
*a
, Item
*b
) {
1304 if (a
->type
!= b
->type
)
1307 if (!streq_ptr(a
->name
, b
->name
))
1310 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1313 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1316 if (!streq_ptr(a
->description
, b
->description
))
1319 if (a
->uid_set
!= b
->uid_set
)
1322 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1325 if (a
->gid_set
!= b
->gid_set
)
1328 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1331 if (!streq_ptr(a
->home
, b
->home
))
1337 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1339 static const Specifier specifier_table
[] = {
1340 { 'm', specifier_machine_id
, NULL
},
1341 { 'b', specifier_boot_id
, NULL
},
1342 { 'H', specifier_host_name
, NULL
},
1343 { 'v', specifier_kernel_release
, NULL
},
1347 _cleanup_free_
char *action
= NULL
, *name
= NULL
, *id
= NULL
, *resolved_name
= NULL
, *resolved_id
= NULL
, *description
= NULL
, *home
= NULL
;
1348 _cleanup_(item_freep
) Item
*i
= NULL
;
1360 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
, &action
, &name
, &id
, &description
, &home
, NULL
);
1362 log_error("[%s:%u] Syntax error.", fname
, line
);
1366 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1370 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1375 if (strlen(action
) != 1) {
1376 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1380 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1381 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1386 if (isempty(name
) || streq(name
, "-"))
1390 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1392 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1396 if (!valid_user_group_name(resolved_name
)) {
1397 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1403 if (isempty(id
) || streq(id
, "-"))
1407 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1409 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1414 /* Verify description */
1415 if (isempty(description
) || streq(description
, "-"))
1416 description
= mfree(description
);
1419 if (!valid_gecos(description
)) {
1420 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, description
);
1426 if (isempty(home
) || streq(home
, "-"))
1430 if (!valid_home(home
)) {
1431 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, home
);
1436 switch (action
[0]) {
1439 if (resolved_name
) {
1440 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1445 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1450 log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname
, line
);
1455 log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname
, line
);
1459 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1461 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1470 /* Try to extend an existing member or group item */
1472 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1477 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1481 if (!valid_user_group_name(resolved_id
)) {
1482 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1487 log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname
, line
);
1492 log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname
, line
);
1496 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1500 l
= ordered_hashmap_get(members
, resolved_id
);
1502 /* A list for this group name already exists, let's append to it */
1503 r
= strv_push(&l
, resolved_name
);
1507 resolved_name
= NULL
;
1509 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1511 /* No list for this group name exists yet, create one */
1513 l
= new0(char *, 2);
1517 l
[0] = resolved_name
;
1520 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1526 resolved_id
= resolved_name
= NULL
;
1534 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1538 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1547 if (path_is_absolute(resolved_id
)) {
1548 i
->uid_path
= resolved_id
;
1551 path_kill_slashes(i
->uid_path
);
1553 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1554 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1555 r
= parse_gid(gid
, &i
->gid
);
1557 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1559 i
->id_set_strict
= true;
1560 free_and_replace(resolved_id
, uid
);
1562 r
= parse_uid(resolved_id
, &i
->uid
);
1564 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1570 i
->description
= description
;
1581 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1586 log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname
, line
);
1591 log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname
, line
);
1595 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1604 if (path_is_absolute(resolved_id
)) {
1605 i
->gid_path
= resolved_id
;
1608 path_kill_slashes(i
->gid_path
);
1610 r
= parse_gid(resolved_id
, &i
->gid
);
1612 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1625 i
->type
= action
[0];
1626 i
->name
= resolved_name
;
1627 resolved_name
= NULL
;
1629 existing
= ordered_hashmap_get(h
, i
->name
);
1632 /* Two identical items are fine */
1633 if (!item_equal(existing
, i
))
1634 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1639 r
= ordered_hashmap_put(h
, i
->name
, i
);
1647 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1648 _cleanup_fclose_
FILE *rf
= NULL
;
1650 char line
[LINE_MAX
];
1659 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1661 if (ignore_enoent
&& r
== -ENOENT
)
1664 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1670 FOREACH_LINE(line
, f
, break) {
1677 if (IN_SET(*l
, 0, '#'))
1680 k
= parse_line(fn
, v
, l
);
1681 if (k
< 0 && r
== 0)
1686 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1694 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1698 name
= hashmap_first(by_id
);
1702 hashmap_remove(by_name
, name
);
1704 hashmap_steal_first_key(by_id
);
1708 while ((name
= hashmap_steal_first_key(by_name
)))
1711 hashmap_free(by_name
);
1712 hashmap_free(by_id
);
1715 static void help(void) {
1716 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1717 "Creates system user accounts.\n\n"
1718 " -h --help Show this help\n"
1719 " --version Show package version\n"
1720 " --root=PATH Operate on an alternate filesystem root\n"
1721 , program_invocation_short_name
);
1724 static int parse_argv(int argc
, char *argv
[]) {
1727 ARG_VERSION
= 0x100,
1731 static const struct option options
[] = {
1732 { "help", no_argument
, NULL
, 'h' },
1733 { "version", no_argument
, NULL
, ARG_VERSION
},
1734 { "root", required_argument
, NULL
, ARG_ROOT
},
1743 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1755 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1764 assert_not_reached("Unhandled option");
1770 int main(int argc
, char *argv
[]) {
1772 _cleanup_close_
int lock
= -1;
1778 r
= parse_argv(argc
, argv
);
1782 log_set_target(LOG_TARGET_AUTO
);
1783 log_parse_environment();
1788 r
= mac_selinux_init();
1790 log_error_errno(r
, "SELinux setup failed: %m");
1794 if (optind
< argc
) {
1797 for (j
= optind
; j
< argc
; j
++) {
1798 k
= read_config_file(argv
[j
], false);
1799 if (k
< 0 && r
== 0)
1803 _cleanup_strv_free_
char **files
= NULL
;
1806 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, 0, conf_file_dirs
);
1808 log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1812 STRV_FOREACH(f
, files
) {
1813 k
= read_config_file(*f
, true);
1814 if (k
< 0 && r
== 0)
1819 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1820 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1821 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1822 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1824 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1825 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1830 /* Default to default range of 1..SYSTEM_UID_MAX */
1831 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1842 lock
= take_etc_passwd_lock(arg_root
);
1844 log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
1848 r
= load_user_database();
1850 log_error_errno(r
, "Failed to load user database: %m");
1854 r
= load_group_database();
1856 log_error_errno(r
, "Failed to read group database: %m");
1860 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
1863 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
1868 log_error_errno(r
, "Failed to write files: %m");
1871 ordered_hashmap_free_with_destructor(groups
, item_free
);
1872 ordered_hashmap_free_with_destructor(users
, item_free
);
1874 while ((n
= ordered_hashmap_first_key(members
))) {
1875 strv_free(ordered_hashmap_steal_first(members
));
1878 ordered_hashmap_free(members
);
1880 ordered_hashmap_free(todo_uids
);
1881 ordered_hashmap_free(todo_gids
);
1883 free_database(database_user
, database_uid
);
1884 free_database(database_group
, database_gid
);
1888 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;