root/usr/src/cmd/rpcbind/rpcb_svc_com.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.
 */
/*
 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2017 Joyent Inc
 */
/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

/*
 * rpcb_svc_com.c
 * The commom server procedure for the rpcbind.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <rpc/rpc.h>
#include <rpc/rpcb_prot.h>
#include <rpcsvc/svc_dg_priv.h>
#include <netconfig.h>
#include <sys/param.h>
#include <errno.h>
#include <zone.h>
#include <sys/poll.h>
#include <sys/stropts.h>
#ifdef PORTMAP
#include <netinet/in.h>
#include <rpc/pmap_prot.h>
#else
#define PMAPVERS        2
#endif /* PORTMAP */
#include <syslog.h>
#include <netdir.h>
#include <ucred.h>
#include <alloca.h>
#include <rpcsvc/yp_prot.h>
#include <nfs/nfs.h>
#include <nfs/nfs_acl.h>
#include <rpcsvc/mount.h>
#include <nfs/nfs_acl.h>
#include <rpc/key_prot.h>
#include <rpcsvc/yp_prot.h>
#include <rpcsvc/rquota.h>
#include <rpcsvc/yppasswd.h>
#include <rpcsvc/ypupd.h>
#include <assert.h>
#include <synch.h>
#include "rpcbind.h"
#include <sys/debug.h>

static struct finfo *forward_register(ulong_t, struct netbuf *, int, char *);
static void forward_destroy(struct finfo *);
static void handle_reply(svc_input_id_t, int, unsigned int, void *);
static int netbufcmp(struct netbuf *, struct netbuf *);
static void netbuffree(struct netbuf *);
static struct netbuf *netbufdup(struct netbuf *);
static void find_versions(rpcprog_t, char *, rpcvers_t *, rpcvers_t *);
static rpcblist_ptr find_service(ulong_t, ulong_t, char *);
#ifdef PORTMAP
static int add_pmaplist(RPCB *);
#endif

zoneid_t myzone;

/*
 * Set a mapping of program, version, netid
 */
bool_t
rpcbproc_set_com(RPCB *regp, bool_t *result, struct svc_req *rqstp,
    int rpcbversnum)
{
        char owner[64];

        *result = map_set(regp, getowner(rqstp->rq_xprt, owner));

        rpcbs_set(rpcbversnum - PMAPVERS, *result);

        return (TRUE);
}

bool_t
map_set(RPCB *regp, char *owner)
{
        RPCB *a;
        rpcblist_ptr rbl, fnd;

        /*
         * check to see if already used
         * find_service returns a hit even if
         * the versions don't match, so check for it
         */
        (void) rw_wrlock(&list_rbl_lock);
#ifdef PORTMAP
        (void) rw_wrlock(&list_pml_lock);
#endif /* PORTMAP */
        fnd = find_service(regp->r_prog, regp->r_vers, regp->r_netid);
        if (fnd && (fnd->rpcb_map.r_vers == regp->r_vers)) {
                if (strcmp(fnd->rpcb_map.r_addr, regp->r_addr) == 0) {
                        /*
                         * if these match then it is already
                         * registered so just say "OK".
                         */
#ifdef PORTMAP
                        (void) rw_unlock(&list_pml_lock);
#endif /* PORTMAP */
                        (void) rw_unlock(&list_rbl_lock);
                        return (TRUE);
                } else {
                        /*
                         * Check if server is up.  If so, return FALSE.
                         * If not, cleanup old registrations for the
                         * program and register the new server.
                         */
                        if (is_bound(fnd->rpcb_map.r_netid,
                            fnd->rpcb_map.r_addr)) {
#ifdef PORTMAP
                                (void) rw_unlock(&list_pml_lock);
#endif /* PORTMAP */
                                (void) rw_unlock(&list_rbl_lock);
                                return (FALSE);
                        }

                        delete_prog(regp->r_prog);
                        fnd = NULL;
                }
        }
#ifdef PORTMAP
        (void) rw_unlock(&list_pml_lock);
#endif /* PORTMAP */

        /*
         * add to the end of the list
         */
        rbl = malloc(sizeof (RPCBLIST));
        if (rbl == NULL) {
                (void) rw_unlock(&list_rbl_lock);
                return (FALSE);
        }
        a = &rbl->rpcb_map;
        a->r_prog = regp->r_prog;
        a->r_vers = regp->r_vers;
        a->r_netid = strdup(regp->r_netid);
        a->r_addr = strdup(regp->r_addr);
        a->r_owner = strdup(owner);
        if (a->r_addr == NULL || a->r_netid == NULL|| a->r_owner == NULL) {
                (void) rw_unlock(&list_rbl_lock);
                delete_rbl(rbl);
                return (FALSE);
        }
        rbl->rpcb_next = NULL;
        if (list_rbl == NULL) {
                list_rbl = rbl;
        } else {
                for (fnd = list_rbl; fnd->rpcb_next; fnd = fnd->rpcb_next)
                        ;
                fnd->rpcb_next = rbl;
        }

#ifdef PORTMAP
        (void) add_pmaplist(regp);
#endif
        (void) rw_unlock(&list_rbl_lock);
        return (TRUE);
}

/*
 * Unset a mapping of program, version, netid
 */
bool_t
rpcbproc_unset_com(RPCB *regp, bool_t *result, struct svc_req *rqstp,
    int rpcbversnum)
{
        char owner[64];

        *result = map_unset(regp, getowner(rqstp->rq_xprt, owner));

        rpcbs_unset(rpcbversnum - PMAPVERS, *result);

        return (TRUE);
}

