root/usr.bin/rpcinfo/rpcinfo.c
/*      $NetBSD: rpcinfo.c,v 1.38 2023/12/11 13:56:47 mlelstv Exp $     */

/*
 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.  Users
 * may copy or modify Sun RPC without charge, but are not authorized
 * to license or distribute it to anyone else except as part of a product or
 * program developed by the user.
 * 
 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 * 
 * Sun RPC is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 * 
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
 * OR ANY PART THEREOF.
 * 
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even if
 * Sun has been advised of the possibility of such damages.
 * 
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */

/*
 * Copyright (c) 1986 - 1991 by Sun Microsystems, Inc.
 */

/* #ident       "@(#)rpcinfo.c  1.18    93/07/05 SMI" */

#if 0
#ifndef lint
static char sccsid[] = "@(#)rpcinfo.c 1.16 89/04/05 Copyr 1986 Sun Micro";
#endif
#endif

/*
 * rpcinfo: ping a particular rpc program
 *      or dump the registered programs on the remote machine.
 */

/*
 * We are for now defining PORTMAP here.  It doesnt even compile
 * unless it is defined.
 */
#ifndef PORTMAP
#define PORTMAP
#endif

/*
 * If PORTMAP is defined, rpcinfo will talk to both portmapper and
 * rpcbind programs; else it talks only to rpcbind. In the latter case
 * all the portmapper specific options such as -u, -t, -p become void.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/un.h>
#include <rpc/rpc.h>
#include <stdio.h>
#include <rpc/rpcb_prot.h>
#include <rpc/rpcent.h>
#include <rpc/nettype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <ctype.h>
#include <errno.h>

#ifdef PORTMAP          /* Support for version 2 portmapper */
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <rpc/pmap_prot.h>
#include <rpc/pmap_clnt.h>
#endif

#define MIN_VERS        ((rpcvers_t)0)
#define MAX_VERS        ((rpcvers_t)4294967295UL)
#define PMAP_PROG       ((rpcprog_t)PMAPPROG)
#define PMAP_VERS       ((rpcvers_t)PMAPVERS)
#define RPCB_VERS       ((rpcvers_t)RPCBVERS)
#define RPCB_VERS4      ((rpcvers_t)RPCB_VERS)
#define UL(a)           ((unsigned long)a)
static char unknown[] = "unknown";

/*
 * Functions to be performed.
 */
#define NONE            0       /* no function */
#define PMAPDUMP        1       /* dump portmapper registrations */
#define TCPPING         2       /* ping TCP service */
#define UDPPING         3       /* ping UDP service */
#define BROADCAST       4       /* ping broadcast service */
#define DELETES         5       /* delete registration for the service */
#define ADDRPING        6       /* pings at the given address */
#define PROGPING        7       /* pings a program on a given host */
#define RPCBDUMP        8       /* dump rpcbind registrations */
#define RPCBDUMP_SHORT  9       /* dump rpcbind registrations - short version */
#define RPCBADDRLIST    10      /* dump addr list about one prog */
#define RPCBGETSTAT     11      /* Get statistics */

struct netidlist {
        char *netid;
        struct netidlist *next;
};

struct verslist {
        rpcvers_t vers;
        struct verslist *next;
};

struct rpcbdump_short {
        rpcprog_t prog;
        struct verslist *vlist;
        struct netidlist *nlist;
        struct rpcbdump_short *next;
        char *owner;
};



#ifdef PORTMAP
static void     ip_ping(u_short, const char *, int, char **);
static CLIENT   *clnt_com_create(struct sockaddr_in *, rpcprog_t, rpcvers_t,
    int *, const char *);
static void     pmapdump(int, char **);
static void     get_inet_address(struct sockaddr_in *, const char *);
#endif

static bool_t   reply_proc(void *, struct netbuf *, struct netconfig *);
__dead static void      brdcst(int, char **);
static void     addrping(const char *, const char *, int, char **);
static void     progping(const char *, int, char **);
static CLIENT   *clnt_addr_create(const char *, const struct netconfig *,
    rpcprog_t, rpcvers_t);
static CLIENT   *clnt_rpcbind_create(const char *, rpcvers_t, struct netbuf **);
static CLIENT   *getclnthandle(const char *, const struct netconfig *,
    rpcvers_t, struct netbuf **);
static CLIENT   *local_rpcb(rpcprog_t, rpcvers_t);
static int      pstatus(CLIENT *, rpcprog_t, rpcvers_t);
static void     rpcbdump(int, const char *, int, char **);
static void     rpcbgetstat(int, char **);
static void     rpcbaddrlist(const char *, int, char **);
static void     deletereg(const char *, int, char **);
static void     print_rmtcallstat(int, const rpcb_stat *);
static void     print_getaddrstat(int, const rpcb_stat *);
static void     usage(void) __dead;
static rpcprog_t        getprognum(const char *);
static rpcvers_t        getvers(const char *);
static const char *spaces(size_t);
static bool_t   add_version(struct rpcbdump_short *, rpcvers_t);
static bool_t   add_netid(struct rpcbdump_short *, char *);

