--- /dev/null	Mon Aug  5 17:16:45 2019
+++ new/usr/src/lib/nsswitch/files/common/getnetgrent.c	Mon Aug  5 17:16:45 2019
@@ -0,0 +1,902 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
+ *
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ *	files/getnetgrent.c -- "files" backend for nsswitch "netgroup" database
+ *
+ *	The API for netgroups differs sufficiently from that for the average
+ *	getXXXbyYYY function that we use very few of the support routines in
+ *	files_common.h.
+ *
+ *	The implementation of setnetgrent()/getnetgrent() here follows the
+ *	the 4.x code, inasmuch as the setnetgrent() routine does all the work
+ *	of traversing the netgroup graph and building a (potentially large)
+ *	list in memory, and getnetgrent() just steps down the list.
+ *
+ *	An alternative, and probably better, implementation would lazy-eval
+ *	the netgroup graph in response to getnetgrent() calls (though
+ *	setnetgrent() should still check for the top-level netgroup name
+ *	and return NSS_SUCCESS / NSS_NOTFOUND).
+ */
+
+#include "files_common.h"
+#include <ctype.h>
+#include <rpcsvc/ypclnt.h>
+#include <malloc.h>
+#include <string.h>
+#include <ctype.h>
+
+/*
+ * Tricky debug support
+ */
+
+#pragma weak __nss_files_netgr_debug
+#pragma weak __nss_files_netgr_error
+extern void __nss_files_netgr_debug(const char *, ...);
+extern void __nss_files_netgr_error(const char *, ...);
+
+/*
+ * Start of stuff borrowed from getgrent.c
+ */
+static uint_t
+hash_netgrname(nss_XbyY_args_t *argp, int keyhash, const char *line,
+	int linelen)
+{
+	const char 	*name;
+	int		namelen, i;
+	uint_t		hash = 0;
+
+	if (keyhash) {
+		name = argp->key.name;
+		namelen = strlen(name);
+	} else {
+		name = line;
+		namelen = 0;
+		while (linelen-- && !isspace(*line)) {
+			line++;
+			namelen++;
+		}
+	}
+
+	for (i = 0; i < namelen; i++)
+		hash = hash * 15 + name[i];
+	return (hash);
+}
+
+static files_hash_func hash_netgr[1] = { hash_netgrname };
+
+static files_hash_t hashinfo = {
+	DEFAULTMUTEX,
+	sizeof (struct nss_netgrent),
+	NSS_LINELEN_NETGROUP,
+	1,
+	hash_netgr
+};
+
+static int
+check_netgrname(nss_XbyY_args_t *argp, const char *line, int linelen)
+{
+	const char *linep, *limit;
+	const char *keyp = argp->key.name;
+
+	linep = line;
+	limit = line + linelen;
+
+	/* +/- entries valid for compat source only */
+	if (linelen == 0 || *line == '+' || *line == '-')
+		return (0);
+	while (*keyp && linep < limit && *keyp == *linep) {
+		keyp++;
+		linep++;
+	}
+	return (linep < limit && *keyp == '\0' && isspace(*linep));
+}
+
+static nss_status_t
+getbyname(be, a)
+	files_backend_ptr_t	be;
+	void			*a;
+{
+	return (_nss_files_XY_hash(be, a, 1, &hashinfo, 0, check_netgrname));
+}
+
+/*
+ * End of stuff borrowed from getgrent.c
+ *
+ * Now some "glue" functions based loosely on
+ *   lib/libc/port/gen/getgrnam_r.c
+ */
+
+
+/*
+ * This is a special purpose str2ent (parse) function used only in
+ * the _nss_files_getbyname() below.  A general-purpose version of
+ * this parser would copy the incoming line buffer to the passed
+ * temporary buffer, and fill in the passed struct nss_netgrent with
+ * pointers into that temporary buffer.  Our caller only needs the
+ * list of members of this netgroup, and since that string already
+ * exists in ready-to-use form in the incoming line buffer, we just
+ * use that.  Also special here is the fact that we allocate a copy
+ * of the member list, both because the caller wants it allocated,
+ * and because the buffer at *instr will change after we return.
+ * The caller passes null for a temporary buffer, which we ignore.
+ *
+ * See the test program: cmd/nsstest/netgr_get.c
+ * for a more generic version of this function.
+ */
+/* ARGSUSED */
+static int
+str2netgr(const char *instr, int lenstr, void *ent, char *buffer, int buflen)
+{
+	const char sep[] = " \t\n";
+	struct nss_netgrent *netgr = ent;
+	const char	*p;
+
+	/* skip leading space */
+	p = instr;
+	while (isspace(*p))
+		p++;
+
+	/* should be at the key */
+	if (*p == '\0')
+		return (NSS_STR_PARSE_PARSE);
+	/* Full parser would set netgr_name = p here. */
+
+	/* skip the key ... */
+	p = strpbrk(p, sep);
+	if (p == NULL)
+		return (NSS_STR_PARSE_PARSE);
+	/* Full parser would store a null at *p here. */
+
+	/* skip separators */
+	while (isspace(*p))
+		p++;
+
+	/*
+	 * Should be at the members list, which is the
+	 * rest of the input line.
+	 */
+	if (*p == '\0')
+		return (NSS_STR_PARSE_PARSE);
+
+	/*
+	 * Caller wants this allocated.  Do it now,
+	 * before the inbuf gets re-used.
+	 */
+	netgr->netgr_members = strdup(p);
+
+	return (NSS_STR_PARSE_SUCCESS);
+}
+
+/*
+ * This is a compatibility "shim" used by top_down() to get
+ * the list of members for some netgroup.  On success, the
+ * list of members is returned in allocated memory via valp.
+ */
+static nss_status_t
+netgr_get_members(struct files_backend *be,
+	const char		*name,
+	char			**valp)
+{
+	struct nss_netgrent netgr;
+	nss_XbyY_args_t args;
+	nss_status_t result;
+
+	if (name == (const char *)NULL)
+		return (NSS_ERROR);
+
+	(void) memset(&netgr, 0, sizeof (netgr));
+	(void) memset(&args, 0, sizeof (args));
+	args.buf.result = &netgr;
+	args.str2ent = str2netgr;
+	args.key.name = name;
+	result = getbyname(be, &args);
+
+	if (result == NSS_SUCCESS) {
+		/* Note: allocated memory. */
+		*valp = netgr.netgr_members;
+		if (*valp == NULL)
+			result = NSS_UNAVAIL;
+	}
+
+	return (result);
+}
+
+
+/*
+ * End "glue" functions
+ *
+ * The rest of this is based on:
+ *  lib/nsswitch/nis/common/getnetgrent.c
+ */
+
+
+/*
+ * The nss_backend_t for a getnetgrent() sequence;  we actually give the
+ *   netgroup frontend a pointer to one of these structures in response to
+ *   a (successful) setnetgrent() call on the files_backend backend
+ *   described further down in this file.
+ */
+
+struct files_getnetgr_be;
+typedef nss_status_t	(*files_getnetgr_op_t)(
+	struct files_getnetgr_be *, void *);
+
+struct files_getnetgr_be {
+	files_getnetgr_op_t	*ops;
+	nss_dbop_t		n_ops;
+	/*
+	 * State for set/get/endnetgrent()
+	 */
+	char			*netgroup;
+	struct grouplist	*all_members;
+	struct grouplist	*next_member;
+};
+
+struct grouplist {  /* One element of the list generated by a setnetgrent() */
+	char			*triple[NSS_NETGR_N];
+	struct	grouplist	*gl_nxt;
+};
+
+static nss_status_t
+getnetgr_set(be, a)
+	struct files_getnetgr_be	*be;
+	void			*a;
+{
+	const char		*netgroup = (const char *) a;
+
+	if (be->netgroup != 0 &&
+	    strcmp(be->netgroup, netgroup) == 0) {
+		/* We already have the member-list;  regurgitate it */
+		be->next_member = be->all_members;
+		return (NSS_SUCCESS);
+	}
+	return (NSS_NOTFOUND);
+}
+
+static nss_status_t
+getnetgr_get(be, a)
+	struct files_getnetgr_be	*be;
+	void			*a;
+{
+	struct nss_getnetgrent_args *args = (struct nss_getnetgrent_args *)a;
+	struct grouplist	*mem;
+
+	if ((mem = be->next_member) == 0) {
+		args->status = NSS_NETGR_NO;
+	} else {
+		char			*buffer	= args->buffer;
+		int			buflen	= args->buflen;
+		enum nss_netgr_argn	i;
+
+		args->status = NSS_NETGR_FOUND;
+
+		for (i = 0;  i < NSS_NETGR_N;  i++) {
+			const char	*str;
+			ssize_t	len;
+
+			if ((str = mem->triple[i]) == 0) {
+				args->retp[i] = 0;
+			} else if ((len = strlen(str) + 1) <= buflen) {
+				args->retp[i] = buffer;
+				(void) memcpy(buffer, str, len);
+				buffer += len;
+				buflen -= len;
+			} else {
+				args->status = NSS_NETGR_NOMEM;
+				break;
+			}
+		}
+		be->next_member	= mem->gl_nxt;
+	}
+	return (NSS_SUCCESS);	/* Yup, even for end-of-list, i.e. */
+				/* do NOT advance to next backend. */
+}
+
+/*ARGSUSED*/
+static nss_status_t
+getnetgr_end(be, dummy)
+	struct files_getnetgr_be	*be;
+	void			*dummy;
+{
+	struct grouplist	*gl;
+	struct grouplist	*next;
+
+	for (gl = be->all_members; gl != NULL; gl = next) {
+		enum nss_netgr_argn	i;
+
+		next = gl->gl_nxt;
+		for (i = NSS_NETGR_MACHINE;  i < NSS_NETGR_N;  i++) {
+			if (gl->triple[i] != 0) {
+				free(gl->triple[i]);
+			}
+		}
+		free(gl);
+	}
+	be->all_members = 0;
+	be->next_member = 0;
+	if (be->netgroup != 0) {
+		free(be->netgroup);
+		be->netgroup = 0;
+	}
+	return (NSS_SUCCESS);
+}
+
+/*ARGSUSED*/
+static nss_status_t
+getnetgr_destr(be, dummy)
+	struct files_getnetgr_be	*be;
+	void			*dummy;
+{
+	if (be != 0) {
+		(void) getnetgr_end(be, (void *)0);
+		free(be);
+	}
+	return (NSS_SUCCESS);
+}
+
+static files_getnetgr_op_t getnetgr_ops[] = {
+	getnetgr_destr,
+	getnetgr_end,
+	getnetgr_set,
+	getnetgr_get,	/* getnetgrent_r() */
+};
+
+
+/*
+ * The nss_backend_t for innetgr() and setnetgrent().
+ * Also getbyname(), but that's only for testing.
+ */
+
+
+
+/*
+ * Code to do top-down search in the graph defined by the 'netgroup' YP map
+ */
+
+/*
+ * ===> This code is now used for setnetgrent(), not just innetgr().
+ *
+ * If the easy way doesn't pan out, recursively search the 'netgroup' map.
+ * In order to do this, we:
+ *
+ *    -	remember all the netgroup names we've seen during this search,
+ *	whether or not we've expanded them yet (we want fast insertion
+ *	with duplicate-detection, so use yet another chained hash table),
+ *
+ *    -	keep a list of all the netgroups we haven't expanded yet (we just
+ *	want fast insertion and pop-first, so a linked list will do fine).
+ *	If we insert at the head, we get a depth-first search;  insertion
+ *	at the tail gives breadth-first (?), which seems preferable (?).
+ *
+ * A netgrnam struct contains pointers for both the hash-table and the list.
+ * It also contains the netgroup name;  note that we embed the name at the
+ * end of the structure rather than holding a pointer to yet another
+ * malloc()ed region.
+ *
+ * A netgrtab structure contains the hash-chain heads and the head/tail
+ * pointers for the expansion list.
+ */
+
+struct netgrnam {
+	struct netgrnam	*hash_chain;
+	struct netgrnam	*expand_next;
+	char		name[1];	/* Really [strlen(name) + 1] */
+};
+
+#define	HASHMOD	113
+
+struct netgrtab {
+	struct netgrnam	*expand_first;
+	struct netgrnam	**expand_lastp;
+	struct netgrnam	*hash_heads[HASHMOD];
+};
+
+static void
+ngt_init(ngt)
+	struct netgrtab	*ngt;
+{
+	(void) memset((void *)ngt, 0, sizeof (*ngt));
+	ngt->expand_lastp = &ngt->expand_first;
+}
+
+/* === ? Change ngt_init() and ngt_destroy() to malloc/free struct netgrtab */
+
+static void
+/* ==> ? Should return 'failed' (out-of-memory) status ? */
+ngt_insert(ngt, name, namelen)
+	struct netgrtab	*ngt;
+	const char	*name;
+	size_t		namelen;
+{
+	unsigned	hashval;
+	size_t		i;
+	struct netgrnam	*cur;
+	struct netgrnam	**head;
+
+#define	dummy		((struct netgrnam *)0)
+
+	if (__nss_files_netgr_debug != NULL)
+		__nss_files_netgr_debug(
+		    "ngt_insert: ngt=%p names=%s", ngt, name);
+
+	for (hashval = 0, i = 0;  i < namelen;  i++) {
+		hashval = (hashval << 2) + hashval +
+			((const unsigned char *)name)[i];
+	}
+	head = &ngt->hash_heads[hashval % HASHMOD];
+	for (cur = *head;  cur != 0;  cur = cur->hash_chain) {
+		if (strncmp(cur->name, name, namelen) == 0 &&
+		    cur->name[namelen] == 0) {
+			return;		/* Already in table, do nothing */
+		}
+	}
+	/* Create new netgrnam struct */
+	cur = (struct netgrnam *)
+		malloc(namelen + 1 + (char *)&dummy->name[0] - (char *)dummy);
+	if (cur == 0) {
+		return;			/* Out of memory, too bad */
+	}
+	(void) memcpy(cur->name, name, namelen);
+	cur->name[namelen] = 0;
+
+	/* Insert in hash table */
+	cur->hash_chain = *head;
+	*head = cur;
+
+	/* Insert in expansion list (insert at end for breadth-first search */
+	cur->expand_next = 0;
+	*ngt->expand_lastp = cur;
+	ngt->expand_lastp = &cur->expand_next;
+
+#undef	dummy
+}
+
+static const char *
+ngt_next(ngt)
+	struct netgrtab	*ngt;
+{
+	struct netgrnam	*first;
+
+	if ((first = ngt->expand_first) == 0) {
+		return (0);
+	}
+	if ((ngt->expand_first = first->expand_next) == 0) {
+		ngt->expand_lastp = &ngt->expand_first;
+	}
+	return (first->name);
+}
+
+static void
+ngt_destroy(ngt)
+	struct netgrtab	*ngt;
+{
+	struct netgrnam	*cur;
+	struct netgrnam *next;
+	int		i;
+
+	for (i = 0;  i < HASHMOD;  i++) {
+		for (cur = ngt->hash_heads[i];  cur != 0; /* cstyle */) {
+			next = cur->hash_chain;
+			free(cur);
+			cur = next;
+		}
+	}
+	/* Don't bother zeroing pointers;  must do init if we want to reuse */
+}
+
+typedef const char *ccp;
+
+static nss_status_t
+top_down(struct files_backend *be, const char **groups, int ngroups,
+    int (*func)(ccp triple[3], void *iter_args, nss_status_t *return_val),
+    void *iter_args)
+{
+	struct netgrtab		*ngt;
+	/* netgrtab goes on the heap, not the stack, because it's large and */
+	/* stacks may not be all that big in multi-threaded programs. */
+
+	const char		*group;
+	int			nfound;
+	int			done;
+	nss_status_t		result;
+
+	if ((ngt = (struct netgrtab *)malloc(sizeof (*ngt))) == 0) {
+		return (NSS_UNAVAIL);
+	}
+	ngt_init(ngt);
+
+	while (ngroups > 0) {
+		ngt_insert(ngt, *groups, strlen(*groups));
+		groups++;
+		ngroups--;
+	}
+
+	done	= 0;	/* Set to 1 to indicate that we cut the iteration  */
+			/*   short (and 'result' holds the return value)   */
+	nfound	= 0;	/* Number of successful netgroup getbyname calls   */
+
+	while (!done && (group = ngt_next(ngt)) != 0) {
+		char		*val = NULL;
+		char		*p;
+
+		result = netgr_get_members(be, group, &val);
+		if (result != NSS_SUCCESS) {
+			/*LINTED E_NOP_IF_STMT*/
+			if (result == NSS_NOTFOUND) {
+				if (__nss_files_netgr_error != NULL)
+					__nss_files_netgr_error(
+			    "files netgroup lookup: %s doesn't exist",
+					    group);
+			} else {
+				if (__nss_files_netgr_error != NULL)
+					__nss_files_netgr_error(
+			"files netgroup lookup: getbyname returned [%s]",
+					    strerror(errno));
+				done = 1;	/* Give up, return result */
+			}
+			/* Don't need to clean up anything */
+			continue;
+		}
+
+		if (__nss_files_netgr_debug != NULL)
+			__nss_files_netgr_debug(
+			    "ngt_top: ngt=%p grp=%s members=\"%s\"",
+			    ngt, group, val);
+
+		nfound++;
+
+		if ((p = strpbrk(val, "#\n")) != 0) {
+			*p = '\0';
+		}
+		p = val;
+
+		/* Parse val into triples and recursive netgroup references */
+		/*CONSTCOND*/
+		while (1) {
+			ccp			triple[NSS_NETGR_N];
+			int			syntax_err;
+			enum nss_netgr_argn	i;
+
+			while (isspace(*p)) {
+				p++;
+			}
+			if (*p == '\0') {
+				/* Finished processing this particular val */
+				break;
+			}
+			if (*p != '(') {
+				/* Doesn't look like the start of a triple, */
+				/*   so assume it's a recursive netgroup.   */
+				char *start = p;
+				p = strpbrk(start, " \t");
+				if (p == 0) {
+					/* Point p at the final '\0' */
+					p = start + strlen(start);
+				}
+				ngt_insert(ngt, start, (size_t)(p - start));
+				continue;
+			}
+
+			/* Main case:  a (machine, user, domain) triple */
+			p++;
+			syntax_err = 0;
+			for (i = NSS_NETGR_MACHINE; i < NSS_NETGR_N; i++) {
+				char		*start;
+				char		*limit;
+				const char	*terminators = ",) \t";
+
+				if (i == NSS_NETGR_DOMAIN) {
+					/* Don't allow comma */
+					terminators++;
+				}
+				while (isspace(*p)) {
+					p++;
+				}
+				start = p;
+				limit = strpbrk(start, terminators);
+				if (limit == 0) {
+					syntax_err++;
+					break;
+				}
+				p = limit;
+				while (isspace(*p)) {
+					p++;
+				}
+				if (*p == terminators[0]) {
+					/*
+					 * Successfully parsed this name and
+					 *   the separator after it (comma or
+					 *   right paren); leave p ready for
+					 *   next parse.
+					 */
+					p++;
+					if (start == limit) {
+						/* Wildcard */
+						triple[i] = 0;
+					} else {
+						*limit = '\0';
+						triple[i] = start;
+					}
+				} else {
+					syntax_err++;
+					break;
+				}
+			}
+
+			if (syntax_err) {
+/*
+ * ===> log it;
+ * ===> try skipping past next ')';  failing that, abandon the line;
+ */
+				break;	/* Abandon this line */
+			} else if (!(*func)(triple, iter_args, &result)) {
+				/* Return result, good or bad */
+				done = 1;
+				break;
+			}
+		}
+		/* End of inner loop over val[] */
+		free(val);
+		val = NULL;
+	}
+	/* End of outer loop (!done && ngt_next(ngt) != 0) */
+
+	ngt_destroy(ngt);
+	free(ngt);
+
+	if (done) {
+		return (result);
+	} else if (nfound > 0) {
+		/* ==== ? Should only do this if all the top-level groups */
+		/*	  exist in YP?					  */
+		return (NSS_SUCCESS);
+	} else {
+		return (NSS_NOTFOUND);
+	}
+}
+
+
+/*
+ * Code for setnetgrent()
+ */
+
+/*
+ * Iterator function for setnetgrent():  copy triple, add to be->all_members
+ */
+static int
+save_triple(ccp trippp[NSS_NETGR_N], void *headp_arg,
+    nss_status_t *return_val)
+{
+	struct grouplist	**headp = headp_arg;
+	struct grouplist	*gl;
+	enum nss_netgr_argn	i;
+
+	if (__nss_files_netgr_debug != NULL)
+		__nss_files_netgr_debug(
+		    "save_tripple: h=%s u=%s d=%s",
+		    trippp[0] ? trippp[0] : "*",
+		    trippp[1] ? trippp[1] : "*",
+		    trippp[2] ? trippp[2] : "*");
+
+	if ((gl = (struct grouplist *)malloc(sizeof (*gl))) == 0) {
+		/* Out of memory */
+		*return_val = NSS_UNAVAIL;
+		return (0);
+	}
+	for (i = NSS_NETGR_MACHINE;  i < NSS_NETGR_N;  i++) {
+		if (trippp[i] == 0) {
+			/* Wildcard */
+			gl->triple[i] = 0;
+		} else if ((gl->triple[i] = strdup(trippp[i])) == 0) {
+			/* Out of memory.  Free any we've allocated */
+			enum nss_netgr_argn	j;
+
+			for (j = NSS_NETGR_MACHINE;  j < i;  j++) {
+				if (gl->triple[j] != 0) {
+					free(gl->triple[j]);
+				}
+			}
+			*return_val = NSS_UNAVAIL;
+			return (0);
+		}
+	}
+	gl->gl_nxt = *headp;
+	*headp = gl;
+	return (1);	/* Tell top_down() to keep iterating */
+}
+
+static nss_status_t
+netgr_set(be, a)
+	struct files_backend	*be;
+	void			*a;
+{
+	struct nss_setnetgrent_args *args = (struct nss_setnetgrent_args *)a;
+	struct files_getnetgr_be	*get_be;
+	nss_status_t		res;
+
+	get_be = (struct files_getnetgr_be *)malloc(sizeof (*get_be));
+	if (get_be == 0) {
+		return (NSS_UNAVAIL);
+	}
+
+	get_be->all_members = 0;
+	res = top_down(be, &args->netgroup, 1, save_triple,
+		&get_be->all_members);
+
+	if (res == NSS_SUCCESS) {
+		get_be->ops		= getnetgr_ops;
+		get_be->n_ops		= sizeof (getnetgr_ops) /
+						sizeof (getnetgr_ops[0]);
+		get_be->netgroup	= strdup(args->netgroup);
+		get_be->next_member	= get_be->all_members;
+
+		args->iterator		= (nss_backend_t *)get_be;
+	} else {
+		args->iterator		= 0;
+		free(get_be);
+	}
+	return (res);
+}
+
+
+/*
+ * Code for innetgr()
+ */
+
+/*
+ * Iterator function for innetgr():  Check whether triple matches args
+ */
+static int
+match_triple(ccp triple[NSS_NETGR_N], void *ia_arg, nss_status_t *return_val)
+{
+	struct nss_innetgr_args	*ia = ia_arg;
+	enum nss_netgr_argn	i;
+
+	if (__nss_files_netgr_debug != NULL)
+		__nss_files_netgr_debug(
+		    "match_tripple: h=%s u=%s d=%s",
+		    triple[0] ? triple[0] : "*",
+		    triple[1] ? triple[1] : "*",
+		    triple[2] ? triple[2] : "*");
+
+	for (i = NSS_NETGR_MACHINE;  i < NSS_NETGR_N;  i++) {
+		int		(*cmpf)(const char *, const char *);
+		char		**argv;
+		int		n;
+		const char	*name = triple[i];
+		int		argc = ia->arg[i].argc;
+
+		if (argc == 0 || name == 0) {
+			/* Wildcarded on one side or t'other */
+			continue;
+		}
+		argv = ia->arg[i].argv;
+		cmpf = (i == NSS_NETGR_MACHINE) ? strcasecmp : strcmp;
+		for (n = 0;  n < argc;  n++) {
+			if ((*cmpf)(argv[n], name) == 0) {
+				break;
+			}
+		}
+		if (n >= argc) {
+			/* Match failed, tell top_down() to keep looking */
+			return (1);
+		}
+	}
+	/* Matched on all three, so quit looking and declare victory */
+
+	if (__nss_files_netgr_debug != NULL)
+		__nss_files_netgr_debug("match_tripple: found");
+
+	ia->status = NSS_NETGR_FOUND;
+	*return_val = NSS_SUCCESS;
+	return (0);
+}
+
+/*
+ * Used to have easy_way() and it's support functions here.
+ */
+
+static nss_status_t
+netgr_in(be, a)
+	struct files_backend	*be;
+	void			*a;
+{
+	struct nss_innetgr_args	*ia = (struct nss_innetgr_args *)a;
+	nss_status_t		res;
+
+	ia->status = NSS_NETGR_NO;
+
+	/*
+	 * Used to have "easy_way" calls here for the cases
+	 * where we have just a user, or just a machine.
+	 *
+	 * That was important for NIS, where getting the list of
+	 * members for some netgroup was a yp_match call that may
+	 * need to go over-the-wire.  Here in the "files" backend,
+	 * getting the members of a group (getbyname) is a strictly
+	 * local operation, and is cached (see hashinfo above) so
+	 * it can normally complete with just memory operations.
+	 *
+	 * With a low-cost getbyname operation, the simple
+	 * top_down algorithm has acceptable performance.
+	 */
+
+	/* Nope, try the slow way */
+	ia->status = NSS_NETGR_NO;
+	res = top_down(be, (const char **)ia->groups.argv, ia->groups.argc,
+	    match_triple, ia);
+	return (res);
+}
+
+
+/*
+ * (Almost) boilerplate for a switch backend
+ */
+
+/*ARGSUSED*/
+static nss_status_t
+netgr_destr(be, dummy)
+	struct files_backend	*be;
+	void			*dummy;
+{
+	if (be != 0) {
+		free(be);
+	}
+	return (NSS_SUCCESS);
+}
+
+static files_backend_op_t netgroup_ops[] = {
+	netgr_destr,
+	0,		/* No endent, because no setent/getent */
+	0,		/* No setent;  setnetgrent() is really a getXbyY() */
+	0,		/* No getent in the normal sense */
+
+	netgr_in,	/* innetgr(), via NSS_DBOP_NETGROUP_IN */
+	netgr_set,	/* setnetgrent(), via NSS_DBOP_NETGROUP_SET */
+	getbyname,	/* For testing, via NSS_DBOP_NETGROUP_BYNAME */
+};
+
+/*
+ * This is the one-and-only external entry point in this file.
+ * It's called by the NSS framework when loading this backend.
+ */
+/*ARGSUSED*/
+nss_backend_t *
+_nss_files_netgroup_constr(dummy1, dummy2, dummy3)
+	const char	*dummy1, *dummy2, *dummy3;
+{
+	nss_backend_t	*be;
+
+	be = _nss_files_constr(netgroup_ops,
+	    sizeof (netgroup_ops) / sizeof (netgroup_ops[0]),
+	    "/etc/netgroup",
+	    NSS_LINELEN_NETGROUP,
+	    &hashinfo);
+
+	return (be);
+}