bool_t
map_unset(RPCB *regp, char *owner)
{
#ifdef PORTMAP
        int ans = 0;
#endif
        rpcblist_ptr rbl, next, prev = NULL;

        if (owner == NULL)
                return (0);

        (void) rw_wrlock(&list_rbl_lock);
        for (rbl = list_rbl; rbl != NULL; rbl = next) {
                next = rbl->rpcb_next;

                if ((rbl->rpcb_map.r_prog != regp->r_prog) ||
                    (rbl->rpcb_map.r_vers != regp->r_vers) ||
                    (regp->r_netid[0] && strcasecmp(regp->r_netid,
                    rbl->rpcb_map.r_netid))) {
                        /* prev moves forwards */
                        prev = rbl;
                        continue;
                }

                /*
                 * Check whether appropriate uid. Unset only
                 * if superuser or the owner itself.
                 */
                if (strcmp(owner, "superuser") &&
                    strcmp(rbl->rpcb_map.r_owner, owner)) {
                        (void) rw_unlock(&list_rbl_lock);
                        return (0);
                }

                /* prev stays */
#ifdef PORTMAP
                ans = 1;
#endif
                delete_rbl(rbl);

                if (prev == NULL)
                        list_rbl = next;
                else
                        prev->rpcb_next = next;
        }
#ifdef PORTMAP
        if (ans != 0) {
                (void) rw_wrlock(&list_pml_lock);
                (void) del_pmaplist(regp);
                (void) rw_unlock(&list_pml_lock);
        }
#endif
        (void) rw_unlock(&list_rbl_lock);

        /*
         * We return 1 either when the entry was not there or it
         * was able to unset it.  It can come to this point only if
         * at least one of the conditions is true.
         */
        return (1);
}

void
delete_rbl(rpcblist_ptr rbl)
{
        free(rbl->rpcb_map.r_addr);
        free(rbl->rpcb_map.r_netid);
        free(rbl->rpcb_map.r_owner);
        free(rbl);
}

void
delete_prog(rpcprog_t prog)
{
        rpcblist_ptr rbl, next, prev = NULL;

        assert(RW_WRITE_HELD(&list_rbl_lock));

        for (rbl = list_rbl; rbl != NULL; rbl = next) {
                next = rbl->rpcb_next;

                if (rbl->rpcb_map.r_prog != prog ||
                    is_bound(rbl->rpcb_map.r_netid, rbl->rpcb_map.r_addr)) {
                        prev = rbl;
                        continue;
                }

#ifdef PORTMAP
                (void) del_pmaplist(&rbl->rpcb_map);
#endif
                delete_rbl(rbl);

                if (prev == NULL)
                        list_rbl = next;
                else
                        prev->rpcb_next = next;
        }
}

/*
 * Lookup the mapping for a program, version and return its
 * address. Assuming that the caller wants the address of the
 * server running on the transport on which the request came.
 *
 * For RPCBPROC_GETVERSADDR it will return a service with the exact version
 * number only.
 *
 * Otherwise, even if a service with a different version number is available,
 * it will return that address.  The client should check with an
 * clnt_call to verify whether the service is the one that is desired.
 *
 * We also try to resolve the universal address in terms of
 * address of the caller.
 */
bool_t
rpcbproc_getaddr_com(RPCB *regp, char **result, struct svc_req *rqstp,
    ulong_t rpcbversnum)
{
        char *saddr = NULL;
        rpcblist_ptr fnd;
        struct netconfig *trans_conf;   /* transport netconfig */
        SVCXPRT *transp = rqstp->rq_xprt;
        int verstype = rqstp->rq_proc == RPCBPROC_GETVERSADDR ? RPCB_ONEVERS :
            RPCB_ALLVERS;
        bool_t pml_locked = FALSE;

        /*
         * There is a potential window at startup during which rpcbind
         * service has been established over IPv6 but not over IPv4.  If an
         * IPv4 request comes in during that window, the IP code will map
         * it into IPv6.  We could patch up the request so that it looks
         * like IPv4 (so that rpcbind returns an IPv4 uaddr to the caller),
         * but that requires some non-trivial code and it's hard to test.
         * Instead, drop the request on the floor and force the caller to
         * retransmit.  By the time rpcbind sees the retransmission, IPv4
         * service should be in place and it should see the request as
         * IPv4, as desired.
         */
        trans_conf = rpcbind_get_conf(transp->xp_netid);
        if (strcmp(trans_conf->nc_protofmly, NC_INET6) == 0) {
                struct sockaddr_in6 *rmtaddr;

                rmtaddr = (struct sockaddr_in6 *)transp->xp_rtaddr.buf;
                if (IN6_IS_ADDR_V4MAPPED(&rmtaddr->sin6_addr)) {
                        syslog(LOG_DEBUG,
                            "IPv4 GETADDR request mapped to IPv6: ignoring");
                        *result = NULL;
                        return (FALSE);
                }
        }

        (void) rw_rdlock(&list_rbl_lock);
retry:
        fnd = find_service(regp->r_prog, regp->r_vers, transp->xp_netid);
        if (fnd && ((verstype == RPCB_ALLVERS) ||
            (regp->r_vers == fnd->rpcb_map.r_vers))) {
                if (*(regp->r_addr) != '\0') {  /* may contain a hint about */
                        saddr = regp->r_addr;   /* the interface that we    */
                }                               /* should use */
                if (!(*result = mergeaddr(transp, transp->xp_netid,
                    fnd->rpcb_map.r_addr, saddr))) {
                        /* Try whatever we have */
                        *result = strdup(fnd->rpcb_map.r_addr);
                } else if (!(*result)[0]) {
                        if (!pml_locked) {
                                (void) rw_unlock(&list_rbl_lock);
                                (void) rw_wrlock(&list_rbl_lock);
#ifdef PORTMAP
                                (void) rw_wrlock(&list_pml_lock);
#endif /* PORTMAP */
                                pml_locked = TRUE;
                                goto retry;
                        }
                        /*
                         * The server died.  Unset all versions of this prog.
                         */
                        delete_prog(regp->r_prog);
                        *result = NULL;
                }
        } else {
                *result = NULL;
        }
#ifdef PORTMAP
        if (pml_locked)
                (void) rw_unlock(&list_pml_lock);
#endif /* PORTMAP */
        (void) rw_unlock(&list_rbl_lock);

        rpcbs_getaddr(rpcbversnum - PMAPVERS, regp->r_prog, regp->r_vers,
            transp->xp_netid, *result);
        return (TRUE);
}

/* ARGSUSED */
bool_t
rpcbproc_dump_com(void *argp, rpcblist_ptr **result)
{
        /*
         * list_rbl_lock is unlocked in xdr_rpcblist_ptr_ptr()
         */
        (void) rw_rdlock(&list_rbl_lock);
        *result = &list_rbl;
        return (TRUE);
}

