root/usr/src/lib/libnsl/nis/gen/nis_subr.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * This module contains the subroutines used by the server to manipulate
 * objects and names.
 */
#include "mt.h"
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <rpc/rpc.h>    /* Must be ahead of rpcb_clnt.h */
#include <rpc/svc.h>
#include <tiuser.h>
#include <netconfig.h>
#include <netdir.h>
#include <rpc/rpcb_clnt.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nis.h>
#include <rpcsvc/nis_dhext.h>
#include "nis_clnt.h"
#include <sys/systeminfo.h>
#include <nsswitch.h>

#define MAXIPRINT       (11)    /* max length of printed integer */
/*
 * send and receive buffer size used for clnt_tli_create if not specified.
 * This is only used for "UDP" connection.
 * This limit can be changed from the application if this value is too
 * small for the application.  To use the maximum value for the transport,
 * set this value to 0.
 */
int __nisipbufsize = 8192;


/*
 * Static function prototypes.
 */
static struct local_names *__get_local_names(void);
static char *__map_addr(struct netconfig *, char *, rpcprog_t, rpcvers_t);

/*
 * nis_dir_cmp() -- the results can be read as:
 *      "Name 'n1' is a $result than name 'n2'"
 */
name_pos
nis_dir_cmp(
        nis_name        n1,
        nis_name        n2)     /* See if these are the same domain */
{
        size_t          l1, l2;
        name_pos        result;

        if ((n1 == NULL) || (n2 == NULL))
                return (BAD_NAME);

        l1 = strlen(n1);
        l2 = strlen(n2);

        /* In this routine we're lenient and don't require a trailing '.' */
        /*   so we need to ignore it if it does appear.                   */
        /* ==== That's what the previous version did so this one does     */
        /*   too, but why?  Is this inconsistent with rest of system?     */
        if (l1 != 0 && n1[l1 - 1] == '.') {
                --l1;
        }
        if (l2 != 0 && n2[l2 - 1] == '.') {
                --l2;
        }

        if (l1 > l2) {
                result = LOWER_NAME;
        } else if (l1 == l2) {
                result = SAME_NAME;
        } else /* (l1 < l2); swap l1/l2 and n1/n2 */ {
                nis_name        ntmp;
                size_t          ltmp;
                ntmp = n1; n1 = n2; n2 = ntmp;
                ltmp = l1; l1 = l2; l2 = ltmp;

                result = HIGHER_NAME;
        }

        /* Now l1 >= l2 in all cases */
        if (l2 == 0) {
                /* Special case for n2 == "." or "" */
                return (result);
        }
        if (l1 > l2) {
                n1 += l1 - l2;
                if (n1[-1] != '.') {
                        return (NOT_SEQUENTIAL);
                }
        }
        if (strncasecmp(n1, n2, l2) == 0) {
                return (result);
        }
        return (NOT_SEQUENTIAL);
}

#define LN_BUFSIZE      (size_t)1024

struct principal_list {
        uid_t uid;
        char principal[LN_BUFSIZE];
        struct principal_list *next;
};


struct local_names {
        char domain[LN_BUFSIZE];
        char host[LN_BUFSIZE];
        char *rpcdomain;
        struct principal_list *principal_map;
        char group[LN_BUFSIZE];
};

static mutex_t ln_lock = DEFAULTMUTEX; /* lock level 2 */
static struct local_names *ln = NULL;
static struct local_names *__get_local_names1();

static struct local_names *
__get_local_names(void)
{
        struct local_names *names;

        sig_mutex_lock(&ln_lock);
        names = __get_local_names1();
        sig_mutex_unlock(&ln_lock);
        return (names);
}


