/*	$NetBSD: getgrent.c,v 1.62 2008/04/28 20:22:59 martin Exp $	*/

/*-
 * Copyright (c) 1999-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) 1989, 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, 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[] = "@(#)getgrent.c	8.2 (Berkeley) 3/21/94";
#else
__RCSID("$NetBSD: getgrent.c,v 1.62 2008/04/28 20:22:59 martin Exp $");
#endif
#endif /* LIBC_SCCS and not lint */

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

#include <sys/param.h>

#include <assert.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <nsswitch.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

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

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

#include "gr_private.h"

#ifdef __weak_alias
__weak_alias(endgrent,_endgrent)
__weak_alias(getgrent,_getgrent)
__weak_alias(getgrent_r,_getgrent_r)
__weak_alias(getgrgid,_getgrgid)
__weak_alias(getgrgid_r,_getgrgid_r)
__weak_alias(getgrnam,_getgrnam)
__weak_alias(getgrnam_r,_getgrnam_r)
__weak_alias(setgrent,_setgrent)
__weak_alias(setgroupent,_setgroupent)
#endif

#ifdef _REENTRANT
mutex_t	__grmutex = MUTEX_INITIALIZER;
#endif

/*
 * _gr_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 *
_gr_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;
}

/*
 * _gr_parse
 *	Parses entry as a line per group(5) (without the trailing \n)
 *	and fills in grp with corresponding values; memory for strings
 *	and arrays will be allocated from buf (of size buflen).
 *	Returns 1 if parsed successfully, 0 on parse failure.
 */
static int
_gr_parse(const char *entry, struct group *grp, char *buf, size_t buflen)
{
	unsigned long	id;
	const char	*bp;
	char		*ep;
	size_t		count;
	int		memc;

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

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

#if 0
	if (*entry == '+')			/* fail on compat `+' token */
		return 0;
#endif

	count = strcspn(entry, ":");		/* parse gr_name */
	if (entry[count] == '\0')
		return 0;
	COPYTOBUF(grp->gr_name);
	entry += count + 1;

	count = strcspn(entry, ":");		/* parse gr_passwd */
	if (entry[count] == '\0')
		return 0;
	COPYTOBUF(grp->gr_passwd);
	entry += count + 1;

	count = strcspn(entry, ":");		/* parse gr_gid */
	if (entry[count] == '\0')
		return 0;
	id = strtoul(entry, &ep, 10);
	if (id > GID_MAX || *ep != ':')
		return 0;
	grp->gr_gid = (gid_t)id;
	entry += count + 1;

	memc = 1;				/* for final NULL */
	if (*entry != '\0')
		memc++;				/* for first item */
	for (bp = entry; *bp != '\0'; bp++) {
		if (*bp == ',')
			memc++;
	}
				/* grab ALIGNed char **gr_mem from buf */
	ep = _gr_memfrombuf(memc * sizeof(char *) + ALIGNBYTES, &buf, &buflen);
	grp->gr_mem = (char **)ALIGN(ep);
	if (grp->gr_mem == NULL)
		return 0;

	for (memc = 0; *entry != '\0'; memc++) {
		count = strcspn(entry, ",");	/* parse member */
		COPYTOBUF(grp->gr_mem[memc]);
		entry += count;
		if (*entry == ',')
			entry++;
	}

#undef COPYTOBUF

	grp->gr_mem[memc] = NULL;
	return 1;
}

/*
 * _gr_copy
 *	Copy the contents of fromgrp to grp; memory for strings
 *	and arrays will be allocated from buf (of size buflen).
 *	Returns 1 if copied successfully, 0 on copy failure.
 *	NOTE: fromgrp must not use buf for its own pointers.
 */
static int
_gr_copy(struct group *fromgrp, struct group *grp, char *buf, size_t buflen)
{
	char	*ep;
	int	memc;

	_DIAGASSERT(fromgrp != NULL);
	_DIAGASSERT(grp != NULL);
	_DIAGASSERT(buf != NULL);

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

	COPYSTR(grp->gr_name, fromgrp->gr_name);
	COPYSTR(grp->gr_passwd, fromgrp->gr_passwd);
	grp->gr_gid = fromgrp->gr_gid;

	for (memc = 0; fromgrp->gr_mem[memc]; memc++)
		continue;
	memc++;					/* for final NULL */

				/* grab ALIGNed char **gr_mem from buf */
	ep = _gr_memfrombuf(memc * sizeof(char *) + ALIGNBYTES, &buf, &buflen);
	grp->gr_mem = (char **)ALIGN(ep);
	if (grp->gr_mem == NULL)
		return 0;

	for (memc = 0; fromgrp->gr_mem[memc]; memc++) {
		COPYSTR(grp->gr_mem[memc], fromgrp->gr_mem[memc]);
	}

#undef COPYSTR

	grp->gr_mem[memc] = NULL;
	return 1;
}

		/*
		 *	files methods
		 */