bool_t
xdr_rpcblist_ptr_ptr(XDR *xdrs, rpcblist_ptr **objp)
{
        if (xdrs->x_op == XDR_FREE) {
                /*
                 * list_rbl_lock is locked in rpcbproc_dump_com()
                 */
                rw_unlock(&list_rbl_lock);
                return (TRUE);
        }

        return (xdr_rpcblist_ptr(xdrs, *objp));
}

/* ARGSUSED */
bool_t
rpcbproc_gettime_com(void *argp, ulong_t *result)
{
        (void) time((time_t *)result);

        return (TRUE);
}

/*
 * Convert uaddr to taddr. Should be used only by
 * local servers/clients. (kernel level stuff only)
 */
bool_t
rpcbproc_uaddr2taddr_com(char **uaddrp, struct netbuf *result,
    struct svc_req *rqstp)
{
        struct netconfig *nconf;
        struct netbuf *taddr;

        if (((nconf = rpcbind_get_conf(rqstp->rq_xprt->xp_netid)) == NULL) ||
            ((taddr = uaddr2taddr(nconf, *uaddrp)) == NULL)) {
                (void) memset(result, 0, sizeof (*result));
                return (TRUE);
        }

        memcpy(result, taddr, sizeof (*result));
        free(taddr);

        return (TRUE);
}

/*
 * Convert taddr to uaddr. Should be used only by
 * local servers/clients. (kernel level stuff only)
 */
bool_t
rpcbproc_taddr2uaddr_com(struct netbuf *taddr, char **result,
    struct svc_req *rqstp)
{
        struct netconfig *nconf;

        if ((nconf = rpcbind_get_conf(rqstp->rq_xprt->xp_netid)) == NULL)
                *result = NULL;
        else
                *result = taddr2uaddr(nconf, taddr);

        return (TRUE);
}

/*
 * Stuff for the rmtcall service
 */
bool_t
xdr_rpcb_rmtcallargs(XDR *xdrs, rpcb_rmtcallargs *objp)
{
        if (!xdr_u_long(xdrs, &objp->prog))
                return (FALSE);
        if (!xdr_u_long(xdrs, &objp->vers))
                return (FALSE);
        if (!xdr_u_long(xdrs, &objp->proc))
                return (FALSE);
        if (!xdr_bytes(xdrs, (char **)&objp->args.args_val,
            (uint_t *)&objp->args.args_len, ~0))
                return (FALSE);
        return (TRUE);
}

#ifdef PORTMAP
bool_t
xdr_rmtcallres(XDR *xdrs, rmtcallres *objp)
{
        if (!xdr_u_long(xdrs, &objp->port))
                return (FALSE);
        if (!xdr_bytes(xdrs, (char **)&objp->res.res_val,
            (uint_t *)&objp->res.res_len, ~0))
                return (FALSE);
        return (TRUE);
}
#endif

bool_t
xdr_rpcb_rmtcallres(XDR *xdrs, rpcb_rmtcallres *objp)
{
        if (!xdr_string(xdrs, &objp->addr, ~0))
                return (FALSE);
        if (!xdr_bytes(xdrs, (char **)&objp->results.results_val,
            (uint_t *)&objp->results.results_len, ~0))
                return (FALSE);
        return (TRUE);
}

struct rmtcallfd_list {
        int fd;
        char *netid;
        struct rmtcallfd_list *next;
};

static struct rmtcallfd_list *rmthead;
static struct rmtcallfd_list *rmttail;

#define MASKVAL (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)

int
create_rmtcall_fd(struct netconfig *nconf)
{
        int fd;
        struct rmtcallfd_list *rmt;

        if ((fd = t_open(nconf->nc_device, O_RDWR, NULL)) == -1) {
                if (debugging)
                        fprintf(stderr, "create_rmtcall_fd: couldn't open "
                            "\"%s\" (errno %d, t_errno %d)\n",
                            nconf->nc_device, errno, t_errno);
                return (-1);
        }

        if (t_bind(fd, NULL, NULL) == -1) {
                if (debugging)
                        fprintf(stderr, "create_rmtcall_fd: couldn't bind to "
                            "fd for \"%s\" (errno %d, t_errno %d)\n",
                            nconf->nc_device, errno, t_errno);
                return (-1);
        }

        rmt = malloc(sizeof (struct rmtcallfd_list));
        if (rmt == NULL) {
                syslog(LOG_ERR, "create_rmtcall_fd: no memory!");
                return (-1);
        }

        rmt->netid = strdup(nconf->nc_netid);
        if (rmt->netid == NULL) {
                free(rmt);
                syslog(LOG_ERR, "create_rmtcall_fd: no memory!");
                return (-1);
        }

        if (svc_add_input(fd, MASKVAL, handle_reply, rmt->netid) == -1) {
                free(rmt->netid);
                free(rmt);
                syslog(LOG_ERR, "create_rmtcall_fd: svc_add_input() failed!");
                return (-1);
        }

        rmt->fd = fd;
        rmt->next = NULL;
        if (rmthead == NULL) {
                rmthead = rmt;
                rmttail = rmt;
        } else {
                rmttail->next = rmt;
                rmttail = rmt;
        }

        return (fd);
}

static int
find_rmtcallfd_by_netid(char *netid)
{
        struct rmtcallfd_list *rmt;

        for (rmt = rmthead; rmt != NULL; rmt = rmt->next) {
                if (strcmp(netid, rmt->netid) == 0) {
                        return (rmt->fd);
                }
        }
        return (-1);
}

#define MAXTIME_OFF     300     /* 5 minutes timeout for rmtcalls */

struct finfo {
        struct finfo    *prev;
        struct finfo    *next;
        int             flag;
#define FINFO_ACTIVE    0x1
        ulong_t         caller_xid;
        struct netbuf   *caller_addr;
        ulong_t         forward_xid;
        int             forward_fd;
        char            *uaddr;
        struct t_unitdata *reply_data;
        struct rpc_err  reply_error;
        uint_t          res_len;
        void            *res_val;
        cond_t          cv;
};

