]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nss: hook up nss-systemd with userdb varlink bits
authorLennart Poettering <lennart@poettering.net>
Thu, 4 Jul 2019 16:31:11 +0000 (18:31 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 15 Jan 2020 14:29:07 +0000 (15:29 +0100)
This changes nss-systemd to use the new varlink user/group APIs for
looking up everything.

(This also changes the factory /etc/nsswitch.conf line to use for
hooking up nss-system to use glibc's [SUCCESS=merge] feature so that we
can properly merge group membership lists).

Fixes: #12492
factory/etc/nsswitch.conf
meson.build
src/nss-systemd/nss-systemd.c
src/nss-systemd/nss-systemd.sym
src/nss-systemd/userdb-glue.c [new file with mode: 0644]
src/nss-systemd/userdb-glue.h [new file with mode: 0644]

index 5470993e34b2a616598f598953ad2f5e48fe03fc..e7365cd1426505ee330a1f4a492b56a0660a1ad6 100644 (file)
@@ -1,7 +1,7 @@
 # This file is part of systemd.
 
 passwd:         compat mymachines systemd
-group:          compat mymachines systemd
+group:          compat [SUCCESS=merge] mymachines [SUCCESS=merge] systemd
 shadow:         compat
 
 hosts:          files mymachines resolve [!UNAVAIL=return] dns myhostname
index 6837fb9271b41b6f375c6b823eeb9767ea5a05b2..46f3e9c7ba31179196b9d2b3a1251585a671e38f 100644 (file)
@@ -1567,7 +1567,7 @@ test_dlopen = executable(
         build_by_default : want_tests != 'false')
 
 foreach tuple : [['myhostname', 'ENABLE_NSS_MYHOSTNAME'],
-                 ['systemd',    'ENABLE_NSS_SYSTEMD'],
+                 ['systemd',    'ENABLE_NSS_SYSTEMD', 'src/nss-systemd/userdb-glue.c src/nss-systemd/userdb-glue.h'],
                  ['mymachines', 'ENABLE_NSS_MYMACHINES'],
                  ['resolve',    'ENABLE_NSS_RESOLVE']]
 
@@ -1578,9 +1578,14 @@ foreach tuple : [['myhostname', 'ENABLE_NSS_MYHOSTNAME'],
                 sym = 'src/nss-@0@/nss-@0@.sym'.format(module)
                 version_script_arg = join_paths(project_source_root, sym)
 
+                sources = ['src/nss-@0@/nss-@0@.c'.format(module)]
+                if tuple.length() > 2
+                        sources += tuple[2].split()
+                endif
+
                 nss = shared_library(
                         'nss_' + module,
-                        'src/nss-@0@/nss-@0@.c'.format(module),
+                        sources,
                         disable_mempool_c,
                         version : '2',
                         include_directories : includes,
index 8ef1cd5ea9e7413d09eed822ec20b657ff0d3b55..34f886a8cc5025c322a3d68c60edde28984b2d28 100644 (file)
@@ -3,28 +3,17 @@
 #include <nss.h>
 #include <pthread.h>
 
-#include "sd-bus.h"
-
-#include "alloc-util.h"
-#include "bus-common-errors.h"
-#include "dirent-util.h"
 #include "env-util.h"
+#include "errno-util.h"
 #include "fd-util.h"
-#include "format-util.h"
-#include "fs-util.h"
-#include "list.h"
+#include "group-record-nss.h"
 #include "macro.h"
 #include "nss-util.h"
 #include "signal-util.h"
-#include "stdio-util.h"
-#include "string-util.h"
+#include "strv.h"
 #include "user-util.h"
-#include "util.h"
-
-#define DYNAMIC_USER_GECOS       "Dynamic User"
-#define DYNAMIC_USER_PASSWD      "*" /* locked */
-#define DYNAMIC_USER_DIR         "/"
-#define DYNAMIC_USER_SHELL       NOLOGIN
+#include "userdb-glue.h"
+#include "userdb.h"
 
 static const struct passwd root_passwd = {
         .pw_name = (char*) "root",
@@ -60,78 +49,32 @@ static const struct group nobody_group = {
         .gr_mem = (char*[]) { NULL },
 };
 
-typedef struct UserEntry UserEntry;
-typedef struct GetentData GetentData;
+typedef struct GetentData {
+        /* As explained in NOTES section of getpwent_r(3) as 'getpwent_r() is not really reentrant since it
+         * shares the reading position in the stream with all other threads', we need to protect the data in
+         * UserDBIterator from multithreaded programs which may call setpwent(), getpwent_r(), or endpwent()
+         * simultaneously. So, each function locks the data by using the mutex below. */
+        pthread_mutex_t mutex;
+        UserDBIterator *iterator;
 
-struct UserEntry {
-        uid_t id;
-        char *name;
+        /* Applies to group iterations only: true while we iterate over groups defined through NSS, false
+         * otherwise. */
+        bool by_membership;
+} GetentData;
 
-        GetentData *data;
-        LIST_FIELDS(UserEntry, entries);
+static GetentData getpwent_data = {
+        .mutex = PTHREAD_MUTEX_INITIALIZER
 };
 
-struct GetentData {
-        /* As explained in NOTES section of getpwent_r(3) as 'getpwent_r() is not really
-         * reentrant since it shares the reading position in the stream with all other threads',
-         * we need to protect the data in UserEntry from multithreaded programs which may call
-         * setpwent(), getpwent_r(), or endpwent() simultaneously. So, each function locks the
-         * data by using the mutex below. */
-        pthread_mutex_t mutex;
-
-        UserEntry *position;
-        LIST_HEAD(UserEntry, entries);
+static GetentData getgrent_data = {
+        .mutex = PTHREAD_MUTEX_INITIALIZER
 };
 
-static GetentData getpwent_data = { PTHREAD_MUTEX_INITIALIZER, NULL, NULL };
-static GetentData getgrent_data = { PTHREAD_MUTEX_INITIALIZER, NULL, NULL };
-
 NSS_GETPW_PROTOTYPES(systemd);
 NSS_GETGR_PROTOTYPES(systemd);
-enum nss_status _nss_systemd_endpwent(void) _public_;
-enum nss_status _nss_systemd_setpwent(int stayopen) _public_;
-enum nss_status _nss_systemd_getpwent_r(struct passwd *result, char *buffer, size_t buflen, int *errnop) _public_;
-enum nss_status _nss_systemd_endgrent(void) _public_;
-enum nss_status _nss_systemd_setgrent(int stayopen) _public_;
-enum nss_status _nss_systemd_getgrent_r(struct group *result, char *buffer, size_t buflen, int *errnop) _public_;
-
-static int direct_lookup_name(const char *name, uid_t *ret) {
-        _cleanup_free_ char *s = NULL;
-        const char *path;
-        int r;
-
-        assert(name);
-
-        /* Normally, we go via the bus to resolve names. That has the benefit that it is available from any mount
-         * namespace and subject to proper authentication. However, there's one problem: if our module is called from
-         * dbus-daemon itself we really can't use D-Bus to communicate. In this case, resort to a client-side hack,
-         * and look for the dynamic names directly. This is pretty ugly, but breaks the cyclic dependency. */
-
-        path = strjoina("/run/systemd/dynamic-uid/direct:", name);
-        r = readlink_malloc(path, &s);
-        if (r < 0)
-                return r;
-
-        return parse_uid(s, ret);
-}
-
-static int direct_lookup_uid(uid_t uid, char **ret) {
-        char path[STRLEN("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1], *s;
-        int r;
-
-        xsprintf(path, "/run/systemd/dynamic-uid/direct:" UID_FMT, uid);
-
-        r = readlink_malloc(path, &s);
-        if (r < 0)
-                return r;
-        if (!valid_user_group_name(s)) { /* extra safety check */
-                free(s);
-                return -EINVAL;
-        }
-
-        *ret = s;
-        return 0;
-}
+NSS_PWENT_PROTOTYPES(systemd);
+NSS_GRENT_PROTOTYPES(systemd);
+NSS_INITGROUPS_PROTOTYPE(systemd);
 
 enum nss_status _nss_systemd_getpwnam_r(
                 const char *name,
@@ -139,99 +82,49 @@ enum nss_status _nss_systemd_getpwnam_r(
                 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 bypass, r;
+        enum nss_status status;
+        int e;
 
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
         assert(name);
         assert(pwd);
+        assert(errnop);
 
-        /* If the username is not valid, then we don't know it. Ideally libc would filter these for us anyway. We don't
-         * generate EINVAL here, because it isn't really out business to complain about invalid user names. */
+        /* If the username is not valid, then we don't know it. Ideally libc would filter these for us
+         * anyway. We don't generate EINVAL here, because it isn't really out business to complain about
+         * invalid user names. */
         if (!valid_user_group_name(name))
                 return NSS_STATUS_NOTFOUND;
 
         /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
         if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
+
                 if (streq(name, root_passwd.pw_name)) {
                         *pwd = root_passwd;
                         return NSS_STATUS_SUCCESS;
                 }
-                if (synthesize_nobody() &&
-                    streq(name, nobody_passwd.pw_name)) {
-                        *pwd = nobody_passwd;
-                        return NSS_STATUS_SUCCESS;
-                }
-        }
-
-        /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */
-        if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
-                return NSS_STATUS_NOTFOUND;
 
-        bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS");
-        if (bypass <= 0) {
-                r = sd_bus_open_system(&bus);
-                if (r < 0)
-                        bypass = 1;
-        }
-
-        if (bypass > 0) {
-                r = direct_lookup_name(name, (uid_t*) &translated);
-                if (r == -ENOENT)
-                        return NSS_STATUS_NOTFOUND;
-                if (r < 0)
-                        goto fail;
-        } else {
-                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))
+                if (streq(name, nobody_passwd.pw_name)) {
+                        if (!synthesize_nobody())
                                 return NSS_STATUS_NOTFOUND;
 
-                        goto fail;
+                        *pwd = nobody_passwd;
+                        return NSS_STATUS_SUCCESS;
                 }
 
-                r = sd_bus_message_read(reply, "u", &translated);
-                if (r < 0)
-                        goto fail;
-        }
+        } else if (STR_IN_SET(name, root_passwd.pw_name, nobody_passwd.pw_name))
+                return NSS_STATUS_NOTFOUND;
 
-        l = strlen(name);
-        if (buflen < l+1) {
+        status = userdb_getpwnam(name, pwd, buffer, buflen, &e);
+        if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
                 UNPROTECT_ERRNO;
-                *errnop = ERANGE;
-                return NSS_STATUS_TRYAGAIN;
+                *errnop = e;
+                return status;
         }
 
-        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_GECOS;
-        pwd->pw_passwd = (char*) DYNAMIC_USER_PASSWD;
-        pwd->pw_dir = (char*) DYNAMIC_USER_DIR;
-        pwd->pw_shell = (char*) DYNAMIC_USER_SHELL;
-
-        return NSS_STATUS_SUCCESS;
-
-fail:
-        UNPROTECT_ERRNO;
-        *errnop = -r;
-        return NSS_STATUS_UNAVAIL;
+        return status;
 }
 
 enum nss_status _nss_systemd_getpwuid_r(
@@ -240,100 +133,45 @@ enum nss_status _nss_systemd_getpwuid_r(
                 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;
-        _cleanup_free_ char *direct = NULL;
-        const char *translated;
-        size_t l;
-        int bypass, r;
+        enum nss_status status;
+        int e;
 
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
+        assert(pwd);
+        assert(errnop);
+
         if (!uid_is_valid(uid))
                 return NSS_STATUS_NOTFOUND;
 
         /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */
         if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
+
                 if (uid == root_passwd.pw_uid) {
                         *pwd = root_passwd;
                         return NSS_STATUS_SUCCESS;
                 }
-                if (synthesize_nobody() &&
-                    uid == nobody_passwd.pw_uid) {
-                        *pwd = nobody_passwd;
-                        return NSS_STATUS_SUCCESS;
-                }
-        }
 
-        if (!uid_is_dynamic(uid))
-                return NSS_STATUS_NOTFOUND;
-
-        if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
-                return NSS_STATUS_NOTFOUND;
-
-        bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS");
-        if (bypass <= 0) {
-                r = sd_bus_open_system(&bus);
-                if (r < 0)
-                        bypass = 1;
-        }
-
-        if (bypass > 0) {
-                r = direct_lookup_uid(uid, &direct);
-                if (r == -ENOENT)
-                        return NSS_STATUS_NOTFOUND;
-                if (r < 0)
-                        goto fail;
-
-                translated = direct;
-
-        } else {
-                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))
+                if (uid == nobody_passwd.pw_uid) {
+                        if (!synthesize_nobody())
                                 return NSS_STATUS_NOTFOUND;
 
-                        goto fail;
+                        *pwd = nobody_passwd;
+                        return NSS_STATUS_SUCCESS;
                 }
 
-                r = sd_bus_message_read(reply, "s", &translated);
-                if (r < 0)
-                        goto fail;
-        }
+        } else if (uid == root_passwd.pw_uid || uid == nobody_passwd.pw_uid)
+                return NSS_STATUS_NOTFOUND;
 
-        l = strlen(translated) + 1;
-        if (buflen < l) {
+        status = userdb_getpwuid(uid, pwd, buffer, buflen, &e);
+        if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
                 UNPROTECT_ERRNO;
-                *errnop = ERANGE;
-                return NSS_STATUS_TRYAGAIN;
+                *errnop = e;
+                return status;
         }
 
-        memcpy(buffer, translated, l);
-
-        pwd->pw_name = buffer;
-        pwd->pw_uid = uid;
-        pwd->pw_gid = uid;
-        pwd->pw_gecos = (char*) DYNAMIC_USER_GECOS;
-        pwd->pw_passwd = (char*) DYNAMIC_USER_PASSWD;
-        pwd->pw_dir = (char*) DYNAMIC_USER_DIR;
-        pwd->pw_shell = (char*) DYNAMIC_USER_SHELL;
-
-        return NSS_STATUS_SUCCESS;
-
-fail:
-        UNPROTECT_ERRNO;
-        *errnop = -r;
-        return NSS_STATUS_UNAVAIL;
+        return status;
 }
 
 #pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
@@ -344,94 +182,46 @@ enum nss_status _nss_systemd_getgrnam_r(
                 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 bypass, r;
+        enum nss_status status;
+        int e;
 
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
         assert(name);
         assert(gr);
+        assert(errnop);
 
         if (!valid_user_group_name(name))
                 return NSS_STATUS_NOTFOUND;
 
         /* Synthesize records for root and nobody, in case they are missing form /etc/group */
         if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
+
                 if (streq(name, root_group.gr_name)) {
                         *gr = root_group;
                         return NSS_STATUS_SUCCESS;
                 }
-                if (synthesize_nobody() &&
-                    streq(name, nobody_group.gr_name)) {
-                        *gr = nobody_group;
-                        return NSS_STATUS_SUCCESS;
-                }
-        }
-
-        if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
-                return NSS_STATUS_NOTFOUND;
 
-        bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS");
-        if (bypass <= 0) {
-                r = sd_bus_open_system(&bus);
-                if (r < 0)
-                        bypass = 1;
-        }
-
-        if (bypass > 0) {
-                r = direct_lookup_name(name, (uid_t*) &translated);
-                if (r == -ENOENT)
-                        return NSS_STATUS_NOTFOUND;
-                if (r < 0)
-                        goto fail;
-        } else {
-                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))
+                if (streq(name, nobody_group.gr_name)) {
+                        if (!synthesize_nobody())
                                 return NSS_STATUS_NOTFOUND;
 
-                        goto fail;
+                        *gr = nobody_group;
+                        return NSS_STATUS_SUCCESS;
                 }
 
-                r = sd_bus_message_read(reply, "u", &translated);
-                if (r < 0)
-                        goto fail;
-        }
+        } else if (STR_IN_SET(name, root_group.gr_name, nobody_group.gr_name))
+                return NSS_STATUS_NOTFOUND;
 
