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
;
403 if (ordered_hashmap_size(todo_uids
) == 0)
406 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
410 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
);
433 if (putpwent(pw
, passwd
) < 0)
434 return errno
? -errno
: -EIO
;
438 if (!IN_SET(errno
, 0, ENOENT
))
444 if (fchmod(fileno(passwd
), 0644) < 0)
448 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
453 .pw_gecos
= i
->description
,
455 /* "x" means the password is stored in the shadow file */
456 .pw_passwd
= (char*) "x",
458 /* We default to the root directory as home */
459 .pw_dir
= i
->home
? i
->home
: (char*) "/",
461 /* Initialize the shell to nologin, with one exception:
462 * for root we patch in something special */
463 .pw_shell
= i
->shell
?: (char*) default_shell(i
->uid
),
467 if (putpwent(&n
, passwd
) != 0)
468 return errno
? -errno
: -EIO
;
471 r
= fflush_and_check(passwd
);
476 *tmpfile_path
= passwd_tmp
;
482 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
483 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
484 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
490 if (ordered_hashmap_size(todo_uids
) == 0)
493 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
497 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
499 original
= fopen(shadow_path
, "re");
503 r
= sync_rights(original
, shadow
);
508 while ((sp
= fgetspent(original
))) {
510 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
511 if (i
&& i
->todo_user
) {
512 /* we will update the existing entry */
513 sp
->sp_lstchg
= lstchg
;
515 /* only the /etc/shadow stage is left, so we can
516 * safely remove the item from the todo set */
517 i
->todo_user
= false;
518 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
522 if (putspent(sp
, shadow
) < 0)
523 return errno
? -errno
: -EIO
;
527 if (!IN_SET(errno
, 0, ENOENT
))
533 if (fchmod(fileno(shadow
), 0000) < 0)
537 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
540 .sp_pwdp
= (char*) "!!",
547 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
551 if (putspent(&n
, shadow
) != 0)
552 return errno
? -errno
: -EIO
;
555 r
= fflush_sync_and_check(shadow
);
560 *tmpfile_path
= shadow_tmp
;
566 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
567 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
568 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
569 bool group_changed
= false;
574 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
577 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
581 original
= fopen(group_path
, "re");
585 r
= sync_rights(original
, group
);
590 while ((gr
= fgetgrent(original
))) {
591 /* Safety checks against name and GID collisions. Normally,
592 * this should be unnecessary, but given that we look at the
593 * entries anyway here, let's make an extra verification
594 * step that we don't generate duplicate entries. */
596 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
597 if (i
&& i
->todo_group
) {
598 log_error("%s: Group \"%s\" already exists.", group_path
, gr
->gr_name
);
602 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
))) {
603 log_error("%s: Detected collision for GID " GID_FMT
".", group_path
, gr
->gr_gid
);
607 r
= putgrent_with_members(gr
, group
);
611 group_changed
= true;
615 if (!IN_SET(errno
, 0, ENOENT
))
621 if (fchmod(fileno(group
), 0644) < 0)
625 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
629 .gr_passwd
= (char*) "x",
632 r
= putgrent_with_members(&n
, group
);
636 group_changed
= true;
639 r
= fflush_sync_and_check(group
);
645 *tmpfile_path
= group_tmp
;
652 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
654 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
655 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
656 bool group_changed
= false;
661 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
664 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
668 original
= fopen(gshadow_path
, "re");
672 r
= sync_rights(original
, gshadow
);
677 while ((sg
= fgetsgent(original
))) {
679 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
680 if (i
&& i
->todo_group
) {
681 log_error("%s: Group \"%s\" already exists.", gshadow_path
, sg
->sg_namp
);
685 r
= putsgent_with_members(sg
, gshadow
);
689 group_changed
= true;
693 if (!IN_SET(errno
, 0, ENOENT
))
699 if (fchmod(fileno(gshadow
), 0000) < 0)
703 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
706 .sg_passwd
= (char*) "!!",
709 r
= putsgent_with_members(&n
, gshadow
);
713 group_changed
= true;
716 r
= fflush_sync_and_check(gshadow
);
722 *tmpfile_path
= gshadow_tmp
;
732 static int write_files(void) {
733 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
734 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
735 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
738 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
739 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
740 group_path
= prefix_roota(arg_root
, "/etc/group");
741 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
743 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
747 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
751 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
755 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
759 /* Make a backup of the old files */
761 r
= make_backup("/etc/group", group_path
);
766 r
= make_backup("/etc/gshadow", gshadow_path
);
772 r
= make_backup("/etc/passwd", passwd_path
);
777 r
= make_backup("/etc/shadow", shadow_path
);
782 /* And make the new files count */
784 r
= rename_and_apply_smack(group_tmp
, group_path
);
788 group_tmp
= mfree(group_tmp
);
791 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
795 gshadow_tmp
= mfree(gshadow_tmp
);
799 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
803 passwd_tmp
= mfree(passwd_tmp
);
806 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
810 shadow_tmp
= mfree(shadow_tmp
);
816 static int uid_is_ok(uid_t uid
, const char *name
, bool check_with_gid
) {
822 /* Let's see if we already have assigned the UID a second time */
823 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
826 /* Try to avoid using uids that are already used by a group
827 * that doesn't have the same name as our new user. */
828 if (check_with_gid
) {
829 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
830 if (i
&& !streq(i
->name
, name
))
834 /* Let's check the files directly */
835 if (hashmap_contains(database_uid
, UID_TO_PTR(uid
)))
838 if (check_with_gid
) {
839 n
= hashmap_get(database_gid
, GID_TO_PTR(uid
));
840 if (n
&& !streq(n
, name
))
844 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
850 if (!IN_SET(errno
, 0, ENOENT
))
853 if (check_with_gid
) {
855 g
= getgrgid((gid_t
) uid
);
857 if (!streq(g
->gr_name
, name
))
859 } else if (!IN_SET(errno
, 0, ENOENT
))
867 static int root_stat(const char *p
, struct stat
*st
) {
870 fix
= prefix_roota(arg_root
, p
);
871 if (stat(fix
, st
) < 0)
877 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
879 bool found_uid
= false, found_gid
= false;
885 /* First, try to get the gid directly */
886 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
891 /* Then, try to get the uid directly */
892 if ((_uid
|| (_gid
&& !found_gid
))
894 && root_stat(i
->uid_path
, &st
) >= 0) {
899 /* If we need the gid, but had no success yet, also derive it from the uid path */
900 if (_gid
&& !found_gid
) {
906 /* If that didn't work yet, then let's reuse the gid as uid */
907 if (_uid
&& !found_uid
&& i
->gid_path
) {
912 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
913 uid
= (uid_t
) st
.st_gid
;
935 static int add_user(Item
*i
) {
941 /* Check the database directly */
942 z
= hashmap_get(database_user
, i
->name
);
944 log_debug("User %s already exists.", i
->name
);
945 i
->uid
= PTR_TO_UID(z
);
955 p
= getpwnam(i
->name
);
957 log_debug("User %s already exists.", i
->name
);
961 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
967 if (!IN_SET(errno
, 0, ENOENT
))
968 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
971 /* Try to use the suggested numeric uid */
973 r
= uid_is_ok(i
->uid
, i
->name
, !i
->id_set_strict
);
975 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
977 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
982 /* If that didn't work, try to read it from the specified path */
986 if (read_id_from_file(i
, &c
, NULL
) > 0) {
988 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
989 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
991 r
= uid_is_ok(c
, i
->name
, true);
993 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
998 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
1003 /* Otherwise, try to reuse the group ID */
1004 if (!i
->uid_set
&& i
->gid_set
) {
1005 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
, true);
1007 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1009 i
->uid
= (uid_t
) i
->gid
;
1014 /* And if that didn't work either, let's try to find a free one */
1017 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1019 log_error("No free user ID available for %s.", i
->name
);
1023 r
= uid_is_ok(search_uid
, i
->name
, true);
1025 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1031 i
->uid
= search_uid
;
1034 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1038 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1042 i
->todo_user
= true;
1043 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1048 static int gid_is_ok(gid_t gid
) {
1052 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1055 /* Avoid reusing gids that are already used by a different user */
1056 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1059 if (hashmap_contains(database_gid
, GID_TO_PTR(gid
)))
1062 if (hashmap_contains(database_uid
, UID_TO_PTR(gid
)))
1070 if (!IN_SET(errno
, 0, ENOENT
))
1074 p
= getpwuid((uid_t
) gid
);
1077 if (!IN_SET(errno
, 0, ENOENT
))
1084 static int add_group(Item
*i
) {
1090 /* Check the database directly */
1091 z
= hashmap_get(database_group
, i
->name
);
1093 log_debug("Group %s already exists.", i
->name
);
1094 i
->gid
= PTR_TO_GID(z
);
1099 /* Also check NSS */
1104 g
= getgrnam(i
->name
);
1106 log_debug("Group %s already exists.", i
->name
);
1111 if (!IN_SET(errno
, 0, ENOENT
))
1112 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", i
->name
);
1115 /* Try to use the suggested numeric gid */
1117 r
= gid_is_ok(i
->gid
);
1119 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1120 if (i
->id_set_strict
) {
1121 /* If we require the gid to already exist we can return here:
1122 * r > 0: means the gid does not exist -> fail
1123 * r == 0: means the gid exists -> nothing more to do.
1126 log_error("Failed to create %s: please create GID %d", i
->name
, i
->gid
);
1133 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1138 /* Try to reuse the numeric uid, if there's one */
1139 if (!i
->gid_set
&& i
->uid_set
) {
1140 r
= gid_is_ok((gid_t
) i
->uid
);
1142 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1144 i
->gid
= (gid_t
) i
->uid
;
1149 /* If that didn't work, try to read it from the specified path */
1153 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1155 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1156 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1160 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1165 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1170 /* And if that didn't work either, let's try to find a free one */
1173 /* We look for new GIDs in the UID pool! */
1174 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1176 log_error("No free group ID available for %s.", i
->name
);
1180 r
= gid_is_ok(search_uid
);
1182 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1188 i
->gid
= search_uid
;
1191 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1195 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1199 i
->todo_group
= true;
1200 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1205 static int process_item(Item
*i
) {
1215 j
= ordered_hashmap_get(groups
, i
->name
);
1216 if (j
&& j
->todo_group
) {
1217 /* When the group with the same name is already in queue,
1218 * use the information about the group and do not create
1219 * duplicated group entry. */
1220 i
->gid_set
= j
->gid_set
;
1222 i
->id_set_strict
= true;
1233 return add_group(i
);
1236 assert_not_reached("Unknown item type");
1240 static void item_free(Item
*i
) {
1248 free(i
->description
);
1254 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1256 static int add_implicit(void) {
1261 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1262 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1266 if (!ordered_hashmap_get(users
, *m
)) {
1267 _cleanup_(item_freep
) Item
*j
= NULL
;
1269 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1278 j
->name
= strdup(*m
);
1282 r
= ordered_hashmap_put(users
, j
->name
, j
);
1286 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1290 if (!(ordered_hashmap_get(users
, g
) ||
1291 ordered_hashmap_get(groups
, g
))) {
1292 _cleanup_(item_freep
) Item
*j
= NULL
;
1294 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1302 j
->type
= ADD_GROUP
;
1303 j
->name
= strdup(g
);
1307 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1311 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1319 static bool item_equal(Item
*a
, Item
*b
) {
1323 if (a
->type
!= b
->type
)
1326 if (!streq_ptr(a
->name
, b
->name
))
1329 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1332 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1335 if (!streq_ptr(a
->description
, b
->description
))
1338 if (a
->uid_set
!= b
->uid_set
)
1341 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1344 if (a
->gid_set
!= b
->gid_set
)
1347 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1350 if (!streq_ptr(a
->home
, b
->home
))
1353 if (!streq_ptr(a
->shell
, b
->shell
))
1359 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1361 static const Specifier specifier_table
[] = {
1362 { 'm', specifier_machine_id
, NULL
},
1363 { 'b', specifier_boot_id
, NULL
},
1364 { 'H', specifier_host_name
, NULL
},
1365 { 'v', specifier_kernel_release
, NULL
},
1369 _cleanup_free_
char *action
= NULL
,
1370 *name
= NULL
, *resolved_name
= NULL
,
1371 *id
= NULL
, *resolved_id
= NULL
,
1372 *description
= NULL
,
1374 *shell
, *resolved_shell
= NULL
;
1375 _cleanup_(item_freep
) Item
*i
= NULL
;
1387 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
,
1388 &action
, &name
, &id
, &description
, &home
, &shell
, NULL
);
1390 log_error("[%s:%u] Syntax error.", fname
, line
);
1394 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1398 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1403 if (strlen(action
) != 1) {
1404 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1408 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1409 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1414 if (isempty(name
) || streq(name
, "-"))
1418 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1420 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1424 if (!valid_user_group_name(resolved_name
)) {
1425 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1431 if (isempty(id
) || streq(id
, "-"))
1435 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1437 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1442 /* Verify description */
1443 if (isempty(description
) || streq(description
, "-"))
1444 description
= mfree(description
);
1447 if (!valid_gecos(description
)) {
1448 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, description
);
1454 if (isempty(home
) || streq(home
, "-"))
1458 if (!valid_home(home
)) {
1459 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, home
);
1465 if (isempty(shell
) || streq(shell
, "-"))
1466 shell
= mfree(shell
);
1469 r
= specifier_printf(shell
, specifier_table
, NULL
, &resolved_shell
);
1471 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, shell
);
1475 if (!valid_shell(resolved_shell
)) {
1476 log_error("[%s:%u] '%s' is not a valid login shell field.", fname
, line
, resolved_shell
);
1482 switch (action
[0]) {
1485 if (resolved_name
) {
1486 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1491 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1495 if (description
|| home
|| shell
) {
1496 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1497 fname
, line
, action
[0],
1498 description
? "GECOS" : home
? "home directory" : "login shell");
1502 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1504 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1513 /* Try to extend an existing member or group item */
1515 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1520 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1524 if (!valid_user_group_name(resolved_id
)) {
1525 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1529 if (description
|| home
|| shell
) {
1530 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1531 fname
, line
, action
[0],
1532 description
? "GECOS" : home
? "home directory" : "login shell");
1536 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1540 l
= ordered_hashmap_get(members
, resolved_id
);
1542 /* A list for this group name already exists, let's append to it */
1543 r
= strv_push(&l
, resolved_name
);
1547 resolved_name
= NULL
;
1549 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1551 /* No list for this group name exists yet, create one */
1553 l
= new0(char *, 2);
1557 l
[0] = resolved_name
;
1560 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1566 resolved_id
= resolved_name
= NULL
;
1574 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1578 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1587 if (path_is_absolute(resolved_id
)) {
1588 i
->uid_path
= resolved_id
;
1591 path_kill_slashes(i
->uid_path
);
1593 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1594 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1595 r
= parse_gid(gid
, &i
->gid
);
1597 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1599 i
->id_set_strict
= true;
1600 free_and_replace(resolved_id
, uid
);
1602 if (!streq(resolved_id
, "-")) {
1603 r
= parse_uid(resolved_id
, &i
->uid
);
1605 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1611 i
->description
= description
;
1617 i
->shell
= resolved_shell
;
1618 resolved_shell
= NULL
;
1625 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1629 if (description
|| home
|| shell
) {
1630 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1631 fname
, line
, action
[0],
1632 description
? "GECOS" : home
? "home directory" : "login shell");
1636 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1645 if (path_is_absolute(resolved_id
)) {
1646 i
->gid_path
= resolved_id
;
1649 path_kill_slashes(i
->gid_path
);
1651 r
= parse_gid(resolved_id
, &i
->gid
);
1653 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1666 i
->type
= action
[0];
1667 i
->name
= resolved_name
;
1668 resolved_name
= NULL
;
1670 existing
= ordered_hashmap_get(h
, i
->name
);
1673 /* Two identical items are fine */
1674 if (!item_equal(existing
, i
))
1675 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1680 r
= ordered_hashmap_put(h
, i
->name
, i
);
1688 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1689 _cleanup_fclose_
FILE *rf
= NULL
;
1691 char line
[LINE_MAX
];
1700 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1702 if (ignore_enoent
&& r
== -ENOENT
)
1705 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1711 FOREACH_LINE(line
, f
, break) {
1718 if (IN_SET(*l
, 0, '#'))
1721 k
= parse_line(fn
, v
, l
);
1722 if (k
< 0 && r
== 0)
1727 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1735 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1739 name
= hashmap_first(by_id
);
1743 hashmap_remove(by_name
, name
);
1745 hashmap_steal_first_key(by_id
);
1749 while ((name
= hashmap_steal_first_key(by_name
)))
1752 hashmap_free(by_name
);
1753 hashmap_free(by_id
);
1756 static void help(void) {
1757 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1758 "Creates system user accounts.\n\n"
1759 " -h --help Show this help\n"
1760 " --version Show package version\n"
1761 " --root=PATH Operate on an alternate filesystem root\n"
1762 " --replace=PATH Treat arguments as replacement for PATH\n"
1763 " --inline Treat arguments as configuration lines\n"
1764 , program_invocation_short_name
);
1767 static int parse_argv(int argc
, char *argv
[]) {
1770 ARG_VERSION
= 0x100,
1776 static const struct option options
[] = {
1777 { "help", no_argument
, NULL
, 'h' },
1778 { "version", no_argument
, NULL
, ARG_VERSION
},
1779 { "root", required_argument
, NULL
, ARG_ROOT
},
1780 { "replace", required_argument
, NULL
, ARG_REPLACE
},
1781 { "inline", no_argument
, NULL
, ARG_INLINE
},
1790 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1802 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1808 if (!path_is_absolute(optarg
) ||
1809 !endswith(optarg
, ".conf")) {
1810 log_error("The argument to --replace= must an absolute path to a config file");
1814 arg_replace
= optarg
;
1825 assert_not_reached("Unhandled option");
1828 if (arg_replace
&& optind
>= argc
) {
1829 log_error("When --replace= is given, some configuration items must be specified");
1836 static int parse_arguments(char **args
) {
1841 STRV_FOREACH(arg
, args
) {
1843 /* Use (argument):n, where n==1 for the first positional arg */
1844 r
= parse_line("(argument)", pos
, *arg
);
1846 r
= read_config_file(*arg
, false);
1856 static int read_config_files(const char* dirs
, char **args
) {
1857 _cleanup_strv_free_
char **files
= NULL
;
1858 _cleanup_free_
char *p
= NULL
;
1862 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, 0, dirs
);
1864 return log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1867 r
= conf_files_insert_nulstr(&files
, arg_root
, dirs
, arg_replace
);
1869 return log_error_errno(r
, "Failed to extend sysusers.d file list: %m");
1871 p
= path_join(arg_root
, arg_replace
, NULL
);
1876 STRV_FOREACH(f
, files
)
1877 if (p
&& path_equal(*f
, p
)) {
1878 log_debug("Parsing arguments at position \"%s\"…", *f
);
1880 r
= parse_arguments(args
);
1884 log_debug("Reading config file \"%s\"…", *f
);
1886 /* Just warn, ignore result otherwise */
1887 (void) read_config_file(*f
, true);
1893 int main(int argc
, char *argv
[]) {
1895 _cleanup_close_
int lock
= -1;
1901 r
= parse_argv(argc
, argv
);
1905 log_set_target(LOG_TARGET_AUTO
);
1906 log_parse_environment();
1911 r
= mac_selinux_init();
1913 log_error_errno(r
, "SELinux setup failed: %m");
1917 /* If command line arguments are specified along with --replace, read all
1918 * configuration files and insert the positional arguments at the specified
1919 * place. Otherwise, if command line arguments are specified, execute just
1920 * them, and finally, without --replace= or any positional arguments, just
1921 * read configuration and execute it.
1923 if (arg_replace
|| optind
>= argc
)
1924 r
= read_config_files(conf_file_dirs
, argv
+ optind
);
1926 r
= parse_arguments(argv
+ optind
);
1930 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1931 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1932 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1933 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1935 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1936 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1941 /* Default to default range of 1..SYSTEM_UID_MAX */
1942 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1953 lock
= take_etc_passwd_lock(arg_root
);
1955 log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
1959 r
= load_user_database();
1961 log_error_errno(r
, "Failed to load user database: %m");
1965 r
= load_group_database();
1967 log_error_errno(r
, "Failed to read group database: %m");
1971 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
1974 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
1979 log_error_errno(r
, "Failed to write files: %m");
1982 ordered_hashmap_free_with_destructor(groups
, item_free
);
1983 ordered_hashmap_free_with_destructor(users
, item_free
);
1985 while ((n
= ordered_hashmap_first_key(members
))) {
1986 strv_free(ordered_hashmap_steal_first(members
));
1989 ordered_hashmap_free(members
);
1991 ordered_hashmap_free(todo_uids
);
1992 ordered_hashmap_free(todo_gids
);
1994 free_database(database_user
, database_uid
);
1995 free_database(database_group
, database_gid
);
1999 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;