/*	$NetBSD: getpwent.c,v 1.83 2022/04/19 20:32:15 rillig Exp $	*/

/*-
 * Copyright (c) 1997-2000, 2004-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.
 *
 * 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) 1988, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * 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. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 */

/*
 * Portions Copyright (c) 1994, 1995, Jason Downs.  All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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.
 */

#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)getpwent.c	8.2 (Berkeley) 4/27/95";
#else
__RCSID("$NetBSD: getpwent.c,v 1.83 2022/04/19 20:32:15 rillig Exp $");
#endif
#endif /* LIBC_SCCS and not lint */

#include "namespace.h"
#include "reentrant.h"

#include <sys/param.h>

#include <assert.h>
#include <db.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netgroup.h>
#include <nsswitch.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#ifdef HESIOD
#include <hesiod.h>
#endif

#ifdef YP
#include <rpc/rpc.h>
#include <rpcsvc/yp_prot.h>
#include <rpcsvc/ypclnt.h>
#endif

#include "pw_private.h"

#define	_PASSWD_COMPAT	/* "passwd" defaults to compat, so always provide it */

#ifdef __weak_alias
__weak_alias(endpwent,_endpwent)
__weak_alias(setpassent,_setpassent)
__weak_alias(setpwent,_setpwent)
#endif

#ifdef _REENTRANT
static 	mutex_t			_pwmutex = MUTEX_INITIALIZER;
#endif

const char __yp_token[] = "__YP!";	/* Let pwd_mkdb pull this in. */


/*
 * The pwd.db lookup techniques and data extraction code here must be kept
 * in sync with that in `pwd_mkdb'.
 */

#if defined(YP) || defined(HESIOD)
/*
 * _pw_parse
 *	Parses entry using pw_scan(3) (without the trailing \n)
 *	after copying to buf, and fills in pw with corresponding values.
 *	If old is non-zero, entry is in _PASSWORD_OLDFMT.
 *	Returns 1 if parsed successfully, 0 on parse failure.
 */
static int
_pw_parse(const char *entry, struct passwd *pw, char *buf, size_t buflen,
	int old)
{
	int	flags;

	_DIAGASSERT(entry != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buf != NULL);

	if (strlcpy(buf, entry, buflen) >= buflen)
		return 0;
	flags = _PASSWORD_NOWARN;
	if (old)
		flags |= _PASSWORD_OLDFMT;
	return __pw_scan(buf, pw, &flags);
}
#endif /* YP || HESIOD */

/*
 * _pw_opendb
 *	if *db is NULL, dbopen(3) /etc/spwd.db or /etc/pwd.db (depending
 *	upon permissions, etc)
 */
static int
_pw_opendb(DB **db, int *version)
{
	static int	warned;
	DBT		key;
	DBT		value;

	const char	*dbfile = NULL;

	_DIAGASSERT(db != NULL);
	_DIAGASSERT(version != NULL);
	if (*db != NULL)					/* open *db */
		return NS_SUCCESS;

	if (geteuid() == 0) {
		dbfile = _PATH_SMP_DB;
		*db = dbopen(dbfile, O_RDONLY, 0, DB_HASH, NULL);
	}
	if (*db == NULL) {
		dbfile = _PATH_MP_DB;
		*db = dbopen(dbfile, O_RDONLY, 0, DB_HASH, NULL);
	}
	if (*db == NULL) {
		if (!warned) {
			int	serrno = errno;
			syslog(LOG_ERR, "%s: %m", dbfile);
			errno = serrno;
		}
		warned = 1;
		return NS_UNAVAIL;
	}
	key.data = __UNCONST("VERSION");
	key.size = strlen((char *)key.data) + 1;
	switch ((*(*db)->get)(*db, &key, &value, 0)) {
	case 0:
		if (sizeof(*version) != value.size)
			return NS_UNAVAIL;
		(void)memcpy(version, value.data, value.size);
		break;			/* found */
	case 1:
		*version = 0;		/* not found */
		break;
	case -1:
		return NS_UNAVAIL;	/* error in db routines */
	default:
		abort();
	}
	return NS_SUCCESS;
}

/*
 * _pw_getkey
 *	Lookup key in *db, filling in pw
 *	with the result, allocating memory from buffer (size buflen).
 *	(The caller may point key.data to buffer on entry; the contents
 *	of key.data will be invalid on exit.)
 */
static int
_pw_getkey(DB *db, DBT *key,
	struct passwd *pw, char *buffer, size_t buflen, int *pwflags,
	int version)
{
	char		*p, *t;
	DBT		data;

	_DIAGASSERT(db != NULL);
	_DIAGASSERT(key != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	/* pwflags may be NULL (if we don't care about them */

	if (db == NULL)			/* this shouldn't happen */
		return NS_UNAVAIL;

	switch ((db->get)(db, key, &data, 0)) {
	case 0:
		break;			/* found */
	case 1:
		return NS_NOTFOUND;	/* not found */
	case -1:
		return NS_UNAVAIL;	/* error in db routines */
	default:
		abort();
	}

	p = (char *)data.data;
	if (data.size > buflen) {
		errno = ERANGE;
		return NS_UNAVAIL;
	}

			/*
			 * THE DECODING BELOW MUST MATCH THAT IN pwd_mkdb.
			 */
	t = buffer;
#define MACRO(a)	do { a } while (0)
#define	EXPAND(e)	MACRO(e = t; while ((*t++ = *p++));)
#define	SCALAR(v)	MACRO(memmove(&(v), p, sizeof v); p += sizeof v;)
	EXPAND(pw->pw_name);
	EXPAND(pw->pw_passwd);
	SCALAR(pw->pw_uid);
	SCALAR(pw->pw_gid);
	if (version == 0) {
		int32_t tmp;
		SCALAR(tmp);
		pw->pw_change = tmp;
	} else
		SCALAR(pw->pw_change);
	EXPAND(pw->pw_class);
	EXPAND(pw->pw_gecos);
	EXPAND(pw->pw_dir);
	EXPAND(pw->pw_shell);
	if (version == 0) {
		int32_t tmp;
		SCALAR(tmp);
		pw->pw_expire = tmp;
	} else
		SCALAR(pw->pw_expire);
	if (pwflags) {
		/* See if there's any data left.  If so, read in flags. */
		if (data.size > (size_t) (p - (char *)data.data)) {
			SCALAR(*pwflags);
		} else {				/* default */
			*pwflags = _PASSWORD_NOUID|_PASSWORD_NOGID;
		}
	}

	return NS_SUCCESS;
}

/*
 * _pw_memfrombuf
 *	Obtain want bytes from buffer (of size buflen) and return a pointer
 *	to the available memory after adjusting buffer/buflen.
 *	Returns NULL if there is insufficient space.
 */
static char *
_pw_memfrombuf(size_t want, char **buffer, size_t *buflen)
{
	char	*rv;

	if (want > *buflen) {
		errno = ERANGE;
		return NULL;
	}
	rv = *buffer;
	*buffer += want;
	*buflen -= want;
	return rv;
}

/*
 * _pw_copy
 *	Copy the contents of frompw to pw; memory for strings
 *	and arrays will be allocated from buf (of size buflen).
 *	If proto != NULL, use various fields in proto in preference to frompw.
 *	Returns 1 if copied successfully, 0 on copy failure.
 *	NOTE: frompw must not use buf for its own pointers.
 */
static int
_pw_copy(const struct passwd *frompw, struct passwd *pw,
	char *buf, size_t buflen, const struct passwd *protopw, int protoflags)
{
	size_t	count;
	int	useproto;

	_DIAGASSERT(frompw != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buf != NULL);
	/* protopw may be NULL */

	useproto = protopw && protopw->pw_name;

#define	COPYSTR(to, from) \
	do { \
		count = strlen((from)); \
		(to) = _pw_memfrombuf(count+1, &buf, &buflen); \
		if ((to) == NULL) \
			return 0; \
		memmove((to), (from), count); \
		to[count] = '\0'; \
	} while (0)	/* LINTED */

#define	COPYFIELD(field)	COPYSTR(pw->field, frompw->field)

#define	COPYPROTOFIELD(field)	COPYSTR(pw->field, \
		(useproto && *protopw->field ? protopw->field : frompw->field))

	COPYFIELD(pw_name);

#ifdef PW_OVERRIDE_PASSWD
	COPYPROTOFIELD(pw_passwd);
#else
	COPYFIELD(pw_passwd);
#endif

	if (useproto && !(protoflags & _PASSWORD_NOUID))
		pw->pw_uid = protopw->pw_uid;
	else
		pw->pw_uid = frompw->pw_uid;

	if (useproto && !(protoflags & _PASSWORD_NOGID))
		pw->pw_gid = protopw->pw_gid;
	else
		pw->pw_gid = frompw->pw_gid;

	pw->pw_change = frompw->pw_change;
	COPYFIELD(pw_class);
	COPYPROTOFIELD(pw_gecos);
	COPYPROTOFIELD(pw_dir);
	COPYPROTOFIELD(pw_shell);

#undef COPYSTR
#undef COPYFIELD
#undef COPYPROTOFIELD

	return 1;
}


		/*
		 *	files methods
		 */

	/* state shared between files methods */