-        l = sizeof(char*) + strlen(name) + 1;
-        if (buflen < l) {
+        status = userdb_getgrnam(name, gr, buffer, buflen, &e);
+        if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
                 UNPROTECT_ERRNO;
-                *errnop = ERANGE;
-                return NSS_STATUS_TRYAGAIN;
+                *errnop = e;
+                return status;
         }
 
-        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*) DYNAMIC_USER_PASSWD;
-        gr->gr_mem = (char**) buffer;
-
-        return NSS_STATUS_SUCCESS;
-
-fail:
-        UNPROTECT_ERRNO;
-        *errnop = -r;
-        return NSS_STATUS_UNAVAIL;
+        return status;
 }
 
 enum nss_status _nss_systemd_getgrgid_r(
@@ -440,154 +230,56 @@ enum nss_status _nss_systemd_getgrgid_r(
                 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;
-        _cleanup_free_ char *direct = NULL;
-        const char *translated;
-        size_t l;
-        int bypass, r;
+        enum nss_status status;
+        int e;
 
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
+        assert(gr);
+        assert(errnop);
+
         if (!gid_is_valid(gid))
                 return NSS_STATUS_NOTFOUND;
 
         /* Synthesize records for root and nobody, in case they are missing from /etc/group */
         if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
+
                 if (gid == root_group.gr_gid) {
                         *gr = root_group;
                         return NSS_STATUS_SUCCESS;
                 }
-                if (synthesize_nobody() &&
-                    gid == nobody_group.gr_gid) {
-                        *gr = nobody_group;
-                        return NSS_STATUS_SUCCESS;
-                }
-        }
-
-        if (!gid_is_dynamic(gid))
-                return NSS_STATUS_NOTFOUND;
-
-        if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
-                return NSS_STATUS_NOTFOUND;
 
-        bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS");
-        if (bypass <= 0) {
-                r = sd_bus_open_system(&bus);
-                if (r < 0)
-                        bypass = 1;
-        }
-
-        if (bypass > 0) {
-                r = direct_lookup_uid(gid, &direct);
-                if (r == -ENOENT)
-                        return NSS_STATUS_NOTFOUND;
-                if (r < 0)
-                        goto fail;
-
-                translated = direct;
-
-        } else {
-                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))
+                if (gid == nobody_group.gr_gid) {
+                        if (!synthesize_nobody())
                                 return NSS_STATUS_NOTFOUND;
 
-                        goto fail;
+                        *gr = nobody_group;
+                        return NSS_STATUS_SUCCESS;
                 }
 