static struct local_names *
__get_local_names1(void)
{
        char            *t;

        if (ln != NULL) {
                /* Second and subsequent calls go this way */
                return (ln);
        }
        /* First call goes this way */
        ln = calloc(1, sizeof (*ln));
        if (ln == NULL) {
                syslog(LOG_ERR, "__get_local_names: Out of heap.");
                return (NULL);
        }
        ln->principal_map = NULL;

        if (sysinfo(SI_SRPC_DOMAIN, ln->domain, LN_BUFSIZE) < 0)
                return (ln);
        /* If no dot exists, add one. */
        if (ln->domain[strlen(ln->domain)-1] != '.')
                (void) strcat(ln->domain, ".");
        if (sysinfo(SI_HOSTNAME, ln->host, LN_BUFSIZE) < 0)
                return (ln);

        /*
         * Check for fully qualified hostname.  If it's a fully qualified
         * hostname, strip off the domain part.  We always use the local
         * domainname for the host principal name.
         */
        t = strchr(ln->host, '.');
        if (t)
                *t = 0;
        if (ln->domain[0] != '.')
                (void) strcat(ln->host, ".");
        ln->rpcdomain = strdup(ln->domain);
        (void) strcat(ln->host, ln->domain);

        t = getenv("NIS_GROUP");
        if (t == NULL) {
                ln->group[0] = '\0';
        } else {
                size_t maxlen = LN_BUFSIZE-1;   /* max chars to copy */
                char *temp;                     /* temp marker */

                /*
                 * Copy <= maximum characters from NIS_GROUP; strncpy()
                 * doesn't terminate, so we do that manually. #1223323
                 * Also check to see if it's "".  If it's the null string,
                 * we return because we don't want to add ".domain".
                 */
                (void) strncpy(ln->group, t, maxlen);
                if (strcmp(ln->group, "") == 0) {
                        return (ln);
                }
                ln->group[maxlen] = '\0';

                /* Is the group name somewhat fully-qualified? */
                temp = strrchr(ln->group, '.');

                /* If not, we need to add ".domain" to the group */
                if ((temp == NULL) || (temp[1] != '\0')) {

                        /* truncate to make room for ".domain" */
                        ln->group[maxlen - (strlen(ln->domain)+1)] = '\0';

                        /* concat '.' if domain doesn't already have it */
                        if (ln->domain[0] != '.') {
                                (void) strcat(ln->group, ".");
                        }
                        (void) strcat(ln->group, ln->domain);
                }
        }
        return (ln);
}

/*
 * nis_local_group()
 *
 * Return's the group name of the current user.
 */
nis_name
nis_local_group(void)
{
        struct local_names      *ln = __get_local_names();

        /* LOCK NOTE: Warning, after initialization, "ln" is expected    */
        /* to stay constant, So no need to lock here. If this assumption */
        /* is changed, this code must be protected.                      */
        if (!ln)
                return (NULL);
        return (ln->group);
}

/*
 * __nis_nextsep_of()
 *
 * This internal funtion will accept a pointer to a NIS name string and
 * return a pointer to the next separator occurring in it (it will point
 * just past the first label).  It allows for labels to be "quoted" to
 * prevent the the dot character within them to be interpreted as a
 * separator, also the quote character itself can be quoted by using
 * it twice.  If the the name contains only one label and no trailing
 * dot character, a pointer to the terminating NULL is returned.
 */
nis_name
__nis_nextsep_of(char *s)
{
        char    *d;
        int     in_quotes = FALSE, quote_quote = FALSE;

        if (!s)
                return (NULL);

        for (d = s; (in_quotes && (*d != '\0')) ||
            (!in_quotes && (*d != '.') && (*d != '\0')); d++) {
                if (quote_quote && in_quotes && (*d != '"')) {
                        quote_quote = FALSE;
                        in_quotes = FALSE;
                        if (*d == '.')
                                break;
                } else if (quote_quote && in_quotes && (*d == '"')) {
                        quote_quote = FALSE;
                } else if (quote_quote && (*d != '"')) {
                        quote_quote = FALSE;
                        in_quotes = TRUE;
                } else if (quote_quote && (*d == '"')) {
                        quote_quote = FALSE;
                } else if (in_quotes && (*d == '"')) {
                        quote_quote = TRUE;
                } else if (!in_quotes && (*d == '"')) {
                        quote_quote = TRUE;
                }
        }

        if (quote_quote || in_quotes) {
                syslog(LOG_DEBUG, "__nis_nextsep_of: "
                    "Mismatched quotes in %s", s);
        }

        return (d);
}

/*
 * nis_domain_of()
 *
 * This internal funtion will accept a pointer to a NIS name string and
 * return a pointer to the "domain" part of it.
 *
 * ==== We don't need nis_domain_of_r(), but should we provide one for
 *      uniformity?
 */
nis_name
nis_domain_of(char *s)
{
        char    *d;

        d = __nis_nextsep_of(s);
        if (d == NULL)
                return (NULL);
        if (*d == '.')
                d++;
        if (*d == '\0') /* Don't return a zero length string */
                return ("."); /* return root domain instead */
        return (d);
}


