From: Paul Pluzhnikov Date: Fri, 28 Feb 2014 20:54:06 +0000 (-0800) Subject: Integrate nss_{borg,cache} local changes from glibc-2.18 to 2.19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eb0025167791355d35cd52776d3976db03e0530e;p=thirdparty%2Fglibc.git Integrate nss_{borg,cache} local changes from glibc-2.18 to 2.19 --- diff --git a/include/grp.h b/include/grp.h index 871701adbea..16dd8bd2e7f 100644 --- a/include/grp.h +++ b/include/grp.h @@ -53,6 +53,7 @@ extern enum nss_status _nss_ ## service ##_initgroups_dyn \ long int *size, gid_t **groupsp, long int limit, \ int *errnop); +DECLARE_NSS_PROTOTYPES (cache) DECLARE_NSS_PROTOTYPES (compat) DECLARE_NSS_PROTOTYPES (files) DECLARE_NSS_PROTOTYPES (hesiod) diff --git a/include/pwd.h b/include/pwd.h index fc995065d9b..de8272cdd96 100644 --- a/include/pwd.h +++ b/include/pwd.h @@ -45,8 +45,10 @@ extern enum nss_status _nss_ ## service ##_getpwent_r \ (struct passwd *result, char *buffer, \ size_t buflen, int *errnop); +DECLARE_NSS_PROTOTYPES (cache) DECLARE_NSS_PROTOTYPES (compat) DECLARE_NSS_PROTOTYPES (files) +DECLARE_NSS_PROTOTYPES (borg) DECLARE_NSS_PROTOTYPES (hesiod) DECLARE_NSS_PROTOTYPES (nis) DECLARE_NSS_PROTOTYPES (nisplus) diff --git a/include/shadow.h b/include/shadow.h index 366ea834829..3b7bf777d97 100644 --- a/include/shadow.h +++ b/include/shadow.h @@ -41,6 +41,7 @@ extern enum nss_status _nss_ ## service ## _getspnam_r \ (const char *name, struct spwd *pwd, \ char *buffer, size_t buflen, int *errnop); +DECLARE_NSS_PROTOTYPES (cache) DECLARE_NSS_PROTOTYPES (compat) DECLARE_NSS_PROTOTYPES (files) DECLARE_NSS_PROTOTYPES (hesiod) diff --git a/nss/Makefile b/nss/Makefile index a5cd2aacae8..375f060b65e 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -73,7 +73,7 @@ tests += tst-cancel-getpwuid_r endif # Specify rules for the nss_* modules. We have some services. -services := files db compat +services := files db compat borg cache extra-libs = $(services:%=libnss_%) # These libraries will be built in the `others' pass rather than @@ -87,6 +87,8 @@ vpath %.c $(subdir-dirs) ../locale/programs ../intl libnss_files-routines := $(addprefix files-,$(databases)) \ files-initgroups files-init +libnss_borg-routines := borg-pwd +libnss_cache-routines := nss_cache libnss_db-dbs := $(addprefix db-,\ $(filter-out hosts network key alias,\ @@ -104,10 +106,14 @@ install-others += $(inst_vardbdir)/Makefile # Build static module into libc if requested libnss_files-inhibit-o = $(filter-out .os,$(object-suffixes)) libnss_db-inhibit-o = $(filter-out .os,$(object-suffixes)) +libnss_borg-inhibit-o = $(filter-out .os,$(object-suffixes)) +libnss_cache-inhibit-o = $(filter-out .os,$(object-suffixes)) libnss_compat-inhibit-o = $(filter-out .os,$(object-suffixes)) ifeq ($(build-static-nss),yes) -routines += $(libnss_files-routines) -static-only-routines += $(libnss_files-routines) +routines += $(libnss_files-routines) $(libnss_borg-routines) \ + $(libnss_cache-routines) +static-only-routines += $(libnss_files-routines) $(libnss_borg-routines) \ + $(libnss_cache-routines) tests-static += tst-nss-static endif extra-test-objs += nss_test1.os nss_test2.os diff --git a/nss/Versions b/nss/Versions index db8c8877202..f472d64d593 100644 --- a/nss/Versions +++ b/nss/Versions @@ -174,3 +174,32 @@ libnss_compat { _nss_compat_initgroups_dyn; } } + +libnss_borg { + GLIBC_PRIVATE { + _nss_borg_setpwent; + _nss_borg_endpwent; + _nss_borg_getpwent_r; + _nss_borg_getpwnam_r; + _nss_borg_getpwuid_r; + } +} + +libnss_cache { + GLIBC_PRIVATE { + _nss_cache_setpwent; + _nss_cache_endpwent; + _nss_cache_getpwent_r; + _nss_cache_getpwuid_r; + _nss_cache_getpwnam_r; + _nss_cache_setgrent; + _nss_cache_endgrent; + _nss_cache_getgrent_r; + _nss_cache_getgrgid_r; + _nss_cache_getgrnam_r; + _nss_cache_setspent; + _nss_cache_endspent; + _nss_cache_getspent_r; + _nss_cache_getspnam_r; + } +} diff --git a/nss/function.def b/nss/function.def index 1a38d5a8528..6464a921e7e 100644 --- a/nss/function.def +++ b/nss/function.def @@ -31,6 +31,9 @@ DEFINE_ENT (files, ether) DEFINE_ENT (files, gr) DEFINE_GET (files, grgid) DEFINE_GET (files, grnam) +DEFINE_ENT (cache, gr) +DEFINE_GET (cache, grgid) +DEFINE_GET (cache, grnam) /* hosts */ DEFINE_ENT (files, host) @@ -62,6 +65,11 @@ DEFINE_GETBY (files, proto, number) DEFINE_ENT (files, pw) DEFINE_GET (files, pwnam) DEFINE_GET (files, pwuid) +DEFINE_GET (borg, pwnam) /* /etc/passwd2 */ +DEFINE_GET (borg, pwuid) +DEFINE_ENT (cache, pw) +DEFINE_GET (cache, pwnam) +DEFINE_GET (cache, pwuid) /* rpc */ DEFINE_ENT (files, rpc) @@ -76,3 +84,5 @@ DEFINE_GETBY (files, serv, port) /* shadow */ DEFINE_ENT (files, sp) DEFINE_GET (files, spnam) +DEFINE_ENT (cache, sp) +DEFINE_GET (cache, spnam) diff --git a/nss/nss_borg/borg-pwd.c b/nss/nss_borg/borg-pwd.c new file mode 100644 index 00000000000..dc8206a4892 --- /dev/null +++ b/nss/nss_borg/borg-pwd.c @@ -0,0 +1,171 @@ +// Copyright 2004 Google Inc. +// Author: Paul Menage + +// An NSS module that extends local user account lookup to the file /etc/passwd.borg + +#include +#include +#include +#include +#include +#include + +#ifdef NSSBORG_STANDALONE +#include +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +#define NSSBORG_LOCK pthread_mutex_lock(&mutex) +#define NSSBORG_UNLOCK pthread_mutex_unlock(&mutex) +#else +#include +__libc_lock_define_initialized (static, lock) +#define NSSBORG_LOCK __libc_lock_lock (lock) +#define NSSBORG_UNLOCK __libc_lock_unlock (lock); +#endif + +static FILE *f; + +#define DEBUG(fmt, ...) + +// _nss_borg_setpwent_locked() +// Internal setup routine + +static enum nss_status _nss_borg_setpwent_locked(void) { + + DEBUG("Opening passwd.borg\n"); + f = fopen("/etc/passwd.borg", "r"); + + if (f) { + return NSS_STATUS_SUCCESS; + } else { + return NSS_STATUS_UNAVAIL; + } +} + +// _nss_borg_setpwent() +// Called by NSS to open the passwd file +// Oddly, NSS passes a boolean saying whether to keep the database file open; ignore it + +enum nss_status _nss_borg_setpwent(int stayopen) { + enum nss_status ret; + NSSBORG_LOCK; + ret = _nss_borg_setpwent_locked(); + NSSBORG_UNLOCK; + return ret; +} + +// _nss_borg_endpwent_locked() +// Internal close routine + +static enum nss_status _nss_borg_endpwent_locked(void) { + + DEBUG("Closing passwd.borg\n"); + if (f) { + fclose(f); + f = NULL; + } + return NSS_STATUS_SUCCESS; +} + +// _nss_borg_endpwent() +// Called by NSS to close the passwd file + +enum nss_status _nss_borg_endpwent() { + enum nss_status ret; + NSSBORG_LOCK; + ret = _nss_borg_endpwent_locked(); + NSSBORG_UNLOCK; + return ret; +} + +// _nss_borg_getpwent_r_locked() +// Called internally to return the next entry from the passwd file + +static enum nss_status _nss_borg_getpwent_r_locked(struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + + enum nss_status ret; + + if (fgetpwent_r(f, result, buffer, buflen, &result) == 0) { + DEBUG("Returning user %d:%s\n", result->pw_uid, result->pw_name); + ret = NSS_STATUS_SUCCESS; + } else { + *errnop = errno; + switch (*errnop) { + case ERANGE: + ret = NSS_STATUS_TRYAGAIN; + break; + case ENOENT: + default: + ret = NSS_STATUS_NOTFOUND; + } + } + + return ret; +} + +// _nss_borg_getpwent_r() +// Called by NSS (I think) to look up next entry in passwd file +enum nss_status _nss_borg_getpwent_r(struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + enum nss_status ret; + NSSBORG_LOCK; + ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop); + NSSBORG_UNLOCK; + return ret; +} + +// _nss_borg_getpwuid_r() +// Find a user account by uid + +enum nss_status _nss_borg_getpwuid_r(uid_t uid, struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + + enum nss_status ret; + + NSSBORG_LOCK; + ret = _nss_borg_setpwent_locked(); + DEBUG("Looking for uid %d\n", uid); + + if (ret == NSS_STATUS_SUCCESS) { + while ((ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop)) + == NSS_STATUS_SUCCESS) { + if (result->pw_uid == uid) + break; + } + } + + _nss_borg_endpwent_locked(); + NSSBORG_UNLOCK; + + return ret; +} + +// _nss_borg_getpwnam_r() +// Find a user account by name + +enum nss_status _nss_borg_getpwnam_r(const char *name, struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + + enum nss_status ret; + + NSSBORG_LOCK; + ret = _nss_borg_setpwent_locked(); + DEBUG("Looking for user %s\n", name); + + if (ret == NSS_STATUS_SUCCESS) { + while ((ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop)) + == NSS_STATUS_SUCCESS) { + if (!strcmp(result->pw_name, name)) + break; + } + } + + _nss_borg_endpwent_locked(); + NSSBORG_UNLOCK; + + return ret; +} diff --git a/nss/nss_cache/nss_cache.c b/nss/nss_cache/nss_cache.c new file mode 100644 index 00000000000..6613dbbeea5 --- /dev/null +++ b/nss/nss_cache/nss_cache.c @@ -0,0 +1,978 @@ +/* Copyright 2009 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + */ + +/* An NSS module which adds supports for file maps with a trailing .cache + * suffix (/etc/passwd.cache, /etc/group.cache, and /etc/shadow.cache) + */ + +#include "nss_cache.h" + +// Locking implementation: use pthreads. +#include +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +#pragma weak pthread_mutex_lock +#define NSS_CACHE_LOCK() do { \ + if (&pthread_mutex_lock != NULL) pthread_mutex_lock(&mutex); \ + } while (0) + +#pragma weak pthread_mutex_unlock +#define NSS_CACHE_UNLOCK() do { \ + if (&pthread_mutex_unlock != NULL) pthread_mutex_unlock(&mutex); \ + } while (0) + +static FILE *p_file = NULL; +static FILE *g_file = NULL; +static FILE *s_file = NULL; +static char p_filename[NSS_CACHE_PATH_LENGTH] = "/etc/passwd.cache"; +static char g_filename[NSS_CACHE_PATH_LENGTH] = "/etc/group.cache"; +static char s_filename[NSS_CACHE_PATH_LENGTH] = "/etc/shadow.cache"; + +/* Common return code routine for all *ent_r_locked functions. + * We need to return TRYAGAIN if the underlying files guy raises ERANGE, + * so that our caller knows to try again with a bigger buffer. + */ + +static inline enum nss_status _nss_cache_ent_bad_return_code(int errnoval) { + enum nss_status ret; + + switch (errnoval) { + case ERANGE: + DEBUG("ERANGE: Try again with a bigger buffer\n"); + ret = NSS_STATUS_TRYAGAIN; + break; + case ENOENT: + default: + DEBUG("ENOENT or default case: Not found\n"); + ret = NSS_STATUS_NOTFOUND; + }; + return ret; +} + +// +// Binary search routines below here +// + +// _nss_cache_bsearch_lookup() +// Binary search through a sorted nss file for a single record. + +enum nss_status _nss_cache_bsearch_lookup(FILE *file, + struct nss_cache_args *args, + int *errnop) { + enum nss_cache_match (*lookup)( + FILE *, + struct nss_cache_args * + ) = args->lookup_function; + long min = 0; + long max; + long pos; + + // get the size of the file + if (fseek(file, 0, SEEK_END) != 0) { + DEBUG("fseek fail\n"); + return NSS_STATUS_UNAVAIL; + } + max = ftell(file); + + // binary search until we are within 100 chars of the right line + while (min + 100 < max) { + pos = (min + max) / 2; + + if (fseek(file, pos, SEEK_SET) != 0) { + DEBUG("fseek fail\n"); + return NSS_STATUS_UNAVAIL; + } + + // scan forward to the start of the next line. + for (;;) { + int c = getc_unlocked(file); + if (c == EOF) break; + ++pos; + if (c == '\n') break; + } + + // break if we've stopped making progress in this loop (long lines) + if (pos <= min || pos >= max) { + break; + } + + // see if this line matches + switch (lookup(file, args)) { + case NSS_CACHE_EXACT: + return NSS_STATUS_SUCCESS; // done! + case NSS_CACHE_HIGH: + max = pos; + continue; // search again + case NSS_CACHE_LOW: + min = pos; + continue; // search again + case NSS_CACHE_ERROR: + if (errno == ERANGE) { + // let the caller retry + *errnop = errno; + return _nss_cache_ent_bad_return_code(*errnop); + } + DEBUG("expected error %s [errno=%d] from lookup function\n", + strerror(errno), errno); + return NSS_STATUS_UNAVAIL; + } + } + + // fall back on a linear search in the remaining space + DEBUG("Switching to linear scan\n"); + pos = min - 100; // back 100, might be in the middle of the right line + if (fseek(file, pos, SEEK_SET) != 0) { + DEBUG("fseek fail\n"); + return NSS_STATUS_UNAVAIL; + } + + while (pos < max) { + switch (lookup(file, args)) { + case NSS_CACHE_EXACT: + return NSS_STATUS_SUCCESS; + case NSS_CACHE_HIGH: + case NSS_CACHE_LOW: + pos = ftell(file); + continue; + case NSS_CACHE_ERROR: + if (errno == ERANGE) { + // let the caller retry + *errnop = errno; + return _nss_cache_ent_bad_return_code(*errnop); + } + break; + } + break; + } + + return NSS_STATUS_NOTFOUND; +} + +// _nss_cache_bsearch() +// If a sorted nss file is present, attempt a binary search on it. + +enum nss_status _nss_cache_bsearch(struct nss_cache_args *args, int *errnop) { + FILE *file = NULL; + struct stat system_file; + struct stat sorted_file; + enum nss_status ret; + + file = fopen(args->sorted_filename, "r"); + if (file == NULL) { + DEBUG("error opening %s\n", args->sorted_filename); + return NSS_STATUS_UNAVAIL; + } + + // if the sorted file is older than the system file, do not risk stale + // data and abort + // TODO(vasilios): should be a compile or runtime option + if (stat(args->system_filename, &system_file) != 0) { + DEBUG("failed to stat %s\n", args->system_filename); + fclose(file); + return NSS_STATUS_UNAVAIL; + } + if (fstat(fileno(file), &sorted_file) != 0) { + DEBUG("failed to stat %s\n", args->sorted_filename); + fclose(file); + return NSS_STATUS_UNAVAIL; + } + if (difftime(system_file.st_mtime, sorted_file.st_mtime) > 0) { + DEBUG("%s may be stale, aborting lookup\n", args->sorted_filename); + fclose(file); + return NSS_STATUS_UNAVAIL; + } + + ret = _nss_cache_bsearch_lookup(file, args, errnop); + + fclose(file); + return ret; + +} + +// +// Routines for passwd map defined below here +// + +// _nss_cache_setpwent_path() +// Helper function for testing + +extern char* _nss_cache_setpwent_path(const char *path) { + + DEBUG("%s %s\n", "Setting p_filename to", path); + return strncpy(p_filename, path, NSS_CACHE_PATH_LENGTH - 1); + +} + +// _nss_cache_pwuid_wrap() +// Internal wrapper for binary searches, using uid-specific calls. + +static enum nss_cache_match _nss_cache_pwuid_wrap(FILE *file, + struct nss_cache_args *args) { + struct passwd *result = args->lookup_result; + uid_t *uid = args->lookup_value; + + if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) { + if (result->pw_uid == *uid) { + DEBUG("SUCCESS: found user %d:%s\n", result->pw_uid, result->pw_name); + return NSS_CACHE_EXACT; + } + DEBUG("Failed match at uid %d\n", result->pw_uid); + if (result->pw_uid > *uid) { + return NSS_CACHE_HIGH; + } else { + return NSS_CACHE_LOW; + } + } + + return NSS_CACHE_ERROR; +} + +// _nss_cache_pwnam_wrap() +// Internal wrapper for binary searches, using username-specific calls. + +static enum nss_cache_match _nss_cache_pwnam_wrap(FILE *file, + struct nss_cache_args *args) { + struct passwd *result = args->lookup_result; + char *name = args->lookup_value; + int ret; + + if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) { + ret = strcoll(result->pw_name, name); + if (ret == 0) { + DEBUG("SUCCESS: found user %s\n", result->pw_name); + return NSS_CACHE_EXACT; + } + DEBUG("Failed match at name %s\n", result->pw_name); + if (ret > 0) { + return NSS_CACHE_HIGH; + } else { + return NSS_CACHE_LOW; + } + } + + return NSS_CACHE_ERROR; +} + +// _nss_cache_setpwent_locked() +// Internal setup routine + +static enum nss_status _nss_cache_setpwent_locked(void) { + + DEBUG("%s %s\n", "Opening", p_filename); + p_file = fopen(p_filename, "r"); + + if (p_file) { + return NSS_STATUS_SUCCESS; + } else { + return NSS_STATUS_UNAVAIL; + } +} + +// _nss_cache_setpwent() +// Called by NSS to open the passwd file +// 'stayopen' parameter is ignored. + +enum nss_status _nss_cache_setpwent(int stayopen) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_setpwent_locked(); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_endpwent_locked() +// Internal close routine + +static enum nss_status _nss_cache_endpwent_locked(void) { + + DEBUG("Closing passwd.cache\n"); + if (p_file) { + fclose(p_file); + p_file = NULL; + } + return NSS_STATUS_SUCCESS; +} + +// _nss_cache_endpwent() +// Called by NSS to close the passwd file + +enum nss_status _nss_cache_endpwent(void) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_endpwent_locked(); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_getpwent_r_locked() +// Called internally to return the next entry from the passwd file + +static enum nss_status _nss_cache_getpwent_r_locked(struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + enum nss_status ret = NSS_STATUS_SUCCESS; + + if (p_file == NULL) { + DEBUG("p_file == NULL, going to setpwent\n"); + ret = _nss_cache_setpwent_locked(); + } + + if (ret == NSS_STATUS_SUCCESS) { + if (fgetpwent_r(p_file, result, buffer, buflen, &result) == 0) { + DEBUG("Returning user %d:%s\n", result->pw_uid, result->pw_name); + } else { + *errnop = errno; + ret = _nss_cache_ent_bad_return_code(*errnop); + } + } + + return ret; +} + +// _nss_cache_getpwent_r() +// Called by NSS to look up next entry in passwd file + +enum nss_status _nss_cache_getpwent_r(struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_getpwent_r_locked(result, buffer, buflen, errnop); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_getpwuid_r() +// Find a user account by uid + +enum nss_status _nss_cache_getpwuid_r(uid_t uid, struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + char filename[NSS_CACHE_PATH_LENGTH]; + struct nss_cache_args args; + enum nss_status ret; + + strncpy(filename, p_filename, NSS_CACHE_PATH_LENGTH - 1); + if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 7) { + DEBUG("filename too long\n"); + return NSS_STATUS_UNAVAIL; + } + strncat(filename, ".byuid", 6); + + args.sorted_filename = filename; + args.system_filename = p_filename; + args.lookup_function = _nss_cache_pwuid_wrap; + args.lookup_value = &uid; + args.lookup_result = result; + args.buffer = buffer; + args.buflen = buflen; + + DEBUG("Binary search for uid %d\n", uid); + NSS_CACHE_LOCK(); + ret = _nss_cache_bsearch(&args, errnop); + + // TODO(vasilios): make this a runtime or compile-time option, as this slows + // down legitimate misses as the trade off for safety. + if (ret == NSS_STATUS_NOTFOUND) { + DEBUG("Binary search returned nothing.\n"); + ret = NSS_STATUS_UNAVAIL; + } + + if (ret == NSS_STATUS_UNAVAIL) { + DEBUG("Binary search failed, falling back to full linear search\n"); + ret = _nss_cache_setpwent_locked(); + + if (ret == NSS_STATUS_SUCCESS) { + while ((ret = _nss_cache_getpwent_r_locked(result, + buffer, + buflen, + errnop)) + == NSS_STATUS_SUCCESS) { + if (result->pw_uid == uid) + break; + } + } + } + + _nss_cache_endpwent_locked(); + NSS_CACHE_UNLOCK(); + + return ret; +} + +// _nss_cache_getpwnam_r() +// Find a user account by name + +enum nss_status _nss_cache_getpwnam_r(const char *name, struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { + char *pw_name; + char filename[NSS_CACHE_PATH_LENGTH]; + struct nss_cache_args args; + enum nss_status ret; + + NSS_CACHE_LOCK(); + + // name is a const char, we need a non-const copy + pw_name = malloc(strlen(name) + 1); + if (pw_name == NULL) { + DEBUG("malloc error\n"); + return NSS_STATUS_UNAVAIL; + } + strncpy(pw_name, name, strlen(name) + 1); + + strncpy(filename, p_filename, NSS_CACHE_PATH_LENGTH - 1); + if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 8) { + DEBUG("filename too long\n"); + free(pw_name); + return NSS_STATUS_UNAVAIL; + } + strncat(filename, ".byname", 7); + + args.sorted_filename = filename; + args.system_filename = p_filename; + args.lookup_function = _nss_cache_pwnam_wrap; + args.lookup_value = pw_name; + args.lookup_result = result; + args.buffer = buffer; + args.buflen = buflen; + + DEBUG("Binary search for user %s\n", pw_name); + ret = _nss_cache_bsearch(&args, errnop); + + // TODO(vasilios): make this a runtime or compile-time option, as this slows + // down legitimate misses as the trade off for safety. + if (ret == NSS_STATUS_NOTFOUND) { + DEBUG("Binary search returned nothing.\n"); + ret = NSS_STATUS_UNAVAIL; + } + + if (ret == NSS_STATUS_UNAVAIL) { + DEBUG("Binary search failed, falling back to full linear search\n"); + ret = _nss_cache_setpwent_locked(); + + if (ret == NSS_STATUS_SUCCESS) { + while ((ret = _nss_cache_getpwent_r_locked(result, + buffer, + buflen, + errnop)) + == NSS_STATUS_SUCCESS) { + if (!strcmp(result->pw_name, name)) + break; + } + } + } + + free(pw_name); + _nss_cache_endpwent_locked(); + NSS_CACHE_UNLOCK(); + + return ret; +} + +// +// Routines for group map defined here. +// + +// _nss_cache_setgrent_path() +// Helper function for testing + +extern char* _nss_cache_setgrent_path(const char *path) { + + DEBUG("%s %s\n", "Setting g_filename to", path); + return strncpy(g_filename, path, NSS_CACHE_PATH_LENGTH - 1); + +} + +// _nss_cache_setgrent_locked() +// Internal setup routine + +static enum nss_status _nss_cache_setgrent_locked(void) { + + DEBUG("%s %s\n", "Opening", g_filename); + g_file = fopen(g_filename, "r"); + + if (g_file) { + return NSS_STATUS_SUCCESS; + } else { + return NSS_STATUS_UNAVAIL; + } +} + +// _nss_cache_grgid_wrap() +// Internal wrapper for binary searches, using gid-specific calls. + +static enum nss_cache_match _nss_cache_grgid_wrap(FILE *file, + struct nss_cache_args *args) { + struct group *result = args->lookup_result; + gid_t *gid = args->lookup_value; + + if (fgetgrent_r(file, result, args->buffer, args->buflen, &result) == 0) { + if (result->gr_gid == *gid) { + DEBUG("SUCCESS: found group %d:%s\n", result->gr_gid, result->gr_name); + return NSS_CACHE_EXACT; + } + DEBUG("Failed match at gid %d\n", result->gr_gid); + if (result->gr_gid > *gid) { + return NSS_CACHE_HIGH; + } else { + return NSS_CACHE_LOW; + } + } + + return NSS_CACHE_ERROR; +} + +// _nss_cache_grnam_wrap() +// Internal wrapper for binary searches, using groupname-specific calls. + +static enum nss_cache_match _nss_cache_grnam_wrap(FILE *file, + struct nss_cache_args *args) { + struct group *result = args->lookup_result; + char *name = args->lookup_value; + int ret; + + if (fgetgrent_r(file, result, args->buffer, args->buflen, &result) == 0) { + ret = strcoll(result->gr_name, name); + if (ret == 0) { + DEBUG("SUCCESS: found group %s\n", result->gr_name); + return NSS_CACHE_EXACT; + } + DEBUG("Failed match at name %s\n", result->gr_name); + if (ret > 0) { + return NSS_CACHE_HIGH; + } else { + return NSS_CACHE_LOW; + } + } + + return NSS_CACHE_ERROR; +} + +// _nss_cache_setgrent() +// Called by NSS to open the group file +// 'stayopen' parameter is ignored. + +enum nss_status _nss_cache_setgrent(int stayopen) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_setgrent_locked(); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_endgrent_locked() +// Internal close routine + +static enum nss_status _nss_cache_endgrent_locked(void) { + + DEBUG("Closing group.cache\n"); + if (g_file) { + fclose(g_file); + g_file = NULL; + } + return NSS_STATUS_SUCCESS; +} + +// _nss_cache_endgrent() +// Called by NSS to close the group file + +enum nss_status _nss_cache_endgrent(void) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_endgrent_locked(); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_getgrent_r_locked() +// Called internally to return the next entry from the group file + +static enum nss_status _nss_cache_getgrent_r_locked(struct group *result, + char *buffer, size_t buflen, + int *errnop) { + enum nss_status ret = NSS_STATUS_SUCCESS; + + if (g_file == NULL) { + DEBUG("g_file == NULL, going to setgrent\n"); + ret = _nss_cache_setgrent_locked(); + } + + if (ret == NSS_STATUS_SUCCESS) { + fpos_t position; + + fgetpos(g_file, &position); + if (fgetgrent_r(g_file, result, buffer, buflen, &result) == 0) { + DEBUG("Returning group %s (%d)\n", result->gr_name, result->gr_gid); + } else { + /* Rewind back to where we were just before, otherwise the data read + * into the buffer is probably going to be lost because there's no + * guarantee that the caller is going to have preserved the line we + * just read. Note that glibc's nss/nss_files/files-XXX.c does + * something similar in CONCAT(_nss_files_get,ENTNAME_r) (around + * line 242 in glibc 2.4 sources). + */ + fsetpos(g_file, &position); + *errnop = errno; + ret = _nss_cache_ent_bad_return_code(*errnop); + } + } + + return ret; +} + +// _nss_cache_getgrent_r() +// Called by NSS to look up next entry in group file + +enum nss_status _nss_cache_getgrent_r(struct group *result, + char *buffer, size_t buflen, + int *errnop) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_getgrent_r_locked(result, buffer, buflen, errnop); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_getgrgid_r() +// Find a group by gid + +enum nss_status _nss_cache_getgrgid_r(gid_t gid, struct group *result, + char *buffer, size_t buflen, + int *errnop) { + char filename[NSS_CACHE_PATH_LENGTH]; + struct nss_cache_args args; + enum nss_status ret; + + strncpy(filename, g_filename, NSS_CACHE_PATH_LENGTH - 1); + if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 7) { + DEBUG("filename too long\n"); + return NSS_STATUS_UNAVAIL; + } + strncat(filename, ".bygid", 6); + + args.sorted_filename = filename; + args.system_filename = g_filename; + args.lookup_function = _nss_cache_grgid_wrap; + args.lookup_value = &gid; + args.lookup_result = result; + args.buffer = buffer; + args.buflen = buflen; + + DEBUG("Binary search for gid %d\n", gid); + NSS_CACHE_LOCK(); + ret = _nss_cache_bsearch(&args, errnop); + + // TODO(vasilios): make this a runtime or compile-time option, as this slows + // down legitimate misses as the trade off for safety. + if (ret == NSS_STATUS_NOTFOUND) { + DEBUG("Binary search returned nothing.\n"); + ret = NSS_STATUS_UNAVAIL; + } + + if (ret == NSS_STATUS_UNAVAIL) { + DEBUG("Binary search failed, falling back to full linear search\n"); + ret = _nss_cache_setgrent_locked(); + + if (ret == NSS_STATUS_SUCCESS) { + while ((ret = _nss_cache_getgrent_r_locked(result, + buffer, + buflen, + errnop)) + == NSS_STATUS_SUCCESS) { + if (result->gr_gid == gid) + break; + } + } + } + + _nss_cache_endgrent_locked(); + NSS_CACHE_UNLOCK(); + + return ret; +} + +// _nss_cache_getgrnam_r() +// Find a group by name + +enum nss_status _nss_cache_getgrnam_r(const char *name, struct group *result, + char *buffer, size_t buflen, + int *errnop) { + char *gr_name; + char filename[NSS_CACHE_PATH_LENGTH]; + struct nss_cache_args args; + enum nss_status ret; + + NSS_CACHE_LOCK(); + + // name is a const char, we need a non-const copy + gr_name = malloc(strlen(name) + 1); + if (gr_name == NULL) { + DEBUG("malloc error\n"); + return NSS_STATUS_UNAVAIL; + } + strncpy(gr_name, name, strlen(name) + 1); + + strncpy(filename, g_filename, NSS_CACHE_PATH_LENGTH - 1); + if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 8) { + DEBUG("filename too long\n"); + free(gr_name); + return NSS_STATUS_UNAVAIL; + } + strncat(filename, ".byname", 7); + + args.sorted_filename = filename; + args.system_filename = g_filename; + args.lookup_function = _nss_cache_grnam_wrap; + args.lookup_value = gr_name; + args.lookup_result = result; + args.buffer = buffer; + args.buflen = buflen; + + DEBUG("Binary search for group %s\n", gr_name); + ret = _nss_cache_bsearch(&args, errnop); + + // TODO(vasilios): make this a runtime or compile-time option, as this slows + // down legitimate misses as the trade off for safety. + if (ret == NSS_STATUS_NOTFOUND) { + DEBUG("Binary search returned nothing.\n"); + ret = NSS_STATUS_UNAVAIL; + } + + if (ret == NSS_STATUS_UNAVAIL) { + DEBUG("Binary search failed, falling back to full linear search\n"); + ret = _nss_cache_setgrent_locked(); + + if (ret == NSS_STATUS_SUCCESS) { + while ((ret = _nss_cache_getgrent_r_locked(result, + buffer, + buflen, + errnop)) + == NSS_STATUS_SUCCESS) { + if (!strcmp(result->gr_name, name)) + break; + } + } + } + + free(gr_name); + _nss_cache_endgrent_locked(); + NSS_CACHE_UNLOCK(); + + return ret; +} + +// +// Routines for shadow map defined here. +// + +// _nss_cache_setspent_path() +// Helper function for testing + +extern char* _nss_cache_setspent_path(const char *path) { + + DEBUG("%s %s\n", "Setting s_filename to", path); + return strncpy(s_filename, path, NSS_CACHE_PATH_LENGTH - 1); + +} + +// _nss_cache_setspent_locked() +// Internal setup routine + +static enum nss_status _nss_cache_setspent_locked(void) { + + DEBUG("%s %s\n", "Opening", g_filename); + s_file = fopen(s_filename, "r"); + + if (s_file) { + return NSS_STATUS_SUCCESS; + } else { + return NSS_STATUS_UNAVAIL; + } +} + +// _nss_cache_spnam_wrap() +// Internal wrapper for binary searches, using shadow-specific calls. + +static enum nss_cache_match _nss_cache_spnam_wrap(FILE *file, + struct nss_cache_args *args) { + struct spwd *result = args->lookup_result; + char *name = args->lookup_value; + int ret; + + if (fgetspent_r(file, result, args->buffer, args->buflen, &result) == 0) { + ret = strcoll(result->sp_namp, name); + if (ret == 0) { + DEBUG("SUCCESS: found user %s\n", result->sp_namp); + return NSS_CACHE_EXACT; + } + DEBUG("Failed match at name %s\n", result->sp_namp); + if (ret > 0) { + return NSS_CACHE_HIGH; + } else { + return NSS_CACHE_LOW; + } + } + + return NSS_CACHE_ERROR; +} + +// _nss_cache_setspent() +// Called by NSS to open the shadow file +// 'stayopen' parameter is ignored. + +enum nss_status _nss_cache_setspent(int stayopen) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_setspent_locked(); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_endspent_locked() +// Internal close routine + +static enum nss_status _nss_cache_endspent_locked(void) { + + DEBUG("Closing shadow.cache\n"); + if (s_file) { + fclose(s_file); + s_file = NULL; + } + return NSS_STATUS_SUCCESS; +} + +// _nss_cache_endspent() +// Called by NSS to close the shadow file + +enum nss_status _nss_cache_endspent(void) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_endspent_locked(); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_getspent_r_locked() +// Called internally to return the next entry from the shadow file + +static enum nss_status _nss_cache_getspent_r_locked(struct spwd *result, + char *buffer, size_t buflen, + int *errnop) { + + enum nss_status ret = NSS_STATUS_SUCCESS; + + if (s_file == NULL) { + DEBUG("s_file == NULL, going to setspent\n"); + ret = _nss_cache_setspent_locked(); + } + + if (ret == NSS_STATUS_SUCCESS) { + if (fgetspent_r(s_file, result, buffer, buflen, &result) == 0) { + DEBUG("Returning shadow entry %s\n", result->sp_namp); + } else { + *errnop = errno; + ret = _nss_cache_ent_bad_return_code(*errnop); + } + } + + return ret; +} + +// _nss_cache_getspent_r() +// Called by NSS to look up next entry in the shadow file + +enum nss_status _nss_cache_getspent_r(struct spwd *result, + char *buffer, size_t buflen, + int *errnop) { + enum nss_status ret; + NSS_CACHE_LOCK(); + ret = _nss_cache_getspent_r_locked(result, buffer, buflen, errnop); + NSS_CACHE_UNLOCK(); + return ret; +} + +// _nss_cache_getspnam_r() +// Find a user by name + +enum nss_status _nss_cache_getspnam_r(const char *name, struct spwd *result, + char *buffer, size_t buflen, + int *errnop) { + char *sp_namp; + char filename[NSS_CACHE_PATH_LENGTH]; + struct nss_cache_args args; + enum nss_status ret; + + NSS_CACHE_LOCK(); + + // name is a const char, we need a non-const copy + sp_namp = malloc(strlen(name) + 1); + if (sp_namp == NULL) { + DEBUG("malloc error\n"); + return NSS_STATUS_UNAVAIL; + } + strncpy(sp_namp, name, strlen(name) + 1); + + strncpy(filename, s_filename, NSS_CACHE_PATH_LENGTH - 1); + if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 8) { + DEBUG("filename too long\n"); + free(sp_namp); + return NSS_STATUS_UNAVAIL; + } + strncat(filename, ".byname", 7); + + args.sorted_filename = filename; + args.system_filename = s_filename; + args.lookup_function = _nss_cache_spnam_wrap; + args.lookup_value = sp_namp; + args.lookup_result = result; + args.buffer = buffer; + args.buflen = buflen; + + DEBUG("Binary search for user %s\n", sp_namp); + ret = _nss_cache_bsearch(&args, errnop); + + // TODO(vasilios): make this a runtime or compile-time option, as this slows + // down legitimate misses as the trade off for safety. + if (ret == NSS_STATUS_NOTFOUND) { + DEBUG("Binary search returned nothing.\n"); + ret = NSS_STATUS_UNAVAIL; + } + + if (ret == NSS_STATUS_UNAVAIL) { + DEBUG("Binary search failed, falling back to full linear search\n"); + ret = _nss_cache_setspent_locked(); + + if (ret == NSS_STATUS_SUCCESS) { + while ((ret = _nss_cache_getspent_r_locked(result, + buffer, + buflen, + errnop)) + == NSS_STATUS_SUCCESS) { + if (!strcmp(result->sp_namp, name)) + break; + } + } + } + + free(sp_namp); + _nss_cache_endspent_locked(); + NSS_CACHE_UNLOCK(); + + return ret; +} diff --git a/nss/nss_cache/nss_cache.h b/nss/nss_cache/nss_cache.h new file mode 100644 index 00000000000..8ce4b97d339 --- /dev/null +++ b/nss/nss_cache/nss_cache.h @@ -0,0 +1,65 @@ +/* Copyright 2009 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NSS_CACHE_H +#define NSS_CACHE_H + +#ifdef DEBUG +#undef DEBUG +#define DEBUG(fmt, args...) do { fprintf(stderr, fmt, ##args); } while (0) +#else +#define DEBUG(fmt, ...) do { } while (0) +#endif /* DEBUG */ + +#define NSS_CACHE_PATH_LENGTH 255 +extern char* _nss_cache_setpwent_path(const char *path); +extern char* _nss_cache_setgrent_path(const char *path); +extern char* _nss_cache_setspent_path(const char *path); + +enum nss_cache_match { + NSS_CACHE_EXACT = 0, + NSS_CACHE_HIGH = 1, + NSS_CACHE_LOW = 2, + NSS_CACHE_ERROR = 3, +}; + +struct nss_cache_args { + char *system_filename; + char *sorted_filename; + void *lookup_function; + void *lookup_value; + void *lookup_result; + char *buffer; + size_t buflen; +}; + +#endif /* NSS_CACHE_H */ diff --git a/shlib-versions b/shlib-versions index b9cb99d2fbb..749346e2678 100644 --- a/shlib-versions +++ b/shlib-versions @@ -48,6 +48,8 @@ libnss_nisplus=2 libnss_ldap=2 libnss_hesiod=2 libnss_db=2 +libnss_borg=2 +libnss_cache=2 # Tests for NSS. They must have the same NSS_SHLIB_REVISION number as # the rest.