struct files_state {
	int	 stayopen;		/* see getpassent(3) */
	DB	*db;			/* passwd file handle */
	int	 keynum;		/* key counter, -1 if no more */
	int	 version;
};

static struct files_state	_files_state;
					/* storage for non _r functions */
static struct passwd		_files_passwd;
static char			_files_passwdbuf[_GETPW_R_SIZE_MAX];

static int
_files_start(struct files_state *state)
{
	int	rv;

	_DIAGASSERT(state != NULL);

	state->keynum = 0;
	rv = _pw_opendb(&state->db, &state->version);
	if (rv != NS_SUCCESS)
		return rv;
	return NS_SUCCESS;
}

static int
_files_end(struct files_state *state)
{

	_DIAGASSERT(state != NULL);

	state->keynum = 0;
	if (state->db) {
		(void)(state->db->close)(state->db);
		state->db = NULL;
	}
	return NS_SUCCESS;
}

/*
 * _files_pwscan
 *	Search state->db for the next desired entry.
 *	If search is _PW_KEYBYNUM, look for state->keynum.
 *	If search is _PW_KEYBYNAME, look for name.
 *	If search is _PW_KEYBYUID, look for uid.
 *	Sets *retval to the errno if the result is not NS_SUCCESS
 *	or NS_NOTFOUND.
 */
static int
_files_pwscan(int *retval, struct passwd *pw, char *buffer, size_t buflen,
	struct files_state *state, int search, const char *name, uid_t uid)
{
	const void	*from;
	size_t		 fromlen;
	DBT		 key;
	int		 rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(state != NULL);
	/* name is NULL to indicate searching for uid */

	*retval = 0;

	if (state->db == NULL) {	/* only start if file not open yet */
		rv = _files_start(state);
		if (rv != NS_SUCCESS)
			goto filespwscan_out;
	}

	for (;;) {				/* search for a match */
		switch (search) {
		case _PW_KEYBYNUM:
			if (state->keynum == -1)
				return NS_NOTFOUND;	/* no more records */
			state->keynum++;
			from = &state->keynum;
			fromlen = sizeof(state->keynum);
			break;
		case _PW_KEYBYNAME:
			from = name;
			fromlen = strlen(name);
			break;
		case _PW_KEYBYUID:
			from = &uid;
			fromlen = sizeof(uid);
			break;
		default:
			abort();
		}

		if (buflen <= fromlen) {		/* buffer too small */
			*retval = ERANGE;
			return NS_UNAVAIL;
		}
		buffer[0] = search;			/* setup key */
		memmove(buffer + 1, from, fromlen);
		key.size = fromlen + 1;
		key.data = (u_char *)buffer;

							/* search for key */
		rv = _pw_getkey(state->db, &key, pw, buffer, buflen, NULL,
		    state->version);
		if (rv != NS_SUCCESS)			/* no match */
			break;
		if (pw->pw_name[0] == '+' || pw->pw_name[0] == '-') {
						/* if a compat line */
			if (search == _PW_KEYBYNUM)
				continue;	/* read next if pwent */
			rv = NS_NOTFOUND;	/* don't match if pw{nam,uid} */
			break;
		}
		break;
	}

	if (rv == NS_NOTFOUND && search == _PW_KEYBYNUM)
		state->keynum = -1;		/* flag `no more records' */

	if (rv == NS_SUCCESS) {
		if ((search == _PW_KEYBYUID && pw->pw_uid != uid) ||
		    (search == _PW_KEYBYNAME && strcmp(pw->pw_name, name) != 0))
			rv = NS_NOTFOUND;
	}

 filespwscan_out:
	if (rv != NS_SUCCESS && rv != NS_NOTFOUND)
		*retval = errno;
	return rv;
}

/*ARGSUSED*/
static int
_files_setpwent(void *nsrv, void *nscb, va_list ap)
{

	_files_state.stayopen = 0;
	return _files_start(&_files_state);
}

/*ARGSUSED*/
static int
_files_setpassent(void *nsrv, void *nscb, va_list ap)
{
	int	*retval		= va_arg(ap, int *);
	int	 stayopen	= va_arg(ap, int);

	int	rv;

	_files_state.stayopen = stayopen;
	rv = _files_start(&_files_state);
	*retval = (rv == NS_SUCCESS);
	return rv;
}

/*ARGSUSED*/
static int
_files_endpwent(void *nsrv, void *nscb, va_list ap)
{

	_files_state.stayopen = 0;
	return _files_end(&_files_state);
}

/*ARGSUSED*/
static int
_files_getpwent(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _files_pwscan(&rerror, &_files_passwd,
	    _files_passwdbuf, sizeof(_files_passwdbuf),
	    &_files_state, _PW_KEYBYNUM, NULL, 0);
	if (rv == NS_SUCCESS)
		*retval = &_files_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_files_getpwent_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	int	rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	rv = _files_pwscan(retval, pw, buffer, buflen, &_files_state,
	    _PW_KEYBYNUM, NULL, 0);
	if (rv == NS_SUCCESS)
		*result = pw;
	else
		*result = NULL;
	return rv;
}