int
__grstart_files(struct __grstate_files *state)
{

	_DIAGASSERT(state != NULL);

	if (state->fp == NULL) {
		state->fp = fopen(_PATH_GROUP, "r");
		if (state->fp == NULL)
			return NS_UNAVAIL;
	} else {
		rewind(state->fp);
	}
	return NS_SUCCESS;
}

int
__grend_files(struct __grstate_files *state)
{

	_DIAGASSERT(state != NULL);

	if (state->fp) {
		(void) fclose(state->fp);
		state->fp = NULL;
	}
	return NS_SUCCESS;
}

/*
 * __grscan_files
 *	Scan state->fp for the next desired entry.
 *	If search is zero, return the next entry.
 *	If search is non-zero, look for a specific name (if name != NULL),
 *	or a specific gid (if name == NULL).
 *	Sets *retval to the errno if the result is not NS_SUCCESS
 *	or NS_NOTFOUND.
 */
int
__grscan_files(int *retval, struct group *grp, char *buffer, size_t buflen,
	struct __grstate_files *state, int search, const char *name, gid_t gid)
{
	int	rv;
	char	filebuf[_GETGR_R_SIZE_MAX], *ep;

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

	*retval = 0;

	if (state->fp == NULL) {	/* only start if file not open yet */
		rv = __grstart_files(state);
		if (rv != NS_SUCCESS)
			goto filesgrscan_out;
	}

	rv = NS_NOTFOUND;

							/* scan line by line */
	while (fgets(filebuf, sizeof(filebuf), state->fp) != NULL) {
		ep = strchr(filebuf, '\n');
		if (ep == NULL) {	/* skip lines that are too big */
			int ch;

			while ((ch = getc(state->fp)) != '\n' && ch != EOF)
				continue;
			continue;
		}
		*ep = '\0';				/* clear trailing \n */

		if (filebuf[0] == '+')			/* skip compat line */
			continue;

							/* validate line */
		if (! _gr_parse(filebuf, grp, buffer, buflen)) {
			continue;			/* skip bad lines */
		}
		if (! search) {				/* just want this one */
			rv = NS_SUCCESS;
			break;
		}
							/* want specific */
		if ((name && strcmp(name, grp->gr_name) == 0) ||
		    (!name && gid == grp->gr_gid)) {
			rv = NS_SUCCESS;
			break;
		}
	}

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


static struct __grstate_files	_files_state;
					/* storage for non _r functions */
static struct group		_files_group;
static char			_files_groupbuf[_GETGR_R_SIZE_MAX];

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

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

/*ARGSUSED*/
static int
_files_setgroupent(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 = __grstart_files(&_files_state);
	*retval = (rv == NS_SUCCESS);
	return rv;
}

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

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

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

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grscan_files(&rerror, &_files_group,
	    _files_groupbuf, sizeof(_files_groupbuf),
	    &_files_state, 0, NULL, 0);
	if (rv == NS_SUCCESS)
		*retval = &_files_group;
	return rv;
}

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

	int	rv;

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

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