/*
 * nis_leaf_of()
 *
 * Returns the first label of a name. (other half of __domain_of)
 */
nis_name
nis_leaf_of_r(
        const nis_name  s,
        char            *buf,
        size_t          bufsize)
{
        size_t          nchars;
        const char      *d = __nis_nextsep_of((char *)s);

        if (d == 0) {
                return (0);
        }
        nchars = d - s;
        if (bufsize < nchars + 1) {
                return (0);
        }
        (void) strncpy(buf, s, nchars);
        buf[nchars] = '\0';
        return (buf);
}

static pthread_key_t buf_key = PTHREAD_ONCE_KEY_NP;
static char buf_main[LN_BUFSIZE];

nis_name
nis_leaf_of(char *s)
{
        char *buf = thr_main()? buf_main :
            thr_get_storage(&buf_key, LN_BUFSIZE, free);

        if (buf == NULL)
                return (NULL);
        return (nis_leaf_of_r(s, buf,  LN_BUFSIZE));
}

/*
 * nis_name_of()
 * This internal function will remove from the NIS name, the domain
 * name of the current server, this will leave the unique part in
 * the name this becomes the "internal" version of the name. If this
 * function returns NULL then the name we were given to resolve is
 * bad somehow.
 * NB: Uses static storage and this is a no-no with threads. XXX
 */

nis_name
nis_name_of_r(
        char    *s,     /* string with the name in it. */
        char            *buf,
        size_t          bufsize)
{
        char                    *d;
        struct local_names      *ln = __get_local_names();
        size_t                  dl, sl;
        name_pos                p;

#ifdef lint
        bufsize = bufsize;
#endif /* lint */
        if ((!s) || (!ln))
                return (NULL);          /* No string, this can't continue */

        d  = &(ln->domain[0]);
        dl = strlen(ln->domain);        /* _always dot terminated_   */

        sl = strlen(s);
        if (sl >= bufsize || (s[sl-1] != '.' && sl >= bufsize-1))
                return (NULL);
        (void) strcpy(buf, s);          /* Make a private copy of 's'   */
        if (buf[sl-1] != '.') { /* Add a dot if necessary.  */
                (void) strcat(buf, ".");
                sl++;
        }

        if (dl == 1) {                  /* We're the '.' directory   */
                buf[sl-1] = '\0';       /* Lose the 'dot'         */
                return (buf);
        }

        p = nis_dir_cmp(buf, d);

        /* 's' is above 'd' in the tree */
        if ((p == HIGHER_NAME) || (p == NOT_SEQUENTIAL) || (p == SAME_NAME))
                return (NULL);

        /* Insert a NUL where the domain name starts in the string */
        buf[(sl - dl) - 1] = '\0';

        /* Don't return a zero length name */
        if (buf[0] == '\0')
                return (NULL);

        return (buf);
}

nis_name
nis_name_of(
        char    *s)     /* string with the name in it. */
{
        char *buf = thr_main()? buf_main :
            thr_get_storage(&buf_key, LN_BUFSIZE, free);

        if (!buf)
                return (NULL);
        return (nis_name_of_r(s, buf,  LN_BUFSIZE));
}



/*
 * nis_local_directory()
 *
 * Return a pointer to a string with the local directory name in it.
 */
nis_name
nis_local_directory(void)
{
        struct local_names      *ln = __get_local_names();

        /* LOCK NOTE: Warning, after initialization, "ln" is expected    */
        /* to stay constant, So no need to lock here. If this assumption */
        /* is changed, this code must be protected.                      */
        if (ln == NULL)
                return (NULL);
        return (ln->domain);
}

/*
 * __nis_rpc_domain()
 *
 * Return a pointer to a string with the rpc domain name in it.
 */
nis_name
__nis_rpc_domain()
{
        struct local_names      *ln = __get_local_names();

        /* LOCK NOTE: Warning, after initialization, "ln" is expected    */
        /* to stay constant, So no need to lock here. If this assumption */
        /* is changed, this code must be protected.                      */
        if (ln == NULL)
                return (NULL);
        return (ln->rpcdomain);
}


/*
 * nis_local_host()
 * Generate the principal name for this host, "hostname"+"domainname"
 * unless the hostname already has "dots" in its name.
 */
