From a30452355d9bb547608dd10c4e6228ce8796692f Mon Sep 17 00:00:00 2001 From: lukem Date: Mon, 28 Feb 2005 02:56:28 +0000 Subject: [PATCH] Overhaul the use of nsdispatch(3) by public APIs so that the back-end methods use va_list in a manner that is directly related to the public API. This makes it much easier to write dynamic nsswitch backends for the getusershell(3) (et al) functions. Per my proposal on tech-userlevel in September 2004. --- lib/libc/gen/getusershell.c | 598 +++++++++++++++++++++++++----------- 1 file changed, 423 insertions(+), 175 deletions(-) diff --git a/lib/libc/gen/getusershell.c b/lib/libc/gen/getusershell.c index f954afac3cd2..1bf9db69a6a2 100644 --- a/lib/libc/gen/getusershell.c +++ b/lib/libc/gen/getusershell.c @@ -1,4 +1,40 @@ -/* $NetBSD: getusershell.c,v 1.23 2003/08/07 16:42:51 agc Exp $ */ +/* $NetBSD: getusershell.c,v 1.24 2005/02/28 02:56:28 lukem Exp $ */ + +/*- + * Copyright (c) 1999, 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ /* * Copyright (c) 1985, 1993 @@ -34,14 +70,17 @@ #if 0 static char sccsid[] = "@(#)getusershell.c 8.1 (Berkeley) 6/4/93"; #else -__RCSID("$NetBSD: getusershell.c,v 1.23 2003/08/07 16:42:51 agc Exp $"); +__RCSID("$NetBSD: getusershell.c,v 1.24 2005/02/28 02:56:28 lukem Exp $"); #endif #endif /* LIBC_SCCS and not lint */ #include "namespace.h" +#include "reentrant.h" + #include #include +#include #include #include #include @@ -50,7 +89,6 @@ __RCSID("$NetBSD: getusershell.c,v 1.23 2003/08/07 16:42:51 agc Exp $"); #include #include #include -#include #include #ifdef HESIOD @@ -69,74 +107,97 @@ __weak_alias(setusershell,_setusershell) #endif /* - * Local shells should NOT be added here. They should be added in - * /etc/shells. + * Local shells should NOT be added here. + * They should be added in /etc/shells. */ - static const char *const okshells[] = { _PATH_BSHELL, _PATH_CSHELL, NULL }; -static const char *const *curshell; -static StringList *sl; -static const char *const *initshells __P((void)); +#ifdef _REENTRANT +static mutex_t __shellmutex = MUTEX_INITIALIZER; +#endif -/* - * Get a list of shells from "shells" nsswitch database - */ -__aconst char * -getusershell() -{ - __aconst char *ret; +static char curshell[MAXPATHLEN + 2]; - if (curshell == NULL) - curshell = initshells(); - /*LINTED*/ - ret = (__aconst char *)*curshell; - if (ret != NULL) - curshell++; - return (ret); -} +static const char *const *curokshell = okshells; +static int shellsfound = 0; -void -endusershell() -{ - if (sl) - sl_free(sl, 1); - sl = NULL; - curshell = NULL; -} + /* + * files methods + */ -void -setusershell() + /* state shared between files methods */ +struct files_state { + FILE *fp; +}; + +static struct files_state _files_state; + + +static int +_files_start(struct files_state *state) { - curshell = initshells(); + _DIAGASSERT(state != NULL); + + if (state->fp == NULL) { + state->fp = fopen(_PATH_SHELLS, "r"); + if (state->fp == NULL) + return NS_UNAVAIL; + } else { + rewind(state->fp); + } + return NS_SUCCESS; } +static int +_files_end(struct files_state *state) +{ -static int _local_initshells __P((void *, void *, va_list)); + _DIAGASSERT(state != NULL); + + if (state->fp) { + (void) fclose(state->fp); + state->fp = NULL; + } + return NS_SUCCESS; +} /*ARGSUSED*/ static int -_local_initshells(rv, cb_data, ap) - void *rv; - void *cb_data; - va_list ap; +_files_setusershell(void *nsrv, void *nscb, va_list ap) { + + return _files_start(&_files_state); +} + +/*ARGSUSED*/ +static int +_files_endusershell(void *nsrv, void *nscb, va_list ap) +{ + + return _files_end(&_files_state); +} + +/*ARGSUSED*/ +static int +_files_getusershell(void *nsrv, void *nscb, va_list ap) +{ + char **retval = va_arg(ap, char **); + char *sp, *cp; - FILE *fp; - char line[MAXPATHLEN + 2]; + int rv; - if (sl) - sl_free(sl, 1); - sl = sl_init(); - if (!sl) - return (NS_UNAVAIL); + _DIAGASSERT(retval != NULL); - if ((fp = fopen(_PATH_SHELLS, "r")) == NULL) - return (NS_UNAVAIL); + *retval = NULL; + if (_files_state.fp == NULL) { /* only start if file not open yet */ + rv = _files_start(&_files_state); + if (rv != NS_SUCCESS) + return rv; + } - sp = cp = line; - while (fgets(cp, MAXPATHLEN + 1, fp) != NULL) { + while (fgets(curshell, sizeof(curshell) - 1, _files_state.fp) != NULL) { + sp = cp = curshell; while (*cp != '#' && *cp != '/' && *cp != '\0') cp++; if (*cp == '#' || *cp == '\0') @@ -146,161 +207,348 @@ _local_initshells(rv, cb_data, ap) && *cp != '\0') cp++; *cp++ = '\0'; - if (sl_add(sl, strdup(sp)) == -1) - return (NS_UNAVAIL); + *retval = sp; + return NS_SUCCESS; } - (void)fclose(fp); - return (NS_SUCCESS); + + return NS_NOTFOUND; } + #ifdef HESIOD -static int _dns_initshells __P((void *, void *, va_list)); + /* + * dns methods + */ + + /* state shared between dns methods */ +struct dns_state { + void *context; /* Hesiod context */ + int num; /* shell index, -1 if no more */ +}; + +static struct dns_state _dns_state; + +static int +_dns_start(struct dns_state *state) +{ + + _DIAGASSERT(state != NULL); + + state->num = 0; + if (state->context == NULL) { /* setup Hesiod */ + if (hesiod_init(&state->context) == -1) + return NS_UNAVAIL; + } + + return NS_SUCCESS; +} + +static int +_dns_end(struct dns_state *state) +{ + + _DIAGASSERT(state != NULL); + + state->num = 0; + if (state->context) { + hesiod_end(state->context); + state->context = NULL; + } + return NS_SUCCESS; +} /*ARGSUSED*/ static int -_dns_initshells(rv, cb_data, ap) - void *rv; - void *cb_data; - va_list ap; +_dns_setusershell(void *nsrv, void *nscb, va_list ap) { - char shellname[] = "shells-XXXXX"; - int hsindex, hpi, r; - char **hp; - void *context; - if (sl) - sl_free(sl, 1); - sl = sl_init(); - if (!sl) - return (NS_UNAVAIL); - - r = NS_UNAVAIL; - if (hesiod_init(&context) == -1) - return (r); - - for (hsindex = 0; ; hsindex++) { - snprintf(shellname, sizeof(shellname)-1, "shells-%d", hsindex); - hp = hesiod_resolve(context, shellname, "shells"); - if (hp == NULL) { - if (errno == ENOENT) { - if (hsindex == 0) - r = NS_NOTFOUND; - else - r = NS_SUCCESS; - } - break; - } else { - int bad = 0; - - for (hpi = 0; hp[hpi]; hpi++) - if (sl_add(sl, hp[hpi]) == -1) { - bad = 1; - break; - } - free(hp); - if (bad) - break; - } - } - hesiod_end(context); - return (r); + return _dns_start(&_dns_state); } + +/*ARGSUSED*/ +static int +_dns_endusershell(void *nsrv, void *nscb, va_list ap) +{ + + return _dns_end(&_dns_state); +} + +/*ARGSUSED*/ +static int +_dns_getusershell(void *nsrv, void *nscb, va_list ap) +{ + char **retval = va_arg(ap, char **); + + char shellname[] = "shells-NNNNNNNNNN"; + char **hp, *ep; + int rv; + + _DIAGASSERT(retval != NULL); + + *retval = NULL; + + if (_dns_state.num == -1) /* exhausted search */ + return NS_NOTFOUND; + + if (_dns_state.context == NULL) { + /* only start if Hesiod not setup */ + rv = _dns_start(&_dns_state); + if (rv != NS_SUCCESS) + return rv; + } + + hp = NULL; + rv = NS_NOTFOUND; + + /* find shells-NNN */ + snprintf(shellname, sizeof(shellname), "shells-%d", _dns_state.num); + _dns_state.num++; + + hp = hesiod_resolve(_dns_state.context, shellname, "shells"); + if (hp == NULL) { + if (errno == ENOENT) + rv = NS_NOTFOUND; + else + rv = NS_UNAVAIL; + } else { + if ((ep = strchr(hp[0], '\n')) != NULL) + *ep = '\0'; /* clear trailing \n */ + /* only use first result */ + strlcpy(curshell, hp[0], sizeof(curshell)); + *retval = curshell; + rv = NS_SUCCESS; + } + + if (hp) + hesiod_free_list(_dns_state.context, hp); + if (rv != NS_SUCCESS) + _dns_state.num = -1; /* any failure halts search */ + return rv; +} + #endif /* HESIOD */ + #ifdef YP -static int _nis_initshells __P((void *, void *, va_list)); + /* + * nis methods + */ + /* state shared between nis methods */ +struct nis_state { + char *domain; /* NIS domain */ + int done; /* non-zero if search exhausted */ + char *current; /* current first/next match */ + int currentlen; /* length of _nis_current */ +}; + +static struct nis_state _nis_state; -/*ARGSUSED*/ static int -_nis_initshells(rv, cb_data, ap) - void *rv; - void *cb_data; - va_list ap; +_nis_start(struct nis_state *state) { - static char *ypdomain; - if (sl) - sl_free(sl, 1); - sl = sl_init(); - if (!sl) - return (NS_UNAVAIL); + _DIAGASSERT(state != NULL); - if (ypdomain == NULL) { - switch (yp_get_default_domain(&ypdomain)) { + state->done = 0; + if (state->current) { + free(state->current); + state->current = NULL; + } + if (state->domain == NULL) { /* setup NIS */ + switch (yp_get_default_domain(&state->domain)) { case 0: break; case YPERR_RESRC: - return (NS_TRYAGAIN); + return NS_TRYAGAIN; default: - return (NS_UNAVAIL); + return NS_UNAVAIL; } } - - for (;;) { - char *ypcur = NULL; - int ypcurlen = 0; /* XXX: GCC */ - char *key, *data; - int keylen, datalen; - int r; - - key = data = NULL; - if (ypcur) { - r = yp_next(ypdomain, "shells", ypcur, ypcurlen, - &key, &keylen, &data, &datalen); - free(ypcur); - switch (r) { - case 0: - break; - case YPERR_NOMORE: - free(key); - free(data); - return (NS_SUCCESS); - default: - free(key); - free(data); - return (NS_UNAVAIL); - } - ypcur = key; - ypcurlen = keylen; - } else { - if (yp_first(ypdomain, "shells", &ypcur, - &ypcurlen, &data, &datalen)) { - free(data); - return (NS_UNAVAIL); - } - } - data[datalen] = '\0'; /* clear trailing \n */ - if (sl_add(sl, data) == -1) - return (NS_UNAVAIL); - } + return NS_SUCCESS; } + +static int +_nis_end(struct nis_state *state) +{ + + _DIAGASSERT(state != NULL); + + if (state->domain) + state->domain = NULL; + state->done = 0; + if (state->current) + free(state->current); + state->current = NULL; + return NS_SUCCESS; +} + +/*ARGSUSED*/ +static int +_nis_setusershell(void *nsrv, void *nscb, va_list ap) +{ + + return _nis_start(&_nis_state); +} + +/*ARGSUSED*/ +static int +_nis_endusershell(void *nsrv, void *nscb, va_list ap) +{ + + return _nis_end(&_nis_state); +} + +/*ARGSUSED*/ +static int +_nis_getusershell(void *nsrv, void *nscb, va_list ap) +{ + char **retval = va_arg(ap, char **); + + char *key, *data; + int keylen, datalen, rv, nisr; + + _DIAGASSERT(retval != NULL); + + *retval = NULL; + + if (_nis_state.done) /* exhausted search */ + return NS_NOTFOUND; + if (_nis_state.domain == NULL) { + /* only start if NIS not setup */ + rv = _nis_start(&_nis_state); + if (rv != NS_SUCCESS) + return rv; + } + + key = NULL; + data = NULL; + rv = NS_NOTFOUND; + + if (_nis_state.current) { /* already searching */ + nisr = yp_next(_nis_state.domain, "shells", + _nis_state.current, _nis_state.currentlen, + &key, &keylen, &data, &datalen); + free(_nis_state.current); + _nis_state.current = NULL; + switch (nisr) { + case 0: + _nis_state.current = key; + _nis_state.currentlen = keylen; + key = NULL; + break; + case YPERR_NOMORE: + rv = NS_NOTFOUND; + goto nisent_out; + default: + rv = NS_UNAVAIL; + goto nisent_out; + } + } else { /* new search */ + if (yp_first(_nis_state.domain, "shells", + &_nis_state.current, &_nis_state.currentlen, + &data, &datalen)) { + rv = NS_UNAVAIL; + goto nisent_out; + } + } + + data[datalen] = '\0'; /* clear trailing \n */ + strlcpy(curshell, data, sizeof(curshell)); + *retval = curshell; + rv = NS_SUCCESS; + + nisent_out: + if (key) + free(key); + if (data) + free(data); + if (rv != NS_SUCCESS) /* any failure halts search */ + _nis_state.done = 1; + return rv; +} + #endif /* YP */ -static const char *const * -initshells() + + /* + * public functions + */ + +void +endusershell(void) { static const ns_dtab dtab[] = { - NS_FILES_CB(_local_initshells, NULL) - NS_DNS_CB(_dns_initshells, NULL) - NS_NIS_CB(_nis_initshells, NULL) + NS_FILES_CB(_files_endusershell, NULL) + NS_DNS_CB(_dns_endusershell, NULL) + NS_NIS_CB(_nis_endusershell, NULL) { 0 } }; - if (sl) - sl_free(sl, 1); - sl = sl_init(); - if (!sl) - goto badinitshells; - if (nsdispatch(NULL, dtab, NSDB_SHELLS, "initshells", __nsdefaultsrc) - != NS_SUCCESS) { - badinitshells: - if (sl) - sl_free(sl, 1); - sl = NULL; - return (okshells); - } - if (sl_add(sl, NULL) == -1) - goto badinitshells; + mutex_lock(&__shellmutex); - return (const char *const *)(sl->sl_str); + curokshell = okshells; /* reset okshells fallback state */ + shellsfound = 0; + + /* force all endusershell() methods */ + (void) nsdispatch(NULL, dtab, NSDB_SHELLS, "endusershell", + __nsdefaultfiles_forceall); + mutex_unlock(&__shellmutex); +} + +__aconst char * +getusershell(void) +{ + int rv; + __aconst char *retval; + + static const ns_dtab dtab[] = { + NS_FILES_CB(_files_getusershell, NULL) + NS_DNS_CB(_dns_getusershell, NULL) + NS_NIS_CB(_nis_getusershell, NULL) + { 0 } + }; + + mutex_lock(&__shellmutex); + + retval = NULL; + do { + rv = nsdispatch(NULL, dtab, NSDB_SHELLS, "getusershell", + __nsdefaultsrc, &retval); + /* loop until failure or non-blank result */ + } while (rv == NS_SUCCESS && retval[0] == '\0'); + + if (rv == NS_SUCCESS) { + shellsfound++; + } else if (shellsfound == 0) { /* no shells; fall back to okshells */ + if (curokshell != NULL) { + /*LINTED*/ + retval = (__aconst char *)*curokshell; + curokshell++; + rv = NS_SUCCESS; + } + } + + mutex_unlock(&__shellmutex); + return (rv == NS_SUCCESS) ? retval : NULL; +} + +void +setusershell(void) +{ + static const ns_dtab dtab[] = { + NS_FILES_CB(_files_setusershell, NULL) + NS_DNS_CB(_dns_setusershell, NULL) + NS_NIS_CB(_nis_setusershell, NULL) + { 0 } + }; + + mutex_lock(&__shellmutex); + + curokshell = okshells; /* reset okshells fallback state */ + shellsfound = 0; + + /* force all setusershell() methods */ + (void) nsdispatch(NULL, dtab, NSDB_SHELLS, "setusershell", + __nsdefaultfiles_forceall); + mutex_unlock(&__shellmutex); }