1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
10 #include "conf-files.h"
11 #include "constants.h"
13 #include "creds-util.h"
14 #include "dissect-image.h"
16 #include "errno-util.h"
17 #include "extract-word.h"
20 #include "format-util.h"
23 #include "image-policy.h"
24 #include "label-util.h"
25 #include "libaudit-util.h"
26 #include "libcrypt-util.h"
28 #include "loop-util.h"
29 #include "main-func.h"
30 #include "mount-util.h"
32 #include "parse-argument.h"
33 #include "path-util.h"
34 #include "pretty-print.h"
36 #include "smack-util.h"
37 #include "specifier.h"
38 #include "string-util.h"
40 #include "sync-util.h"
41 #include "time-util.h"
42 #include "tmpfile-util-label.h"
43 #include "uid-classification.h"
44 #include "uid-range.h"
45 #include "user-util.h"
48 typedef enum ItemType
{
55 static const char* item_type_to_string(ItemType t
) {
89 /* When set the group with the specified GID must exist
90 * and the check if a UID clashes with the GID is skipped.
102 static char *arg_root
= NULL
;
103 static char *arg_image
= NULL
;
104 static CatFlags arg_cat_flags
= CAT_CONFIG_OFF
;
105 static const char *arg_replace
= NULL
;
106 static bool arg_dry_run
= false;
107 static bool arg_inline
= false;
108 static PagerFlags arg_pager_flags
= 0;
109 static ImagePolicy
*arg_image_policy
= NULL
;
111 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
112 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
113 STATIC_DESTRUCTOR_REGISTER(arg_image_policy
, image_policy_freep
);
115 typedef struct Context
{
118 OrderedHashmap
*users
, *groups
;
119 OrderedHashmap
*todo_uids
, *todo_gids
;
120 OrderedHashmap
*members
;
122 Hashmap
*database_by_uid
, *database_by_username
;
123 Hashmap
*database_by_gid
, *database_by_groupname
;
125 /* A helper set to hold names that are used by database_by_{uid,gid,username,groupname} above. */
131 UGIDAllocationRange login_defs
;
132 bool login_defs_need_warning
;
135 static void context_done(Context
*c
) {
138 c
->audit_fd
= close_audit_fd(c
->audit_fd
);
140 ordered_hashmap_free(c
->groups
);
141 ordered_hashmap_free(c
->users
);
142 ordered_hashmap_free(c
->members
);
143 ordered_hashmap_free(c
->todo_uids
);
144 ordered_hashmap_free(c
->todo_gids
);
146 hashmap_free(c
->database_by_uid
);
147 hashmap_free(c
->database_by_username
);
148 hashmap_free(c
->database_by_gid
);
149 hashmap_free(c
->database_by_groupname
);
152 uid_range_free(c
->uid_range
);
155 static void maybe_emit_login_defs_warning(Context
*c
) {
158 if (!c
->login_defs_need_warning
)
161 if (c
->login_defs
.system_alloc_uid_min
!= SYSTEM_ALLOC_UID_MIN
||
162 c
->login_defs
.system_uid_max
!= SYSTEM_UID_MAX
)
163 log_warning("login.defs specifies UID allocation range "UID_FMT
"–"UID_FMT
164 " that is different than the built-in defaults ("UID_FMT
"–"UID_FMT
")",
165 c
->login_defs
.system_alloc_uid_min
, c
->login_defs
.system_uid_max
,
166 (uid_t
) SYSTEM_ALLOC_UID_MIN
, (uid_t
) SYSTEM_UID_MAX
);
167 if (c
->login_defs
.system_alloc_gid_min
!= SYSTEM_ALLOC_GID_MIN
||
168 c
->login_defs
.system_gid_max
!= SYSTEM_GID_MAX
)
169 log_warning("login.defs specifies GID allocation range "GID_FMT
"–"GID_FMT
170 " that is different than the built-in defaults ("GID_FMT
"–"GID_FMT
")",
171 c
->login_defs
.system_alloc_gid_min
, c
->login_defs
.system_gid_max
,
172 (gid_t
) SYSTEM_ALLOC_GID_MIN
, (gid_t
) SYSTEM_GID_MAX
);
174 c
->login_defs_need_warning
= false;
177 static void log_audit_accounts(Context
*c
, ItemType what
) {
180 assert(IN_SET(what
, ADD_USER
, ADD_GROUP
));
182 if (arg_dry_run
|| c
->audit_fd
< 0)
186 int type
= what
== ADD_USER
? AUDIT_ADD_USER
: AUDIT_ADD_GROUP
;
187 const char *op
= what
== ADD_USER
? "adding-user" : "adding-group";
191 * The op must not contain whitespace. The format with a dash matches what Fedora shadow-utils uses.
193 * We send id == -1, even though we know the number, in particular on success. This is because if we
194 * send the id, the generated audit message will not contain the name. The name seems more useful
195 * than the number, hence send just the name:
197 * type=ADD_USER msg=audit(01/10/2025 16:02:00.639:3854) :
198 * pid=3846380 uid=root auid=zbyszek ses=2 msg='op=adding-user id=unknown(952) exe=systemd-sysusers ... res=success'
200 * type=ADD_USER msg=audit(01/10/2025 16:03:15.457:3908) :
201 * pid=3846607 uid=root auid=zbyszek ses=2 msg='op=adding-user acct=foo5 exe=systemd-sysusers ... res=success'
204 ORDERED_HASHMAP_FOREACH(i
, what
== ADD_USER
? c
->todo_uids
: c
->todo_gids
)
205 audit_log_acct_message(
208 program_invocation_short_name
,
211 /* id= */ (unsigned) -1,
219 static int load_user_database(Context
*c
) {
220 _cleanup_free_
char *passwd_path
= NULL
;
221 _cleanup_fclose_
FILE *f
= NULL
;
227 r
= chase_and_fopen_unlocked("/etc/passwd", arg_root
, CHASE_PREFIX_ROOT
, "re", &passwd_path
, &f
);
233 while ((r
= fgetpwent_sane(f
, &pw
)) > 0) {
235 char *n
= strdup(pw
->pw_name
);
239 /* Note that we use trivial_hash_ops_free here, so identical strings can exist in the set. */
240 r
= set_ensure_consume(&c
->names
, &trivial_hash_ops_free
, n
);
243 assert(r
> 0); /* The set uses pointer comparisons, so n must not be in the set. */
245 r
= hashmap_ensure_put(&c
->database_by_username
, &string_hash_ops
, n
, UID_TO_PTR(pw
->pw_uid
));
247 log_debug_errno(r
, "%s: user '%s' is listed twice, ignoring duplicate uid.",
252 r
= hashmap_ensure_put(&c
->database_by_uid
, /* hash_ops= */ NULL
, UID_TO_PTR(pw
->pw_uid
), n
);
254 log_debug_errno(r
, "%s: uid "UID_FMT
" is listed twice, ignoring duplicate name.",
255 passwd_path
, pw
->pw_uid
);
262 static int load_group_database(Context
*c
) {
263 _cleanup_free_
char *group_path
= NULL
;
264 _cleanup_fclose_
FILE *f
= NULL
;
270 r
= chase_and_fopen_unlocked("/etc/group", arg_root
, CHASE_PREFIX_ROOT
, "re", &group_path
, &f
);
276 while ((r
= fgetgrent_sane(f
, &gr
)) > 0) {
277 char *n
= strdup(gr
->gr_name
);
281 /* Note that we use trivial_hash_ops_free here, so identical strings can exist in the set. */
282 r
= set_ensure_consume(&c
->names
, &trivial_hash_ops_free
, n
);
285 assert(r
> 0); /* The set uses pointer comparisons, so n must not be in the set. */
287 r
= hashmap_ensure_put(&c
->database_by_groupname
, &string_hash_ops
, n
, GID_TO_PTR(gr
->gr_gid
));
289 log_debug_errno(r
, "%s: group '%s' is listed twice, ignoring duplicate gid.",
294 r
= hashmap_ensure_put(&c
->database_by_gid
, /* hash_ops= */ NULL
, GID_TO_PTR(gr
->gr_gid
), n
);
296 log_debug_errno(r
, "%s: gid "GID_FMT
" is listed twice, ignoring duplicate name.",
297 group_path
, gr
->gr_gid
);
304 static int make_backup(const char *target
, const char *x
) {
305 _cleanup_(unlink_and_freep
) char *dst_tmp
= NULL
;
306 _cleanup_fclose_
FILE *dst
= NULL
;
307 _cleanup_close_
int src
= -EBADF
;
315 src
= open(x
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
317 if (errno
== ENOENT
) /* No backup necessary... */
323 if (fstat(src
, &st
) < 0)
326 r
= fopen_temporary_label(
327 target
, /* The path for which to the look up the label */
328 x
, /* Where we want the file actually to end up */
329 &dst
, /* The temporary file we write to */
334 r
= copy_bytes(src
, fileno(dst
), UINT64_MAX
, COPY_REFLINK
);
338 backup
= strjoina(x
, "-");
340 /* Copy over the access mask. Don't fail on chmod() or chown(). If it stays owned by us and/or
341 * unreadable by others, then it isn't too bad... */
342 r
= fchmod_and_chown_with_fallback(fileno(dst
), dst_tmp
, st
.st_mode
& 07777, st
.st_uid
, st
.st_gid
);
344 log_warning_errno(r
, "Failed to change access mode or ownership of %s: %m", backup
);
346 if (futimens(fileno(dst
), (const struct timespec
[2]) { st
.st_atim
, st
.st_mtim
}) < 0)
347 log_warning_errno(errno
, "Failed to fix access and modification time of %s: %m", backup
);
349 r
= fsync_full(fileno(dst
));
353 if (rename(dst_tmp
, backup
) < 0)
356 dst_tmp
= mfree(dst_tmp
); /* disable the unlink_and_freep() hook now that the file has been renamed */
360 static int putgrent_with_members(
362 const struct group
*gr
,
372 a
= ordered_hashmap_get(c
->members
, gr
->gr_name
);
374 _cleanup_strv_free_
char **l
= NULL
;
377 l
= strv_copy(gr
->gr_mem
);
382 if (strv_contains(l
, *i
))
385 r
= strv_extend(&l
, *i
);
400 r
= putgrent_sane(&t
, group
);
401 return r
< 0 ? r
: 1;
405 return putgrent_sane(gr
, group
);
409 static int putsgent_with_members(
411 const struct sgrp
*sg
,
420 a
= ordered_hashmap_get(c
->members
, sg
->sg_namp
);
422 _cleanup_strv_free_
char **l
= NULL
;
425 l
= strv_copy(sg
->sg_mem
);
430 if (strv_contains(l
, *i
))
433 r
= strv_extend(&l
, *i
);
448 r
= putsgent_sane(&t
, gshadow
);
449 return r
< 0 ? r
: 1;
453 return putsgent_sane(sg
, gshadow
);
457 static const char* pick_shell(const Item
*i
) {
460 if (i
->type
!= ADD_USER
)
464 if (i
->uid_set
&& i
->uid
== 0)
465 return default_root_shell(arg_root
);
469 static int write_temporary_passwd(
471 const char *passwd_path
,
473 char **ret_tmpfile_path
) {
475 _cleanup_fclose_
FILE *original
= NULL
, *passwd
= NULL
;
476 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
;
477 struct passwd
*pw
= NULL
;
483 if (ordered_hashmap_isempty(c
->todo_uids
))
487 log_info("Would write /etc/passwd%s", glyph(GLYPH_ELLIPSIS
));
491 r
= fopen_temporary_label("/etc/passwd", passwd_path
, &passwd
, &passwd_tmp
);
493 return log_debug_errno(r
, "Failed to open temporary copy of %s: %m", passwd_path
);
495 original
= fopen(passwd_path
, "re");
498 /* Allow fallback path for when /proc is not mounted. On any normal system /proc will be
499 * mounted, but e.g. when 'dnf --installroot' is used, it might not be. There is no security
500 * relevance here, since the environment is ultimately trusted, and not requiring /proc makes
501 * it easier to depend on sysusers in packaging scripts and suchlike. */
502 r
= copy_rights_with_fallback(fileno(original
), fileno(passwd
), passwd_tmp
);
504 return log_debug_errno(r
, "Failed to copy permissions from %s to %s: %m",
505 passwd_path
, passwd_tmp
);
507 while ((r
= fgetpwent_sane(original
, &pw
)) > 0) {
508 i
= ordered_hashmap_get(c
->users
, pw
->pw_name
);
509 if (i
&& i
->todo_user
)
510 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
511 "%s: User \"%s\" already exists.",
512 passwd_path
, pw
->pw_name
);
514 if (ordered_hashmap_contains(c
->todo_uids
, UID_TO_PTR(pw
->pw_uid
)))
515 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
516 "%s: Detected collision for UID " UID_FMT
".",
517 passwd_path
, pw
->pw_uid
);
519 /* Make sure we keep the NIS entries (if any) at the end. */
520 if (IN_SET(pw
->pw_name
[0], '+', '-'))
523 r
= putpwent_sane(pw
, passwd
);
525 return log_debug_errno(r
, "Failed to add existing user \"%s\" to temporary passwd file: %m",
529 return log_debug_errno(r
, "Failed to read %s: %m", passwd_path
);
533 return log_debug_errno(errno
, "Failed to open %s: %m", passwd_path
);
534 if (fchmod(fileno(passwd
), 0644) < 0)
535 return log_debug_errno(errno
, "Failed to fchmod %s: %m", passwd_tmp
);
538 ORDERED_HASHMAP_FOREACH(i
, c
->todo_uids
) {
539 _cleanup_free_
char *creds_shell
= NULL
, *cn
= NULL
;
545 .pw_gecos
= (char*) strempty(i
->description
),
547 /* "x" means the password is stored in the shadow file */
548 .pw_passwd
= (char*) PASSWORD_SEE_SHADOW
,
550 /* We default to the root directory as home */
551 .pw_dir
= i
->home
?: (char*) "/",
553 /* Initialize the shell to nologin, with one exception:
554 * for root we patch in something special */
555 .pw_shell
= (char*) pick_shell(i
),
558 /* Try to pick up the shell for this account via the credentials logic */
559 cn
= strjoin("passwd.shell.", i
->name
);
563 r
= read_credential(cn
, (void**) &creds_shell
, NULL
);
565 log_debug_errno(r
, "Couldn't read credential '%s', ignoring: %m", cn
);
567 n
.pw_shell
= creds_shell
;
569 r
= putpwent_sane(&n
, passwd
);
571 return log_debug_errno(r
, "Failed to add new user \"%s\" to temporary passwd file: %m",
575 /* Append the remaining NIS entries if any */
577 r
= putpwent_sane(pw
, passwd
);
579 return log_debug_errno(r
, "Failed to add existing user \"%s\" to temporary passwd file: %m",
582 r
= fgetpwent_sane(original
, &pw
);
584 return log_debug_errno(r
, "Failed to read %s: %m", passwd_path
);
589 r
= fflush_sync_and_check(passwd
);
591 return log_debug_errno(r
, "Failed to flush %s: %m", passwd_tmp
);
594 *ret_tmpfile
= TAKE_PTR(passwd
);
595 *ret_tmpfile_path
= TAKE_PTR(passwd_tmp
);
599 static usec_t
epoch_or_now(void) {
602 if (secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch
) >= 0) {
603 if (epoch
> UINT64_MAX
/USEC_PER_SEC
) /* Overflow check */
604 return USEC_INFINITY
;
605 return (usec_t
) epoch
* USEC_PER_SEC
;
608 return now(CLOCK_REALTIME
);
611 static int write_temporary_shadow(
613 const char *shadow_path
,
615 char **ret_tmpfile_path
) {
617 _cleanup_fclose_
FILE *original
= NULL
, *shadow
= NULL
;
618 _cleanup_(unlink_and_freep
) char *shadow_tmp
= NULL
;
619 struct spwd
*sp
= NULL
;
626 if (ordered_hashmap_isempty(c
->todo_uids
))
630 log_info("Would write /etc/shadow%s", glyph(GLYPH_ELLIPSIS
));
634 r
= fopen_temporary_label("/etc/shadow", shadow_path
, &shadow
, &shadow_tmp
);
636 return log_debug_errno(r
, "Failed to open temporary copy of %s: %m", shadow_path
);
638 lstchg
= (long) (epoch_or_now() / USEC_PER_DAY
);
640 original
= fopen(shadow_path
, "re");
643 r
= copy_rights_with_fallback(fileno(original
), fileno(shadow
), shadow_tmp
);
645 return log_debug_errno(r
, "Failed to copy permissions from %s to %s: %m",
646 shadow_path
, shadow_tmp
);
648 while ((r
= fgetspent_sane(original
, &sp
)) > 0) {
649 i
= ordered_hashmap_get(c
->users
, sp
->sp_namp
);
650 if (i
&& i
->todo_user
) {
651 /* we will update the existing entry */
652 sp
->sp_lstchg
= lstchg
;
654 /* only the /etc/shadow stage is left, so we can
655 * safely remove the item from the todo set */
656 i
->todo_user
= false;
657 ordered_hashmap_remove(c
->todo_uids
, UID_TO_PTR(i
->uid
));
660 /* Make sure we keep the NIS entries (if any) at the end. */
661 if (IN_SET(sp
->sp_namp
[0], '+', '-'))
664 r
= putspent_sane(sp
, shadow
);
666 return log_debug_errno(r
, "Failed to add existing user \"%s\" to temporary shadow file: %m",
671 return log_debug_errno(r
, "Failed to read %s: %m", shadow_path
);
675 return log_debug_errno(errno
, "Failed to open %s: %m", shadow_path
);
676 if (fchmod(fileno(shadow
), 0000) < 0)
677 return log_debug_errno(errno
, "Failed to fchmod %s: %m", shadow_tmp
);
680 ORDERED_HASHMAP_FOREACH(i
, c
->todo_uids
) {
681 _cleanup_(erase_and_freep
) char *creds_password
= NULL
;
691 .sp_expire
= i
->locked
? 1 : -1, /* Negative expiration means "unset". Expiration 0 or 1 means "locked" */
692 .sp_flag
= ULONG_MAX
, /* this appears to be what everybody does ... */
695 r
= get_credential_user_password(i
->name
, &creds_password
, &is_hashed
);
697 log_debug_errno(r
, "Couldn't read password credential for user '%s', ignoring: %m", i
->name
);
699 if (creds_password
&& !is_hashed
) {
700 _cleanup_(erase_and_freep
) char* plaintext_password
= TAKE_PTR(creds_password
);
701 r
= hash_password(plaintext_password
, &creds_password
);
703 return log_debug_errno(r
, "Failed to hash password: %m");
707 n
.sp_pwdp
= creds_password
;
708 else if (streq(i
->name
, "root"))
709 /* Let firstboot set the password later */
710 n
.sp_pwdp
= (char*) PASSWORD_UNPROVISIONED
;
712 n
.sp_pwdp
= (char*) PASSWORD_LOCKED_AND_INVALID
;
714 r
= putspent_sane(&n
, shadow
);
716 return log_debug_errno(r
, "Failed to add new user \"%s\" to temporary shadow file: %m",
720 /* Append the remaining NIS entries if any */
722 r
= putspent_sane(sp
, shadow
);
724 return log_debug_errno(r
, "Failed to add existing user \"%s\" to temporary shadow file: %m",
727 r
= fgetspent_sane(original
, &sp
);
729 return log_debug_errno(r
, "Failed to read %s: %m", shadow_path
);
733 if (!IN_SET(errno
, 0, ENOENT
))
736 r
= fflush_sync_and_check(shadow
);
738 return log_debug_errno(r
, "Failed to flush %s: %m", shadow_tmp
);
741 *ret_tmpfile
= TAKE_PTR(shadow
);
742 *ret_tmpfile_path
= TAKE_PTR(shadow_tmp
);
746 static int write_temporary_group(
748 const char *group_path
,
750 char **ret_tmpfile_path
) {
752 _cleanup_fclose_
FILE *original
= NULL
, *group
= NULL
;
753 _cleanup_(unlink_and_freep
) char *group_tmp
= NULL
;
754 bool group_changed
= false;
755 struct group
*gr
= NULL
;
761 if (ordered_hashmap_isempty(c
->todo_gids
) && ordered_hashmap_isempty(c
->members
))
765 log_info("Would write /etc/group%s", glyph(GLYPH_ELLIPSIS
));
769 r
= fopen_temporary_label("/etc/group", group_path
, &group
, &group_tmp
);
771 return log_error_errno(r
, "Failed to open temporary copy of %s: %m", group_path
);
773 original
= fopen(group_path
, "re");
776 r
= copy_rights_with_fallback(fileno(original
), fileno(group
), group_tmp
);
778 return log_error_errno(r
, "Failed to copy permissions from %s to %s: %m",
779 group_path
, group_tmp
);
781 while ((r
= fgetgrent_sane(original
, &gr
)) > 0) {
782 /* Safety checks against name and GID collisions. Normally,
783 * this should be unnecessary, but given that we look at the
784 * entries anyway here, let's make an extra verification
785 * step that we don't generate duplicate entries. */
787 i
= ordered_hashmap_get(c
->groups
, gr
->gr_name
);
788 if (i
&& i
->todo_group
)
789 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
790 "%s: Group \"%s\" already exists.",
791 group_path
, gr
->gr_name
);
793 if (ordered_hashmap_contains(c
->todo_gids
, GID_TO_PTR(gr
->gr_gid
)))
794 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
795 "%s: Detected collision for GID " GID_FMT
".",
796 group_path
, gr
->gr_gid
);
798 /* Make sure we keep the NIS entries (if any) at the end. */
799 if (IN_SET(gr
->gr_name
[0], '+', '-'))
802 r
= putgrent_with_members(c
, gr
, group
);
804 return log_error_errno(r
, "Failed to add existing group \"%s\" to temporary group file: %m",
807 group_changed
= true;
810 return log_error_errno(r
, "Failed to read %s: %m", group_path
);
814 return log_error_errno(errno
, "Failed to open %s: %m", group_path
);
815 if (fchmod(fileno(group
), 0644) < 0)
816 return log_error_errno(errno
, "Failed to fchmod %s: %m", group_tmp
);
819 ORDERED_HASHMAP_FOREACH(i
, c
->todo_gids
) {
823 .gr_passwd
= (char*) PASSWORD_SEE_SHADOW
,
826 r
= putgrent_with_members(c
, &n
, group
);
828 return log_error_errno(r
, "Failed to add new group \"%s\" to temporary group file: %m",
831 group_changed
= true;
834 /* Append the remaining NIS entries if any */
836 r
= putgrent_sane(gr
, group
);
838 return log_error_errno(r
, "Failed to add existing group \"%s\" to temporary group file: %m",
841 r
= fgetgrent_sane(original
, &gr
);
843 return log_error_errno(r
, "Failed to read %s: %m", group_path
);
848 r
= fflush_sync_and_check(group
);
850 return log_error_errno(r
, "Failed to flush %s: %m", group_tmp
);
854 *ret_tmpfile
= TAKE_PTR(group
);
855 *ret_tmpfile_path
= TAKE_PTR(group_tmp
);
858 *ret_tmpfile_path
= NULL
;
863 static int write_temporary_gshadow(
865 const char * gshadow_path
,
867 char **ret_tmpfile_path
) {
870 _cleanup_fclose_
FILE *original
= NULL
, *gshadow
= NULL
;
871 _cleanup_(unlink_and_freep
) char *gshadow_tmp
= NULL
;
872 bool group_changed
= false;
878 if (ordered_hashmap_isempty(c
->todo_gids
) && ordered_hashmap_isempty(c
->members
))
882 log_info("Would write /etc/gshadow%s", glyph(GLYPH_ELLIPSIS
));
886 r
= fopen_temporary_label("/etc/gshadow", gshadow_path
, &gshadow
, &gshadow_tmp
);
888 return log_error_errno(r
, "Failed to open temporary copy of %s: %m", gshadow_path
);
890 original
= fopen(gshadow_path
, "re");
894 r
= copy_rights_with_fallback(fileno(original
), fileno(gshadow
), gshadow_tmp
);
896 return log_error_errno(r
, "Failed to copy permissions from %s to %s: %m",
897 gshadow_path
, gshadow_tmp
);
899 while ((r
= fgetsgent_sane(original
, &sg
)) > 0) {
901 i
= ordered_hashmap_get(c
->groups
, sg
->sg_namp
);
902 if (i
&& i
->todo_group
)
903 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
904 "%s: Group \"%s\" already exists.",
905 gshadow_path
, sg
->sg_namp
);
907 r
= putsgent_with_members(c
, sg
, gshadow
);
909 return log_error_errno(r
, "Failed to add existing group \"%s\" to temporary gshadow file: %m",
912 group_changed
= true;
919 return log_error_errno(errno
, "Failed to open %s: %m", gshadow_path
);
920 if (fchmod(fileno(gshadow
), 0000) < 0)
921 return log_error_errno(errno
, "Failed to fchmod %s: %m", gshadow_tmp
);
924 ORDERED_HASHMAP_FOREACH(i
, c
->todo_gids
) {
927 .sg_passwd
= (char*) PASSWORD_LOCKED_AND_INVALID
,
930 r
= putsgent_with_members(c
, &n
, gshadow
);
932 return log_error_errno(r
, "Failed to add new group \"%s\" to temporary gshadow file: %m",
935 group_changed
= true;
938 r
= fflush_sync_and_check(gshadow
);
940 return log_error_errno(r
, "Failed to flush %s: %m", gshadow_tmp
);
944 *ret_tmpfile
= TAKE_PTR(gshadow
);
945 *ret_tmpfile_path
= TAKE_PTR(gshadow_tmp
);
950 *ret_tmpfile_path
= NULL
;
955 static int write_files(Context
*c
) {
956 _cleanup_fclose_
FILE *passwd
= NULL
, *group
= NULL
, *shadow
= NULL
, *gshadow
= NULL
;
957 _cleanup_(unlink_and_freep
) char *passwd_tmp
= NULL
, *group_tmp
= NULL
, *shadow_tmp
= NULL
, *gshadow_tmp
= NULL
;
960 _cleanup_free_
char *passwd_path
= path_join(arg_root
, "/etc/passwd");
964 _cleanup_free_
char *shadow_path
= path_join(arg_root
, "/etc/shadow");
968 _cleanup_free_
char *group_path
= path_join(arg_root
, "/etc/group");
972 _cleanup_free_
char *gshadow_path
= path_join(arg_root
, "/etc/gshadow");
978 r
= write_temporary_group(c
, group_path
, &group
, &group_tmp
);
982 r
= write_temporary_gshadow(c
, gshadow_path
, &gshadow
, &gshadow_tmp
);
986 r
= write_temporary_passwd(c
, passwd_path
, &passwd
, &passwd_tmp
);
990 r
= write_temporary_shadow(c
, shadow_path
, &shadow
, &shadow_tmp
);
994 /* Make a backup of the old files */
996 r
= make_backup("/etc/group", group_path
);
998 return log_error_errno(r
, "Failed to backup %s: %m", group_path
);
1001 r
= make_backup("/etc/gshadow", gshadow_path
);
1003 return log_error_errno(r
, "Failed to backup %s: %m", gshadow_path
);
1007 r
= make_backup("/etc/passwd", passwd_path
);
1009 return log_error_errno(r
, "Failed to backup %s: %m", passwd_path
);
1012 r
= make_backup("/etc/shadow", shadow_path
);
1014 return log_error_errno(r
, "Failed to backup %s: %m", shadow_path
);
1017 /* And make the new files count */
1019 r
= rename_and_apply_smack_floor_label(group_tmp
, group_path
);
1021 return log_error_errno(r
, "Failed to rename %s to %s: %m",
1022 group_tmp
, group_path
);
1023 group_tmp
= mfree(group_tmp
);
1025 /* OK, we have written the group entries successfully */
1026 log_audit_accounts(c
, ADD_GROUP
);
1028 r
= rename_and_apply_smack_floor_label(gshadow_tmp
, gshadow_path
);
1030 return log_error_errno(r
, "Failed to rename %s to %s: %m",
1031 gshadow_tmp
, gshadow_path
);
1033 gshadow_tmp
= mfree(gshadow_tmp
);
1037 r
= rename_and_apply_smack_floor_label(passwd_tmp
, passwd_path
);
1039 return log_error_errno(r
, "Failed to rename %s to %s: %m",
1040 passwd_tmp
, passwd_path
);
1042 passwd_tmp
= mfree(passwd_tmp
);
1044 /* OK, we have written the user entries successfully */
1045 log_audit_accounts(c
, ADD_USER
);
1047 r
= rename_and_apply_smack_floor_label(shadow_tmp
, shadow_path
);
1049 return log_error_errno(r
, "Failed to rename %s to %s: %m",
1050 shadow_tmp
, shadow_path
);
1052 shadow_tmp
= mfree(shadow_tmp
);
1058 static int uid_is_ok(
1062 bool check_with_gid
) {
1067 /* Let's see if we already have assigned the UID a second time */
1068 if (ordered_hashmap_get(c
->todo_uids
, UID_TO_PTR(uid
)))
1071 /* Try to avoid using uids that are already used by a group
1072 * that doesn't have the same name as our new user. */
1073 if (check_with_gid
) {
1076 i
= ordered_hashmap_get(c
->todo_gids
, GID_TO_PTR(uid
));
1077 if (i
&& !streq(i
->name
, name
))
1081 /* Let's check the files directly */
1082 if (hashmap_contains(c
->database_by_uid
, UID_TO_PTR(uid
)))
1085 if (check_with_gid
) {
1088 n
= hashmap_get(c
->database_by_gid
, GID_TO_PTR(uid
));
1089 if (n
&& !streq(n
, name
))
1093 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
1095 _cleanup_free_
struct group
*g
= NULL
;
1097 r
= getpwuid_malloc(uid
, /* ret= */ NULL
);
1101 log_warning_errno(r
, "Unexpected failure while looking up UID '" UID_FMT
"' via NSS, assuming it doesn't exist: %m", uid
);
1103 if (check_with_gid
) {
1104 r
= getgrgid_malloc((gid_t
) uid
, &g
);
1106 if (!streq(g
->gr_name
, name
))
1108 } else if (r
!= -ESRCH
)
1109 log_warning_errno(r
, "Unexpected failure while looking up GID '" GID_FMT
"' via NSS, assuming it doesn't exist: %m", uid
);
1116 static int root_stat(const char *p
, struct stat
*ret_st
) {
1117 return chase_and_stat(p
, arg_root
, CHASE_PREFIX_ROOT
, /* ret_path= */ NULL
, ret_st
);
1120 static int read_id_from_file(Item
*i
, uid_t
*ret_uid
, gid_t
*ret_gid
) {
1122 bool found_uid
= false, found_gid
= false;
1128 /* First, try to get the GID directly */
1129 if (ret_gid
&& i
->gid_path
&& root_stat(i
->gid_path
, &st
) >= 0) {
1134 /* Then, try to get the UID directly */
1135 if ((ret_uid
|| (ret_gid
&& !found_gid
))
1137 && root_stat(i
->uid_path
, &st
) >= 0) {
1142 /* If we need the gid, but had no success yet, also derive it from the UID path */
1143 if (ret_gid
&& !found_gid
) {
1149 /* If that didn't work yet, then let's reuse the GID as UID */
1150 if (ret_uid
&& !found_uid
&& i
->gid_path
) {
1155 } else if (root_stat(i
->gid_path
, &st
) >= 0) {
1156 uid
= (uid_t
) st
.st_gid
;
1178 static int add_user(Context
*c
, Item
*i
) {
1185 /* Check the database directly */
1186 z
= hashmap_get(c
->database_by_username
, i
->name
);
1188 log_debug("User %s already exists.", i
->name
);
1189 i
->uid
= PTR_TO_UID(z
);
1195 _cleanup_free_
struct passwd
*p
= NULL
;
1197 /* Also check NSS */
1198 r
= getpwnam_malloc(i
->name
, &p
);
1200 log_debug("User %s already exists.", i
->name
);
1204 r
= free_and_strdup(&i
->description
, p
->pw_gecos
);
1211 log_warning_errno(r
, "Unexpected failure while looking up user '%s' via NSS, assuming it doesn't exist: %m", i
->name
);
1214 /* Try to use the suggested numeric UID */
1216 r
= uid_is_ok(c
, i
->uid
, i
->name
, !i
->id_set_strict
);
1218 return log_error_errno(r
, "Failed to verify UID " UID_FMT
": %m", i
->uid
);
1220 log_info("Suggested user ID " UID_FMT
" for %s already used.", i
->uid
, i
->name
);
1225 /* If that didn't work, try to read it from the specified path */
1229 if (read_id_from_file(i
, &candidate
, NULL
) > 0) {
1231 if (candidate
<= 0 || !uid_range_contains(c
->uid_range
, candidate
))
1232 log_debug("User ID " UID_FMT
" of file not suitable for %s.", candidate
, i
->name
);
1234 r
= uid_is_ok(c
, candidate
, i
->name
, true);
1236 return log_error_errno(r
, "Failed to verify UID " UID_FMT
": %m", i
->uid
);
1241 log_debug("User ID " UID_FMT
" of file for %s is already used.", candidate
, i
->name
);
1246 /* Otherwise, try to reuse the group ID */
1247 if (!i
->uid_set
&& i
->gid_set
) {
1248 r
= uid_is_ok(c
, (uid_t
) i
->gid
, i
->name
, true);
1250 return log_error_errno(r
, "Failed to verify UID " UID_FMT
": %m", i
->uid
);
1252 i
->uid
= (uid_t
) i
->gid
;
1257 /* And if that didn't work either, let's try to find a free one */
1259 maybe_emit_login_defs_warning(c
);
1262 r
= uid_range_next_lower(c
->uid_range
, &c
->search_uid
);
1264 return log_error_errno(r
, "No free user ID available for %s.", i
->name
);
1266 r
= uid_is_ok(c
, c
->search_uid
, i
->name
, true);
1268 return log_error_errno(r
, "Failed to verify UID " UID_FMT
": %m", i
->uid
);
1274 i
->uid
= c
->search_uid
;
1277 r
= ordered_hashmap_ensure_put(&c
->todo_uids
, NULL
, UID_TO_PTR(i
->uid
), i
);
1279 return log_error_errno(r
, "Requested user %s with UID " UID_FMT
" and gid" GID_FMT
" to be created is duplicated "
1280 "or conflicts with another user.", i
->name
, i
->uid
, i
->gid
);
1284 return log_error_errno(r
, "Failed to store user %s with UID " UID_FMT
" and GID " GID_FMT
" to be created: %m",
1285 i
->name
, i
->uid
, i
->gid
);
1287 i
->todo_user
= true;
1288 log_info("Creating user '%s' (%s) with UID " UID_FMT
" and GID " GID_FMT
".",
1289 i
->name
, strna(i
->description
), i
->uid
, i
->gid
);
1294 static int gid_is_ok(
1297 const char *groupname
,
1298 bool check_with_uid
) {
1307 if (ordered_hashmap_get(c
->todo_gids
, GID_TO_PTR(gid
)))
1310 /* Avoid reusing gids that are already used by a different user */
1311 if (check_with_uid
) {
1312 user
= ordered_hashmap_get(c
->todo_uids
, UID_TO_PTR(gid
));
1313 if (user
&& !streq(user
->name
, groupname
))
1317 if (hashmap_contains(c
->database_by_gid
, GID_TO_PTR(gid
)))
1320 if (check_with_uid
) {
1321 username
= hashmap_get(c
->database_by_uid
, UID_TO_PTR(gid
));
1322 if (username
&& !streq(username
, groupname
))
1327 r
= getgrgid_malloc(gid
, /* ret= */ NULL
);
1331 log_warning_errno(r
, "Unexpected failure while looking up GID '" GID_FMT
"' via NSS, assuming it doesn't exist: %m", gid
);
1333 if (check_with_uid
) {
1334 r
= getpwuid_malloc(gid
, /* ret= */ NULL
);
1338 log_warning_errno(r
, "Unexpected failure while looking up GID '" GID_FMT
"' via NSS, assuming it doesn't exist: %m", gid
);
1345 static int get_gid_by_name(
1356 /* Check the database directly */
1357 z
= hashmap_get(c
->database_by_groupname
, name
);
1359 *ret_gid
= PTR_TO_GID(z
);
1363 /* Also check NSS */
1365 _cleanup_free_
struct group
*g
= NULL
;
1367 r
= getgrnam_malloc(name
, &g
);
1369 *ret_gid
= g
->gr_gid
;
1373 log_warning_errno(r
, "Unexpected failure while looking up group '%s' via NSS, assuming it doesn't exist: %m", name
);
1379 static int add_group(Context
*c
, Item
*i
) {
1385 r
= get_gid_by_name(c
, i
->name
, &i
->gid
);
1389 log_debug("Group %s already exists.", i
->name
);
1394 /* Try to use the suggested numeric GID */
1396 r
= gid_is_ok(c
, i
->gid
, i
->name
, false);
1398 return log_error_errno(r
, "Failed to verify GID " GID_FMT
": %m", i
->gid
);
1399 if (i
->id_set_strict
) {
1400 /* If we require the GID to already exist we can return here:
1401 * r > 0: means the GID does not exist -> fail
1402 * r == 0: means the GID exists -> nothing more to do.
1405 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1406 "Failed to create %s: please create GID " GID_FMT
,
1412 log_info("Suggested group ID " GID_FMT
" for %s already used.", i
->gid
, i
->name
);
1417 /* Try to reuse the numeric uid, if there's one */
1418 if (!i
->gid_set
&& i
->uid_set
) {
1419 r
= gid_is_ok(c
, (gid_t
) i
->uid
, i
->name
, true);
1421 return log_error_errno(r
, "Failed to verify GID " GID_FMT
": %m", i
->gid
);
1423 i
->gid
= (gid_t
) i
->uid
;
1428 /* If that didn't work, try to read it from the specified path */
1432 if (read_id_from_file(i
, NULL
, &candidate
) > 0) {
1434 if (candidate
<= 0 || !uid_range_contains(c
->uid_range
, candidate
))
1435 log_debug("Group ID " GID_FMT
" of file not suitable for %s.", candidate
, i
->name
);
1437 r
= gid_is_ok(c
, candidate
, i
->name
, true);
1439 return log_error_errno(r
, "Failed to verify GID " GID_FMT
": %m", i
->gid
);
1444 log_debug("Group ID " GID_FMT
" of file for %s already used.", candidate
, i
->name
);
1449 /* And if that didn't work either, let's try to find a free one */
1451 maybe_emit_login_defs_warning(c
);
1454 /* We look for new GIDs in the UID pool! */
1455 r
= uid_range_next_lower(c
->uid_range
, &c
->search_uid
);
1457 return log_error_errno(r
, "No free group ID available for %s.", i
->name
);
1459 r
= gid_is_ok(c
, c
->search_uid
, i
->name
, true);
1461 return log_error_errno(r
, "Failed to verify GID " GID_FMT
": %m", i
->gid
);
1467 i
->gid
= c
->search_uid
;
1470 r
= ordered_hashmap_ensure_put(&c
->todo_gids
, NULL
, GID_TO_PTR(i
->gid
), i
);
1472 return log_error_errno(r
, "Requested group %s with GID "GID_FMT
" to be created is duplicated or conflicts with another user.", i
->name
, i
->gid
);
1476 return log_error_errno(r
, "Failed to store group %s with GID " GID_FMT
" to be created: %m", i
->name
, i
->gid
);
1478 i
->todo_group
= true;
1479 log_info("Creating group '%s' with GID " GID_FMT
".", i
->name
, i
->gid
);
1484 static int process_item(Context
*c
, Item
*i
) {
1496 j
= ordered_hashmap_get(c
->groups
, i
->group_name
?: i
->name
);
1498 /* If that's not a match, also check if the group name
1499 * matches a user name in the queue. */
1500 if (!j
&& i
->group_name
)
1501 j
= ordered_hashmap_get(c
->users
, i
->group_name
);
1504 if (j
&& j
->todo_group
) {
1505 /* When a group with the target name is already in queue,
1506 * use the information about the group and do not create
1507 * duplicated group entry. */
1508 i
->gid_set
= j
->gid_set
;
1510 i
->id_set_strict
= true;
1511 } else if (i
->group_name
) {
1512 /* When a group name was given instead of a GID and it's
1513 * not in queue, then it must already exist. */
1514 r
= get_gid_by_name(c
, i
->group_name
, &i
->gid
);
1516 return log_error_errno(r
, "Group %s not found.", i
->group_name
);
1518 i
->id_set_strict
= true;
1520 r
= add_group(c
, i
);
1525 return add_user(c
, i
);
1529 return add_group(c
, i
);
1532 assert_not_reached();
1536 static Item
* item_free(Item
*i
) {
1541 free(i
->group_name
);
1544 free(i
->description
);
1551 DEFINE_TRIVIAL_CLEANUP_FUNC(Item
*, item_free
);
1552 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_hash_ops
, char, string_hash_func
, string_compare_func
, Item
, item_free
);
1554 static Item
* item_new(ItemType type
, const char *name
, const char *filename
, unsigned line
) {
1556 assert(!!filename
== (line
> 0));
1558 _cleanup_(item_freep
) Item
*new = new(Item
, 1);
1567 if (free_and_strdup(&new->name
, name
) < 0 ||
1568 free_and_strdup(&new->filename
, filename
) < 0)
1571 return TAKE_PTR(new);
1574 static int add_implicit(Context
*c
) {
1580 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1581 ORDERED_HASHMAP_FOREACH_KEY(l
, g
, c
->members
) {
1583 if (!ordered_hashmap_get(c
->users
, *m
)) {
1584 _cleanup_(item_freep
) Item
*j
=
1585 item_new(ADD_USER
, *m
, /* filename= */ NULL
, /* line= */ 0);
1589 r
= ordered_hashmap_ensure_put(&c
->users
, &item_hash_ops
, j
->name
, j
);
1593 return log_error_errno(r
, "Failed to add implicit user '%s': %m", j
->name
);
1595 log_debug("Adding implicit user '%s' due to m line", j
->name
);
1599 if (!(ordered_hashmap_get(c
->users
, g
) ||
1600 ordered_hashmap_get(c
->groups
, g
))) {
1601 _cleanup_(item_freep
) Item
*j
=
1602 item_new(ADD_GROUP
, g
, /* filename= */ NULL
, /* line= */ 0);
1606 r
= ordered_hashmap_ensure_put(&c
->groups
, &item_hash_ops
, j
->name
, j
);
1610 return log_error_errno(r
, "Failed to add implicit group '%s': %m", j
->name
);
1612 log_debug("Adding implicit group '%s' due to m line", j
->name
);
1620 static int item_equivalent(Item
*a
, Item
*b
) {
1626 if (a
->type
!= b
->type
) {
1627 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1628 "Item not equivalent because types differ");
1632 if (!streq_ptr(a
->name
, b
->name
)) {
1633 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1634 "Item not equivalent because names differ ('%s' vs. '%s')",
1639 /* Paths were simplified previously, so we can use streq. */
1640 if (!streq_ptr(a
->uid_path
, b
->uid_path
)) {
1641 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1642 "Item not equivalent because UID paths differ (%s vs. %s)",
1643 a
->uid_path
?: "(unset)", b
->uid_path
?: "(unset)");
1647 if (!streq_ptr(a
->gid_path
, b
->gid_path
)) {
1648 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1649 "Item not equivalent because GID paths differ (%s vs. %s)",
1650 a
->gid_path
?: "(unset)", b
->gid_path
?: "(unset)");
1654 if (!streq_ptr(a
->description
, b
->description
)) {
1655 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1656 "Item not equivalent because descriptions differ ('%s' vs. '%s')",
1657 strempty(a
->description
), strempty(b
->description
));
1661 if ((a
->uid_set
!= b
->uid_set
) ||
1662 (a
->uid_set
&& a
->uid
!= b
->uid
)) {
1663 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1664 "Item not equivalent because UIDs differ (%s vs. %s)",
1665 a
->uid_set
? FORMAT_UID(a
->uid
) : "(unset)",
1666 b
->uid_set
? FORMAT_UID(b
->uid
) : "(unset)");
1670 if ((a
->gid_set
!= b
->gid_set
) ||
1671 (a
->gid_set
&& a
->gid
!= b
->gid
)) {
1672 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1673 "Item not equivalent because GIDs differ (%s vs. %s)",
1674 a
->gid_set
? FORMAT_GID(a
->gid
) : "(unset)",
1675 b
->gid_set
? FORMAT_GID(b
->gid
) : "(unset)");
1679 if (!streq_ptr(a
->home
, b
->home
)) {
1680 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1681 "Item not equivalent because home directories differ ('%s' vs. '%s')",
1682 strempty(a
->description
), strempty(b
->description
));
1686 /* Check if the two paths refer to the same file.
1687 * If the paths are equal (after normalization), it's obviously the same file.
1688 * If both paths specify a nologin shell, treat them as the same (e.g. /bin/true and /bin/false).
1689 * Otherwise, try to resolve the paths, and see if we get the same result, (e.g. /sbin/nologin and
1690 * /usr/sbin/nologin).
1691 * If we can't resolve something, treat different paths as different. */
1693 const char *a_shell
= pick_shell(a
),
1694 *b_shell
= pick_shell(b
);
1695 if (!path_equal(a_shell
, b_shell
) &&
1696 !(is_nologin_shell(a_shell
) && is_nologin_shell(b_shell
))) {
1697 _cleanup_free_
char *pa
= NULL
, *pb
= NULL
;
1699 r
= chase(a_shell
, arg_root
, CHASE_PREFIX_ROOT
| CHASE_NONEXISTENT
, &pa
, NULL
);
1701 log_full_errno(ERRNO_IS_RESOURCE(r
) ? LOG_ERR
: LOG_DEBUG
,
1702 r
, "Failed to look up path '%s%s%s': %m",
1703 strempty(arg_root
), arg_root
? "/" : "", a_shell
);
1704 return ERRNO_IS_RESOURCE(r
) ? r
: false;
1707 r
= chase(b_shell
, arg_root
, CHASE_PREFIX_ROOT
| CHASE_NONEXISTENT
, &pb
, NULL
);
1709 log_full_errno(ERRNO_IS_RESOURCE(r
) ? LOG_ERR
: LOG_DEBUG
,
1710 r
, "Failed to look up path '%s%s%s': %m",
1711 strempty(arg_root
), arg_root
? "/" : "", b_shell
);
1712 return ERRNO_IS_RESOURCE(r
) ? r
: false;
1715 if (!path_equal(pa
, pb
)) {
1716 log_syntax(NULL
, LOG_DEBUG
, a
->filename
, a
->line
, 0,
1717 "Item not equivalent because shells differ ('%s' vs. '%s')",
1726 static int parse_line(
1730 bool *invalid_config
,
1733 Context
*c
= ASSERT_PTR(context
);
1734 _cleanup_free_
char *action
= NULL
,
1735 *name
= NULL
, *resolved_name
= NULL
,
1736 *id
= NULL
, *resolved_id
= NULL
,
1737 *description
= NULL
, *resolved_description
= NULL
,
1738 *home
= NULL
, *resolved_home
= NULL
,
1739 *shell
= NULL
, *resolved_shell
= NULL
;
1740 _cleanup_(item_freep
) Item
*i
= NULL
;
1749 assert(!invalid_config
); /* We don't support invalid_config yet. */
1753 r
= extract_many_words(&p
, NULL
, EXTRACT_UNQUOTE
,
1754 &action
, &name
, &id
, &description
, &home
, &shell
);
1756 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
, "Syntax error.");
1758 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1759 "Missing action and name columns.");
1761 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1762 "Trailing garbage.");
1764 if (isempty(action
))
1765 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EBADMSG
),
1766 "Empty command specification.");
1768 bool locked
= false;
1769 for (int pos
= 1; action
[pos
]; pos
++)
1770 if (action
[pos
] == '!' && !locked
)
1773 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EBADMSG
),
1774 "Unknown modifiers in command '%s'.", action
);
1776 if (!IN_SET(action
[0], ADD_USER
, ADD_GROUP
, ADD_MEMBER
, ADD_RANGE
))
1777 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EBADMSG
),
1778 "Unknown command type '%c'.", action
[0]);
1781 if (empty_or_dash(name
))
1785 r
= specifier_printf(name
, NAME_MAX
, system_and_tmp_specifier_table
, arg_root
, NULL
, &resolved_name
);
1787 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
, "Failed to replace specifiers in '%s': %m", name
);
1789 if (!valid_user_group_name(resolved_name
, 0))
1790 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1791 "'%s' is not a valid user or group name.", resolved_name
);
1795 if (empty_or_dash(id
))
1799 r
= specifier_printf(id
, PATH_MAX
-1, system_and_tmp_specifier_table
, arg_root
, NULL
, &resolved_id
);
1801 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
,
1802 "Failed to replace specifiers in '%s': %m", name
);
1805 /* Verify description */
1806 if (empty_or_dash(description
))
1807 description
= mfree(description
);
1810 r
= specifier_printf(description
, LONG_LINE_MAX
, system_and_tmp_specifier_table
, arg_root
, NULL
, &resolved_description
);
1812 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
,
1813 "Failed to replace specifiers in '%s': %m", description
);
1815 if (!valid_gecos(resolved_description
))
1816 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1817 "'%s' is not a valid GECOS field.", resolved_description
);
1821 if (empty_or_dash(home
))
1825 r
= specifier_printf(home
, PATH_MAX
-1, system_and_tmp_specifier_table
, arg_root
, NULL
, &resolved_home
);
1827 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
,
1828 "Failed to replace specifiers in '%s': %m", home
);
1830 path_simplify(resolved_home
);
1832 if (!valid_home(resolved_home
))
1833 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1834 "'%s' is not a valid home directory field.", resolved_home
);
1838 if (empty_or_dash(shell
))
1839 shell
= mfree(shell
);
1842 r
= specifier_printf(shell
, PATH_MAX
-1, system_and_tmp_specifier_table
, arg_root
, NULL
, &resolved_shell
);
1844 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
,
1845 "Failed to replace specifiers in '%s': %m", shell
);
1847 path_simplify(resolved_shell
);
1849 if (!valid_shell(resolved_shell
))
1850 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1851 "'%s' is not a valid login shell field.", resolved_shell
);
1854 switch (action
[0]) {
1858 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1859 "Flag '!' not permitted on lines of type 'r'.");
1862 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1863 "Lines of type 'r' don't take a name field.");
1866 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1867 "Lines of type 'r' require an ID range in the third field.");
1869 if (description
|| home
|| shell
)
1870 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1871 "Lines of type '%c' don't take a %s field.",
1873 description
? "GECOS" : home
? "home directory" : "login shell");
1875 r
= uid_range_add_str(&c
->uid_range
, resolved_id
);
1877 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1878 "Invalid UID range %s.", resolved_id
);
1883 /* Try to extend an existing member or group item */
1885 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1886 "Lines of type 'm' require a user name in the second field.");
1889 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1890 "Flag '!' not permitted on lines of type 'm'.");
1893 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1894 "Lines of type 'm' require a group name in the third field.");
1896 if (!valid_user_group_name(resolved_id
, 0))
1897 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1898 "'%s' is not a valid user or group name.", resolved_id
);
1900 if (description
|| home
|| shell
)
1901 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1902 "Lines of type '%c' don't take a %s field.",
1904 description
? "GECOS" : home
? "home directory" : "login shell");
1906 r
= string_strv_ordered_hashmap_put(&c
->members
, resolved_id
, resolved_name
);
1908 return log_error_errno(r
, "Failed to store mapping for %s: %m", resolved_id
);
1915 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1916 "Lines of type 'u' require a user name in the second field.");
1918 r
= ordered_hashmap_ensure_allocated(&c
->users
, &item_hash_ops
);
1922 i
= item_new(ADD_USER
, resolved_name
, fname
, line
);
1927 if (path_is_absolute(resolved_id
))
1928 i
->uid_path
= path_simplify(TAKE_PTR(resolved_id
));
1930 _cleanup_free_
char *uid
= NULL
, *gid
= NULL
;
1931 if (split_pair(resolved_id
, ":", &uid
, &gid
) == 0) {
1932 r
= parse_gid(gid
, &i
->gid
);
1934 if (valid_user_group_name(gid
, 0))
1935 i
->group_name
= TAKE_PTR(gid
);
1937 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
,
1938 "Failed to parse GID: '%s': %m", id
);
1941 i
->id_set_strict
= true;
1943 free_and_replace(resolved_id
, uid
);
1945 if (!streq(resolved_id
, "-")) {
1946 r
= parse_uid(resolved_id
, &i
->uid
);
1948 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
,
1949 "Failed to parse UID: '%s': %m", id
);
1955 i
->description
= TAKE_PTR(resolved_description
);
1956 i
->home
= TAKE_PTR(resolved_home
);
1957 i
->shell
= TAKE_PTR(resolved_shell
);
1965 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1966 "Lines of type 'g' require a user name in the second field.");
1969 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1970 "Flag '!' not permitted on lines of type 'g'.");
1972 if (description
|| home
|| shell
)
1973 return log_syntax(NULL
, LOG_ERR
, fname
, line
, SYNTHETIC_ERRNO(EINVAL
),
1974 "Lines of type '%c' don't take a %s field.",
1976 description
? "GECOS" : home
? "home directory" : "login shell");
1978 r
= ordered_hashmap_ensure_allocated(&c
->groups
, &item_hash_ops
);
1982 i
= item_new(ADD_GROUP
, resolved_name
, fname
, line
);
1987 if (path_is_absolute(resolved_id
))
1988 i
->gid_path
= path_simplify(TAKE_PTR(resolved_id
));
1990 r
= parse_gid(resolved_id
, &i
->gid
);
1992 return log_syntax(NULL
, LOG_ERR
, fname
, line
, r
,
1993 "Failed to parse GID: '%s': %m", id
);
2003 assert_not_reached();
2006 existing
= ordered_hashmap_get(h
, i
->name
);
2008 /* Two functionally-equivalent items are fine */
2009 r
= item_equivalent(i
, existing
);
2013 if (existing
->filename
)
2014 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0,
2015 "Conflict with earlier configuration for %s '%s' in %s:%u, ignoring line.",
2016 item_type_to_string(i
->type
),
2018 existing
->filename
, existing
->line
);
2020 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0,
2021 "Conflict with earlier configuration for %s '%s', ignoring line.",
2022 item_type_to_string(i
->type
),
2029 r
= ordered_hashmap_put(h
, i
->name
, i
);
2037 static int read_config_file(Context
*c
, const char *fn
, bool ignore_enoent
) {
2038 return conf_file_read(
2040 (const char**) CONF_PATHS_STRV("sysusers.d"),
2045 /* invalid_config= */ NULL
);
2048 static int cat_config(void) {
2049 _cleanup_strv_free_
char **files
= NULL
;
2052 r
= conf_files_list_with_replacement(arg_root
, CONF_PATHS_STRV("sysusers.d"), arg_replace
, &files
, NULL
);
2056 pager_open(arg_pager_flags
);
2058 return cat_files(NULL
, files
, arg_cat_flags
);
2061 static int help(void) {
2062 _cleanup_free_
char *link
= NULL
;
2065 r
= terminal_urlify_man("systemd-sysusers.service", "8", &link
);
2069 printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n"
2070 "\n%2$sCreates system user and group accounts.%4$s\n"
2071 "\n%3$sCommands:%4$s\n"
2072 " --cat-config Show configuration files\n"
2073 " --tldr Show non-comment parts of configuration\n"
2074 " -h --help Show this help\n"
2075 " --version Show package version\n"
2076 "\n%3$sOptions:%4$s\n"
2077 " --root=PATH Operate on an alternate filesystem root\n"
2078 " --image=PATH Operate on disk image as filesystem root\n"
2079 " --image-policy=POLICY Specify disk image dissection policy\n"
2080 " --replace=PATH Treat arguments as replacement for PATH\n"
2081 " --dry-run Just print what would be done\n"
2082 " --inline Treat arguments as configuration lines\n"
2083 " --no-pager Do not pipe output into a pager\n"
2084 "\nSee the %5$s for details.\n",
2085 program_invocation_short_name
,
2094 static int parse_argv(int argc
, char *argv
[]) {
2097 ARG_VERSION
= 0x100,
2109 static const struct option options
[] = {
2110 { "help", no_argument
, NULL
, 'h' },
2111 { "version", no_argument
, NULL
, ARG_VERSION
},
2112 { "cat-config", no_argument
, NULL
, ARG_CAT_CONFIG
},
2113 { "tldr", no_argument
, NULL
, ARG_TLDR
},
2114 { "root", required_argument
, NULL
, ARG_ROOT
},
2115 { "image", required_argument
, NULL
, ARG_IMAGE
},
2116 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
2117 { "replace", required_argument
, NULL
, ARG_REPLACE
},
2118 { "dry-run", no_argument
, NULL
, ARG_DRY_RUN
},
2119 { "inline", no_argument
, NULL
, ARG_INLINE
},
2120 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
2129 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
2139 case ARG_CAT_CONFIG
:
2140 arg_cat_flags
= CAT_CONFIG_ON
;
2144 arg_cat_flags
= CAT_TLDR
;
2148 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_root
);
2155 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
2156 "This systemd-sysusers version is compiled without support for --image=.");
2158 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
2164 case ARG_IMAGE_POLICY
:
2165 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
2171 if (!path_is_absolute(optarg
))
2172 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2173 "The argument to --replace= must be an absolute path.");
2174 if (!endswith(optarg
, ".conf"))
2175 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2176 "The argument to --replace= must have the extension '.conf'.");
2178 arg_replace
= optarg
;
2190 arg_pager_flags
|= PAGER_DISABLE
;
2197 assert_not_reached();
2200 if (arg_replace
&& arg_cat_flags
!= CAT_CONFIG_OFF
)
2201 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2202 "Option --replace= is not supported with --cat-config/--tldr.");
2204 if (arg_replace
&& optind
>= argc
)
2205 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2206 "When --replace= is given, some configuration items must be specified.");
2208 if (arg_image
&& arg_root
)
2209 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2210 "Use either --root= or --image=, the combination of both is not supported.");
2215 static int parse_arguments(Context
*c
, char **args
) {
2221 STRV_FOREACH(arg
, args
) {
2223 /* Use (argument):n, where n==1 for the first positional arg */
2224 r
= parse_line("(argument)", pos
, *arg
, /* invalid_config= */ NULL
, c
);
2226 r
= read_config_file(c
, *arg
, /* ignore_enoent= */ false);
2236 static int read_config_files(Context
*c
, char **args
) {
2237 _cleanup_strv_free_
char **files
= NULL
;
2238 _cleanup_free_
char *p
= NULL
;
2243 r
= conf_files_list_with_replacement(arg_root
, CONF_PATHS_STRV("sysusers.d"), arg_replace
, &files
, &p
);
2247 STRV_FOREACH(f
, files
)
2248 if (p
&& path_equal(*f
, p
)) {
2249 log_debug("Parsing arguments at position \"%s\"%s", *f
, glyph(GLYPH_ELLIPSIS
));
2251 r
= parse_arguments(c
, args
);
2255 log_debug("Reading config file \"%s\"%s", *f
, glyph(GLYPH_ELLIPSIS
));
2257 /* Just warn, ignore result otherwise */
2258 (void) read_config_file(c
, *f
, /* ignore_enoent= */ true);
2264 static int read_credential_lines(Context
*c
) {
2265 _cleanup_free_
char *j
= NULL
;
2271 r
= get_credentials_dir(&d
);
2275 return log_error_errno(r
, "Failed to get credentials directory: %m");
2277 j
= path_join(d
, "sysusers.extra");
2281 (void) read_config_file(c
, j
, /* ignore_enoent= */ true);
2285 static int run(int argc
, char *argv
[]) {
2287 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
2288 _cleanup_(umount_and_freep
) char *mounted_dir
= NULL
;
2290 _cleanup_close_
int lock
= -EBADF
;
2291 _cleanup_(context_done
) Context c
= {
2293 .search_uid
= UID_INVALID
,
2299 r
= parse_argv(argc
, argv
);
2305 if (arg_cat_flags
!= CAT_CONFIG_OFF
)
2306 return cat_config();
2308 if (should_bypass("SYSTEMD_SYSUSERS"))
2321 r
= mount_image_privately_interactively(
2324 DISSECT_IMAGE_GENERIC_ROOT
|
2325 DISSECT_IMAGE_REQUIRE_ROOT
|
2326 DISSECT_IMAGE_VALIDATE_OS
|
2327 DISSECT_IMAGE_RELAX_VAR_CHECK
|
2328 DISSECT_IMAGE_FSCK
|
2329 DISSECT_IMAGE_GROWFS
|
2330 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY
,
2332 /* ret_dir_fd= */ NULL
,
2337 arg_root
= strdup(mounted_dir
);
2345 /* Prepare to emit audit events, but only if we're operating on the host system. */
2347 c
.audit_fd
= open_audit_fd_or_warn();
2349 /* If command line arguments are specified along with --replace, read all configuration files and
2350 * insert the positional arguments at the specified place. Otherwise, if command line arguments are
2351 * specified, execute just them, and finally, without --replace= or any positional arguments, just
2352 * read configuration and execute it. */
2353 if (arg_replace
|| optind
>= argc
)
2354 r
= read_config_files(&c
, argv
+ optind
);
2356 r
= parse_arguments(&c
, argv
+ optind
);
2360 r
= read_credential_lines(&c
);
2364 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our
2365 * detection whether the names or UID/GID area already used otherwise doesn't get confused. After
2366 * all, even though nss-systemd synthesizes these users/groups, they should still appear in
2367 * /etc/passwd and /etc/group, as the synthesizing logic is merely supposed to be fallback for cases
2368 * where we run with a completely unpopulated /etc. */
2369 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0)
2370 return log_error_errno(errno
, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
2373 /* Default to default range of SYSTEMD_UID_MIN..SYSTEM_UID_MAX. */
2374 r
= read_login_defs(&c
.login_defs
, NULL
, arg_root
);
2376 return log_error_errno(r
, "Failed to read %s%s: %m",
2377 strempty(arg_root
), "/etc/login.defs");
2379 c
.login_defs_need_warning
= true;
2381 /* We pick a range that very conservative: we look at compiled-in maximum and the value in
2382 * /etc/login.defs. That way the UIDs/GIDs which we allocate will be interpreted correctly,
2383 * even if /etc/login.defs is removed later. (The bottom bound doesn't matter much, since
2384 * it's only used during allocation, so we use the configured value directly). */
2385 uid_t begin
= c
.login_defs
.system_alloc_uid_min
,
2386 end
= MIN3((uid_t
) SYSTEM_UID_MAX
, c
.login_defs
.system_uid_max
, c
.login_defs
.system_gid_max
);
2388 r
= uid_range_add(&c
.uid_range
, begin
, end
- begin
+ 1);
2394 r
= add_implicit(&c
);
2399 lock
= take_etc_passwd_lock(arg_root
);
2401 return log_error_errno(lock
, "Failed to take /etc/passwd lock: %m");
2404 r
= load_user_database(&c
);
2406 return log_error_errno(r
, "Failed to load user database: %m");
2408 r
= load_group_database(&c
);
2410 return log_error_errno(r
, "Failed to read group database: %m");
2412 ORDERED_HASHMAP_FOREACH(i
, c
.groups
)
2413 (void) process_item(&c
, i
);
2415 ORDERED_HASHMAP_FOREACH(i
, c
.users
)
2416 (void) process_item(&c
, i
);
2418 return write_files(&c
);
2421 DEFINE_MAIN_FUNCTION(run
);