1
0
mirror of https://github.com/systemd/systemd synced 2026-03-14 17:14:49 +01:00

Compare commits

..

14 Commits

Author SHA1 Message Date
Lennart Poettering
a346a34f7f
Merge pull request #19548 from poettering/userdb-dropin
userdb: add support for loading user/group records from JSON drop-ins
2021-05-10 17:53:45 +02:00
Lennart Poettering
f2147ed5ea docs: link info about static user/group drop-in files from the relevant specs 2021-05-10 14:59:26 +02:00
Lennart Poettering
62a90b48d0 man: document new userdbd features 2021-05-10 14:59:05 +02:00
Lennart Poettering
71b5738030 man: update nss-systemd documentation with new features 2021-05-10 14:58:44 +02:00
Lennart Poettering
8fbb1941f1 userdbd: also listen on a varlink socket io.systemd.DropIn
Let's explicitly support looking things up via dropin as a varlink
service.
2021-05-10 14:58:39 +02:00
Lennart Poettering
85f088abe8 userdb: optionally read user/group/membership "dropins", too 2021-05-10 14:58:07 +02:00
Zbigniew Jędrzejewski-Szmek
2d0b71b6f6
Merge pull request #19542 from yuwata/unit-after-socket
network, timesync, resolve: check bus is ready before emitting property change or signal
2021-05-10 14:44:15 +02:00
Zbigniew Jędrzejewski-Szmek
8808d3289e
Merge pull request #19556 from lucasrangit/network-wifi-interface-type-typos
network: update documentation and examples to use correct interface type and lookup command
2021-05-10 13:55:07 +02:00
Lucas Magasweran
2480ca95ba man: network: use networkctl list instead of status to list network interface type
To determine the network interface type for use in the `Type=` directive, it is more concise to use the `list` command. Whereas, the `status` command requires an interface parameter.

For example, on a RaspberryPi 4 the following shows that the `wlan0` interface type `wlan` is more coveniently listed by the `list` command.

```
root@raspberrypi4-64:~# networkctl list
IDX LINK  TYPE     OPERATIONAL SETUP
  1 lo    loopback carrier     unmanaged
  2 eth0  ether    routable    configured
  3 wlan0 wlan     off         unmanaged

3 links listed.
```

Whereas the `networkctl status` command doesn't include this information.

```
root@raspberrypi4-64:~# networkctl status
●   State: routable
  Address: 192.168.1.141 on eth0
           fd8b:8779:b7a4::f43 on eth0
           fd8b:8779:b7a4:0:dea6:32ff:febe:d1ce on eth0
           fe80::dea6:32ff:febe:d1ce on eth0
  Gateway: 192.168.1.1 (CZ.NIC, z.s.p.o.) on eth0
      DNS: 192.168.1.1

May 07 14:17:18 raspberrypi4-64 systemd-networkd[212]: eth0: Gained carrier
May 07 14:17:19 raspberrypi4-64 systemd-networkd[212]: eth0: Gained IPv6LL
May 07 14:17:19 raspberrypi4-64 systemd-networkd[212]: eth0: DHCPv6 address fd8b:8779:b7a4::f43/128 timeout preferred -1 valid -1
May 07 14:17:21 raspberrypi4-64 systemd-networkd[212]: eth0: DHCPv4 address 192.168.1.141/24 via 192.168.1.1
```

To get the interface type using the `status` command you need to specify an additional argument.

```
root@raspberrypi4-64:~# networkctl status wlan0
● 3: wlan0
                     Link File: /lib/systemd/network/99-default.link
                  Network File: n/a
                          Type: wlan
                         State: off (unmanaged)
                          Path: platform-fe300000.mmcnr
                        Driver: brcmfmac
                    HW Address: dc:a6:32:be:d1:cf (Raspberry Pi Trading Ltd)
                           MTU: 1500 (min: 68, max: 1500)
                         QDisc: noop
  IPv6 Address Generation Mode: eui64
          Queue Length (Tx/Rx): 1/1
```
2021-05-10 13:40:33 +02:00
Lucas Magasweran
b419e8776b network: examples: use wlan for Type instead of wifi 2021-05-10 11:28:52 +02:00
Yu Watanabe
b8d6689a7f resolve: check that bus is ready before emitting signal or property change 2021-05-08 15:12:31 +09:00
Yu Watanabe
933e95d716 timesync: check that bus is ready before emitting property change 2021-05-08 15:12:31 +09:00
Yu Watanabe
706875f165 network: check that bus is ready at one more place 2021-05-08 15:12:31 +09:00
Yu Watanabe
098d42b67e local-addresses: wrap long comment
Follow-up for 54e6f97bc9931679aa9b895546621b15e0f464a4.
2021-05-08 15:12:19 +09:00
24 changed files with 729 additions and 100 deletions