/*ARGSUSED*/
static int
_files_getpwnam(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	const char	*name	= va_arg(ap, const char *);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _files_start(&_files_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = _files_pwscan(&rerror, &_files_passwd,
	    _files_passwdbuf, sizeof(_files_passwdbuf),
	    &_files_state, _PW_KEYBYNAME, name, 0);
	if (!_files_state.stayopen)
		_files_end(&_files_state);
	if (rv == NS_SUCCESS)
		*retval = &_files_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_files_getpwnam_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	const char	*name	= va_arg(ap, const char *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct files_state state;
	int	rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = _files_pwscan(retval, pw, buffer, buflen, &state,
	    _PW_KEYBYNAME, name, 0);
	_files_end(&state);
	if (rv == NS_SUCCESS)
		*result = pw;
	return rv;
}

/*ARGSUSED*/
static int
_files_getpwuid(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	uid_t		 uid	= va_arg(ap, uid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _files_start(&_files_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = _files_pwscan(&rerror, &_files_passwd,
	    _files_passwdbuf, sizeof(_files_passwdbuf),
	    &_files_state, _PW_KEYBYUID, NULL, uid);
	if (!_files_state.stayopen)
		_files_end(&_files_state);
	if (rv == NS_SUCCESS)
		*retval = &_files_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_files_getpwuid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	uid_t		 uid	= va_arg(ap, uid_t);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct files_state state;
	int	rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = _files_pwscan(retval, pw, buffer, buflen, &state,
	    _PW_KEYBYUID, NULL, uid);
	_files_end(&state);
	if (rv == NS_SUCCESS)
		*result = pw;
	return rv;
}


#ifdef HESIOD
		/*
		 *	dns methods
		 */

	/* state shared between dns methods */
struct dns_state {
	int	 stayopen;		/* see getpassent(3) */
	void	*context;		/* Hesiod context */
	int	 num;			/* passwd index, -1 if no more */
};

static struct dns_state		_dns_state;
					/* storage for non _r functions */
static struct passwd		_dns_passwd;
static char			_dns_passwdbuf[_GETPW_R_SIZE_MAX];

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;
}

/*
 * _dns_pwscan
 *	Look for the Hesiod name provided in buffer in the NULL-terminated
 *	list of zones,
 *	and decode into pw/buffer/buflen.
 */
static int
_dns_pwscan(int *retval, struct passwd *pw, char *buffer, size_t buflen,
	struct dns_state *state, const char **zones)
{
	const char	**curzone;
	char		**hp, *ep;
	int		rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(state != NULL);
	_DIAGASSERT(zones != NULL);

	*retval = 0;

	if (state->context == NULL) {	/* only start if Hesiod not setup */
		rv = _dns_start(state);
		if (rv != NS_SUCCESS)
			return rv;
	}

	hp = NULL;
	rv = NS_NOTFOUND;

	for (curzone = zones; *curzone; curzone++) {	/* search zones */
		hp = hesiod_resolve(state->context, buffer, *curzone);
		if (hp != NULL)
			break;
		if (errno != ENOENT) {
			rv = NS_UNAVAIL;
			goto dnspwscan_out;
		}
	}
	if (*curzone == NULL)
		goto dnspwscan_out;

	if ((ep = strchr(hp[0], '\n')) != NULL)
		*ep = '\0';				/* clear trailing \n */
	if (_pw_parse(hp[0], pw, buffer, buflen, 1))	/* validate line */
		rv = NS_SUCCESS;
	else
		rv = NS_UNAVAIL;

 dnspwscan_out:
	if (rv != NS_SUCCESS && rv != NS_NOTFOUND)
		*retval = errno;
	if (hp)
		hesiod_free_list(state->context, hp);
	return rv;
}

/*ARGSUSED*/
static int
_dns_setpwent(void *nsrv, void *nscb, va_list ap)
{

	_dns_state.stayopen = 0;
	return _dns_start(&_dns_state);
}

/*ARGSUSED*/
static int
_dns_setpassent(void *nsrv, void *nscb, va_list ap)
{
	int	*retval		= va_arg(ap, int *);
	int	 stayopen	= va_arg(ap, int);

	int	rv;

	_dns_state.stayopen = stayopen;
	rv = _dns_start(&_dns_state);
	*retval = (rv == NS_SUCCESS);
	return rv;
}

/*ARGSUSED*/
static int
_dns_endpwent(void *nsrv, void *nscb, va_list ap)
{

	_dns_state.stayopen = 0;
	return _dns_end(&_dns_state);
}

/*ARGSUSED*/
static int
_dns_getpwent(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);

	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;
	}

 next_dns_entry:
	hp = NULL;
	rv = NS_NOTFOUND;

							/* find passwd-NNN */
	snprintf(_dns_passwdbuf, sizeof(_dns_passwdbuf),
	    "passwd-%u", _dns_state.num);
	_dns_state.num++;

	hp = hesiod_resolve(_dns_state.context, _dns_passwdbuf, "passwd");
	if (hp == NULL) {
		if (errno == ENOENT)
			_dns_state.num = -1;
		else
			rv = NS_UNAVAIL;
	} else {
		if ((ep = strchr(hp[0], '\n')) != NULL)
			*ep = '\0';			/* clear trailing \n */
							/* validate line */
		if (_pw_parse(hp[0], &_dns_passwd,
		    _dns_passwdbuf, sizeof(_dns_passwdbuf), 1))
			rv = NS_SUCCESS;
		else {				/* dodgy entry, try again */
			hesiod_free_list(_dns_state.context, hp);
			goto next_dns_entry;
		}
	}

	if (hp)
		hesiod_free_list(_dns_state.context, hp);
	if (rv == NS_SUCCESS)
		*retval = &_dns_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_dns_getpwent_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	char	**hp, *ep;
	int	  rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*retval = 0;

	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;
	}

 next_dns_entry:
	hp = NULL;
	rv = NS_NOTFOUND;

							/* find passwd-NNN */
	snprintf(buffer, buflen, "passwd-%u", _dns_state.num);
	_dns_state.num++;

	hp = hesiod_resolve(_dns_state.context, buffer, "passwd");
	if (hp == NULL) {
		if (errno == ENOENT)
			_dns_state.num = -1;
		else
			rv = NS_UNAVAIL;
	} else {
		if ((ep = strchr(hp[0], '\n')) != NULL)
			*ep = '\0';			/* clear trailing \n */
							/* validate line */
		if (_pw_parse(hp[0], pw, buffer, buflen, 1))
			rv = NS_SUCCESS;
		else {				/* dodgy entry, try again */
			hesiod_free_list(_dns_state.context, hp);
			goto next_dns_entry;
		}
	}

	if (hp)
		hesiod_free_list(_dns_state.context, hp);
	if (rv == NS_SUCCESS)
		*result = pw;
	else
		*result = NULL;
	return rv;
}

static const char *_dns_uid_zones[] = {
	"uid",
	"passwd",
	NULL
};

