summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--factory/etc/nsswitch.conf2
-rw-r--r--meson.build9
-rw-r--r--src/nss-systemd/nss-systemd.c942
-rw-r--r--src/nss-systemd/nss-systemd.sym1
-rw-r--r--src/nss-systemd/userdb-glue.c344
-rw-r--r--src/nss-systemd/userdb-glue.h20
6 files changed, 749 insertions, 569 deletions
diff --git a/factory/etc/nsswitch.conf b/factory/etc/nsswitch.conf
index 5470993e34..e7365cd142 100644
--- a/factory/etc/nsswitch.conf
+++ b/factory/etc/nsswitch.conf
@@ -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
diff --git a/meson.build b/meson.build
index 6837fb9271..46f3e9c7ba 100644
--- a/meson.build
+++ b/meson.build
@@ -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,
diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c
index 8ef1cd5ea9..34f886a8cc 100644
--- a/src/nss-systemd/nss-systemd.c
+++ b/src/nss-systemd/nss-systemd.c
@@ -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;
}
diff --git a/src/nss-systemd/nss-systemd.sym b/src/nss-systemd/nss-systemd.sym
index ff63382b15..77e1fbe93f 100644
--- a/src/nss-systemd/nss-systemd.sym
+++ b/src/nss-systemd/nss-systemd.sym
@@ -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
index 0000000000..81705fa1b6
--- /dev/null
+++ b/src/nss-systemd/userdb-glue.c
@@ -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
index 0000000000..02add24b6b
--- /dev/null
+++ b/src/nss-systemd/userdb-glue.h
@@ -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);