]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
userdb: optionally read user/group/membership "dropins", too
authorLennart Poettering <lennart@poettering.net>
Fri, 30 Apr 2021 21:10:59 +0000 (23:10 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 10 May 2021 12:58:07 +0000 (14:58 +0200)
src/shared/meson.build
src/shared/userdb-dropin.c [new file with mode: 0644]
src/shared/userdb-dropin.h [new file with mode: 0644]
src/shared/userdb.c
src/shared/userdb.h

index 4b590be1e03e5975645d51cbf0595941de7bdd60..2e7132c40261b2c92f292e71624f1a64ab0a81eb 100644 (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
diff --git a/src/shared/userdb-dropin.c b/src/shared/userdb-dropin.c
new file mode 100644 (file)
index 0000000..442c6c9
--- /dev/null
@@ -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);
+}
diff --git a/src/shared/userdb-dropin.h b/src/shared/userdb-dropin.h
new file mode 100644 (file)
index 0000000..94cdd15
--- /dev/null
@@ -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);
index caef7cdf0639f9dfbc5ab218aa78b398d73acbfa..3e08b339f5b861aabf0a21d1b598d56d671f0d00 100644 (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;
 
-        setgrent();
-        iterator->nss_iterating = true;
+        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;
 
-        r = 0;
+                setgrent();
+                iterator->nss_iterating = true;
+        }
 
-finish:
-        if (r >= 0)
-                *ret = TAKE_PTR(iterator);
-        return r;
+        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;
+
+        *ret = TAKE_PTR(iterator);
+        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,40 +1215,49 @@ 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;
 
-        r = userdb_iterator_block_nss_systemd(iterator);
-        if (r < 0)
-                return r;
+        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
 
-        /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
-        (void) nss_group_record_by_name(name, false, &gr);
-        if (gr) {
-                iterator->members_of_group = strv_copy(gr->members);
-                if (!iterator->members_of_group)
-                        return -ENOMEM;
+        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
+                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
 
-                iterator->index_members_of_group = 0;
+                r = userdb_iterator_block_nss_systemd(iterator);
+                if (r < 0)
+                        return r;
 
-                iterator->found_group_name = strdup(name);
-                if (!iterator->found_group_name)
-                        return -ENOMEM;
+                /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
+                (void) nss_group_record_by_name(name, false, &gr);
+                if (gr) {
+                        iterator->members_of_group = strv_copy(gr->members);
+                        if (!iterator->members_of_group)
+                                return -ENOMEM;
+
+                        iterator->index_members_of_group = 0;
+
+                        iterator->found_group_name = strdup(name);
+                        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);
 
-finish:
-        if (r >= 0)
-                *ret = TAKE_PTR(iterator);
+        if (qr < 0 &&
+            strv_isempty(iterator->members_of_group) &&
+            strv_isempty(iterator->dropins))
+                return qr;
 
-        return r;
+        *ret = TAKE_PTR(iterator);
+        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);
 
-        r = userdb_iterator_block_nss_systemd(iterator);
-        if (r < 0)
-                return r;
+        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;
+                setgrent();
+                iterator->nss_iterating = true;
+        }
 
-        r = 0;
+        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
+                discover_membership_dropins(iterator, flags);
 
-finish:
-        if (r >= 0)
-                *ret = TAKE_PTR(iterator);
+        if (qr < 0 &&
+            !iterator->nss_iterating &&
+            strv_isempty(iterator->dropins))
+                return qr;
 
-        return r;
+        *ret = TAKE_PTR(iterator);
+        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;
index d69c6db034014c8750351cd4c30bfbc13eb257b8..a851cbd6fba4073a4572ce0c7c0904ad70ff29f2 100644 (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,17 @@ 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,
 } 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);