int
main(int argc, char **argv)
{
        int c;
        int errflg;
        int function;
        char *netid = NULL;
        char *address = NULL;
#ifdef PORTMAP
        char *strptr;
        u_short portnum = 0;
#endif

        function = NONE;
        errflg = 0;
#ifdef PORTMAP
        while ((c = getopt(argc, argv, "a:bdlmn:pstT:u")) != -1) {
#else
        while ((c = getopt(argc, argv, "a:bdlmn:sT:")) != -1) {
#endif
                switch (c) {
#ifdef PORTMAP
                case 'p':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = PMAPDUMP;
                        break;

                case 't':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = TCPPING;
                        break;

                case 'u':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = UDPPING;
                        break;

                case 'n':
                        portnum = (u_short) strtol(optarg, &strptr, 10);
                        if (strptr == optarg || *strptr != '\0')
                                errx(1, "Illegal port number `%s'", optarg);
                        break;
#endif
                case 'a':
                        address = optarg;
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = ADDRPING;
                        break;
                case 'b':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = BROADCAST;
                        break;

                case 'd':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = DELETES;
                        break;

                case 'l':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = RPCBADDRLIST;
                        break;

                case 'm':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = RPCBGETSTAT;
                        break;

                case 's':
                        if (function != NONE)
                                errflg = 1;
                        else
                                function = RPCBDUMP_SHORT;
                        break;

                case 'T':
                        netid = optarg;
                        break;
                case '?':
                        errflg = 1;
                        break;
                }
        }

        if (errflg || ((function == ADDRPING) && !netid))
                usage();

        if (function == NONE) {
                if (argc - optind > 1)
                        function = PROGPING;
                else
                        function = RPCBDUMP;
        }

        switch (function) {
#ifdef PORTMAP
        case PMAPDUMP:
                if (portnum != 0)
                        usage();
                pmapdump(argc - optind, argv + optind);
                break;

        case UDPPING:
                ip_ping(portnum, "udp", argc - optind, argv + optind);
                break;

        case TCPPING:
                ip_ping(portnum, "tcp", argc - optind, argv + optind);
                break;
#endif
        case BROADCAST:
                brdcst(argc - optind, argv + optind);
                break;
        case DELETES:
                deletereg(netid, argc - optind, argv + optind);
                break;
        case ADDRPING:
                addrping(address, netid, argc - optind, argv + optind);
                break;
        case PROGPING:
                progping(netid, argc - optind, argv + optind);
                break;
        case RPCBDUMP:
        case RPCBDUMP_SHORT:
                rpcbdump(function, netid, argc - optind, argv + optind);
                break;
        case RPCBGETSTAT:
                rpcbgetstat(argc - optind, argv + optind);
                break;
        case RPCBADDRLIST:
                rpcbaddrlist(netid, argc - optind, argv + optind);
                break;
        }
        return (0);
}

static CLIENT *
local_rpcb(rpcprog_t prog, rpcvers_t vers)
{
        struct netbuf nbuf;
        struct sockaddr_un sun;
        int sock;

        (void)memset(&sun, 0, sizeof sun);
        sock = socket(AF_LOCAL, SOCK_STREAM, 0);
        if (sock < 0)
                return NULL;

        sun.sun_family = AF_LOCAL;
        (void)strlcpy(sun.sun_path, _PATH_RPCBINDSOCK, sizeof(sun.sun_path));
        nbuf.len = sun.sun_len = SUN_LEN(&sun);
        nbuf.maxlen = sizeof (struct sockaddr_un);
        nbuf.buf = &sun;

        return clnt_vc_create(sock, &nbuf, prog, vers, 0, 0);
}

#ifdef PORTMAP
static CLIENT *
clnt_com_create(struct sockaddr_in *addr, rpcprog_t prog, rpcvers_t vers,
    int *fdp, const char *trans)
{
        CLIENT *clnt;

        if (strcmp(trans, "tcp") == 0) {
                clnt = clnttcp_create(addr, UL(prog), UL(vers), fdp, 0, 0);
        } else {
                struct timeval to;

                to.tv_sec = 5;
                to.tv_usec = 0;
                clnt = clntudp_create(addr, UL(prog), UL(vers), to, fdp);
        }
        if (clnt == NULL) {
                char *m = clnt_spcreateerror("") + 2;
                if (vers == MIN_VERS)
                        errx(1, "Program %lu is not available (%s)",
                            (unsigned long)prog, m);
                else
                        errx(1, "Program %lu version %lu is not available (%s)",
                            (unsigned long)prog, (unsigned long)vers, m);
        }
        return clnt;
}

/*
 * If portnum is 0, then go and get the address from portmapper, which happens
 * transparently through clnt*_create(); If version number is not given, it
 * tries to find out the version number by making a call to version 0 and if
 * that fails, it obtains the high order and the low order version number. If
 * version 0 calls succeeds, it tries for MAXVERS call and repeats the same.
 */
static void
ip_ping(u_short portnum, const char *trans, int argc, char **argv)
{
        CLIENT *client;
        int fd = RPC_ANYFD;
        struct timeval to;
        struct sockaddr_in addr;
        enum clnt_stat rpc_stat;
        rpcprog_t prognum, vers, minvers, maxvers;
        struct rpc_err rpcerr;
        int failure = 0;

        if (argc < 2 || argc > 3)
                usage();
        to.tv_sec = 10;
        to.tv_usec = 0;
        prognum = getprognum(argv[1]);
        get_inet_address(&addr, argv[0]);
        if (argc == 2) {        /* Version number not known */
                /*
                 * A call to version 0 should fail with a program/version
                 * mismatch, and give us the range of versions supported.
                 */
                vers = MIN_VERS;
        } else {
                vers = getvers(argv[2]);
        }
        addr.sin_port = htons(portnum);
        client = clnt_com_create(&addr, prognum, vers, &fd, trans);
        rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
            NULL, (xdrproc_t)xdr_void, NULL, to);
        if (argc != 2) {
                /* Version number was known */
                if (pstatus(client, prognum, vers) < 0)
                        exit(1);
                (void)CLNT_DESTROY(client);
                return;
        }
        /* Version number not known */
        (void)CLNT_CONTROL(client, CLSET_FD_NCLOSE, NULL);
        if (rpc_stat == RPC_PROGVERSMISMATCH) {
                clnt_geterr(client, &rpcerr);
                minvers = rpcerr.re_vers.low;
                maxvers = rpcerr.re_vers.high;
        } else if (rpc_stat == RPC_SUCCESS) {
                /*
                 * Oh dear, it DOES support version 0.
                 * Let's try version MAX_VERS.
                 */
                (void)CLNT_DESTROY(client);
                addr.sin_port = htons(portnum);
                client = clnt_com_create(&addr, (unsigned int)prognum,
                    MAX_VERS, &fd, trans);
                rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
                    NULL, (xdrproc_t)xdr_void, NULL, to);
                if (rpc_stat == RPC_PROGVERSMISMATCH) {
                        clnt_geterr(client, &rpcerr);
                        minvers = rpcerr.re_vers.low;
                        maxvers = rpcerr.re_vers.high;
                } else if (rpc_stat == RPC_SUCCESS) {
                        /*
                         * It also supports version MAX_VERS.
                         * Looks like we have a wise guy.
                         * OK, we give them information on all
                         * 4 billion versions they support...
                         */
                        minvers = 0;
                        maxvers = MAX_VERS;
                } else {
                        (void)pstatus(client, prognum, MAX_VERS);
                        exit(1);
                }
        } else {
                (void)pstatus(client, prognum, MIN_VERS);
                exit(1);
        }
        (void)CLNT_DESTROY(client);
        for (vers = minvers; vers <= maxvers; vers++) {
                addr.sin_port = htons(portnum);
                client = clnt_com_create(&addr, prognum, vers, &fd, trans);
                rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
                    NULL, (xdrproc_t)xdr_void, NULL, to);
                if (pstatus(client, prognum, vers) < 0)
                    failure = 1;
                (void)CLNT_DESTROY(client);
        }
        if (failure)
                exit(1);
        (void)close(fd);
        return;
}

/*
 * Dump all the portmapper registerations
 */
