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
) {
1220 return add_group(i
);
1223 assert_not_reached("Unknown item type");
1227 static void item_free(Item
*i
) {
1235 free(i
->description
);
1241 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1243 static int add_implicit(void) {
1248 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1250 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1254 i
= ordered_hashmap_get(groups
, g
);
1256 _cleanup_(item_freep
) Item
*j
= NULL
;
1258 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1266 j
->type
= ADD_GROUP
;
1267 j
->name
= strdup(g
);
1271 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1275 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1279 STRV_FOREACH(m
, l
) {
1281 i
= ordered_hashmap_get(users
, *m
);
1283 _cleanup_(item_freep
) Item
*j
= NULL
;
1285 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1294 j
->name
= strdup(*m
);
1298 r
= ordered_hashmap_put(users
, j
->name
, j
);
1302 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1311 static bool item_equal(Item
*a
, Item
*b
) {
1315 if (a
->type
!= b
->type
)
1318 if (!streq_ptr(a
->name
, b
->name
))
1321 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1324 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1327 if (!streq_ptr(a
->description
, b
->description
))
1330 if (a
->uid_set
!= b
->uid_set
)
1333 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1336 if (a
->gid_set
!= b
->gid_set
)
1339 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1342 if (!streq_ptr(a
->home
, b
->home
))
1345 if (!streq_ptr(a
->shell
, b
->shell
))
1351 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1353 static const Specifier specifier_table
[] = {
1354 { 'm', specifier_machine_id
, NULL
},
1355 { 'b', specifier_boot_id
, NULL
},
1356 { 'H', specifier_host_name
, NULL
},
1357 { 'v', specifier_kernel_release
, NULL
},
1361 _cleanup_free_
char *action
= NULL
,
1362 *name
= NULL
, *resolved_name
= NULL
,
1363 *id
= NULL
, *resolved_id
= NULL
,
1364 *description
= NULL
,
1366 *shell
, *resolved_shell
= NULL
;
1367 _cleanup_(item_freep
) Item
*i
= NULL
;
1379 r
= extract_many_words(&p
, NULL
, EXTRACT_QUOTES
,
1380 &action
, &name
, &id
, &description
, &home
, &shell
, NULL
);
1382 log_error("[%s:%u] Syntax error.", fname
, line
);
1386 log_error("[%s:%u] Missing action and name columns.", fname
, line
);
1390 log_error("[%s:%u] Trailing garbage.", fname
, line
);
1395 if (strlen(action
) != 1) {
1396 log_error("[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1400 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
)) {
1401 log_error("[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1406 if (isempty(name
) || streq(name
, "-"))
1410 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1412 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1416 if (!valid_user_group_name(resolved_name
)) {
1417 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_name
);
1423 if (isempty(id
) || streq(id
, "-"))
1427 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1429 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, name
);
1434 /* Verify description */
1435 if (isempty(description
) || streq(description
, "-"))
1436 description
= mfree(description
);
1439 if (!valid_gecos(description
)) {
1440 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname
, line
, description
);
1446 if (isempty(home
) || streq(home
, "-"))
1450 if (!valid_home(home
)) {
1451 log_error("[%s:%u] '%s' is not a valid home directory field.", fname
, line
, home
);
1457 if (isempty(shell
) || streq(shell
, "-"))
1458 shell
= mfree(shell
);
1461 r
= specifier_printf(shell
, specifier_table
, NULL
, &resolved_shell
);
1463 log_error("[%s:%u] Failed to replace specifiers: %s", fname
, line
, shell
);
1467 if (!valid_shell(resolved_shell
)) {
1468 log_error("[%s:%u] '%s' is not a valid login shell field.", fname
, line
, resolved_shell
);
1474 switch (action
[0]) {
1477 if (resolved_name
) {
1478 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname
, line
);
1483 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname
, line
);
1487 if (description
|| home
|| shell
) {
1488 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1489 fname
, line
, action
[0],
1490 description
? "GECOS" : home
? "home directory" : "login shell");
1494 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1496 log_error("[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1505 /* Try to extend an existing member or group item */
1507 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname
, line
);
1512 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname
, line
);
1516 if (!valid_user_group_name(resolved_id
)) {
1517 log_error("[%s:%u] '%s' is not a valid user or group name.", fname
, line
, resolved_id
);
1521 if (description
|| home
|| shell
) {
1522 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1523 fname
, line
, action
[0],
1524 description
? "GECOS" : home
? "home directory" : "login shell");
1528 r
= ordered_hashmap_ensure_allocated(&members
, &string_hash_ops
);
1532 l
= ordered_hashmap_get(members
, resolved_id
);
1534 /* A list for this group name already exists, let's append to it */
1535 r
= strv_push(&l
, resolved_name
);
1539 resolved_name
= NULL
;
1541 assert_se(ordered_hashmap_update(members
, resolved_id
, l
) >= 0);
1543 /* No list for this group name exists yet, create one */
1545 l
= new0(char *, 2);
1549 l
[0] = resolved_name
;
1552 r
= ordered_hashmap_put(members
, resolved_id
, l
);
1558 resolved_id
= resolved_name
= NULL
;
1566 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname
, line
);
1570 r
= ordered_hashmap_ensure_allocated(&users
, &string_hash_ops
);
1579 if (path_is_absolute(resolved_id
)) {
1580 i
->uid_path
= resolved_id
;
1583 path_kill_slashes(i
->uid_path
);
1585 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1586 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1587 r
= parse_gid(gid
, &i
->gid
);
1589 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1591 i
->id_set_strict
= true;
1592 free_and_replace(resolved_id
, uid
);
1594 r
= parse_uid(resolved_id
, &i
->uid
);
1596 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1602 i
->description
= description
;
1608 i
->shell
= resolved_shell
;
1609 resolved_shell
= NULL
;
1616 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname
, line
);
1620 if (description
|| home
|| shell
) {
1621 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1622 fname
, line
, action
[0],
1623 description
? "GECOS" : home
? "home directory" : "login shell");
1627 r
= ordered_hashmap_ensure_allocated(&groups
, &string_hash_ops
);
1636 if (path_is_absolute(resolved_id
)) {
1637 i
->gid_path
= resolved_id
;
1640 path_kill_slashes(i
->gid_path
);
1642 r
= parse_gid(resolved_id
, &i
->gid
);
1644 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1657 i
->type
= action
[0];
1658 i
->name
= resolved_name
;
1659 resolved_name
= NULL
;
1661 existing
= ordered_hashmap_get(h
, i
->name
);
1664 /* Two identical items are fine */
1665 if (!item_equal(existing
, i
))
1666 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1671 r
= ordered_hashmap_put(h
, i
->name
, i
);
1679 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1680 _cleanup_fclose_
FILE *rf
= NULL
;
1682 char line
[LINE_MAX
];
1691 r
= search_and_fopen_nulstr(fn
, "re", arg_root
, conf_file_dirs
, &rf
);
1693 if (ignore_enoent
&& r
== -ENOENT
)
1696 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1702 FOREACH_LINE(line
, f
, break) {
1709 if (IN_SET(*l
, 0, '#'))
1712 k
= parse_line(fn
, v
, l
);
1713 if (k
< 0 && r
== 0)
1718 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1726 static void free_database(Hashmap
*by_name
, Hashmap
*by_id
) {
1730 name
= hashmap_first(by_id
);
1734 hashmap_remove(by_name
, name
);
1736 hashmap_steal_first_key(by_id
);
1740 while ((name
= hashmap_steal_first_key(by_name
)))
1743 hashmap_free(by_name
);
1744 hashmap_free(by_id
);
1747 static void help(void) {
1748 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1749 "Creates system user accounts.\n\n"
1750 " -h --help Show this help\n"
1751 " --version Show package version\n"
1752 " --root=PATH Operate on an alternate filesystem root\n"
1753 " --replace=PATH Treat arguments as replacement for PATH\n"
1754 " --inline Treat arguments as configuration lines\n"
1755 , program_invocation_short_name
);
1758 static int parse_argv(int argc
, char *argv
[]) {
1761 ARG_VERSION
= 0x100,
1767 static const struct option options
[] = {
1768 { "help", no_argument
, NULL
, 'h' },
1769 { "version", no_argument
, NULL
, ARG_VERSION
},
1770 { "root", required_argument
, NULL
, ARG_ROOT
},
1771 { "replace", required_argument
, NULL
, ARG_REPLACE
},
1772 { "inline", no_argument
, NULL
, ARG_INLINE
},
1781 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1793 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
1799 if (!path_is_absolute(optarg
) ||
1800 !endswith(optarg
, ".conf")) {
1801 log_error("The argument to --replace= must an absolute path to a config file");
1805 arg_replace
= optarg
;
1816 assert_not_reached("Unhandled option");
1819 if (arg_replace
&& optind
>= argc
) {
1820 log_error("When --replace= is given, some configuration items must be specified");
1827 static int parse_arguments(char **args
) {
1832 STRV_FOREACH(arg
, args
) {
1834 /* Use (argument):n, where n==1 for the first positional arg */
1835 r
= parse_line("(argument)", pos
, *arg
);
1837 r
= read_config_file(*arg
, false);
1847 static int read_config_files(const char* dirs
, char **args
) {
1848 _cleanup_strv_free_
char **files
= NULL
;
1849 _cleanup_free_
char *p
= NULL
;
1853 r
= conf_files_list_nulstr(&files
, ".conf", arg_root
, 0, dirs
);
1855 return log_error_errno(r
, "Failed to enumerate sysusers.d files: %m");
1858 r
= conf_files_insert(&files
, arg_root
, dirs
, arg_replace
);
1860 return log_error_errno(r
, "Failed to extend sysusers.d file list: %m");
1862 p
= path_join(arg_root
, arg_replace
, NULL
);
1867 STRV_FOREACH(f
, files
)
1868 if (p
&& path_equal(*f
, p
)) {
1869 log_debug("Parsing arguments at position \"%s\"…", *f
);
1871 r
= parse_arguments(args
);
1875 log_debug("Reading config file \"%s\"…", *f
);
1877 /* Just warn, ignore result otherwise */
1878 (void) read_config_file(*f
, true);
1884 int main(int argc
, char *argv
[]) {
1886 _cleanup_close_
int lock
= -1;
1892 r
= parse_argv(argc
, argv
);
1896 log_set_target(LOG_TARGET_AUTO
);
1897 log_parse_environment();
1902 r
= mac_selinux_init();
1904 log_error_errno(r
, "SELinux setup failed: %m");
1908 /* If command line arguments are specified along with --replace, read all
1909 * configuration files and insert the positional arguments at the specified
1910 * place. Otherwise, if command line arguments are specified, execute just
1911 * them, and finally, without --replace= or any positional arguments, just
1912 * read configuration and execute it.
1914 if (arg_replace
|| optind
>= argc
)
1915 r
= read_config_files(conf_file_dirs
, argv
+ optind
);
1917 r
= parse_arguments(argv
+ optind
);
1921 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1922 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1923 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1924 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1926 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1927 r
= log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1932 /* Default to default range of 1..SYSTEM_UID_MAX */
1933 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1944 lock
= take_etc_passwd_lock(arg_root
);
1946 log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
1950 r
= load_user_database();
1952 log_error_errno(r
, "Failed to load user database: %m");
1956 r
= load_group_database();
1958 log_error_errno(r
, "Failed to read group database: %m");
1962 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
1965 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
1970 log_error_errno(r
, "Failed to write files: %m");
1973 ordered_hashmap_free_with_destructor(groups
, item_free
);
1974 ordered_hashmap_free_with_destructor(users
, item_free
);
1976 while ((n
= ordered_hashmap_first_key(members
))) {
1977 strv_free(ordered_hashmap_steal_first(members
));
1980 ordered_hashmap_free(members
);
1982 ordered_hashmap_free(todo_uids
);
1983 ordered_hashmap_free(todo_gids
);
1985 free_database(database_user
, database_uid
);
1986 free_database(database_group
, database_gid
);
1990 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;