/*ARGSUSED*/
static int
_files_getgrgid(void *nsrv, void *nscb, va_list ap)
{
	struct group	**retval = va_arg(ap, struct group **);
	gid_t		 gid	= va_arg(ap, gid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_files(&_files_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_files(&rerror, &_files_group,
	    _files_groupbuf, sizeof(_files_groupbuf),
	    &_files_state, 1, NULL, gid);
	if (!_files_state.stayopen)
		__grend_files(&_files_state);
	if (rv == NS_SUCCESS)
		*retval = &_files_group;
	return rv;
}

/*ARGSUSED*/
static int
_files_getgrgid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	gid_t		 gid	= va_arg(ap, gid_t);
	struct group	*grp	= va_arg(ap, struct group *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct group   **result	= va_arg(ap, struct group **);

	struct __grstate_files state;
	int	rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_files(retval, grp, buffer, buflen, &state, 1, NULL, gid);
	__grend_files(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}

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

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_files(&_files_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_files(&rerror, &_files_group,
	    _files_groupbuf, sizeof(_files_groupbuf),
	    &_files_state, 1, name, 0);
	if (!_files_state.stayopen)
		__grend_files(&_files_state);
	if (rv == NS_SUCCESS)
		*retval = &_files_group;
	return rv;
}

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

	struct __grstate_files state;
	int	rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_files(retval, grp, buffer, buflen, &state, 1, name, 0);
	__grend_files(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}


#ifdef HESIOD
		/*
		 *	dns methods
		 */

int
__grstart_dns(struct __grstate_dns *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;
}

int
__grend_dns(struct __grstate_dns *state)
{

	_DIAGASSERT(state != NULL);

	state->num = 0;
	if (state->context) {
		hesiod_end(state->context);
		state->context = NULL;
	}
	return NS_SUCCESS;
}

/*
 * __grscan_dns
 *	Search Hesiod for the next desired entry.
 *	If search is zero, return the next entry.
 *	If search is non-zero, look for a specific name (if name != NULL),
 *	or a specific gid (if name == NULL).
 */
int
__grscan_dns(int *retval, struct group *grp, char *buffer, size_t buflen,
	struct __grstate_dns *state, int search, const char *name, gid_t gid)
{
	const char	**curzone;
	char		**hp, *ep;
	int		rv;

	static const char *zones_gid_group[] = {
		"gid",
		"group",
		NULL
	};

	static const char *zones_group[] = {
		"group",
		NULL
	};

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

	*retval = 0;

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

 next_dns_entry:
	hp = NULL;
	rv = NS_NOTFOUND;

	if (! search) {			/* find next entry */
		if (state->num == -1)		/* exhausted search */
			return NS_NOTFOUND;
						/* find group-NNN */
		snprintf(buffer, buflen, "group-%u", state->num);
		state->num++;
		curzone = zones_group;
	} else if (name) {		/* find group name */
		snprintf(buffer, buflen, "%s", name);
		curzone = zones_group;
	} else {			/* find gid */
		snprintf(buffer, buflen, "%u", (unsigned int)gid);
		curzone = zones_gid_group;
	}

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

	if ((ep = strchr(hp[0], '\n')) != NULL)
		*ep = '\0';				/* clear trailing \n */
	if (_gr_parse(hp[0], grp, buffer, buflen)) {	/* validate line */
		if (! search) {				/* just want this one */
			rv = NS_SUCCESS;
		} else if ((name && strcmp(name, grp->gr_name) == 0) ||
		    (!name && gid == grp->gr_gid)) {	/* want specific */
			rv = NS_SUCCESS;
		}
	} else {					/* dodgy entry */
		if (!search) {			/* try again if ! searching */
			hesiod_free_list(state->context, hp);
			goto next_dns_entry;
		}
	}

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

static struct __grstate_dns	_dns_state;
					/* storage for non _r functions */
static struct group		_dns_group;
static char			_dns_groupbuf[_GETGR_R_SIZE_MAX];

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

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

/*ARGSUSED*/
static int
_dns_setgroupent(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 = __grstart_dns(&_dns_state);
	*retval = (rv == NS_SUCCESS);
	return rv;
}

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

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

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

	int	  rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grscan_dns(&rerror, &_dns_group,
	    _dns_groupbuf, sizeof(_dns_groupbuf), &_dns_state, 0, NULL, 0);
	if (rv == NS_SUCCESS)
		*retval = &_dns_group;
	return rv;
}

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

	int	rv;

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

	rv = __grscan_dns(retval, grp, buffer, buflen,
	    &_dns_state, 0, NULL, 0);
	if (rv == NS_SUCCESS)
		*result = grp;
	else
		*result = NULL;
	return rv;
}
/*ARGSUSED*/
static int
_dns_getgrgid(void *nsrv, void *nscb, va_list ap)
{
	struct group	**retval = va_arg(ap, struct group **);
	gid_t		 gid	= va_arg(ap, gid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_dns(&_dns_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_dns(&rerror, &_dns_group,
	    _dns_groupbuf, sizeof(_dns_groupbuf), &_dns_state, 1, NULL, gid);
	if (!_dns_state.stayopen)
		__grend_dns(&_dns_state);
	if (rv == NS_SUCCESS)
		*retval = &_dns_group;
	return rv;
}

/*ARGSUSED*/
static int
_dns_getgrgid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	gid_t		 gid	= va_arg(ap, gid_t);
	struct group	*grp	= va_arg(ap, struct group *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct group   **result	= va_arg(ap, struct group **);

	struct __grstate_dns state;
	int	rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_dns(retval, grp, buffer, buflen, &state, 1, NULL, gid);
	__grend_dns(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}

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

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_dns(&_dns_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_dns(&rerror, &_dns_group,
	    _dns_groupbuf, sizeof(_dns_groupbuf), &_dns_state, 1, name, 0);
	if (!_dns_state.stayopen)
		__grend_dns(&_dns_state);
	if (rv == NS_SUCCESS)
		*retval = &_dns_group;
	return rv;
}

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

	struct __grstate_dns state;
	int	rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_dns(retval, grp, buffer, buflen, &state, 1, name, 0);
	__grend_dns(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}

#endif /* HESIOD */


#ifdef YP
		/*
		 *	nis methods
		 */

int
__grstart_nis(struct __grstate_nis *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;
		}
	}
	return NS_SUCCESS;
}

int
__grend_nis(struct __grstate_nis *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;
}

/*
 * __grscan_nis
 *	Search NIS for the next desired entry.
 *	If search is zero, return the next entry.
 *	If search is non-zero, look for a specific name (if name != NULL),
 *	or a specific gid (if name == NULL).
 */
int
__grscan_nis(int *retval, struct group *grp, char *buffer, size_t buflen,
	struct __grstate_nis *state, int search, const char *name, gid_t gid)
{
	const char *map;
	char	*key, *data;
	int	nisr, rv, keylen, datalen;

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

	*retval = 0;

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

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

	if (! search) 	{			/* find next entry */
		if (state->done)			/* exhausted search */
			return NS_NOTFOUND;
		map = "group.byname";
		if (state->current) {			/* already searching */
			nisr = yp_next(state->domain, map,
			    state->current, state->currentlen,
			    &key, &keylen, &data, &datalen);
			free(state->current);
			state->current = NULL;
			switch (nisr) {
			case 0:
				state->current = key;
				state->currentlen = keylen;
				key = NULL;
				break;
			case YPERR_NOMORE:
				rv = NS_NOTFOUND;
				state->done = 1;
				break;
			default:
				rv = NS_UNAVAIL;
				break;
			}
		} else {				/* new search */
			if (yp_first(state->domain, map,
			    &state->current, &state->currentlen,
			    &data, &datalen)) {
				rv = NS_UNAVAIL;
			}
		}
	} else {				/* search for specific item */
		if (name) {			/* find group name */
			snprintf(buffer, buflen, "%s", name);
			map = "group.byname";
		} else {			/* find gid */
			snprintf(buffer, buflen, "%u", (unsigned int)gid);
			map = "group.bygid";
		}
		nisr = yp_match(state->domain, map, buffer, (int)strlen(buffer),
		    &data, &datalen);
		switch (nisr) {
		case 0:
			break;
		case YPERR_KEY:
			rv = NS_NOTFOUND;
			break;
		default:
			rv = NS_UNAVAIL;
			break;
		}
	}
	if (rv == NS_SUCCESS) {				/* validate data */
		data[datalen] = '\0';			/* clear trailing \n */
		if (_gr_parse(data, grp, buffer, buflen)) {
			if (! search) {			/* just want this one */
				rv = NS_SUCCESS;
			} else if ((name && strcmp(name, grp->gr_name) == 0) ||
			    (!name && gid == grp->gr_gid)) {
							/* want specific */
				rv = NS_SUCCESS;
			}
		} else {				/* dodgy entry */
			if (!search) {		/* try again if ! searching */
				free(data);
				goto next_nis_entry;
			}
		}
	}

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

static struct __grstate_nis	_nis_state;
					/* storage for non _r functions */
static struct group		_nis_group;
static char			_nis_groupbuf[_GETGR_R_SIZE_MAX];

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

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

/*ARGSUSED*/
static int
_nis_setgroupent(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 = __grstart_nis(&_nis_state);
	*retval = (rv == NS_SUCCESS);
	return rv;
}

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

	return __grend_nis(&_nis_state);
}

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

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grscan_nis(&rerror, &_nis_group,
	    _nis_groupbuf, sizeof(_nis_groupbuf), &_nis_state, 0, NULL, 0);
	if (rv == NS_SUCCESS)
		*retval = &_nis_group;
	return rv;
}

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

	int	rv;

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

	rv = __grscan_nis(retval, grp, buffer, buflen,
	    &_nis_state, 0, NULL, 0);
	if (rv == NS_SUCCESS)
		*result = grp;
	else
		*result = NULL;
	return rv;
}

/*ARGSUSED*/
static int
_nis_getgrgid(void *nsrv, void *nscb, va_list ap)
{
	struct group	**retval = va_arg(ap, struct group **);
	gid_t		 gid	= va_arg(ap, gid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_nis(&_nis_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_nis(&rerror, &_nis_group,
	    _nis_groupbuf, sizeof(_nis_groupbuf), &_nis_state, 1, NULL, gid);
	if (!_nis_state.stayopen)
		__grend_nis(&_nis_state);
	if (rv == NS_SUCCESS)
		*retval = &_nis_group;
	return rv;
}

/*ARGSUSED*/
static int
_nis_getgrgid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	gid_t		 gid	= va_arg(ap, gid_t);
	struct group	*grp	= va_arg(ap, struct group *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct group   **result	= va_arg(ap, struct group **);

	struct __grstate_nis state;
	int	rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_nis(retval, grp, buffer, buflen, &state, 1, NULL, gid);
	__grend_nis(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}

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

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_nis(&_nis_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_nis(&rerror, &_nis_group,
	    _nis_groupbuf, sizeof(_nis_groupbuf), &_nis_state, 1, name, 0);
	if (!_nis_state.stayopen)
		__grend_nis(&_nis_state);
	if (rv == NS_SUCCESS)
		*retval = &_nis_group;
	return rv;
}

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

	struct __grstate_nis state;
	int	rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_nis(retval, grp, buffer, buflen, &state, 1, name, 0);
	__grend_nis(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}

#endif /* YP */


#ifdef _GROUP_COMPAT
		/*
		 *	compat methods
		 */

int
__grstart_compat(struct __grstate_compat *state)
{

	_DIAGASSERT(state != NULL);

	if (state->fp == NULL) {
		state->fp = fopen(_PATH_GROUP, "r");
		if (state->fp == NULL)
			return NS_UNAVAIL;
	} else {
		rewind(state->fp);
	}
	return NS_SUCCESS;
}

int
__grend_compat(struct __grstate_compat *state)
{

	_DIAGASSERT(state != NULL);

	if (state->name) {
		free(state->name);
		state->name = NULL;
	}
	if (state->fp) {
		(void) fclose(state->fp);
		state->fp = NULL;
	}
	return NS_SUCCESS;
}


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

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

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

/*
 * __grscan_compat
 *	Scan state->fp for the next desired entry.
 *	If search is zero, return the next entry.
 *	If search is non-zero, look for a specific name (if name != NULL),
 *	or a specific gid (if name == NULL).
 *	Sets *retval to the errno if the result is not NS_SUCCESS or
 *	NS_NOTFOUND.
 *
 *	searchfunc is invoked when a compat "+" lookup is required;
 *	searchcookie is passed as the first argument to searchfunc,
 *	the second argument is the group result.
 *	This should return NS_NOTFOUND when "no more groups" from compat src.
 *	If searchfunc is NULL then nsdispatch of getgrent is used.
 *	This is primarily intended for getgroupmembership(3)'s compat backend.
 */
int
__grscan_compat(int *retval, struct group *grp, char *buffer, size_t buflen,
	struct __grstate_compat *state, int search, const char *name, gid_t gid,
	int (*searchfunc)(void *, struct group **), void *searchcookie)
{
	int		rv;
	char		filebuf[_GETGR_R_SIZE_MAX], *ep;

	static const ns_dtab compatentdtab[] = {
		NS_FILES_CB(__grbad_compat, "files")
		NS_DNS_CB(_dns_getgrent_r, NULL)
		NS_NIS_CB(_nis_getgrent_r, NULL)
		NS_COMPAT_CB(__grbad_compat, "compat")
		NS_NULL_CB
	};
	static const ns_dtab compatgiddtab[] = {
		NS_FILES_CB(__grbad_compat, "files")
		NS_DNS_CB(_dns_getgrgid_r, NULL)
		NS_NIS_CB(_nis_getgrgid_r, NULL)
		NS_COMPAT_CB(__grbad_compat, "compat")
		NS_NULL_CB
	};
	static const ns_dtab compatnamdtab[] = {
		NS_FILES_CB(__grbad_compat, "files")
		NS_DNS_CB(_dns_getgrnam_r, NULL)
		NS_NIS_CB(_nis_getgrnam_r, NULL)
		NS_COMPAT_CB(__grbad_compat, "compat")
		NS_NULL_CB
	};

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

	*retval = 0;

	if (state->fp == NULL) {	/* only start if file not open yet */
		rv = __grstart_compat(state);
		if (rv != NS_SUCCESS)
			goto compatgrscan_out;
	}
	rv = NS_NOTFOUND;

	for (;;) {					/* loop through file */
		if (state->name != NULL) {
					/* processing compat entry */
			int		crv, cretval;
			struct group	cgrp, *cgrpres;

			if (state->name[0]) {		/* specific +group: */
				crv = nsdispatch(NULL, compatnamdtab,
				    NSDB_GROUP_COMPAT, "getgrnam_r",
				    __nsdefaultnis,
				    &cretval, state->name,
				    &cgrp, filebuf, sizeof(filebuf), &cgrpres);
				free(state->name);	/* (only check 1 grp) */
				state->name = NULL;
			} else if (!search) {		/* any group */
				if (searchfunc) {
					crv = searchfunc(searchcookie,
					    &cgrpres);
				} else {
					crv = nsdispatch(NULL, compatentdtab,
					    NSDB_GROUP_COMPAT, "getgrent_r",
					    __nsdefaultnis,
					    &cretval, &cgrp, filebuf,
					    sizeof(filebuf), &cgrpres);
				}
			} else if (name) {		/* specific group */
				crv = nsdispatch(NULL, compatnamdtab,
				    NSDB_GROUP_COMPAT, "getgrnam_r",
				    __nsdefaultnis,
				    &cretval, name,
				    &cgrp, filebuf, sizeof(filebuf), &cgrpres);
			} else {			/* specific gid */
				crv = nsdispatch(NULL, compatgiddtab,
				    NSDB_GROUP_COMPAT, "getgrgid_r",
				    __nsdefaultnis,
				    &cretval, gid,
				    &cgrp, filebuf, sizeof(filebuf), &cgrpres);
			}
			if (crv != NS_SUCCESS) {	/* not found */
				free(state->name);
				state->name = NULL;
				continue;		/* try next line */
			}
			if (!_gr_copy(cgrpres, grp, buffer, buflen)) {
				rv = NS_UNAVAIL;
				break;
			}
			goto compatgrscan_cmpgrp;	/* skip to grp test */
		}

							/* get next file line */
		if (fgets(filebuf, sizeof(filebuf), state->fp) == NULL)
			break;

		ep = strchr(filebuf, '\n');
		if (ep == NULL) {	/* skip lines that are too big */
			int ch;

			while ((ch = getc(state->fp)) != '\n' && ch != EOF)
				continue;
			continue;
		}
		*ep = '\0';				/* clear trailing \n */

		if (filebuf[0] == '+') {		/* parse compat line */
			if (state->name)
				free(state->name);
			state->name = NULL;
			switch(filebuf[1]) {
			case ':':
			case '\0':
				state->name = strdup("");
				break;
			default:
				ep = strchr(filebuf + 1, ':');
				if (ep == NULL)
					break;
				*ep = '\0';
				state->name = strdup(filebuf + 1);
				break;
			}
			if (state->name == NULL) {
				rv = NS_UNAVAIL;
				break;
			}
			continue;
		}

							/* validate line */
		if (! _gr_parse(filebuf, grp, buffer, buflen)) {
			continue;			/* skip bad lines */
		}

 compatgrscan_cmpgrp:
		if (! search) {				/* just want this one */
			rv = NS_SUCCESS;
			break;
		}
							/* want specific */
		if ((name && strcmp(name, grp->gr_name) == 0) ||
		    (!name && gid == grp->gr_gid)) {
			rv = NS_SUCCESS;
			break;
		}

	}

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

static struct __grstate_compat	_compat_state;
					/* storage for non _r functions */
static struct group		_compat_group;
static char			_compat_groupbuf[_GETGR_R_SIZE_MAX];

/*ARGSUSED*/
static int
_compat_setgrent(void *nsrv, void *nscb, va_list ap)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(__grbad_compat, "files")
		NS_DNS_CB(_dns_setgrent, NULL)
		NS_NIS_CB(_nis_setgrent, NULL)
		NS_COMPAT_CB(__grbad_compat, "compat")
		NS_NULL_CB
	};

					/* force group_compat setgrent() */
	(void) nsdispatch(NULL, dtab, NSDB_GROUP_COMPAT, "setgrent",
	    __nsdefaultnis_forceall);

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

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

	int	rv;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(__grbad_compat, "files")
		NS_DNS_CB(_dns_setgroupent, NULL)
		NS_NIS_CB(_nis_setgroupent, NULL)
		NS_COMPAT_CB(__grbad_compat, "compat")
		NS_NULL_CB
	};

					/* force group_compat setgroupent() */
	(void) nsdispatch(NULL, dtab, NSDB_GROUP_COMPAT, "setgroupent",
	    __nsdefaultnis_forceall, &rv, stayopen);

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

/*ARGSUSED*/
static int
_compat_endgrent(void *nsrv, void *nscb, va_list ap)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(__grbad_compat, "files")
		NS_DNS_CB(_dns_endgrent, NULL)
		NS_NIS_CB(_nis_endgrent, NULL)
		NS_COMPAT_CB(__grbad_compat, "compat")
		NS_NULL_CB
	};

					/* force group_compat endgrent() */
	(void) nsdispatch(NULL, dtab, NSDB_GROUP_COMPAT, "endgrent",
	    __nsdefaultnis_forceall);

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

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

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grscan_compat(&rerror, &_compat_group,
	    _compat_groupbuf, sizeof(_compat_groupbuf),
	    &_compat_state, 0, NULL, 0, NULL, NULL);
	if (rv == NS_SUCCESS)
		*retval = &_compat_group;
	return rv;
}

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

	int	rv;

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

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

/*ARGSUSED*/
static int
_compat_getgrgid(void *nsrv, void *nscb, va_list ap)
{
	struct group	**retval = va_arg(ap, struct group **);
	gid_t		 gid	= va_arg(ap, gid_t);

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_compat(&_compat_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_compat(&rerror, &_compat_group,
	    _compat_groupbuf, sizeof(_compat_groupbuf),
	    &_compat_state, 1, NULL, gid, NULL, NULL);
	if (!_compat_state.stayopen)
		__grend_compat(&_compat_state);
	if (rv == NS_SUCCESS)
		*retval = &_compat_group;
	return rv;
}

/*ARGSUSED*/
static int
_compat_getgrgid_r(void *nsrv, void *nscb, va_list ap)
{
	int		*retval	= va_arg(ap, int *);
	gid_t		 gid	= va_arg(ap, gid_t);
	struct group	*grp	= va_arg(ap, struct group *);
	char		*buffer	= va_arg(ap, char *);
	size_t		 buflen	= va_arg(ap, size_t);
	struct group   **result	= va_arg(ap, struct group **);

	struct __grstate_compat	state;
	int		rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_compat(retval, grp, buffer, buflen, &state,
	    1, NULL, gid, NULL, NULL);
	__grend_compat(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}

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

	int	rv, rerror;

	_DIAGASSERT(retval != NULL);

	*retval = NULL;
	rv = __grstart_compat(&_compat_state);
	if (rv != NS_SUCCESS)
		return rv;
	rv = __grscan_compat(&rerror, &_compat_group,
	    _compat_groupbuf, sizeof(_compat_groupbuf),
	    &_compat_state, 1, name, 0, NULL, NULL);
	if (!_compat_state.stayopen)
		__grend_compat(&_compat_state);
	if (rv == NS_SUCCESS)
		*retval = &_compat_group;
	return rv;
}

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

	struct __grstate_compat	state;
	int		rv;

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

	*result = NULL;
	memset(&state, 0, sizeof(state));
	rv = __grscan_compat(retval, grp, buffer, buflen, &state,
	    1, name, 0, NULL, NULL);
	__grend_compat(&state);
	if (rv == NS_SUCCESS)
		*result = grp;
	return rv;
}

#endif	/* _GROUP_COMPAT */


		/*
		 *	public functions
		 */

struct group *
getgrent(void)
{
	int		rv;
	struct group	*retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getgrent, NULL)
		NS_DNS_CB(_dns_getgrent, NULL)
		NS_NIS_CB(_nis_getgrent, NULL)
		NS_COMPAT_CB(_compat_getgrent, NULL)
		NS_NULL_CB
	};

	mutex_lock(&__grmutex);
	rv = nsdispatch(NULL, dtab, NSDB_GROUP, "getgrent", __nsdefaultcompat,
	    &retval);
	mutex_unlock(&__grmutex);
	return (rv == NS_SUCCESS) ? retval : NULL;
}

