blob: e87f1d31b3401e94ab141bb5c9ffdb91a8ebe2fc [file] [log] [blame]
/* 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 "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 = (char*) "/bin/sh",
};
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*) "User Nobody",
.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,
char *buffer, size_t buflen,
int *errnop) {
size_t required;
assert(dest);
assert(src);
assert(src->pw_name);
assert(src->pw_passwd);
assert(src->pw_gecos);
assert(src->pw_dir);
assert(src->pw_shell);
required = strlen(src->pw_name) + 1;
required += strlen(src->pw_passwd) + 1;
required += strlen(src->pw_gecos) + 1;
required += strlen(src->pw_dir) + 1;
required += strlen(src->pw_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, src->pw_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) {
size_t required;
assert(dest);
assert(src);
assert(src->sp_namp);
assert(src->sp_pwdp);
required = strlen(src->sp_namp) + 1;
required += 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) {
size_t required;
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... */
required = strlen(src->gr_name) + 1;
required += strlen(src->gr_passwd) + 1;
required += 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) {
size_t required;
assert(dest);
assert(src);
assert(src->sg_namp);
assert(src->sg_passwd);
required = strlen(src->sg_namp) + 1;
required += 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, buffer, buflen, errnop);
if (streq(name, nobody_passwd.pw_name)) {
if (!synthesize_nobody())
return NSS_STATUS_NOTFOUND;
return copy_synthesized_passwd(pwd, &nobody_passwd, 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, buffer, buflen, errnop);
if (uid == nobody_passwd.pw_uid) {
if (!synthesize_nobody())
return NSS_STATUS_NOTFOUND;
return copy_synthesized_passwd(pwd, &nobody_passwd, 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;
}