--- /dev/null
+exec_prefix = /
+libdir = $(exec_prefix)/lib
+
+INSTALL = install
+CC = gcc
+CFLAGS = -O2 -Wall
+LD = ld
+
+ALL_CFLAGS = $(CFLAGS) -fPIC
+ALL_LDFLAGS = $(LDFLAGS) -shared -Wl,-x
+
+all: libnss_nonlocal.so.2 linktest
+
+libnss_nonlocal.so.2: nonlocal-passwd.o nonlocal-group.o nonlocal-shadow.o
+ $(CC) -o $@ $(ALL_LDFLAGS) -Wl,-soname,$@ $^ $(LOADLIBES) $(LDLIBS)
+
+%.o: %.c
+ $(CC) -c $(ALL_CFLAGS) $(CPPFLAGS) $<
+
+nonlocal-passwd.o: nonlocal-passwd.c nsswitch-internal.h nonlocal.h
+nonlocal-group.o: nonlocal-group.c nsswitch-internal.h nonlocal.h
+nonlocal-shadow.o: nonlocal-shadow.c nsswitch-internal.h nonlocal.h
+
+linktest: libnss_nonlocal.so.2
+ $(LD) --entry=0 -o /dev/null $^
+
+install: libnss_nonlocal.so.2
+ $(INSTALL) -d $(DESTDIR)$(libdir)
+ $(INSTALL) -m a+r,u+w $< $(DESTDIR)$(libdir)/
+
+clean:
+ rm -f *.so.* *.o test-nonlocal
+
+.PHONY: all linktest install clean
--- /dev/null
+This is nss_nonlocal, an nsswitch module that acts as a proxy for other
+nsswitch modules like hesiod, but prevents non-local users from
+potentially gaining local privileges by spoofing local UIDs and GIDs.
+
+To use it, configure /etc/nsswitch.conf as follows:
+
+passwd: compat nonlocal
+passwd_nonlocal: hesiod
+group: compat nonlocal
+group_nonlocal: hesiod
--- /dev/null
+/*
+ * nonlocal-group.c
+ * group database for nss_nonlocal proxy
+ *
+ * Copyright © 2007 Anders Kaseorg <andersk@mit.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <errno.h>
+#include <grp.h>
+#include <nss.h>
+#include "nsswitch-internal.h"
+#include "nonlocal.h"
+
+
+static service_user *
+nss_group_nonlocal_database(void)
+{
+ static service_user *nip = NULL;
+ if (nip == NULL)
+ __nss_database_lookup("group_nonlocal", NULL, "", &nip);
+
+ return nip;
+}
+
+
+static __thread int local_only = 0;
+
+enum nss_status
+local_getgrgid_r(gid_t gid, struct group *grp,
+ char *buffer, size_t buflen, int *errnop)
+{
+ int old_local_only = local_only;
+ int old_errno = errno;
+ int ret;
+ errno = *errnop;
+ local_only = 1;
+
+ ret = getgrgid_r(gid, grp, buffer, buflen, &grp);
+
+ local_only = old_local_only;
+ *errnop = errno;
+ errno = old_errno;
+
+ if (grp != NULL)
+ return NSS_STATUS_SUCCESS;
+ else if (ret == 0)
+ return NSS_STATUS_NOTFOUND;
+ else
+ return NSS_STATUS_TRYAGAIN;
+}
+
+enum nss_status
+check_nonlocal_gid(const char *user, gid_t gid, int *errnop)
+{
+ struct group local_grp;
+ int local_errno = errno;
+ enum nss_status local_status, status = NSS_STATUS_SUCCESS;
+ int local_buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
+ char *local_buffer = malloc(local_buflen);
+ if (local_buffer == NULL) {
+ *errnop = ENOMEM;
+ errno = local_errno;
+ return NSS_STATUS_TRYAGAIN;
+ }
+ local_errno = 0;
+ local_status = local_getgrgid_r(gid, &local_grp, local_buffer,
+ local_buflen, &local_errno);
+ if (local_status == NSS_STATUS_SUCCESS) {
+ syslog(LOG_WARNING, "nss_nonlocal: removing local group %u (%s) from non-local user %s\n", local_grp.gr_gid, local_grp.gr_name, user);
+ status = NSS_STATUS_NOTFOUND;
+ } else if (local_status != NSS_STATUS_NOTFOUND &&
+ local_status != NSS_STATUS_UNAVAIL) {
+ *errnop = local_errno;
+ status = local_status;
+ }
+ free(local_buffer);
+ return status;
+}
+
+
+static service_user *grent_nip = NULL;
+static void *grent_fct_start;
+static union {
+ enum nss_status (*l)(struct group *grp, char *buffer, size_t buflen,
+ int *errnop);
+ void *ptr;
+} grent_fct;
+static const char *grent_fct_name = "getgrent_r";
+
+enum nss_status
+_nss_nonlocal_setgrent(int stayopen)
+{
+ static const char *fct_name = "setgrent";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(int stayopen);
+ void *ptr;
+ } fct;
+
+ nip = nss_group_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (stayopen));
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ grent_nip = nip;
+ if (grent_fct_start == NULL)
+ grent_fct_start = __nss_lookup_function(nip, grent_fct_name);
+ grent_fct.ptr = grent_fct_start;
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status
+_nss_nonlocal_endgrent(void)
+{
+ static const char *fct_name = "endgrent";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(void);
+ void *ptr;
+ } fct;
+
+ grent_nip = NULL;
+
+ nip = nss_group_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, ());
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ return status;
+}
+
+enum nss_status
+_nss_nonlocal_getgrent_r(struct group *grp, char *buffer, size_t buflen,
+ int *errnop)
+{
+ enum nss_status status;
+ if (grent_nip == NULL) {
+ status = _nss_nonlocal_setgrent(0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+ }
+ do {
+ if (grent_fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else {
+ int nonlocal_errno;
+ do
+ status = DL_CALL_FCT(grent_fct.l, (grp, buffer, buflen, errnop));
+ while (status == NSS_STATUS_SUCCESS &&
+ check_nonlocal_gid("(unknown)", grp->gr_gid, &nonlocal_errno) != NSS_STATUS_SUCCESS);
+ }
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ return status;
+
+ if (status == NSS_STATUS_SUCCESS)
+ return NSS_STATUS_SUCCESS;
+ } while (__nss_next(&grent_nip, grent_fct_name, &grent_fct.ptr, status, 0) == 0);
+
+ grent_nip = NULL;
+ return NSS_STATUS_NOTFOUND;
+}
+
+
+enum nss_status
+_nss_nonlocal_getgrnam_r(const char *name, struct group *grp,
+ char *buffer, size_t buflen, int *errnop)
+{
+ static const char *fct_name = "getgrnam_r";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(const char *name, struct group *grp,
+ char *buffer, size_t buflen, int *errnop);
+ void *ptr;
+ } fct;
+
+ nip = nss_group_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (name, grp, buffer, buflen, errnop));
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ break;
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ return check_nonlocal_gid(name, grp->gr_gid, errnop);
+}
+
+enum nss_status
+_nss_nonlocal_getgrgid_r(gid_t gid, struct group *grp,
+ char *buffer, size_t buflen, int *errnop)
+{
+ static const char *fct_name = "getgrgid_r";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(gid_t gid, struct group *grp,
+ char *buffer, size_t buflen, int *errnop);
+ void *ptr;
+ } fct;
+
+ if (local_only == 1)
+ return NSS_STATUS_UNAVAIL;
+
+ nip = nss_group_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (gid, grp, buffer, buflen, errnop));
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ break;
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ return check_nonlocal_gid(grp->gr_name, grp->gr_gid, errnop);
+}
+
+enum nss_status
+_nss_nonlocal_initgroups_dyn(const char *user, gid_t group, long int *start,
+ long int *size, gid_t **groupsp, long int limit,
+ int *errnop)
+{
+ static const char *fct_name = "initgroups_dyn";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(const char *user, gid_t group, long int *start,
+ long int *size, gid_t **groupsp, long int limit,
+ int *errnop);
+ void *ptr;
+ } fct;
+ int in = *start, out = *start, i;
+
+ nip = nss_group_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (user, group, start, size, groupsp, limit, errnop));
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ break;
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ for (; in < *start; ++in) {
+ int nonlocal_errno = *errnop;
+
+ for (i = 0; i < out; ++i)
+ if ((*groupsp)[i] == (*groupsp)[in])
+ break;
+ if (i < out)
+ continue;
+
+ status = check_nonlocal_gid(user, (*groupsp)[in], &nonlocal_errno);
+ if (status == NSS_STATUS_SUCCESS) {
+ (*groupsp)[out++] = (*groupsp)[in];
+ } else if (status != NSS_STATUS_NOTFOUND) {
+ *start = out;
+ *errnop = nonlocal_errno;
+ return status;
+ }
+ }
+
+ *start = out;
+ return NSS_STATUS_SUCCESS;
+}
--- /dev/null
+/*
+ * nonlocal-passwd.c
+ * passwd database for nss_nonlocal proxy.
+ *
+ * Copyright © 2007 Anders Kaseorg <andersk@mit.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <nss.h>
+#include "nsswitch-internal.h"
+#include "nonlocal.h"
+
+
+static service_user *
+nss_passwd_nonlocal_database(void)
+{
+ static service_user *nip = NULL;
+ if (nip == NULL)
+ __nss_database_lookup("passwd_nonlocal", NULL, "", &nip);
+
+ return nip;
+}
+
+
+static __thread int local_only = 0;
+
+enum nss_status
+local_getpwuid_r(uid_t uid, struct passwd *pwd,
+ char *buffer, size_t buflen, int *errnop)
+{
+ int old_local_only = local_only;
+ int old_errno = errno;
+ int ret;
+ errno = *errnop;
+ local_only = 1;
+
+ ret = getpwuid_r(uid, pwd, buffer, buflen, &pwd);
+
+ local_only = old_local_only;
+ *errnop = errno;
+ errno = old_errno;
+
+ if (pwd != NULL)
+ return NSS_STATUS_SUCCESS;
+ else if (ret == 0)
+ return NSS_STATUS_NOTFOUND;
+ else
+ return NSS_STATUS_TRYAGAIN;
+}
+
+enum nss_status
+check_nonlocal_uid(const char *user, uid_t uid, int *errnop)
+{
+ struct passwd local_pwd;
+ int local_errno = errno;
+ enum nss_status local_status, status = NSS_STATUS_SUCCESS;
+ int local_buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ char *local_buffer = malloc(local_buflen);
+ if (local_buffer == NULL) {
+ *errnop = ENOMEM;
+ errno = local_errno;
+ return NSS_STATUS_TRYAGAIN;
+ }
+ local_errno = 0;
+ local_status = local_getpwuid_r(uid, &local_pwd, local_buffer,
+ local_buflen, &local_errno);
+ if (local_status == NSS_STATUS_SUCCESS) {
+ syslog(LOG_ERR, "nss_nonlocal: possible spoofing attack: non-local user %s has same UID as local user %s!\n", user, local_pwd.pw_name);
+ status = NSS_STATUS_NOTFOUND;
+ } else if (local_status != NSS_STATUS_NOTFOUND &&
+ local_status != NSS_STATUS_UNAVAIL) {
+ *errnop = local_errno;
+ status = local_status;
+ }
+ free(local_buffer);
+ return status;
+}
+
+
+static service_user *pwent_nip = NULL;
+static void *pwent_fct_start;
+static union {
+ enum nss_status (*l)(struct passwd *pwd, char *buffer, size_t buflen,
+ int *errnop);
+ void *ptr;
+} pwent_fct;
+static const char *pwent_fct_name = "getpwent_r";
+
+enum nss_status
+_nss_nonlocal_setpwent(int stayopen)
+{
+ static const char *fct_name = "setpwent";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(int stayopen);
+ void *ptr;
+ } fct;
+
+ nip = nss_passwd_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (stayopen));
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ pwent_nip = nip;
+ if (pwent_fct_start == NULL)
+ pwent_fct_start = __nss_lookup_function(nip, pwent_fct_name);
+ pwent_fct.ptr = pwent_fct_start;
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status
+_nss_nonlocal_endpwent(void)
+{
+ static const char *fct_name = "endpwent";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(void);
+ void *ptr;
+ } fct;
+
+ pwent_nip = NULL;
+
+ nip = nss_passwd_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, ());
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ return status;
+}
+
+enum nss_status
+_nss_nonlocal_getpwent_r(struct passwd *pwd, char *buffer, size_t buflen,
+ int *errnop)
+{
+ enum nss_status status;
+ if (pwent_nip == NULL) {
+ status = _nss_nonlocal_setpwent(0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+ }
+ do {
+ if (pwent_fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else {
+ int nonlocal_errno;
+ do
+ status = DL_CALL_FCT(pwent_fct.l, (pwd, buffer, buflen, errnop));
+ while (status == NSS_STATUS_SUCCESS &&
+ check_nonlocal_uid(pwd->pw_name, pwd->pw_uid, &nonlocal_errno) != NSS_STATUS_SUCCESS);
+ }
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ return status;
+
+ if (status == NSS_STATUS_SUCCESS)
+ return NSS_STATUS_SUCCESS;
+ } while (__nss_next(&pwent_nip, pwent_fct_name, &pwent_fct.ptr, status, 0) == 0);
+
+ pwent_nip = NULL;
+ return NSS_STATUS_NOTFOUND;
+}
+
+
+enum nss_status
+_nss_nonlocal_getpwnam_r(const char *name, struct passwd *pwd,
+ char *buffer, size_t buflen, int *errnop)
+{
+ static const char *fct_name = "getpwnam_r";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(const char *name, struct passwd *pwd,
+ char *buffer, size_t buflen, int *errnop);
+ void *ptr;
+ } fct;
+ int group_errno;
+
+ nip = nss_passwd_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (name, pwd, buffer, buflen, errnop));
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ break;
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ status = check_nonlocal_uid(name, pwd->pw_uid, errnop);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ if (check_nonlocal_gid(name, pwd->pw_gid, &group_errno) !=
+ NSS_STATUS_SUCCESS)
+ pwd->pw_gid = 65534 /* nogroup */;
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status
+_nss_nonlocal_getpwuid_r(uid_t uid, struct passwd *pwd,
+ char *buffer, size_t buflen, int *errnop)
+{
+ static const char *fct_name = "getpwuid_r";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(uid_t uid, struct passwd *pwd,
+ char *buffer, size_t buflen, int *errnop);
+ void *ptr;
+ } fct;
+ int group_errno;
+
+ if (local_only == 1)
+ return NSS_STATUS_UNAVAIL;
+
+ nip = nss_passwd_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (uid, pwd, buffer, buflen, errnop));
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ break;
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ status = check_nonlocal_uid(pwd->pw_name, pwd->pw_uid, errnop);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ if (check_nonlocal_gid(pwd->pw_name, pwd->pw_gid, &group_errno) !=
+ NSS_STATUS_SUCCESS)
+ pwd->pw_gid = 65534 /* nogroup */;
+ return NSS_STATUS_SUCCESS;
+}
--- /dev/null
+/*
+ * nonlocal-shadow.c
+ * shadow database for nss_nonlocal proxy.
+ *
+ * Copyright © 2007 Anders Kaseorg <andersk@mit.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <errno.h>
+#include <shadow.h>
+#include <nss.h>
+
+#include "nsswitch-internal.h"
+#include "nonlocal.h"
+
+
+static service_user *
+nss_shadow_nonlocal_database(void)
+{
+ static service_user *nip = NULL;
+ if (nip == NULL)
+ __nss_database_lookup("shadow_nonlocal", NULL, "", &nip);
+
+ return nip;
+}
+
+
+static service_user *spent_nip = NULL;
+static void *spent_fct_start;
+static union {
+ enum nss_status (*l)(struct spwd *pwd, char *buffer, size_t buflen,
+ int *errnop);
+ void *ptr;
+} spent_fct;
+static const char *spent_fct_name = "getspent_r";
+
+enum nss_status
+_nss_nonlocal_setspent(int stayopen)
+{
+ static const char *fct_name = "setspent";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(int stayopen);
+ void *ptr;
+ } fct;
+
+ nip = nss_shadow_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (stayopen));
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+
+ spent_nip = nip;
+ if (spent_fct_start == NULL)
+ spent_fct_start = __nss_lookup_function(nip, spent_fct_name);
+ spent_fct.ptr = spent_fct_start;
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status
+_nss_nonlocal_endspent(void)
+{
+ static const char *fct_name = "endspent";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(void);
+ void *ptr;
+ } fct;
+
+ spent_nip = NULL;
+
+ nip = nss_shadow_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, ());
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ return status;
+}
+
+enum nss_status
+_nss_nonlocal_getspent_r(struct spwd *pwd, char *buffer, size_t buflen,
+ int *errnop)
+{
+ enum nss_status status;
+ if (spent_nip == NULL) {
+ status = _nss_nonlocal_setspent(0);
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+ }
+ do {
+ if (spent_fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(spent_fct.l, (pwd, buffer, buflen, errnop));
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ return status;
+
+ if (status == NSS_STATUS_SUCCESS)
+ return NSS_STATUS_SUCCESS;
+ } while (__nss_next(&spent_nip, spent_fct_name, &spent_fct.ptr, status, 0) == 0);
+
+ spent_nip = NULL;
+ return NSS_STATUS_NOTFOUND;
+}
+
+
+enum nss_status
+_nss_nonlocal_getspnam_r(const char *name, struct spwd *pwd,
+ char *buffer, size_t buflen, int *errnop)
+{
+ static const char *fct_name = "getspnam_r";
+ static void *fct_start = NULL;
+ enum nss_status status;
+ service_user *nip;
+ union {
+ enum nss_status (*l)(const char *name, struct spwd *pwd,
+ char *buffer, size_t buflen, int *errnop);
+ void *ptr;
+ } fct;
+
+ nip = nss_shadow_nonlocal_database();
+ if (nip == NULL)
+ return NSS_STATUS_UNAVAIL;
+ if (fct_start == NULL)
+ fct_start = __nss_lookup_function(nip, fct_name);
+ fct.ptr = fct_start;
+ do {
+ if (fct.ptr == NULL)
+ status = NSS_STATUS_UNAVAIL;
+ else
+ status = DL_CALL_FCT(fct.l, (name, pwd, buffer, buflen, errnop));
+ if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
+ break;
+ } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
+ return status;
+}
--- /dev/null
+#ifndef NONLOCAL_H
+#define NONLOCAL_H
+enum nss_status check_nonlocal_uid(const char *user, uid_t uid, int *errnop);
+enum nss_status check_nonlocal_gid(const char *user, gid_t gid, int *errnop);
+#endif /* NON_LOCAL_H */
--- /dev/null
+/*
+ * nsswitch_internal.h
+ * Prototypes for some internal glibc functions that we use. Shhh.
+ */
+
+#ifndef NSSWITCH_INTERNAL_H
+#define NSSWITCH_INTERNAL_H
+
+typedef struct service_user service_user;
+
+extern int
+__nss_next (service_user **ni, const char *fct_name, void **fctp, int status,
+ int all_values);
+
+extern int
+__nss_database_lookup (const char *database,
+ const char *alternative_name,
+ const char *defconfig, service_user **ni);
+
+extern int
+__nss_configure_lookup (const char *dbname, const char *service_line);
+
+extern void
+*__nss_lookup_function (service_user *ni, const char *fct_name);
+
+#endif /* NSSWITCH_INTERNAL_H */