/*
 * finfo_lock protects rpcb_rmtcalls, rpcb_rmtcalls_max, lastxid,
 * fihead, and fitail.
 */
static mutex_t finfo_lock = DEFAULTMUTEX;

static int rpcb_rmtcalls;
static int rpcb_rmtcalls_max;
static ulong_t lastxid;
static struct finfo *fihead;
static struct finfo *fitail;

void
set_rpcb_rmtcalls_max(int max)
{
        (void) mutex_lock(&finfo_lock);
        rpcb_rmtcalls_max = max;
        if (rpcb_rmtcalls > rpcb_rmtcalls_max) {
                assert(fitail != NULL);
                (void) cond_signal(&fitail->cv);
        }
        (void) mutex_unlock(&finfo_lock);
}

/*
 * Call a remote procedure service.  This procedure is very quiet when things
 * go wrong.  The proc is written to support broadcast rpc.  In the broadcast
 * case, a machine should shut-up instead of complain, lest the requestor be
 * overrun with complaints at the expense of not hearing a valid reply.
 * When receiving a request and verifying that the service exists, we
 *
 *      receive the request
 *
 *      open a new TLI endpoint on the same transport on which we received
 *      the original request
 *
 *      remember the original request's XID (which requires knowing the format
 *      of the svc_dg_data structure)
 *
 *      forward the request, with a new XID, to the requested service,
 *      remembering the XID used to send this request (for later use in
 *      reassociating the answer with the original request), the requestor's
 *      address, the file descriptor on which the forwarded request is
 *      made and the service's address
 *
 *      wait for either the timeout or the condition variable is signalled from
 *      handle_reply().
 *
 * At some time in the future, a reply will be received from the service to
 * which we forwarded the request.  At that time, svc_run() detect that the
 * socket used was for forwarding and call handle_reply() to
 *
 *      receive the reply
 *
 *      bundle the reply, along with the service's universal address
 *
 *      put the reply into the particular finfo
 *
 *      signal the condition variable.
 */

#define RPC_BUF_MAX     65536   /* can be raised if required */

/*
 *  This is from ../ypcmd/yp_b.h
 *  It does not appear in <rpcsvc/yp_prot.h>
 */
#define YPBINDPROG ((ulong_t)100007)
#define YPBINDPROC_SETDOM ((ulong_t)2)

/*
 * reply_type - which proc number
 * versnum - which vers was called
 */