int
getgrent_r(struct group *grp, char *buffer, size_t buflen,
    struct group **result)
{
	int		rv, retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getgrent_r, NULL)
		NS_DNS_CB(_dns_getgrent_r, NULL)
		NS_NIS_CB(_nis_getgrent_r, NULL)
		NS_COMPAT_CB(_compat_getgrent_r, NULL)
		NS_NULL_CB
	};

	mutex_lock(&__grmutex);
	rv = nsdispatch(NULL, dtab, NSDB_GROUP, "getgrent_r", __nsdefaultcompat,
	    &retval, grp, buffer, buflen, result);
	mutex_unlock(&__grmutex);
	switch (rv) {
	case NS_SUCCESS:
	case NS_NOTFOUND:
		return 0;
	default:
		return retval;
	}
}


struct group *
getgrgid(gid_t gid)
{
	int		rv;
	struct group	*retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getgrgid, NULL)
		NS_DNS_CB(_dns_getgrgid, NULL)
		NS_NIS_CB(_nis_getgrgid, NULL)
		NS_COMPAT_CB(_compat_getgrgid, NULL)
		NS_NULL_CB
	};

	mutex_lock(&__grmutex);
	rv = nsdispatch(NULL, dtab, NSDB_GROUP, "getgrgid", __nsdefaultcompat,
	    &retval, gid);
	mutex_unlock(&__grmutex);
	return (rv == NS_SUCCESS) ? retval : NULL;
}