static void
pmapdump(int argc, char **argv)
{
        struct sockaddr_in server_addr;
        struct pmaplist *head = NULL;
        int sock = RPC_ANYSOCK;
        struct timeval minutetimeout;
        CLIENT *client;
        struct rpcent *rpc;
        enum clnt_stat clnt_st;
        struct rpc_err error;
        char *host = NULL;

        if (argc > 1)
                usage();
        if (argc == 1) {
                host = argv[0];
                get_inet_address(&server_addr, host);
                server_addr.sin_port = htons(PMAPPORT);
                client = clnttcp_create(&server_addr, PMAPPROG, PMAPVERS,
                    &sock, 50, 500);
        } else
                client = local_rpcb(PMAP_PROG, PMAP_VERS);

        if (client == NULL) {
                if (rpc_createerr.cf_stat == RPC_TLIERROR) {
                        /*
                         * "Misc. TLI error" is not too helpful. Most likely
                         * the connection to the remote server timed out, so
                         * this error is at least less perplexing.
                         */
                        rpc_createerr.cf_stat = RPC_PMAPFAILURE;
                        rpc_createerr.cf_error.re_status = RPC_FAILED;
                }
                errx(1, "Can't contact portmapper (%s)",
                    clnt_spcreateerror("") + 2);
        }

        minutetimeout.tv_sec = 60;
        minutetimeout.tv_usec = 0;

        clnt_st = CLNT_CALL(client, (unsigned int)PMAPPROC_DUMP,
            (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_pmaplist_ptr,
            (char *)(void *)&head, minutetimeout);
        if (clnt_st != RPC_SUCCESS) {
                if ((clnt_st == RPC_PROGVERSMISMATCH) ||
                    (clnt_st == RPC_PROGUNAVAIL)) {
                        CLNT_GETERR(client, &error);
                        if (error.re_vers.low > PMAPVERS) {
                                if (host)
                                        warnx(
        "%s does not support portmapper. Try 'rpcinfo %s' instead",
                                            host, host);
                                else
                                        warnx(
        "local host does not support portmapper.  Try 'rpcinfo' instead\n");
                        }
                        exit(1);
                }
                errx(1, "Can't contact portmapper (%s)",
                    clnt_sperror(client, "") + 2);
                exit(1);
        }
        if (head == NULL) {
                (void)printf("No remote programs registered.\n");
        } else {
                (void)printf("   program vers proto   port  service\n");
                for (; head != NULL; head = head->pml_next) {
                        (void)printf("%10ld%5ld",
                                head->pml_map.pm_prog,
                                head->pml_map.pm_vers);
                        if (head->pml_map.pm_prot == IPPROTO_UDP)
                                (void)printf("%6s", "udp");
                        else if (head->pml_map.pm_prot == IPPROTO_TCP)
                                (void)printf("%6s", "tcp");
                        else if (head->pml_map.pm_prot == 0)
                                (void)printf("%6s", "local");
                        else
                                (void)printf("%6ld", head->pml_map.pm_prot);
                        (void)printf("%7ld", head->pml_map.pm_port);
                        rpc = getrpcbynumber((int)head->pml_map.pm_prog);
                        if (rpc)
                                (void)printf("  %s\n", rpc->r_name);
                        else
                                (void)printf("\n");
                }
        }
}

static void
get_inet_address(struct sockaddr_in *addr, const char *host)
{
        struct netconfig *nconf;
        struct addrinfo hints, *res;
        int error;

        (void)memset(addr, 0, sizeof (*addr));
        addr->sin_addr.s_addr = inet_addr(host);
        if (addr->sin_addr.s_addr == (in_addr_t)-1 ||
            addr->sin_addr.s_addr == 0) {
                if ((nconf = __rpc_getconfip("udp")) == NULL &&
                    (nconf = __rpc_getconfip("tcp")) == NULL) {
                        errx(1, "Couldn't find a suitable transport");
                } else {
                        (void)memset(&hints, 0, sizeof hints);
                        hints.ai_family = AF_INET;
                        if ((error = getaddrinfo(host, "sunrpc", &hints, &res))
                            != 0) {
                                errx(1, "%s: %s", host, gai_strerror(error));
                        } else {
                                (void)memcpy(addr, res->ai_addr,
                                    res->ai_addrlen);
                                freeaddrinfo(res);
                        }
                        (void)freenetconfigent(nconf);
                }
        } else {
                addr->sin_family = AF_INET;
        }
}
#endif /* PORTMAP */

/*
 * reply_proc collects replies from the broadcast.
 * to get a unique list of responses the output of rpcinfo should
 * be piped through sort(1) and then uniq(1).
 */

/*ARGSUSED*/
static bool_t
reply_proc(
        void *res,              /* Nothing comes back */
        struct netbuf *who,     /* Who sent us the reply */
        struct netconfig *nconf /* On which transport the reply came */
)
{
        const char *uaddr;
        char *uf;
        char hostbuf[NI_MAXHOST];
        const char *hostname;
        struct sockaddr *sa = (struct sockaddr *)who->buf;

        if (getnameinfo(sa, (socklen_t)sa->sa_len, hostbuf, NI_MAXHOST, NULL,
            0, 0)) {
                hostname = unknown;
        } else {
                hostname = hostbuf;
        }
        if (!(uaddr = uf = taddr2uaddr(nconf, who))) {
                uaddr = unknown;
        }
        (void)printf("%s\t%s\n", uaddr, hostname);
        if (uf)
                free(uf);
        return FALSE;
}

static void
brdcst(int argc, char **argv)
{
        enum clnt_stat rpc_stat;
        rpcprog_t prognum;
        rpcvers_t vers;

        if (argc != 2)
                usage();
        prognum = getprognum(argv[0]);
        vers = getvers(argv[1]);
        rpc_stat = rpc_broadcast(prognum, vers, NULLPROC,
            (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void,
            NULL, (resultproc_t) reply_proc, NULL);
        if ((rpc_stat != RPC_SUCCESS) && (rpc_stat != RPC_TIMEDOUT))
                errx(1, "broadcast failed: %s", clnt_sperrno(rpc_stat));
        exit(0);
}

static bool_t
add_version(struct rpcbdump_short *rs, rpcvers_t vers)
{
        struct verslist *vl;

        for (vl = rs->vlist; vl; vl = vl->next)
                if (vl->vers == vers)
                        break;
        if (vl)
                return TRUE;
        vl = malloc(sizeof (struct verslist));
        if (vl == NULL)
                return FALSE;
        vl->vers = vers;
        vl->next = rs->vlist;
        rs->vlist = vl;
        return TRUE;
}

static bool_t
add_netid(struct rpcbdump_short *rs, char *netid)
{
        struct netidlist *nl;

        for (nl = rs->nlist; nl; nl = nl->next)
                if (strcmp(nl->netid, netid) == 0)
                        break;
        if (nl)
                return TRUE;
        nl = malloc(sizeof(*nl));
        if (nl == NULL)
                return FALSE;
        nl->netid = netid;
        nl->next = rs->nlist;
        rs->nlist = nl;
        return TRUE;
}

static void
rpcbdump(int dumptype, const char *netid, int argc, char **argv)
{
        rpcblist_ptr head = NULL, p;
        struct timeval minutetimeout;
        CLIENT *client = NULL;
        struct rpcent *rpc;
        char *host;
        struct netidlist *nl;
        struct verslist *vl;
        struct rpcbdump_short *rs, *rs_tail = NULL;
        char buf[256];
        enum clnt_stat clnt_st;
        struct rpc_err error;
        struct rpcbdump_short *rs_head = NULL;

        if (argc > 1)
                usage();
        if (argc == 1) {
                host = argv[0];
                if (netid == NULL) {
                        client = clnt_rpcbind_create(host, RPCBVERS, NULL);
                } else {
                        struct netconfig *nconf;
        
                        nconf = getnetconfigent(netid);
                        if (nconf == NULL)
                                errx(1, "Invalid transport (%s)",
                                    nc_sperror());
                        client = getclnthandle(host, nconf, RPCBVERS, NULL);
                        if (nconf)
                                (void)freenetconfigent(nconf);
                }
        } else
                client = local_rpcb(PMAP_PROG, RPCB_VERS);

        if (client == NULL)
                errx(1, "Can't contact rpcbind (%s)",
                    clnt_spcreateerror(""));
        minutetimeout.tv_sec = 60;
        minutetimeout.tv_usec = 0;
        clnt_st = CLNT_CALL(client, RPCBPROC_DUMP, (xdrproc_t)xdr_void,
                NULL, (xdrproc_t)xdr_rpcblist_ptr, (char *)(void *)&head,
                minutetimeout);
        if (clnt_st != RPC_SUCCESS) {
            if ((clnt_st == RPC_PROGVERSMISMATCH) ||
                (clnt_st == RPC_PROGUNAVAIL)) {
                int vers;

                CLNT_GETERR(client, &error);
                if (error.re_vers.low == RPCBVERS4) {
                    vers = RPCBVERS4;
                    clnt_control(client, CLSET_VERS, (char *)(void *)&vers);
                    clnt_st = CLNT_CALL(client, RPCBPROC_DUMP,
                        (xdrproc_t)xdr_void, NULL,
                        (xdrproc_t)xdr_rpcblist_ptr, (char *)(void *)&head,
                        minutetimeout);
                    if (clnt_st != RPC_SUCCESS)
                        goto failed;
                } else {
                    if (error.re_vers.high == PMAPVERS) {
                        int high, low;
                        struct pmaplist *pmaphead = NULL;
                        rpcblist_ptr list, prev = NULL;

                        vers = PMAPVERS;
                        clnt_control(client, CLSET_VERS, (char *)(void *)&vers);
                        clnt_st = CLNT_CALL(client, (unsigned int)PMAPPROC_DUMP,
                                (xdrproc_t)xdr_void, NULL,
                                (xdrproc_t)xdr_pmaplist_ptr,
                                (char *)(void *)&pmaphead, minutetimeout);
                        if (clnt_st != RPC_SUCCESS)
                                goto failed;
                        /*
                         * convert to rpcblist_ptr format
                         */
                        for (head = NULL; pmaphead != NULL;
                                pmaphead = pmaphead->pml_next) {
                            list = malloc(sizeof (rpcblist));
                            if (list == NULL)
                                goto error;
                            if (head == NULL)
                                head = list;
                            else
                                prev->rpcb_next = (rpcblist_ptr) list;

                            list->rpcb_next = NULL;
                            list->rpcb_map.r_prog = pmaphead->pml_map.pm_prog;
                            list->rpcb_map.r_vers = pmaphead->pml_map.pm_vers;
                            if (pmaphead->pml_map.pm_prot == IPPROTO_UDP)
                                list->rpcb_map.r_netid = strdup("udp");
                            else if (pmaphead->pml_map.pm_prot == IPPROTO_TCP)
                                list->rpcb_map.r_netid = strdup("tcp");
                            else {
                                (void)asprintf(&list->rpcb_map.r_netid, "%6ld",
                                    pmaphead->pml_map.pm_prot);
                                if (list->rpcb_map.r_netid == NULL)
                                        goto error;
                            }
                            list->rpcb_map.r_owner = unknown;
                            low = pmaphead->pml_map.pm_port & 0xff;
                            high = (pmaphead->pml_map.pm_port >> 8) & 0xff;
                            (void)asprintf(&list->rpcb_map.r_addr,
                                "0.0.0.0.%d.%d", high, low);
                            if (list->rpcb_map.r_addr == NULL)
                                    goto error;
                            prev = list;
                        }
                    }
                }
            } else {    /* any other error */
failed:
                    errx(1, "Can't contact rpcbind (%s)",
                        clnt_sperror(client, "") + 2);
            }
        }
        if (head == NULL) {
                (void)printf("No remote programs registered.\n");
        } else if (dumptype == RPCBDUMP) {
                (void)printf(
"   program version netid     address                service    owner\n");
                for (p = head; p != NULL; p = p->rpcb_next) {
                        (void)printf("%10u%5u    ",
                            p->rpcb_map.r_prog, p->rpcb_map.r_vers);
                        (void)printf("%-9s ", p->rpcb_map.r_netid);
                        (void)printf("%-22s", p->rpcb_map.r_addr);
                        rpc = getrpcbynumber((int)p->rpcb_map.r_prog);
                        if (rpc)
                                (void)printf(" %-10s", rpc->r_name);
                        else
                                (void)printf(" %-10s", "-");
                        (void)printf(" %s\n", p->rpcb_map.r_owner);
                }
        } else if (dumptype == RPCBDUMP_SHORT) {
                for (p = head; p != NULL; p = p->rpcb_next) {
                        for (rs = rs_head; rs; rs = rs->next)
                                if (p->rpcb_map.r_prog == rs->prog)
                                        break;
                        if (rs == NULL) {
                                rs = malloc(sizeof(*rs));
                                if (rs == NULL)
                                        goto error;
                                rs->next = NULL;
                                if (rs_head == NULL) {
                                        rs_head = rs;
                                        rs_tail = rs;
                                } else {
                                        rs_tail->next = rs;
                                        rs_tail = rs;
                                }
                                rs->prog = p->rpcb_map.r_prog;
                                rs->owner = p->rpcb_map.r_owner;
                                rs->nlist = NULL;
                                rs->vlist = NULL;
                        }
                        if (add_version(rs, p->rpcb_map.r_vers) == FALSE)
                                goto error;
                        if (add_netid(rs, p->rpcb_map.r_netid) == FALSE)
                                goto error;
                }
                (void)printf(
"   program version(s) netid(s)                         service     owner\n");
                for (rs = rs_head; rs; rs = rs->next) {
                        char *q = buf;

                        (void)printf("%10lu  ", (unsigned long)rs->prog);
                        for (vl = rs->vlist; vl; vl = vl->next) {
                                (void)snprintf(q, sizeof(buf) - (q - buf),
                                    "%lu", (unsigned long)vl->vers);
                                q = q + strlen(q);
                                if (vl->next) {
                                        (void)snprintf(q,
                                            sizeof(buf) - (q - buf), ",");
                                        q++;
                                }
                        }
                        (void)printf("%-10s", buf);
                        buf[0] = 0;
                        for (nl = rs->nlist; nl; nl = nl->next) {
                                (void)strlcat(buf, nl->netid, sizeof(buf));
                                if (nl->next)
                                        (void)strlcat(buf, ",", sizeof(buf));
                        }
                        (void)printf("%-32s", buf);
                        rpc = getrpcbynumber((int)rs->prog);
                        if (rpc)
                                (void)printf(" %-11s", rpc->r_name);
                        else
                                (void)printf(" %-11s", "-");
                        (void)printf(" %s\n", rs->owner);
                }
        }
        if (client)
                clnt_destroy(client);
        while (head != NULL) {
                rpcblist_ptr list = head->rpcb_next;
                if (head->rpcb_map.r_addr)
                        free(head->rpcb_map.r_addr);
                if (head->rpcb_map.r_netid)
                        free(head->rpcb_map.r_netid);
                free(head);
                head = list;
        }
        while (rs_head) {
                rs = rs_head;
                rs_head = rs_head->next;
                free(rs);
        }
        return;
error:  err(1, "Cannot allocate memory");
}

static char nullstring[] = "\000";

static void
rpcbaddrlist(const char *netid, int argc, char **argv)
{
        rpcb_entry_list_ptr head = NULL;
        struct timeval minutetimeout;
        CLIENT *client;
        struct rpcent *rpc;
        char *host;
        RPCB parms;
        struct netbuf *targaddr;

        if (argc != 3)
                usage();
        host = argv[0];
        if (netid == NULL) {
                client = clnt_rpcbind_create(host, RPCBVERS4, &targaddr);
        } else {
                struct netconfig *nconf;

                nconf = getnetconfigent(netid);
                if (nconf == NULL)
                        errx(1, "Invalid transport (%s)", nc_sperror());
                client = getclnthandle(host, nconf, RPCBVERS4, &targaddr);
                if (nconf)
                        (void)freenetconfigent(nconf);
        }
        if (client == NULL)
                errx(1, "Can't contact rpcbind (%s)", 
                    clnt_spcreateerror("") + 2);
        minutetimeout.tv_sec = 60;
        minutetimeout.tv_usec = 0;

        parms.r_prog =  getprognum(argv[1]);
        parms.r_vers =  getvers(argv[2]);
        parms.r_netid = client->cl_netid;
        if (targaddr == NULL) {
                parms.r_addr = nullstring;      /* for XDRing */
        } else {
                /*
                 * We also send the remote system the address we
                 * used to contact it in case it can help it
                 * connect back with us
                 */
                struct netconfig *nconf;

                nconf = getnetconfigent(client->cl_netid);
                if (nconf != NULL) {
                        parms.r_addr = taddr2uaddr(nconf, targaddr);
                        if (parms.r_addr == NULL)
                                parms.r_addr = nullstring;
                        freenetconfigent(nconf);
                } else {
                        parms.r_addr = nullstring;      /* for XDRing */
                }
                free(targaddr->buf);
                free(targaddr);
        }
        parms.r_owner = nullstring;

        if (CLNT_CALL(client, RPCBPROC_GETADDRLIST, (xdrproc_t)xdr_rpcb,
                (char *)(void *)&parms, (xdrproc_t)xdr_rpcb_entry_list_ptr,
                (char *)(void *)&head, minutetimeout) != RPC_SUCCESS) {
                errx(1, "Can't contact rpcbind (%s)",
                    clnt_sperror(client, "") + 2);
        }
        if (head == NULL) {
                (void)printf("No remote programs registered.\n");
        } else {
                (void)printf(
        "   program vers  tp_family/name/class    address\t\t  service\n");
                for (; head != NULL; head = head->rpcb_entry_next) {
                        rpcb_entry *re;
                        char buf[128];

                        re = &head->rpcb_entry_map;
                        (void)printf("%10u%3u    ",
                            parms.r_prog, parms.r_vers);
                        (void)snprintf(buf, sizeof(buf), "%s/%s/%s ",
                            re->r_nc_protofmly, re->r_nc_proto,
                            re->r_nc_semantics == NC_TPI_CLTS ? "clts" :
                            re->r_nc_semantics == NC_TPI_COTS ? "cots" :
                            "cots_ord");
                        (void)printf("%-24s", buf);
                        (void)printf("%-24s", re->r_maddr);
                        rpc = getrpcbynumber((int)parms.r_prog);
                        if (rpc)
                                (void)printf(" %-13s", rpc->r_name);
                        else
                                (void)printf(" %-13s", "-");
                        (void)printf("\n");
                }
        }
        clnt_destroy(client);
        return;
}

/*
 * monitor rpcbind
 */
static void
rpcbgetstat(int argc, char **argv)
{
        rpcb_stat_byvers inf;
        struct timeval minutetimeout;
        CLIENT *client;
        char *host;
        int i, j;
        rpcbs_addrlist *pa;
        rpcbs_rmtcalllist *pr;
        size_t cnt, flen;
#define MAXFIELD        64
        char fieldbuf[MAXFIELD];
#define MAXLINE         256
        char linebuf[MAXLINE];
        char *cp, *lp;
        static const char *pmaphdr[] = {
                "NULL", "SET", "UNSET", "GETPORT",
                "DUMP", "CALLIT"
        };
        static const char *rpcb3hdr[] = {
                "NULL", "SET", "UNSET", "GETADDR", "DUMP", "CALLIT", "TIME",
                "U2T", "T2U"
        };
        static const char *rpcb4hdr[] = {
                "NULL", "SET", "UNSET", "GETADDR", "DUMP", "CALLIT", "TIME",
                "U2T",  "T2U", "VERADDR", "INDRECT", "GETLIST", "GETSTAT"
        };

#define TABSTOP 8

        if (argc >= 1) {
                host = argv[0];
                client = clnt_rpcbind_create(host, RPCBVERS4, NULL);
        } else
                client = local_rpcb(PMAP_PROG, RPCB_VERS4);
        if (client == NULL)
                errx(1, "Can't contact rpcbind (%s)",
                    clnt_spcreateerror("") + 2);
        minutetimeout.tv_sec = 60;
        minutetimeout.tv_usec = 0;
        (void)memset(&inf, 0, sizeof (rpcb_stat_byvers));
        if (CLNT_CALL(client, RPCBPROC_GETSTAT, (xdrproc_t)xdr_void, NULL,
            (xdrproc_t)xdr_rpcb_stat_byvers, (char *)(void *)&inf,
            minutetimeout) != RPC_SUCCESS) {
                errx(1, "Can't contact rpcbind (%s)",
                    clnt_sperror(client, "") + 2);
        }
        (void)printf("PORTMAP (version 2) statistics\n");
        lp = linebuf;
        for (i = 0; i <= rpcb_highproc_2; i++) {
                fieldbuf[0] = '\0';
                switch (i) {
                case PMAPPROC_SET:
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%d/",
                            inf[RPCBVERS_2_STAT].setinfo);
                        break;
                case PMAPPROC_UNSET:
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%d/",
                            inf[RPCBVERS_2_STAT].unsetinfo);
                        break;
                case PMAPPROC_GETPORT:
                        cnt = 0;
                        for (pa = inf[RPCBVERS_2_STAT].addrinfo; pa;
                            pa = pa->next)
                                cnt += pa->success;
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%zu/", cnt);
                        break;
                case PMAPPROC_CALLIT:
                        cnt = 0;
                        for (pr = inf[RPCBVERS_2_STAT].rmtinfo; pr;
                            pr = pr->next)
                                cnt += pr->success;
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%zu/", cnt);
                        break;
                default: break;  /* For the remaining ones */
                }
                cp = &fieldbuf[0] + strlen(fieldbuf);
                (void)snprintf(cp, sizeof(fieldbuf) - (cp - fieldbuf), "%d",
                    inf[RPCBVERS_2_STAT].info[i]);
                flen = strlen(fieldbuf);
                (void)printf("%s%s", pmaphdr[i],
                    spaces((TABSTOP * (1 + flen / TABSTOP))
                    - strlen(pmaphdr[i])));
                (void)snprintf(lp, sizeof(linebuf) - (lp - linebuf), "%s%s",
                    fieldbuf, spaces(cnt = ((TABSTOP * (1 + flen / TABSTOP))
                    - flen)));
                lp += (flen + cnt);
        }
        (void)printf("\n%s\n\n", linebuf);

        if (inf[RPCBVERS_2_STAT].info[PMAPPROC_CALLIT]) {
                (void)printf("PMAP_RMTCALL call statistics\n");
                print_rmtcallstat(RPCBVERS_2_STAT, &inf[RPCBVERS_2_STAT]);
                (void)printf("\n");
        }

        if (inf[RPCBVERS_2_STAT].info[PMAPPROC_GETPORT]) {
                (void)printf("PMAP_GETPORT call statistics\n");
                print_getaddrstat(RPCBVERS_2_STAT, &inf[RPCBVERS_2_STAT]);
                (void)printf("\n");
        }

        (void)printf("RPCBIND (version 3) statistics\n");
        lp = linebuf;
        for (i = 0; i <= rpcb_highproc_3; i++) {
                fieldbuf[0] = '\0';
                switch (i) {
                case RPCBPROC_SET:
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%d/",
                            inf[RPCBVERS_3_STAT].setinfo);
                        break;
                case RPCBPROC_UNSET:
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%d/",
                            inf[RPCBVERS_3_STAT].unsetinfo);
                        break;
                case RPCBPROC_GETADDR:
                        cnt = 0;
                        for (pa = inf[RPCBVERS_3_STAT].addrinfo; pa;
                            pa = pa->next)
                                cnt += pa->success;
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%zu/", cnt);
                        break;
                case RPCBPROC_CALLIT:
                        cnt = 0;
                        for (pr = inf[RPCBVERS_3_STAT].rmtinfo; pr;
                            pr = pr->next)
                                cnt += pr->success;
                        (void)snprintf(fieldbuf, sizeof(fieldbuf), "%zu/", cnt);
                        break;
                default: break;  /* For the remaining ones */
                }
                cp = &fieldbuf[0] + strlen(fieldbuf);
                (void)snprintf(cp, sizeof(fieldbuf) - (cp - fieldbuf),
                    "%d", inf[RPCBVERS_3_STAT].info[i]);
                flen = strlen(fieldbuf);
                (void)printf("%s%s", rpcb3hdr[i],
                    spaces((TABSTOP * (1 + flen / TABSTOP))
                    - strlen(rpcb3hdr[i])));
                (void)snprintf(lp, sizeof(linebuf) - (lp - linebuf), "%s%s",
                    fieldbuf, spaces(cnt = ((TABSTOP * (1 + flen / TABSTOP))
                    - flen)));
                lp += (flen + cnt);
        }
        (void)printf("\n%s\n\n", linebuf);

        if (inf[RPCBVERS_3_STAT].info[RPCBPROC_CALLIT]) {
                (void)printf("RPCB_RMTCALL (version 3) call statistics\n");
                print_rmtcallstat(RPCBVERS_3_STAT, &inf[RPCBVERS_3_STAT]);
                (void)printf("\n");
        }

        if (inf[RPCBVERS_3_STAT].info[RPCBPROC_GETADDR]) {
                (void)printf("RPCB_GETADDR (version 3) call statistics\n");
                print_getaddrstat(RPCBVERS_3_STAT, &inf[RPCBVERS_3_STAT]);
                (void)printf("\n");
        }

        (void)printf("RPCBIND (version 4) statistics\n");

        for (j = 0; j <= 9; j += 9) { /* Just two iterations for printing */
                lp = linebuf;
                for (i = j; i <= MAX(8, rpcb_highproc_4 - 9 + j); i++) {
                        fieldbuf[0] = '\0';
                        switch (i) {
                        case RPCBPROC_SET:
                                (void)snprintf(fieldbuf, sizeof(fieldbuf),
                                    "%d/", inf[RPCBVERS_4_STAT].setinfo);
                                break;
                        case RPCBPROC_UNSET:
                                (void)snprintf(fieldbuf, sizeof(fieldbuf),
                                    "%d/", inf[RPCBVERS_4_STAT].unsetinfo);
                                break;
                        case RPCBPROC_GETADDR:
                                cnt = 0;
                                for (pa = inf[RPCBVERS_4_STAT].addrinfo; pa;
                                        pa = pa->next)
                                        cnt += pa->success;
                                (void)snprintf(fieldbuf, sizeof(fieldbuf),
                                    "%zu/", cnt);
                                break;
                        case RPCBPROC_CALLIT:
                                cnt = 0;
                                for (pr = inf[RPCBVERS_4_STAT].rmtinfo; pr;
                                    pr = pr->next)
                                        cnt += pr->success;
                                (void)snprintf(fieldbuf, sizeof(fieldbuf),
                                    "%zu/", cnt);
                                break;
                        default: break;  /* For the remaining ones */
                        }
                        cp = &fieldbuf[0] + strlen(fieldbuf);
                        /*
                         * XXX: We also add RPCBPROC_GETADDRLIST queries to
                         * RPCB_GETADDR because rpcbind includes the
                         * RPCB_GETADDRLIST successes in RPCB_GETADDR.
                         */
                        if (i != RPCBPROC_GETADDR)
                            (void)snprintf(cp,
                                sizeof(fieldbuf) - (cp - fieldbuf),
                                "%d", inf[RPCBVERS_4_STAT].info[i]);
                        else
                            (void)snprintf(cp,
                                sizeof(fieldbuf) - (cp - fieldbuf),
                                "%d", inf[RPCBVERS_4_STAT].info[i] +
                                inf[RPCBVERS_4_STAT].
                                info[RPCBPROC_GETADDRLIST]);
                        flen = strlen(fieldbuf);
                        (void)printf("%s%s", rpcb4hdr[i],
                            spaces((TABSTOP * (1 + flen / TABSTOP))
                            - strlen(rpcb4hdr[i])));
                        (void)snprintf(lp, sizeof(linebuf) - (lp - linebuf),
                            "%s%s", fieldbuf,
                            spaces(cnt = ((TABSTOP * (1 + flen / TABSTOP))
                            - flen)));
                        lp += (flen + cnt);
                }
                (void)printf("\n%s\n", linebuf);
        }

        if (inf[RPCBVERS_4_STAT].info[RPCBPROC_CALLIT] ||
                            inf[RPCBVERS_4_STAT].info[RPCBPROC_INDIRECT]) {
                (void)printf("\n");
                (void)printf("RPCB_RMTCALL (version 4) call statistics\n");
                print_rmtcallstat(RPCBVERS_4_STAT, &inf[RPCBVERS_4_STAT]);
        }

        if (inf[RPCBVERS_4_STAT].info[RPCBPROC_GETADDR]) {
                (void)printf("\n");
                (void)printf("RPCB_GETADDR (version 4) call statistics\n");
                print_getaddrstat(RPCBVERS_4_STAT, &inf[RPCBVERS_4_STAT]);
        }
        clnt_destroy(client);
}