/*ARGSUSED*/
static int
_dns_getpwuid(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	uid_t		 uid	= va_arg(ap, uid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _dns_start(&_dns_state);
	if (rv != NS_SUCCESS)
		return rv;
	snprintf(_dns_passwdbuf, sizeof(_dns_passwdbuf),
	    "%u", (unsigned int)uid);
	rv = _dns_pwscan(&rerror, &_dns_passwd,
	    _dns_passwdbuf, sizeof(_dns_passwdbuf),
	    &_dns_state, _dns_uid_zones);
	if (!_dns_state.stayopen)
		_dns_end(&_dns_state);
	if (rv == NS_SUCCESS && uid == _dns_passwd.pw_uid)
		*retval = &_dns_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_dns_getpwuid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	uid_t		 uid	= va_arg(ap, uid_t);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct dns_state state;
	int	rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	memset(&state, 0, sizeof(state));
	snprintf(buffer, buflen, "%u", (unsigned int)uid);
	rv = _dns_pwscan(retval, pw, buffer, buflen, &state, _dns_uid_zones);
	_dns_end(&state);
	if (rv != NS_SUCCESS)
		return rv;
	if (uid == pw->pw_uid) {
		*result = pw;
		return NS_SUCCESS;
	} else
		return NS_NOTFOUND;
}

static const char *_dns_nam_zones[] = {
	"passwd",
	NULL
};

/*ARGSUSED*/
static int
_dns_getpwnam(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	const char	*name	= va_arg(ap, const char *);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _dns_start(&_dns_state);
	if (rv != NS_SUCCESS)
		return rv;
	snprintf(_dns_passwdbuf, sizeof(_dns_passwdbuf), "%s", name);
	rv = _dns_pwscan(&rerror, &_dns_passwd,
	    _dns_passwdbuf, sizeof(_dns_passwdbuf),
	    &_dns_state, _dns_nam_zones);
	if (!_dns_state.stayopen)
		_dns_end(&_dns_state);
	if (rv == NS_SUCCESS && strcmp(name, _dns_passwd.pw_name) == 0)
		*retval = &_dns_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_dns_getpwnam_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	const char	*name	= va_arg(ap, const char *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct dns_state state;
	int	rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	memset(&state, 0, sizeof(state));
	snprintf(buffer, buflen, "%s", name);
	rv = _dns_pwscan(retval, pw, buffer, buflen, &state, _dns_nam_zones);
	_dns_end(&state);
	if (rv != NS_SUCCESS)
		return rv;
	if (strcmp(name, pw->pw_name) == 0) {
		*result = pw;
		return NS_SUCCESS;
	} else
		return NS_NOTFOUND;
}

#endif /* HESIOD */


#ifdef YP
		/*
		 *	nis methods
		 */
	/* state shared between nis methods */
struct nis_state {
	int		 stayopen;	/* see getpassent(3) */
	char		*domain;	/* NIS domain */
	int		 done;		/* non-zero if search exhausted */
	char		*current;	/* current first/next match */
	int		 currentlen;	/* length of _nis_current */
	enum {				/* shadow map type */
		NISMAP_UNKNOWN = 0,	/*  unknown ... */
		NISMAP_NONE,		/*  none: use "passwd.by*" */
		NISMAP_ADJUNCT,		/*  pw_passwd from "passwd.adjunct.*" */
		NISMAP_MASTER		/*  all from "master.passwd.by*" */
	}		 maptype;
};

static struct nis_state		_nis_state;
					/* storage for non _r functions */
static struct passwd		_nis_passwd;
static char			_nis_passwdbuf[_GETPW_R_SIZE_MAX];

static const char __nis_pw_n_1[] = "master.passwd.byname";
static const char __nis_pw_n_2[] = "passwd.byname";
static const char __nis_pw_u_1[] = "master.passwd.byuid";
static const char __nis_pw_u_2[] = "passwd.byuid";

static const char * const __nis_pw_n_map[4] = { __nis_pw_n_2, __nis_pw_n_2, __nis_pw_n_2, __nis_pw_n_1 };
static const char * const __nis_pw_u_map[4] = { __nis_pw_u_2, __nis_pw_u_2, __nis_pw_u_2, __nis_pw_u_1 };

	/* macros for deciding which NIS maps to use. */
#define	PASSWD_BYNAME(x)	((x)->maptype == NISMAP_MASTER ? __nis_pw_n_1 : __nis_pw_n_2)
#define	PASSWD_BYUID(x)		((x)->maptype == NISMAP_MASTER ? __nis_pw_u_1 : __nis_pw_u_2)

static int
_nis_start(struct nis_state *state)
{

	_DIAGASSERT(state != NULL);

	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;
		default:
			return NS_UNAVAIL;
		}
	}

				/* determine where to get pw_passwd from */
	if (state->maptype == NISMAP_UNKNOWN) {
		int	r, order;

		state->maptype = NISMAP_NONE;	/* default to no adjunct */
		if (geteuid() != 0)		/* non-root can't use adjunct */
			return NS_SUCCESS;

						/* look for "master.passwd.*" */
		r = yp_order(state->domain, "master.passwd.byname", &order);
		if (r == 0) {
			state->maptype = NISMAP_MASTER;
			return NS_SUCCESS;
		}

			/* master.passwd doesn't exist, try passwd.adjunct */
		if (r == YPERR_MAP) {
			r = yp_order(state->domain, "passwd.adjunct.byname",
			    &order);
			if (r == 0)
				state->maptype = NISMAP_ADJUNCT;
		}
	}
	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;
	state->maptype = NISMAP_UNKNOWN;
	return NS_SUCCESS;
}

/*
 * nis_parse
 *	wrapper to _pw_parse that obtains the real password from the
 *	"passwd.adjunct.byname" NIS map if the maptype is NISMAP_ADJUNCT.
 */
static int
_nis_parse(const char *entry, struct passwd *pw, char *buf, size_t buflen,
	struct nis_state *state)
{
	size_t	elen;

	_DIAGASSERT(entry != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buf != NULL);
	_DIAGASSERT(state != NULL);

	elen = strlen(entry) + 1;
	if (elen >= buflen)
		return 0;
	if (! _pw_parse(entry, pw, buf, buflen,
	    !(state->maptype == NISMAP_MASTER)))
		return 0;

	if ((state->maptype == NISMAP_ADJUNCT) &&
	    (strstr(pw->pw_passwd, "##") != NULL)) {
		char	*data;
		int	datalen;

		if (yp_match(state->domain, "passwd.adjunct.byname",
		    pw->pw_name, (int)strlen(pw->pw_name),
		    &data, &datalen) == 0) {
			char	*bp, *ep;
						/* skip name to get password */
			ep = data;
			if (strsep(&ep, ":") != NULL &&
			    (bp = strsep(&ep, ":")) != NULL) {
					/* store new pw_passwd after entry */
				if (strlcpy(buf + elen, bp, buflen - elen) >=
				    buflen - elen) {
					free(data);
					return 0;
				}
				pw->pw_passwd = &buf[elen];
			}
			free(data);
		}
	}

	return 1;
}


/*
 * _nis_pwscan
 *	Look for the yp key provided in buffer from map,
 *	and decode into pw/buffer/buflen.
 */
static int
_nis_pwscan(int *retval, struct passwd *pw, char *buffer, size_t buflen,
	struct nis_state *state, const char * const *map_arr, size_t nmaps)
{
	char	*data;
	int	nisr, rv, datalen;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(state != NULL);
	_DIAGASSERT(map_arr != NULL);

	*retval = 0;

	if (state->domain == NULL) {	/* only start if NIS not setup */
		rv = _nis_start(state);
		if (rv != NS_SUCCESS)
			return rv;
	}

	data = NULL;
	rv = NS_NOTFOUND;
	_DIAGASSERT(state->maptype != NISMAP_UNKNOWN &&
		    (unsigned)state->maptype < nmaps);

							/* search map */
	nisr = yp_match(state->domain, map_arr[state->maptype], buffer, (int)strlen(buffer),
	    &data, &datalen);
	switch (nisr) {
	case 0:
		data[datalen] = '\0';			/* clear trailing \n */
		if (_nis_parse(data, pw, buffer, buflen, state))
			rv = NS_SUCCESS;		/* validate line */
		else
			rv = NS_UNAVAIL;
		break;
	case YPERR_KEY:
		break;
	default:
		rv = NS_UNAVAIL;
		break;
	}

	if (rv != NS_SUCCESS && rv != NS_NOTFOUND)
		*retval = errno;
	if (data)
		free(data);
	return rv;
}

/*ARGSUSED*/
static int
_nis_setpwent(void *nsrv, void *nscb, va_list ap)
{

	_nis_state.stayopen = 0;
	return _nis_start(&_nis_state);
}

/*ARGSUSED*/
static int
_nis_setpassent(void *nsrv, void *nscb, va_list ap)
{
	int	*retval		= va_arg(ap, int *);
	int	 stayopen	= va_arg(ap, int);

	int	rv;

	_nis_state.stayopen = stayopen;
	rv = _nis_start(&_nis_state);
	*retval = (rv == NS_SUCCESS);
	return rv;
}

/*ARGSUSED*/
static int
_nis_endpwent(void *nsrv, void *nscb, va_list ap)
{

	return _nis_end(&_nis_state);
}


/*ARGSUSED*/
static int
_nis_getpwent(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);

	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;
	}

 next_nis_entry:
	key = NULL;
	data = NULL;
	rv = NS_NOTFOUND;

	if (_nis_state.current) {			/* already searching */
		nisr = yp_next(_nis_state.domain, PASSWD_BYNAME(&_nis_state),
		    _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:
			_nis_state.done = 1;
			goto nisent_out;
		default:
			rv = NS_UNAVAIL;
			goto nisent_out;
		}
	} else {					/* new search */
		if (yp_first(_nis_state.domain, PASSWD_BYNAME(&_nis_state),
		    &_nis_state.current, &_nis_state.currentlen,
		    &data, &datalen)) {
			rv = NS_UNAVAIL;
			goto nisent_out;
		}
	}

	data[datalen] = '\0';				/* clear trailing \n */
							/* validate line */
	if (_nis_parse(data, &_nis_passwd,
	    _nis_passwdbuf, sizeof(_nis_passwdbuf), &_nis_state))
		rv = NS_SUCCESS;
	else {					/* dodgy entry, try again */
		free(data);
		goto next_nis_entry;
	}

 nisent_out:
	if (key)
		free(key);
	if (data)
		free(data);
	if (rv == NS_SUCCESS)
		*retval = &_nis_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_nis_getpwent_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	char	*key, *data;
	int	keylen, datalen, rv, nisr;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*retval = 0;

	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;
	}

 next_nis_entry:
	key = NULL;
	data = NULL;
	rv = NS_NOTFOUND;

	if (_nis_state.current) {			/* already searching */
		nisr = yp_next(_nis_state.domain, PASSWD_BYNAME(&_nis_state),
		    _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:
			_nis_state.done = 1;
			goto nisent_out;
		default:
			rv = NS_UNAVAIL;
			goto nisent_out;
		}
	} else {					/* new search */
		if (yp_first(_nis_state.domain, PASSWD_BYNAME(&_nis_state),
		    &_nis_state.current, &_nis_state.currentlen,
		    &data, &datalen)) {
			rv = NS_UNAVAIL;
			goto nisent_out;
		}
	}

	data[datalen] = '\0';				/* clear trailing \n */
							/* validate line */
	if (_nis_parse(data, pw, buffer, buflen, &_nis_state))
		rv = NS_SUCCESS;
	else {					/* dodgy entry, try again */
		if (key)
			free(key);
		free(data);
		goto next_nis_entry;
	}

 nisent_out:
	if (key)
		free(key);
	if (data)
		free(data);
	if (rv == NS_SUCCESS)
		*result = pw;
	else
		*result = NULL;
	return rv;
}