nis_name
nis_local_host(void)
{
        struct local_names      *ln = __get_local_names();

        /* LOCK NOTE: Warning, after initialization, "ln" is expected    */
        /* to stay constant, So no need to lock here. If this assumption */
        /* is changed, this code must be protected.                      */
        if (ln == NULL)
                return (NULL);

        return (ln->host);
}

/*
 * nis_destroy_object()
 * This function takes a pointer to a NIS object and deallocates it. This
 * is the inverse of __clone_object below. It must be able to correctly
 * deallocate partially allocated objects because __clone_object will call
 * it if it runs out of memory and has to abort. Everything is freed,
 * INCLUDING the pointer that is passed.
 */
void
nis_destroy_object(nis_object *obj)     /* The object to clone */
{
        if (obj == 0)
                return;
        xdr_free(xdr_nis_object, (char *)obj);
        free(obj);
} /* nis_destroy_object */

static void
destroy_nis_sdata(void *p)
{
        struct nis_sdata        *ns = p;

        if (ns->buf != 0)
                free(ns->buf);
        free(ns);
}

/* XXX Why are these static ? */
/* static XDR in_xdrs, out_xdrs; */


/*
 * __clone_object_r()
 * This function takes a pointer to a NIS object and clones it. This
 * duplicate object is now available for use in the local context.
 */
nis_object *
nis_clone_object_r(
        nis_object      *obj,   /* The object to clone */
        nis_object      *dest,  /* Use this pointer if non-null */
        struct nis_sdata *clone_buf_ptr)
{
        nis_object      *result; /* The clone itself */
        int             status; /* a counter variable */
        XDR             in_xdrs, out_xdrs;

        if (!nis_get_static_storage(clone_buf_ptr, 1,
            xdr_sizeof(xdr_nis_object, obj)))
                return (NULL);

        (void) memset(&in_xdrs, 0, sizeof (in_xdrs));
        (void) memset(&out_xdrs, 0, sizeof (out_xdrs));
        xdrmem_create(&in_xdrs, clone_buf_ptr->buf, clone_buf_ptr->size,
            XDR_ENCODE);
        xdrmem_create(&out_xdrs, clone_buf_ptr->buf, clone_buf_ptr->size,
            XDR_DECODE);

        /* Allocate a basic NIS object structure */
        if (dest) {
                (void) memset(dest, 0, sizeof (nis_object));
                result = dest;
        } else
                result = calloc(1, sizeof (nis_object));

        if (result == NULL)
                return (NULL);

        /* Encode our object into the clone buffer */
        (void) xdr_setpos(&in_xdrs, 0);
        status = xdr_nis_object(&in_xdrs, obj);
        if (status == FALSE)
                return (NULL);

        /* Now decode the buffer into our result pointer ... */
        (void) xdr_setpos(&out_xdrs, 0);
        status = xdr_nis_object(&out_xdrs, result);
        if (status == FALSE)
                return (NULL);

        /* presto changeo, a new object */
        return (result);
} /* __clone_object_r */


nis_object *
nis_clone_object(
        nis_object      *obj,   /* The object to clone */
        nis_object      *dest)  /* Use this pointer if non-null */
{
        static pthread_key_t clone_buf_key = PTHREAD_ONCE_KEY_NP;
        static struct nis_sdata clone_buf_main;
        struct nis_sdata *clone_buf_ptr;

        clone_buf_ptr = thr_main()? &clone_buf_main :
            thr_get_storage(&clone_buf_key, sizeof (struct nis_sdata),
            destroy_nis_sdata);
        return (nis_clone_object_r(obj, dest, clone_buf_ptr));
} /* __clone_object */

/* Various subroutines used by the server code */
nis_object *
nis_read_obj(char *f)   /* name of the object to read */
{
        FILE    *rootfile;
        int     status; /* Status of the XDR decoding */
        XDR     xdrs;   /* An xdr stream handle */
        nis_object      *res;

        res = calloc(1, sizeof (nis_object));
        if (!res)
                return (NULL);

        rootfile = fopen(f, "rF");
        if (rootfile == NULL) {
                /* This is ok if we are the root of roots. */
                free(res);
                return (NULL);
        }
        /* Now read in the object */
        xdrstdio_create(&xdrs, rootfile, XDR_DECODE);
        status = xdr_nis_object(&xdrs, res);
        xdr_destroy(&xdrs);
        (void) fclose(rootfile);
        if (!status) {
                syslog(LOG_ERR, "Object file %s is corrupt!", f);
                xdr_free(xdr_nis_object, (char *)res);
                free(res);
                return (NULL);
        }
        return (res);
}