void
rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, ulong_t reply_type,
    int versnum)
{
        struct t_info tinfo;
        uint_t sendsz;

        rpcb_rmtcallargs arg;
        rpcblist_ptr rbl;

        struct netconfig *nconf;
        struct netbuf *caller;
        struct nd_mergearg ma;
        int stat;

        int fd;
        struct svc_dg_data *bd;
        struct finfo *fi;

        struct rpc_msg call_msg;
        char outbuf[RPC_BUF_MAX];
        char *outbuf_alloc = NULL;
        XDR outxdr;
        bool_t outxdr_created = FALSE;

        AUTH *auth;

        struct t_unitdata tu_data;
        struct netbuf *na;

        timestruc_t to;

        (void) mutex_lock(&finfo_lock);
        if (!allow_indirect || rpcb_rmtcalls_max == 0) {
                (void) mutex_unlock(&finfo_lock);
                return;
        }
        (void) mutex_unlock(&finfo_lock);

        if (t_getinfo(transp->xp_fd, &tinfo) == -1) {
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                return;
        }
        if (tinfo.servtype != T_CLTS)
                return; /* Only datagram type accepted */

        sendsz = __rpc_get_t_size(0, tinfo.tsdu);
        if (sendsz == 0) {      /* data transfer not supported */
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                return;
        }
        /*
         * Should be multiple of 4 for XDR.
         */
        sendsz = ((sendsz + 3) / 4) * 4;

        (void) memset((char *)&arg, 0, sizeof (arg));
        if (!svc_getargs(transp, xdr_rpcb_rmtcallargs, (char *)&arg)) {
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_decode(transp);
                if (debugging)
                        fprintf(stderr,
                        "rpcbproc_callit_com:  svc_getargs failed\n");
                goto error;
        }

        /*
         * Disallow calling rpcbind for certain procedures.
         * Allow calling NULLPROC - per man page on rpcb_rmtcall().
         * switch is in alphabetical order.
         */
        if (arg.proc != NULLPROC) {
                switch (arg.prog) {
                case KEY_PROG:
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting KEY_PROG(%d)\n",
                                    arg.proc);
                        goto error;
                case MOUNTPROG:
                        if (arg.proc != MOUNTPROC_MNT)
                                break;
                        /*
                         * In Solaris 2.6, the host-based accesss control
                         * is done by the NFS server on each request.
                         * Prior to 2.6 we rely on mountd.
                         */
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting MOUNTPROG(%d)\n",
                                    arg.proc);
                        goto error;
                case NFS_ACL_PROGRAM:
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting NFS_ACL_PROGRAM(%d)\n",
                                    arg.proc);
                        goto error;
                case NFS_PROGRAM:
                        /* also NFS3_PROGRAM */
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting NFS_PROGRAM(%d)\n",
                                    arg.proc);
                        goto error;
                case RPCBPROG:
                        /*
                         * Disallow calling rpcbind for certain procedures.
                         * Luckily Portmap set/unset/callit also have same
                         * procedure numbers.  So, will not check for those.
                         */
                        switch (arg.proc) {
                        case RPCBPROC_SET:
                        case RPCBPROC_UNSET:
                        case RPCBPROC_CALLIT:
                        case RPCBPROC_INDIRECT:
                                if (reply_type == RPCBPROC_INDIRECT)
                                        svcerr_weakauth(transp); /* XXX */
                                if (debugging)
                                        fprintf(stderr, "rpcbproc_callit_com: "
                                            "calling RPCBPROG procs SET, "
                                            "UNSET, CALLIT, or INDIRECT not "
                                            "allowed\n");
                                goto error;
                        default:
                                /*
                                 * Ideally, we should have called rpcb_service()
                                 * or pmap_service() with appropriate parameters
                                 * instead of going about in a roundabout
                                 * manner.  Hopefully, this case should happen
                                 * rarely.
                                 */
                                break;
                        }
                        break;
                case RQUOTAPROG:
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting RQUOTAPROG(%d)\n",
                                    arg.proc);
                        goto error;
                case YPPASSWDPROG:
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting YPPASSWDPROG(%d)\n",
                                    arg.proc);
                        goto error;
                case YPU_PROG:
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting YPU_PROG(%d)\n",
                                    arg.proc);
                        goto error;
                case YPBINDPROG:
                        if (arg.proc != YPBINDPROC_SETDOM)
                                break;
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbind: rejecting YPBINDPROG(%d)\n",
                                    arg.proc);
                        goto error;
                case YPPROG:
                        switch (arg.proc) {
                        case YPPROC_FIRST:
                        case YPPROC_NEXT:
                        case YPPROC_MATCH:
                        case YPPROC_ALL:
                                if (debugging)
                                        fprintf(stderr,
                                            "rpcbind: rejecting YPPROG(%d)\n",
                                            arg.proc);
                                goto error;
                        default:
                                break;
                        }
                        break;
                default:
                        break;
                }
        }

        (void) rw_rdlock(&list_rbl_lock);
        rbl = find_service(arg.prog, arg.vers, transp->xp_netid);

        rpcbs_rmtcall(versnum - PMAPVERS, reply_type, arg.prog, arg.vers,
            arg.proc, transp->xp_netid, rbl);

        if (rbl == NULL) {
                (void) rw_unlock(&list_rbl_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_noprog(transp);
                goto error;
        }
        if (rbl->rpcb_map.r_vers != arg.vers) {
                if (reply_type == RPCBPROC_INDIRECT) {
                        ulong_t vers_low, vers_high;

                        find_versions(arg.prog, transp->xp_netid,
                            &vers_low, &vers_high);
                        (void) rw_unlock(&list_rbl_lock);
                        svcerr_progvers(transp, vers_low, vers_high);
                } else {
                        (void) rw_unlock(&list_rbl_lock);
                }
                goto error;
        }

        /*
         * Check whether this entry is valid and a server is present
         * Mergeaddr() returns NULL if no such entry is present, and
         * returns "" if the entry was present but the server is not
         * present (i.e., it crashed).
         */
        if (reply_type == RPCBPROC_INDIRECT) {
                char *uaddr = mergeaddr(transp, transp->xp_netid,
                    rbl->rpcb_map.r_addr, NULL);
                if ((uaddr == (char *)NULL) || uaddr[0] == '\0') {
                        (void) rw_unlock(&list_rbl_lock);
                        svcerr_noprog(transp);
                        goto error;
                } else {
                        free(uaddr);
                }
        }

        nconf = rpcbind_get_conf(transp->xp_netid);
        if (nconf == NULL) {
                (void) rw_unlock(&list_rbl_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  rpcbind_get_conf failed\n");
                goto error;
        }

        caller = svc_getrpccaller(transp);
        ma.c_uaddr = taddr2uaddr(nconf, caller);
        ma.s_uaddr = rbl->rpcb_map.r_addr;

        /*
         * A mergeaddr operation allocates a string, which it stores in
         * ma.m_uaddr.  It's passed to forward_register() and is
         * eventually freed by forward_destroy().
         */
        stat = netdir_options(nconf, ND_MERGEADDR, 0, (char *)&ma);
        (void) rw_unlock(&list_rbl_lock);
        free(ma.c_uaddr);
        if (stat)
                (void) syslog(LOG_ERR, "netdir_merge failed for %s: %s",
                    nconf->nc_netid, netdir_sperror());

        if ((fd = find_rmtcallfd_by_netid(nconf->nc_netid)) == -1) {
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                free(ma.m_uaddr);
                goto error;
        }

        bd = get_svc_dg_data(transp);

        assert(!MUTEX_HELD(&finfo_lock));
        fi = forward_register(bd->su_xid, caller, fd, ma.m_uaddr);
        if (fi == NULL) {
                /*  forward_register failed.  Perhaps no memory. */
                free(ma.m_uaddr);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  forward_register failed\n");
                assert(!MUTEX_HELD(&finfo_lock));
                goto error;
        }
        /* forward_register() returns with finfo_lock held when successful */
        assert(MUTEX_HELD(&finfo_lock));

        if (fi->flag & FINFO_ACTIVE) {
                /*
                 * A duplicate request for the slow server.  Let's not
                 * beat on it any more.
                 */
                (void) mutex_unlock(&finfo_lock);
                free(ma.m_uaddr);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  duplicate request\n");
                goto error;
        }
        fi->flag |= FINFO_ACTIVE;

        call_msg.rm_xid = fi->forward_xid;
        call_msg.rm_direction = CALL;
        call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
        call_msg.rm_call.cb_prog = arg.prog;
        call_msg.rm_call.cb_vers = arg.vers;

        if (sendsz > RPC_BUF_MAX) {
                outbuf_alloc = malloc(sendsz);
                if (outbuf_alloc == NULL) {
                        forward_destroy(fi);
                        (void) mutex_unlock(&finfo_lock);
                        if (reply_type == RPCBPROC_INDIRECT)
                                svcerr_systemerr(transp);
                        if (debugging)
                                fprintf(stderr,
                                    "rpcbproc_callit_com:  No memory!\n");
                        goto error;
                }
                xdrmem_create(&outxdr, outbuf_alloc, sendsz, XDR_ENCODE);
        } else {
                xdrmem_create(&outxdr, outbuf, sendsz, XDR_ENCODE);
        }
        outxdr_created = TRUE;

        if (!xdr_callhdr(&outxdr, &call_msg)) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  xdr_callhdr failed\n");
                goto error;
        }

        if (!xdr_u_long(&outxdr, &arg.proc)) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  xdr_u_long failed\n");
                goto error;
        }

        if (rqstp->rq_cred.oa_flavor == AUTH_NULL) {
                auth = authnone_create();
        } else if (rqstp->rq_cred.oa_flavor == AUTH_SYS) {
                struct authsys_parms *au;

                CTASSERT(sizeof (struct authsys_parms) <= RQCRED_SIZE);
                au = (struct authsys_parms *)rqstp->rq_clntcred;
                auth = authsys_create(au->aup_machname, au->aup_uid,
                    au->aup_gid, au->aup_len, au->aup_gids);
                if (auth == NULL) /* fall back */
                        auth = authnone_create();
        } else {
                /* we do not support any other authentication scheme */
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_weakauth(transp); /* XXX too strong.. */
                if (debugging)
                        fprintf(stderr, "rpcbproc_callit_com:  oa_flavor != "
                            "AUTH_NONE and oa_flavor != AUTH_SYS\n");
                goto error;
        }
        if (auth == NULL) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        fprintf(stderr, "rpcbproc_callit_com:  "
                            "authwhatever_create returned NULL\n");
                goto error;
        }
        if (!AUTH_MARSHALL(auth, &outxdr)) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                AUTH_DESTROY(auth);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  AUTH_MARSHALL failed\n");
                goto error;
        }
        AUTH_DESTROY(auth);

        if (!xdr_opaque(&outxdr, arg.args.args_val, arg.args.args_len)) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  xdr_opaque failed\n");
                goto error;
        }

        tu_data.udata.len = XDR_GETPOS(&outxdr);
        if (outbuf_alloc)
                tu_data.udata.buf = outbuf_alloc;
        else
                tu_data.udata.buf = outbuf;
        tu_data.opt.len = 0;

        na = uaddr2taddr(nconf, ma.m_uaddr);
        if (!na) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);
                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                goto error;
        }
        tu_data.addr = *na;

        if (t_sndudata(fd, &tu_data) == -1) {
                int err = errno;
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);

                netdir_free((char *)na, ND_ADDR);

                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        fprintf(stderr,
                            "rpcbproc_callit_com:  t_sndudata failed:  "
                            "t_errno %d, errno %d\n", t_errno, err);
                goto error;
        }

        netdir_free((char *)na, ND_ADDR);
        xdr_destroy(&outxdr);
        outxdr_created = FALSE;
        if (outbuf_alloc != NULL) {
                free(outbuf_alloc);
                outbuf_alloc = NULL;
        }
        svc_freeargs(transp, xdr_rpcb_rmtcallargs, (char *)&arg);

        to.tv_sec = time(NULL) + MAXTIME_OFF;
        to.tv_nsec = 0;

        while (fi->reply_data == NULL &&
            cond_timedwait(&fi->cv, &finfo_lock, &to) != ETIME)
                ;

        if (fi->reply_data == NULL) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);

                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        (void) fprintf(stderr,
                            "rpcbproc_callit_com:  timeout\n");
                return;
        }

        if (fi->reply_error.re_status != RPC_SUCCESS) {
                forward_destroy(fi);
                (void) mutex_unlock(&finfo_lock);

                if (reply_type == RPCBPROC_INDIRECT)
                        svcerr_systemerr(transp);
                if (debugging)
                        (void) fprintf(stderr,
                            "rpcbproc_callit_com:  error in reply:  %s\n",
                            clnt_sperrno(fi->reply_error.re_status));
                return;
        }

        switch (versnum) {
#ifdef PORTMAP
        case PMAPVERS:
                {
                        rmtcallres result;
                        int h1, h2, h3, h4, p1, p2;

                        /* interpret the universal address for TCP/IP */
                        if (sscanf(fi->uaddr, "%d.%d.%d.%d.%d.%d",
                            &h1, &h2, &h3, &h4, &p1, &p2) != 6)
                                break;

                        result.port = ((p1 & 0xff) << 8) + (p2 & 0xff);
                        result.res.res_len = fi->res_len;
                        result.res.res_val = fi->res_val;

                        svc_sendreply(transp, xdr_rmtcallres, (char *)&result);
                }
                break;
#endif
        case RPCBVERS:
        case RPCBVERS4:
                {
                        rpcb_rmtcallres result;

                        result.addr = fi->uaddr;
                        result.results.results_len = fi->res_len;
                        result.results.results_val = fi->res_val;

                        svc_sendreply(transp, xdr_rpcb_rmtcallres,
                            (char *)&result);
                }
                break;
        }

        forward_destroy(fi);
        (void) mutex_unlock(&finfo_lock);

        return;

