diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
commit | b750101eb236130cf056c675997decbac904cc49 (patch) | |
tree | a5df1a06754bdd014cb975c051c83b01c9a97532 /src/nss-systemd | |
parent | Initial commit. (diff) | |
download | systemd-b750101eb236130cf056c675997decbac904cc49.tar.xz systemd-b750101eb236130cf056c675997decbac904cc49.zip |
Adding upstream version 252.22.upstream/252.22
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/nss-systemd')
-rw-r--r-- | src/nss-systemd/nss-systemd.c | 1084 | ||||
-rw-r--r-- | src/nss-systemd/nss-systemd.h | 13 | ||||
-rw-r--r-- | src/nss-systemd/nss-systemd.sym | 36 | ||||
-rw-r--r-- | src/nss-systemd/userdb-glue.c | 478 | ||||
-rw-r--r-- | src/nss-systemd/userdb-glue.h | 27 |
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); |