/*
 * Delete registeration for this (prog, vers, netid)
 */
static void
deletereg(const char *netid, int argc, char **argv)
{
        struct netconfig *nconf = NULL;

        if (argc != 2)
                usage();
        if (netid) {
                nconf = getnetconfigent(netid);
                if (nconf == NULL)
                        errx(1, "netid %s not supported", netid);
        }
        if ((rpcb_unset(getprognum(argv[0]), getvers(argv[1]), nconf)) == 0)
                errx(1, "Could not delete registration for prog %s version %s",
                    argv[0], argv[1]);
}

/*
 * Create and return a handle for the given nconf.
 * Exit if cannot create handle.
 */
static CLIENT *
clnt_addr_create(const char *address, const struct netconfig *nconf,
    rpcprog_t prog, rpcvers_t vers)
{
        CLIENT *client;
        static struct netbuf *nbuf;
        static int fd = RPC_ANYFD;

        if (fd == RPC_ANYFD) {
                if ((fd = __rpc_nconf2fd(nconf)) == -1) {
                        rpc_createerr.cf_stat = RPC_TLIERROR;
                        clnt_pcreateerror(getprogname());
                        exit(1);
                }
                /* Convert the uaddr to taddr */
                nbuf = uaddr2taddr(nconf, address);
                if (nbuf == NULL)
                        errx(1, "No address for client handle");
        }
        client = clnt_tli_create(fd, nconf, nbuf, prog, vers, 0, 0);
        if (client == NULL) {
                clnt_pcreateerror(getprogname());
                exit(1);
        }
        return client;
}