error:
        if (outxdr_created)
                xdr_destroy(&outxdr);
        free(outbuf_alloc);
        svc_freeargs(transp, xdr_rpcb_rmtcallargs, (char *)&arg);
}

static struct finfo *forward_find(ulong_t, char *);

/*
 * Adds an entry into the finfo list for the given request. Returns the finfo
 * and finfo_lock is left held.  If duplicate request, returns finfo with
 * FINFO_ACTIVE, else returns finfo without FINFO_ACTIVE.
 * If failed, returns NULL and finfo_lock is left unheld.
 */
static struct finfo *
forward_register(ulong_t caller_xid, struct netbuf *caller_addr, int forward_fd,
    char *uaddr)
{
        struct finfo    *fi;

        (void) mutex_lock(&finfo_lock);
        if (rpcb_rmtcalls_max == 0) {
                (void) mutex_unlock(&finfo_lock);
                return (NULL);
        }

        /*
         * initialization: once this has happened, lastxid will
         * never be 0 again, when entering or returning from this function.
         */
        if (lastxid == 0)
                lastxid = time(NULL);

        /*
         * Check if it is an duplicate entry
         */
        for (fi = fihead; fi != NULL; fi = fi->next) {
                if (fi->caller_xid == caller_xid &&
                    netbufcmp(fi->caller_addr, caller_addr)) {
                        assert(fi->flag & FINFO_ACTIVE);
                        return (fi);
                }
        }

        fi = malloc(sizeof (*fi));
        if (fi == NULL) {
                (void) mutex_unlock(&finfo_lock);
                return (NULL);
        }

        if ((fi->caller_addr = netbufdup(caller_addr)) == NULL) {
                (void) mutex_unlock(&finfo_lock);
                free(fi);
                return (NULL);
        }

        /*
         * Generate new xid and make sure it is unique.
         */
        do {
                lastxid++;
                /* avoid lastxid wraparound to 0 */
                if (lastxid == 0)
                        lastxid = 1;
        } while (forward_find(lastxid, uaddr) != NULL);

        fi->prev = NULL;
        fi->next = fihead;
        if (fihead != NULL)
                fihead->prev = fi;
        fihead = fi;
        if (fitail == NULL)
                fitail = fi;

        fi->flag = 0;
        fi->caller_xid = caller_xid;

        fi->forward_xid = lastxid;
        fi->forward_fd = forward_fd;

        /*
         * Though uaddr is not allocated here, it will still be freed
         * from forward_destroy().
         */
        fi->uaddr = uaddr;

        fi->reply_data = NULL;
        (void) cond_init(&fi->cv, USYNC_THREAD, NULL);

        rpcb_rmtcalls++;
        if (rpcb_rmtcalls > rpcb_rmtcalls_max) {
                assert(fitail != fi);
                (void) cond_signal(&fitail->cv);
        }

        return (fi);
}

