From f690366765c39b7177ef677cce8f6f62744e604b Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Tue, 17 Jul 2007 02:13:25 -0400 Subject: [PATCH] Initial release. Signed-off-by: Anders Kaseorg --- Makefile | 34 +++++ README | 10 ++ nonlocal-group.c | 341 ++++++++++++++++++++++++++++++++++++++++++++ nonlocal-passwd.c | 301 ++++++++++++++++++++++++++++++++++++++ nonlocal-shadow.c | 183 ++++++++++++++++++++++++ nonlocal.h | 5 + nsswitch-internal.h | 26 ++++ 7 files changed, 900 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 nonlocal-group.c create mode 100644 nonlocal-passwd.c create mode 100644 nonlocal-shadow.c create mode 100644 nonlocal.h create mode 100644 nsswitch-internal.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..58e185f --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +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 diff --git a/README b/README new file mode 100644 index 0000000..c0b0eee --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +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 diff --git a/nonlocal-group.c b/nonlocal-group.c new file mode 100644 index 0000000..9071fe1 --- /dev/null +++ b/nonlocal-group.c @@ -0,0 +1,341 @@ +/* + * nonlocal-group.c + * group database for nss_nonlocal proxy + * + * Copyright © 2007 Anders Kaseorg + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/nonlocal-passwd.c b/nonlocal-passwd.c new file mode 100644 index 0000000..cebc819 --- /dev/null +++ b/nonlocal-passwd.c @@ -0,0 +1,301 @@ +/* + * nonlocal-passwd.c + * passwd database for nss_nonlocal proxy. + * + * Copyright © 2007 Anders Kaseorg + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/nonlocal-shadow.c b/nonlocal-shadow.c new file mode 100644 index 0000000..6524ee8 --- /dev/null +++ b/nonlocal-shadow.c @@ -0,0 +1,183 @@ +/* + * nonlocal-shadow.c + * shadow database for nss_nonlocal proxy. + * + * Copyright © 2007 Anders Kaseorg + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/nonlocal.h b/nonlocal.h new file mode 100644 index 0000000..c583bf4 --- /dev/null +++ b/nonlocal.h @@ -0,0 +1,5 @@ +#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 */ diff --git a/nsswitch-internal.h b/nsswitch-internal.h new file mode 100644 index 0000000..94ed968 --- /dev/null +++ b/nsswitch-internal.h @@ -0,0 +1,26 @@ +/* + * 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 */ -- 2.44.0