int
getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t buflen,
	struct group **result)
{
	int	rv, retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getgrgid_r, NULL)
		NS_DNS_CB(_dns_getgrgid_r, NULL)
		NS_NIS_CB(_nis_getgrgid_r, NULL)
		NS_COMPAT_CB(_compat_getgrgid_r, NULL)
		NS_NULL_CB
	};

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

	*result = NULL;
	retval = 0;
	mutex_lock(&__grmutex);
	rv = nsdispatch(NULL, dtab, NSDB_GROUP, "getgrgid_r", __nsdefaultcompat,
	    &retval, gid, grp, buffer, buflen, result);
	mutex_unlock(&__grmutex);
	switch (rv) {
	case NS_SUCCESS:
	case NS_NOTFOUND:
		return 0;
	default:
		return retval;
	}
}

struct group *
getgrnam(const char *name)
{
	int		rv;
	struct group	*retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getgrnam, NULL)
		NS_DNS_CB(_dns_getgrnam, NULL)
		NS_NIS_CB(_nis_getgrnam, NULL)
		NS_COMPAT_CB(_compat_getgrnam, NULL)
		NS_NULL_CB
	};

	mutex_lock(&__grmutex);
	rv = nsdispatch(NULL, dtab, NSDB_GROUP, "getgrnam", __nsdefaultcompat,
	    &retval, name);
	mutex_unlock(&__grmutex);
	return (rv == NS_SUCCESS) ? retval : NULL;
}

