/*
 * Copyright (c) 2009, Sun Microsystems, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of Sun Microsystems, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 1986 - 1991 by Sun Microsystems, Inc.
 */
/*
 * rpcinfo: ping a particular rpc program
 * 	or dump the 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/un.h>
#include <rpc/rpc.h>
#include <stdio.h>
#include <rpc/rpcb_prot.h>
#include <rpc/rpcent.h>
#include <rpc/nettype.h>
#include <rpc/rpc_com.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <ctype.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 max(a,b) ((a) > (b) ? (a) : (b))

#define	MIN_VERS	((u_long)0)
#define	MAX_VERS	((u_long)4294967295UL)
#define	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
{
  int vers;
  struct verslist *next;
};

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


#ifdef PORTMAP
static void ip_ping (u_short, char *, int, char **);
static void pmapdump (int, char **);
static CLIENT *ip_getclient(const char *hostname, rpcprog_t prognum, rpcvers_t versnum, const char *proto);
#endif

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

int main (int argc, char **argv);

int
main (int argc, char **argv)
{
  register 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')
	    {
	      fprintf (stderr, "rpcinfo: %s is illegal port number\n",
		       optarg);
	      exit (1);
	    }
	  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 ();
      return 1;
    }

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

  switch (function)
    {
#ifdef PORTMAP
    case PMAPDUMP:
      if (portnum != 0)
	{
	  usage ();
	  return 1;
	}
      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)
{
#if 0
  void *localhandle;
  struct netconfig *nconf;
  CLIENT *clnt;

  localhandle = setnetconfig();
  while ((nconf = getnetconfig(localhandle)) != NULL) {
    if (nconf->nc_protofmly != NULL &&
	strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0)
      break;
  }
  if (nconf == NULL) {
    warnx("getnetconfig: %s", nc_sperror());
    return (NULL);
  }

  clnt = clnt_tp_create (/* "localhost"*/ NULL, prog, vers, nconf);
  endnetconfig(localhandle);
  return clnt;
#else
  struct netbuf nbuf;
  struct sockaddr_un sun;
  int sock;

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

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

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

#ifdef PORTMAP
static enum clnt_stat
ip_ping_one(client, vers)
     CLIENT *client;
     u_int32_t vers;
{
  struct timeval to = { .tv_sec = 10, .tv_usec = 0 };

  (void) CLNT_CONTROL (client, CLSET_VERS, &vers);
  return CLNT_CALL (client, NULLPROC, (xdrproc_t) xdr_void,
		    (char *) NULL, (xdrproc_t) xdr_void, (char *) NULL,
		    to);
}

/*
 * 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 (portnum, proto, argc, argv)
     u_short portnum;
     char *proto;
     int argc;
     char **argv;
{
  CLIENT *client;
  enum clnt_stat rpc_stat;
  const char *hostname;
  u_long prognum, vers, minvers, maxvers;
  struct rpc_err rpcerr;
  int failure = 0;

  if (argc < 2 || argc > 3)
    {
      usage ();
      exit (1);
    }

  hostname = argv[0];
  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.
       */
      vers = MIN_VERS;
    }
  else
    {
      vers = getvers (argv[2]);
    }

  client = ip_getclient(hostname, prognum, vers, proto);

  rpc_stat = ip_ping_one(client, vers);
  if (argc != 2)
    {
      /* Version number was known */
      if (pstatus (client, prognum, vers) < 0)
	exit (1);
      (void) CLNT_DESTROY (client);
      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.
       */
      rpc_stat = ip_ping_one(client, MAX_VERS);
      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, (u_long) 0);
      exit (1);
    }
  for (vers = minvers; vers <= maxvers; vers++)
    {
      rpc_stat = ip_ping_one(client, vers);
      if (pstatus (client, prognum, vers) < 0)
	failure = 1;
    }
  if (failure)
    exit (1);
  (void) CLNT_DESTROY (client);
  return;
}

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

  if (argc > 1)
    {
      usage ();
      exit (1);
    }
  if (argc == 1)
    {
      host = argv[0];

      /* This is a little bit more complicated than it should be.
       * ip_getclient will do an rpcb_getaddr call to identify the
       * port of the portmapper - but it works, and it's easier than
       * creating a copy of ip_getclient that avoids the getaddr call.
       */
      client = ip_getclient(host, PMAPPROG, PMAPVERS, "tcp");
    }
  else
    client = local_rpcb (PMAPPROG, PMAPVERS);

  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;
	}
      clnt_pcreateerror ("rpcinfo: can't contact portmapper");
      exit (1);
    }

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

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

