2 This file is part of systemd.
4 Copyright 2014 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include "alloc-util.h"
28 #include "conf-files.h"
33 #include "fileio-label.h"
34 #include "format-util.h"
36 #include "path-util.h"
37 #include "selinux-util.h"
38 #include "smack-util.h"
39 #include "specifier.h"
40 #include "string-util.h"
42 #include "uid-range.h"
43 #include "user-util.h"
47 typedef enum ItemType
{
72 static char *arg_root
= NULL
;
74 static const char conf_file_dirs
[] = CONF_PATHS_NULSTR("sysusers.d");
76 static Hashmap
*users
= NULL
, *groups
= NULL
;
77 static Hashmap
*todo_uids
= NULL
, *todo_gids
= NULL
;
78 static Hashmap
*members
= NULL
;
80 static Hashmap
*database_uid
= NULL
, *database_user
= NULL
;
81 static Hashmap
*database_gid
= NULL
, *database_group
= NULL
;
83 static uid_t search_uid
= UID_INVALID
;
84 static UidRange
*uid_range
= NULL
;
85 static unsigned n_uid_range
= 0;
87 static int load_user_database(void) {
88 _cleanup_fclose_
FILE *f
= NULL
;
89 const char *passwd_path
;
93 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
94 f
= fopen(passwd_path
, "re");
96 return errno
== ENOENT
? 0 : -errno
;
98 r
= hashmap_ensure_allocated(&database_user
, &string_hash_ops
);
102 r
= hashmap_ensure_allocated(&database_uid
, NULL
);
107 while ((pw
= fgetpwent(f
))) {
111 n
= strdup(pw
->pw_name
);
115 k
= hashmap_put(database_user
, n
, UID_TO_PTR(pw
->pw_uid
));
116 if (k
< 0 && k
!= -EEXIST
) {
121 q
= hashmap_put(database_uid
, UID_TO_PTR(pw
->pw_uid
), n
);
122 if (q
< 0 && q
!= -EEXIST
) {
133 if (!IN_SET(errno
, 0, ENOENT
))
139 static int load_group_database(void) {
140 _cleanup_fclose_
FILE *f
= NULL
;
141 const char *group_path
;
145 group_path
= prefix_roota(arg_root
, "/etc/group");
146 f
= fopen(group_path
, "re");
148 return errno
== ENOENT
? 0 : -errno
;
150 r
= hashmap_ensure_allocated(&database_group
, &string_hash_ops
);
154 r
= hashmap_ensure_allocated(&database_gid
, NULL
);
159 while ((gr
= fgetgrent(f
))) {
163 n
= strdup(gr
->gr_name
);
167 k
= hashmap_put(database_group
, n
, GID_TO_PTR(gr
->gr_gid
));
168 if (k
< 0 && k
!= -EEXIST
) {
173 q
= hashmap_put(database_gid
, GID_TO_PTR(gr
->gr_gid
), n
);
174 if (q
< 0 && q
!= -EEXIST
) {
185 if (!IN_SET(errno
, 0, ENOENT
))
191 static int make_backup(const char *target
, const char *x
) {
192 _cleanup_close_
int src
= -1;
193 _cleanup_fclose_
FILE *dst
= NULL
;
194 _cleanup_free_
char *temp
= NULL
;
196 struct timespec ts
[2];
200 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
202 if (errno
== ENOENT
) /* No backup necessary... */
208 if (fstat(src
, &st
) < 0)
211 r
= fopen_temporary_label(target
, x
, &dst
, &temp
);
215 r
= copy_bytes(src
, fileno(dst
), (uint64_t) -1, COPY_REFLINK
);
219 /* Don't fail on chmod() or chown(). If it stays owned by us
220 * and/or unreadable by others, then it isn't too bad... */
222 backup
= strjoina(x
, "-");
224 /* Copy over the access mask */
225 if (fchmod(fileno(dst
), st
.st_mode
& 07777) < 0)
226 log_warning_errno(errno
, "Failed to change mode on %s: %m", backup
);
228 if (fchown(fileno(dst
), st
.st_uid
, st
.st_gid
)< 0)
229 log_warning_errno(errno
, "Failed to change ownership of %s: %m", backup
);
233 if (futimens(fileno(dst
), ts
) < 0)
234 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
236 r
= fflush_sync_and_check(dst
);
240 if (rename(temp
, backup
) < 0) {
252 static int putgrent_with_members(const struct group
*gr
, FILE *group
) {
258 a
= hashmap_get(members
, gr
->gr_name
);
260 _cleanup_strv_free_
char **l
= NULL
;
264 l
= strv_copy(gr
->gr_mem
);
269 if (strv_find(l
, *i
))
272 if (strv_extend(&l
, *i
) < 0)
288 if (putgrent(&t
, group
) != 0)
289 return errno
> 0 ? -errno
: -EIO
;
296 if (putgrent(gr
, group
) != 0)
297 return errno
> 0 ? -errno
: -EIO
;
302 #ifdef ENABLE_GSHADOW
303 static int putsgent_with_members(const struct sgrp
*sg
, FILE *gshadow
) {
309 a
= hashmap_get(members
, sg
->sg_namp
);
311 _cleanup_strv_free_
char **l
= NULL
;
315 l
= strv_copy(sg
->sg_mem
);
320 if (strv_find(l
, *i
))
323 if (strv_extend(&l
, *i
) < 0)
339 if (putsgent(&t
, gshadow
) != 0)
340 return errno
> 0 ? -errno
: -EIO
;
347 if (putsgent(sg
, gshadow
) != 0)
348 return errno
> 0 ? -errno
: -EIO
;
354 static int sync_rights(FILE *from
, FILE *to
) {
357 if (fstat(fileno(from
), &st
) < 0)
360 if (fchmod(fileno(to
), st
.st_mode
& 07777) < 0)
363 if (fchown(fileno(to
), st
.st_uid
, st
.st_gid
) < 0)
369 static int rename_and_apply_smack(const char *temp_path
, const char *dest_path
) {
371 if (rename(temp_path
, dest_path
) < 0)
374 #ifdef SMACK_RUN_LABEL
375 r
= mac_smack_apply(dest_path
, SMACK_ATTR_ACCESS
, SMACK_FLOOR_LABEL
);
382 static int write_temporary_passwd(const char *passwd_path
, FILE **tmpfile
, char **tmpfile_path
) {
383 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
384 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
389 if (hashmap_size(todo_uids
) == 0)
392 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
396 original
= fopen(passwd_path
, "re");
400 r
= sync_rights(original
, passwd
);
405 while ((pw
= fgetpwent(original
))) {
407 i
= hashmap_get(users
, pw
->pw_name
);
408 if (i
&& i
->todo_user
) {
409 log_error("%s: User \"%s\" already exists.", passwd_path
, pw
->pw_name
);
413 if (hashmap_contains(todo_uids
, UID_TO_PTR(pw
->pw_uid
))) {
414 log_error("%s: Detected collision for UID " UID_FMT
".", passwd_path
, pw
->pw_uid
);
419 if (putpwent(pw
, passwd
) < 0)
420 return errno
? -errno
: -EIO
;
424 if (!IN_SET(errno
, 0, ENOENT
))
430 if (fchmod(fileno(passwd
), 0644) < 0)
434 HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
439 .pw_gecos
= i
->description
,
441 /* "x" means the password is stored in the shadow file */
442 .pw_passwd
= (char*) "x",
444 /* We default to the root directory as home */
445 .pw_dir
= i
->home
? i
->home
: (char*) "/",
447 /* Initialize the shell to nologin, with one exception:
448 * for root we patch in something special */
449 .pw_shell
= i
->uid
== 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
453 if (putpwent(&n
, passwd
) != 0)
454 return errno
? -errno
: -EIO
;
457 r
= fflush_and_check(passwd
);
462 *tmpfile_path
= passwd_tmp
;
468 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
469 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
470 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
476 if (hashmap_size(todo_uids
) == 0)
479 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
483 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
485 original
= fopen(shadow_path
, "re");
489 r
= sync_rights(original
, shadow
);
494 while ((sp
= fgetspent(original
))) {
496 i
= hashmap_get(users
, sp
->sp_namp
);
497 if (i
&& i
->todo_user
) {
498 /* we will update the existing entry */
499 sp
->sp_lstchg
= lstchg
;
501 /* only the /etc/shadow stage is left, so we can
502 * safely remove the item from the todo set */
503 i
->todo_user
= false;
504 hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
508 if (putspent(sp
, shadow
) < 0)
509 return errno
? -errno
: -EIO
;
513 if (!IN_SET(errno
, 0, ENOENT
))
519 if (fchmod(fileno(shadow
), 0000) < 0)
523 HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
526 .sp_pwdp
= (char*) "!!",
533 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
537 if (putspent(&n
, shadow
) != 0)
538 return errno
? -errno
: -EIO
;
541 r
= fflush_sync_and_check(shadow
);
546 *tmpfile_path
= shadow_tmp
;
552 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
553 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
554 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
555 bool group_changed
= false;
560 if (hashmap_size(todo_gids
) == 0 && hashmap_size(members
) == 0)
563 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
567 original
= fopen(group_path
, "re");
571 r
= sync_rights(original
, group
);
576 while ((gr
= fgetgrent(original
))) {
577 /* Safety checks against name and GID collisions. Normally,
578 * this should be unnecessary, but given that we look at the
579 * entries anyway here, let's make an extra verification
580 * step that we don't generate duplicate entries. */
582 i
= hashmap_get(groups
, gr
->gr_name
);
583 if (i
&& i
->todo_group
) {
584 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
588 if (hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
589 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
593 r
= putgrent_with_members(gr
, group
);
597 group_changed
= true;
601 if (!IN_SET(errno
, 0, ENOENT
))
607 if (fchmod(fileno(group
), 0644) < 0)
611 HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
615 .gr_passwd
= (char*) "x",
618 r
= putgrent_with_members(&n
, group
);
622 group_changed
= true;
625 r
= fflush_sync_and_check(group
);
631 *tmpfile_path
= group_tmp
;
638 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
639 #ifdef ENABLE_GSHADOW
640 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
641 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
642 bool group_changed
= false;
647 if (hashmap_size(todo_gids
) == 0 && hashmap_size(members
) == 0)
650 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
654 original
= fopen(gshadow_path
, "re");
658 r
= sync_rights(original
, gshadow
);
663 while ((sg
= fgetsgent(original
))) {
665 i
= hashmap_get(groups
, sg
->sg_namp
);
666 if (i
&& i
->todo_group
) {
667 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
671 r
= putsgent_with_members(sg
, gshadow
);
675 group_changed
= true;
679 if (!IN_SET(errno
, 0, ENOENT
))
685 if (fchmod(fileno(gshadow
), 0000) < 0)
689 HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
692 .sg_passwd
= (char*) "!!",
695 r
= putsgent_with_members(&n
, gshadow
);
699 group_changed
= true;
702 r
= fflush_sync_and_check(gshadow
);
708 *tmpfile_path
= gshadow_tmp
;
718 static int write_files(void) {
719 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
720 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
721 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
724 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
725 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
726 group_path
= prefix_roota(arg_root
, "/etc/group");
727 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
729 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
733 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
737 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
741 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
745 /* Make a backup of the old files */
747 r
= make_backup("/etc/group", group_path
);
752 r
= make_backup("/etc/gshadow", gshadow_path
);
758 r
= make_backup("/etc/passwd", passwd_path
);
763 r
= make_backup("/etc/shadow", shadow_path
);
768 /* And make the new files count */
770 r
= rename_and_apply_smack(group_tmp
, group_path
);
774 group_tmp
= mfree(group_tmp
);
777 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
781 gshadow_tmp
= mfree(gshadow_tmp
);
785 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
789 passwd_tmp
= mfree(passwd_tmp
);
792 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
796 shadow_tmp
= mfree(shadow_tmp
);
802 static int uid_is_ok(uid_t uid
, const char *name
) {
808 /* Let's see if we already have assigned the UID a second time */
809 if (hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
812 /* Try to avoid using uids that are already used by a group
813 * that doesn't have the same name as our new user. */
814 i
= hashmap_get(todo_gids
, GID_TO_PTR(uid
));
815 if (i
&& !streq(i
->name
, name
))
818 /* Let's check the files directly */
819 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
822 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
823 if (n
&& !streq(n
, name
))
826 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
832 if (!IN_SET(errno
, 0, ENOENT
))
836 g
= getgrgid((gid_t
) uid
);
838 if (!streq(g
->gr_name
, name
))
840 } else if (!IN_SET(errno
, 0, ENOENT
))
847 static int root_stat(const char *p
, struct stat
*st
) {
850 fix
= prefix_roota(arg_root
, p
);
851 if (stat(fix
, st
) < 0)
857 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
859 bool found_uid
= false, found_gid
= false;
865 /* First, try to get the gid directly */
866 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
871 /* Then, try to get the uid directly */
872 if ((_uid
|| (_gid
&& !found_gid
))
874 && root_stat(i
->uid_path
, &st
) >= 0) {
879 /* If we need the gid, but had no success yet, also derive it from the uid path */
880 if (_gid
&& !found_gid
) {
886 /* If that didn't work yet, then let's reuse the gid as uid */
887 if (_uid
&& !found_uid
&& i
->gid_path
) {
892 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
893 uid
= (uid_t
) st
.st_gid
;
915 static int add_user(Item
*i
) {
921 /* Check the database directly */
922 z
= hashmap_get(database_user
, i
->name
);
924 log_debug("User %s already exists.", i
->name
);
925 i
->uid
= PTR_TO_UID(z
);
935 p
= getpwnam(i
->name
);
937 log_debug("User %s already exists.", i
->name
);
941 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
947 if (!IN_SET(errno
, 0, ENOENT
))
948 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
951 /* Try to use the suggested numeric uid */
953 r
= uid_is_ok(i
->uid
, i
->name
);
955 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
957 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
962 /* If that didn't work, try to read it from the specified path */
966 if (read_id_from_file(i
, &c
, NULL
) > 0) {
968 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
969 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
971 r
= uid_is_ok(c
, i
->name
);
973 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
978 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
983 /* Otherwise, try to reuse the group ID */
984 if (!i
->uid_set
&& i
->gid_set
) {
985 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
);
987 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
989 i
->uid
= (uid_t
) i
->gid
;
994 /* And if that didn't work either, let's try to find a free one */
997 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
999 log_error("No free user ID available for %s.", i
->name
);
1003 r
= uid_is_ok(search_uid
, i
->name
);
1005 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1011 i
->uid
= search_uid
;
1014 r
= hashmap_ensure_allocated(&todo_uids
, NULL
);
1018 r
= hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1022 i
->todo_user
= true;
1023 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1028 static int gid_is_ok(gid_t gid
) {
1032 if (hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1035 /* Avoid reusing gids that are already used by a different user */
1036 if (hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1039 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1042 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1050 if (!IN_SET(errno
, 0, ENOENT
))
1054 p
= getpwuid((uid_t
) gid
);
1057 if (!IN_SET(errno
, 0, ENOENT
))
1064 static int add_group(Item
*i
) {
1070 /* Check the database directly */
1071 z
= hashmap_get(database_group
, i
->name
);
1073 log_debug("Group %s already exists.", i
->name
);
1074 i
->gid
= PTR_TO_GID(z
);
1079 /* Also check NSS */
1084 g
= getgrnam(i
->name
);
1086 log_debug("Group %s already exists.", i
->name
);
1091 if (!IN_SET(errno
, 0, ENOENT
))
1092 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1095 /* Try to use the suggested numeric gid */
1097 r
= gid_is_ok(i
->gid
);
1099 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1101 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1106 /* Try to reuse the numeric uid, if there's one */
1107 if (!i
->gid_set
&& i
->uid_set
) {
1108 r
= gid_is_ok((gid_t
) i
->uid
);
1110 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1112 i
->gid
= (gid_t
) i
->uid
;
1117 /* If that didn't work, try to read it from the specified path */
1121 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1123 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1124 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1128 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1133 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1138 /* And if that didn't work either, let's try to find a free one */
1141 /* We look for new GIDs in the UID pool! */
1142 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1144 log_error("No free group ID available for %s.", i
->name
);
1148 r
= gid_is_ok(search_uid
);
1150 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1156 i
->gid
= search_uid
;
1159 r
= hashmap_ensure_allocated(&todo_gids
, NULL
);
1163 r
= hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1167 i
->todo_group
= true;
1168 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1173 static int process_item(Item
*i
) {
1190 j
= hashmap_get(users
, i
->name
);
1192 /* There's already user to be created for this
1193 * name, let's process that in one step */
1201 r
= free_and_strdup(&j
->gid_path
, i
->gid_path
);
1209 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 HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1243 i
= hashmap_get(groups
, g
);
1245 _cleanup_(item_freep
) Item
*j
= NULL
;
1247 r
= hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1255 j
->type
= ADD_GROUP
;
1256 j
->name
= strdup(g
);
1260 r
= 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
= hashmap_get(users
, *m
);
1272 _cleanup_(item_freep
) Item
*j
= NULL
;
1274 r
= hashmap_ensure_allocated(&users
, &string_hash_ops
);
1283 j
->name
= strdup(*m
);
1287 r
= 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
= hashmap_ensure_allocated(&members
, &string_hash_ops
);
1500 l
= 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(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
= 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
= 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 r
= parse_uid(resolved_id
, &i
->uid
);
1555 log_error("Failed to parse UID: %s", id
);
1563 i
->description
= description
;
1574 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1579 log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname
, line
);
1584 log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname
, line
);
1588 r
= hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1597 if (path_is_absolute(resolved_id
)) {
1598 i
->gid_path
= resolved_id
;
1601 path_kill_slashes(i
->gid_path
);
1603 r
= parse_gid(resolved_id
, &i
->gid
);
1605 log_error("Failed to parse GID: %s", id
);
1620 i
->type
= action
[0];
1621 i
->name
= resolved_name
;
1622 resolved_name
= NULL
;
1624 existing
= hashmap_get(h
, i
->name
);
1627 /* Two identical items are fine */
1628 if (!item_equal(existing
, i
))
1629 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1634 r
= hashmap_put(h
, i
->name
, i
);
1642 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1643 _cleanup_fclose_
FILE *rf
= NULL
;
1645 char line
[LINE_MAX
];
1654 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1656 if (ignore_enoent
&& r
== -ENOENT
)
1659 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1665 FOREACH_LINE(line
, f
, break) {
1672 if (*l
== '#' || *l
== 0)
1675 k
= parse_line(fn
, v
, l
);
1676 if (k
< 0 && r
== 0)
1681 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1689 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1693 name
= hashmap_first(by_id
);
1697 hashmap_remove(by_name
, name
);
1699 hashmap_steal_first_key(by_id
);
1703 while ((name
= hashmap_steal_first_key(by_name
)))
1706 hashmap_free(by_name
);
1707 hashmap_free(by_id
);
1710 static void help(void) {
1711 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1712 "Creates system user accounts.\n\n"
1713 " -h --help Show this help\n"
1714 " --version Show package version\n"
1715 " --root=PATH Operate on an alternate filesystem root\n"
1716 , program_invocation_short_name
);
1719 static int parse_argv(int argc
, char *argv
[]) {
1722 ARG_VERSION
= 0x100,
1726 static const struct option options
[] = {
1727 { "help", no_argument
, NULL
, 'h' },
1728 { "version", no_argument
, NULL
, ARG_VERSION
},
1729 { "root", required_argument
, NULL
, ARG_ROOT
},
1738 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1750 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1759 assert_not_reached("Unhandled option");
1765 int main(int argc
, char *argv
[]) {
1767 _cleanup_close_
int lock
= -1;
1773 r
= parse_argv(argc
, argv
);
1777 log_set_target(LOG_TARGET_AUTO
);
1778 log_parse_environment();
1783 r
= mac_selinux_init();
1785 log_error_errno(r
, "SELinux setup failed: %m");
1789 if (optind
< argc
) {
1792 for (j
= optind
; j
< argc
; j
++) {
1793 k
= read_config_file(argv
[j
], false);
1794 if (k
< 0 && r
== 0)
1798 _cleanup_strv_free_
char **files
= NULL
;
1801 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, conf_file_dirs
);
1803 log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1807 STRV_FOREACH(f
, files
) {
1808 k
= read_config_file(*f
, true);
1809 if (k
< 0 && r
== 0)
1815 /* Default to default range of 1..SYSTEMD_UID_MAX */
1816 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1827 lock
= take_etc_passwd_lock(arg_root
);
1829 log_error_errno(lock
, "Failed to take lock: %m");
1833 r
= load_user_database();
1835 log_error_errno(r
, "Failed to load user database: %m");
1839 r
= load_group_database();
1841 log_error_errno(r
, "Failed to read group database: %m");
1845 HASHMAP_FOREACH(i
, groups
, iterator
)
1848 HASHMAP_FOREACH(i
, users
, iterator
)
1853 log_error_errno(r
, "Failed to write files: %m");
1856 while ((i
= hashmap_steal_first(groups
)))
1859 while ((i
= hashmap_steal_first(users
)))
1862 while ((n
= hashmap_first_key(members
))) {
1863 strv_free(hashmap_steal_first(members
));
1867 hashmap_free(groups
);
1868 hashmap_free(users
);
1869 hashmap_free(members
);
1870 hashmap_free(todo_uids
);
1871 hashmap_free(todo_gids
);
1873 free_database(database_user
, database_uid
);
1874 free_database(database_group
, database_gid
);
1878 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;