/*ARGSUSED*/
static int
_nis_getpwuid(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	uid_t		 uid	= va_arg(ap, uid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _nis_start(&_nis_state);
	if (rv != NS_SUCCESS)
		return rv;
	snprintf(_nis_passwdbuf, sizeof(_nis_passwdbuf), "%u", (unsigned int)uid);
	rv = _nis_pwscan(&rerror, &_nis_passwd,
	    _nis_passwdbuf, sizeof(_nis_passwdbuf),
	    &_nis_state, __nis_pw_u_map, __arraycount(__nis_pw_u_map));
	if (!_nis_state.stayopen)
		_nis_end(&_nis_state);
	if (rv == NS_SUCCESS && uid == _nis_passwd.pw_uid)
		*retval = &_nis_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_nis_getpwuid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	uid_t		 uid	= va_arg(ap, uid_t);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct nis_state state;
	int	rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	snprintf(buffer, buflen, "%u", (unsigned int)uid);
/* remark: we run under a global mutex inside of this module ... */
	if (_nis_state.stayopen)
	  { /* use global state only if stayopen is set - otherwise we would blow up getpwent_r() ... */
	    rv = _nis_pwscan(retval, pw, buffer, buflen,
		&_nis_state, __nis_pw_u_map, __arraycount(__nis_pw_u_map));
	  }
	else
	  { /* keep old semantic if no stayopen set - no need to call _nis_start() here - _nis_pwscan() will do it for us ... */
	    /* use same way as in getgrent.c ... */
	    memset(&state, 0, sizeof(state));
	    rv = _nis_pwscan(retval, pw, buffer, buflen,
		&state, __nis_pw_u_map, __arraycount(__nis_pw_u_map));
	    _nis_end(&state);
	  }
	if (rv != NS_SUCCESS)
		return rv;
	if (uid == pw->pw_uid) {
		*result = pw;
		return NS_SUCCESS;
	} else
		return NS_NOTFOUND;
}

/*ARGSUSED*/
static int
_nis_getpwnam(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	const char	*name	= va_arg(ap, const char *);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _nis_start(&_nis_state);
	if (rv != NS_SUCCESS)
		return rv;
	snprintf(_nis_passwdbuf, sizeof(_nis_passwdbuf), "%s", name);
	rv = _nis_pwscan(&rerror, &_nis_passwd,
	    _nis_passwdbuf, sizeof(_nis_passwdbuf),
	    &_nis_state, __nis_pw_n_map, __arraycount(__nis_pw_n_map));
	if (!_nis_state.stayopen)
		_nis_end(&_nis_state);
	if (rv == NS_SUCCESS && strcmp(name, _nis_passwd.pw_name) == 0)
		*retval = &_nis_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_nis_getpwnam_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	const char	*name	= va_arg(ap, const char *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct nis_state state;
	int	rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	snprintf(buffer, buflen, "%s", name);
/* remark: we run under a global mutex inside of this module ... */
	if (_nis_state.stayopen)
	  { /* use global state only if stayopen is set - otherwise we would blow up getpwent_r() ... */
	    rv = _nis_pwscan(retval, pw, buffer, buflen,
		&_nis_state, __nis_pw_n_map, __arraycount(__nis_pw_n_map));
	  }
	else
	  { /* keep old semantic if no stayopen set - no need to call _nis_start() here - _nis_pwscan() will do it for us ... */
	    /* use same way as in getgrent.c ... */
	    memset(&state, 0, sizeof(state));
	    rv = _nis_pwscan(retval, pw, buffer, buflen,
		&state, __nis_pw_n_map, __arraycount(__nis_pw_n_map));
	    _nis_end(&state);
	  }
	if (rv != NS_SUCCESS)
		return rv;
	if (strcmp(name, pw->pw_name) == 0) {
		*result = pw;
		return NS_SUCCESS;
	} else
		return NS_NOTFOUND;
}

#endif /* YP */


#ifdef _PASSWD_COMPAT
		/*
		 *	compat methods
		 */

	/* state shared between compat methods */

struct compat_state {
	int		 stayopen;	/* see getpassent(3) */
	DB		*db;		/* passwd DB */
	int		 keynum;	/* key counter, -1 if no more */
	enum {				/* current compat mode */
		COMPAT_NOTOKEN = 0,	/*  no compat token present */
		COMPAT_NONE,		/*  parsing normal pwd.db line */
		COMPAT_FULL,		/*  parsing `+' entries */
		COMPAT_USER,		/*  parsing `+name' entries */
		COMPAT_NETGROUP		/*  parsing `+@netgroup' entries */
	}		 mode;
	char		*user;		/* COMPAT_USER "+name" */
	DB		*exclude;	/* compat exclude DB */
	struct passwd	 proto;		/* proto passwd entry */
	char		 protobuf[_GETPW_R_SIZE_MAX];
					/* buffer for proto ptrs */
	int		 protoflags;	/* proto passwd flags */
	int		 version;
};

static struct compat_state	_compat_state;
					/* storage for non _r functions */
static struct passwd		_compat_passwd;
static char			_compat_passwdbuf[_GETPW_R_SIZE_MAX];

static int
_compat_start(struct compat_state *state)
{
	int	rv;

	_DIAGASSERT(state != NULL);

	state->keynum = 0;
	if (state->db == NULL) {		/* not open yet */
		DBT	key, data;
		DBT	pkey, pdata;
		char	bf[MAXLOGNAME];

		rv = _pw_opendb(&state->db, &state->version);
		if (rv != NS_SUCCESS)
			return rv;

		state->mode = COMPAT_NOTOKEN;

		/*
		 *	Determine if the "compat" token is present in pwd.db;
		 *	either "__YP!" or PW_KEYBYNAME+"+".
		 *	Only works if pwd_mkdb installs the token.
		 */
		key.data = (u_char *)__UNCONST(__yp_token);
		key.size = strlen(__yp_token);

		bf[0] = _PW_KEYBYNAME;	 /* Pre-token database support. */
		bf[1] = '+';
		pkey.data = (u_char *)bf;
		pkey.size = 2;

		if ((state->db->get)(state->db, &key, &data, 0) == 0
		    || (state->db->get)(state->db, &pkey, &pdata, 0) == 0)
			state->mode = COMPAT_NONE;
	}
	return NS_SUCCESS;
}

static int
_compat_end(struct compat_state *state)
{

	_DIAGASSERT(state != NULL);

	state->keynum = 0;
	if (state->db) {
		(void)(state->db->close)(state->db);
		state->db = NULL;
	}
	state->mode = COMPAT_NOTOKEN;
	if (state->user)
		free(state->user);
	state->user = NULL;
	if (state->exclude != NULL)
		(void)(state->exclude->close)(state->exclude);
	state->exclude = NULL;
	state->proto.pw_name = NULL;
	state->protoflags = 0;
	return NS_SUCCESS;
}

/*
 * _compat_add_exclude
 *	add the name to the exclude list in state->exclude.
 */
static int
_compat_add_exclude(struct compat_state *state, const char *name)
{
	DBT	key, data;

	_DIAGASSERT(state != NULL);
	_DIAGASSERT(name != NULL);

				/* initialize the exclusion table if needed */
	if (state->exclude == NULL) {
		state->exclude = dbopen(NULL, O_RDWR, 600, DB_HASH, NULL);
		if (state->exclude == NULL)
			return 0;
	}

	key.size = strlen(name);			/* set up the key */
	key.data = (u_char *)__UNCONST(name);

	data.data = NULL;				/* data is nothing */
	data.size = 0;

							/* store it */
	if ((state->exclude->put)(state->exclude, &key, &data, 0) == -1)
		return 0;

	return 1;
}

/*
 * _compat_is_excluded
 *	test if a name is on the compat mode exclude list
 */
static int
_compat_is_excluded(struct compat_state *state, const char *name)
{
	DBT	key, data;

	_DIAGASSERT(state != NULL);
	_DIAGASSERT(name != NULL);

	if (state->exclude == NULL)
		return 0;	/* nothing excluded */

	key.size = strlen(name);			/* set up the key */
	key.data = (u_char *)__UNCONST(name);

	if ((state->exclude->get)(state->exclude, &key, &data, 0) == 0)
		return 1;				/* is excluded */

	return 0;
}


/*
 * _passwdcompat_bad
 *	log an error if "files" or "compat" is specified in
 *	passwd_compat database
 */
/*ARGSUSED*/
static int
_passwdcompat_bad(void *nsrv, void *nscb, va_list ap)
{
	static int warned;

	_DIAGASSERT(nsrv != NULL);
	_DIAGASSERT(nscb != NULL);

	if (!warned) {
		syslog(LOG_ERR,
			"nsswitch.conf passwd_compat database can't use '%s'",
			(char *)nscb);
	}
	warned = 1;
	return NS_UNAVAIL;
}

/*
 * _passwdcompat_setpassent
 *	Call setpassent for all passwd_compat sources.
 */
static int
_passwdcompat_setpassent(int stayopen)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_passwdcompat_bad, "files")
		NS_DNS_CB(_dns_setpassent, NULL)
		NS_NIS_CB(_nis_setpassent, NULL)
		NS_COMPAT_CB(_passwdcompat_bad, "compat")
		NS_NULL_CB
	};

	int	rv, result;

	rv = nsdispatch(NULL, dtab, NSDB_PASSWD_COMPAT, "setpassent",
	    __nsdefaultnis_forceall, &result, stayopen);
	return rv;
}