int
getgrnam_r(const char *name, struct group *grp, char *buffer, size_t buflen,
	struct group **result)
{
	int	rv, retval;

	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_getgrnam_r, NULL)
		NS_DNS_CB(_dns_getgrnam_r, NULL)
		NS_NIS_CB(_nis_getgrnam_r, NULL)
		NS_COMPAT_CB(_compat_getgrnam_r, NULL)
		NS_NULL_CB
	};

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

	*result = NULL;
	retval = 0;
	mutex_lock(&__grmutex);
	rv = nsdispatch(NULL, dtab, NSDB_GROUP, "getgrnam_r", __nsdefaultcompat,
	    &retval, name, grp, buffer, buflen, result);
	mutex_unlock(&__grmutex);
	switch (rv) {
	case NS_SUCCESS:
	case NS_NOTFOUND:
		return 0;
	default:
		return retval;
	}
}

void
endgrent(void)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_endgrent, NULL)
		NS_DNS_CB(_dns_endgrent, NULL)
		NS_NIS_CB(_nis_endgrent, NULL)
		NS_COMPAT_CB(_compat_endgrent, NULL)
		NS_NULL_CB
	};

	mutex_lock(&__grmutex);
					/* force all endgrent() methods */
	(void) nsdispatch(NULL, dtab, NSDB_GROUP, "endgrent",
	    __nsdefaultcompat_forceall);
	mutex_unlock(&__grmutex);
}

int
setgroupent(int stayopen)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_setgroupent, NULL)
		NS_DNS_CB(_dns_setgroupent, NULL)
		NS_NIS_CB(_nis_setgroupent, NULL)
		NS_COMPAT_CB(_compat_setgroupent, NULL)
		NS_NULL_CB
	};
	int	rv, retval;

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

void
setgrent(void)
{
	static const ns_dtab dtab[] = {
		NS_FILES_CB(_files_setgrent, NULL)
		NS_DNS_CB(_dns_setgrent, NULL)
		NS_NIS_CB(_nis_setgrent, NULL)
		NS_COMPAT_CB(_compat_setgrent, NULL)
		NS_NULL_CB
	};

	mutex_lock(&__grmutex);
					/* force all setgrent() methods */
	(void) nsdispatch(NULL, dtab, NSDB_GROUP, "setgrent",
	    __nsdefaultcompat_forceall);
	mutex_unlock(&__grmutex);
}
