mirror of
https://github.com/systemd/systemd.git
synced 2025-03-22 06:50:18 +03:00
Merge pull request #3728 from poettering/dynamic-users
This commit is contained in:
commit
dadd6ecfa5
@ -23,6 +23,7 @@ MANPAGES += \
|
||||
man/localtime.5 \
|
||||
man/machine-id.5 \
|
||||
man/machine-info.5 \
|
||||
man/nss-systemd.8 \
|
||||
man/os-release.5 \
|
||||
man/sd-bus-errors.3 \
|
||||
man/sd-bus.3 \
|
||||
@ -255,6 +256,7 @@ MANPAGES_ALIAS += \
|
||||
man/SD_WARNING.3 \
|
||||
man/init.1 \
|
||||
man/journald.conf.d.5 \
|
||||
man/libnss_systemd.so.2.8 \
|
||||
man/poweroff.8 \
|
||||
man/reboot.8 \
|
||||
man/sd_bus_creds_get_audit_login_uid.3 \
|
||||
@ -587,6 +589,7 @@ man/SD_NOTICE.3: man/sd-daemon.3
|
||||
man/SD_WARNING.3: man/sd-daemon.3
|
||||
man/init.1: man/systemd.1
|
||||
man/journald.conf.d.5: man/journald.conf.5
|
||||
man/libnss_systemd.so.2.8: man/nss-systemd.8
|
||||
man/poweroff.8: man/halt.8
|
||||
man/reboot.8: man/halt.8
|
||||
man/sd_bus_creds_get_audit_login_uid.3: man/sd_bus_creds_get_pid.3
|
||||
@ -1071,6 +1074,9 @@ man/init.html: man/systemd.html
|
||||
man/journald.conf.d.html: man/journald.conf.html
|
||||
$(html-alias)
|
||||
|
||||
man/libnss_systemd.so.2.html: man/nss-systemd.html
|
||||
$(html-alias)
|
||||
|
||||
man/poweroff.html: man/halt.html
|
||||
$(html-alias)
|
||||
|
||||
@ -2519,6 +2525,7 @@ EXTRA_DIST += \
|
||||
man/nss-myhostname.xml \
|
||||
man/nss-mymachines.xml \
|
||||
man/nss-resolve.xml \
|
||||
man/nss-systemd.xml \
|
||||
man/os-release.xml \
|
||||
man/pam_systemd.xml \
|
||||
man/resolved.conf.xml \
|
||||
|
23
Makefile.am
23
Makefile.am
@ -1199,6 +1199,8 @@ libcore_la_SOURCES = \
|
||||
src/core/load-dropin.h \
|
||||
src/core/execute.c \
|
||||
src/core/execute.h \
|
||||
src/core/dynamic-user.c \
|
||||
src/core/dynamic-user.h \
|
||||
src/core/kill.c \
|
||||
src/core/kill.h \
|
||||
src/core/dbus.c \
|
||||
@ -5001,6 +5003,27 @@ test_nss_LDADD = \
|
||||
manual_tests += \
|
||||
test-nss
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
libnss_systemd_la_SOURCES = \
|
||||
src/nss-systemd/nss-systemd.sym \
|
||||
src/nss-systemd/nss-systemd.c
|
||||
|
||||
libnss_systemd_la_LDFLAGS = \
|
||||
$(AM_LDFLAGS) \
|
||||
-module \
|
||||
-export-dynamic \
|
||||
-avoid-version \
|
||||
-shared \
|
||||
-shrext .so.2 \
|
||||
-Wl,--version-script=$(top_srcdir)/src/nss-systemd/nss-systemd.sym
|
||||
|
||||
libnss_systemd_la_LIBADD = \
|
||||
libsystemd-internal.la \
|
||||
libbasic.la
|
||||
|
||||
lib_LTLIBRARIES += \
|
||||
libnss_systemd.la
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
if HAVE_MYHOSTNAME
|
||||
libnss_myhostname_la_SOURCES = \
|
||||
|
21
README
21
README
@ -201,7 +201,7 @@ USERS AND GROUPS:
|
||||
"systemd-coredump" system user and group to exist.
|
||||
|
||||
NSS:
|
||||
systemd ships with three NSS modules:
|
||||
systemd ships with four glibc NSS modules:
|
||||
|
||||
nss-myhostname resolves the local hostname to locally
|
||||
configured IP addresses, as well as "localhost" to
|
||||
@ -210,15 +210,22 @@ NSS:
|
||||
nss-resolve enables DNS resolution via the systemd-resolved
|
||||
DNS/LLMNR caching stub resolver "systemd-resolved".
|
||||
|
||||
nss-mymachines enables resolution of all local containers
|
||||
registered with machined to their respective IP addresses.
|
||||
nss-mymachines enables resolution of all local containers registered
|
||||
with machined to their respective IP addresses. It also maps UID/GIDs
|
||||
ranges used by containers to useful names.
|
||||
|
||||
To make use of these NSS modules, please add them to the
|
||||
"hosts: " line in /etc/nsswitch.conf. The "resolve" module
|
||||
should replace the glibc "dns" module in this file.
|
||||
nss-systemd enables resolution of all dynamically allocated service
|
||||
users. (See the DynamicUser= setting in unit files.)
|
||||
|
||||
The three modules should be used in the following order:
|
||||
To make use of these NSS modules, please add them to the "hosts:",
|
||||
"passwd:" and "group:" lines in /etc/nsswitch.conf. The "resolve"
|
||||
module should replace the glibc "dns" module in this file (and don't
|
||||
worry, it chain-loads the "dns" module if it can't talk to resolved).
|
||||
|
||||
The four modules should be used in the following order:
|
||||
|
||||
passwd: compat mymachines systemd
|
||||
group: compat mymachines systemd
|
||||
hosts: files mymachines resolve myhostname
|
||||
|
||||
SYSV INIT.D SCRIPTS:
|
||||
|
27
TODO
27
TODO
@ -33,6 +33,29 @@ Janitorial Clean-ups:
|
||||
|
||||
Features:
|
||||
|
||||
* RemoveIPC= in unit files for removing POSIX/SysV IPC objects
|
||||
|
||||
* Set SERVICE_RESULT= as env var while running ExecStop=
|
||||
|
||||
* Introduce ProtectSystem=strict for making the entire OS hierarchy read-only
|
||||
except for a select few
|
||||
|
||||
* nspawn: start UID allocation loop from hash of container name
|
||||
|
||||
* in the DynamicUser=1 nss module, also map "nobody" and "root" statically
|
||||
|
||||
* pid1: log about all processes we kill with with SIGKILL or in abandoned scopes, as this should normally not happen
|
||||
|
||||
* nspawn: support that /proc, /sys/, /dev are pre-mounted
|
||||
|
||||
* nspawn: mount esp, so that bootctl can work
|
||||
|
||||
* define gpt header bits to select volatility mode
|
||||
|
||||
* nspawn: mount loopback filesystems with "discard"
|
||||
|
||||
* Make TasksMax= take percentages, taken relative to the pids_max sysctl and pids.max cgroup limit
|
||||
|
||||
* ProtectKernelLogs= (drops CAP_SYSLOG, add seccomp for syslog() syscall, and DeviceAllow to /dev/kmsg) in service files
|
||||
|
||||
* ProtectClock= (drops CAP_SYS_TIMES, adds seecomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc
|
||||
@ -46,7 +69,7 @@ Features:
|
||||
* PrivateUsers= which maps the all user ids except root and the one specified
|
||||
in User= to nobody
|
||||
|
||||
* Add AllocateUser= for allowing dynamic user ids per-service
|
||||
* ProtectControlGroups= which mounts all of /sys/fs/cgroup read-only
|
||||
|
||||
* Add DataDirectory=, CacheDirectory= and LogDirectory= to match
|
||||
RuntimeDirectory=, and create it as necessary when starting a service, owned by the right user.
|
||||
@ -60,8 +83,6 @@ Features:
|
||||
|
||||
* RestrictNamespaces= or so in services (taking away the ability to create namespaces, with setns, unshare, clone)
|
||||
|
||||
* nspawn: make /proc/sys/net writable?
|
||||
|
||||
* make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things
|
||||
|
||||
* journalctl: make sure -f ends when the container indicated by -M terminates
|
||||
|
@ -106,8 +106,8 @@
|
||||
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables
|
||||
<command>nss-myhostname</command> correctly:</para>
|
||||
|
||||
<programlisting>passwd: compat mymachines
|
||||
group: compat mymachines
|
||||
<programlisting>passwd: compat mymachines systemd
|
||||
group: compat mymachines systemd
|
||||
shadow: compat
|
||||
|
||||
hosts: files mymachines resolve <command>myhostname</command>
|
||||
@ -138,6 +138,7 @@ netgroup: nis</programlisting>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
|
@ -82,8 +82,8 @@
|
||||
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables
|
||||
<command>nss-mymachines</command> correctly:</para>
|
||||
|
||||
<programlisting>passwd: compat <command>mymachines</command>
|
||||
group: compat <command>mymachines</command>
|
||||
<programlisting>passwd: compat <command>mymachines</command> systemd
|
||||
group: compat <command>mymachines</command> systemd
|
||||
shadow: compat
|
||||
|
||||
hosts: files <command>mymachines</command> resolve myhostname
|
||||
@ -103,6 +103,7 @@ netgroup: nis</programlisting>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-machined.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
|
@ -81,8 +81,8 @@
|
||||
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables <command>nss-resolve</command>
|
||||
correctly:</para>
|
||||
|
||||
<programlisting>passwd: compat mymachines
|
||||
group: compat mymachines
|
||||
<programlisting>passwd: compat mymachines systemd
|
||||
group: compat mymachines systemd
|
||||
shadow: compat
|
||||
|
||||
hosts: files mymachines <command>resolve</command> myhostname
|
||||
@ -102,8 +102,9 @@ netgroup: nis</programlisting>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-resolved</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
107
man/nss-systemd.xml
Normal file
107
man/nss-systemd.xml
Normal file
@ -0,0 +1,107 @@
|
||||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
|
||||
<!--
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2016 Lennart Poettering
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
systemd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<refentry id="nss-systemd">
|
||||
|
||||
<refentryinfo>
|
||||
<title>nss-systemd</title>
|
||||
<productname>systemd</productname>
|
||||
|
||||
<authorgroup>
|
||||
<author>
|
||||
<contrib>Developer</contrib>
|
||||
<firstname>Lennart</firstname>
|
||||
<surname>Poettering</surname>
|
||||
<email>lennart@poettering.net</email>
|
||||
</author>
|
||||
</authorgroup>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nss-systemd</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>nss-systemd</refname>
|
||||
<refname>libnss_systemd.so.2</refname>
|
||||
<refpurpose>Provide UNIX user and group name resolution for dynamic users and groups.</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>libnss_systemd.so.2</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>nss-systemd</command> is a plug-in module for the GNU Name Service Switch (NSS) functionality of the
|
||||
GNU C Library (<command>glibc</command>), providing UNIX user and group name resolution for dynamic users and
|
||||
groups allocated through the <varname>DynamicUser=</varname> option in systemd unit files. See
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details on
|
||||
this option.</para>
|
||||
|
||||
<para>To activate the NSS module, add <literal>systemd</literal> to the lines starting with
|
||||
<literal>passwd:</literal> and <literal>group:</literal> in <filename>/etc/nsswitch.conf</filename>.</para>
|
||||
|
||||
<para>It is recommended to place <literal>systemd</literal> after the <literal>files</literal> or
|
||||
<literal>compat</literal> entry of the <filename>/etc/nsswitch.conf</filename> lines so that
|
||||
<filename>/etc/passwd</filename> and <filename>/etc/group</filename> based mappings take precedence.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Example</title>
|
||||
|
||||
<para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables
|
||||
<command>nss-systemd</command> correctly:</para>
|
||||
|
||||
<programlisting>passwd: compat mymachines <command>systemd</command>
|
||||
group: compat mymachines <command>systemd</command>
|
||||
shadow: compat
|
||||
|
||||
hosts: files mymachines resolve myhostname
|
||||
networks: files
|
||||
|
||||
protocols: db files
|
||||
services: db files
|
||||
ethers: db files
|
||||
rpc: db files
|
||||
|
||||
netgroup: nis</programlisting>
|
||||
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>getent</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
@ -143,10 +143,38 @@
|
||||
<term><varname>User=</varname></term>
|
||||
<term><varname>Group=</varname></term>
|
||||
|
||||
<listitem><para>Sets the Unix user or group that the processes
|
||||
are executed as, respectively. Takes a single user or group
|
||||
name or ID as argument. If no group is set, the default group
|
||||
of the user is chosen. These do not affect commands prefixed with <literal>+</literal>.</para></listitem>
|
||||
<listitem><para>Set the UNIX user or group that the processes are executed as, respectively. Takes a single
|
||||
user or group name, or numeric ID as argument. If no group is set, the default group of the user is used. This
|
||||
setting does not affect commands whose command line is prefixed with <literal>+</literal>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>DynamicUser=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean parameter. If set, a UNIX user and group pair is allocated dynamically when the
|
||||
unit is started, and released as soon as it is stopped. The user and group will not be added to
|
||||
<filename>/etc/passwd</filename> or <filename>/etc/group</filename>, but are managed transiently during
|
||||
runtime. The <citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
glibc NSS module provides integration of these dynamic users/groups into the system's user and group
|
||||
databases. The user and group name to use may be configured via <varname>User=</varname> and
|
||||
<varname>Group=</varname> (see above). If these options are not used and dynamic user/group allocation is
|
||||
enabled for a unit, the name of the dynamic user/group is implicitly derived from the unit name. If the unit
|
||||
name without the type suffix qualifies as valid user name it is used directly, otherwise a name incorporating a
|
||||
hash of it is used. If a statically allocated user or group of the configured name already exists, it is used
|
||||
and no dynamic user/group is allocated. Dynamic users/groups are allocated from the UID/GID range
|
||||
61184…65519. It is recommended to avoid this range for regular system or login users. At any point in time
|
||||
each UID/GID from this range is only assigned to zero or one dynamically allocated users/groups in
|
||||
use. However, UID/GIDs are recycled after a unit is terminated. Care should be taken that any processes running
|
||||
as part of a unit for which dynamic users/groups are enabled do not leave files or directories owned by these
|
||||
users/groups around, as a different unit might get the same UID/GID assigned later on, and thus gain access to
|
||||
these files or directories. If <varname>DynamicUser=</varname> is enabled, <varname>PrivateTmp=</varname> is
|
||||
implied. This ensures that the lifetime of temporary files created by the executed processes is bound to the
|
||||
runtime of the service, and hence the lifetime of the dynamic user/group. Since <filename>/tmp</filename> and
|
||||
<filename>/var/tmp</filename> are usually the only world-writable directories on a system this ensures that a
|
||||
unit making use of dynamic user/group allocation cannot leave files around after unit termination. Use
|
||||
<varname>RuntimeDirectory=</varname> (see below) in order to assign a writable runtime directory to a service,
|
||||
owned by the dynamic user/group and removed automatically when the unit is terminated. Defaults to
|
||||
off.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
@ -1046,3 +1046,17 @@ int flush_accept(int fd) {
|
||||
close(cfd);
|
||||
}
|
||||
}
|
||||
|
||||
struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) {
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
assert(mh);
|
||||
|
||||
CMSG_FOREACH(cmsg, mh)
|
||||
if (cmsg->cmsg_level == level &&
|
||||
cmsg->cmsg_type == type &&
|
||||
(length == (socklen_t) -1 || length == cmsg->cmsg_len))
|
||||
return cmsg;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
@ -142,6 +142,8 @@ int flush_accept(int fd);
|
||||
#define CMSG_FOREACH(cmsg, mh) \
|
||||
for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
|
||||
|
||||
struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
|
||||
|
||||
/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */
|
||||
#define SOCKADDR_UN_LEN(sa) \
|
||||
({ \
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <utmp.h>
|
||||
|
||||
#include "missing.h"
|
||||
#include "alloc-util.h"
|
||||
@ -39,6 +40,7 @@
|
||||
#include "path-util.h"
|
||||
#include "string-util.h"
|
||||
#include "user-util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
bool uid_is_valid(uid_t uid) {
|
||||
|
||||
@ -479,3 +481,94 @@ int take_etc_passwd_lock(const char *root) {
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
bool valid_user_group_name(const char *u) {
|
||||
const char *i;
|
||||
long sz;
|
||||
|
||||
/* Checks if the specified name is a valid user/group name. */
|
||||
|
||||
if (isempty(u))
|
||||
return false;
|
||||
|
||||
if (!(u[0] >= 'a' && u[0] <= 'z') &&
|
||||
!(u[0] >= 'A' && u[0] <= 'Z') &&
|
||||
u[0] != '_')
|
||||
return false;
|
||||
|
||||
for (i = u+1; *i; i++) {
|
||||
if (!(*i >= 'a' && *i <= 'z') &&
|
||||
!(*i >= 'A' && *i <= 'Z') &&
|
||||
!(*i >= '0' && *i <= '9') &&
|
||||
*i != '_' &&
|
||||
*i != '-')
|
||||
return false;
|
||||
}
|
||||
|
||||
sz = sysconf(_SC_LOGIN_NAME_MAX);
|
||||
assert_se(sz > 0);
|
||||
|
||||
if ((size_t) (i-u) > (size_t) sz)
|
||||
return false;
|
||||
|
||||
if ((size_t) (i-u) > UT_NAMESIZE - 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_user_group_name_or_id(const char *u) {
|
||||
|
||||
/* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right
|
||||
* range, and not the invalid user ids. */
|
||||
|
||||
if (isempty(u))
|
||||
return false;
|
||||
|
||||
if (valid_user_group_name(u))
|
||||
return true;
|
||||
|
||||
return parse_uid(u, NULL) >= 0;
|
||||
}
|
||||
|
||||
bool valid_gecos(const char *d) {
|
||||
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
if (!utf8_is_valid(d))
|
||||
return false;
|
||||
|
||||
if (string_has_cc(d, NULL))
|
||||
return false;
|
||||
|
||||
/* Colons are used as field separators, and hence not OK */
|
||||
if (strchr(d, ':'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_home(const char *p) {
|
||||
|
||||
if (isempty(p))
|
||||
return false;
|
||||
|
||||
if (!utf8_is_valid(p))
|
||||
return false;
|
||||
|
||||
if (string_has_cc(p, NULL))
|
||||
return false;
|
||||
|
||||
if (!path_is_absolute(p))
|
||||
return false;
|
||||
|
||||
if (!path_is_safe(p))
|
||||
return false;
|
||||
|
||||
/* Colons are used as field separators, and hence not OK */
|
||||
if (strchr(p, ':'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -68,3 +68,8 @@ int take_etc_passwd_lock(const char *root);
|
||||
static inline bool userns_supported(void) {
|
||||
return access("/proc/self/uid_map", F_OK) >= 0;
|
||||
}
|
||||
|
||||
bool valid_user_group_name(const char *u);
|
||||
bool valid_user_group_name_or_id(const char *u);
|
||||
bool valid_gecos(const char *d);
|
||||
bool valid_home(const char *p);
|
||||
|
@ -44,6 +44,7 @@
|
||||
#endif
|
||||
#include "strv.h"
|
||||
#include "syslog-util.h"
|
||||
#include "user-util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput);
|
||||
@ -693,6 +694,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
|
||||
SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
|
||||
@ -840,6 +842,9 @@ int bus_exec_context_set_transient_property(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!isempty(uu) && !valid_user_group_name_or_id(uu))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user name: %s", uu);
|
||||
|
||||
if (mode != UNIT_CHECK) {
|
||||
|
||||
if (isempty(uu))
|
||||
@ -859,6 +864,9 @@ int bus_exec_context_set_transient_property(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!isempty(gg) && !valid_user_group_name_or_id(gg))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group name: %s", gg);
|
||||
|
||||
if (mode != UNIT_CHECK) {
|
||||
|
||||
if (isempty(gg))
|
||||
@ -1061,7 +1069,8 @@ int bus_exec_context_set_transient_property(
|
||||
} else if (STR_IN_SET(name,
|
||||
"IgnoreSIGPIPE", "TTYVHangup", "TTYReset",
|
||||
"PrivateTmp", "PrivateDevices", "PrivateNetwork",
|
||||
"NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute", "RestrictRealtime")) {
|
||||
"NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute",
|
||||
"RestrictRealtime", "DynamicUser")) {
|
||||
int b;
|
||||
|
||||
r = sd_bus_message_read(message, "b", &b);
|
||||
@ -1089,6 +1098,8 @@ int bus_exec_context_set_transient_property(
|
||||
c->memory_deny_write_execute = b;
|
||||
else if (streq(name, "RestrictRealtime"))
|
||||
c->restrict_realtime = b;
|
||||
else if (streq(name, "DynamicUser"))
|
||||
c->dynamic_user = b;
|
||||
|
||||
unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, yes_no(b));
|
||||
}
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "syslog-util.h"
|
||||
#include "user-util.h"
|
||||
#include "virt.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
@ -1511,8 +1512,8 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd
|
||||
}
|
||||
|
||||
static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
uint8_t code;
|
||||
Manager *m = userdata;
|
||||
uint8_t code;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
@ -1534,6 +1535,61 @@ static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
Manager *m = userdata;
|
||||
const char *name;
|
||||
uid_t uid;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_read_basic(message, 's', &name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!MANAGER_IS_SYSTEM(m))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
|
||||
if (!valid_user_group_name(name))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name);
|
||||
|
||||
r = dynamic_user_lookup_name(m, name, &uid);
|
||||
if (r == -ESRCH)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user %s does not exist.", name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_reply_method_return(message, "u", (uint32_t) uid);
|
||||
}
|
||||
|
||||
static int method_lookup_dynamic_user_by_uid(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_free_ char *name = NULL;
|
||||
Manager *m = userdata;
|
||||
uid_t uid;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
assert_cc(sizeof(uid) == sizeof(uint32_t));
|
||||
r = sd_bus_message_read_basic(message, 'u', &uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!MANAGER_IS_SYSTEM(m))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
|
||||
if (!uid_is_valid(uid))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User ID invalid: " UID_FMT, uid);
|
||||
|
||||
r = dynamic_user_lookup_uid(m, uid, &name);
|
||||
if (r == -ESRCH)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user ID " UID_FMT " does not exist.", uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_reply_method_return(message, "s", name);
|
||||
}
|
||||
|
||||
static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
Manager *m = userdata;
|
||||
@ -2199,6 +2255,8 @@ const sd_bus_vtable bus_manager_vtable[] = {
|
||||
SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("LookupDynamicUserByName", "s", "u", method_lookup_dynamic_user_by_name, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("LookupDynamicUserByUID", "u", "s", method_lookup_dynamic_user_by_uid, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
SD_BUS_SIGNAL("UnitNew", "so", 0),
|
||||
SD_BUS_SIGNAL("UnitRemoved", "so", 0),
|
||||
|
763
src/core/dynamic-user.c
Normal file
763
src/core/dynamic-user.c
Normal file
@ -0,0 +1,763 @@
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2016 Lennart Poettering
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
systemd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#include "dynamic-user.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "random-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "user-util.h"
|
||||
#include "fileio.h"
|
||||
|
||||
/* Let's pick a UIDs within the 16bit range, so that we are compatible with containers using 16bit user namespacing. At
|
||||
* least on Fedora normal users are allocated until UID 60000, hence do not allocate from below this. Also stay away
|
||||
* from the upper end of the range as that is often used for overflow/nobody users. */
|
||||
#define UID_PICK_MIN ((uid_t) UINT32_C(0x0000EF00))
|
||||
#define UID_PICK_MAX ((uid_t) UINT32_C(0x0000FFEF))
|
||||
|
||||
/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */
|
||||
#define UID_CLAMP_INTO_RANGE(rnd) (((uid_t) (rnd) % (UID_PICK_MAX - UID_PICK_MIN + 1)) + UID_PICK_MIN)
|
||||
|
||||
static DynamicUser* dynamic_user_free(DynamicUser *d) {
|
||||
if (!d)
|
||||
return NULL;
|
||||
|
||||
if (d->manager)
|
||||
(void) hashmap_remove(d->manager->dynamic_users, d->name);
|
||||
|
||||
safe_close_pair(d->storage_socket);
|
||||
free(d);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int dynamic_user_add(Manager *m, const char *name, int storage_socket[2], DynamicUser **ret) {
|
||||
DynamicUser *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(name);
|
||||
assert(storage_socket);
|
||||
|
||||
r = hashmap_ensure_allocated(&m->dynamic_users, &string_hash_ops);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
d = malloc0(offsetof(DynamicUser, name) + strlen(name) + 1);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
strcpy(d->name, name);
|
||||
|
||||
d->storage_socket[0] = storage_socket[0];
|
||||
d->storage_socket[1] = storage_socket[1];
|
||||
|
||||
r = hashmap_put(m->dynamic_users, d->name, d);
|
||||
if (r < 0) {
|
||||
free(d);
|
||||
return r;
|
||||
}
|
||||
|
||||
d->manager = m;
|
||||
|
||||
if (ret)
|
||||
*ret = d;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) {
|
||||
_cleanup_close_pair_ int storage_socket[2] = { -1, -1 };
|
||||
DynamicUser *d;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(name);
|
||||
|
||||
/* Return the DynamicUser structure for a specific user name. Note that this won't actually allocate a UID for
|
||||
* it, but just prepare the data structure for it. The UID is allocated only on demand, when it's really
|
||||
* needed, and in the child process we fork off, since allocation involves NSS checks which are not OK to do
|
||||
* from PID 1. To allow the children and PID 1 share information about allocated UIDs we use an anonymous
|
||||
* AF_UNIX/SOCK_DGRAM socket (called the "storage socket") that contains at most one datagram with the
|
||||
* allocated UID number, plus an fd referencing the lock file for the UID
|
||||
* (i.e. /run/systemd/dynamic-uid/$UID). Why involve the socket pair? So that PID 1 and all its children can
|
||||
* share the same storage for the UID and lock fd, simply by inheriting the storage socket fds. The socket pair
|
||||
* may exist in three different states:
|
||||
*
|
||||
* a) no datagram stored. This is the initial state. In this case the dynamic user was never realized.
|
||||
*
|
||||
* b) a datagram containing a UID stored, but no lock fd attached to it. In this case there was already a
|
||||
* statically assigned UID by the same name, which we are reusing.
|
||||
*
|
||||
* c) a datagram containing a UID stored, and a lock fd is attached to it. In this case we allocated a dynamic
|
||||
* UID and locked it in the file system, using the lock fd.
|
||||
*
|
||||
* As PID 1 and various children might access the socket pair simultaneously, and pop the datagram or push it
|
||||
* back in any time, we also maintain a lock on the socket pair. Note one peculiarity regarding locking here:
|
||||
* the UID lock on disk is protected via a BSD file lock (i.e. an fd-bound lock), so that the lock is kept in
|
||||
* place as long as there's a reference to the fd open. The lock on the storage socket pair however is a POSIX
|
||||
* file lock (i.e. a process-bound lock), as all users share the same fd of this (after all it is anonymous,
|
||||
* nobody else could get any access to it except via our own fd) and we want to synchronize access between all
|
||||
* processes that have access to it. */
|
||||
|
||||
d = hashmap_get(m->dynamic_users, name);
|
||||
if (d) {
|
||||
/* We already have a structure for the dynamic user, let's increase the ref count and reuse it */
|
||||
d->n_ref++;
|
||||
*ret = d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!valid_user_group_name_or_id(name))
|
||||
return -EINVAL;
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0)
|
||||
return -errno;
|
||||
|
||||
r = dynamic_user_add(m, name, storage_socket, &d);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
storage_socket[0] = storage_socket[1] = -1;
|
||||
|
||||
if (ret) {
|
||||
d->n_ref++;
|
||||
*ret = d;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int pick_uid(const char *name, uid_t *ret_uid) {
|
||||
|
||||
static const uint8_t hash_key[] = {
|
||||
0x37, 0x53, 0x7e, 0x31, 0xcf, 0xce, 0x48, 0xf5,
|
||||
0x8a, 0xbb, 0x39, 0x57, 0x8d, 0xd9, 0xec, 0x59
|
||||
};
|
||||
|
||||
unsigned n_tries = 100;
|
||||
uid_t candidate;
|
||||
int r;
|
||||
|
||||
/* A static user by this name does not exist yet. Let's find a free ID then, and use that. We start with a UID
|
||||
* generated as hash from the user name. */
|
||||
candidate = UID_CLAMP_INTO_RANGE(siphash24(name, strlen(name), hash_key));
|
||||
|
||||
(void) mkdir("/run/systemd/dynamic-uid", 0755);
|
||||
|
||||
for (;;) {
|
||||
char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
|
||||
_cleanup_close_ int lock_fd = -1;
|
||||
ssize_t l;
|
||||
|
||||
if (--n_tries <= 0) /* Give up retrying eventually */
|
||||
return -EBUSY;
|
||||
|
||||
if (candidate < UID_PICK_MIN || candidate > UID_PICK_MAX)
|
||||
goto next;
|
||||
|
||||
xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, candidate);
|
||||
|
||||
for (;;) {
|
||||
struct stat st;
|
||||
|
||||
lock_fd = open(lock_path, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
|
||||
if (lock_fd < 0)
|
||||
return -errno;
|
||||
|
||||
r = flock(lock_fd, LOCK_EX|LOCK_NB); /* Try to get a BSD file lock on the UID lock file */
|
||||
if (r < 0) {
|
||||
if (errno == EBUSY || errno == EAGAIN)
|
||||
goto next; /* already in use */
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (fstat(lock_fd, &st) < 0)
|
||||
return -errno;
|
||||
if (st.st_nlink > 0)
|
||||
break;
|
||||
|
||||
/* Oh, bummer, we got got the lock, but the file was unlinked between the time we opened it and
|
||||
* got the lock. Close it, and try again. */
|
||||
lock_fd = safe_close(lock_fd);
|
||||
}
|
||||
|
||||
/* Some superficial check whether this UID/GID might already be taken by some static user */
|
||||
if (getpwuid(candidate) || getgrgid((gid_t) candidate)) {
|
||||
(void) unlink(lock_path);
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* Let's store the user name in the lock file, so that we can use it for looking up the username for a UID */
|
||||
l = pwritev(lock_fd,
|
||||
(struct iovec[2]) {
|
||||
{ .iov_base = (char*) name, .iov_len = strlen(name) },
|
||||
{ .iov_base = (char[1]) { '\n' }, .iov_len = 1 }
|
||||
}, 2, 0);
|
||||
if (l < 0) {
|
||||
(void) unlink(lock_path);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
(void) ftruncate(lock_fd, l);
|
||||
|
||||
*ret_uid = candidate;
|
||||
r = lock_fd;
|
||||
lock_fd = -1;
|
||||
|
||||
return r;
|
||||
|
||||
next:
|
||||
/* Pick another random UID, and see if that works for us. */
|
||||
random_bytes(&candidate, sizeof(candidate));
|
||||
candidate = UID_CLAMP_INTO_RANGE(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
static int dynamic_user_pop(DynamicUser *d, uid_t *ret_uid, int *ret_lock_fd) {
|
||||
uid_t uid = UID_INVALID;
|
||||
struct iovec iov = {
|
||||
.iov_base = &uid,
|
||||
.iov_len = sizeof(uid),
|
||||
};
|
||||
union {
|
||||
struct cmsghdr cmsghdr;
|
||||
uint8_t buf[CMSG_SPACE(sizeof(int))];
|
||||
} control = {};
|
||||
struct msghdr mh = {
|
||||
.msg_control = &control,
|
||||
.msg_controllen = sizeof(control),
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
ssize_t k;
|
||||
int lock_fd = -1;
|
||||
|
||||
assert(d);
|
||||
assert(ret_uid);
|
||||
assert(ret_lock_fd);
|
||||
|
||||
/* Read the UID and lock fd that is stored in the storage AF_UNIX socket. This should be called with the lock
|
||||
* on the socket taken. */
|
||||
|
||||
k = recvmsg(d->storage_socket[0], &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC);
|
||||
if (k < 0)
|
||||
return -errno;
|
||||
|
||||
cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int)));
|
||||
if (cmsg)
|
||||
lock_fd = *(int*) CMSG_DATA(cmsg);
|
||||
else
|
||||
cmsg_close_all(&mh); /* just in case... */
|
||||
|
||||
*ret_uid = uid;
|
||||
*ret_lock_fd = lock_fd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dynamic_user_push(DynamicUser *d, uid_t uid, int lock_fd) {
|
||||
struct iovec iov = {
|
||||
.iov_base = &uid,
|
||||
.iov_len = sizeof(uid),
|
||||
};
|
||||
union {
|
||||
struct cmsghdr cmsghdr;
|
||||
uint8_t buf[CMSG_SPACE(sizeof(int))];
|
||||
} control = {};
|
||||
struct msghdr mh = {
|
||||
.msg_control = &control,
|
||||
.msg_controllen = sizeof(control),
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
ssize_t k;
|
||||
|
||||
assert(d);
|
||||
|
||||
/* Store the UID and lock_fd in the storage socket. This should be called with the socket pair lock taken. */
|
||||
|
||||
if (lock_fd >= 0) {
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
cmsg = CMSG_FIRSTHDR(&mh);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
memcpy(CMSG_DATA(cmsg), &lock_fd, sizeof(int));
|
||||
|
||||
mh.msg_controllen = CMSG_SPACE(sizeof(int));
|
||||
} else {
|
||||
mh.msg_control = NULL;
|
||||
mh.msg_controllen = 0;
|
||||
}
|
||||
|
||||
k = sendmsg(d->storage_socket[1], &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
|
||||
if (k < 0)
|
||||
return -errno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void unlink_uid_lock(int lock_fd, uid_t uid) {
|
||||
char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
|
||||
|
||||
if (lock_fd < 0)
|
||||
return;
|
||||
|
||||
xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid);
|
||||
(void) unlink_noerrno(lock_path);
|
||||
}
|
||||
|
||||
int dynamic_user_realize(DynamicUser *d, uid_t *ret) {
|
||||
|
||||
_cleanup_close_ int etc_passwd_lock_fd = -1, uid_lock_fd = -1;
|
||||
uid_t uid = UID_INVALID;
|
||||
int r;
|
||||
|
||||
assert(d);
|
||||
|
||||
/* Acquire a UID for the user name. This will allocate a UID for the user name if the user doesn't exist
|
||||
* yet. If it already exists its existing UID/GID will be reused. */
|
||||
|
||||
if (lockf(d->storage_socket[0], F_LOCK, 0) < 0)
|
||||
return -errno;
|
||||
|
||||
r = dynamic_user_pop(d, &uid, &uid_lock_fd);
|
||||
if (r < 0) {
|
||||
int new_uid_lock_fd;
|
||||
uid_t new_uid;
|
||||
|
||||
if (r != -EAGAIN)
|
||||
goto finish;
|
||||
|
||||
/* OK, nothing stored yet, let's try to find something useful. While we are working on this release the
|
||||
* lock however, so that nobody else blocks on our NSS lookups. */
|
||||
(void) lockf(d->storage_socket[0], F_ULOCK, 0);
|
||||
|
||||
/* Let's see if a proper, static user or group by this name exists. Try to take the lock on
|
||||
* /etc/passwd, if that fails with EROFS then /etc is read-only. In that case it's fine if we don't
|
||||
* take the lock, given that users can't be added there anyway in this case. */
|
||||
etc_passwd_lock_fd = take_etc_passwd_lock(NULL);
|
||||
if (etc_passwd_lock_fd < 0 && etc_passwd_lock_fd != -EROFS)
|
||||
return etc_passwd_lock_fd;
|
||||
|
||||
/* First, let's parse this as numeric UID */
|
||||
r = parse_uid(d->name, &uid);
|
||||
if (r < 0) {
|
||||
struct passwd *p;
|
||||
struct group *g;
|
||||
|
||||
/* OK, this is not a numeric UID. Let's see if there's a user by this name */
|
||||
p = getpwnam(d->name);
|
||||
if (p)
|
||||
uid = p->pw_uid;
|
||||
|
||||
/* Let's see if there's a group by this name */
|
||||
g = getgrnam(d->name);
|
||||
if (g) {
|
||||
/* If the UID/GID of the user/group of the same don't match, refuse operation */
|
||||
if (uid != UID_INVALID && uid != (uid_t) g->gr_gid)
|
||||
return -EILSEQ;
|
||||
|
||||
uid = (uid_t) g->gr_gid;
|
||||
}
|
||||
}
|
||||
|
||||
if (uid == UID_INVALID) {
|
||||
/* No static UID assigned yet, excellent. Let's pick a new dynamic one, and lock it. */
|
||||
|
||||
uid_lock_fd = pick_uid(d->name, &uid);
|
||||
if (uid_lock_fd < 0)
|
||||
return uid_lock_fd;
|
||||
}
|
||||
|
||||
/* So, we found a working UID/lock combination. Let's see if we actually still need it. */
|
||||
if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) {
|
||||
unlink_uid_lock(uid_lock_fd, uid);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = dynamic_user_pop(d, &new_uid, &new_uid_lock_fd);
|
||||
if (r < 0) {
|
||||
if (r != -EAGAIN) {
|
||||
/* OK, something bad happened, let's get rid of the bits we acquired. */
|
||||
unlink_uid_lock(uid_lock_fd, uid);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Great! Nothing is stored here, still. Store our newly acquired data. */
|
||||
} else {
|
||||
/* Hmm, so as it appears there's now something stored in the storage socket. Throw away what we
|
||||
* acquired, and use what's stored now. */
|
||||
|
||||
unlink_uid_lock(uid_lock_fd, uid);
|
||||
safe_close(uid_lock_fd);
|
||||
|
||||
uid = new_uid;
|
||||
uid_lock_fd = new_uid_lock_fd;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the UID/GID was already allocated dynamically, push the data we popped out back in. If it was already
|
||||
* allocated statically, push the UID back too, but do not push the lock fd in. If we allocated the UID
|
||||
* dynamically right here, push that in along with the lock fd for it. */
|
||||
r = dynamic_user_push(d, uid, uid_lock_fd);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
*ret = uid;
|
||||
r = 0;
|
||||
|
||||
finish:
|
||||
(void) lockf(d->storage_socket[0], F_ULOCK, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
int dynamic_user_current(DynamicUser *d, uid_t *ret) {
|
||||
_cleanup_close_ int lock_fd = -1;
|
||||
uid_t uid;
|
||||
int r;
|
||||
|
||||
assert(d);
|
||||
assert(ret);
|
||||
|
||||
/* Get the currently assigned UID for the user, if there's any. This simply pops the data from the storage socket, and pushes it back in right-away. */
|
||||
|
||||
if (lockf(d->storage_socket[0], F_LOCK, 0) < 0)
|
||||
return -errno;
|
||||
|
||||
r = dynamic_user_pop(d, &uid, &lock_fd);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = dynamic_user_push(d, uid, lock_fd);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
*ret = uid;
|
||||
r = 0;
|
||||
|
||||
finish:
|
||||
(void) lockf(d->storage_socket[0], F_ULOCK, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
DynamicUser* dynamic_user_ref(DynamicUser *d) {
|
||||
if (!d)
|
||||
return NULL;
|
||||
|
||||
assert(d->n_ref > 0);
|
||||
d->n_ref++;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
DynamicUser* dynamic_user_unref(DynamicUser *d) {
|
||||
if (!d)
|
||||
return NULL;
|
||||
|
||||
/* Note that this doesn't actually release any resources itself. If a dynamic user should be fully destroyed
|
||||
* and its UID released, use dynamic_user_destroy() instead. NB: the dynamic user table may contain entries
|
||||
* with no references, which is commonly the case right before a daemon reload. */
|
||||
|
||||
assert(d->n_ref > 0);
|
||||
d->n_ref--;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int dynamic_user_close(DynamicUser *d) {
|
||||
_cleanup_close_ int lock_fd = -1;
|
||||
uid_t uid;
|
||||
int r;
|
||||
|
||||
/* Release the user ID, by releasing the lock on it, and emptying the storage socket. After this the user is
|
||||
* unrealized again, much like it was after it the DynamicUser object was first allocated. */
|
||||
|
||||
if (lockf(d->storage_socket[0], F_LOCK, 0) < 0)
|
||||
return -errno;
|
||||
|
||||
r = dynamic_user_pop(d, &uid, &lock_fd);
|
||||
if (r == -EAGAIN) {
|
||||
/* User wasn't realized yet, nothing to do. */
|
||||
r = 0;
|
||||
goto finish;
|
||||
}
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
/* This dynamic user was realized and dynamically allocated. In this case, let's remove the lock file. */
|
||||
unlink_uid_lock(lock_fd, uid);
|
||||
r = 1;
|
||||
|
||||
finish:
|
||||
(void) lockf(d->storage_socket[0], F_ULOCK, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
DynamicUser* dynamic_user_destroy(DynamicUser *d) {
|
||||
if (!d)
|
||||
return NULL;
|
||||
|
||||
/* Drop a reference to a DynamicUser object, and destroy the user completely if this was the last
|
||||
* reference. This is called whenever a service is shut down and wants its dynamic UID gone. Note that
|
||||
* dynamic_user_unref() is what is called whenever a service is simply freed, for example during a reload
|
||||
* cycle, where the dynamic users should not be destroyed, but our datastructures should. */
|
||||
|
||||
dynamic_user_unref(d);
|
||||
|
||||
if (d->n_ref > 0)
|
||||
return NULL;
|
||||
|
||||
(void) dynamic_user_close(d);
|
||||
return dynamic_user_free(d);
|
||||
}
|
||||
|
||||
int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds) {
|
||||
DynamicUser *d;
|
||||
Iterator i;
|
||||
|
||||
assert(m);
|
||||
assert(f);
|
||||
assert(fds);
|
||||
|
||||
/* Dump the dynamic user database into the manager serialization, to deal with daemon reloads. */
|
||||
|
||||
HASHMAP_FOREACH(d, m->dynamic_users, i) {
|
||||
int copy0, copy1;
|
||||
|
||||
copy0 = fdset_put_dup(fds, d->storage_socket[0]);
|
||||
if (copy0 < 0)
|
||||
return copy0;
|
||||
|
||||
copy1 = fdset_put_dup(fds, d->storage_socket[1]);
|
||||
if (copy1 < 0)
|
||||
return copy1;
|
||||
|
||||
fprintf(f, "dynamic-user=%s %i %i\n", d->name, copy0, copy1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds) {
|
||||
_cleanup_free_ char *name = NULL, *s0 = NULL, *s1 = NULL;
|
||||
int r, fd0, fd1;
|
||||
|
||||
assert(m);
|
||||
assert(value);
|
||||
assert(fds);
|
||||
|
||||
/* Parse the serialization again, after a daemon reload */
|
||||
|
||||
r = extract_many_words(&value, NULL, 0, &name, &s0, &s1, NULL);
|
||||
if (r != 3 || !isempty(value)) {
|
||||
log_debug("Unable to parse dynamic user line.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (safe_atoi(s0, &fd0) < 0 || !fdset_contains(fds, fd0)) {
|
||||
log_debug("Unable to process dynamic user fd specification.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (safe_atoi(s1, &fd1) < 0 || !fdset_contains(fds, fd1)) {
|
||||
log_debug("Unable to process dynamic user fd specification.");
|
||||
return;
|
||||
}
|
||||
|
||||
r = dynamic_user_add(m, name, (int[]) { fd0, fd1 }, NULL);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to add dynamic user: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
(void) fdset_remove(fds, fd0);
|
||||
(void) fdset_remove(fds, fd1);
|
||||
}
|
||||
|
||||
void dynamic_user_vacuum(Manager *m, bool close_user) {
|
||||
DynamicUser *d;
|
||||
Iterator i;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* Empty the dynamic user database, optionally cleaning up orphaned dynamic users, i.e. destroy and free users
|
||||
* to which no reference exist. This is called after a daemon reload finished, in order to destroy users which
|
||||
* might not be referenced anymore. */
|
||||
|
||||
HASHMAP_FOREACH(d, m->dynamic_users, i) {
|
||||
if (d->n_ref > 0)
|
||||
continue;
|
||||
|
||||
if (close_user) {
|
||||
log_debug("Removing orphaned dynamic user %s", d->name);
|
||||
(void) dynamic_user_close(d);
|
||||
}
|
||||
|
||||
dynamic_user_free(d);
|
||||
}
|
||||
}
|
||||
|
||||
int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret) {
|
||||
char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
|
||||
_cleanup_free_ char *user = NULL;
|
||||
uid_t check_uid;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(ret);
|
||||
|
||||
/* A friendly way to translate a dynamic user's UID into a his name. */
|
||||
|
||||
if (uid < UID_PICK_MIN)
|
||||
return -ESRCH;
|
||||
if (uid > UID_PICK_MAX)
|
||||
return -ESRCH;
|
||||
|
||||
xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid);
|
||||
r = read_one_line_file(lock_path, &user);
|
||||
if (r == -ENOENT)
|
||||
return -ESRCH;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* The lock file might be stale, hence let's verify the data before we return it */
|
||||
r = dynamic_user_lookup_name(m, user, &check_uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (check_uid != uid) /* lock file doesn't match our own idea */
|
||||
return -ESRCH;
|
||||
|
||||
*ret = user;
|
||||
user = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) {
|
||||
DynamicUser *d;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(name);
|
||||
assert(ret);
|
||||
|
||||
/* A friendly call for translating a dynamic user's name into its UID */
|
||||
|
||||
d = hashmap_get(m->dynamic_users, name);
|
||||
if (!d)
|
||||
return -ESRCH;
|
||||
|
||||
r = dynamic_user_current(d, ret);
|
||||
if (r == -EAGAIN) /* not realized yet? */
|
||||
return -ESRCH;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group) {
|
||||
bool acquired = false;
|
||||
int r;
|
||||
|
||||
assert(creds);
|
||||
assert(m);
|
||||
|
||||
/* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some
|
||||
* services use different user and groups. For cases like that there's DynamicCreds containing a pair of user
|
||||
* and group. This call allocates a pair. */
|
||||
|
||||
if (!creds->user && user) {
|
||||
r = dynamic_user_acquire(m, user, &creds->user);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
acquired = true;
|
||||
}
|
||||
|
||||
if (!creds->group) {
|
||||
|
||||
if (creds->user && (!group || streq_ptr(user, group)))
|
||||
creds->group = dynamic_user_ref(creds->user);
|
||||
else {
|
||||
r = dynamic_user_acquire(m, group, &creds->group);
|
||||
if (r < 0) {
|
||||
if (acquired)
|
||||
creds->user = dynamic_user_unref(creds->user);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid) {
|
||||
uid_t u = UID_INVALID;
|
||||
gid_t g = GID_INVALID;
|
||||
int r;
|
||||
|
||||
assert(creds);
|
||||
assert(uid);
|
||||
assert(gid);
|
||||
|
||||
/* Realize both the referenced user and group */
|
||||
|
||||
if (creds->user) {
|
||||
r = dynamic_user_realize(creds->user, &u);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (creds->group && creds->group != creds->user) {
|
||||
r = dynamic_user_realize(creds->group, &g);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else
|
||||
g = u;
|
||||
|
||||
*uid = u;
|
||||
*gid = g;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dynamic_creds_unref(DynamicCreds *creds) {
|
||||
assert(creds);
|
||||
|
||||
creds->user = dynamic_user_unref(creds->user);
|
||||
creds->group = dynamic_user_unref(creds->group);
|
||||
}
|
||||
|
||||
void dynamic_creds_destroy(DynamicCreds *creds) {
|
||||
assert(creds);
|
||||
|
||||
creds->user = dynamic_user_destroy(creds->user);
|
||||
creds->group = dynamic_user_destroy(creds->group);
|
||||
}
|
66
src/core/dynamic-user.h
Normal file
66
src/core/dynamic-user.h
Normal file
@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2016 Lennart Poettering
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
systemd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
typedef struct DynamicUser DynamicUser;
|
||||
|
||||
typedef struct DynamicCreds {
|
||||
/* A combination of a dynamic user and group */
|
||||
DynamicUser *user;
|
||||
DynamicUser *group;
|
||||
} DynamicCreds;
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
/* Note that this object always allocates a pair of user and group under the same name, even if one of them isn't
|
||||
* used. This means, if you want to allocate a group and user pair, and they might have two different names, then you
|
||||
* need to allocated two of these objects. DynamicCreds below makes that easy. */
|
||||
struct DynamicUser {
|
||||
int n_ref;
|
||||
Manager *manager;
|
||||
|
||||
/* An AF_UNIX socket pair that contains a datagram containing both the numeric ID assigned, as well as a lock
|
||||
* file fd locking the user ID we picked. */
|
||||
int storage_socket[2];
|
||||
|
||||
char name[];
|
||||
};
|
||||
|
||||
int dynamic_user_acquire(Manager *m, const char *name, DynamicUser **ret);
|
||||
|
||||
int dynamic_user_realize(DynamicUser *d, uid_t *ret);
|
||||
int dynamic_user_current(DynamicUser *d, uid_t *ret);
|
||||
|
||||
DynamicUser* dynamic_user_ref(DynamicUser *d);
|
||||
DynamicUser* dynamic_user_unref(DynamicUser *d);
|
||||
DynamicUser* dynamic_user_destroy(DynamicUser *d);
|
||||
|
||||
int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds);
|
||||
void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds);
|
||||
void dynamic_user_vacuum(Manager *m, bool close_user);
|
||||
|
||||
int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret);
|
||||
int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret);
|
||||
|
||||
int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group);
|
||||
int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid);
|
||||
|
||||
void dynamic_creds_unref(DynamicCreds *creds);
|
||||
void dynamic_creds_destroy(DynamicCreds *creds);
|
@ -1526,14 +1526,28 @@ static bool exec_needs_mount_namespace(
|
||||
return false;
|
||||
}
|
||||
|
||||
static void append_socket_pair(int *array, unsigned *n, int pair[2]) {
|
||||
assert(array);
|
||||
assert(n);
|
||||
|
||||
if (!pair)
|
||||
return;
|
||||
|
||||
if (pair[0] >= 0)
|
||||
array[(*n)++] = pair[0];
|
||||
if (pair[1] >= 0)
|
||||
array[(*n)++] = pair[1];
|
||||
}
|
||||
|
||||
static int close_remaining_fds(
|
||||
const ExecParameters *params,
|
||||
ExecRuntime *runtime,
|
||||
DynamicCreds *dcreds,
|
||||
int socket_fd,
|
||||
int *fds, unsigned n_fds) {
|
||||
|
||||
unsigned n_dont_close = 0;
|
||||
int dont_close[n_fds + 7];
|
||||
int dont_close[n_fds + 11];
|
||||
|
||||
assert(params);
|
||||
|
||||
@ -1551,11 +1565,14 @@ static int close_remaining_fds(
|
||||
n_dont_close += n_fds;
|
||||
}
|
||||
|
||||
if (runtime) {
|
||||
if (runtime->netns_storage_socket[0] >= 0)
|
||||
dont_close[n_dont_close++] = runtime->netns_storage_socket[0];
|
||||
if (runtime->netns_storage_socket[1] >= 0)
|
||||
dont_close[n_dont_close++] = runtime->netns_storage_socket[1];
|
||||
if (runtime)
|
||||
append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket);
|
||||
|
||||
if (dcreds) {
|
||||
if (dcreds->user)
|
||||
append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket);
|
||||
if (dcreds->group)
|
||||
append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket);
|
||||
}
|
||||
|
||||
return close_all_fds(dont_close, n_dont_close);
|
||||
@ -1567,6 +1584,7 @@ static int exec_child(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
ExecRuntime *runtime,
|
||||
DynamicCreds *dcreds,
|
||||
char **argv,
|
||||
int socket_fd,
|
||||
int *fds, unsigned n_fds,
|
||||
@ -1617,7 +1635,7 @@ static int exec_child(
|
||||
|
||||
log_forget_fds();
|
||||
|
||||
r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds);
|
||||
r = close_remaining_fds(params, runtime, dcreds, socket_fd, fds, n_fds);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_FDS;
|
||||
return r;
|
||||
@ -1650,26 +1668,49 @@ static int exec_child(
|
||||
}
|
||||
}
|
||||
|
||||
if (context->user) {
|
||||
username = context->user;
|
||||
r = get_user_creds(&username, &uid, &gid, &home, &shell);
|
||||
if (context->dynamic_user && dcreds) {
|
||||
|
||||
/* Make sure we bypass our own NSS module for any NSS checks */
|
||||
if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = dynamic_creds_realize(dcreds, &uid, &gid);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->group) {
|
||||
const char *g = context->group;
|
||||
if (uid == UID_INVALID || gid == GID_INVALID) {
|
||||
*exit_status = EXIT_USER;
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
r = get_group_creds(&g, &gid);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_GROUP;
|
||||
return r;
|
||||
if (dcreds->user)
|
||||
username = dcreds->user->name;
|
||||
|
||||
} else {
|
||||
if (context->user) {
|
||||
username = context->user;
|
||||
r = get_user_creds(&username, &uid, &gid, &home, &shell);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->group) {
|
||||
const char *g = context->group;
|
||||
|
||||
r = get_group_creds(&g, &gid);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_GROUP;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* If a socket is connected to STDIN/STDOUT/STDERR, we
|
||||
* must sure to drop O_NONBLOCK */
|
||||
if (socket_fd >= 0)
|
||||
@ -2192,6 +2233,7 @@ int exec_spawn(Unit *unit,
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
ExecRuntime *runtime,
|
||||
DynamicCreds *dcreds,
|
||||
pid_t *ret) {
|
||||
|
||||
_cleanup_strv_free_ char **files_env = NULL;
|
||||
@ -2250,6 +2292,7 @@ int exec_spawn(Unit *unit,
|
||||
context,
|
||||
params,
|
||||
runtime,
|
||||
dcreds,
|
||||
argv,
|
||||
socket_fd,
|
||||
fds, n_fds,
|
||||
@ -2723,6 +2766,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
|
||||
if (c->group)
|
||||
fprintf(f, "%sGroup: %s\n", prefix, c->group);
|
||||
|
||||
fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user));
|
||||
|
||||
if (strv_length(c->supplementary_groups) > 0) {
|
||||
fprintf(f, "%sSupplementaryGroups:", prefix);
|
||||
strv_fprintf(f, c->supplementary_groups);
|
||||
|
@ -92,6 +92,8 @@ struct ExecRuntime {
|
||||
char *tmp_dir;
|
||||
char *var_tmp_dir;
|
||||
|
||||
/* An AF_UNIX socket pair, that contains a datagram containing a file descriptor referring to the network
|
||||
* namespace. */
|
||||
int netns_storage_socket[2];
|
||||
};
|
||||
|
||||
@ -174,6 +176,8 @@ struct ExecContext {
|
||||
|
||||
bool no_new_privileges;
|
||||
|
||||
bool dynamic_user;
|
||||
|
||||
/* This is not exposed to the user but available
|
||||
* internally. We need it to make sure that whenever we spawn
|
||||
* /usr/bin/mount it is run in the same process group as us so
|
||||
@ -235,12 +239,14 @@ struct ExecParameters {
|
||||
};
|
||||
|
||||
#include "unit.h"
|
||||
#include "dynamic-user.h"
|
||||
|
||||
int exec_spawn(Unit *unit,
|
||||
ExecCommand *command,
|
||||
const ExecContext *context,
|
||||
const ExecParameters *exec_params,
|
||||
ExecRuntime *runtime,
|
||||
DynamicCreds *dynamic_creds,
|
||||
pid_t *ret);
|
||||
|
||||
void exec_command_done(ExecCommand *c);
|
||||
|
@ -19,9 +19,9 @@ m4_dnl Define the context options only once
|
||||
m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
|
||||
`$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context)
|
||||
$1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory)
|
||||
$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user)
|
||||
$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group)
|
||||
$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups)
|
||||
$1.User, config_parse_user_group, 0, offsetof($1, exec_context.user)
|
||||
$1.Group, config_parse_user_group, 0, offsetof($1, exec_context.group)
|
||||
$1.SupplementaryGroups, config_parse_user_group_strv, 0, offsetof($1, exec_context.supplementary_groups)
|
||||
$1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context)
|
||||
$1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context)
|
||||
$1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context)
|
||||
@ -34,6 +34,7 @@ $1.UMask, config_parse_mode, 0,
|
||||
$1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment)
|
||||
$1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files)
|
||||
$1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment)
|
||||
$1.DynamicUser, config_parse_bool, 0, offsetof($1, exec_context.dynamic_user)
|
||||
$1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input)
|
||||
$1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output)
|
||||
$1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error)
|
||||
@ -285,8 +286,8 @@ Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC
|
||||
Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command)
|
||||
Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command)
|
||||
Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec)
|
||||
Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user)
|
||||
Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group)
|
||||
Socket.SocketUser, config_parse_user_group, 0, offsetof(Socket, user)
|
||||
Socket.SocketGroup, config_parse_user_group, 0, offsetof(Socket, group)
|
||||
Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode)
|
||||
Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode)
|
||||
Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept)
|
||||
|
@ -64,6 +64,7 @@
|
||||
#include "unit-name.h"
|
||||
#include "unit-printf.h"
|
||||
#include "unit.h"
|
||||
#include "user-util.h"
|
||||
#include "utf8.h"
|
||||
#include "web-util.h"
|
||||
|
||||
@ -1763,6 +1764,123 @@ int config_parse_sec_fix_0(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_user_group(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
char **user = data, *n;
|
||||
Unit *u = userdata;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(u);
|
||||
|
||||
if (isempty(rvalue))
|
||||
n = NULL;
|
||||
else {
|
||||
_cleanup_free_ char *k = NULL;
|
||||
|
||||
r = unit_full_printf(u, rvalue, &k);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!valid_user_group_name_or_id(k)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = k;
|
||||
k = NULL;
|
||||
}
|
||||
|
||||
free(*user);
|
||||
*user = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_user_group_strv(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
char ***users = data;
|
||||
Unit *u = userdata;
|
||||
const char *p;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(u);
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
char **empty;
|
||||
|
||||
empty = new0(char*, 1);
|
||||
if (!empty)
|
||||
return log_oom();
|
||||
|
||||
strv_free(*users);
|
||||
*users = empty;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = rvalue;
|
||||
for (;;) {
|
||||
_cleanup_free_ char *word = NULL, *k = NULL;
|
||||
|
||||
r = extract_first_word(&p, &word, WHITESPACE, 0);
|
||||
if (r == 0)
|
||||
break;
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
|
||||
break;
|
||||
}
|
||||
|
||||
r = unit_full_printf(u, word, &k);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", word);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!valid_user_group_name_or_id(k)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = strv_push(users, k);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
k = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_busname_service(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
|
@ -111,6 +111,8 @@ int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned
|
||||
int config_parse_working_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_fdname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_user_group(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
int config_parse_user_group_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||
|
||||
/* gperf prototypes */
|
||||
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);
|
||||
|
@ -1004,6 +1004,9 @@ Manager* manager_free(Manager *m) {
|
||||
|
||||
bus_done(m);
|
||||
|
||||
dynamic_user_vacuum(m, false);
|
||||
hashmap_free(m->dynamic_users);
|
||||
|
||||
hashmap_free(m->units);
|
||||
hashmap_free(m->jobs);
|
||||
hashmap_free(m->watch_pids1);
|
||||
@ -1227,6 +1230,9 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
|
||||
/* Third, fire things up! */
|
||||
manager_coldplug(m);
|
||||
|
||||
/* Release any dynamic users no longer referenced */
|
||||
dynamic_user_vacuum(m, true);
|
||||
|
||||
if (serialization) {
|
||||
assert(m->n_reloading > 0);
|
||||
m->n_reloading--;
|
||||
@ -2403,6 +2409,10 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
|
||||
|
||||
bus_track_serialize(m->subscribed, f);
|
||||
|
||||
r = dynamic_user_serialize(m, f, fds);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fputc('\n', f);
|
||||
|
||||
HASHMAP_FOREACH_KEY(u, t, m->units, i) {
|
||||
@ -2579,7 +2589,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
|
||||
m->kdbus_fd = fdset_remove(fds, fd);
|
||||
}
|
||||
|
||||
} else {
|
||||
} else if (startswith(l, "dynamic-user="))
|
||||
dynamic_user_deserialize_one(m, l + 13, fds);
|
||||
else {
|
||||
int k;
|
||||
|
||||
k = bus_track_deserialize_item(&m->deserialized_subscribed, l);
|
||||
@ -2660,6 +2672,7 @@ int manager_reload(Manager *m) {
|
||||
manager_clear_jobs_and_units(m);
|
||||
lookup_paths_flush_generator(&m->lookup_paths);
|
||||
lookup_paths_free(&m->lookup_paths);
|
||||
dynamic_user_vacuum(m, false);
|
||||
|
||||
q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL);
|
||||
if (q < 0 && r >= 0)
|
||||
@ -2696,6 +2709,9 @@ int manager_reload(Manager *m) {
|
||||
/* Third, fire things up! */
|
||||
manager_coldplug(m);
|
||||
|
||||
/* Release any dynamic users no longer referenced */
|
||||
dynamic_user_vacuum(m, true);
|
||||
|
||||
/* Sync current state of bus names with our set of listening units */
|
||||
if (m->api_bus)
|
||||
manager_sync_bus_names(m, m->api_bus);
|
||||
|
@ -298,6 +298,9 @@ struct Manager {
|
||||
/* Used for processing polkit authorization responses */
|
||||
Hashmap *polkit_registry;
|
||||
|
||||
/* Dynamic users/groups, indexed by their name */
|
||||
Hashmap *dynamic_users;
|
||||
|
||||
/* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */
|
||||
RateLimit ctrl_alt_del_ratelimit;
|
||||
|
||||
|
@ -245,6 +245,8 @@ static void mount_done(Unit *u) {
|
||||
exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX);
|
||||
m->control_command = NULL;
|
||||
|
||||
dynamic_creds_unref(&m->dynamic_creds);
|
||||
|
||||
mount_unwatch_control_pid(m);
|
||||
|
||||
m->timer_event_source = sd_event_source_unref(m->timer_event_source);
|
||||
@ -648,6 +650,9 @@ static int mount_coldplug(Unit *u) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!IN_SET(new_state, MOUNT_DEAD, MOUNT_FAILED))
|
||||
(void) unit_setup_dynamic_creds(u);
|
||||
|
||||
mount_set_state(m, new_state);
|
||||
return 0;
|
||||
}
|
||||
@ -716,6 +721,10 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = unit_setup_dynamic_creds(UNIT(m));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec));
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -732,6 +741,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
|
||||
&m->exec_context,
|
||||
&exec_params,
|
||||
m->exec_runtime,
|
||||
&m->dynamic_creds,
|
||||
&pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -752,12 +762,14 @@ static void mount_enter_dead(Mount *m, MountResult f) {
|
||||
if (f != MOUNT_SUCCESS)
|
||||
m->result = f;
|
||||
|
||||
mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD);
|
||||
|
||||
exec_runtime_destroy(m->exec_runtime);
|
||||
m->exec_runtime = exec_runtime_unref(m->exec_runtime);
|
||||
|
||||
exec_context_destroy_runtime_directory(&m->exec_context, manager_get_runtime_prefix(UNIT(m)->manager));
|
||||
|
||||
mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD);
|
||||
dynamic_creds_destroy(&m->dynamic_creds);
|
||||
}
|
||||
|
||||
static void mount_enter_mounted(Mount *m, MountResult f) {
|
||||
@ -1817,6 +1829,7 @@ const UnitVTable mount_vtable = {
|
||||
.cgroup_context_offset = offsetof(Mount, cgroup_context),
|
||||
.kill_context_offset = offsetof(Mount, kill_context),
|
||||
.exec_runtime_offset = offsetof(Mount, exec_runtime),
|
||||
.dynamic_creds_offset = offsetof(Mount, dynamic_creds),
|
||||
|
||||
.sections =
|
||||
"Unit\0"
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
typedef struct Mount Mount;
|
||||
|
||||
#include "execute.h"
|
||||
#include "kill.h"
|
||||
#include "dynamic-user.h"
|
||||
|
||||
typedef enum MountExecCommand {
|
||||
MOUNT_EXEC_MOUNT,
|
||||
@ -85,6 +85,7 @@ struct Mount {
|
||||
CGroupContext cgroup_context;
|
||||
|
||||
ExecRuntime *exec_runtime;
|
||||
DynamicCreds dynamic_creds;
|
||||
|
||||
MountState state, deserialized_state;
|
||||
|
||||
|
@ -322,6 +322,8 @@ static void service_done(Unit *u) {
|
||||
s->control_command = NULL;
|
||||
s->main_command = NULL;
|
||||
|
||||
dynamic_creds_unref(&s->dynamic_creds);
|
||||
|
||||
exit_status_set_free(&s->restart_prevent_status);
|
||||
exit_status_set_free(&s->restart_force_status);
|
||||
exit_status_set_free(&s->success_status);
|
||||
@ -1030,6 +1032,9 @@ static int service_coldplug(Unit *u) {
|
||||
if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
|
||||
service_start_watchdog(s);
|
||||
|
||||
if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART))
|
||||
(void) unit_setup_dynamic_creds(u);
|
||||
|
||||
service_set_state(s, s->deserialized_state);
|
||||
return 0;
|
||||
}
|
||||
@ -1184,6 +1189,10 @@ static int service_spawn(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = unit_setup_dynamic_creds(UNIT(s));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (pass_fds ||
|
||||
s->exec_context.std_input == EXEC_INPUT_SOCKET ||
|
||||
s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
|
||||
@ -1285,6 +1294,7 @@ static int service_spawn(
|
||||
&s->exec_context,
|
||||
&exec_params,
|
||||
s->exec_runtime,
|
||||
&s->dynamic_creds,
|
||||
&pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1418,9 +1428,12 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
|
||||
exec_runtime_destroy(s->exec_runtime);
|
||||
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
|
||||
|
||||
/* Also, remove the runtime directory in */
|
||||
/* Also, remove the runtime directory */
|
||||
exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
|
||||
|
||||
/* Release the user, and destroy it if we are the only remaining owner */
|
||||
dynamic_creds_destroy(&s->dynamic_creds);
|
||||
|
||||
/* Try to delete the pid file. At this point it will be
|
||||
* out-of-date, and some software might be confused by it, so
|
||||
* let's remove it. */
|
||||
@ -3323,6 +3336,7 @@ const UnitVTable service_vtable = {
|
||||
.cgroup_context_offset = offsetof(Service, cgroup_context),
|
||||
.kill_context_offset = offsetof(Service, kill_context),
|
||||
.exec_runtime_offset = offsetof(Service, exec_runtime),
|
||||
.dynamic_creds_offset = offsetof(Service, dynamic_creds),
|
||||
|
||||
.sections =
|
||||
"Unit\0"
|
||||
|
@ -148,6 +148,7 @@ struct Service {
|
||||
|
||||
/* Runtime data of the execution context */
|
||||
ExecRuntime *exec_runtime;
|
||||
DynamicCreds dynamic_creds;
|
||||
|
||||
pid_t main_pid, control_pid;
|
||||
int socket_fd;
|
||||
|
@ -150,6 +150,8 @@ static void socket_done(Unit *u) {
|
||||
exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX);
|
||||
s->control_command = NULL;
|
||||
|
||||
dynamic_creds_unref(&s->dynamic_creds);
|
||||
|
||||
socket_unwatch_control_pid(s);
|
||||
|
||||
unit_ref_unset(&s->service);
|
||||
@ -1602,6 +1604,9 @@ static int socket_coldplug(Unit *u) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED))
|
||||
(void) unit_setup_dynamic_creds(u);
|
||||
|
||||
socket_set_state(s, s->deserialized_state);
|
||||
return 0;
|
||||
}
|
||||
@ -1633,6 +1638,10 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = unit_setup_dynamic_creds(UNIT(s));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec));
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1654,6 +1663,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
|
||||
&s->exec_context,
|
||||
&exec_params,
|
||||
s->exec_runtime,
|
||||
&s->dynamic_creds,
|
||||
&pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1757,12 +1767,14 @@ static void socket_enter_dead(Socket *s, SocketResult f) {
|
||||
if (f != SOCKET_SUCCESS)
|
||||
s->result = f;
|
||||
|
||||
socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
|
||||
|
||||
exec_runtime_destroy(s->exec_runtime);
|
||||
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
|
||||
|
||||
exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
|
||||
|
||||
socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
|
||||
dynamic_creds_destroy(&s->dynamic_creds);
|
||||
}
|
||||
|
||||
static void socket_enter_signal(Socket *s, SocketState state, SocketResult f);
|
||||
@ -2930,6 +2942,7 @@ const UnitVTable socket_vtable = {
|
||||
.cgroup_context_offset = offsetof(Socket, cgroup_context),
|
||||
.kill_context_offset = offsetof(Socket, kill_context),
|
||||
.exec_runtime_offset = offsetof(Socket, exec_runtime),
|
||||
.dynamic_creds_offset = offsetof(Socket, dynamic_creds),
|
||||
|
||||
.sections =
|
||||
"Unit\0"
|
||||
|
@ -94,7 +94,9 @@ struct Socket {
|
||||
ExecContext exec_context;
|
||||
KillContext kill_context;
|
||||
CGroupContext cgroup_context;
|
||||
|
||||
ExecRuntime *exec_runtime;
|
||||
DynamicCreds dynamic_creds;
|
||||
|
||||
/* For Accept=no sockets refers to the one service we'll
|
||||
activate. For Accept=yes sockets is either NULL, or filled
|
||||
|
@ -153,6 +153,8 @@ static void swap_done(Unit *u) {
|
||||
exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX);
|
||||
s->control_command = NULL;
|
||||
|
||||
dynamic_creds_unref(&s->dynamic_creds);
|
||||
|
||||
swap_unwatch_control_pid(s);
|
||||
|
||||
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
|
||||
@ -553,6 +555,9 @@ static int swap_coldplug(Unit *u) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED))
|
||||
(void) unit_setup_dynamic_creds(u);
|
||||
|
||||
swap_set_state(s, new_state);
|
||||
return 0;
|
||||
}
|
||||
@ -628,6 +633,10 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
r = unit_setup_dynamic_creds(UNIT(s));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec));
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
@ -644,6 +653,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
|
||||
&s->exec_context,
|
||||
&exec_params,
|
||||
s->exec_runtime,
|
||||
&s->dynamic_creds,
|
||||
&pid);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
@ -668,12 +678,14 @@ static void swap_enter_dead(Swap *s, SwapResult f) {
|
||||
if (f != SWAP_SUCCESS)
|
||||
s->result = f;
|
||||
|
||||
swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD);
|
||||
|
||||
exec_runtime_destroy(s->exec_runtime);
|
||||
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
|
||||
|
||||
exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
|
||||
|
||||
swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD);
|
||||
dynamic_creds_destroy(&s->dynamic_creds);
|
||||
}
|
||||
|
||||
static void swap_enter_active(Swap *s, SwapResult f) {
|
||||
@ -1466,6 +1478,7 @@ const UnitVTable swap_vtable = {
|
||||
.cgroup_context_offset = offsetof(Swap, cgroup_context),
|
||||
.kill_context_offset = offsetof(Swap, kill_context),
|
||||
.exec_runtime_offset = offsetof(Swap, exec_runtime),
|
||||
.dynamic_creds_offset = offsetof(Swap, dynamic_creds),
|
||||
|
||||
.sections =
|
||||
"Unit\0"
|
||||
|
@ -82,6 +82,7 @@ struct Swap {
|
||||
CGroupContext cgroup_context;
|
||||
|
||||
ExecRuntime *exec_runtime;
|
||||
DynamicCreds dynamic_creds;
|
||||
|
||||
SwapState state, deserialized_state;
|
||||
|
||||
|
@ -3224,6 +3224,33 @@ void unit_ref_unset(UnitRef *ref) {
|
||||
ref->unit = NULL;
|
||||
}
|
||||
|
||||
static int user_from_unit_name(Unit *u, char **ret) {
|
||||
|
||||
static const uint8_t hash_key[] = {
|
||||
0x58, 0x1a, 0xaf, 0xe6, 0x28, 0x58, 0x4e, 0x96,
|
||||
0xb4, 0x4e, 0xf5, 0x3b, 0x8c, 0x92, 0x07, 0xec
|
||||
};
|
||||
|
||||
_cleanup_free_ char *n = NULL;
|
||||
int r;
|
||||
|
||||
r = unit_name_to_prefix(u->id, &n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (valid_user_group_name(n)) {
|
||||
*ret = n;
|
||||
n = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we can't use the unit name as a user name, then let's hash it and use that */
|
||||
if (asprintf(ret, "_du%016" PRIx64, siphash24(n, strlen(n), hash_key)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int unit_patch_contexts(Unit *u) {
|
||||
CGroupContext *cc;
|
||||
ExecContext *ec;
|
||||
@ -3268,6 +3295,22 @@ int unit_patch_contexts(Unit *u) {
|
||||
|
||||
if (ec->private_devices)
|
||||
ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD);
|
||||
|
||||
if (ec->dynamic_user) {
|
||||
if (!ec->user) {
|
||||
r = user_from_unit_name(u, &ec->user);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!ec->group) {
|
||||
ec->group = strdup(ec->user);
|
||||
if (!ec->group)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ec->private_tmp = true;
|
||||
}
|
||||
}
|
||||
|
||||
cc = unit_get_cgroup_context(u);
|
||||
@ -3776,6 +3819,26 @@ int unit_setup_exec_runtime(Unit *u) {
|
||||
return exec_runtime_make(rt, unit_get_exec_context(u), u->id);
|
||||
}
|
||||
|
||||
int unit_setup_dynamic_creds(Unit *u) {
|
||||
ExecContext *ec;
|
||||
DynamicCreds *dcreds;
|
||||
size_t offset;
|
||||
|
||||
assert(u);
|
||||
|
||||
offset = UNIT_VTABLE(u)->dynamic_creds_offset;
|
||||
assert(offset > 0);
|
||||
dcreds = (DynamicCreds*) ((uint8_t*) u + offset);
|
||||
|
||||
ec = unit_get_exec_context(u);
|
||||
assert(ec);
|
||||
|
||||
if (!ec->dynamic_user)
|
||||
return 0;
|
||||
|
||||
return dynamic_creds_acquire(dcreds, u->manager, ec->user, ec->group);
|
||||
}
|
||||
|
||||
bool unit_type_supported(UnitType t) {
|
||||
if (_unlikely_(t < 0))
|
||||
return false;
|
||||
|
@ -291,6 +291,10 @@ struct UnitVTable {
|
||||
* that */
|
||||
size_t exec_runtime_offset;
|
||||
|
||||
/* If greater than 0, the offset into the object where the pointer to DynamicCreds is found, if the unit type
|
||||
* has that. */
|
||||
size_t dynamic_creds_offset;
|
||||
|
||||
/* The name of the configuration file section with the private settings of this unit */
|
||||
const char *private_section;
|
||||
|
||||
@ -589,6 +593,7 @@ CGroupContext *unit_get_cgroup_context(Unit *u) _pure_;
|
||||
ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_;
|
||||
|
||||
int unit_setup_exec_runtime(Unit *u);
|
||||
int unit_setup_dynamic_creds(Unit *u);
|
||||
|
||||
int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data);
|
||||
int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5);
|
||||
|
@ -44,6 +44,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_ISOLATION, EPERM),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH),
|
||||
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT),
|
||||
|
@ -40,6 +40,7 @@
|
||||
#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation"
|
||||
#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown"
|
||||
#define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning"
|
||||
#define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser"
|
||||
|
||||
#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
|
||||
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
|
||||
|
1
src/nss-systemd/Makefile
Symbolic link
1
src/nss-systemd/Makefile
Symbolic link
@ -0,0 +1 @@
|
||||
../Makefile
|
332
src/nss-systemd/nss-systemd.c
Normal file
332
src/nss-systemd/nss-systemd.c
Normal file
@ -0,0 +1,332 @@
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2016 Lennart Poettering
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
systemd is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <nss.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "bus-common-errors.h"
|
||||
#include "env-util.h"
|
||||
#include "macro.h"
|
||||
#include "nss-util.h"
|
||||
#include "signal-util.h"
|
||||
#include "user-util.h"
|
||||
#include "util.h"
|
||||
|
||||
NSS_GETPW_PROTOTYPES(systemd);
|
||||
NSS_GETGR_PROTOTYPES(systemd);
|
||||
|
||||
enum nss_status _nss_systemd_getpwnam_r(
|
||||
const char *name,
|
||||
struct passwd *pwd,
|
||||
char *buffer, size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
uint32_t translated;
|
||||
size_t l;
|
||||
int r;
|
||||
|
||||
BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
|
||||
|
||||
assert(name);
|
||||
assert(pwd);
|
||||
|
||||
/* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */
|
||||
if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
|
||||
goto not_found;
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
r = sd_bus_call_method(bus,
|
||||
"org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1",
|
||||
"org.freedesktop.systemd1.Manager",
|
||||
"LookupDynamicUserByName",
|
||||
&error,
|
||||
&reply,
|
||||
"s",
|
||||
name);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
|
||||
goto not_found;
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = sd_bus_message_read(reply, "u", &translated);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
l = strlen(name);
|
||||
if (buflen < l+1) {
|
||||
*errnop = ENOMEM;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
memcpy(buffer, name, l+1);
|
||||
|
||||
pwd->pw_name = buffer;
|
||||
pwd->pw_uid = (uid_t) translated;
|
||||
pwd->pw_gid = (uid_t) translated;
|
||||
pwd->pw_gecos = (char*) "Dynamic User";
|
||||
pwd->pw_passwd = (char*) "*"; /* locked */
|
||||
pwd->pw_dir = (char*) "/";
|
||||
pwd->pw_shell = (char*) "/sbin/nologin";
|
||||
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_SUCCESS;
|
||||
|
||||
not_found:
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
fail:
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
enum nss_status _nss_systemd_getpwuid_r(
|
||||
uid_t uid,
|
||||
struct passwd *pwd,
|
||||
char *buffer, size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
const char *translated;
|
||||
size_t l;
|
||||
int r;
|
||||
|
||||
BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
|
||||
|
||||
if (!uid_is_valid(uid)) {
|
||||
r = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (uid <= SYSTEM_UID_MAX)
|
||||
goto not_found;
|
||||
|
||||
if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
|
||||
goto not_found;
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
r = sd_bus_call_method(bus,
|
||||
"org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1",
|
||||
"org.freedesktop.systemd1.Manager",
|
||||
"LookupDynamicUserByUID",
|
||||
&error,
|
||||
&reply,
|
||||
"u",
|
||||
(uint32_t) uid);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
|
||||
goto not_found;
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = sd_bus_message_read(reply, "s", &translated);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
l = strlen(translated) + 1;
|
||||
if (buflen < l) {
|
||||
*errnop = ENOMEM;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
memcpy(buffer, translated, l);
|
||||
|
||||
pwd->pw_name = buffer;
|
||||
pwd->pw_uid = uid;
|
||||
pwd->pw_gid = uid;
|
||||
pwd->pw_gecos = (char*) "Dynamic User";
|
||||
pwd->pw_passwd = (char*) "*"; /* locked */
|
||||
pwd->pw_dir = (char*) "/";
|
||||
pwd->pw_shell = (char*) "/sbin/nologin";
|
||||
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_SUCCESS;
|
||||
|
||||
not_found:
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
fail:
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
enum nss_status _nss_systemd_getgrnam_r(
|
||||
const char *name,
|
||||
struct group *gr,
|
||||
char *buffer, size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
uint32_t translated;
|
||||
size_t l;
|
||||
int r;
|
||||
|
||||
BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
|
||||
|
||||
assert(name);
|
||||
assert(gr);
|
||||
|
||||
if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
|
||||
goto not_found;
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
r = sd_bus_call_method(bus,
|
||||
"org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1",
|
||||
"org.freedesktop.systemd1.Manager",
|
||||
"LookupDynamicUserByName",
|
||||
&error,
|
||||
&reply,
|
||||
"s",
|
||||
name);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
|
||||
goto not_found;
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = sd_bus_message_read(reply, "u", &translated);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
l = sizeof(char*) + strlen(name) + 1;
|
||||
if (buflen < l) {
|
||||
*errnop = ENOMEM;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
memzero(buffer, sizeof(char*));
|
||||
strcpy(buffer + sizeof(char*), name);
|
||||
|
||||
gr->gr_name = buffer + sizeof(char*);
|
||||
gr->gr_gid = (gid_t) translated;
|
||||
gr->gr_passwd = (char*) "*"; /* locked */
|
||||
gr->gr_mem = (char**) buffer;
|
||||
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_SUCCESS;
|
||||
|
||||
not_found:
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
fail:
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
enum nss_status _nss_systemd_getgrgid_r(
|
||||
gid_t gid,
|
||||
struct group *gr,
|
||||
char *buffer, size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
const char *translated;
|
||||
size_t l;
|
||||
int r;
|
||||
|
||||
BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
|
||||
|
||||
if (!gid_is_valid(gid)) {
|
||||
r = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (gid <= SYSTEM_GID_MAX)
|
||||
goto not_found;
|
||||
|
||||
if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
|
||||
goto not_found;
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
r = sd_bus_call_method(bus,
|
||||
"org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1",
|
||||
"org.freedesktop.systemd1.Manager",
|
||||
"LookupDynamicUserByUID",
|
||||
&error,
|
||||
&reply,
|
||||
"u",
|
||||
(uint32_t) gid);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
|
||||
goto not_found;
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = sd_bus_message_read(reply, "s", &translated);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
l = sizeof(char*) + strlen(translated) + 1;
|
||||
if (buflen < l) {
|
||||
*errnop = ENOMEM;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
memzero(buffer, sizeof(char*));
|
||||
strcpy(buffer + sizeof(char*), translated);
|
||||
|
||||
gr->gr_name = buffer + sizeof(char*);
|
||||
gr->gr_gid = gid;
|
||||
gr->gr_passwd = (char*) "*"; /* locked */
|
||||
gr->gr_mem = (char**) buffer;
|
||||
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_SUCCESS;
|
||||
|
||||
not_found:
|
||||
*errnop = 0;
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
fail:
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
17
src/nss-systemd/nss-systemd.sym
Normal file
17
src/nss-systemd/nss-systemd.sym
Normal file
@ -0,0 +1,17 @@
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
systemd is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
***/
|
||||
|
||||
{
|
||||
global:
|
||||
_nss_systemd_getpwnam_r;
|
||||
_nss_systemd_getpwuid_r;
|
||||
_nss_systemd_getgrnam_r;
|
||||
_nss_systemd_getgrgid_r;
|
||||
local: *;
|
||||
};
|
@ -199,11 +199,12 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
|
||||
r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur);
|
||||
|
||||
} else if (STR_IN_SET(field,
|
||||
"CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
|
||||
"SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
|
||||
"IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
|
||||
"PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
|
||||
"SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute")) {
|
||||
"CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
|
||||
"SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
|
||||
"IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
|
||||
"PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
|
||||
"SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
|
||||
"RestrictRealtime", "DynamicUser")) {
|
||||
|
||||
r = parse_boolean(eq);
|
||||
if (r < 0)
|
||||
|
@ -1299,81 +1299,6 @@ static bool item_equal(Item *a, Item *b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_user_group_name(const char *u) {
|
||||
const char *i;
|
||||
long sz;
|
||||
|
||||
if (isempty(u))
|
||||
return false;
|
||||
|
||||
if (!(u[0] >= 'a' && u[0] <= 'z') &&
|
||||
!(u[0] >= 'A' && u[0] <= 'Z') &&
|
||||
u[0] != '_')
|
||||
return false;
|
||||
|
||||
for (i = u+1; *i; i++) {
|
||||
if (!(*i >= 'a' && *i <= 'z') &&
|
||||
!(*i >= 'A' && *i <= 'Z') &&
|
||||
!(*i >= '0' && *i <= '9') &&
|
||||
*i != '_' &&
|
||||
*i != '-')
|
||||
return false;
|
||||
}
|
||||
|
||||
sz = sysconf(_SC_LOGIN_NAME_MAX);
|
||||
assert_se(sz > 0);
|
||||
|
||||
if ((size_t) (i-u) > (size_t) sz)
|
||||
return false;
|
||||
|
||||
if ((size_t) (i-u) > UT_NAMESIZE - 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_gecos(const char *d) {
|
||||
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
if (!utf8_is_valid(d))
|
||||
return false;
|
||||
|
||||
if (string_has_cc(d, NULL))
|
||||
return false;
|
||||
|
||||
/* Colons are used as field separators, and hence not OK */
|
||||
if (strchr(d, ':'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_home(const char *p) {
|
||||
|
||||
if (isempty(p))
|
||||
return false;
|
||||
|
||||
if (!utf8_is_valid(p))
|
||||
return false;
|
||||
|
||||
if (string_has_cc(p, NULL))
|
||||
return false;
|
||||
|
||||
if (!path_is_absolute(p))
|
||||
return false;
|
||||
|
||||
if (!path_is_safe(p))
|
||||
return false;
|
||||
|
||||
/* Colons are used as field separators, and hence not OK */
|
||||
if (strchr(p, ':'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int parse_line(const char *fname, unsigned line, const char *buffer) {
|
||||
|
||||
static const Specifier specifier_table[] = {
|
||||
|
@ -61,6 +61,88 @@ static void test_uid_ptr(void) {
|
||||
assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
|
||||
}
|
||||
|
||||
static void test_valid_user_group_name(void) {
|
||||
assert_se(!valid_user_group_name(NULL));
|
||||
assert_se(!valid_user_group_name(""));
|
||||
assert_se(!valid_user_group_name("1"));
|
||||
assert_se(!valid_user_group_name("65535"));
|
||||
assert_se(!valid_user_group_name("-1"));
|
||||
assert_se(!valid_user_group_name("-kkk"));
|
||||
assert_se(!valid_user_group_name("rööt"));
|
||||
assert_se(!valid_user_group_name("."));
|
||||
assert_se(!valid_user_group_name("eff.eff"));
|
||||
assert_se(!valid_user_group_name("foo\nbar"));
|
||||
assert_se(!valid_user_group_name("0123456789012345678901234567890123456789"));
|
||||
assert_se(!valid_user_group_name_or_id("aaa:bbb"));
|
||||
|
||||
assert_se(valid_user_group_name("root"));
|
||||
assert_se(valid_user_group_name("lennart"));
|
||||
assert_se(valid_user_group_name("LENNART"));
|
||||
assert_se(valid_user_group_name("_kkk"));
|
||||
assert_se(valid_user_group_name("kkk-"));
|
||||
assert_se(valid_user_group_name("kk-k"));
|
||||
|
||||
assert_se(valid_user_group_name("some5"));
|
||||
assert_se(!valid_user_group_name("5some"));
|
||||
assert_se(valid_user_group_name("INNER5NUMBER"));
|
||||
}
|
||||
|
||||
static void test_valid_user_group_name_or_id(void) {
|
||||
assert_se(!valid_user_group_name_or_id(NULL));
|
||||
assert_se(!valid_user_group_name_or_id(""));
|
||||
assert_se(valid_user_group_name_or_id("0"));
|
||||
assert_se(valid_user_group_name_or_id("1"));
|
||||
assert_se(valid_user_group_name_or_id("65534"));
|
||||
assert_se(!valid_user_group_name_or_id("65535"));
|
||||
assert_se(valid_user_group_name_or_id("65536"));
|
||||
assert_se(!valid_user_group_name_or_id("-1"));
|
||||
assert_se(!valid_user_group_name_or_id("-kkk"));
|
||||
assert_se(!valid_user_group_name_or_id("rööt"));
|
||||
assert_se(!valid_user_group_name_or_id("."));
|
||||
assert_se(!valid_user_group_name_or_id("eff.eff"));
|
||||
assert_se(!valid_user_group_name_or_id("foo\nbar"));
|
||||
assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789"));
|
||||
assert_se(!valid_user_group_name_or_id("aaa:bbb"));
|
||||
|
||||
assert_se(valid_user_group_name_or_id("root"));
|
||||
assert_se(valid_user_group_name_or_id("lennart"));
|
||||
assert_se(valid_user_group_name_or_id("LENNART"));
|
||||
assert_se(valid_user_group_name_or_id("_kkk"));
|
||||
assert_se(valid_user_group_name_or_id("kkk-"));
|
||||
assert_se(valid_user_group_name_or_id("kk-k"));
|
||||
|
||||
assert_se(valid_user_group_name_or_id("some5"));
|
||||
assert_se(!valid_user_group_name_or_id("5some"));
|
||||
assert_se(valid_user_group_name_or_id("INNER5NUMBER"));
|
||||
}
|
||||
|
||||
static void test_valid_gecos(void) {
|
||||
|
||||
assert_se(!valid_gecos(NULL));
|
||||
assert_se(valid_gecos(""));
|
||||
assert_se(valid_gecos("test"));
|
||||
assert_se(valid_gecos("Ümläüt"));
|
||||
assert_se(!valid_gecos("In\nvalid"));
|
||||
assert_se(!valid_gecos("In:valid"));
|
||||
}
|
||||
|
||||
static void test_valid_home(void) {
|
||||
|
||||
assert_se(!valid_home(NULL));
|
||||
assert_se(!valid_home(""));
|
||||
assert_se(!valid_home("."));
|
||||
assert_se(!valid_home("/home/.."));
|
||||
assert_se(!valid_home("/home/../"));
|
||||
assert_se(!valid_home("/home\n/foo"));
|
||||
assert_se(!valid_home("./piep"));
|
||||
assert_se(!valid_home("piep"));
|
||||
assert_se(!valid_home("/home/user:lennart"));
|
||||
|
||||
assert_se(valid_home("/"));
|
||||
assert_se(valid_home("/home"));
|
||||
assert_se(valid_home("/home/foo"));
|
||||
}
|
||||
|
||||
int main(int argc, char*argv[]) {
|
||||
|
||||
test_uid_to_name_one(0, "root");
|
||||
@ -75,5 +157,10 @@ int main(int argc, char*argv[]) {
|
||||
test_parse_uid();
|
||||
test_uid_ptr();
|
||||
|
||||
test_valid_user_group_name();
|
||||
test_valid_user_group_name_or_id();
|
||||
test_valid_gecos();
|
||||
test_valid_home();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user