1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "alloc-util.h"
7 #include "conf-files.h"
12 #include "format-util.h"
15 #include "main-func.h"
17 #include "path-util.h"
18 #include "pretty-print.h"
20 #include "selinux-util.h"
21 #include "smack-util.h"
22 #include "specifier.h"
23 #include "string-util.h"
25 #include "tmpfile-util-label.h"
26 #include "uid-range.h"
27 #include "user-util.h"
31 typedef enum ItemType
{
54 /* When set the group with the specified gid must exist
55 * and the check if a uid clashes with the gid is skipped.
65 static char *arg_root
= NULL
;
66 static bool arg_cat_config
= false;
67 static const char *arg_replace
= NULL
;
68 static bool arg_inline
= false;
69 static PagerFlags arg_pager_flags
= 0;
71 static OrderedHashmap
*users
= NULL
, *groups
= NULL
;
72 static OrderedHashmap
*todo_uids
= NULL
, *todo_gids
= NULL
;
73 static OrderedHashmap
*members
= NULL
;
75 static Hashmap
*database_by_uid
= NULL
, *database_by_username
= NULL
;
76 static Hashmap
*database_by_gid
= NULL
, *database_by_groupname
= NULL
;
77 static Set
*database_users
= NULL
, *database_groups
= NULL
;
79 static uid_t search_uid
= UID_INVALID
;
80 static UidRange
*uid_range
= NULL
;
81 static unsigned n_uid_range
= 0;
83 STATIC_DESTRUCTOR_REGISTER(groups
, ordered_hashmap_freep
);
84 STATIC_DESTRUCTOR_REGISTER(users
, ordered_hashmap_freep
);
85 STATIC_DESTRUCTOR_REGISTER(members
, ordered_hashmap_freep
);
86 STATIC_DESTRUCTOR_REGISTER(todo_uids
, ordered_hashmap_freep
);
87 STATIC_DESTRUCTOR_REGISTER(todo_gids
, ordered_hashmap_freep
);
88 STATIC_DESTRUCTOR_REGISTER(database_by_uid
, hashmap_freep
);
89 STATIC_DESTRUCTOR_REGISTER(database_by_username
, hashmap_freep
);
90 STATIC_DESTRUCTOR_REGISTER(database_users
, set_free_freep
);
91 STATIC_DESTRUCTOR_REGISTER(database_by_gid
, hashmap_freep
);
92 STATIC_DESTRUCTOR_REGISTER(database_by_groupname
, hashmap_freep
);
93 STATIC_DESTRUCTOR_REGISTER(database_groups
, set_free_freep
);
94 STATIC_DESTRUCTOR_REGISTER(uid_range
, freep
);
95 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
97 static int errno_is_not_exists(int code
) {
98 /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
100 return IN_SET(code
, 0, ENOENT
, ESRCH
, EBADF
, EPERM
);
103 static int load_user_database(void) {
104 _cleanup_fclose_
FILE *f
= NULL
;
105 const char *passwd_path
;
109 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
110 f
= fopen(passwd_path
, "re");
112 return errno
== ENOENT
? 0 : -errno
;
114 r
= hashmap_ensure_allocated(&database_by_username
, &string_hash_ops
);
118 r
= hashmap_ensure_allocated(&database_by_uid
, NULL
);
122 r
= set_ensure_allocated(&database_users
, NULL
);
126 while ((r
= fgetpwent_sane(f
, &pw
)) > 0) {
130 n
= strdup(pw
->pw_name
);
134 k
= set_put(database_users
, n
);
140 k
= hashmap_put(database_by_username
, n
, UID_TO_PTR(pw
->pw_uid
));
141 if (k
< 0 && k
!= -EEXIST
)
144 q
= hashmap_put(database_by_uid
, UID_TO_PTR(pw
->pw_uid
), n
);
145 if (q
< 0 && q
!= -EEXIST
)
151 static int load_group_database(void) {
152 _cleanup_fclose_
FILE *f
= NULL
;
153 const char *group_path
;
157 group_path
= prefix_roota(arg_root
, "/etc/group");
158 f
= fopen(group_path
, "re");
160 return errno
== ENOENT
? 0 : -errno
;
162 r
= hashmap_ensure_allocated(&database_by_groupname
, &string_hash_ops
);
166 r
= hashmap_ensure_allocated(&database_by_gid
, NULL
);
170 r
= set_ensure_allocated(&database_groups
, NULL
);
174 while ((r
= fgetgrent_sane(f
, &gr
)) > 0) {
178 n
= strdup(gr
->gr_name
);
182 k
= set_put(database_groups
, n
);
188 k
= hashmap_put(database_by_groupname
, n
, GID_TO_PTR(gr
->gr_gid
));
189 if (k
< 0 && k
!= -EEXIST
)
192 q
= hashmap_put(database_by_gid
, GID_TO_PTR(gr
->gr_gid
), n
);
193 if (q
< 0 && q
!= -EEXIST
)
199 static int make_backup(const char *target
, const char *x
) {
200 _cleanup_close_
int src
= -1;
201 _cleanup_fclose_
FILE *dst
= NULL
;
202 _cleanup_free_
char *dst_tmp
= NULL
;
204 struct timespec ts
[2];
208 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
210 if (errno
== ENOENT
) /* No backup necessary... */
216 if (fstat(src
, &st
) < 0)
219 r
= fopen_temporary_label(target
, x
, &dst
, &dst_tmp
);
223 r
= copy_bytes(src
, fileno(dst
), (uint64_t) -1, COPY_REFLINK
);
227 /* Don't fail on chmod() or chown(). If it stays owned by us
228 * and/or unreadable by others, then it isn't too bad... */
230 backup
= strjoina(x
, "-");
232 /* Copy over the access mask */
233 r
= chmod_and_chown_unsafe(dst_tmp
, st
.st_mode
& 07777, st
.st_uid
, st
.st_gid
);
235 log_warning_errno(r
, "Failed to change access mode or ownership of %s: %m", backup
);
239 if (futimens(fileno(dst
), ts
) < 0)
240 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
242 r
= fflush_sync_and_check(dst
);
246 if (rename(dst_tmp
, backup
) < 0) {
254 (void) unlink(dst_tmp
);
258 static int putgrent_with_members(const struct group
*gr
, FILE *group
) {
264 a
= ordered_hashmap_get(members
, gr
->gr_name
);
266 _cleanup_strv_free_
char **l
= NULL
;
270 l
= strv_copy(gr
->gr_mem
);
275 if (strv_find(l
, *i
))
278 if (strv_extend(&l
, *i
) < 0)
294 r
= putgrent_sane(&t
, group
);
295 return r
< 0 ? r
: 1;
299 return putgrent_sane(gr
, group
);
303 static int putsgent_with_members(const struct sgrp
*sg
, FILE *gshadow
) {
309 a
= ordered_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 r
= putsgent_sane(&t
, gshadow
);
340 return r
< 0 ? r
: 1;
344 return putsgent_sane(sg
, gshadow
);
348 static const char* default_shell(uid_t uid
) {
349 return uid
== 0 ? "/bin/sh" : NOLOGIN
;
352 static int write_temporary_passwd(const char *passwd_path
, FILE **tmpfile
, char **tmpfile_path
) {
353 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
354 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
355 struct passwd
*pw
= NULL
;
360 if (ordered_hashmap_size(todo_uids
) == 0)
363 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
367 original
= fopen(passwd_path
, "re");
370 r
= sync_rights(original
, passwd_tmp
);
374 while ((r
= fgetpwent_sane(original
, &pw
)) > 0) {
376 i
= ordered_hashmap_get(users
, pw
->pw_name
);
377 if (i
&& i
->todo_user
)
378 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
379 "%s: User \"%s\" already exists.",
380 passwd_path
, pw
->pw_name
);
382 if (ordered_hashmap_contains(todo_uids
, UID_TO_PTR(pw
->pw_uid
)))
383 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
384 "%s: Detected collision for UID " UID_FMT
".",
385 passwd_path
, pw
->pw_uid
);
387 /* Make sure we keep the NIS entries (if any) at the end. */
388 if (IN_SET(pw
->pw_name
[0], '+', '-'))
391 r
= putpwent_sane(pw
, passwd
);
401 if (fchmod(fileno(passwd
), 0644) < 0)
405 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
410 .pw_gecos
= i
->description
,
412 /* "x" means the password is stored in the shadow file */
413 .pw_passwd
= (char*) "x",
415 /* We default to the root directory as home */
416 .pw_dir
= i
->home
?: (char*) "/",
418 /* Initialize the shell to nologin, with one exception:
419 * for root we patch in something special */
420 .pw_shell
= i
->shell
?: (char*) default_shell(i
->uid
),
423 r
= putpwent_sane(&n
, passwd
);
428 /* Append the remaining NIS entries if any */
430 r
= putpwent_sane(pw
, passwd
);
434 r
= fgetpwent_sane(original
, &pw
);
441 r
= fflush_and_check(passwd
);
445 *tmpfile
= TAKE_PTR(passwd
);
446 *tmpfile_path
= TAKE_PTR(passwd_tmp
);
451 static int write_temporary_shadow(const char *shadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
452 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
453 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
454 struct spwd
*sp
= NULL
;
460 if (ordered_hashmap_size(todo_uids
) == 0)
463 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
467 lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
469 original
= fopen(shadow_path
, "re");
472 r
= sync_rights(original
, shadow_tmp
);
476 while ((r
= fgetspent_sane(original
, &sp
)) > 0) {
478 i
= ordered_hashmap_get(users
, sp
->sp_namp
);
479 if (i
&& i
->todo_user
) {
480 /* we will update the existing entry */
481 sp
->sp_lstchg
= lstchg
;
483 /* only the /etc/shadow stage is left, so we can
484 * safely remove the item from the todo set */
485 i
->todo_user
= false;
486 ordered_hashmap_remove(todo_uids
, UID_TO_PTR(i
->uid
));
489 /* Make sure we keep the NIS entries (if any) at the end. */
490 if (IN_SET(sp
->sp_namp
[0], '+', '-'))
493 r
= putspent_sane(sp
, shadow
);
503 if (fchmod(fileno(shadow
), 0000) < 0)
507 ORDERED_HASHMAP_FOREACH(i
, todo_uids
, iterator
) {
510 .sp_pwdp
= (char*) "!*", /* lock this password, and make it invalid */
517 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
520 r
= putspent_sane(&n
, shadow
);
525 /* Append the remaining NIS entries if any */
527 r
= putspent_sane(sp
, shadow
);
531 r
= fgetspent_sane(original
, &sp
);
537 if (!IN_SET(errno
, 0, ENOENT
))
540 r
= fflush_sync_and_check(shadow
);
544 *tmpfile
= TAKE_PTR(shadow
);
545 *tmpfile_path
= TAKE_PTR(shadow_tmp
);
550 static int write_temporary_group(const char *group_path
, FILE **tmpfile
, char **tmpfile_path
) {
551 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
552 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
553 bool group_changed
= false;
554 struct group
*gr
= NULL
;
559 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
562 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
566 original
= fopen(group_path
, "re");
569 r
= sync_rights(original
, group_tmp
);
573 while ((r
= fgetgrent_sane(original
, &gr
)) > 0) {
574 /* Safety checks against name and GID collisions. Normally,
575 * this should be unnecessary, but given that we look at the
576 * entries anyway here, let's make an extra verification
577 * step that we don't generate duplicate entries. */
579 i
= ordered_hashmap_get(groups
, gr
->gr_name
);
580 if (i
&& i
->todo_group
)
581 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
582 "%s: Group \"%s\" already exists.",
583 group_path
, gr
->gr_name
);
585 if (ordered_hashmap_contains(todo_gids
, GID_TO_PTR(gr
->gr_gid
)))
586 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
587 "%s: Detected collision for GID " GID_FMT
".",
588 group_path
, gr
->gr_gid
);
590 /* Make sure we keep the NIS entries (if any) at the end. */
591 if (IN_SET(gr
->gr_name
[0], '+', '-'))
594 r
= putgrent_with_members(gr
, group
);
598 group_changed
= true;
606 if (fchmod(fileno(group
), 0644) < 0)
610 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
614 .gr_passwd
= (char*) "x",
617 r
= putgrent_with_members(&n
, group
);
621 group_changed
= true;
624 /* Append the remaining NIS entries if any */
626 r
= putgrent_sane(gr
, group
);
630 r
= fgetgrent_sane(original
, &gr
);
637 r
= fflush_sync_and_check(group
);
642 *tmpfile
= TAKE_PTR(group
);
643 *tmpfile_path
= TAKE_PTR(group_tmp
);
648 static int write_temporary_gshadow(const char * gshadow_path
, FILE **tmpfile
, char **tmpfile_path
) {
650 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
651 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
652 bool group_changed
= false;
657 if (ordered_hashmap_size(todo_gids
) == 0 && ordered_hashmap_size(members
) == 0)
660 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
664 original
= fopen(gshadow_path
, "re");
668 r
= sync_rights(original
, gshadow_tmp
);
672 while ((r
= fgetsgent_sane(original
, &sg
)) > 0) {
674 i
= ordered_hashmap_get(groups
, sg
->sg_namp
);
675 if (i
&& i
->todo_group
)
676 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
677 "%s: Group \"%s\" already exists.",
678 gshadow_path
, sg
->sg_namp
);
680 r
= putsgent_with_members(sg
, gshadow
);
684 group_changed
= true;
692 if (fchmod(fileno(gshadow
), 0000) < 0)
696 ORDERED_HASHMAP_FOREACH(i
, todo_gids
, iterator
) {
699 .sg_passwd
= (char*) "!!",
702 r
= putsgent_with_members(&n
, gshadow
);
706 group_changed
= true;
709 r
= fflush_sync_and_check(gshadow
);
714 *tmpfile
= TAKE_PTR(gshadow
);
715 *tmpfile_path
= TAKE_PTR(gshadow_tmp
);
723 static int write_files(void) {
724 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
725 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
726 const char *passwd_path
= NULL
, *group_path
= NULL
, *shadow_path
= NULL
, *gshadow_path
= NULL
;
729 passwd_path
= prefix_roota(arg_root
, "/etc/passwd");
730 shadow_path
= prefix_roota(arg_root
, "/etc/shadow");
731 group_path
= prefix_roota(arg_root
, "/etc/group");
732 gshadow_path
= prefix_roota(arg_root
, "/etc/gshadow");
734 r
= write_temporary_group(group_path
, &group
, &group_tmp
);
738 r
= write_temporary_gshadow(gshadow_path
, &gshadow
, &gshadow_tmp
);
742 r
= write_temporary_passwd(passwd_path
, &passwd
, &passwd_tmp
);
746 r
= write_temporary_shadow(shadow_path
, &shadow
, &shadow_tmp
);
750 /* Make a backup of the old files */
752 r
= make_backup("/etc/group", group_path
);
757 r
= make_backup("/etc/gshadow", gshadow_path
);
763 r
= make_backup("/etc/passwd", passwd_path
);
768 r
= make_backup("/etc/shadow", shadow_path
);
773 /* And make the new files count */
775 r
= rename_and_apply_smack(group_tmp
, group_path
);
779 group_tmp
= mfree(group_tmp
);
782 r
= rename_and_apply_smack(gshadow_tmp
, gshadow_path
);
786 gshadow_tmp
= mfree(gshadow_tmp
);
790 r
= rename_and_apply_smack(passwd_tmp
, passwd_path
);
794 passwd_tmp
= mfree(passwd_tmp
);
797 r
= rename_and_apply_smack(shadow_tmp
, shadow_path
);
801 shadow_tmp
= mfree(shadow_tmp
);
807 static int uid_is_ok(uid_t uid
, const char *name
, bool check_with_gid
) {
813 /* Let's see if we already have assigned the UID a second time */
814 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(uid
)))
817 /* Try to avoid using uids that are already used by a group
818 * that doesn't have the same name as our new user. */
819 if (check_with_gid
) {
820 i
= ordered_hashmap_get(todo_gids
, GID_TO_PTR(uid
));
821 if (i
&& !streq(i
->name
, name
))
825 /* Let's check the files directly */
826 if (hashmap_contains(database_by_uid
, UID_TO_PTR(uid
)))
829 if (check_with_gid
) {
830 n
= hashmap_get(database_by_gid
, GID_TO_PTR(uid
));
831 if (n
&& !streq(n
, name
))
835 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
841 if (!IN_SET(errno
, 0, ENOENT
))
844 if (check_with_gid
) {
846 g
= getgrgid((gid_t
) uid
);
848 if (!streq(g
->gr_name
, name
))
850 } else if (!IN_SET(errno
, 0, ENOENT
))
858 static int root_stat(const char *p
, struct stat
*st
) {
861 fix
= prefix_roota(arg_root
, p
);
862 if (stat(fix
, st
) < 0)
868 static int read_id_from_file(Item
*i
, uid_t
*_uid
, gid_t
*_gid
) {
870 bool found_uid
= false, found_gid
= false;
876 /* First, try to get the gid directly */
877 if (_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
882 /* Then, try to get the uid directly */
883 if ((_uid
|| (_gid
&& !found_gid
))
885 && root_stat(i
->uid_path
, &st
) >= 0) {
890 /* If we need the gid, but had no success yet, also derive it from the uid path */
891 if (_gid
&& !found_gid
) {
897 /* If that didn't work yet, then let's reuse the gid as uid */
898 if (_uid
&& !found_uid
&& i
->gid_path
) {
903 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
904 uid
= (uid_t
) st
.st_gid
;
926 static int add_user(Item
*i
) {
932 /* Check the database directly */
933 z
= hashmap_get(database_by_username
, i
->name
);
935 log_debug("User %s already exists.", i
->name
);
936 i
->uid
= PTR_TO_UID(z
);
946 p
= getpwnam(i
->name
);
948 log_debug("User %s already exists.", i
->name
);
952 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
958 if (!errno_is_not_exists(errno
))
959 return log_error_errno(errno
, "Failed to check if user %s already exists: %m", i
->name
);
962 /* Try to use the suggested numeric uid */
964 r
= uid_is_ok(i
->uid
, i
->name
, !i
->id_set_strict
);
966 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
968 log_debug("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
973 /* If that didn't work, try to read it from the specified path */
977 if (read_id_from_file(i
, &c
, NULL
) > 0) {
979 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
980 log_debug("User ID " UID_FMT
" of file not suitable for %s.", c
, i
->name
);
982 r
= uid_is_ok(c
, i
->name
, true);
984 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
989 log_debug("User ID " UID_FMT
" of file for %s is already used.", c
, i
->name
);
994 /* Otherwise, try to reuse the group ID */
995 if (!i
->uid_set
&& i
->gid_set
) {
996 r
= uid_is_ok((uid_t
) i
->gid
, i
->name
, true);
998 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1000 i
->uid
= (uid_t
) i
->gid
;
1005 /* And if that didn't work either, let's try to find a free one */
1008 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1010 return log_error_errno(r
, "No free user ID available for %s.", i
->name
);
1012 r
= uid_is_ok(search_uid
, i
->name
, true);
1014 return log_error_errno(r
, "Failed to verify uid " UID_FMT
": %m", i
->uid
);
1020 i
->uid
= search_uid
;
1023 r
= ordered_hashmap_ensure_allocated(&todo_uids
, NULL
);
1027 r
= ordered_hashmap_put(todo_uids
, UID_TO_PTR(i
->uid
), i
);
1031 i
->todo_user
= true;
1032 log_info("Creating user %s (%s) with uid " UID_FMT
" and gid " GID_FMT
".", i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1037 static int gid_is_ok(gid_t gid
) {
1041 if (ordered_hashmap_get(todo_gids
, GID_TO_PTR(gid
)))
1044 /* Avoid reusing gids that are already used by a different user */
1045 if (ordered_hashmap_get(todo_uids
, UID_TO_PTR(gid
)))
1048 if (hashmap_contains(database_by_gid
, GID_TO_PTR(gid
)))
1051 if (hashmap_contains(database_by_uid
, UID_TO_PTR(gid
)))
1059 if (!IN_SET(errno
, 0, ENOENT
))
1063 p
= getpwuid((uid_t
) gid
);
1066 if (!IN_SET(errno
, 0, ENOENT
))
1073 static int get_gid_by_name(const char *name
, gid_t
*gid
) {
1078 /* Check the database directly */
1079 z
= hashmap_get(database_by_groupname
, name
);
1081 *gid
= PTR_TO_GID(z
);
1085 /* Also check NSS */
1095 if (!errno_is_not_exists(errno
))
1096 return log_error_errno(errno
, "Failed to check if group %s already exists: %m", name
);
1102 static int add_group(Item
*i
) {
1107 r
= get_gid_by_name(i
->name
, &i
->gid
);
1111 log_debug("Group %s already exists.", i
->name
);
1116 /* Try to use the suggested numeric gid */
1118 r
= gid_is_ok(i
->gid
);
1120 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1121 if (i
->id_set_strict
) {
1122 /* If we require the gid to already exist we can return here:
1123 * r > 0: means the gid does not exist -> fail
1124 * r == 0: means the gid exists -> nothing more to do.
1127 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1128 "Failed to create %s: please create GID %d",
1134 log_debug("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1139 /* Try to reuse the numeric uid, if there's one */
1140 if (!i
->gid_set
&& i
->uid_set
) {
1141 r
= gid_is_ok((gid_t
) i
->uid
);
1143 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1145 i
->gid
= (gid_t
) i
->uid
;
1150 /* If that didn't work, try to read it from the specified path */
1154 if (read_id_from_file(i
, NULL
, &c
) > 0) {
1156 if (c
<= 0 || !uid_range_contains(uid_range
, n_uid_range
, c
))
1157 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", c
, i
->name
);
1161 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1166 log_debug("Group ID " GID_FMT
" of file for %s already used.", c
, i
->name
);
1171 /* And if that didn't work either, let's try to find a free one */
1174 /* We look for new GIDs in the UID pool! */
1175 r
= uid_range_next_lower(uid_range
, n_uid_range
, &search_uid
);
1177 return log_error_errno(r
, "No free group ID available for %s.", i
->name
);
1179 r
= gid_is_ok(search_uid
);
1181 return log_error_errno(r
, "Failed to verify gid " GID_FMT
": %m", i
->gid
);
1187 i
->gid
= search_uid
;
1190 r
= ordered_hashmap_ensure_allocated(&todo_gids
, NULL
);
1194 r
= ordered_hashmap_put(todo_gids
, GID_TO_PTR(i
->gid
), i
);
1198 i
->todo_group
= true;
1199 log_info("Creating group %s with gid " GID_FMT
".", i
->name
, i
->gid
);
1204 static int process_item(Item
*i
) {
1214 j
= ordered_hashmap_get(groups
, i
->group_name
?: i
->name
);
1215 if (j
&& j
->todo_group
) {
1216 /* When a group with the target name is already in queue,
1217 * use the information about the group and do not create
1218 * duplicated group entry. */
1219 i
->gid_set
= j
->gid_set
;
1221 i
->id_set_strict
= true;
1222 } else if (i
->group_name
) {
1223 /* When a group name was given instead of a GID and it's
1224 * not in queue, then it must already exist. */
1225 r
= get_gid_by_name(i
->group_name
, &i
->gid
);
1227 return log_error_errno(r
, "Group %s not found.", i
->group_name
);
1229 i
->id_set_strict
= true;
1240 return add_group(i
);
1243 assert_not_reached("Unknown item type");
1247 static Item
* item_free(Item
*i
) {
1252 free(i
->group_name
);
1255 free(i
->description
);
1261 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1262 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_hash_ops
, char, string_hash_func
, string_compare_func
, Item
, item_free
);
1264 static int add_implicit(void) {
1269 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1270 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, members
, iterator
) {
1274 if (!ordered_hashmap_get(users
, *m
)) {
1275 _cleanup_(item_freep
) Item
*j
= NULL
;
1277 r
= ordered_hashmap_ensure_allocated(&users
, &item_hash_ops
);
1286 j
->name
= strdup(*m
);
1290 r
= ordered_hashmap_put(users
, j
->name
, j
);
1294 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1298 if (!(ordered_hashmap_get(users
, g
) ||
1299 ordered_hashmap_get(groups
, g
))) {
1300 _cleanup_(item_freep
) Item
*j
= NULL
;
1302 r
= ordered_hashmap_ensure_allocated(&groups
, &item_hash_ops
);
1310 j
->type
= ADD_GROUP
;
1311 j
->name
= strdup(g
);
1315 r
= ordered_hashmap_put(groups
, j
->name
, j
);
1319 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1327 static bool item_equal(Item
*a
, Item
*b
) {
1331 if (a
->type
!= b
->type
)
1334 if (!streq_ptr(a
->name
, b
->name
))
1337 if (!streq_ptr(a
->uid_path
, b
->uid_path
))
1340 if (!streq_ptr(a
->gid_path
, b
->gid_path
))
1343 if (!streq_ptr(a
->description
, b
->description
))
1346 if (a
->uid_set
!= b
->uid_set
)
1349 if (a
->uid_set
&& a
->uid
!= b
->uid
)
1352 if (a
->gid_set
!= b
->gid_set
)
1355 if (a
->gid_set
&& a
->gid
!= b
->gid
)
1358 if (!streq_ptr(a
->home
, b
->home
))
1361 if (!streq_ptr(a
->shell
, b
->shell
))
1367 static int parse_line(const char *fname
, unsigned line
, const char *buffer
) {
1369 static const Specifier specifier_table
[] = {
1370 { 'm', specifier_machine_id
, NULL
},
1371 { 'b', specifier_boot_id
, NULL
},
1372 { 'H', specifier_host_name
, NULL
},
1373 { 'l', specifier_short_host_name
, NULL
},
1374 { 'v', specifier_kernel_release
, NULL
},
1375 { 'a', specifier_architecture
, NULL
},
1376 { 'o', specifier_os_id
, NULL
},
1377 { 'w', specifier_os_version_id
, NULL
},
1378 { 'B', specifier_os_build_id
, NULL
},
1379 { 'W', specifier_os_variant_id
, NULL
},
1380 { 'T', specifier_tmp_dir
, NULL
},
1381 { 'V', specifier_var_tmp_dir
, NULL
},
1385 _cleanup_free_
char *action
= NULL
,
1386 *name
= NULL
, *resolved_name
= NULL
,
1387 *id
= NULL
, *resolved_id
= NULL
,
1388 *description
= NULL
, *resolved_description
= NULL
,
1389 *home
= NULL
, *resolved_home
= NULL
,
1390 *shell
= NULL
, *resolved_shell
= NULL
;
1391 _cleanup_(item_freep
) Item
*i
= NULL
;
1403 r
= extract_many_words(&p
, NULL
, EXTRACT_UNQUOTE
,
1404 &action
, &name
, &id
, &description
, &home
, &shell
, NULL
);
1406 return log_error_errno(r
, "[%s:%u] Syntax error.", fname
, line
);
1408 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1409 "[%s:%u] Missing action and name columns.", fname
, line
);
1411 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1412 "[%s:%u] Trailing garbage.", fname
, line
);
1415 if (strlen(action
) != 1)
1416 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1417 "[%s:%u] Unknown modifier '%s'", fname
, line
, action
);
1419 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
))
1420 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
),
1421 "[%s:%u] Unknown command type '%c'.", fname
, line
, action
[0]);
1424 if (empty_or_dash(name
))
1428 r
= specifier_printf(name
, specifier_table
, NULL
, &resolved_name
);
1430 return log_error_errno(r
, "[%s:%u] Failed to replace specifiers in '%s': %m", fname
, line
, name
);
1432 if (!valid_user_group_name(resolved_name
, 0))
1433 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1434 "[%s:%u] '%s' is not a valid user or group name.",
1435 fname
, line
, resolved_name
);
1439 if (empty_or_dash(id
))
1443 r
= specifier_printf(id
, specifier_table
, NULL
, &resolved_id
);
1445 return log_error_errno(r
, "[%s:%u] Failed to replace specifiers in '%s': %m",
1449 /* Verify description */
1450 if (empty_or_dash(description
))
1451 description
= mfree(description
);
1454 r
= specifier_printf(description
, specifier_table
, NULL
, &resolved_description
);
1456 return log_error_errno(r
, "[%s:%u] Failed to replace specifiers in '%s': %m",
1457 fname
, line
, description
);
1459 if (!valid_gecos(resolved_description
))
1460 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1461 "[%s:%u] '%s' is not a valid GECOS field.",
1462 fname
, line
, resolved_description
);
1466 if (empty_or_dash(home
))
1470 r
= specifier_printf(home
, specifier_table
, NULL
, &resolved_home
);
1472 return log_error_errno(r
, "[%s:%u] Failed to replace specifiers in '%s': %m",
1475 if (!valid_home(resolved_home
))
1476 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1477 "[%s:%u] '%s' is not a valid home directory field.",
1478 fname
, line
, resolved_home
);
1482 if (empty_or_dash(shell
))
1483 shell
= mfree(shell
);
1486 r
= specifier_printf(shell
, specifier_table
, NULL
, &resolved_shell
);
1488 return log_error_errno(r
, "[%s:%u] Failed to replace specifiers in '%s': %m",
1489 fname
, line
, shell
);
1491 if (!valid_shell(resolved_shell
))
1492 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1493 "[%s:%u] '%s' is not a valid login shell field.",
1494 fname
, line
, resolved_shell
);
1497 switch (action
[0]) {
1501 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1502 "[%s:%u] Lines of type 'r' don't take a name field.",
1506 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1507 "[%s:%u] Lines of type 'r' require a ID range in the third field.",
1510 if (description
|| home
|| shell
)
1511 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1512 "[%s:%u] Lines of type '%c' don't take a %s field.",
1513 fname
, line
, action
[0],
1514 description
? "GECOS" : home
? "home directory" : "login shell");
1516 r
= uid_range_add_str(&uid_range
, &n_uid_range
, resolved_id
);
1518 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1519 "[%s:%u] Invalid UID range %s.", fname
, line
, resolved_id
);
1524 /* Try to extend an existing member or group item */
1526 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1527 "[%s:%u] Lines of type 'm' require a user name in the second field.",
1531 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1532 "[%s:%u] Lines of type 'm' require a group name in the third field.",
1535 if (!valid_user_group_name(resolved_id
, 0))
1536 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1537 "[%s:%u] '%s' is not a valid user or group name.",
1538 fname
, line
, resolved_id
);
1540 if (description
|| home
|| shell
)
1541 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1542 "[%s:%u] Lines of type '%c' don't take a %s field.",
1543 fname
, line
, action
[0],
1544 description
? "GECOS" : home
? "home directory" : "login shell");
1546 r
= string_strv_ordered_hashmap_put(&members
, resolved_id
, resolved_name
);
1548 return log_error_errno(r
, "Failed to store mapping for %s: %m", resolved_id
);
1555 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1556 "[%s:%u] Lines of type 'u' require a user name in the second field.",
1559 r
= ordered_hashmap_ensure_allocated(&users
, &item_hash_ops
);
1568 if (path_is_absolute(resolved_id
)) {
1569 i
->uid_path
= TAKE_PTR(resolved_id
);
1570 path_simplify(i
->uid_path
, false);
1572 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1573 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1574 r
= parse_gid(gid
, &i
->gid
);
1576 if (valid_user_group_name(gid
, 0))
1577 i
->group_name
= TAKE_PTR(gid
);
1579 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1582 i
->id_set_strict
= true;
1584 free_and_replace(resolved_id
, uid
);
1586 if (!streq(resolved_id
, "-")) {
1587 r
= parse_uid(resolved_id
, &i
->uid
);
1589 return log_error_errno(r
, "Failed to parse UID: '%s': %m", id
);
1595 i
->description
= TAKE_PTR(resolved_description
);
1596 i
->home
= TAKE_PTR(resolved_home
);
1597 i
->shell
= TAKE_PTR(resolved_shell
);
1604 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1605 "[%s:%u] Lines of type 'g' require a user name in the second field.",
1608 if (description
|| home
|| shell
)
1609 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1610 "[%s:%u] Lines of type '%c' don't take a %s field.",
1611 fname
, line
, action
[0],
1612 description
? "GECOS" : home
? "home directory" : "login shell");
1614 r
= ordered_hashmap_ensure_allocated(&groups
, &item_hash_ops
);
1623 if (path_is_absolute(resolved_id
)) {
1624 i
->gid_path
= TAKE_PTR(resolved_id
);
1625 path_simplify(i
->gid_path
, false);
1627 r
= parse_gid(resolved_id
, &i
->gid
);
1629 return log_error_errno(r
, "Failed to parse GID: '%s': %m", id
);
1642 i
->type
= action
[0];
1643 i
->name
= TAKE_PTR(resolved_name
);
1645 existing
= ordered_hashmap_get(h
, i
->name
);
1647 /* Two identical items are fine */
1648 if (!item_equal(existing
, i
))
1649 log_warning("Two or more conflicting lines for %s configured, ignoring.", i
->name
);
1654 r
= ordered_hashmap_put(h
, i
->name
, i
);
1662 static int read_config_file(const char *fn
, bool ignore_enoent
) {
1663 _cleanup_fclose_
FILE *rf
= NULL
;
1673 r
= search_and_fopen(fn
, "re", arg_root
, (const char**) CONF_PATHS_STRV("sysusers.d"), &rf
);
1675 if (ignore_enoent
&& r
== -ENOENT
)
1678 return log_error_errno(r
, "Failed to open '%s', ignoring: %m", fn
);
1685 _cleanup_free_
char *line
= NULL
;
1689 k
= read_line(f
, LONG_LINE_MAX
, &line
);
1691 return log_error_errno(k
, "Failed to read '%s': %m", fn
);
1698 if (IN_SET(*l
, 0, '#'))
1701 k
= parse_line(fn
, v
, l
);
1702 if (k
< 0 && r
== 0)
1707 log_error_errno(errno
, "Failed to read from file %s: %m", fn
);
1715 static int cat_config(void) {
1716 _cleanup_strv_free_
char **files
= NULL
;
1719 r
= conf_files_list_with_replacement(arg_root
, CONF_PATHS_STRV("sysusers.d"), arg_replace
, &files
, NULL
);
1723 (void) pager_open(arg_pager_flags
);
1725 return cat_files(NULL
, files
, 0);
1728 static int help(void) {
1729 _cleanup_free_
char *link
= NULL
;
1732 r
= terminal_urlify_man("systemd-sysusers.service", "8", &link
);
1736 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1737 "Creates system user accounts.\n\n"
1738 " -h --help Show this help\n"
1739 " --version Show package version\n"
1740 " --cat-config Show configuration files\n"
1741 " --root=PATH Operate on an alternate filesystem root\n"
1742 " --replace=PATH Treat arguments as replacement for PATH\n"
1743 " --inline Treat arguments as configuration lines\n"
1744 " --no-pager Do not pipe output into a pager\n"
1745 "\nSee the %s for details.\n"
1746 , program_invocation_short_name
1753 static int parse_argv(int argc
, char *argv
[]) {
1756 ARG_VERSION
= 0x100,
1764 static const struct option options
[] = {
1765 { "help", no_argument
, NULL
, 'h' },
1766 { "version", no_argument
, NULL
, ARG_VERSION
},
1767 { "cat-config", no_argument
, NULL
, ARG_CAT_CONFIG
},
1768 { "root", required_argument
, NULL
, ARG_ROOT
},
1769 { "replace", required_argument
, NULL
, ARG_REPLACE
},
1770 { "inline", no_argument
, NULL
, ARG_INLINE
},
1771 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1780 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
1790 case ARG_CAT_CONFIG
:
1791 arg_cat_config
= true;
1795 r
= parse_path_argument_and_warn(optarg
, /* suppress_root= */ false, &arg_root
);
1801 if (!path_is_absolute(optarg
) ||
1802 !endswith(optarg
, ".conf"))
1803 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1804 "The argument to --replace= must an absolute path to a config file");
1806 arg_replace
= optarg
;
1814 arg_pager_flags
|= PAGER_DISABLE
;
1821 assert_not_reached("Unhandled option");
1824 if (arg_replace
&& arg_cat_config
)
1825 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1826 "Option --replace= is not supported with --cat-config");
1828 if (arg_replace
&& optind
>= argc
)
1829 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1830 "When --replace= is given, some configuration items must be specified");
1835 static int parse_arguments(char **args
) {
1840 STRV_FOREACH(arg
, args
) {
1842 /* Use (argument):n, where n==1 for the first positional arg */
1843 r
= parse_line("(argument)", pos
, *arg
);
1845 r
= read_config_file(*arg
, false);
1855 static int read_config_files(char **args
) {
1856 _cleanup_strv_free_
char **files
= NULL
;
1857 _cleanup_free_
char *p
= NULL
;
1861 r
= conf_files_list_with_replacement(arg_root
, CONF_PATHS_STRV("sysusers.d"), arg_replace
, &files
, &p
);
1865 STRV_FOREACH(f
, files
)
1866 if (p
&& path_equal(*f
, p
)) {
1867 log_debug("Parsing arguments at position \"%s\"…", *f
);
1869 r
= parse_arguments(args
);
1873 log_debug("Reading config file \"%s\"…", *f
);
1875 /* Just warn, ignore result otherwise */
1876 (void) read_config_file(*f
, true);
1882 static int run(int argc
, char *argv
[]) {
1883 _cleanup_close_
int lock
= -1;
1888 r
= parse_argv(argc
, argv
);
1892 log_setup_service();
1895 return cat_config();
1899 r
= mac_selinux_init();
1901 return log_error_errno(r
, "SELinux setup failed: %m");
1903 /* If command line arguments are specified along with --replace, read all
1904 * configuration files and insert the positional arguments at the specified
1905 * place. Otherwise, if command line arguments are specified, execute just
1906 * them, and finally, without --replace= or any positional arguments, just
1907 * read configuration and execute it.
1909 if (arg_replace
|| optind
>= argc
)
1910 r
= read_config_files(argv
+ optind
);
1912 r
= parse_arguments(argv
+ optind
);
1916 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1917 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1918 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1919 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1921 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0)
1922 return log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1925 /* Default to default range of 1..SYSTEM_UID_MAX */
1926 r
= uid_range_add(&uid_range
, &n_uid_range
, 1, SYSTEM_UID_MAX
);
1935 lock
= take_etc_passwd_lock(arg_root
);
1937 return log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
1939 r
= load_user_database();
1941 return log_error_errno(r
, "Failed to load user database: %m");
1943 r
= load_group_database();
1945 return log_error_errno(r
, "Failed to read group database: %m");
1947 ORDERED_HASHMAP_FOREACH(i
, groups
, iterator
)
1948 (void) process_item(i
);
1950 ORDERED_HASHMAP_FOREACH(i
, users
, iterator
)
1951 (void) process_item(i
);
1955 return log_error_errno(r
, "Failed to write files: %m");
1960 DEFINE_MAIN_FUNCTION(run
);