static void
forward_destroy(struct finfo *fi)
{
        assert(MUTEX_HELD(&finfo_lock));
        assert(fi->flag & FINFO_ACTIVE);

        if (fihead == fi) {
                assert(fi->prev == NULL);
                fihead = fi->next;
        } else {
                fi->prev->next = fi->next;
        }

        if (fitail == fi) {
                assert(fi->next == NULL);
                fitail = fi->prev;
        } else {
                fi->next->prev = fi->prev;
        }

        netbuffree(fi->caller_addr);
        free(fi->uaddr);
        if (fi->reply_data != NULL)
                t_free((char *)fi->reply_data, T_UNITDATA);
        (void) cond_destroy(&fi->cv);

        free(fi);

        rpcb_rmtcalls--;
        if (rpcb_rmtcalls > rpcb_rmtcalls_max) {
                assert(fitail != NULL);
                (void) cond_signal(&fitail->cv);
        }
}

static struct finfo *
forward_find(ulong_t reply_xid, char *uaddr)
{
        struct finfo *fi;

        assert(MUTEX_HELD(&finfo_lock));

        for (fi = fihead; fi != NULL; fi = fi->next) {
                if (fi->forward_xid == reply_xid &&
                    strcmp(fi->uaddr, uaddr) == 0)
                        return (fi);
        }

        return (NULL);
}

static int
netbufcmp(struct netbuf *n1, struct netbuf *n2)
{
        return ((n1->len != n2->len) || memcmp(n1->buf, n2->buf, n1->len));
}

static struct netbuf *
netbufdup(struct netbuf *ap)
{
        struct netbuf *np;

        np = malloc(sizeof (struct netbuf) + ap->len);
        if (np) {
                np->maxlen = np->len = ap->len;
                np->buf = ((char *)np) + sizeof (struct netbuf);
                (void) memcpy(np->buf, ap->buf, ap->len);
        }
        return (np);
}

static void
netbuffree(struct netbuf *ap)
{
        free(ap);
}

static void
handle_reply(svc_input_id_t id, int fd, unsigned int events, void *cookie)
{
        struct t_unitdata *tr_data;
        int res;

        unsigned int inlen;
        char *buffer;
        XDR reply_xdrs;

        struct rpc_msg reply_msg;
        unsigned int pos;
        unsigned int len;

        struct netconfig *nconf;
        char *uaddr = NULL;

        struct finfo *fi;

        tr_data = (struct t_unitdata *)t_alloc(fd, T_UNITDATA,
            T_ADDR | T_UDATA);
        if (tr_data == NULL) {
                syslog(LOG_ERR, "handle_reply: t_alloc failed!");
                return;
        }

        do {
                int moreflag = 0;

                if (errno == EINTR)
                        errno = 0;
                res = t_rcvudata(fd, tr_data, &moreflag);
                if (moreflag & T_MORE) {
                        /* Drop this packet - we have no more space. */
                        if (debugging)
                                fprintf(stderr, "handle_reply:  recvd packet "
                                    "with T_MORE flag set\n");
                        goto done;
                }
        } while (res < 0 && t_errno == TSYSERR && errno == EINTR);

        if (res < 0) {
                if (debugging)
                        fprintf(stderr, "handle_reply:  t_rcvudata returned "
                            "%d, t_errno %d, errno %d\n", res, t_errno, errno);

                if (t_errno == TLOOK)
                        (void) t_rcvuderr(fd, NULL);

                goto done;
        }

        inlen = tr_data->udata.len;
        buffer = tr_data->udata.buf;
        assert(buffer != NULL);
        xdrmem_create(&reply_xdrs, buffer, inlen, XDR_DECODE);

        reply_msg.acpted_rply.ar_verf = _null_auth;
        reply_msg.acpted_rply.ar_results.where = 0;
        reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void;

        if (!xdr_replymsg(&reply_xdrs, &reply_msg)) {
                xdr_destroy(&reply_xdrs);
                if (debugging)
                        (void) fprintf(stderr,
                            "handle_reply:  xdr_replymsg failed\n");
                goto done;
        }
        pos = XDR_GETPOS(&reply_xdrs);
        xdr_destroy(&reply_xdrs);

        len = inlen - pos;

        nconf = rpcbind_get_conf((char *)cookie);
        if (nconf == NULL) {
                syslog(LOG_ERR, "handle_reply: rpcbind_get_conf failed!");
                goto done;
        }
        uaddr = taddr2uaddr(nconf, &tr_data->addr);
        if (uaddr == NULL) {
                syslog(LOG_ERR, "handle_reply: taddr2uaddr failed!");
                goto done;
        }

        (void) mutex_lock(&finfo_lock);
        fi = forward_find(reply_msg.rm_xid, uaddr);
        if (fi == NULL) {
                (void) mutex_unlock(&finfo_lock);
                goto done;
        }

        fi->reply_data = tr_data;
        tr_data = NULL;

        __seterr_reply(&reply_msg, &fi->reply_error);

        fi->res_len = len;
        fi->res_val = &buffer[pos];

        (void) cond_signal(&fi->cv);
        (void) mutex_unlock(&finfo_lock);

done:
        free(uaddr);
        if (tr_data)
                t_free((char *)tr_data, T_UNITDATA);
}

/*
 * prog: Program Number
 * netid: Transport Provider token
 * lowvp: Low version number
 * highvp: High version number
 */