/*
 * If the version number is given, ping that (prog, vers); else try to find
 * the version numbers supported for that prog and ping all the versions.
 * Remote rpcbind is not contacted for this service. The requests are
 * sent directly to the services themselves.
 */
static void
addrping(const char *address, const char *netid, int argc, char **argv)
{
        CLIENT *client;
        struct timeval to;
        enum clnt_stat rpc_stat;
        rpcprog_t prognum, versnum, minvers, maxvers;
        struct rpc_err rpcerr;
        int failure = 0;
        struct netconfig *nconf;
        int fd;

        if (argc < 1 || argc > 2 || (netid == NULL))
                usage();
        nconf = getnetconfigent(netid);
        if (nconf == NULL)
                errx(1, "Could not find %s", netid);
        to.tv_sec = 10;
        to.tv_usec = 0;
        prognum = getprognum(argv[0]);
        if (argc == 1) {        /* Version number not known */
                /*
                 * A call to version 0 should fail with a program/version
                 * mismatch, and give us the range of versions supported.
                 */
                versnum = MIN_VERS;
        } else {
                versnum = getvers(argv[1]);
        }
        client = clnt_addr_create(address, nconf, prognum, versnum);
        rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
            NULL, (xdrproc_t)xdr_void, NULL, to);
        if (argc == 2) {
                /* Version number was known */
                if (pstatus(client, prognum, versnum) < 0)
                        failure = 1;
                (void)CLNT_DESTROY(client);
                if (failure)
                        exit(1);
                return;
        }
        /* Version number not known */
        (void)CLNT_CONTROL(client, CLSET_FD_NCLOSE, NULL);
        (void)CLNT_CONTROL(client, CLGET_FD, (char *)(void *)&fd);
        if (rpc_stat == RPC_PROGVERSMISMATCH) {
                clnt_geterr(client, &rpcerr);
                minvers = rpcerr.re_vers.low;
                maxvers = rpcerr.re_vers.high;
        } else if (rpc_stat == RPC_SUCCESS) {
                /*
                 * Oh dear, it DOES support version 0.
                 * Let's try version MAX_VERS.
                 */
                (void)CLNT_DESTROY(client);
                client = clnt_addr_create(address, nconf, prognum, MAX_VERS);
                rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
                    NULL, (xdrproc_t)xdr_void, NULL, to);
                if (rpc_stat == RPC_PROGVERSMISMATCH) {
                        clnt_geterr(client, &rpcerr);
                        minvers = rpcerr.re_vers.low;
                        maxvers = rpcerr.re_vers.high;
                } else if (rpc_stat == RPC_SUCCESS) {
                        /*
                         * It also supports version MAX_VERS.
                         * Looks like we have a wise guy.
                         * OK, we give them information on all
                         * 4 billion versions they support...
                         */
                        minvers = 0;
                        maxvers = MAX_VERS;
                } else {
                        (void)pstatus(client, prognum, MAX_VERS);
                        exit(1);
                }
        } else {
                (void)pstatus(client, prognum, MIN_VERS);
                exit(1);
        }
        (void)CLNT_DESTROY(client);
        for (versnum = minvers; versnum <= maxvers; versnum++) {
                client = clnt_addr_create(address, nconf, prognum, versnum);
                rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
                    NULL, (xdrproc_t)xdr_void, NULL, to);
                if (pstatus(client, prognum, versnum) < 0)
                        failure = 1;
                (void)CLNT_DESTROY(client);
        }
        (void)close(fd);
        if (failure)
                exit(1);
        return;
}