int
nis_write_obj(
        char    *f,     /* name of the object to read */
        nis_object *o)  /* The object to write */
{
        FILE    *rootfile;
        int     status; /* Status of the XDR decoding */
        XDR     xdrs;   /* An xdr stream handle */

        rootfile = fopen(f, "wF");
        if (rootfile == NULL) {
                return (0);
        }
        /* Now encode the object */
        xdrstdio_create(&xdrs, rootfile, XDR_ENCODE);
        status = xdr_nis_object(&xdrs, o);
        xdr_destroy(&xdrs);
        (void) fclose(rootfile);
        return (status);
}

/*
 * Transport INDEPENDENT RPC code. This code assumes you
 * are using the new RPC/tli code and will build
 * a ping handle on top of a datagram transport.
 */

/*
 * __map_addr()
 *
 * This is our internal function that replaces rpcb_getaddr(). We
 * build our own to prevent calling netdir_getbyname() which could
 * recurse to the nameservice.
 */
static char *
__map_addr(
        struct netconfig        *nc,            /* Our transport        */
        char                    *uaddr,         /* RPCBIND address */
        rpcprog_t               prog,           /* Name service Prog */
        rpcvers_t               ver)
{
        CLIENT *client;
        RPCB            parms;          /* Parameters for RPC binder      */
        enum clnt_stat  clnt_st;        /* Result from the rpc call       */
        char            *ua = NULL;     /* Universal address of service   */
        char            *res = NULL;    /* Our result to the parent       */
        struct timeval  tv;             /* Timeout for our rpcb call      */
        int             ilen, olen;     /* buffer length for clnt_tli_create */

        /*
         * If using "udp", use __nisipbufsize if inbuf and outbuf are set to 0.
         */
        if (strcmp(NC_UDP, nc->nc_proto) == 0) {
                        /* for udp only */
                ilen = olen = __nisipbufsize;
        } else {
                ilen = olen = 0;
        }
        client = __nis_clnt_create(RPC_ANYFD, nc, uaddr, 0, 0,
            RPCBPROG, RPCBVERS, ilen, olen);
        if (!client)
                return (NULL);

        (void) clnt_control(client, CLSET_FD_CLOSE, NULL);

        /*
         * Now make the call to get the NIS service address.
         * We set the retry timeout to 3 seconds so that we
         * will retry a few times.  Retries should be rare
         * because we are usually only called when we know
         * a server is available.
         */
        tv.tv_sec = 3;
        tv.tv_usec = 0;
        (void) clnt_control(client, CLSET_RETRY_TIMEOUT, (char *)&tv);

        tv.tv_sec = 10;
        tv.tv_usec = 0;
        parms.r_prog = prog;
        parms.r_vers = ver;
        parms.r_netid = nc->nc_netid;   /* not needed */
        parms.r_addr = "";      /* not needed; just for xdring */
        parms.r_owner = "";     /* not needed; just for xdring */
        clnt_st = clnt_call(client, RPCBPROC_GETADDR, xdr_rpcb, (char *)&parms,
            xdr_wrapstring, (char *)&ua, tv);

        if (clnt_st == RPC_SUCCESS) {
                clnt_destroy(client);
                if (*ua == '\0') {
                        free(ua);
                        return (NULL);
                }
                res = strdup(ua);
                xdr_free(xdr_wrapstring, (char *)&ua);
                return (res);
        } else if (((clnt_st == RPC_PROGVERSMISMATCH) ||
            (clnt_st == RPC_PROGUNAVAIL)) &&
            (strcmp(nc->nc_protofmly, NC_INET) == 0)) {
                /*
                 * version 3 not available. Try version 2
                 * The assumption here is that the netbuf
                 * is arranged in the sockaddr_in
                 * style for IP cases.
                 *
                 * Note:        If the remote host doesn't support version 3,
                 *              we assume it doesn't know IPv6 either.
                 */
                ushort_t                port;
                struct sockaddr_in      *sa;
                struct netbuf           remote;
                int                     protocol;
                char                    buf[32];

                (void) clnt_control(client, CLGET_SVC_ADDR, (char *)&remote);
                /* LINTED pointer cast */
                sa = (struct sockaddr_in *)(remote.buf);
                protocol = strcmp(nc->nc_proto, NC_TCP) ?
                    IPPROTO_UDP : IPPROTO_TCP;
                port = (ushort_t)pmap_getport(sa, prog, ver, protocol);

                if (port != 0) {
                        port = htons(port);
                        (void) sprintf(buf, "%d.%d.%d.%d.%d.%d",
                            (sa->sin_addr.s_addr >> 24) & 0xff,
                            (sa->sin_addr.s_addr >> 16) & 0xff,
                            (sa->sin_addr.s_addr >>  8) & 0xff,
                            (sa->sin_addr.s_addr) & 0xff,
                            (port >> 8) & 0xff,
                            port & 0xff);
                        res = strdup(buf);
                } else
                        res = NULL;
                clnt_destroy(client);
                return (res);
        }
        if (clnt_st == RPC_TIMEDOUT)
                syslog(LOG_ERR, "NIS+ server not responding");
        else
                syslog(LOG_ERR, "NIS+ server could not be contacted: %s",
                    clnt_sperrno(clnt_st));
        clnt_destroy(client);
        return (NULL);
}


