root/usr/src/lib/nsswitch/files/common/getnetgrent.c
/*
 * 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);
}