/*
 * If the version number is given, ping that (prog, vers); else try to find
 * the version numbers supported for that prog and ping all the versions.
 * Remote rpcbind is *contacted* for this service. The requests are
 * then sent directly to the services themselves.
 */
static void
progping(const char *netid, int argc, char **argv)
{
        CLIENT *client;
        struct timeval to;
        enum clnt_stat rpc_stat;
        rpcprog_t prognum;
        rpcvers_t versnum, minvers, maxvers;
        struct rpc_err rpcerr;
        int failure = 0;
        struct netconfig *nconf;

        if (argc < 2 || argc > 3 || (netid == NULL))
                usage();
        prognum = getprognum(argv[1]);
        if (argc == 2) { /* Version number not known */
                /*
                 * A call to version 0 should fail with a program/version
                 * mismatch, and give us the range of versions supported.
                 */
                versnum = MIN_VERS;
        } else {
                versnum = getvers(argv[2]);
        }
        if (netid) {
                nconf = getnetconfigent(netid);
                if (nconf == NULL)
                        errx(1, "Could not find `%s'", netid);
                client = clnt_tp_create(argv[0], prognum, versnum, nconf);
        } else {
                client = clnt_create(argv[0], prognum, versnum, "NETPATH");
        }
        if (client == NULL) {
                clnt_pcreateerror(getprogname());
                exit(1);
        }
        to.tv_sec = 10;
        to.tv_usec = 0;
        rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
            NULL, (xdrproc_t)xdr_void, NULL, to);
        if (argc == 3) {
                /* Version number was known */
                if (pstatus(client, prognum, versnum) < 0)
                        failure = 1;
                (void)CLNT_DESTROY(client);
                if (failure)
                        exit(1);
                return;
        }
        /* Version number not known */
        if (rpc_stat == RPC_PROGVERSMISMATCH) {
                clnt_geterr(client, &rpcerr);
                minvers = rpcerr.re_vers.low;
                maxvers = rpcerr.re_vers.high;
        } else if (rpc_stat == RPC_SUCCESS) {
                /*
                 * Oh dear, it DOES support version 0.
                 * Let's try version MAX_VERS.
                 */
                versnum = MAX_VERS;
                (void)CLNT_CONTROL(client, CLSET_VERS,
                    (char *)(void *)&versnum);
                rpc_stat = CLNT_CALL(client, NULLPROC,
                    (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL, to);
                if (rpc_stat == RPC_PROGVERSMISMATCH) {
                        clnt_geterr(client, &rpcerr);
                        minvers = rpcerr.re_vers.low;
                        maxvers = rpcerr.re_vers.high;
                } else if (rpc_stat == RPC_SUCCESS) {
                        /*
                         * It also supports version MAX_VERS.
                         * Looks like we have a wise guy.
                         * OK, we give them information on all
                         * 4 billion versions they support...
                         */
                        minvers = 0;
                        maxvers = MAX_VERS;
                } else {
                        (void)pstatus(client, prognum, MAX_VERS);
                        exit(1);
                }
        } else {
                (void)pstatus(client, prognum, MIN_VERS);
                exit(1);
        }
        for (versnum = minvers; versnum <= maxvers; versnum++) {
                (void)CLNT_CONTROL(client, CLSET_VERS,
                    (char *)(void *)&versnum);
                rpc_stat = CLNT_CALL(client, NULLPROC, (xdrproc_t)xdr_void,
                    NULL, (xdrproc_t)xdr_void, NULL, to);
                if (pstatus(client, prognum, versnum) < 0)
                        failure = 1;
        }
        (void)CLNT_DESTROY(client);
        if (failure)
                exit(1);
        return;
}