#define MAX_EP (20)

extern int __can_use_af(sa_family_t af);

CLIENT *
__nis_clnt_create(int fd, struct netconfig *nc, char *uaddr,
                        struct netbuf *addr, int domapaddr,
                        int prog, int ver, int inbuf, int outbuf) {

        char            *svc_addr;
        CLIENT          *clnt;
        int             freeaddr = 0;

        /* Sanity check */
        if (nc == 0 || (addr == 0 && uaddr == 0)) {
                return (0);
        }

        /*
         * Check if we have a useable interface for this address family.
         * This check properly belongs in RPC (or even further down),
         * but until they provide it, we roll our own.
         */
        if (__can_use_af((strcmp(nc->nc_protofmly, NC_INET6) == 0) ?
                        AF_INET6 : AF_INET) == 0) {
                return (0);
        }

        if (domapaddr) {
                svc_addr = __map_addr(nc, uaddr, prog, ver);
                if (svc_addr == 0)
                        return (0);
                addr = uaddr2taddr(nc, svc_addr);
                freeaddr = 1;
                free(svc_addr);
        } else if (addr == 0) {
                addr = uaddr2taddr(nc, uaddr);
                freeaddr = 1;
        }

        if (addr == 0) {
                return (0);
        }

        clnt = clnt_tli_create(fd, nc, addr, prog, ver, outbuf, inbuf);

        if (clnt) {
                if (clnt_control(clnt, CLGET_FD, (char *)&fd))
                        /* make it "close on exec" */
                        (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
                (void) clnt_control(clnt, CLSET_FD_CLOSE, NULL);
        }

        if (freeaddr)
                netdir_free(addr, ND_ADDR);

        return (clnt);
}

static mutex_t __nis_ss_used_lock = DEFAULTMUTEX; /* lock level 3 */
int     __nis_ss_used = 0;

/*
 * nis_get_static_storage()
 *
 * This function is used by various functions in their effort to minimize the
 * hassles of memory management in an RPC daemon. Because the service doesn't
 * implement any hard limits, this function allows people to get automatically
 * growing buffers that meet their storage requirements. It returns the
 * pointer in the nis_sdata structure.
 *
 */
void *
nis_get_static_storage(
        struct nis_sdata        *bs,    /* User buffer structure */
        uint_t                  el,     /* Sizeof elements       */
        uint_t                  nel)    /* Number of elements    */
{
        uint_t  sz;

        sz = nel * el;
        if (!bs)
                return (NULL);

        if (!bs->buf) {
                bs->buf = malloc(sz);
                if (!bs->buf)
                        return (NULL);
                bs->size = sz;
                sig_mutex_lock(&__nis_ss_used_lock);
                __nis_ss_used += sz;
                sig_mutex_unlock(&__nis_ss_used_lock);
        } else if (bs->size < sz) {
                int     size_delta;

                free(bs->buf);
                size_delta = - (bs->size);
                bs->buf = malloc(sz);

                /* check the result of malloc() first   */
                /* then update the statistic.           */
                if (!bs->buf)
                        return (NULL);
                bs->size = sz;
                size_delta += sz;
                sig_mutex_lock(&__nis_ss_used_lock);
                __nis_ss_used += size_delta;
                sig_mutex_unlock(&__nis_ss_used_lock);
        }

        (void) memset(bs->buf, 0, sz); /* SYSV version of bzero() */
        return (bs->buf);
}