/*
 * _passwdcompat_endpwent
 *	Call endpwent for all passwd_compat sources.
 */
static int
_passwdcompat_endpwent(void)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_passwdcompat_bad, "files")
		NS_DNS_CB(_dns_endpwent, NULL)
		NS_NIS_CB(_nis_endpwent, NULL)
		NS_COMPAT_CB(_passwdcompat_bad, "compat")
		NS_NULL_CB
	};

	return nsdispatch(NULL, dtab, NSDB_PASSWD_COMPAT, "endpwent",
	    __nsdefaultnis_forceall);
}

/*
 * _passwdcompat_pwscan
 *	When a name lookup in compat mode is required (e.g., `+name', or a
 *	name in `+@netgroup'), look it up in the 'passwd_compat' nsswitch
 *	database.
 *	Fail if passwd_compat contains files or compat.
 */
static int
_passwdcompat_pwscan(struct passwd *pw, char *buffer, size_t buflen,
	int search, const char *name, uid_t uid)
{
	static const ns_dtab compatentdtab[] = {
		NS_FILES_CB(_passwdcompat_bad, "files")
		NS_DNS_CB(_dns_getpwent_r, NULL)
		NS_NIS_CB(_nis_getpwent_r, NULL)
		NS_COMPAT_CB(_passwdcompat_bad, "compat")
		NS_NULL_CB
	};
	static const ns_dtab compatuiddtab[] = {
		NS_FILES_CB(_passwdcompat_bad, "files")
		NS_DNS_CB(_dns_getpwuid_r, NULL)
		NS_NIS_CB(_nis_getpwuid_r, NULL)
		NS_COMPAT_CB(_passwdcompat_bad, "compat")
		NS_NULL_CB
	};
	static const ns_dtab compatnamdtab[] = {
		NS_FILES_CB(_passwdcompat_bad, "files")
		NS_DNS_CB(_dns_getpwnam_r, NULL)
		NS_NIS_CB(_nis_getpwnam_r, NULL)
		NS_COMPAT_CB(_passwdcompat_bad, "compat")
		NS_NULL_CB
	};

	int		rv, crv;
	struct passwd	*cpw;

	switch (search) {
	case _PW_KEYBYNUM:
		rv = nsdispatch(NULL, compatentdtab,
		    NSDB_PASSWD_COMPAT, "getpwent_r", __nsdefaultnis,
		    &crv, pw, buffer, buflen, &cpw);
		break;
	case _PW_KEYBYNAME:
		_DIAGASSERT(name != NULL);
		rv = nsdispatch(NULL, compatnamdtab,
		    NSDB_PASSWD_COMPAT, "getpwnam_r", __nsdefaultnis,
		    &crv, name, pw, buffer, buflen, &cpw);
		break;
	case _PW_KEYBYUID:
		rv = nsdispatch(NULL, compatuiddtab,
		    NSDB_PASSWD_COMPAT, "getpwuid_r", __nsdefaultnis,
		    &crv, uid, pw, buffer, buflen, &cpw);
		break;
	default:
		abort();
		/*NOTREACHED*/
	}
	return rv;
}

/*
 * _compat_pwscan
 *	Search state->db for the next desired entry.
 *	If search is _PW_KEYBYNUM, look for state->keynum.
 *	If search is _PW_KEYBYNAME, look for name.
 *	If search is _PW_KEYBYUID, look for uid.
 *	Sets *retval to the errno if the result is not NS_SUCCESS
 *	or NS_NOTFOUND.
 */
static int
_compat_pwscan(int *retval, struct passwd *pw, char *buffer, size_t buflen,
	struct compat_state *state, int search, const char *name, uid_t uid)
{
	DBT		 key;
	int		 rv, r, pwflags;
	const char	*user, *host, *dom;
	const void	*from;
	size_t		 fromlen;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(state != NULL);
	/* name may be NULL */

	*retval = 0;

	if (state->db == NULL) {
		rv = _compat_start(state);
		if (rv != NS_SUCCESS)
			return rv;
	}
	if (buflen <= 1) {			/* buffer too small */
		*retval = ERANGE;
		return NS_UNAVAIL;
	}

