summaryrefslogtreecommitdiff
path: root/usr/src/lib/nsswitch/files/common/getnetgrent.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/nsswitch/files/common/getnetgrent.c')
-rw-r--r--usr/src/lib/nsswitch/files/common/getnetgrent.c869
1 files changed, 869 insertions, 0 deletions
diff --git a/usr/src/lib/nsswitch/files/common/getnetgrent.c b/usr/src/lib/nsswitch/files/common/getnetgrent.c
new file mode 100644
index 0000000000..58e5eaae3e
--- /dev/null
+++ b/usr/src/lib/nsswitch/files/common/getnetgrent.c
@@ -0,0 +1,869 @@
+/*
+ * 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>
+#include <sys/sysmacros.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;
+ uint_t 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(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.
+ */
+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);
+ if (netgr->netgr_members == NULL)
+ return (NSS_STR_PARSE_PARSE);
+
+ 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(struct files_getnetgr_be *be, void *a)
+{
+ const char *netgroup = (const char *) a;
+
+ if (be->netgroup != NULL &&
+ 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(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] = NULL;
+ } 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. */
+}
+
+static nss_status_t
+getnetgr_end(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++) {
+ free(gl->triple[i]);
+ }
+ free(gl);
+ }
+ be->all_members = NULL;
+ be->next_member = NULL;
+ free(be->netgroup);
+ be->netgroup = NULL;
+ return (NSS_SUCCESS);
+}
+
+static nss_status_t
+getnetgr_destr(struct files_getnetgr_be *be, void *dummy)
+{
+ if (be != NULL) {
+ (void) getnetgr_end(be, NULL);
+ 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(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(struct netgrtab *ngt, const char *name, size_t namelen)
+{
+ unsigned hashval;
+ size_t i;
+ struct netgrnam *cur;
+ struct netgrnam **head;
+
+ 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 = malloc(offsetof(struct netgrnam, name) + namelen + 1);
+ if (cur == NULL) {
+ 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 = NULL;
+ *ngt->expand_lastp = cur;
+ ngt->expand_lastp = &cur->expand_next;
+}
+
+static const char *
+ngt_next(struct netgrtab *ngt)
+{
+ struct netgrnam *first;
+
+ if ((first = ngt->expand_first) == NULL) {
+ return (NULL);
+ }
+ if ((ngt->expand_first = first->expand_next) == NULL) {
+ ngt->expand_lastp = &ngt->expand_first;
+ }
+ return (first->name);
+}
+
+static void
+ngt_destroy(struct netgrtab *ngt)
+{
+ struct netgrnam *cur;
+ struct netgrnam *next;
+ int i;
+
+ for (i = 0; i < HASHMOD; i++) {
+ for (cur = ngt->hash_heads[i]; cur != NULL; ) {
+ 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 = malloc(sizeof (*ngt))) == NULL) {
+ 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)) != NULL) {
+ char *val = NULL;
+ char *p;
+
+ result = netgr_get_members(be, group, &val);
+ if (result != NSS_SUCCESS) {
+ 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")) != NULL) {
+ *p = '\0';
+ }
+ p = val;
+
+ /* Parse val into triples and recursive netgroup references */
+ for (;;) {
+ 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) == 0) {
+ /* 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 = malloc(sizeof (*gl))) == NULL) {
+ /* Out of memory */
+ *return_val = NSS_UNAVAIL;
+ return (0);
+ }
+ for (i = NSS_NETGR_MACHINE; i < NSS_NETGR_N; i++) {
+ if (trippp[i] == NULL) {
+ /* Wildcard */
+ gl->triple[i] = NULL;
+ } else if ((gl->triple[i] = strdup(trippp[i])) == NULL) {
+ /* Out of memory. Free any we've allocated */
+ enum nss_netgr_argn j;
+
+ for (j = NSS_NETGR_MACHINE; j < i; j++) {
+ free(gl->triple[j]);
+ }
+ free(gl);
+ *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(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 = malloc(sizeof (*get_be));
+ if (get_be == NULL) {
+ return (NSS_UNAVAIL);
+ }
+
+ get_be->all_members = NULL;
+ 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 = ARRAY_SIZE(getnetgr_ops);
+ get_be->netgroup = strdup(args->netgroup);
+ if (get_be->netgroup == NULL) {
+ /* Out of memory. */
+ args->iterator = NULL;
+ free(get_be);
+ return (NSS_UNAVAIL);
+ }
+ get_be->next_member = get_be->all_members;
+
+ args->iterator = (nss_backend_t *)get_be;
+ } else {
+ args->iterator = NULL;
+ 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_triple: 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;
+ uint_t n;
+ const char *name = triple[i];
+ int argc = ia->arg[i].argc;
+
+ if (argc == 0 || name == NULL) {
+ /* 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_triple: 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(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
+ */
+
+static nss_status_t
+netgr_destr(struct files_backend *be, void *dummy)
+{
+ free(be);
+ return (NSS_SUCCESS);
+}
+
+static files_backend_op_t netgroup_ops[] = {
+ netgr_destr,
+ NULL, /* No endent, because no setent/getent */
+ NULL, /* No setent; setnetgrent() is really a getXbyY() */
+ NULL, /* 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.
+ */
+nss_backend_t *
+_nss_files_netgroup_constr(const char *dummy1, const char *dummy2,
+ const char *dummy3)
+{
+ nss_backend_t *be;
+
+ be = _nss_files_constr(netgroup_ops,
+ ARRAY_SIZE(netgroup_ops),
+ "/etc/netgroup",
+ NSS_LINELEN_NETGROUP,
+ &hashinfo);
+
+ return (be);
+}