/*
 * Try to obtain the address of a given host/program/version, using the
 * specified protocol (one of udp or tcp).
 * This loops over all netconfig entries (according to the order given by
 * netpath and the config file), and tries to resolve the hostname, and obtain
 * the address using rpcb_getaddr.
 */
CLIENT *
ip_getclient(hostname, prognum, versnum, proto)
     const char *hostname;
     rpcprog_t prognum;
     rpcvers_t versnum;
     const char *proto;
{
  void *handle;
  enum clnt_stat saved_stat = RPC_SUCCESS;
  struct netconfig *nconf, *result = NULL;
  struct netbuf bind_address;
  struct sockaddr_storage __sa;
  CLIENT *client;

  memset(&bind_address, 0, sizeof(bind_address));
  bind_address.maxlen = sizeof(__sa);
  bind_address.buf = &__sa;

  handle = setnetconfig();
  while ((nconf = getnetconfig(handle)) != NULL)
    {
      if (!strcmp(nconf->nc_proto, proto)) {
        if (rpcb_getaddr(prognum, versnum, nconf, &bind_address, hostname))
          {
	    result = getnetconfigent(nconf->nc_netid);
	    endnetconfig(handle);
	    break;
	  }

        if (rpc_createerr.cf_stat != RPC_UNKNOWNHOST)
          {
            clnt_pcreateerror (hostname);
	    exit (1);
	  }

	saved_stat = rpc_createerr.cf_stat;
      }
    }

  if (result == NULL)
    {
      if (saved_stat != RPC_SUCCESS)
        {
          rpc_createerr.cf_stat = saved_stat;
          clnt_pcreateerror (hostname);
        }
      else
        fprintf (stderr, "Cannot find suitable transport for protocol %s\n", proto);

      exit (1);
    }

  client = clnt_tli_create(RPC_ANYFD, result, &bind_address, prognum, versnum, 0, 0);
  if (client == NULL)
    {
      clnt_pcreateerror(hostname);
      exit (1);
    }

  freenetconfigent(result);
  return client;
}
#endif /* PORTMAP */

static int
sa_len(struct sockaddr *sa)
{
    socklen_t salen;

    switch (sa->sa_family)
    {
        case AF_LOCAL:
            salen = sizeof (struct sockaddr_un);
            break;
        case AF_INET:
            salen = sizeof (struct sockaddr_in);
            break;
        case AF_INET6:
            salen = sizeof (struct sockaddr_in6);
            break;
        default:
            salen = 0;
            break;
    }
    return salen;
}

/*
 * 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 (res, who, nconf)
     void *res;			/* Nothing comes back */
     struct netbuf *who;	/* Who sent us the reply */
     struct netconfig *nconf;	/* On which transport the reply came */
{
  char *uaddr;
  char hostbuf[NI_MAXHOST];
  char *hostname;
  struct sockaddr *sa = (struct sockaddr *) who->buf;

  if (getnameinfo (sa, sa_len (sa), hostbuf, NI_MAXHOST, NULL, 0, 0))
    {
      hostname = UNKNOWN;
    }
  else
    {
      hostname = hostbuf;
    }
  if (!(uaddr = taddr2uaddr (nconf, who)))
    {
      printf ("%s\t%s\n", UNKNOWN, hostname);
    } else {
      printf ("%s\t%s\n", uaddr, hostname);
      free ((char *) uaddr);
    }
  return (FALSE);
}

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

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

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

  for (vl = rs->vlist; vl; vl = vl->next)
    if (vl->vers == vers)
      break;
  if (vl)
    return (TRUE);
  vl = (struct verslist *) 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 (rs, 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 = (struct netidlist *) malloc (sizeof (struct netidlist));
  if (nl == NULL)
    return (FALSE);
  nl->netid = netid;
  nl->next = rs->nlist;
  rs->nlist = nl;
  return (TRUE);
}