static void
find_versions(rpcprog_t prog, char *netid, rpcvers_t *lowvp, rpcvers_t *highvp)
{
        rpcblist_ptr rbl;
        rpcvers_t lowv = 0;
        rpcvers_t highv = 0;

        assert(RW_LOCK_HELD(&list_rbl_lock));

        for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) {
                if ((rbl->rpcb_map.r_prog != prog) ||
                    (strcasecmp(rbl->rpcb_map.r_netid, netid) != 0))
                        continue;
                if (lowv == 0) {
                        highv = rbl->rpcb_map.r_vers;
                        lowv = highv;
                } else if (rbl->rpcb_map.r_vers < lowv) {
                        lowv = rbl->rpcb_map.r_vers;
                } else if (rbl->rpcb_map.r_vers > highv) {
                        highv = rbl->rpcb_map.r_vers;
                }
        }

        *lowvp = lowv;
        *highvp = highv;
}

/*
 * returns the item with the given program, version number and netid.
 * If that version number is not found, it returns the item with that
 * program number, so that address is now returned to the caller. The
 * caller when makes a call to this program, version number, the call
 * will fail and it will return with PROGVERS_MISMATCH. The user can
 * then determine the highest and the lowest version number for this
 * program using clnt_geterr() and use those program version numbers.
 *
 * Returns the RPCBLIST for the given prog, vers and netid
 *
 * prog: Program Number
 * vers: Version Number
 * netid: Transport Provider token
 */
static rpcblist_ptr
find_service(rpcprog_t prog, rpcvers_t vers, char *netid)
{
        rpcblist_ptr hit = NULL;
        rpcblist_ptr rbl;

        assert(RW_LOCK_HELD(&list_rbl_lock));

        for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) {
                if ((rbl->rpcb_map.r_prog != prog) ||
                    (strcasecmp(rbl->rpcb_map.r_netid, netid) != 0))
                        continue;
                hit = rbl;
                if (rbl->rpcb_map.r_vers == vers)
                        break;
        }

        return (hit);
}

/*
 * If the caller is from our zone and we know
 * who it is, we return the uid.
 */
uid_t
rpcb_caller_uid(SVCXPRT *transp)
{
        ucred_t *uc = alloca(ucred_size());

        if (svc_getcallerucred(transp, &uc) != 0 ||
            (ucred_getzoneid(uc)) != myzone) {
                return (-1);
        } else {
                return (ucred_geteuid(uc));
        }
}

/*
 * Copies the name associated with the uid of the caller and returns
 * a pointer to it.  Similar to getwd().
 */
char *
getowner(SVCXPRT *transp, char *owner)
{
        uid_t uid = rpcb_caller_uid(transp);

        switch (uid) {
        case -1:
                return (strcpy(owner, "unknown"));
        case 0:
                return (strcpy(owner, "superuser"));
        default:
                (void) sprintf(owner, "%u", uid);
                return (owner);
        }
}

#ifdef PORTMAP
/*
 * Add this to the pmap list only if it is UDP or TCP.
 */
static int
add_pmaplist(RPCB *arg)
{
        pmap pmap;
        pmaplist *pml;
        int h1, h2, h3, h4, p1, p2;

        if (strcmp(arg->r_netid, udptrans) == 0) {
                /* It is UDP! */
                pmap.pm_prot = IPPROTO_UDP;
        } else if (strcmp(arg->r_netid, tcptrans) == 0) {
                /* It is TCP */
                pmap.pm_prot = IPPROTO_TCP;
        } else
                /* Not a IP protocol */
                return (0);

        /* interpret the universal address for TCP/IP */
        if (sscanf(arg->r_addr, "%d.%d.%d.%d.%d.%d",
            &h1, &h2, &h3, &h4, &p1, &p2) != 6)
                return (0);
        pmap.pm_port = ((p1 & 0xff) << 8) + (p2 & 0xff);
        pmap.pm_prog = arg->r_prog;
        pmap.pm_vers = arg->r_vers;
        /*
         * add to END of list
         */
        pml = (pmaplist *) malloc((uint_t)sizeof (pmaplist));
        if (pml == NULL) {
                (void) syslog(LOG_ERR, "rpcbind: no memory!\n");
                return (1);
        }
        pml->pml_map = pmap;
        pml->pml_next = NULL;

        (void) rw_wrlock(&list_pml_lock);
        if (list_pml == NULL) {
                list_pml = pml;
        } else {
                pmaplist *fnd;

                /* Attach to the end of the list */
                for (fnd = list_pml; fnd->pml_next; fnd = fnd->pml_next)
                        ;
                fnd->pml_next = pml;
        }
        (void) rw_unlock(&list_pml_lock);

        return (0);
}

/*
 * Delete this from the pmap list only if it is UDP or TCP.
 */
int
del_pmaplist(RPCB *arg)
{
        pmaplist *pml;
        pmaplist *prevpml, *fnd;
        rpcport_t prot;

        if (strcmp(arg->r_netid, udptrans) == 0) {
                /* It is UDP! */
                prot = IPPROTO_UDP;
        } else if (strcmp(arg->r_netid, tcptrans) == 0) {
                /* It is TCP */
                prot = IPPROTO_TCP;
        } else if (arg->r_netid[0] == '\0') {
                prot = 0;       /* Remove all occurrences */
        } else {
                /* Not a IP protocol */
                return (0);
        }

        assert(RW_WRITE_HELD(&list_pml_lock));

        for (prevpml = NULL, pml = list_pml; pml; /* cstyle */) {
                if ((pml->pml_map.pm_prog != arg->r_prog) ||
                    (pml->pml_map.pm_vers != arg->r_vers) ||
                    (prot && (pml->pml_map.pm_prot != prot))) {
                        /* both pml & prevpml move forwards */
                        prevpml = pml;
                        pml = pml->pml_next;
                        continue;
                }
                /* found it; pml moves forward, prevpml stays */
                fnd = pml;
                pml = pml->pml_next;
                if (prevpml == NULL)
                        list_pml = pml;
                else
                        prevpml->pml_next = pml;
                free(fnd);
        }

        return (0);
}
#endif /* PORTMAP */