	for (;;) {				/* loop over pwd.db */
		rv = NS_NOTFOUND;
		if (state->mode != COMPAT_NOTOKEN &&
		    state->mode != COMPAT_NONE) {
						/* doing a compat lookup */
			struct passwd	cpw;
			char		cbuf[_GETPW_R_SIZE_MAX];

			switch (state->mode) {

			case COMPAT_FULL:
					/* get next user or lookup by key */
				rv = _passwdcompat_pwscan(&cpw,
				    cbuf, sizeof(cbuf), search, name, uid);
				if (rv != NS_SUCCESS)
					state->mode = COMPAT_NONE;
				break;

			case COMPAT_NETGROUP:
/* XXXREENTRANT: getnetgrent is not thread safe */
					/* get next user from netgroup */
				r = getnetgrent(&host, &user, &dom);
				if (r == 0) {	/* end of group */
					endnetgrent();
					state->mode = COMPAT_NONE;
					break;
				}
				if (!user || !*user)
					break;
				rv = _passwdcompat_pwscan(&cpw,
				    cbuf, sizeof(cbuf),
				    _PW_KEYBYNAME, user, 0);
				break;

			case COMPAT_USER:
					/* get specific user */
				if (state->user == NULL) {
					state->mode = COMPAT_NONE;
					break;
				}
				rv = _passwdcompat_pwscan(&cpw,
				    cbuf, sizeof(cbuf),
				    _PW_KEYBYNAME, state->user, 0);
				free(state->user);
				state->user = NULL;
				state->mode = COMPAT_NONE;
				break;

			case COMPAT_NOTOKEN:
			case COMPAT_NONE:
				abort();

			}
			if (rv != NS_SUCCESS)	/* if not matched, next loop */
				continue;

				/* copy cpw to pw, applying prototype */
			if (! _pw_copy(&cpw, pw, buffer, buflen,
			    &state->proto, state->protoflags)) {
				rv = NS_UNAVAIL;
				break;
			}

			if (_compat_is_excluded(state, pw->pw_name))
				continue;	/* excluded; next loop */

			if ((search == _PW_KEYBYNAME
					&& strcmp(pw->pw_name, name) != 0)
			    || (search == _PW_KEYBYUID && pw->pw_uid != uid)) {
				continue;	/* not specific; next loop */
			}

			break;			/* exit loop if found */
		} else {			/* not a compat line */
			state->proto.pw_name = NULL;
						/* clear prototype */
		}

		if (state->mode == COMPAT_NOTOKEN) {
				/* no compat token; do direct lookup */
			switch (search) {
			case _PW_KEYBYNUM:
				if (state->keynum == -1)  /* no more records */
					return NS_NOTFOUND;
				state->keynum++;
				from = &state->keynum;
				fromlen = sizeof(state->keynum);
				break;
			case _PW_KEYBYNAME:
				from = name;
				fromlen = strlen(name);
				break;
			case _PW_KEYBYUID:
				from = &uid;
				fromlen = sizeof(uid);
				break;
			default:
				abort();
			}
			buffer[0] = search;
		} else {
				/* compat token; do line by line */
			if (state->keynum == -1)  /* no more records */
				return NS_NOTFOUND;
			state->keynum++;
			from = &state->keynum;
			fromlen = sizeof(state->keynum);
			buffer[0] = _PW_KEYBYNUM;
		}

		if (buflen <= fromlen) {		/* buffer too small */
			*retval = ERANGE;
			return NS_UNAVAIL;
		}
		memmove(buffer + 1, from, fromlen);	/* setup key */
		key.size = fromlen + 1;
		key.data = (u_char *)buffer;

		rv = _pw_getkey(state->db, &key, pw, buffer, buflen, &pwflags,
		    state->version);
		if (rv != NS_SUCCESS)		/* stop on error */
			break;

		if (state->mode == COMPAT_NOTOKEN)
			break;			/* stop if no compat token */

		if (pw->pw_name[0] == '+') {
						/* compat inclusion */
			switch(pw->pw_name[1]) {
			case '\0':		/* `+' */
				state->mode = COMPAT_FULL;
						/* reset passwd_compat search */
/* XXXREENTRANT: setpassent is not thread safe ? */
				(void) _passwdcompat_setpassent(_compat_state.stayopen);
				break;
			case '@':		/* `+@netgroup' */
				state->mode = COMPAT_NETGROUP;
						/* reset netgroup search */
/* XXXREENTRANT: setnetgrent is not thread safe */
				setnetgrent(pw->pw_name + 2);
				break;
			default:		/* `+name' */
				state->mode = COMPAT_USER;
				if (state->user)
					free(state->user);
				state->user = strdup(pw->pw_name + 1);
				break;
			}
						/* save the prototype */
			state->protoflags = pwflags;
			if (! _pw_copy(pw, &state->proto, state->protobuf,
			    sizeof(state->protobuf), NULL, 0)) {
				rv = NS_UNAVAIL;
				break;
			}
			continue;		/* loop again after inclusion */
		} else if (pw->pw_name[0] == '-') {
						/* compat exclusion */
			rv = NS_SUCCESS;
			switch(pw->pw_name[1]) {
			case '\0':		/* `-' */
				break;
			case '@':		/* `-@netgroup' */
/* XXXREENTRANT: {set,get,end}netgrent is not thread safe */
				setnetgrent(pw->pw_name + 2);
				while (getnetgrent(&host, &user, &dom)) {
					if (!user || !*user)
						continue;
					if (! _compat_add_exclude(state,user)) {
						rv = NS_UNAVAIL;
						break;
					}
				}
				endnetgrent();
				break;
			default:		/* `-name' */
				if (! _compat_add_exclude(state,
				    pw->pw_name + 1)) {
					rv = NS_UNAVAIL;
				}
				break;
			}
			if (rv != NS_SUCCESS)	/* exclusion failure */
				break;
			continue;		/* loop again after exclusion */
		}
		if (search == _PW_KEYBYNUM ||
		    (search == _PW_KEYBYUID && pw->pw_uid == uid) ||
		    (search == _PW_KEYBYNAME && strcmp(pw->pw_name, name) == 0))
			break;			/* token mode match found */
	}

	if (rv == NS_NOTFOUND &&
	    (search == _PW_KEYBYNUM || state->mode != COMPAT_NOTOKEN))
		state->keynum = -1;		/* flag `no more records' */

	if (rv == NS_SUCCESS) {
		if ((search == _PW_KEYBYNAME && strcmp(pw->pw_name, name) != 0)
		    || (search == _PW_KEYBYUID && pw->pw_uid != uid))
			rv = NS_NOTFOUND;
	}

	if (rv != NS_SUCCESS && rv != NS_NOTFOUND)
		*retval = errno;
	return rv;
}

/*ARGSUSED*/
static int
_compat_setpwent(void *nsrv, void *nscb, va_list ap)
{

					/* force passwd_compat setpwent() */
	(void) _passwdcompat_setpassent(0);

					/* reset state, keep db open */
	_compat_state.stayopen = 0;
	return _compat_start(&_compat_state);
}

/*ARGSUSED*/
static int
_compat_setpassent(void *nsrv, void *nscb, va_list ap)
{
	int	*retval		= va_arg(ap, int *);
	int	 stayopen	= va_arg(ap, int);

	int	rv;

					/* force passwd_compat setpassent() */
	(void) _passwdcompat_setpassent(stayopen);

	_compat_state.stayopen = stayopen;
	rv = _compat_start(&_compat_state);
	*retval = (rv == NS_SUCCESS);
	return rv;
}

/*ARGSUSED*/
static int
_compat_endpwent(void *nsrv, void *nscb, va_list ap)
{

					/* force passwd_compat endpwent() */
	(void) _passwdcompat_endpwent();

					/* reset state, close db */
	_compat_state.stayopen = 0;
	return _compat_end(&_compat_state);
}


/*ARGSUSED*/
static int
_compat_getpwent(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _compat_pwscan(&rerror, &_compat_passwd,
	    _compat_passwdbuf, sizeof(_compat_passwdbuf),
	    &_compat_state, _PW_KEYBYNUM, NULL, 0);
	if (rv == NS_SUCCESS)
		*retval = &_compat_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_compat_getpwent_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	int		rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	rv = _compat_pwscan(retval, pw, buffer, buflen, &_compat_state,
	    _PW_KEYBYNUM, NULL, 0);
	if (rv == NS_SUCCESS)
		*result = pw;
	else
		*result = NULL;
	return rv;
}