View File

@ -19,6 +19,12 @@ expose. Or in other words, it both allows applications to efficiently query
user/group records from local services, and allows local subsystems to provide
user/group records efficiently to local applications.
The concepts described here define an IPC interface. Alternatively, user/group
records may be dropped in number of drop-in directories as files where they are
picked up in addition to the users/groups defined by this IPC logic. See
[`nss-systemd(8)`](https://www.freedesktop.org/software/systemd/man/nss-systemd.html)
for details.
This simple API only exposes only three method calls, and requires only a small
subset of the Varlink functionality.

View File

@ -75,7 +75,11 @@ Records](https://systemd.io/GROUP_RECORD) that encapsulate UNIX groups.
JSON User Records may be transferred or written to disk in various protocols
and formats. To inquire about such records defined on the local system use the
[User/Group Lookup API via Varlink](https://systemd.io/USER_GROUP_API).
[User/Group Lookup API via
Varlink](https://systemd.io/USER_GROUP_API). User/group records may also be
dropped in number of drop-in directories as files. See
[`nss-systemd(8)`](https://www.freedesktop.org/software/systemd/man/nss-systemd.html)
for details.
## Why JSON?

View File

@ -56,6 +56,49 @@
<filename>/etc/gshadow</filename> based mappings take precedence.</para>
</refsect1>
<refsect1>
<title>Static Drop-In JSON User/Group Records</title>
<para>Besides user/group records acquired via the aforementioned Varlink IPC interfaces and the
synthesized root and nobody accounts, this module also makes user and group accounts available to the
system that are defined in static drop-in files in the <filename>/etc/userdb/</filename>,
<filename>/run/userdb/</filename>, <filename>/run/host/userdb/</filename> and
<filename>/usr/lib/userdb/</filename> directories.</para>
<para>This is a simple mechanism to provide static user and group records via JSON drop-in files. Such
user records should be defined in the format described by the <ulink
url="https://systemd.io/USER_RECORD">JSON User Record</ulink> specification and be placed in one of the
aforementioned directories under a file name composed of the user name suffixed with
<filename>.user</filename>, with a world-readable access mode. A symlink named after the user record's
UID formatted in decimal and suffixed with <filename>.user</filename> pointing to the primary record file
should be created as well, in order to allow both lookups by username and by UID. Privileged user record
data (e.g. hashed UNIX passwords) may optionally be provided as well, in a pair of separate companion
files with the <filename>.user-privileged</filename> suffix. The data should be stored in a regular file
named after the user name, suffixed with <filename>.user-privileged</filename>, and a symlink pointing to
it, named after the used numeric UID formatted in decimal with the same suffix. These companion files
should not be readable to anyone but root. Example:</para>
<programlisting>-rw-r--r--. 1 root root 723 May 10 foobar.user
-rw-------. 1 root root 123 May 10 foobar.user-privileged
lrwxrwxrwx. 1 root root 19 May 10 4711.user -> foobar.user
lrwxrwxrwx. 1 root root 19 May 10 4711.user-privileged -> foobar.user-privileged</programlisting>
<para>Similarly, group records following the format described in <ulink
url="https://systemd.io/GROUP_RECORD">JSON Group Record</ulink> may be defined, using the file suffixes
<filename>.group</filename> and <filename>.group-privileged</filename>.</para>
<para>The primary user/group record files (i.e. those with the <filename>.user</filename> and
<filename>.group</filename> suffixes) should not contain the <literal>privileged</literal> section as
described in the specifications. The privileged user/group record files (i.e. those with the
<filename>.user-privileged</filename> and <filename>.group-privileged</filename> suffixes) should
contain this section, exclusively.</para>
<para>Note that static user/group records generally do not override conflicting records in
<filename>/etc/passwd</filename> or <filename>/etc/group</filename> or other account databases. In fact,
before dropping in these files a reasonable level of care should be taken to avoid user/group name and
UID/GID conflicts.</para>
</refsect1>
<refsect1>
<title>Configuration in <filename>/etc/nsswitch.conf</filename></title>

View File

@ -32,7 +32,9 @@
<para><command>systemd-userdbd</command> is a system service that multiplexes user/group lookups to all
local services that provide JSON user/group record definitions to the system. In addition it synthesizes
JSON user/group records from classic UNIX/glibc NSS user/group records in order to provide full backwards
compatibility.</para>
compatibility. It may also pick up statically defined JSON user/group records from drop-in files in
<filename>/etc/userdb/</filename>, <filename>/run/userdb/</filename>,
<filename>/run/host/userdb/</filename> and <filename>/use/lib/userdb/</filename>.</para>
<para>Most of <command>systemd-userdbd</command>'s functionality is accessible through the
<citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
@ -45,16 +47,18 @@
multiplexes access other services implementing this API, too. It is thus both server and client of this
API.</para>
<para>This service provides two distinct <ulink url="https://varlink.org/">Varlink</ulink> services:
<para>This service provides three distinct <ulink url="https://varlink.org/">Varlink</ulink> services:
<constant>io.systemd.Multiplexer</constant> provides a single, unified API for querying JSON user and
group records. Internally it talks to all other user/group record services running on the system in
parallel and forwards any information discovered. This simplifies clients substantially since they need
to talk to a single service only instead of all of them in
parallel. <constant>io.systemd.NameServiceSwitch</constant> provides compatibility with classic UNIX/glibc
NSS user records, i.e. converts <type>struct passwd</type> and <type>struct group</type> records as
acquired with APIs such as <citerefentry
project='man-pages'><refentrytitle>getpwnam</refentrytitle><manvolnum>1</manvolnum></citerefentry> to JSON
user/group records, thus hiding the differences between the services as much as possible.</para>
parallel. <constant>io.systemd.NameServiceSwitch</constant> provides compatibility with classic
UNIX/glibc NSS user records, i.e. converts <type>struct passwd</type> and <type>struct group</type>
records as acquired with APIs such as <citerefentry
project='man-pages'><refentrytitle>getpwnam</refentrytitle><manvolnum>1</manvolnum></citerefentry> to
JSON user/group records, thus hiding the differences between the services as much as
possible. <constant>io.systemd.Dropin</constant> makes JSON user/group records from the aforementioned
drop-in directories available.</para>
</refsect1>
<refsect1>

View File

@ -133,7 +133,10 @@
<term><varname>Type=</varname></term>
<listitem>
<para>A whitespace-separated list of shell-style globs matching the device type, as exposed by
<command>networkctl status</command>. If the list is prefixed with a "!", the test is inverted.
<command>networkctl list</command>. If the list is prefixed with a "!", the test is inverted.
Some valid values are <literal>ether</literal>, <literal>loopback</literal>, <literal>wlan</literal>, <literal>wwan</literal>.
Valid types are named either from the udev <literal>DEVTYPE</literal> attribute, or
<literal>ARPHRD_</literal> macros in <filename>linux/if_arp.h</filename>, so this is not comprehensive.
</para>
</listitem>
</varlistentry>

View File

@ -1,5 +1,5 @@
[Match]
Type=wifi
Type=wlan
WLANInterfaceType=ad-hoc
[Network]

View File

@ -1,5 +1,5 @@
[Match]
Type=wifi
Type=wlan
WLANInterfaceType=ap
[Network]

View File

@ -1,5 +1,5 @@
[Match]
Type=wifi
Type=wlan
WLANInterfaceType=station
[Network]

View File

@ -81,6 +81,9 @@ static int dhcp_server_emit_changed(Link *link, const char *property, ...) {
assert(link);
if (sd_bus_is_ready(link->manager->bus) <= 0)
return 0;
path = link_bus_path(link);
if (!path)
return log_oom();

View File

@ -2272,6 +2272,9 @@ int manager_connect_bus(Manager *m) {
int _manager_send_changed(Manager *manager, const char *property, ...) {
assert(manager);
if (sd_bus_is_ready(manager->bus) <= 0)
return 0;
char **l = strv_from_stdarg_alloca(property);
int r = sd_bus_emit_properties_changed_strv(

View File

@ -500,7 +500,7 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
/* Withdraw the conflict item */
i->state = DNS_ZONE_ITEM_WITHDRAWN;
dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key));
(void) dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key));
/* Maybe change the hostname */
if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)

View File

@ -329,10 +329,13 @@ int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t
return 0;
}
void dnssd_signal_conflict(Manager *manager, const char *name) {
int dnssd_signal_conflict(Manager *manager, const char *name) {
DnssdService *s;
int r;
if (sd_bus_is_ready(manager->bus) <= 0)
return 0;
HASHMAP_FOREACH(s, manager->dnssd_services) {
if (s->withdrawn)
continue;
@ -343,22 +346,20 @@ void dnssd_signal_conflict(Manager *manager, const char *name) {
s->withdrawn = true;
r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->name, &path);
if (r < 0) {
log_error_errno(r, "Can't get D-BUS object path: %m");
return;
}
if (r < 0)
return log_error_errno(r, "Can't get D-BUS object path: %m");
r = sd_bus_emit_signal(manager->bus,
path,
"org.freedesktop.resolve1.DnssdService",
"Conflicted",
NULL);
if (r < 0) {
log_error_errno(r, "Cannot emit signal: %m");
return;
}
if (r < 0)
return log_error_errno(r, "Cannot emit signal: %m");
break;
}
}
return 0;
}

View File

@ -58,4 +58,4 @@ int dnssd_load(Manager *manager);
int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtItem **ret_item);
int dnssd_txt_item_new_from_data(const char *key, const void *value, const size_t size, DnsTxtItem **ret_item);
int dnssd_update_rrs(DnssdService *s);
void dnssd_signal_conflict(Manager *manager, const char *name);
int dnssd_signal_conflict(Manager *manager, const char *name);

View File

@ -395,7 +395,8 @@ int local_outbounds(
sa.in = (struct sockaddr_in) {
.sin_family = AF_INET,
.sin_addr = gateways[i].address.in,
.sin_port = htobe16(53), /* doesn't really matter which port we pick — we just care about the routing decision */
.sin_port = htobe16(53), /* doesn't really matter which port we pick —
* we just care about the routing decision */
};
break;

View File

@ -262,6 +262,8 @@ shared_sources = files('''
user-record.h
userdb.c
userdb.h
userdb-dropin.c
userdb-dropin.h
utmp-wtmp.h
varlink.c
varlink.h

302
src/shared/userdb-dropin.c Normal file
View File

@ -0,0 +1,302 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "path-util.h"
#include "stdio-util.h"
#include "user-util.h"
#include "userdb-dropin.h"
static int load_user(
FILE *f,
const char *path,
const char *name,
uid_t uid,
UserDBFlags flags,
UserRecord **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *u = NULL;
bool have_privileged;
int r;
assert(f);
r = json_parse_file(f, path, 0, &v, NULL, NULL);
if (r < 0)
return r;
if (FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW) || !path || !(name || uid_is_valid(uid)))
have_privileged = false;
else {
_cleanup_(json_variant_unrefp) JsonVariant *privileged_v = NULL;
_cleanup_free_ char *d = NULL, *j = NULL;
/* Let's load the "privileged" section from a companion file. But only if USERDB_AVOID_SHADOW
* is not set. After all, the privileged section kinda takes the role of the data from the
* shadow file, hence it makes sense to use the same flag here.
*
* The general assumption is that whoever provides these records makes the .user file
* world-readable, but the .privilege file readable to root and the assigned UID only. But we
* won't verify that here, as it would be too late. */
r = path_extract_directory(path, &d);
if (r < 0)
return r;
if (name) {
j = strjoin(d, "/", name, ".user-privileged");
if (!j)
return -ENOMEM;
} else {
assert(uid_is_valid(uid));
if (asprintf(&j, "%s/" UID_FMT ".user-privileged", d, uid) < 0)
return -ENOMEM;
}
r = json_parse_file(NULL, j, JSON_PARSE_SENSITIVE, &privileged_v, NULL, NULL);
if (ERRNO_IS_PRIVILEGE(r))
have_privileged = false;
else if (r == -ENOENT)
have_privileged = true; /* if the privileged file doesn't exist, we are complete */
else if (r < 0)
return r;
else {
r = json_variant_merge(&v, privileged_v);
if (r < 0)
return r;
have_privileged = true;
}
}
u = user_record_new();
if (!u)
return -ENOMEM;
r = user_record_load(
u, v,
USER_RECORD_REQUIRE_REGULAR|
USER_RECORD_ALLOW_PER_MACHINE|
USER_RECORD_ALLOW_BINDING|
USER_RECORD_ALLOW_SIGNATURE|
(have_privileged ? USER_RECORD_ALLOW_PRIVILEGED : 0));
if (r < 0)
return r;
if (name && !streq_ptr(name, u->user_name))
return -EINVAL;
if (uid_is_valid(uid) && uid != u->uid)
return -EINVAL;
u->incomplete = !have_privileged;
if (ret)
*ret = TAKE_PTR(u);
return 0;
}
int dropin_user_record_by_name(const char *name, const char *path, UserDBFlags flags, UserRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(name);
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno; /* We generally want ESRCH to indicate no such user */
} else {
const char *j;
j = strjoina(name, ".user");
if (!filename_is_valid(j)) /* Doesn't qualify as valid filename? Then it's definitely not provided as a drop-in */
return -ESRCH;
r = search_and_fopen_nulstr(j, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_user(f, path, name, UID_INVALID, flags, ret);
}
int dropin_user_record_by_uid(uid_t uid, const char *path, UserDBFlags flags, UserRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(uid_is_valid(uid));
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno;
} else {
char buf[DECIMAL_STR_MAX(uid_t) + STRLEN(".user") + 1];
xsprintf(buf, UID_FMT ".user", uid);
/* Note that we don't bother to validate this as a filename, as this is generated from a decimal
* integer, i.e. is definitely OK as a filename */
r = search_and_fopen_nulstr(buf, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_user(f, path, NULL, uid, flags, ret);
}
static int load_group(
FILE *f,
const char *path,
const char *name,
gid_t gid,
UserDBFlags flags,
GroupRecord **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
bool have_privileged;
int r;
assert(f);
r = json_parse_file(f, path, 0, &v, NULL, NULL);
if (r < 0)
return r;
if (FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW) || !path || !(name || gid_is_valid(gid)))
have_privileged = false;
else {
_cleanup_(json_variant_unrefp) JsonVariant *privileged_v = NULL;
_cleanup_free_ char *d = NULL, *j = NULL;
r = path_extract_directory(path, &d);
if (r < 0)
return r;
if (name) {
j = strjoin(d, "/", name, ".group-privileged");
if (!j)
return -ENOMEM;
} else {
assert(gid_is_valid(gid));
if (asprintf(&j, "%s/" GID_FMT ".group-privileged", d, gid) < 0)
return -ENOMEM;
}
r = json_parse_file(NULL, j, JSON_PARSE_SENSITIVE, &privileged_v, NULL, NULL);
if (ERRNO_IS_PRIVILEGE(r))
have_privileged = false;
else if (r == -ENOENT)
have_privileged = true; /* if the privileged file doesn't exist, we are complete */
else if (r < 0)
return r;
else {
r = json_variant_merge(&v, privileged_v);
if (r < 0)
return r;
have_privileged = true;
}
}
g = group_record_new();
if (!g)
return -ENOMEM;
r = group_record_load(
g, v,
USER_RECORD_REQUIRE_REGULAR|
USER_RECORD_ALLOW_PER_MACHINE|
USER_RECORD_ALLOW_BINDING|
USER_RECORD_ALLOW_SIGNATURE|
(have_privileged ? USER_RECORD_ALLOW_PRIVILEGED : 0));
if (r < 0)
return r;
if (name && !streq_ptr(name, g->group_name))
return -EINVAL;
if (gid_is_valid(gid) && gid != g->gid)
return -EINVAL;
g->incomplete = !have_privileged;
if (ret)
*ret = TAKE_PTR(g);
return 0;
}
int dropin_group_record_by_name(const char *name, const char *path, UserDBFlags flags, GroupRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(name);
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno;
} else {
const char *j;
j = strjoina(name, ".group");
if (!filename_is_valid(j)) /* Doesn't qualify as valid filename? Then it's definitely not provided as a drop-in */
return -ESRCH;
r = search_and_fopen_nulstr(j, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_group(f, path, name, GID_INVALID, flags, ret);
}
int dropin_group_record_by_gid(gid_t gid, const char *path, UserDBFlags flags, GroupRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(gid_is_valid(gid));
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno;
} else {
char buf[DECIMAL_STR_MAX(gid_t) + STRLEN(".group") + 1];
xsprintf(buf, GID_FMT ".group", gid);
r = search_and_fopen_nulstr(buf, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_group(f, path, NULL, gid, flags, ret);
}

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "def.h"
#include "group-record.h"
#include "user-record.h"
#include "userdb.h"
/* This could be put together with CONF_PATHS_NULSTR, with the exception of the /run/host/ part in the
* middle, which we use here, but not otherwise. */
#define USERDB_DROPIN_DIR_NULSTR(n) \
"/etc/" n "\0" \
"/run/" n "\0" \
"/run/host/" n "\0" \
"/usr/local/lib/" n "\0" \
"/usr/lib/" n "\0" \
_CONF_PATHS_SPLIT_USR_NULSTR(n)
int dropin_user_record_by_name(const char *name, const char *path, UserDBFlags flags, UserRecord **ret);
int dropin_user_record_by_uid(uid_t uid, const char *path, UserDBFlags flags, UserRecord **ret);
int dropin_group_record_by_name(const char *name, const char *path, UserDBFlags flags, GroupRecord **ret);
int dropin_group_record_by_gid(gid_t gid, const char *path, UserDBFlags flags, GroupRecord **ret);

View File

@ -2,10 +2,12 @@
#include <sys/auxv.h>
#include "conf-files.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "missing_syscall.h"
#include "parse-util.h"
#include "set.h"
@ -13,6 +15,7 @@
#include "strv.h"
#include "user-record-nss.h"
#include "user-util.h"
#include "userdb-dropin.h"
#include "userdb.h"
#include "varlink.h"
@ -31,9 +34,12 @@ struct UserDBIterator {
Set *links;
bool nss_covered:1;
bool nss_iterating:1;
bool dropin_covered:1;
bool synthesize_root:1;
bool synthesize_nobody:1;
bool nss_systemd_blocked:1;
char **dropins;
size_t current_dropin;
int error;
unsigned n_found;
sd_event *event;
@ -43,7 +49,7 @@ struct UserDBIterator {
char *found_user_name, *found_group_name; /* when .what == LOOKUP_MEMBERSHIP */
char **members_of_group;
size_t index_members_of_group;
char *filter_user_name;
char *filter_user_name, *filter_group_name;
};
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
@ -51,6 +57,7 @@ UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
return NULL;
set_free(iterator->links);
strv_free(iterator->dropins);
switch (iterator->what) {
@ -75,6 +82,7 @@ UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
free(iterator->found_group_name);
strv_free(iterator->members_of_group);
free(iterator->filter_user_name);
free(iterator->filter_group_name);
if (iterator->nss_iterating)
endgrent();
@ -425,7 +433,7 @@ static int userdb_start_query(
}
/* First, let's talk to the multiplexer, if we can */
if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_DONT_SYNTHESIZE)) == 0 &&
if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE)) == 0 &&
!strv_contains(except, "io.systemd.Multiplexer") &&
(!only || strv_contains(only, "io.systemd.Multiplexer"))) {
_cleanup_(json_variant_unrefp) JsonVariant *patched_query = json_variant_ref(query);
@ -437,6 +445,7 @@ static int userdb_start_query(
r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, patched_query);
if (r >= 0) {
iterator->nss_covered = true; /* The multiplexer does NSS */
iterator->dropin_covered = true; /* It also handles drop-in stuff */
return 0;
}
}
@ -452,7 +461,7 @@ static int userdb_start_query(
FOREACH_DIRENT(de, d, return -errno) {
_cleanup_(json_variant_unrefp) JsonVariant *patched_query = NULL;
_cleanup_free_ char *p = NULL;
bool is_nss;
bool is_nss, is_dropin;
if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
continue;
@ -469,6 +478,11 @@ static int userdb_start_query(
if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
continue;
/* Similar for the drop-in service */
is_dropin = streq(de->d_name, "io.systemd.DropIn");
if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
continue;
if (strv_contains(except, de->d_name))
continue;
@ -485,9 +499,11 @@ static int userdb_start_query(
return log_debug_errno(r, "Unable to set service JSON field: %m");
r = userdb_connect(iterator, p, method, more, patched_query);
if (is_nss && r >= 0) /* Turn off fallback NSS if we found the NSS service and could connect
* to it */
if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
* and could connect to it */
iterator->nss_covered = true;
if (is_dropin && r >= 0)
iterator->dropin_covered = true;
if (ret == 0 && r < 0)
ret = r;
@ -624,6 +640,12 @@ int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
r = dropin_user_record_by_name(name, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
/* Make sure the NSS lookup doesn't recurse back to us. */
@ -671,6 +693,12 @@ int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
r = dropin_user_record_by_uid(uid, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
@ -694,7 +722,7 @@ int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r;
int r, qr;
assert(ret);
@ -704,17 +732,33 @@ int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
iterator->synthesize_root = iterator->synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE);
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (r < 0 || !iterator->nss_covered)) {
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setpwent();
iterator->nss_iterating = true;
} else if (r < 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
r = conf_files_list_nulstr(
&iterator->dropins,
".user",
NULL,
CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
USERDB_DROPIN_DIR_NULSTR("userdb"));
if (r < 0)
log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
}
/* propagate IPC error, but only if there are no drop-ins */
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
*ret = TAKE_PTR(iterator);
return 0;
@ -772,9 +816,45 @@ int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
endpwent();
}
r = userdb_process(iterator, ret, NULL, NULL, NULL);
for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
const char *i = iterator->dropins[iterator->current_dropin];
_cleanup_free_ char *fn = NULL;
uid_t uid;
char *e;
/* Next, let's add in the static drop-ins, which are quick to retrieve */
r = path_extract_filename(i, &fn);
if (r < 0)
return r;
e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
if (!e)
continue;
*e = 0; /* Chop off suffix */
if (parse_uid(fn, &uid) < 0) /* not a UID .user file? Then skip to next */
continue;
r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
if (r < 0) {
log_debug_errno(r, "Failed to parse user record for UID " UID_FMT ", ignoring: %m", uid);
continue; /* If we failed to parse this record, let's suppress it from enumeration,
* and continue with the next record. Maybe someone is dropping it files
* and only partially wrote this one. */
}
iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
iterator->n_found++;
return 0;
}
/* Then, let's return the users provided by varlink IPC */
r = userdb_process(iterator, ret, NULL, NULL, NULL);
if (r < 0) {
/* Finally, synthesize root + nobody if not done yet */
if (iterator->synthesize_root) {
iterator->synthesize_root = false;
iterator->n_found++;
@ -835,6 +915,13 @@ int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
r = dropin_group_record_by_name(name, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
@ -879,6 +966,12 @@ int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
r = dropin_group_record_by_gid(gid, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
@ -901,7 +994,7 @@ int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r;
int r, qr;
assert(ret);
@ -911,17 +1004,32 @@ int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
iterator->synthesize_root = iterator->synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE);
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (r < 0 || !iterator->nss_covered)) {
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setgrent();
iterator->nss_iterating = true;
} else if (r < 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
r = conf_files_list_nulstr(
&iterator->dropins,
".group",
NULL,
CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
USERDB_DROPIN_DIR_NULSTR("userdb"));
if (r < 0)
log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
}
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
*ret = TAKE_PTR(iterator);
return 0;
@ -977,6 +1085,36 @@ int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
endgrent();
}
for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
const char *i = iterator->dropins[iterator->current_dropin];
_cleanup_free_ char *fn = NULL;
gid_t gid;
char *e;
r = path_extract_filename(i, &fn);
if (r < 0)
return r;
e = endswith(fn, ".group");
if (!e)
continue;
*e = 0; /* Chop off suffix */
if (parse_gid(fn, &gid) < 0)
continue;
r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
if (r < 0) {
log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
continue;
}
iterator->current_dropin++;
iterator->n_found++;
return 0;
}
r = userdb_process(iterator, NULL, ret, NULL, NULL);
if (r < 0) {
if (iterator->synthesize_root) {
@ -999,10 +1137,23 @@ int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
return r;
}
static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
int r;
r = conf_files_list_nulstr(
&i->dropins,
".membership",
NULL,
CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
USERDB_DROPIN_DIR_NULSTR("userdb"));
if (r < 0)
log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
}
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
int r;
int r, qr;
assert(ret);
@ -1018,34 +1169,37 @@ int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **r
if (!iterator)
return -ENOMEM;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
if ((r >= 0 && iterator->nss_covered) || FLAGS_SET(flags, USERDB_EXCLUDE_NSS))
goto finish;
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
iterator->filter_user_name = strdup(name);
if (!iterator->filter_user_name)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setgrent();
iterator->nss_iterating = true;
}
r = 0;
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
discover_membership_dropins(iterator, flags);
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
finish:
if (r >= 0)
*ret = TAKE_PTR(iterator);
return r;
return 0;
}
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
int r;
int r, qr;
assert(ret);
@ -1061,9 +1215,14 @@ int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **
if (!iterator)
return -ENOMEM;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
if ((r >= 0 && iterator->nss_covered) || FLAGS_SET(flags, USERDB_EXCLUDE_NSS))
goto finish;
iterator->filter_group_name = strdup(name);
if (!iterator->filter_group_name)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
@ -1082,19 +1241,23 @@ int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **
if (!iterator->found_group_name)
return -ENOMEM;
}
}
r = 0;
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
discover_membership_dropins(iterator, flags);
if (qr < 0 &&
strv_isempty(iterator->members_of_group) &&
strv_isempty(iterator->dropins))
return qr;
finish:
if (r >= 0)
*ret = TAKE_PTR(iterator);
return r;
return 0;
}
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r;
int r, qr;
assert(ret);
@ -1102,24 +1265,27 @@ int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
if (!iterator)
return -ENOMEM;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
if ((r >= 0 && iterator->nss_covered) || FLAGS_SET(flags, USERDB_EXCLUDE_NSS))
goto finish;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setgrent();
iterator->nss_iterating = true;
}
r = 0;
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
discover_membership_dropins(iterator, flags);
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
finish:
if (r >= 0)
*ret = TAKE_PTR(iterator);
return r;
return 0;
}
int membershipdb_iterator_get(
@ -1205,6 +1371,48 @@ int membershipdb_iterator_get(
iterator->found_group_name = mfree(iterator->found_group_name);
}
for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
_cleanup_free_ char *un = NULL, *gn = NULL;
e = endswith(i, ".membership");
if (!e)
continue;
c = memchr(i, ':', e - i);
if (!c)
continue;
un = strndup(i, c - i);
if (!un)
return -ENOMEM;
if (iterator->filter_user_name) {
if (!streq(un, iterator->filter_user_name))
continue;
} else if (!valid_user_group_name(un, VALID_USER_RELAX))
continue;
c++; /* skip over ':' */
gn = strndup(c, e - c);
if (!gn)
return -ENOMEM;
if (iterator->filter_group_name) {
if (!streq(gn, iterator->filter_group_name))
continue;
} else if (!valid_user_group_name(gn, VALID_USER_RELAX))
continue;
iterator->current_dropin++;
iterator->n_found++;
if (ret_user)
*ret_user = TAKE_PTR(un);
if (ret_group)
*ret_group = TAKE_PTR(gn);
return 0;
}
r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
if (r < 0 && iterator->n_found > 0)
return -ESRCH;

View File

@ -18,6 +18,7 @@ typedef enum UserDBFlags {
/* The main sources */
USERDB_EXCLUDE_NSS = 1 << 0, /* don't do client-side nor server-side NSS */
USERDB_EXCLUDE_VARLINK = 1 << 1, /* don't talk to any varlink services */
USERDB_EXCLUDE_DROPIN = 1 << 2, /* don't load drop-in user/group definitions */
/* Modifications */
USERDB_SUPPRESS_SHADOW = 1 << 3, /* don't do client-side shadow calls (server side might happen though) */
@ -26,9 +27,18 @@ typedef enum UserDBFlags {
USERDB_DONT_SYNTHESIZE = 1 << 6, /* don't synthesize root/nobody */
/* Combinations */
USERDB_NSS_ONLY = USERDB_EXCLUDE_VARLINK|USERDB_DONT_SYNTHESIZE,
USERDB_NSS_ONLY = USERDB_EXCLUDE_VARLINK|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE,
USERDB_DROPIN_ONLY = USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_VARLINK|USERDB_DONT_SYNTHESIZE,
} UserDBFlags;
/* Well-known errors we'll return here:
*
* -ESRCH: No such user/group
* -ELINK: Varlink logic turned off (and no other source available)
* -EOPNOTSUPP: Enumeration not supported
* -ETIMEDOUT: Time-out
*/
int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret);
int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret);
int userdb_all(UserDBFlags flags, UserDBIterator **ret);

View File

@ -607,7 +607,13 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re
m->poll_interval_usec / USEC_PER_SEC, offset, delay, m->samples_jitter, m->drift_freq / 65536,
spike ? " (ignored)" : "");
(void) sd_bus_emit_properties_changed(m->bus, "/org/freedesktop/timesync1", "org.freedesktop.timesync1.Manager", "NTPMessage", NULL);
if (sd_bus_is_ready(m->bus) > 0)
(void) sd_bus_emit_properties_changed(
m->bus,
"/org/freedesktop/timesync1",
"org.freedesktop.timesync1.Manager",
"NTPMessage",
NULL);
if (!m->good) {
_cleanup_free_ char *pretty = NULL;

View File

@ -289,6 +289,11 @@ int manager_startup(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to bind io.systemd.Multiplexer: %m");
r = symlink_idempotent("io.systemd.Multiplexer",
"/run/systemd/userdb/io.systemd.DropIn", false);
if (r < 0)
return log_error_errno(r, "Failed to bind io.systemd.Multiplexer: %m");
if (listen(m->listen_fd, SOMAXCONN) < 0)
return log_error_errno(errno, "Failed to listen on socket: %m");
}

View File

@ -17,6 +17,9 @@
* io.systemd.Multiplexer: this multiplexes lookup requests to all Varlink services that have a
* socket in /run/systemd/userdb/. It's supposed to simplify clients that don't want to implement
* the full iterative logic on their own.
*
* io.systemd.DropIn: this makes JSON user/group records dropped into /run/userdb/ available as
* regular users.
*/
static int run(int argc, char *argv[]) {
@ -31,8 +34,8 @@ static int run(int argc, char *argv[]) {
if (argc != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.NameServiceSwitch:io.systemd.Multiplexer", 1) < 0)
return log_error_errno(errno, "Failed to se $SYSTEMD_BYPASS_USERDB: %m");
if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.NameServiceSwitch:io.systemd.Multiplexer:io.systemd.DropIn", 1) < 0)
return log_error_errno(errno, "Failed to set $SYSTEMD_BYPASS_USERDB: %m");
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGUSR2, -1) >= 0);

View File

@ -120,6 +120,8 @@ static int userdb_flags_from_service(Varlink *link, const char *service, UserDBF
if (streq_ptr(service, "io.systemd.NameServiceSwitch"))
*ret = USERDB_NSS_ONLY|USERDB_AVOID_MULTIPLEXER;
if (streq_ptr(service, "io.systemd.DropIn"))
*ret = USERDB_DROPIN_ONLY|USERDB_AVOID_MULTIPLEXER;
else if (streq_ptr(service, "io.systemd.Multiplexer"))
*ret = USERDB_AVOID_MULTIPLEXER;
else

View File

@ -15,7 +15,7 @@ Before=sockets.target
[Socket]
ListenStream=/run/systemd/userdb/io.systemd.Multiplexer
Symlinks=/run/systemd/userdb/io.systemd.NameServiceSwitch
Symlinks=/run/systemd/userdb/io.systemd.NameServiceSwitch /run/systemd/userdb/io.systemd.DropIn
SocketMode=0666
[Install]