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