static void
rpcbdump (dumptype, netid, argc, argv)
     int dumptype;
     char *netid;
     int argc;
     char **argv;
{
  rpcblist_ptr head = NULL;
  struct timeval minutetimeout;
  register CLIENT *client;
  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 err;
  struct rpcbdump_short *rs_head = NULL;

  if (argc > 1)
    {
      usage ();
      exit (1);
    }
  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)
	    {
	      nc_perror ("rpcinfo: invalid transport");
	      exit (1);
	    }
	  client = getclnthandle (host, nconf, RPCBVERS, NULL);
	  if (nconf)
	    (void) freenetconfigent (nconf);
	}
    }
  else
    client = local_rpcb (PMAPPROG, RPCBVERS);

  if (client == (CLIENT *) NULL)
    {
      clnt_pcreateerror ("rpcinfo: can't contact rpcbind");
      exit (1);
    }
  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 *) &head,
		       minutetimeout);
  if (clnt_st != RPC_SUCCESS)
    {
      if ((clnt_st == RPC_PROGVERSMISMATCH) || (clnt_st == RPC_PROGUNAVAIL))
	{
	  int vers;

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

		  vers = PMAPVERS;
		  clnt_control (client, CLSET_VERS, (char *) &vers);
		  clnt_st = CLNT_CALL (client, PMAPPROC_DUMP,
				       (xdrproc_t) xdr_void, NULL,
				       (xdrproc_t) xdr_pmaplist_ptr,
				       (char *) &pmaphead, minutetimeout);
		  if (clnt_st != RPC_SUCCESS)
		    goto failed;
		  /*
		   * convert to rpcblist_ptr format
		   */
		  for (head = NULL; pmaphead != NULL;
		       pmaphead = pmaphead->pml_next)
		    {
		      list = (rpcblist *) 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 = "udp";
		      else if (pmaphead->pml_map.pm_prot == IPPROTO_TCP)
			list->rpcb_map.r_netid = "tcp";
		      else
			{
#define	MAXLONG_AS_STRING	"2147483648"
			  list->rpcb_map.r_netid =
			    malloc (strlen (MAXLONG_AS_STRING) + 1);
			  if (list->rpcb_map.r_netid == NULL)
			    goto error;
			  sprintf (list->rpcb_map.r_netid, "%6ld",
				   pmaphead->pml_map.pm_prot);
			}
		      list->rpcb_map.r_owner = UNKNOWN;
		      low = pmaphead->pml_map.pm_port & 0xff;
		      high = (pmaphead->pml_map.pm_port >> 8) & 0xff;
		      list->rpcb_map.r_addr = strdup ("0.0.0.0.XXX.XXX");
		      sprintf (&list->rpcb_map.r_addr[8], "%d.%d", high, low);
		      prev = list;
		    }
		}
	    }
	}
      else
	{			/* any other error */
	failed:
	  clnt_perror (client, "rpcinfo: can't contact rpcbind: ");
	  exit (1);
	}
    }
  if (head == NULL)
    {
      printf ("No remote programs registered.\n");
    }
  else if (dumptype == RPCBDUMP)
    {
      printf
	("   program version netid     address                service    owner\n");
      for (; head != NULL; head = head->rpcb_next)
	{
	  printf ("%10u%5u    ",
		  head->rpcb_map.r_prog, head->rpcb_map.r_vers);
	  printf ("%-9s ", head->rpcb_map.r_netid);
	  printf ("%-22s", head->rpcb_map.r_addr);
	  rpc = getrpcbynumber (head->rpcb_map.r_prog);
	  if (rpc)
	    printf (" %-10s", rpc->r_name);
	  else
	    printf (" %-10s", "-");
	  printf (" %s\n", head->rpcb_map.r_owner);
	}
    }
  else if (dumptype == RPCBDUMP_SHORT)
    {
      for (; head != NULL; head = head->rpcb_next)
	{
	  for (rs = rs_head; rs; rs = rs->next)
	    if (head->rpcb_map.r_prog == rs->prog)
	      break;
	  if (rs == NULL)
	    {
	      rs = (struct rpcbdump_short *)
		malloc (sizeof (struct rpcbdump_short));
	      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 = head->rpcb_map.r_prog;
	      rs->owner = head->rpcb_map.r_owner;
	      rs->nlist = NULL;
	      rs->vlist = NULL;
	    }
	  if (add_version (rs, head->rpcb_map.r_vers) == FALSE)
	    goto error;
	  if (add_netid (rs, head->rpcb_map.r_netid) == FALSE)
	    goto error;
	}
      printf
	("   program version(s) netid(s)                         service     owner\n");
      for (rs = rs_head; rs; rs = rs->next)
	{
	  size_t netidmax = sizeof(buf) - 1;
	  char *p = buf;

	  printf ("%10ld  ", rs->prog);
	  for (vl = rs->vlist; vl; vl = vl->next)
	    {
	      sprintf (p, "%d", vl->vers);
	      p = p + strlen (p);
	      if (vl->next)
		sprintf (p++, ",");
	    }
	  printf ("%-10s", buf);
	  buf[0] = '\0';

          for (nl = rs->nlist; nl; nl = nl->next)
            {
              strncat (buf, nl->netid, netidmax);
              if (strlen (nl->netid) < netidmax)
                netidmax -= strlen(nl->netid);
              else
                break;

              if (nl->next && netidmax > 1)
                {
                  strncat (buf, ",", netidmax);
                  netidmax --;
                }
            }

	  printf ("%-32s", buf);
	  rpc = getrpcbynumber (rs->prog);
	  if (rpc)
	    printf (" %-11s", rpc->r_name);
	  else
	    printf (" %-11s", "-");
	  printf (" %s\n", rs->owner);
	}
    }
  clnt_destroy (client);
  return;
