summaryrefslogtreecommitdiffstats
path: root/src/nss-systemd
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/nss-systemd/nss-systemd.c1084
-rw-r--r--src/nss-systemd/nss-systemd.h13
-rw-r--r--src/nss-systemd/nss-systemd.sym36
-rw-r--r--src/nss-systemd/userdb-glue.c478
-rw-r--r--src/nss-systemd/userdb-glue.h27
5 files changed, 1638 insertions, 0 deletions
diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c
new file mode 100644
index 0000000..1d6e253
--- /dev/null
+++ b/src/nss-systemd/nss-systemd.c
@@ -0,0 +1,1084 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <nss.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "env-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing_threads.h"
+#include "nss-systemd.h"
+#include "nss-util.h"
+#include "pthread-util.h"
+#include "signal-util.h"
+#include "strv.h"
+#include "user-record-nss.h"
+#include "user-util.h"
+#include "userdb-glue.h"
+#include "userdb.h"
+
+static const struct passwd root_passwd = {
+ .pw_name = (char*) "root",
+ .pw_passwd = (char*) PASSWORD_SEE_SHADOW,
+ .pw_uid = 0,
+ .pw_gid = 0,
+ .pw_gecos = (char*) "Super User",
+ .pw_dir = (char*) "/root",
+ .pw_shell = NULL,
+};
+
+static const struct spwd root_spwd = {
+ .sp_namp = (char*) "root",
+ .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID,
+ .sp_lstchg = -1,
+ .sp_min = -1,
+ .sp_max = -1,
+ .sp_warn = -1,
+ .sp_inact = -1,
+ .sp_expire = -1,
+ .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
+};
+
+static const struct passwd nobody_passwd = {
+ .pw_name = (char*) NOBODY_USER_NAME,
+ .pw_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
+ .pw_uid = UID_NOBODY,
+ .pw_gid = GID_NOBODY,
+ .pw_gecos = (char*) "Kernel Overflow User",
+ .pw_dir = (char*) "/",
+ .pw_shell = (char*) NOLOGIN,
+};
+
+static const struct spwd nobody_spwd = {
+ .sp_namp = (char*) NOBODY_USER_NAME,
+ .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID,
+ .sp_lstchg = -1,
+ .sp_min = -1,
+ .sp_max = -1,
+ .sp_warn = -1,
+ .sp_inact = -1,
+ .sp_expire = -1,
+ .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
+};
+
+static const struct group root_group = {
+ .gr_name = (char*) "root",
+ .gr_gid = 0,
+ .gr_passwd = (char*) PASSWORD_SEE_SHADOW,
+ .gr_mem = (char*[]) { NULL },
+};
+
+static const struct sgrp root_sgrp = {
+ .sg_namp = (char*) "root",
+ .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
+};
+
+static const struct group nobody_group = {
+ .gr_name = (char*) NOBODY_GROUP_NAME,
+ .gr_gid = GID_NOBODY,
+ .gr_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
+ .gr_mem = (char*[]) { NULL },
+};
+
+static const struct sgrp nobody_sgrp = {
+ .sg_namp = (char*) NOBODY_GROUP_NAME,
+ .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
+};
+
+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;
+
+ /* Applies to group iterations only: true while we iterate over groups defined through NSS, false
+ * otherwise. */
+ bool by_membership;
+} GetentData;
+
+static GetentData getpwent_data = {
+ .mutex = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static GetentData getgrent_data = {
+ .mutex = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static GetentData getspent_data = {
+ .mutex = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static GetentData getsgent_data = {
+ .mutex = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static void setup_logging_once(void) {
+ static pthread_once_t once = PTHREAD_ONCE_INIT;
+ assert_se(pthread_once(&once, log_parse_environment_variables) == 0);
+}
+
+#define NSS_ENTRYPOINT_BEGIN \
+ BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); \
+ setup_logging_once()
+
+NSS_GETPW_PROTOTYPES(systemd);
+NSS_GETSP_PROTOTYPES(systemd);
+NSS_GETGR_PROTOTYPES(systemd);
+NSS_GETSG_PROTOTYPES(systemd);
+NSS_PWENT_PROTOTYPES(systemd);
+NSS_SPENT_PROTOTYPES(systemd);
+NSS_GRENT_PROTOTYPES(systemd);
+NSS_SGENT_PROTOTYPES(systemd);
+NSS_INITGROUPS_PROTOTYPE(systemd);
+
+/* Since our NSS functions implement reentrant glibc APIs, we have to guarantee
+ * all the string pointers we return point into the buffer provided by the
+ * caller, not into our own static memory. */
+
+static enum nss_status copy_synthesized_passwd(
+ struct passwd *dest,
+ const struct passwd *src,
+ const char *fallback_shell,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ assert(dest);
+ assert(src);
+ assert(src->pw_name);
+ assert(src->pw_passwd);
+ assert(src->pw_gecos);
+ assert(src->pw_dir);
+
+ const char *shell = ASSERT_PTR(src->pw_shell ?: fallback_shell);
+
+ size_t required =
+ strlen(src->pw_name) + 1 +
+ strlen(src->pw_passwd) + 1 +
+ strlen(src->pw_gecos) + 1 +
+ strlen(src->pw_dir) + 1 +
+ strlen(shell) + 1;
+
+ if (buflen < required) {
+ *errnop = ERANGE;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ assert(buffer);
+
+ *dest = *src;
+
+ /* String fields point into the user-provided buffer */
+ dest->pw_name = buffer;
+ dest->pw_passwd = stpcpy(dest->pw_name, src->pw_name) + 1;
+ dest->pw_gecos = stpcpy(dest->pw_passwd, src->pw_passwd) + 1;
+ dest->pw_dir = stpcpy(dest->pw_gecos, src->pw_gecos) + 1;
+ dest->pw_shell = stpcpy(dest->pw_dir, src->pw_dir) + 1;
+ strcpy(dest->pw_shell, shell);
+
+ return NSS_STATUS_SUCCESS;
+}
+
+static enum nss_status copy_synthesized_spwd(
+ struct spwd *dest,
+ const struct spwd *src,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ assert(dest);
+ assert(src);
+ assert(src->sp_namp);
+ assert(src->sp_pwdp);
+
+ size_t required =
+ strlen(src->sp_namp) + 1 +
+ strlen(src->sp_pwdp) + 1;
+
+ if (buflen < required) {
+ *errnop = ERANGE;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ assert(buffer);
+
+ *dest = *src;
+
+ /* String fields point into the user-provided buffer */
+ dest->sp_namp = buffer;
+ dest->sp_pwdp = stpcpy(dest->sp_namp, src->sp_namp) + 1;
+ strcpy(dest->sp_pwdp, src->sp_pwdp);
+
+ return NSS_STATUS_SUCCESS;
+}
+
+static enum nss_status copy_synthesized_group(
+ struct group *dest,
+ const struct group *src,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ assert(dest);
+ assert(src);
+ assert(src->gr_name);
+ assert(src->gr_passwd);
+ assert(src->gr_mem);
+ assert(!*src->gr_mem); /* Our synthesized records' gr_mem is always just NULL... */
+
+ size_t required =
+ strlen(src->gr_name) + 1 +
+ strlen(src->gr_passwd) + 1 +
+ sizeof(char*); /* ...but that NULL still needs to be stored into the buffer! */
+
+ if (buflen < ALIGN(required)) {
+ *errnop = ERANGE;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ assert(buffer);
+
+ *dest = *src;
+
+ /* String fields point into the user-provided buffer */
+ dest->gr_name = buffer;
+ dest->gr_passwd = stpcpy(dest->gr_name, src->gr_name) + 1;
+ dest->gr_mem = ALIGN_PTR(stpcpy(dest->gr_passwd, src->gr_passwd) + 1);
+ *dest->gr_mem = NULL;
+
+ return NSS_STATUS_SUCCESS;
+}
+
+static enum nss_status copy_synthesized_sgrp(
+ struct sgrp *dest,
+ const struct sgrp *src,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ assert(dest);
+ assert(src);
+ assert(src->sg_namp);
+ assert(src->sg_passwd);
+
+ size_t required =
+ strlen(src->sg_namp) + 1 +
+ strlen(src->sg_passwd) + 1;
+
+ if (buflen < required) {
+ *errnop = ERANGE;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ assert(buffer);
+
+ *dest = *src;
+
+ /* String fields point into the user-provided buffer */
+ dest->sg_namp = buffer;
+ dest->sg_passwd = stpcpy(dest->sg_namp, src->sg_namp) + 1;
+ strcpy(dest->sg_passwd, src->sg_passwd);
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_getpwnam_r(
+ const char *name,
+ struct passwd *pwd,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ enum nss_status status;
+ int e;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ 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 (!valid_user_group_name(name, VALID_USER_RELAX))
+ 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))
+ return copy_synthesized_passwd(pwd, &root_passwd,
+ default_root_shell(NULL),
+ buffer, buflen, errnop);
+
+ if (streq(name, nobody_passwd.pw_name)) {
+ if (!synthesize_nobody())
+ return NSS_STATUS_NOTFOUND;
+
+ return copy_synthesized_passwd(pwd, &nobody_passwd,
+ NULL,
+ buffer, buflen, errnop);
+ }
+
+ } else if (STR_IN_SET(name, root_passwd.pw_name, nobody_passwd.pw_name))
+ return NSS_STATUS_NOTFOUND;
+
+ status = userdb_getpwnam(name, pwd, buffer, buflen, &e);
+ if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+ UNPROTECT_ERRNO;
+ *errnop = e;
+ return status;
+ }
+
+ return status;
+}
+
+enum nss_status _nss_systemd_getpwuid_r(
+ uid_t uid,
+ struct passwd *pwd,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ enum nss_status status;
+ int e;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ 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)
+ return copy_synthesized_passwd(pwd, &root_passwd,
+ default_root_shell(NULL),
+ buffer, buflen, errnop);
+
+ if (uid == nobody_passwd.pw_uid) {
+ if (!synthesize_nobody())
+ return NSS_STATUS_NOTFOUND;
+
+ return copy_synthesized_passwd(pwd, &nobody_passwd,
+ NULL,
+ buffer, buflen, errnop);
+ }
+
+ } else if (uid == root_passwd.pw_uid || uid == nobody_passwd.pw_uid)
+ return NSS_STATUS_NOTFOUND;
+
+ status = userdb_getpwuid(uid, pwd, buffer, buflen, &e);
+ if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+ UNPROTECT_ERRNO;
+ *errnop = e;
+ return status;
+ }
+
+ return status;
+}
+
+enum nss_status _nss_systemd_getspnam_r(
+ const char *name,
+ struct spwd *spwd,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ enum nss_status status;
+ int e;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(name);
+ assert(spwd);
+ assert(errnop);
+
+ if (!valid_user_group_name(name, VALID_USER_RELAX))
+ 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_spwd.sp_namp))
+ return copy_synthesized_spwd(spwd, &root_spwd, buffer, buflen, errnop);
+
+ if (streq(name, nobody_spwd.sp_namp)) {
+ if (!synthesize_nobody())
+ return NSS_STATUS_NOTFOUND;
+
+ return copy_synthesized_spwd(spwd, &nobody_spwd, buffer, buflen, errnop);
+ }
+
+ } else if (STR_IN_SET(name, root_spwd.sp_namp, nobody_spwd.sp_namp))
+ return NSS_STATUS_NOTFOUND;
+
+ status = userdb_getspnam(name, spwd, buffer, buflen, &e);
+ if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+ UNPROTECT_ERRNO;
+ *errnop = e;
+ return status;
+ }
+
+ return status;
+}
+
+#pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
+
+enum nss_status _nss_systemd_getgrnam_r(
+ const char *name,
+ struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ enum nss_status status;
+ int e;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(name);
+ assert(gr);
+ assert(errnop);
+
+ if (!valid_user_group_name(name, VALID_USER_RELAX))
+ 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 (streq(name, root_group.gr_name))
+ return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop);
+
+ if (streq(name, nobody_group.gr_name)) {
+ if (!synthesize_nobody())
+ return NSS_STATUS_NOTFOUND;
+
+ return copy_synthesized_group(gr, &nobody_group, buffer, buflen, errnop);
+ }
+
+ } else if (STR_IN_SET(name, root_group.gr_name, nobody_group.gr_name))
+ return NSS_STATUS_NOTFOUND;
+
+ status = userdb_getgrnam(name, gr, buffer, buflen, &e);
+ if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+ UNPROTECT_ERRNO;
+ *errnop = e;
+ return status;
+ }
+
+ return status;
+}
+
+enum nss_status _nss_systemd_getgrgid_r(
+ gid_t gid,
+ struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ enum nss_status status;
+ int e;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ 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)
+ return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop);
+
+ if (gid == nobody_group.gr_gid) {
+ if (!synthesize_nobody())
+ return NSS_STATUS_NOTFOUND;
+
+ return copy_synthesized_group(gr, &nobody_group, buffer, buflen, errnop);
+ }
+
+ } else if (gid == root_group.gr_gid || gid == nobody_group.gr_gid)
+ return NSS_STATUS_NOTFOUND;
+
+ status = userdb_getgrgid(gid, gr, buffer, buflen, &e);
+ if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+ UNPROTECT_ERRNO;
+ *errnop = e;
+ return status;
+ }
+
+ return status;
+}
+
+enum nss_status _nss_systemd_getsgnam_r(
+ const char *name,
+ struct sgrp *sgrp,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ enum nss_status status;
+ int e;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(name);
+ assert(sgrp);
+ assert(errnop);
+
+ if (!valid_user_group_name(name, VALID_USER_RELAX))
+ 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 (streq(name, root_sgrp.sg_namp))
+ return copy_synthesized_sgrp(sgrp, &root_sgrp, buffer, buflen, errnop);
+
+ if (streq(name, nobody_sgrp.sg_namp)) {
+ if (!synthesize_nobody())
+ return NSS_STATUS_NOTFOUND;
+
+ return copy_synthesized_sgrp(sgrp, &nobody_sgrp, buffer, buflen, errnop);
+ }
+
+ } else if (STR_IN_SET(name, root_sgrp.sg_namp, nobody_sgrp.sg_namp))
+ return NSS_STATUS_NOTFOUND;
+
+ status = userdb_getsgnam(name, sgrp, buffer, buflen, &e);
+ if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+ UNPROTECT_ERRNO;
+ *errnop = e;
+ return status;
+ }
+
+ return status;
+}
+
+static enum nss_status nss_systemd_endent(GetentData *p) {
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(p);
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&p->mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ p->iterator = userdb_iterator_free(p->iterator);
+ p->by_membership = false;
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_endpwent(void) {
+ return nss_systemd_endent(&getpwent_data);
+}
+
+enum nss_status _nss_systemd_endspent(void) {
+ return nss_systemd_endent(&getspent_data);
+}
+
+enum nss_status _nss_systemd_endgrent(void) {
+ return nss_systemd_endent(&getgrent_data);
+}
+
+enum nss_status _nss_systemd_endsgent(void) {
+ return nss_systemd_endent(&getsgent_data);
+}
+
+enum nss_status _nss_systemd_setpwent(int stayopen) {
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getpwent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ getpwent_data.iterator = userdb_iterator_free(getpwent_data.iterator);
+ getpwent_data.by_membership = false;
+
+ /* Don't synthesize root/nobody when iterating. Let nss-files take care of that. If the two records
+ * are missing there, then that's fine, after all getpwent() is known to be possibly incomplete
+ * (think: LDAP/NIS type situations), and our synthesizing of root/nobody is a robustness fallback
+ * only, which matters for getpwnam()/getpwuid() primarily, which are the main NSS entrypoints to the
+ * user database. */
+ r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getpwent_data.iterator);
+ return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_setgrent(int stayopen) {
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getgrent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
+ getgrent_data.by_membership = false;
+
+ /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
+ r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getgrent_data.iterator);
+ return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_setspent(int stayopen) {
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getspent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ getspent_data.iterator = userdb_iterator_free(getspent_data.iterator);
+ getspent_data.by_membership = false;
+
+ /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
+ r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getspent_data.iterator);
+ return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_setsgent(int stayopen) {
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getsgent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ getsgent_data.iterator = userdb_iterator_free(getsgent_data.iterator);
+ getsgent_data.by_membership = false;
+
+ /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
+ r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getsgent_data.iterator);
+ return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_getpwent_r(
+ struct passwd *result,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(result);
+ assert(errnop);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getpwent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ if (!getpwent_data.iterator) {
+ UNPROTECT_ERRNO;
+ *errnop = EHOSTDOWN;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ r = userdb_iterator_get(getpwent_data.iterator, &ur);
+ if (r == -ESRCH)
+ return NSS_STATUS_NOTFOUND;
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ r = nss_pack_user_record(ur, result, buffer, buflen);
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_getgrent_r(
+ struct group *result,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
+ _cleanup_free_ char **members = NULL;
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(result);
+ assert(errnop);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getgrent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ if (!getgrent_data.iterator) {
+ UNPROTECT_ERRNO;
+ *errnop = EHOSTDOWN;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ 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 && r != -ESRCH) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ getgrent_data.by_membership = true;
+ } else if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ } 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 && r != -ESRCH) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+ }
+ }
+
+ if (getgrent_data.by_membership) {
+ _cleanup_(_nss_systemd_unblockp) bool blocked = false;
+
+ if (!getgrent_data.iterator)
+ return NSS_STATUS_NOTFOUND;
+
+ for (;;) {
+ _cleanup_free_ char *user_name = NULL, *group_name = NULL;
+
+ r = membershipdb_iterator_get(getgrent_data.iterator, &user_name, &group_name);
+ if (r == -ESRCH)
+ return NSS_STATUS_NOTFOUND;
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ 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 (!blocked) {
+ r = _nss_systemd_block(true);
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ blocked = true;
+ }
+
+ r = nss_group_record_by_name(group_name, false, &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;
+ }
+ }
+
+ r = nss_pack_group_record(gr, members, result, buffer, buflen);
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_getspent_r(
+ struct spwd *result,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(result);
+ assert(errnop);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getspent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ if (!getspent_data.iterator) {
+ UNPROTECT_ERRNO;
+ *errnop = EHOSTDOWN;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ for (;;) {
+ r = userdb_iterator_get(getspent_data.iterator, &ur);
+ if (r == -ESRCH)
+ return NSS_STATUS_NOTFOUND;
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (!ur->incomplete) /* don't synthesize shadow records for records where we couldn't read shadow data */
+ break;
+
+ ur = user_record_unref(ur);
+ }
+
+ r = nss_pack_user_record_shadow(ur, result, buffer, buflen);
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_getsgent_r(
+ struct sgrp *result,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
+ int r;
+
+ PROTECT_ERRNO;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(result);
+ assert(errnop);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getsgent_data.mutex);
+ (void) _l; /* make llvm shut up about _l not being used. */
+
+ if (!getsgent_data.iterator) {
+ UNPROTECT_ERRNO;
+ *errnop = EHOSTDOWN;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ for (;;) {
+ r = groupdb_iterator_get(getsgent_data.iterator, &gr);
+ if (r == -ESRCH)
+ return NSS_STATUS_NOTFOUND;
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (!gr->incomplete) /* don't synthesize shadow records for records where we couldn't read shadow data */
+ break;
+
+ gr = group_record_unref(gr);
+ }
+
+ r = nss_pack_group_record_shadow(gr, result, buffer, buflen);
+ if (r < 0) {
+ UNPROTECT_ERRNO;
+ *errnop = -r;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+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;
+ NSS_ENTRYPOINT_BEGIN;
+
+ assert(user_name);
+ assert(start);
+ assert(size);
+ assert(groupsp);
+ assert(errnop);
+
+ if (!valid_user_group_name(user_name, VALID_USER_RELAX))
+ 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;
+
+ if (_nss_systemd_is_blocked())
+ 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;
+ }
+
+ 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 = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ /* 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. */
+
+ r = groupdb_by_name(group_name, (nss_glue_userdb_flags() & ~USERDB_EXCLUDE_NSS) | USERDB_SUPPRESS_SHADOW, &g);
+ if (r == -ESRCH)
+ continue;
+ if (r < 0) {
+ log_debug_errno(r, "Failed to resolve group '%s', ignoring: %m", group_name);
+ continue;
+ }
+
+ if (g->gid == gid)
+ continue;
+
+ 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 = reallocarray(*groupsp, new_size, sizeof(**groupsp));
+ if (!new_groups) {
+ UNPROTECT_ERRNO;
+ *errnop = ENOMEM;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ *groupsp = new_groups;
+ *size = new_size;
+ }
+
+ (*groupsp)[(*start)++] = g->gid;
+ any = true;
+ }
+
+ return any ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND;
+}
+
+static thread_local unsigned _blocked = 0;
+
+_public_ int _nss_systemd_block(bool b) {
+
+ /* This blocks recursively: it's blocked for as many times this function is called with `true` until
+ * it is called an equal time with `false`. */
+
+ if (b) {
+ if (_blocked >= UINT_MAX)
+ return -EOVERFLOW;
+
+ _blocked++;
+ } else {
+ if (_blocked <= 0)
+ return -EOVERFLOW;
+
+ _blocked--;
+ }
+
+ return b; /* Return what is passed in, i.e. the new state from the PoV of the caller */
+}
+
+_public_ bool _nss_systemd_is_blocked(void) {
+ return _blocked > 0;
+}
diff --git a/src/nss-systemd/nss-systemd.h b/src/nss-systemd/nss-systemd.h
new file mode 100644
index 0000000..e97b801
--- /dev/null
+++ b/src/nss-systemd/nss-systemd.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+int _nss_systemd_block(bool b);
+bool _nss_systemd_is_blocked(void);
+
+/* For use with the _cleanup_() macro */
+static inline void _nss_systemd_unblockp(bool *b) {
+ if (*b)
+ assert_se(_nss_systemd_block(false) >= 0);
+}
diff --git a/src/nss-systemd/nss-systemd.sym b/src/nss-systemd/nss-systemd.sym
new file mode 100644
index 0000000..5602f00
--- /dev/null
+++ b/src/nss-systemd/nss-systemd.sym
@@ -0,0 +1,36 @@
+/***
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+***/
+
+{
+global:
+ _nss_systemd_getpwnam_r;
+ _nss_systemd_getspnam_r;
+ _nss_systemd_getpwuid_r;
+ _nss_systemd_endpwent;
+ _nss_systemd_setpwent;
+ _nss_systemd_getpwent_r;
+ _nss_systemd_endspent;
+ _nss_systemd_setspent;
+ _nss_systemd_getspent_r;
+ _nss_systemd_getgrnam_r;
+ _nss_systemd_getsgnam_r;
+ _nss_systemd_getgrgid_r;
+ _nss_systemd_endgrent;
+ _nss_systemd_setgrent;
+ _nss_systemd_getgrent_r;
+ _nss_systemd_endsgent;
+ _nss_systemd_setsgent;
+ _nss_systemd_getsgent_r;
+ _nss_systemd_initgroups_dyn;
+
+ /* These two are not used by glibc, but can be used by apps to explicitly disable nss-systemd for the calling thread. */
+ _nss_systemd_block;
+ _nss_systemd_is_blocked;
+local: *;
+};
diff --git a/src/nss-systemd/userdb-glue.c b/src/nss-systemd/userdb-glue.c
new file mode 100644
index 0000000..c69667d
--- /dev/null
+++ b/src/nss-systemd/userdb-glue.c
@@ -0,0 +1,478 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "env-util.h"
+#include "fd-util.h"
+#include "nss-systemd.h"
+#include "strv.h"
+#include "user-record-nss.h"
+#include "user-record.h"
+#include "user-util.h"
+#include "userdb-glue.h"
+#include "userdb.h"
+
+UserDBFlags nss_glue_userdb_flags(void) {
+ UserDBFlags flags = USERDB_EXCLUDE_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_EXCLUDE_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(hr->user_name);
+ required = strlen(hr->user_name) + 1;
+
+ required += 2; /* strlen(PASSWORD_SEE_SHADOW) + 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),
+ };
+
+ assert(buffer);
+
+ pwd->pw_passwd = stpcpy(pwd->pw_name, hr->user_name) + 1;
+ pwd->pw_gecos = stpcpy(pwd->pw_passwd, PASSWORD_SEE_SHADOW) + 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);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ r = userdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &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);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ r = userdb_by_uid(uid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &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_user_record_shadow(
+ UserRecord *hr,
+ struct spwd *spwd,
+ char *buffer,
+ size_t buflen) {
+
+ const char *hashed;
+ size_t required;
+
+ assert(hr);
+ assert(spwd);
+
+ assert(hr->user_name);
+ required = strlen(hr->user_name) + 1;
+
+ assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
+ required += strlen(hashed) + 1;
+
+ if (buflen < required)
+ return -ERANGE;
+
+ *spwd = (struct spwd) {
+ .sp_namp = buffer,
+ .sp_lstchg = hr->last_password_change_usec == 0 ? 1 : /* map 0 to 1, since 0 means please change password on next login */
+ hr->last_password_change_usec == UINT64_MAX ? -1 :
+ (long int) (hr->last_password_change_usec / USEC_PER_DAY),
+ .sp_min = hr->password_change_min_usec != UINT64_MAX ? (long int) (hr->password_change_min_usec / USEC_PER_DAY) : -1,
+ .sp_max = hr->password_change_max_usec != UINT64_MAX ? (long int) (hr->password_change_max_usec / USEC_PER_DAY) : -1,
+ .sp_warn = hr->password_change_warn_usec != UINT64_MAX ? (long int) (hr->password_change_warn_usec / USEC_PER_DAY) : -1,
+ .sp_inact = hr->password_change_inactive_usec != UINT64_MAX ? (long int) (hr->password_change_inactive_usec / USEC_PER_DAY) : -1,
+ .sp_expire = hr->locked > 0 || hr->not_after_usec == 0 ? 1 : /* already expired/locked */
+ hr->not_after_usec == UINT64_MAX ? -1 :
+ (long int) (hr->not_after_usec / USEC_PER_DAY),
+ .sp_flag = ULONG_MAX,
+ };
+
+ assert(buffer);
+
+ spwd->sp_pwdp = stpcpy(spwd->sp_namp, hr->user_name) + 1;
+ strcpy(spwd->sp_pwdp, hashed);
+
+ return 0;
+}
+
+enum nss_status userdb_getspnam(
+ const char *name,
+ struct spwd *spwd,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+ int r;
+
+ assert(spwd);
+ assert(errnop);
+
+ if (_nss_systemd_is_blocked())
+ 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;
+ }
+
+ if (hr->incomplete) /* protected records missing? */
+ return NSS_STATUS_NOTFOUND;
+
+ r = nss_pack_user_record_shadow(hr, spwd, 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;
+ size_t required, n = 0, i = 0;
+
+ assert(g);
+ assert(gr);
+
+ assert(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*) PASSWORD_SEE_SHADOW,
+ .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);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ r = groupdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
+ if (r < 0 && r != -ESRCH) {
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ r = membershipdb_by_group_strv(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &members);
+ if (r < 0 && r != -ESRCH) {
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (!g) {
+ _unused_ _cleanup_(_nss_systemd_unblockp) bool blocked = false;
+
+ 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 _nss_systemd_block_nss() lock. */
+
+ r = _nss_systemd_block(true);
+ if (r < 0)
+ return r;
+
+ blocked = true;
+
+ r = nss_group_record_by_name(name, false, &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);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ r = groupdb_by_gid(gid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
+ if (r < 0 && r != -ESRCH) {
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (!g) {
+ _unused_ _cleanup_(_nss_systemd_unblockp) bool blocked = false;
+
+ /* 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. */
+
+ r = _nss_systemd_block(true);
+ if (r < 0)
+ return r;
+
+ blocked = true;
+
+ r = nss_group_record_by_gid(gid, false, &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()|USERDB_SUPPRESS_SHADOW, &members);
+ if (r < 0 && r != -ESRCH) {
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ /* If we acquired the record via NSS then there's no reason to respond unless we have to augment 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;
+}
+
+int nss_pack_group_record_shadow(
+ GroupRecord *hr,
+ struct sgrp *sgrp,
+ char *buffer,
+ size_t buflen) {
+
+ const char *hashed;
+ size_t required;
+
+ assert(hr);
+ assert(sgrp);
+
+ assert(hr->group_name);
+ required = strlen(hr->group_name) + 1;
+
+ assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
+ required += strlen(hashed) + 1;
+
+ if (buflen < required)
+ return -ERANGE;
+
+ *sgrp = (struct sgrp) {
+ .sg_namp = buffer,
+ };
+
+ assert(buffer);
+
+ sgrp->sg_passwd = stpcpy(sgrp->sg_namp, hr->group_name) + 1;
+ strcpy(sgrp->sg_passwd, hashed);
+
+ return 0;
+}
+
+enum nss_status userdb_getsgnam(
+ const char *name,
+ struct sgrp *sgrp,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(group_record_unrefp) GroupRecord *hr = NULL;
+ int r;
+
+ assert(sgrp);
+ assert(errnop);
+
+ if (_nss_systemd_is_blocked())
+ return NSS_STATUS_NOTFOUND;
+
+ r = groupdb_by_name(name, nss_glue_userdb_flags(), &hr);
+ if (r == -ESRCH)
+ return NSS_STATUS_NOTFOUND;
+ if (r < 0) {
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (hr->incomplete) /* protected records missing? */
+ return NSS_STATUS_NOTFOUND;
+
+ r = nss_pack_group_record_shadow(hr, sgrp, 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 0000000..386cc88
--- /dev/null
+++ b/src/nss-systemd/userdb-glue.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#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);
+
+int nss_pack_user_record_shadow(UserRecord *hr, struct spwd *spwd, char *buffer, size_t buflen);
+int nss_pack_group_record_shadow(GroupRecord *hr, struct sgrp *sgrp, 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_getspnam(const char *name, struct spwd *spwd, 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);
+
+enum nss_status userdb_getsgnam(const char *name, struct sgrp *sgrp, char *buffer, size_t buflen, int *errnop);