sysusers: allow the shell to be specified

This is necessary for some system users where the "login shell" is
set to a specific binary.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-01-30 14:28:10 +01:00
parent 1b600bd522
commit 7b1aaf6633
4 changed files with 111 additions and 64 deletions

View File

@ -57,11 +57,14 @@
<refsect1>
<title>Description</title>
<para><command>systemd-sysusers</command> uses the files from <filename>sysusers.d</filename> directory to create
system users and groups at package installation or boot time. This tool may be used to allocate system users and
groups only, it is not useful for creating non-system (i.e. regular, "human") users and groups, as it accesses
<filename>/etc/passwd</filename> and <filename>/etc/group</filename> directly, bypassing any more complex user
databases, for example any database involving NIS or LDAP.</para>
<para><command>systemd-sysusers</command> uses the files from
<filename>sysusers.d</filename> directory to create system users and groups and
to add users to groups, at package installation or boot time. This tool may be
used to allocate system users and groups only, it is not useful for creating
non-system (i.e. regular, "human") users and groups, as it accesses
<filename>/etc/passwd</filename> and <filename>/etc/group</filename> directly,
bypassing any more complex user databases, for example any database involving NIS
or LDAP.</para>
</refsect1>
<refsect1>
@ -100,15 +103,16 @@
<refsect1>
<title>Configuration File Format</title>
<para>The file format is one line per user or group containing
name, ID, GECOS field description and home directory:</para>
<para>The file format is one line per user or group containing name, ID, GECOS
field description, home directory, and login shell:</para>
<programlisting>#Type Name ID GECOS Home directory
u httpd 440 "HTTP User"
u authd /usr/bin/authd "Authorization user"
g input - -
m authd input
u root 0 "Superuser" /root</programlisting>
<programlisting>#Type Name ID GECOS Home directory Shell
u httpd 404 "HTTP User"
u authd /usr/bin/authd "Authorization user"
u postgres - "Postgresql Database" /var/lib/pgsql /usr/libexec/postgresdb
g input - -
m authd input
u root 0 "Superuser" /root /bin/zsh</programlisting>
<para>Empty lines and lines beginning with the <literal>#</literal> character are ignored, and may be used for
commenting.</para>
@ -122,14 +126,10 @@ u root 0 "Superuser" /root</programlisting>
<variablelist>
<varlistentry>
<term><varname>u</varname></term>
<listitem><para>Create a system user and group of the
specified name should they not exist yet. The user's primary
group will be set to the group bearing the same name. The
user's shell will be set to
<filename>/sbin/nologin</filename>, the home directory to
the specified home directory, or <filename>/</filename> if
none is given. The account will be created disabled, so that
logins are not allowed.</para></listitem>
<listitem><para>Create a system user and group of the specified name should
they not exist yet. The user's primary group will be set to the group
bearing the same name. The account will be created disabled, so that logins
are not allowed.</para></listitem>
</varlistentry>
<varlistentry>
@ -187,7 +187,8 @@ u root 0 "Superuser" /root</programlisting>
numeric 32-bit UID or GID of the user/group. Do not use IDs 65535
or 4294967295, as they have special placeholder meanings.
Specify <literal>-</literal> for automatic UID/GID allocation
for the user or group. Alternatively, specify an absolute path
for the user or group (this is strongly recommended unless it is strictly
necessary to use a specific UID or GID). Alternatively, specify an absolute path
in the file system. In this case, the UID/GID is read from the
path's owner/group. This is useful to create users whose UID/GID
match the owners of pre-existing files (such as SUID or SGID
@ -209,37 +210,45 @@ u root 0 "Superuser" /root</programlisting>
<refsect2>
<title>GECOS</title>
<para>A short, descriptive string for users to be created,
enclosed in quotation marks. Note that this field may not
contain colons.</para>
<para>A short, descriptive string for users to be created, enclosed in
quotation marks. Note that this field may not contain colons.</para>
<para>Only applies to lines of type <varname>u</varname> and
should otherwise be left unset, or be set to
<literal>-</literal>.</para>
<para>Only applies to lines of type <varname>u</varname> and should otherwise
be left unset (or <literal>-</literal>).</para>
</refsect2>
<refsect2>
<title>Home Directory</title>
<para>The home directory for a new system user. If omitted,
defaults to the root directory. It is recommended to not
unnecessarily specify home directories for system users, unless
software strictly requires one to be set.</para>
<para>The home directory for a new system user. If omitted, defaults to the
root directory.</para>
<para>Only applies to lines of type <varname>u</varname> and
should otherwise be left unset, or be set to
<literal>-</literal>.</para>
<para>Only applies to lines of type <varname>u</varname> and should otherwise
be left unset (or <literal>-</literal>). It is recommended to omit this, unless
software strictly requires a home directory to be set.</para>
</refsect2>
<refsect2>
<title>Shell</title>
<para>The login shell of the user. If not specified, this will be set to
<filename>/sbin/nologin</filename>, except if the UID of the user is 0, in
which case <filename>/bin/sh</filename> will be used.</para>
<para>Only applies to lines of type <varname>u</varname> and should otherwise
be left unset (or <literal>-</literal>). It is recommended to omit this, unless
a shell different <filename>/sbin/nologin</filename> must be used.</para>
</refsect2>
</refsect1>
<refsect1>
<title>Idempotence</title>
<para>Note that <command>systemd-sysusers</command> will do
nothing if the specified users or groups already exist, so
normally, there is no reason to override
<filename>sysusers.d</filename> vendor configuration, except to
block certain users or groups from being created.</para>
<para>Note that <command>systemd-sysusers</command> will do nothing if the
specified users or groups already exist or the users are members of specified
groups, so normally there is no reason to override
<filename>sysusers.d</filename> vendor configuration, except to block certain
users or groups from being created.</para>
</refsect1>
<refsect1>

View File

@ -645,6 +645,8 @@ bool valid_gecos(const char *d) {
}
bool valid_home(const char *p) {
/* Note that this function is also called by valid_shell(), any
* changes must account for that. */
if (isempty(p))
return false;

View File

@ -98,6 +98,15 @@ bool valid_user_group_name_or_id(const char *u);
bool valid_gecos(const char *d);
bool valid_home(const char *p);
static inline bool valid_shell(const char *p) {
/* We have the same requirements, so just piggy-back on the home check.
*
* Let's ignore /etc/shells because this is only applicable to real and
* not system users. It is also incompatible with the idea of empty /etc.
*/
return valid_home(p);
}
int maybe_setgroups(size_t size, const gid_t *list);
bool synthesize_nobody(void);

View File

@ -59,6 +59,7 @@ typedef struct Item {
char *gid_path;
char *description;
char *home;
char *shell;
gid_t gid;
uid_t uid;
@ -384,6 +385,10 @@ static int rename_and_apply_smack(const char *temp_path, const char *dest_path)
return r;
}
static const char* default_shell(uid_t uid) {
return uid == 0 ? "/bin/sh" : "/sbin/nologin";
}
static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) {
_cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
_cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
@ -451,7 +456,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
/* Initialize the shell to nologin, with one exception:
* for root we patch in something special */
.pw_shell = i->uid == 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
.pw_shell = i->shell ?: (char*) default_shell(i->uid),
};
errno = 0;
@ -1225,6 +1230,7 @@ static void item_free(Item *i) {
free(i->gid_path);
free(i->description);
free(i->home);
free(i->shell);
free(i);
}
@ -1332,6 +1338,9 @@ static bool item_equal(Item *a, Item *b) {
if (!streq_ptr(a->home, b->home))
return false;
if (!streq_ptr(a->shell, b->shell))
return false;
return true;
}
@ -1345,7 +1354,12 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
{}
};
_cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
_cleanup_free_ char *action = NULL,
*name = NULL, *resolved_name = NULL,
*id = NULL, *resolved_id = NULL,
*description = NULL,
*home = NULL,
*shell, *resolved_shell = NULL;
_cleanup_(item_freep) Item *i = NULL;
Item *existing;
OrderedHashmap *h;
@ -1358,7 +1372,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
/* Parse columns */
p = buffer;
r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL);
r = extract_many_words(&p, NULL, EXTRACT_QUOTES,
&action, &name, &id, &description, &home, &shell, NULL);
if (r < 0) {
log_error("[%s:%u] Syntax error.", fname, line);
return r;
@ -1434,6 +1449,24 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
}
/* Verify shell */
if (isempty(shell) || streq(shell, "-"))
shell = mfree(shell);
if (shell) {
r = specifier_printf(shell, specifier_table, NULL, &resolved_shell);
if (r < 0) {
log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, shell);
return r;
}
if (!valid_shell(resolved_shell)) {
log_error("[%s:%u] '%s' is not a valid login shell field.", fname, line, resolved_shell);
return -EINVAL;
}
}
switch (action[0]) {
case ADD_RANGE:
@ -1447,13 +1480,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@ -1484,13 +1514,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@ -1574,6 +1601,9 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
i->home = home;
home = NULL;
i->shell = resolved_shell;
resolved_shell = NULL;
h = users;
break;
@ -1583,13 +1613,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}