error:fprintf (stderr, "rpcinfo: no memory\n");
  return;
}

static char nullstring[] = "\000";

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

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

      nconf = getnetconfigent (netid);
      if (nconf == NULL)
	{
	  nc_perror ("rpcinfo: invalid transport");
	  exit (1);
	}
      client = getclnthandle (host, nconf, RPCBVERS4, &targaddr);
      if (nconf)
	(void) freenetconfigent (nconf);
    }
  if (client == (CLIENT *) NULL)
    {
      clnt_pcreateerror ("rpcinfo: can't contact rpcbind");
      exit (1);
    }
  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 *) &parms, (xdrproc_t) xdr_rpcb_entry_list_ptr,
		 (char *) &head, minutetimeout) != RPC_SUCCESS)
    {
      clnt_perror (client, "rpcinfo: can't contact rpcbind: ");
      exit (1);
    }
  if (head == NULL)
    {
      printf ("No remote programs registered.\n");
    }
  else
    {
      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;
	  printf ("%10u%3u    ", parms.r_prog, parms.r_vers);
	  sprintf (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");
	  printf ("%-24s", buf);
	  printf ("%-24s", re->r_maddr);
	  rpc = getrpcbynumber (parms.r_prog);
	  if (rpc)
	    printf (" %-13s", rpc->r_name);
	  else
	    printf (" %-13s", "-");
	  printf ("\n");
	}
    }
  clnt_destroy (client);
  return;
}

/*
 * monitor rpcbind
 */