static void
usage(void)
{
        (void)fprintf(stderr, "Usage: %s [-m | -s] [host]\n", getprogname());
#ifdef PORTMAP
        (void)fprintf(stderr, "\t%s -p [host]\n", getprogname());
#endif
        (void)fprintf(stderr, "\t%s -T netid host prognum [versnum]\n",
            getprogname());
        (void)fprintf(stderr, "\t%s -l host prognum versnum\n", getprogname());
#ifdef PORTMAP
        (void)fprintf(stderr,
            "\t%s [-n portnum] -u | -t host prognum [versnum]\n",
            getprogname());
#endif
        (void)fprintf(stderr,
            "\t%s -a serv_address -T netid prognum [version]\n",
            getprogname());
        (void)fprintf(stderr, "\t%s -b prognum versnum\n", getprogname());
        (void)fprintf(stderr, "\t%s -d [-T netid] prognum versnum\n",
            getprogname());
        exit(0);
}

static rpcprog_t
getprognum(const char *arg)
{
        char *strptr;
        struct rpcent *rpc;
        u_long prognum;
        const char *tptr = arg;

        while (*tptr && isdigit((unsigned char)*tptr++))
                continue;
        if (*tptr || isalpha((unsigned char)*(tptr - 1))) {
                rpc = getrpcbyname(arg);
                if (rpc == NULL)
                        errx(1, "Unknown service `%s'", arg);
                prognum = rpc->r_number;
        } else {
                errno = 0;
                prognum = strtoul(arg, &strptr, 0);
                if (strptr == arg || *strptr != '\0' ||
                    (prognum == ULONG_MAX && errno == ERANGE))
                        errx(1, "Illegal program number `%s'", arg);
        }
        return (rpcprog_t)prognum;
}