/*ARGSUSED*/
static int
_compat_getpwnam(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	const char	*name	= va_arg(ap, const char *);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _compat_start(&_compat_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = _compat_pwscan(&rerror, &_compat_passwd,
	    _compat_passwdbuf, sizeof(_compat_passwdbuf),
	    &_compat_state, _PW_KEYBYNAME, name, 0);
	if (!_compat_state.stayopen)
		_compat_end(&_compat_state);
	if (rv == NS_SUCCESS)
		*retval = &_compat_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_compat_getpwnam_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	const char	*name	= va_arg(ap, const char *);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct compat_state	state;
	int		rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = _compat_pwscan(retval, pw, buffer, buflen, &state,
	    _PW_KEYBYNAME, name, 0);
	_compat_end(&state);
	if (rv == NS_SUCCESS)
		*result = pw;
	return rv;
}

/*ARGSUSED*/
static int
_compat_getpwuid(void *nsrv, void *nscb, va_list ap)
{
	struct passwd	**retval = va_arg(ap, struct passwd **);
	uid_t		 uid	= va_arg(ap, uid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = _compat_start(&_compat_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = _compat_pwscan(&rerror, &_compat_passwd,
	    _compat_passwdbuf, sizeof(_compat_passwdbuf),
	    &_compat_state, _PW_KEYBYUID, NULL, uid);
	if (!_compat_state.stayopen)
		_compat_end(&_compat_state);
	if (rv == NS_SUCCESS)
		*retval = &_compat_passwd;
	return rv;
}

/*ARGSUSED*/
static int
_compat_getpwuid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	uid_t		 uid	= va_arg(ap, uid_t);
	struct passwd	*pw	= va_arg(ap, struct passwd *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct passwd  **result	= va_arg(ap, struct passwd **);

	struct compat_state	state;
	int		rv;

	_DIAGASSERT(retval != NULL);
	_DIAGASSERT(pw != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = _compat_pwscan(retval, pw, buffer, buflen, &state,
	    _PW_KEYBYUID, NULL, uid);
	_compat_end(&state);
	if (rv == NS_SUCCESS)
		*result = pw;
	return rv;
}

#endif /* _PASSWD_COMPAT */


		/*
		 *	public functions
		 */

struct passwd *
getpwent(void)
{
	int		r;
	struct passwd	*retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getpwent, NULL)
		NS_DNS_CB(_dns_getpwent, NULL)
		NS_NIS_CB(_nis_getpwent, NULL)
		NS_COMPAT_CB(_compat_getpwent, NULL)
		NS_NULL_CB
	};

	mutex_lock(&_pwmutex);
	r = nsdispatch(NULL, dtab, NSDB_PASSWD, "getpwent", __nsdefaultcompat,
	    &retval);
	mutex_unlock(&_pwmutex);
	return (r == NS_SUCCESS) ? retval : NULL;
}

int
getpwent_r(struct passwd *pwd, char *buffer, size_t buflen,
    struct passwd **result)
{
	int	r, retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getpwent_r, NULL)
		NS_DNS_CB(_dns_getpwent_r, NULL)
		NS_NIS_CB(_nis_getpwent_r, NULL)
		NS_COMPAT_CB(_compat_getpwent_r, NULL)
		NS_NULL_CB
	};

	_DIAGASSERT(pwd != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	retval = 0;
	mutex_lock(&_pwmutex);
	r = nsdispatch(NULL, dtab, NSDB_PASSWD, "getpwent_r", __nsdefaultcompat,
	    &retval, pwd, buffer, buflen, result);
	mutex_unlock(&_pwmutex);
	switch (r) {
	case NS_SUCCESS:
	case NS_NOTFOUND:
		return 0;
	default:
		return retval;
	}
}


struct passwd *
getpwnam(const char *name)
{
	int		rv;
	struct passwd	*retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getpwnam, NULL)
		NS_DNS_CB(_dns_getpwnam, NULL)
		NS_NIS_CB(_nis_getpwnam, NULL)
		NS_COMPAT_CB(_compat_getpwnam, NULL)
		NS_NULL_CB
	};

	mutex_lock(&_pwmutex);
	rv = nsdispatch(NULL, dtab, NSDB_PASSWD, "getpwnam", __nsdefaultcompat,
	    &retval, name);
	mutex_unlock(&_pwmutex);
	return (rv == NS_SUCCESS) ? retval : NULL;
}

int
getpwnam_r(const char *name, struct passwd *pwd, char *buffer, size_t buflen,
	struct passwd **result)
{
	int	r, retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getpwnam_r, NULL)
		NS_DNS_CB(_dns_getpwnam_r, NULL)
		NS_NIS_CB(_nis_getpwnam_r, NULL)
		NS_COMPAT_CB(_compat_getpwnam_r, NULL)
		NS_NULL_CB
	};

	_DIAGASSERT(name != NULL);
	_DIAGASSERT(pwd != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	retval = 0;
	mutex_lock(&_pwmutex);
	r = nsdispatch(NULL, dtab, NSDB_PASSWD, "getpwnam_r", __nsdefaultcompat,
	    &retval, name, pwd, buffer, buflen, result);
	mutex_unlock(&_pwmutex);
	switch (r) {
	case NS_SUCCESS:
	case NS_NOTFOUND:
		return 0;
	default:
		return retval;
	}
}

struct passwd *
getpwuid(uid_t uid)
{
	int		rv;
	struct passwd	*retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getpwuid, NULL)
		NS_DNS_CB(_dns_getpwuid, NULL)
		NS_NIS_CB(_nis_getpwuid, NULL)
		NS_COMPAT_CB(_compat_getpwuid, NULL)
		NS_NULL_CB
	};

	mutex_lock(&_pwmutex);
	rv = nsdispatch(NULL, dtab, NSDB_PASSWD, "getpwuid", __nsdefaultcompat,
	    &retval, uid);
	mutex_unlock(&_pwmutex);
	return (rv == NS_SUCCESS) ? retval : NULL;
}

int
getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer, size_t buflen,
	struct passwd **result)
{
	int	r, retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getpwuid_r, NULL)
		NS_DNS_CB(_dns_getpwuid_r, NULL)
		NS_NIS_CB(_nis_getpwuid_r, NULL)
		NS_COMPAT_CB(_compat_getpwuid_r, NULL)
		NS_NULL_CB
	};

	_DIAGASSERT(pwd != NULL);
	_DIAGASSERT(buffer != NULL);
	_DIAGASSERT(result != NULL);

	*result = NULL;
	retval = 0;
	mutex_lock(&_pwmutex);
	r = nsdispatch(NULL, dtab, NSDB_PASSWD, "getpwuid_r", __nsdefaultcompat,
	    &retval, uid, pwd, buffer, buflen, result);
	mutex_unlock(&_pwmutex);
	switch (r) {
	case NS_SUCCESS:
	case NS_NOTFOUND:
		return 0;
	default:
		return retval;
	}
}

void
endpwent(void)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_endpwent, NULL)
		NS_DNS_CB(_dns_endpwent, NULL)
		NS_NIS_CB(_nis_endpwent, NULL)
		NS_COMPAT_CB(_compat_endpwent, NULL)
		NS_NULL_CB
	};

	mutex_lock(&_pwmutex);
					/* force all endpwent() methods */
	(void) nsdispatch(NULL, dtab, NSDB_PASSWD, "endpwent",
	    __nsdefaultcompat_forceall);
	mutex_unlock(&_pwmutex);
}

/*ARGSUSED*/
int
setpassent(int stayopen)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_setpassent, NULL)
		NS_DNS_CB(_dns_setpassent, NULL)
		NS_NIS_CB(_nis_setpassent, NULL)
		NS_COMPAT_CB(_compat_setpassent, NULL)
		NS_NULL_CB
	};
	int	rv, retval;

	mutex_lock(&_pwmutex);
					/* force all setpassent() methods */
	rv = nsdispatch(NULL, dtab, NSDB_PASSWD, "setpassent",
	    __nsdefaultcompat_forceall, &retval, stayopen);
	mutex_unlock(&_pwmutex);
	return (rv == NS_SUCCESS) ? retval : 0;
}

void
setpwent(void)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_setpwent, NULL)
		NS_DNS_CB(_dns_setpwent, NULL)
		NS_NIS_CB(_nis_setpwent, NULL)
		NS_COMPAT_CB(_compat_setpwent, NULL)
		NS_NULL_CB
	};

	mutex_lock(&_pwmutex);
					/* force all setpwent() methods */
	(void) nsdispatch(NULL, dtab, NSDB_PASSWD, "setpwent",
	    __nsdefaultcompat_forceall);
	mutex_unlock(&_pwmutex);
}