static void
rpcbgetstat (argc, argv)
     int argc;
     char **argv;
{
  rpcb_stat_byvers inf;
  struct timeval minutetimeout;
  register CLIENT *client;
  char *host;
  int i, j;
  rpcbs_addrlist *pa;
  rpcbs_rmtcalllist *pr;
  int cnt, flen;
#define	MAXFIELD	64
  char fieldbuf[MAXFIELD];
#define	MAXLINE		256
  char linebuf[MAXLINE];
  char *cp, *lp;
  char *pmaphdr[] = {
    "NULL", "SET", "UNSET", "GETPORT",
    "DUMP", "CALLIT"
  };
  char *rpcb3hdr[] = {
    "NULL", "SET", "UNSET", "GETADDR", "DUMP", "CALLIT", "TIME",
    "U2T", "T2U"
  };
  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 (PMAPPROG, RPCBVERS4);
  if (client == (CLIENT *) NULL)
    {
      clnt_pcreateerror ("rpcinfo: can't contact rpcbind");
      exit (1);
    }
  minutetimeout.tv_sec = 60;
  minutetimeout.tv_usec = 0;
  memset ((char *) &inf, 0, sizeof (rpcb_stat_byvers));
  if (CLNT_CALL (client, RPCBPROC_GETSTAT, (xdrproc_t) xdr_void, NULL,
		 (xdrproc_t) xdr_rpcb_stat_byvers, (char *) &inf,
		 minutetimeout) != RPC_SUCCESS)
    {
      clnt_perror (client, "rpcinfo: can't contact rpcbind: ");
      exit (1);
    }
  printf ("PORTMAP (version 2) statistics\n");
  lp = linebuf;
  for (i = 0; i <= rpcb_highproc_2; i++)
    {
      fieldbuf[0] = '\0';
      switch (i)
	{
	case PMAPPROC_SET:
	  sprintf (fieldbuf, "%d/", inf[RPCBVERS_2_STAT].setinfo);
	  break;
	case PMAPPROC_UNSET:
	  sprintf (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;
	  sprintf (fieldbuf, "%d/", cnt);
	  break;
	case PMAPPROC_CALLIT:
	  cnt = 0;
	  for (pr = inf[RPCBVERS_2_STAT].rmtinfo; pr; pr = pr->next)
	    cnt += pr->success;
	  sprintf (fieldbuf, "%d/", cnt);
	  break;
	default:
	  break;		/* For the remaining ones */
	}
      cp = &fieldbuf[0] + strlen (fieldbuf);
      sprintf (cp, "%d", inf[RPCBVERS_2_STAT].info[i]);
      flen = strlen (fieldbuf);
      printf ("%s%s", pmaphdr[i],
	      spaces ((TABSTOP * (1 + flen / TABSTOP))
		      - strlen (pmaphdr[i])));
      sprintf (lp, "%s%s", fieldbuf,
	       spaces (cnt = ((TABSTOP * (1 + flen / TABSTOP)) - flen)));
      lp += (flen + cnt);
    }
  printf ("\n%s\n\n", linebuf);

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

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

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

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

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

  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:
	      sprintf (fieldbuf, "%d/", inf[RPCBVERS_4_STAT].setinfo);
	      break;
	    case RPCBPROC_UNSET:
	      sprintf (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;
	      sprintf (fieldbuf, "%d/", cnt);
	      break;
	    case RPCBPROC_CALLIT:
	      cnt = 0;
	      for (pr = inf[RPCBVERS_4_STAT].rmtinfo; pr; pr = pr->next)
		cnt += pr->success;
	      sprintf (fieldbuf, "%d/", 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)
	    sprintf (cp, "%d", inf[RPCBVERS_4_STAT].info[i]);
	  else
	    sprintf (cp, "%d", inf[RPCBVERS_4_STAT].info[i] +
		     inf[RPCBVERS_4_STAT].info[RPCBPROC_GETADDRLIST]);
	  flen = strlen (fieldbuf);
	  printf ("%s%s", rpcb4hdr[i],
		  spaces ((TABSTOP * (1 + flen / TABSTOP))
			  - strlen (rpcb4hdr[i])));
	  sprintf (lp, "%s%s", fieldbuf,
		   spaces (cnt = ((TABSTOP * (1 + flen / TABSTOP)) - flen)));
	  lp += (flen + cnt);
	}
      printf ("\n%s\n", linebuf);
    }

  if (inf[RPCBVERS_4_STAT].info[RPCBPROC_CALLIT] ||
      inf[RPCBVERS_4_STAT].info[RPCBPROC_INDIRECT])
    {
      printf ("\n");
      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])
    {
      printf ("\n");
      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 (netid, argc, argv)
     char *netid;
     int argc;
     char **argv;
{
  struct netconfig *nconf = NULL;

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

/*
 * Create and return a handle for the given nconf.
 * Exit if cannot create handle.
 */
static CLIENT *
clnt_addr_create (address, nconf, prog, vers)
     char *address;
     struct netconfig *nconf;
     u_long prog;
     u_long 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 ("rpcinfo");
	  exit (1);
	}
      /* Convert the uaddr to taddr */
      nbuf = uaddr2taddr (nconf, address);
      if (nbuf == NULL)
	{
	  errx (1, "rpcinfo: no address for client handle");
	  exit (1);
	}
    }
  client = clnt_tli_create (fd, nconf, nbuf, prog, vers, 0, 0);
  if (client == (CLIENT *) NULL)
    {
      clnt_pcreateerror ("rpcinfo");
      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 (address, netid, argc, argv)
     char *address;
     char *netid;
     int argc;
     char **argv;
{
  CLIENT *client;
  struct timeval to;
  enum clnt_stat rpc_stat;
  u_int32_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 ();
      exit (1);
    }
  nconf = getnetconfigent (netid);
  if (nconf == (struct netconfig *) NULL)
    {
      fprintf (stderr, "rpcinfo: Could not find %s\n", netid);
      exit (1);
    }
  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,
			(char *) NULL, (xdrproc_t) xdr_void,
			(char *) 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, (char *) NULL);
  (void) CLNT_CONTROL (client, CLGET_FD, (char *) &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,
			    (char *) NULL, (xdrproc_t) xdr_void,
			    (char *) 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, (u_long) 0);
      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,
			    (char *) NULL, (xdrproc_t) xdr_void,
			    (char *) 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 (netid, argc, argv)
     char *netid;
     int argc;
     char **argv;
{
  CLIENT *client;
  struct timeval to;
  enum clnt_stat rpc_stat;
  u_int32_t prognum, versnum, minvers, maxvers;
  struct rpc_err rpcerr;
  int failure = 0;
  struct netconfig *nconf;

  if (argc < 2 || argc > 3 || (netid == NULL))
    {
      usage ();
      exit (1);
    }
  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 == (struct netconfig *) NULL)
	{
	  fprintf (stderr, "rpcinfo: Could not find %s\n", netid);
	  exit (1);
	}
      client = clnt_tp_create (argv[0], prognum, versnum, nconf);
    }
  else
    {
      client = clnt_create (argv[0], prognum, versnum, "NETPATH");
    }
  if (client == (CLIENT *) NULL)
    {
      clnt_pcreateerror ("rpcinfo");
      exit (1);
    }
  to.tv_sec = 10;
  to.tv_usec = 0;
  rpc_stat = CLNT_CALL (client, NULLPROC, (xdrproc_t) xdr_void,
			(char *) NULL, (xdrproc_t) xdr_void,
			(char *) 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 *) &versnum);
      rpc_stat = CLNT_CALL (client, NULLPROC,
			    (xdrproc_t) xdr_void, (char *) NULL,
			    (xdrproc_t) xdr_void, (char *) 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, (u_long) 0);
      exit (1);
    }
  for (versnum = minvers; versnum <= maxvers; versnum++)
    {
      (void) CLNT_CONTROL (client, CLSET_VERS, (char *) &versnum);
      rpc_stat = CLNT_CALL (client, NULLPROC, (xdrproc_t) xdr_void,
			    (char *) NULL, (xdrproc_t) xdr_void,
			    (char *) NULL, to);
      if (pstatus (client, prognum, versnum) < 0)
	failure = 1;
    }
  (void) CLNT_DESTROY (client);
  if (failure)
    exit (1);
  return;
}

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

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

  while (*tptr && isdigit (*tptr++));
  if (*tptr || isalpha (*(tptr - 1)))
    {
      rpc = getrpcbyname (arg);
      if (rpc == NULL)
	{
	  fprintf (stderr, "rpcinfo: %s is unknown service\n", arg);
	  exit (1);
	}
      prognum = rpc->r_number;
    }
  else
    {
      prognum = strtol (arg, &strptr, 10);
      if (strptr == arg || *strptr != '\0')
	{
	  fprintf (stderr, "rpcinfo: %s is illegal program number\n", arg);
	  exit (1);
	}
    }
  return (prognum);
}

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

  vers = (int) strtol (arg, &strptr, 10);
  if (strptr == arg || *strptr != '\0')
    {
      fprintf (stderr, "rpcinfo: %s is illegal version number\n", arg);
      exit (1);
    }
  return (vers);
}