static rpcvers_t
getvers(const char *arg)
{
        char *strptr;
        u_long vers;

        vers = strtoul(arg, &strptr, 0);
        if (strptr == arg || *strptr != '\0' ||
            (vers == ULONG_MAX && errno == ERANGE))
                errx(1, "Illegal version number `%s'", arg);
        return (rpcvers_t)vers;
}

static in_port_t
getport(const struct netbuf *nb)
{
        const struct sockaddr *sa = nb->buf;
        switch (sa->sa_family) {
        case AF_INET:
                return ((const struct sockaddr_in *)nb->buf)->sin_port;
        case AF_INET6:
                return ((const struct sockaddr_in6 *)nb->buf)->sin6_port;
        default:
                return -1;
        }
}

/*
 * This routine should take a pointer to an "rpc_err" structure, rather than
 * a pointer to a CLIENT structure, but "clnt_sperror" takes a pointer to
 * a CLIENT structure rather than a pointer to an "rpc_err" structure.
 * As such, we have to keep the CLIENT structure around in order to print
 * a good error message.
 */
static int
pstatus(CLIENT *client, rpcprog_t prog, rpcvers_t vers)
{
        struct rpc_err rpcerr;

        clnt_geterr(client, &rpcerr);
        if (rpcerr.re_status != RPC_SUCCESS) {
                warnx("Program %lu version %lu is not available (%s)",
                    (unsigned long)prog, (unsigned long)vers,
                    clnt_sperror(client, "") + 2);
                return -1;
        } else {
                in_port_t portnum;
                struct netbuf nb;
                CLNT_CONTROL(client, CLGET_SVC_ADDR, (char *)&nb);
                portnum = ntohs(getport(&nb));
                (void)printf("program %lu version %lu ready and waiting"
                    " at port %u\n", (unsigned long)prog, (unsigned long)vers,
                    portnum);
                return 0;
        }
}

static CLIENT *
clnt_rpcbind_create(const char *host, rpcvers_t rpcbversnum,
    struct netbuf **targaddr)
{
        static const char *tlist[] = {
                "circuit_n", "circuit_v", "datagram_v"
        };
        size_t i;
        struct netconfig *nconf;
        CLIENT *clnt = NULL;
        void *handle;

        rpc_createerr.cf_stat = RPC_SUCCESS;
        for (i = 0; i < __arraycount(tlist); i++) {
                if ((handle = __rpc_setconf(tlist[i])) == NULL)
                        continue;
                while (clnt == NULL) {
                        if ((nconf = __rpc_getconf(handle)) == NULL) {
                                if (rpc_createerr.cf_stat == RPC_SUCCESS)
                                    rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
                                break;
                        }
                        clnt = getclnthandle(host, nconf, rpcbversnum,
                            targaddr);
                }
                __rpc_endconf(handle);
                if (clnt)
                        return clnt;
        }
        return NULL;
}

static CLIENT*
getclnthandle(const char *host, const struct netconfig *nconf,
    rpcvers_t rpcbversnum, struct netbuf **targaddr)
{
        struct netbuf addr;
        struct addrinfo hints, *res;
        CLIENT *client = NULL;

        /* Get the address of the rpcbind */
        (void)memset(&hints, 0, sizeof hints);
        if (getaddrinfo(host, "sunrpc", &hints, &res) != 0) {
                rpc_createerr.cf_stat = RPC_N2AXLATEFAILURE;
                return NULL;
        }
        addr.len = addr.maxlen = res->ai_addrlen;
        addr.buf = res->ai_addr;
        client = clnt_tli_create(RPC_ANYFD, nconf, &addr, RPCBPROG,
            rpcbversnum, 0, 0);
        if (client) {
                if (targaddr != NULL) {
                        *targaddr = malloc(sizeof(**targaddr));
                        if (*targaddr != NULL) {
                                (*targaddr)->maxlen = addr.maxlen;
                                (*targaddr)->len = addr.len;
                                (*targaddr)->buf = malloc(addr.len);
                                if ((*targaddr)->buf != NULL) {
                                        (void)memcpy((*targaddr)->buf, addr.buf,
                                            addr.len);
                                }
                        }
                }
        } else {
                if (rpc_createerr.cf_stat == RPC_TLIERROR) {
                        /*
                         * Assume that the other system is dead; this is a
                         * better error to display to the user.
                         */
                        rpc_createerr.cf_stat = RPC_RPCBFAILURE;
                        rpc_createerr.cf_error.re_status = RPC_FAILED;
                }
        }
        freeaddrinfo(res);
        return client;
}

static void
print_rmtcallstat(int rtype, const rpcb_stat *infp)
{
        rpcbs_rmtcalllist_ptr pr;
        const struct rpcent *rpc;

        if (rtype == RPCBVERS_4_STAT)
                (void)printf(
                    "prog\t\tvers\tproc\tnetid\tindirect success failure\n");
        else
                (void)printf("prog\t\tvers\tproc\tnetid\tsuccess\tfailure\n");
        for (pr = infp->rmtinfo; pr; pr = pr->next) {
                rpc = getrpcbynumber((int)pr->prog);
                if (rpc)
                        (void)printf("%-16s", rpc->r_name);
                else
                        (void)printf("%-16d", pr->prog);
                (void)printf("%d\t%d\t%s\t",
                        pr->vers, pr->proc, pr->netid);
                if (rtype == RPCBVERS_4_STAT)
                        (void)printf("%d\t ", pr->indirect);
                (void)printf("%d\t%d\n", pr->success, pr->failure);
        }
}

static void
/*ARGSUSED*/
print_getaddrstat(int rtype, const rpcb_stat *infp)
{
        rpcbs_addrlist_ptr al;
        const struct rpcent *rpc;

        (void)printf("prog\t\tvers\tnetid\t  success\tfailure\n");
        for (al = infp->addrinfo; al; al = al->next) {
                rpc = getrpcbynumber((int)al->prog);
                if (rpc)
                        (void)printf("%-16s", rpc->r_name);
                else
                        (void)printf("%-16d", al->prog);
                (void)printf("%d\t%s\t  %-12d\t%d\n", al->vers, al->netid,
                    al->success, al->failure);
        }
}

static const char *
spaces(size_t howmany)
{
        static const char space_array[] =       /* 64 spaces */
        "                                                                ";

        if (howmany >= sizeof(space_array)) {
                return ("");
        }
        return &space_array[sizeof(space_array) - howmany - 1];
}