-                r = sd_bus_message_read(reply, "s", &translated);
-                if (r < 0)
-                        goto fail;
-        }
+        } else if (gid == root_group.gr_gid || gid == nobody_group.gr_gid)
+                return NSS_STATUS_NOTFOUND;
 
-        l = sizeof(char*) + strlen(translated) + 1;
-        if (buflen < l) {
+        status = userdb_getgrgid(gid, gr, buffer, buflen, &e);
+        if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
                 UNPROTECT_ERRNO;
-                *errnop = ERANGE;
-                return NSS_STATUS_TRYAGAIN;
+                *errnop = e;
+                return status;
         }
 
-        memzero(buffer, sizeof(char*));
-        strcpy(buffer + sizeof(char*), translated);
-
-        gr->gr_name = buffer + sizeof(char*);
-        gr->gr_gid = gid;
-        gr->gr_passwd = (char*) DYNAMIC_USER_PASSWD;
-        gr->gr_mem = (char**) buffer;
-
-        return NSS_STATUS_SUCCESS;
-
-fail:
-        UNPROTECT_ERRNO;
-        *errnop = -r;
-        return NSS_STATUS_UNAVAIL;
-}
-
-static void user_entry_free(UserEntry *p) {
-        if (!p)
-                return;
-
-        if (p->data)
-                LIST_REMOVE(entries, p->data->entries, p);
-
-        free(p->name);
-        free(p);
-}
-
-static int user_entry_add(GetentData *data, const char *name, uid_t id) {
-        UserEntry *p;
-
-        assert(data);
-
-        /* This happens when User= or Group= already exists statically. */
-        if (!uid_is_dynamic(id))
-                return -EINVAL;
-
-        p = new0(UserEntry, 1);
-        if (!p)
-                return -ENOMEM;
-
-        p->name = strdup(name);
-        if (!p->name) {
-                free(p);
-                return -ENOMEM;
-        }
-        p->id = id;
-        p->data = data;
-
-        LIST_PREPEND(entries, data->entries, p);
-
-        return 0;
-}
-
-static void systemd_endent(GetentData *data) {
-        UserEntry *p;
-
-        assert(data);
-
-        while ((p = data->entries))
-                user_entry_free(p);
-
-        data->position = NULL;
+        return status;
 }
 
 static enum nss_status nss_systemd_endent(GetentData *p) {
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
+        assert(p);
+
         assert_se(pthread_mutex_lock(&p->mutex) == 0);
-        systemd_endent(p);
+        p->iterator = userdb_iterator_free(p->iterator);
+        p->by_membership = false;
         assert_se(pthread_mutex_unlock(&p->mutex) == 0);
 
         return NSS_STATUS_SUCCESS;
@@ -601,235 +293,353 @@ enum nss_status _nss_systemd_endgrent(void) {
         return nss_systemd_endent(&getgrent_data);
 }
 
-static int direct_enumeration(GetentData *p) {
-        _cleanup_closedir_ DIR *d = NULL;
-        struct dirent *de;
-        int r;
+enum nss_status _nss_systemd_setpwent(int stayopen) {
+        enum nss_status ret;
 
-        assert(p);
+        PROTECT_ERRNO;
+        BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
-        d = opendir("/run/systemd/dynamic-uid/");
-        if (!d)
-                return -errno;
+        if (userdb_nss_compat_is_enabled() <= 0)
+                return NSS_STATUS_NOTFOUND;
 
-        FOREACH_DIRENT(de, d, return -errno) {
-                _cleanup_free_ char *name = NULL;
-                uid_t uid, verified;
+        assert_se(pthread_mutex_lock(&getpwent_data.mutex) == 0);
 
-                if (!dirent_is_file(de))
-                        continue;
+        getpwent_data.iterator = userdb_iterator_free(getpwent_data.iterator);
+        getpwent_data.by_membership = false;
 
-                r = parse_uid(de->d_name, &uid);
-                if (r < 0)
-                        continue;
+        ret = userdb_all(nss_glue_userdb_flags(), &getpwent_data.iterator) < 0 ?
+                NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
 
-                r = direct_lookup_uid(uid, &name);
-                if (r == -ENOMEM)
-                        return r;
-                if (r < 0)
-                        continue;
+        assert_se(pthread_mutex_unlock(&getpwent_data.mutex) == 0);
+        return ret;
+}
 
-                r = direct_lookup_name(name, &verified);
-                if (r < 0)
-                        continue;
+enum nss_status _nss_systemd_setgrent(int stayopen) {
+        enum nss_status ret;
 
-                if (uid != verified)
-                        continue;
+        PROTECT_ERRNO;
+        BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
-                r = user_entry_add(p, name, uid);
-                if (r == -ENOMEM)
-                        return r;
-                if (r < 0)
-                        continue;
-        }
+        if (userdb_nss_compat_is_enabled() <= 0)
+                return NSS_STATUS_NOTFOUND;
 
-        return 0;
+        assert_se(pthread_mutex_lock(&getgrent_data.mutex) == 0);
+
+        getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
+        getpwent_data.by_membership = false;
+
+        ret = groupdb_all(nss_glue_userdb_flags(), &getgrent_data.iterator) < 0 ?
+                NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
+
+        assert_se(pthread_mutex_unlock(&getgrent_data.mutex) == 0);
+        return ret;
 }
 
-static enum nss_status systemd_setent(GetentData *p) {
-        _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 *name;
-        uid_t id;
-        int bypass, r;
+enum nss_status _nss_systemd_getpwent_r(
+                struct passwd *result,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+        enum nss_status ret;
+        int r;
 
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
-        assert(p);
+        assert(result);
+        assert(errnop);
 
-        assert_se(pthread_mutex_lock(&p->mutex) == 0);
+        r = userdb_nss_compat_is_enabled();
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+        if (!r)
+                return NSS_STATUS_NOTFOUND;
 
-        systemd_endent(p);
+        assert_se(pthread_mutex_lock(&getpwent_data.mutex) == 0);
 
-        if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
+        if (!getpwent_data.iterator) {
+                UNPROTECT_ERRNO;
+                *errnop = EHOSTDOWN;
+                ret = NSS_STATUS_UNAVAIL;
                 goto finish;
-
-        bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS");
-
-        if (bypass <= 0) {
-                r = sd_bus_open_system(&bus);
-                if (r < 0)
-                        bypass = 1;
         }
 
-        if (bypass > 0) {
-                r = direct_enumeration(p);
-                if (r < 0)
-                        goto fail;
-
+        r = userdb_iterator_get(getpwent_data.iterator, &ur);
+        if (r == -ESRCH) {
+                ret = NSS_STATUS_NOTFOUND;
+                goto finish;
+        }
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                ret = NSS_STATUS_UNAVAIL;
                 goto finish;
         }
 
-        r = sd_bus_call_method(bus,
-                               "org.freedesktop.systemd1",
-                               "/org/freedesktop/systemd1",
-                               "org.freedesktop.systemd1.Manager",
-                               "GetDynamicUsers",
-                               &error,
-                               &reply,
-                               NULL);
-        if (r < 0)
-                goto fail;
-
-        r = sd_bus_message_enter_container(reply, 'a', "(us)");
-        if (r < 0)
-                goto fail;
-
-        while ((r = sd_bus_message_read(reply, "(us)", &id, &name)) > 0) {
-                r = user_entry_add(p, name, id);
-                if (r == -ENOMEM)
-                        goto fail;
-                if (r < 0)
-                        continue;
+        r = nss_pack_user_record(ur, result, buffer, buflen);
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                ret = NSS_STATUS_TRYAGAIN;
+                goto finish;
         }
-        if (r < 0)
-                goto fail;
 
-        r = sd_bus_message_exit_container(reply);
-        if (r < 0)
-                goto fail;
+        ret = NSS_STATUS_SUCCESS;
 
 finish:
-        p->position = p->entries;
-        assert_se(pthread_mutex_unlock(&p->mutex) == 0);
-
-        return NSS_STATUS_SUCCESS;
-
-fail:
-        systemd_endent(p);
-        assert_se(pthread_mutex_unlock(&p->mutex) == 0);
-
-        return NSS_STATUS_UNAVAIL;
-}
-
-enum nss_status _nss_systemd_setpwent(int stayopen) {
-        return systemd_setent(&getpwent_data);
+        assert_se(pthread_mutex_unlock(&getpwent_data.mutex) == 0);
+        return ret;
 }
 
-enum nss_status _nss_systemd_setgrent(int stayopen) {
-        return systemd_setent(&getgrent_data);
-}
+enum nss_status _nss_systemd_getgrent_r(
+                struct group *result,
+                char *buffer, size_t buflen,
+                int *errnop) {
 
-enum nss_status _nss_systemd_getpwent_r(struct passwd *result, char *buffer, size_t buflen, int *errnop) {
+        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
+        _cleanup_free_ char **members = NULL;
         enum nss_status ret;
-        UserEntry *p;
-        size_t len;
+        int r;
 
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
         assert(result);
-        assert(buffer);
         assert(errnop);
 
-        assert_se(pthread_mutex_lock(&getpwent_data.mutex) == 0);
+        r = userdb_nss_compat_is_enabled();
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+        if (!r)
+                return NSS_STATUS_UNAVAIL;
+
+        assert_se(pthread_mutex_lock(&getgrent_data.mutex) == 0);
 
-        LIST_FOREACH(entries, p, getpwent_data.position) {
-                len = strlen(p->name) + 1;
-                if (buflen < len) {
+        if (!getgrent_data.iterator) {
+                UNPROTECT_ERRNO;
+                *errnop = EHOSTDOWN;
+                ret = NSS_STATUS_UNAVAIL;
+                goto finish;
+        }
+
+        if (!getgrent_data.by_membership) {
+                r = groupdb_iterator_get(getgrent_data.iterator, &gr);
+                if (r == -ESRCH) {
+                        /* So we finished iterating native groups now. let's now continue with iterating
+                         * native memberships, and generate additional group entries for any groups
+                         * referenced there that are defined in NSS only. This means for those groups there
+                         * will be two or more entries generated during iteration, but this is apparently how
+                         * this is supposed to work, and what other implementations do too. Clients are
+                         * supposed to merge the group records found during iteration automatically. */
+                        getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
+
+                        r = membershipdb_all(nss_glue_userdb_flags(), &getgrent_data.iterator);
+                        if (r < 0) {
+                                UNPROTECT_ERRNO;
+                                *errnop = -r;
+                                ret = NSS_STATUS_UNAVAIL;
+                                goto finish;
+                        }
+
+                        getgrent_data.by_membership = true;
+                } else if (r < 0) {
                         UNPROTECT_ERRNO;
-                        *errnop = ERANGE;
-                        ret = NSS_STATUS_TRYAGAIN;
-                        goto finalize;
+                        *errnop = -r;
+                        ret = NSS_STATUS_UNAVAIL;
+                        goto finish;
+                } else if (!STR_IN_SET(gr->group_name, root_group.gr_name, nobody_group.gr_name)) {
+                        r = membershipdb_by_group_strv(gr->group_name, nss_glue_userdb_flags(), &members);
+                        if (r < 0) {
+                                UNPROTECT_ERRNO;
+                                *errnop = -r;
+                                ret = NSS_STATUS_UNAVAIL;
+                                goto finish;
+                        }
                 }
+        }
 
-                memcpy(buffer, p->name, len);
-
-                result->pw_name = buffer;
-                result->pw_uid = p->id;
-                result->pw_gid = p->id;
-                result->pw_gecos = (char*) DYNAMIC_USER_GECOS;
-                result->pw_passwd = (char*) DYNAMIC_USER_PASSWD;
-                result->pw_dir = (char*) DYNAMIC_USER_DIR;
-                result->pw_shell = (char*) DYNAMIC_USER_SHELL;
-                break;
+        if (getgrent_data.by_membership) {
+                _cleanup_close_ int lock_fd = -1;
+
+                for (;;) {
+                        _cleanup_free_ char *user_name = NULL, *group_name = NULL;
+
+                        r = membershipdb_iterator_get(getgrent_data.iterator, &user_name, &group_name);
+                        if (r == -ESRCH) {
+                                ret = NSS_STATUS_NOTFOUND;
+                                goto finish;
+                        }
+                        if (r < 0) {
+                                UNPROTECT_ERRNO;
+                                *errnop = -r;
+                                ret = NSS_STATUS_UNAVAIL;
+                                goto finish;
+                        }
+
+                        if (STR_IN_SET(user_name, root_passwd.pw_name, nobody_passwd.pw_name))
+                                continue;
+                        if (STR_IN_SET(group_name, root_group.gr_name, nobody_group.gr_name))
+                                continue;
+
+                        /* We are about to recursively call into NSS, let's make sure we disable recursion into our own code. */
+                        if (lock_fd < 0) {
+                                lock_fd = userdb_nss_compat_disable();
+                                if (lock_fd < 0 && lock_fd != -EBUSY) {
+                                        UNPROTECT_ERRNO;
+                                        *errnop = -lock_fd;
+                                        ret = NSS_STATUS_UNAVAIL;
+                                        goto finish;
+                                }
+                        }
+
+                        r = nss_group_record_by_name(group_name, &gr);
+                        if (r == -ESRCH)
+                                continue;
+                        if (r < 0) {
+                                log_debug_errno(r, "Failed to do NSS check for group '%s', ignoring: %m", group_name);
+                                continue;
+                        }
+
+                        members = strv_new(user_name);
+                        if (!members) {
+                                UNPROTECT_ERRNO;
+                                *errnop = ENOMEM;
+                                return NSS_STATUS_TRYAGAIN;
+                        }
+
+                        /* Note that we currently generate one group entry per user that is part of a
+                         * group. It's a bit ugly, but equivalent to generating a single entry with a set of
+                         * members in them. */
+                        break;
+                }
         }
-        if (!p) {
-                ret = NSS_STATUS_NOTFOUND;
-                goto finalize;
+
+        r = nss_pack_group_record(gr, members, result, buffer, buflen);
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                ret = NSS_STATUS_TRYAGAIN;
+                goto finish;
         }
 
-        /* On success, step to the next entry. */
-        p = p->entries_next;
         ret = NSS_STATUS_SUCCESS;
 
-finalize:
-        /* Save position for the next call. */
-        getpwent_data.position = p;
-
-        assert_se(pthread_mutex_unlock(&getpwent_data.mutex) == 0);
-
+finish:
+        assert_se(pthread_mutex_unlock(&getgrent_data.mutex) == 0);
         return ret;
 }
 
-enum nss_status _nss_systemd_getgrent_r(struct group *result, char *buffer, size_t buflen, int *errnop) {
-        enum nss_status ret;
-        UserEntry *p;
-        size_t len;
+enum nss_status _nss_systemd_initgroups_dyn(
+                const char *user_name,
+                gid_t gid,
+                long *start,
+                long *size,
+                gid_t **groupsp,
+                long int limit,
+                int *errnop) {
+
+        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
+        bool any = false;
+        int r;
 
         PROTECT_ERRNO;
         BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
 
-        assert(result);
-        assert(buffer);
+        assert(user_name);
+        assert(start);
+        assert(size);
+        assert(groupsp);
         assert(errnop);
 
-        assert_se(pthread_mutex_lock(&getgrent_data.mutex) == 0);
+        if (!valid_user_group_name(user_name))
+                return NSS_STATUS_NOTFOUND;
+
+        /* Don't allow extending these two special users, the same as we won't resolve them via getpwnam() */
+        if (STR_IN_SET(user_name, root_passwd.pw_name, nobody_passwd.pw_name))
+                return NSS_STATUS_NOTFOUND;
+
+        r = userdb_nss_compat_is_enabled();
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+        if (!r)
+                return NSS_STATUS_NOTFOUND;
+
+        r = membershipdb_by_user(user_name, nss_glue_userdb_flags(), &iterator);
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
 
-        LIST_FOREACH(entries, p, getgrent_data.position) {
-                len = sizeof(char*) + strlen(p->name) + 1;
-                if (buflen < len) {
+        for (;;) {
+                _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
+                _cleanup_free_ char *group_name = NULL;
+
+                r = membershipdb_iterator_get(iterator, NULL, &group_name);
+                if (r == -ESRCH)
+                        break;
+                if (r < 0) {
                         UNPROTECT_ERRNO;
-                        *errnop = ERANGE;
-                        ret = NSS_STATUS_TRYAGAIN;
-                        goto finalize;
+                        *errnop = -r;
+                        return NSS_STATUS_UNAVAIL;
                 }
 
-                memzero(buffer, sizeof(char*));
-                strcpy(buffer + sizeof(char*), p->name);
+                /* The group might be defined via traditional NSS only, hence let's do a full look-up without
+                 * disabling NSS. This means we are operating recursively here. */
 
-                result->gr_name = buffer + sizeof(char*);
-                result->gr_gid = p->id;
-                result->gr_passwd = (char*) DYNAMIC_USER_PASSWD;
-                result->gr_mem = (char**) buffer;
-                break;
-        }
-        if (!p) {
-                ret = NSS_STATUS_NOTFOUND;
-                goto finalize;
-        }
+                r = groupdb_by_name(group_name, nss_glue_userdb_flags() & ~USERDB_AVOID_NSS, &g);
+                if (r == -ESRCH)
+                        continue;
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to resolve group '%s', ignoring: %m", group_name);
+                        continue;
+                }
 
-        /* On success, step to the next entry. */
-        p = p->entries_next;
-        ret = NSS_STATUS_SUCCESS;
+                if (g->gid == gid)
+                        continue;
 
-finalize:
-        /* Save position for the next call. */
-        getgrent_data.position = p;
+                if (*start >= *size) {
+                        gid_t *new_groups;
+                        long new_size;
+
+                        if (limit > 0 && *size >= limit) /* Reached the limit.? */
+                                break;
+
+                        if (*size > LONG_MAX/2) { /* Check for overflow */
+                                UNPROTECT_ERRNO;
+                                *errnop = ENOMEM;
+                                return NSS_STATUS_TRYAGAIN;
+                        }
+
+                        new_size = *start * 2;
+                        if (limit > 0 && new_size > limit)
+                                new_size = limit;
+
+                        /* Enlarge buffer */
+                        new_groups = realloc(*groupsp, new_size * sizeof(**groupsp));
+                        if (!new_groups) {
+                                UNPROTECT_ERRNO;
+                                *errnop = ENOMEM;
+                                return NSS_STATUS_TRYAGAIN;
+                        }
+
+                        *groupsp = new_groups;
+                        *size = new_size;
+                }
 
-        assert_se(pthread_mutex_unlock(&getgrent_data.mutex) == 0);
+                (*groupsp)[(*start)++] = g->gid;
+                any = true;
+        }
 
-        return ret;
+        return any ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND;
 }
index ff63382b152c901ebbed96d5191c4cd9fff3f484..77e1fbe93f227e6e878c7847bb9f773b9dfa7769 100644 (file)
@@ -19,5 +19,6 @@ global:
         _nss_systemd_endgrent;
         _nss_systemd_setgrent;
         _nss_systemd_getgrent_r;
+        _nss_systemd_initgroups_dyn;
 local: *;
 };
diff --git a/src/nss-systemd/userdb-glue.c b/src/nss-systemd/userdb-glue.c
new file mode 100644 (file)
index 0000000..81705fa
--- /dev/null
@@ -0,0 +1,344 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "env-util.h"
+#include "fd-util.h"
+#include "group-record-nss.h"
+#include "strv.h"
+#include "user-record.h"
+#include "userdb-glue.h"
+#include "userdb.h"
+
+UserDBFlags nss_glue_userdb_flags(void) {
+        UserDBFlags flags = USERDB_AVOID_NSS;
+
+        /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */
+        if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
+                flags |= USERDB_AVOID_DYNAMIC_USER;
+
+        return flags;
+}
+
+int nss_pack_user_record(
+                UserRecord *hr,
+                struct passwd *pwd,
+                char *buffer,
+                size_t buflen) {
+
+        const char *rn, *hd, *shell;
+        size_t required;
+
+        assert(hr);
+        assert(pwd);
+
+        assert_se(hr->user_name);
+        required = strlen(hr->user_name) + 1;
+
+        assert_se(rn = user_record_real_name(hr));
+        required += strlen(rn) + 1;
+
+        assert_se(hd = user_record_home_directory(hr));
+        required += strlen(hd) + 1;
+
+        assert_se(shell = user_record_shell(hr));
+        required += strlen(shell) + 1;
+
+        if (buflen < required)
+                return -ERANGE;
+
+        *pwd = (struct passwd) {
+                .pw_name = buffer,
+                .pw_uid = hr->uid,
+                .pw_gid = user_record_gid(hr),
+                .pw_passwd = (char*) "x", /* means: see shadow file */
+        };
+
+        assert(buffer);
+
+        pwd->pw_gecos = stpcpy(pwd->pw_name, hr->user_name) + 1;
+        pwd->pw_dir = stpcpy(pwd->pw_gecos, rn) + 1;
+        pwd->pw_shell = stpcpy(pwd->pw_dir, hd) + 1;
+        strcpy(pwd->pw_shell, shell);
+
+        return 0;
+}
+
+enum nss_status userdb_getpwnam(
+                const char *name,
+                struct passwd *pwd,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        int r;
+
+        assert(pwd);
+        assert(errnop);
+
+        r = userdb_nss_compat_is_enabled();
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+        if (!r)
+                return NSS_STATUS_NOTFOUND;
+
+        r = userdb_by_name(name, nss_glue_userdb_flags(), &hr);
+        if (r == -ESRCH)
+                return NSS_STATUS_NOTFOUND;
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        r = nss_pack_user_record(hr, pwd, buffer, buflen);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status userdb_getpwuid(
+                uid_t uid,
+                struct passwd *pwd,
+                char *buffer,
+                size_t buflen,
+                int *errnop) {
+
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        int r;
+
+        assert(pwd);
+        assert(errnop);
+
+        r = userdb_nss_compat_is_enabled();
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+        if (!r)
+                return NSS_STATUS_NOTFOUND;
+
+        r = userdb_by_uid(uid, nss_glue_userdb_flags(), &hr);
+        if (r == -ESRCH)
+                return NSS_STATUS_NOTFOUND;
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        r = nss_pack_user_record(hr, pwd, buffer, buflen);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
+
+int nss_pack_group_record(
+                GroupRecord *g,
+                char **extra_members,
+                struct group *gr,
+                char *buffer,
+                size_t buflen) {
+
+        char **array = NULL, *p, **m;
+        size_t required, n = 0, i = 0;
+
+        assert(g);
+        assert(gr);
+
+        assert_se(g->group_name);
+        required = strlen(g->group_name) + 1;
+
+        STRV_FOREACH(m, g->members) {
+                required += sizeof(char*);  /* space for ptr array entry */
+                required += strlen(*m) + 1;
+                n++;
+        }
+        STRV_FOREACH(m, extra_members) {
+                if (strv_contains(g->members, *m))
+                        continue;
+
+                required += sizeof(char*);
+                required += strlen(*m) + 1;
+                n++;
+        }
+
+        required += sizeof(char*); /* trailing NULL in ptr array entry */
+
+        if (buflen < required)
+                return -ERANGE;
+
+        array = (char**) buffer; /* place ptr array at beginning of buffer, under assumption buffer is aligned */
+        p = buffer + sizeof(void*) * (n + 1); /* place member strings right after the ptr array */
+
+        STRV_FOREACH(m, g->members) {
+                array[i++] = p;
+                p = stpcpy(p, *m) + 1;
+        }
+        STRV_FOREACH(m, extra_members) {
+                if (strv_contains(g->members, *m))
+                        continue;
+
+                array[i++] = p;
+                p = stpcpy(p, *m) + 1;
+        }
+
+        assert_se(i == n);
+        array[n] = NULL;
+
+        *gr = (struct group) {
+                .gr_name = strcpy(p, g->group_name),
+                .gr_gid = g->gid,
+                .gr_passwd = (char*) "x", /* means: see shadow file */
+                .gr_mem = array,
+        };
+
+        return 0;
+}
+
+enum nss_status userdb_getgrnam(
+                const char *name,
+                struct group *gr,
+                char *buffer,
+                size_t buflen,
+                int *errnop) {
+
+        _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
+        _cleanup_strv_free_ char **members = NULL;
+        int r;
+
+        assert(gr);
+        assert(errnop);
+
+        r = userdb_nss_compat_is_enabled();
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+        if (!r)
+                return NSS_STATUS_NOTFOUND;
+
+        r = groupdb_by_name(name, nss_glue_userdb_flags(), &g);
+        if (r < 0 && r != -ESRCH) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        r = membershipdb_by_group_strv(name, nss_glue_userdb_flags(), &members);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        if (!g) {
+                _cleanup_close_ int lock_fd = -1;
+
+                if (strv_isempty(members))
+                        return NSS_STATUS_NOTFOUND;
+
+                /* Grmbl, so we are supposed to extend a group entry, but the group entry itself is not
+                 * accessible via non-NSS. Hence let's do what we have to do, and query NSS after all to
+                 * acquire it, so that we can extend it (that's because glibc's group merging feature will
+                 * merge groups only if both GID and name match and thus we need to have both first). It
+                 * sucks behaving recursively likely this, but it's apparently what everybody does. We break
+                 * the recursion for ourselves via the userdb_nss_compat_disable() lock. */
+
+                lock_fd = userdb_nss_compat_disable();
+                if (lock_fd < 0 && lock_fd != -EBUSY)
+                        return lock_fd;
+
+                r = nss_group_record_by_name(name, &g);
+                if (r == -ESRCH)
+                        return NSS_STATUS_NOTFOUND;
+                if (r < 0) {
+                        *errnop = -r;
+                        return NSS_STATUS_UNAVAIL;
+                }
+        }
+
+        r = nss_pack_group_record(g, members, gr, buffer, buflen);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status userdb_getgrgid(
+                gid_t gid,
+                struct group *gr,
+                char *buffer,
+                size_t buflen,
+                int *errnop) {
+
+
+        _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
+        _cleanup_strv_free_ char **members = NULL;
+        bool from_nss;
+        int r;
+
+        assert(gr);
+        assert(errnop);
+
+        r = userdb_nss_compat_is_enabled();
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+        if (r)
+                return NSS_STATUS_NOTFOUND;
+
+        r = groupdb_by_gid(gid, nss_glue_userdb_flags(), &g);
+        if (r < 0 && r != -ESRCH) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        if (!g) {
+                _cleanup_close_ int lock_fd = -1;
+
+                /* So, quite possibly we have to extend an existing group record with additional members. But
+                 * to do this we need to know the group name first. The group didn't exist via non-NSS
+                 * queries though, hence let's try to acquire it here recursively via NSS. */
+
+                lock_fd = userdb_nss_compat_disable();
+                if (lock_fd < 0 && lock_fd != -EBUSY)
+                        return lock_fd;
+
+                r = nss_group_record_by_gid(gid, &g);
+                if (r == -ESRCH)
+                        return NSS_STATUS_NOTFOUND;
+
+                if (r < 0) {
+                        *errnop = -r;
+                        return NSS_STATUS_UNAVAIL;
+                }
+
+                from_nss = true;
+        } else
+                from_nss = false;
+
+        r = membershipdb_by_group_strv(g->group_name, nss_glue_userdb_flags(), &members);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        /* If we acquired the record via NSS then there's no reason to respond unless we have to agument the
+         * list of members of the group */
+        if (from_nss && strv_isempty(members))
+                return NSS_STATUS_NOTFOUND;
+
+        r = nss_pack_group_record(g, members, gr, buffer, buflen);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
diff --git a/src/nss-systemd/userdb-glue.h b/src/nss-systemd/userdb-glue.h
new file mode 100644 (file)
index 0000000..02add24
--- /dev/null
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <nss.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+
+#include "userdb.h"
+
+UserDBFlags nss_glue_userdb_flags(void);
+
+int nss_pack_user_record(UserRecord *hr, struct passwd *pwd, char *buffer, size_t buflen);
+int nss_pack_group_record(GroupRecord *g, char **extra_members, struct group *gr, char *buffer, size_t buflen);
+
+enum nss_status userdb_getpwnam(const char *name, struct passwd *pwd, char *buffer, size_t buflen, int *errnop);
+enum nss_status userdb_getpwuid(uid_t uid, struct passwd *pwd, char *buffer, size_t buflen, int *errnop);
+
+enum nss_status userdb_getgrnam(const char *name, struct group *gr, char *buffer, size_t buflen, int *errnop);
+enum nss_status userdb_getgrgid(gid_t gid, struct group *gr, char *buffer, size_t buflen, int *errnop);