/*
 * This routine should take a pointer to an "rpc_err" structure, rather than
 * a pointer to a CLIENT structure, but "clnt_perror" 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, prog, vers)
     register CLIENT *client;
     u_long prog;
     u_long vers;
{
  struct rpc_err rpcerr;

  clnt_geterr (client, &rpcerr);
  if (rpcerr.re_status != RPC_SUCCESS)
    {
      clnt_perror (client, "rpcinfo");
      printf ("program %lu version %lu is not available\n", prog, vers);
      return (-1);
    }
  else
    {
      printf ("program %lu version %lu ready and waiting\n", prog, vers);
      return (0);
    }
}

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

  rpc_createerr.cf_stat = RPC_SUCCESS;
  for (i = 0; i < 3; i++)
    {
      if ((handle = __rpc_setconf (tlist[i])) == NULL)
	continue;
      while (clnt == (CLIENT *) 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);
	}
      if (clnt)
	break;
      __rpc_endconf (handle);
    }
  return (clnt);
}

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

  /* Get the address of the rpcbind */
  memset (&hints, 0, sizeof hints);
  if ((getaddrinfo (host, "rpcbind", &hints, &res) != 0) &&
      (getaddrinfo (host, "portmapper",&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 = (struct netbuf *) malloc (sizeof (struct netbuf));
	  if (*targaddr != NULL)
	    {
	      (*targaddr)->maxlen = addr.maxlen;
	      (*targaddr)->len = addr.len;
	      (*targaddr)->buf = (char *) malloc (addr.len);
	      if ((*targaddr)->buf != NULL)
		{
		  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 (rtype, infp)
     int rtype;
     rpcb_stat *infp;
{
  register rpcbs_rmtcalllist_ptr pr;
  struct rpcent *rpc;

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

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

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

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

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