/*	$OpenBSD: main.c,v 1.52 2005/02/10 14:25:08 itojun Exp $	*/
/*	$NetBSD: main.c,v 1.9 1996/05/07 02:55:02 thorpej Exp $	*/

/*
 * Copyright (c) 1983, 1988, 1993
 *	Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 */

#ifndef lint
char copyright[] =
    "@(#) Copyright (c) 1983, 1988, 1993\n\
	Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/utilities.h>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif

#include "main.h"
#include "netstat.h"

#if HAVE_WINSOCK_H
#include "winstub.h"
#endif

int	aflag;		/* show all sockets (including servers) */
int	bflag;		/* show bytes instead of packets */
int	dflag;		/* show i/f dropped packets */
int	gflag;		/* show group (multicast) routing or stats */
int	iflag;		/* show interfaces */
int	lflag;		/* show routing table with use and ref */
int	Lflag;		/* Legacy mibs */
int	mflag;		/* show memory stats */
int	nflag;		/* show addresses numerically */
int	oflag;		/* Open/Net-BSD style octet output */
int	pflag;		/* show given protocol */
int	qflag;		/* only display non-zero values for output */
int	rflag;		/* show routing tables (or routing stats) */
int	Sflag;		/* show source address in routing table */
int	sflag;		/* show protocol statistics */
int	tflag;		/* show i/f watchdog timers */
int	vflag;		/* be verbose */


int	interval;	/* repeat interval for i/f stats */
char	*intrface;	/* desired i/f for stats, or NULL for all i/fs */
int	af;		/* address family */
int     max_getbulk = 32;  /* specifies the max-repeaters value to use with GETBULK requests */

char    *progname = NULL;
const char *pname;
/*
 * struct nlist nl[] - Omitted
 */

typedef void (stringfun)(const char*);

struct protox {
    /* pr_index/pr_sindex - Omitted */
    int		pr_wanted;	/* 1 if wanted, 0 otherwise */
    stringfun	*pr_cblocks;	/* control blocks printing routine */
    stringfun	*pr_stats;	/* statistics printing routine */
    const char	*pr_name;	/* well-known name */
};

struct protox protox[] = {
    { 1,	tcpprotopr,	tcp_stats,	"tcp" },
    { 1,	udpprotopr,	udp_stats,	"udp" },

    { 1,	(stringfun*)0,	ip_stats,	"ip" },	/* protopr Omitted */
    { 1,	(stringfun*)0,	icmp_stats,	"icmp" },
    /* igmp/ah/esp/ipencap/etherip/ipcomp/carp/pfsync/pim - Omitted */
    { 0,	(stringfun*)0,	(stringfun*)0,	NULL }
};

struct protox ip6protox[] = {
    { 1,	tcp6protopr,	(stringfun*)0,	"tcp6" },
    { 1,	udp6protopr,	(stringfun*)0,	"udp6" },

    { 1,	(stringfun*)0,	ip6_stats,	"ip6" },/* ip6protopr Omitted */
    { 1,	(stringfun*)0,	icmp6_stats,	"icmp6" },
    /* pim6/rip6 - Omitted */
    { 0,	(stringfun*)0,	(stringfun*)0,	NULL }
};

struct protox ipxprotox[] = {
    { 1,	tcpxprotopr,	tcp_stats,	"tcp" },
    { 1,	udpxprotopr,	udp_stats,	"udp" },
    { 1,	(stringfun*)0,	ipx_stats,	"ip" },/* ip6protopr Omitted */
    { 1,	(stringfun*)0,	ipx_stats,	"ip6" },/* ip6protopr Omitted */
    { 1,	(stringfun*)0,	icmpx_stats,	"icmp" },
    { 1,	(stringfun*)0,	icmpx_stats,	"icmp6" },
    { 0,	(stringfun*)0,	(stringfun*)0,	NULL }
};

/* {ipx,ns,atalk}protox Omitted */

struct protox *protoprotox[] = {
    protox, ip6protox, NULL
};

static void printproto(struct protox *, const char *);
static void usage(void);
static struct protox *name2protox(const char *);
static struct protox *knownname(const char *);

netsnmp_session *ss;
struct protox *tp = NULL; /* for printing cblocks & stats */

static void
optProc( int argc, char *const *argv, int opt )
{
    switch (opt) {
    case 'C':
        while (*optarg) {
            switch (*optarg++) {
            case 'a':
                aflag = 1;
                break;
            case 'b':
                bflag = 1;
                break;
            case 'd':
                dflag = 1;
                break;
            case 'f':
                if (!*optarg)
                    optarg = argv[optind++];
                if (strcmp(optarg, "inet") == 0)
                    af = AF_INET;
                else if (strcmp(optarg, "inet6") == 0)
                    af = AF_INET6;
                /*
                  else if (strcmp(optarg, "local") == 0)
                  af = AF_LOCAL;
                  else if (strcmp(optarg, "unix") == 0)
                  af = AF_UNIX;
                  else if (strcmp(optarg, "ipx") == 0)
                  af = AF_IPX;
                  else if (strcmp(optarg, "ns") == 0)
                  af = AF_NS;
                  else if (strcmp(optarg, "encap") == 0)
                  af = PF_KEY;
                  else if (strcmp(optarg, "atalk") == 0)
                  af = AF_APPLETALK;
                */
                else {
                    (void)fprintf(stderr,
                                  "%s: %s: unknown address family\n",
                                  progname, optarg);
                    exit(1);
                }
                return;
            case 'g':
                gflag = 1;
                break;
            case 'I':
                iflag = 1;
                if (!*optarg)
                    optarg = argv[optind++];
                intrface = optarg;
                return;
            case 'i':
                iflag = 1;
                break;
            case 'L':
                Lflag = 1;
                break;
                /*  case 'L':		FreeBSD: Display listen queue lengths
                    NetBSD:  Suppress link-level routes */
                /*	case 'l':		OpenBSD: Wider IPv6 display
                        Linux:   Listening sockets only
			lflag = 1;
			break;
                        case 'M':		*BSD:    Memory image
                        Linux:   Masqueraded connections
			memf = optarg;
			break;
                */
            case 'm':
                mflag = 1;
                break;
                /*	case 'N':		*BSD:    Kernel image
			nlistf = optarg;
			break;
                */
            case 'n':
                nflag = 1;
                break;
            case 'o':
                oflag = 1;
                break;
                /*  case 'P':		NetBSD:
                    OpenBSD: dump PCB block */
            case 'p':
                if (!*optarg)
                    optarg = argv[optind++];
                if ((tp = name2protox(optarg)) == NULL) {
                    (void)fprintf(stderr,
                                  "%s: %s: unknown protocol\n",
                                  progname, optarg);
                    exit(1);
                }
                pflag = 1;
                pname = tp->pr_name;
                return;
                /*	case 'q':		NetBSD:  IRQ information
                        OpenBSD: Suppress inactive I/Fs
			qflag = 1;
			break;
                */
            case 'r':
                rflag = 1;
                break;
            case 'R':
                if (optind < argc) {
                    if (argv[optind]) {
                        max_getbulk = atoi(argv[optind]);
                        if (max_getbulk == 0) {
                            usage();
                            fprintf(stderr, "Bad -CR option: %s\n",
                                    argv[optind]);
                            exit(1);
                        }
                    }
                } else {
                    usage();
                    fprintf(stderr, "Bad -CR option: no argument given\n");
                    exit(1);
                }
                optind++;
                break;
            case 'S':	     /* FreeBSD:
                                NetBSD:  Semi-numeric display
                                OpenBSD: Show route source selector */
                Sflag = 1;
                break;
            case 's':
                ++sflag;
                break;
                /*	case 't':		FreeBSD:
                        OpenBSD: Display watchdog timers
			tflag = 1;
			break;
                        case 'u':		OpenBSD: unix sockets only
			af = AF_UNIX;
			break;
                */
            case 'v':
                vflag = 1;
                break;
            case 'w':
                if (!*optarg)
                    optarg = argv[optind++];
                interval = atoi(optarg);
                iflag = 1;
                return;
            case '?':
            default:
                usage();
                exit(1);
            }
        }
        break;   /* End of '-Cx' switch */

        /*
         *  Backward compatability for the main display modes
         *    (where this doesn't clash with standard SNMP flags)
         */
    case 'i':
	iflag = 1;
	break;
    case 'R':
	rflag = 1;    /* -r sets the retry count */
	break;
    case 's':
	++sflag;
	break;
    }
}

int
main(int argc, char *argv[])
{
    netsnmp_session session;
    struct protoent *p;
    char *cp;
    int exit_code = 1;

    SOCK_STARTUP;

    af = AF_UNSPEC;
    cp = strrchr( argv[0], '/' );
    if (cp)
        progname = cp+1;
    else
        progname = argv[0];

    switch (snmp_parse_args( argc, argv, &session, "C:iRs", optProc)) {
    case NETSNMP_PARSE_ARGS_ERROR:
        goto out;
    case NETSNMP_PARSE_ARGS_SUCCESS_EXIT:
        exit_code = 0;
        goto out;
    case NETSNMP_PARSE_ARGS_ERROR_USAGE:
        usage();
        goto out;
    default:
        break;
    }

    /*
     * Check argc vs optind ??
     */
    argv += optind;
    argc -= optind;

    /*
     * Open an SNMP session.
     */
    ss = snmp_open(&session);
    if (ss == NULL) {
        /*
         * diagnose snmp_open errors with the input netsnmp_session pointer
         */
        snmp_sess_perror("snmpnetstat", &session);
        goto out;
    }

    /*
     * Omitted:
     *     Privilege handling
     *    "Backward Compatibility"
     *     Kernel namelis handling
     */

#if 0
    if (mflag) {
        /*
          mbpr(nl[N_MBSTAT].n_value, nl[N_MBPOOL].n_value,
          nl[N_MCLPOOL].n_value);
        */
        exit_code = 0;
        goto out;
    }
    if (pflag) {
        printproto(tp, tp->pr_name);
        exit_code = 0;
        goto out;
    }
#endif
    /*
     * Keep file descriptors open to avoid overhead
     * of open/close on each call to get* routines.
     */
    sethostent(1);
    setnetent(1);
    if (iflag) {
        intpr(interval);
        exit_code = 0;
        goto out;
    }
    if (rflag) {
        /*
          if (sflag)
          rt_stats();
          else
        */
        if (Lflag || routexpr(af) == 0) {
            if (route4pr(af) == 0 && af == AF_INET) routepr();
            route6pr(af);
        }
        exit_code = 0;
        goto out;
    }
    /*
      if (gflag) {
      if (sflag) {
      if (af == AF_INET || af == AF_UNSPEC)
      mrt_stats(nl[N_MRTPROTO].n_value,
      nl[N_MRTSTAT].n_value);
      #ifdef NETSNMP_ENABLE_IPV6
      if (af == AF_INET6 || af == AF_UNSPEC)
      mrt6_stats(nl[N_MRT6PROTO].n_value,
      nl[N_MRT6STAT].n_value);
      #endif
      }
      else {
      if (af == AF_INET || af == AF_UNSPEC)
      mroutepr(nl[N_MRTPROTO].n_value,
      nl[N_MFCHASHTBL].n_value,
      nl[N_MFCHASH].n_value,
      nl[N_VIFTABLE].n_value);
      #ifdef NETSNMP_ENABLE_IPV6
      if (af == AF_INET6 || af == AF_UNSPEC)
      mroute6pr(nl[N_MRT6PROTO].n_value,
      nl[N_MF6CTABLE].n_value,
      nl[N_MIF6TABLE].n_value);
      #endif
      }
      exit_code = 0;
      goto out;
      }
    */
    setservent(1);
    if (Lflag) {
        switch (af) {
        case AF_UNSPEC:
            setprotoent(1);
            /* ugh, this is O(MN) ... why do we do this? */
            while ((p = getprotoent())) {
                for (tp = protox; tp->pr_name; tp++)
                    if (strcmp(tp->pr_name, p->p_name) == 0)
                        if (tp->pr_name && tp->pr_wanted)
                            printproto(tp, p->p_name);
            }
            endprotoent();
            break;
        case AF_INET:
            for (tp = protox; tp->pr_name; tp++)
                printproto(tp, tp->pr_name);
	    /* FALL THROUGH */
        case AF_INET6:
            for (tp = ip6protox; tp->pr_name; tp++)
                printproto(tp, tp->pr_name);
        }
    }
    else {
        for (tp = ipxprotox; tp->pr_name; tp++)
            if (!pname || strcmp(pname,tp->pr_name) == 0)
                printproto(tp, pname);
    }
    /*
      if (af == AF_IPX || af == AF_UNSPEC)
      for (tp = ipxprotox; tp->pr_name; tp++)
      printproto(tp, tp->pr_name);
      if (af == AF_NS || af == AF_UNSPEC)
      for (tp = nsprotox; tp->pr_name; tp++)
      printproto(tp, tp->pr_name);
      if ((af == AF_UNIX || af == AF_UNSPEC) && !sflag)
      unixpr(nl[N_UNIXSW].n_value);
      if (af == AF_APPLETALK || af == AF_UNSPEC)
      for (tp = atalkprotox; tp->pr_name; tp++)
      printproto(tp, tp->pr_name);
    */

    exit_code = 0;

out:
    SOCK_CLEANUP;
    return exit_code;
}

/*
 * Print out protocol statistics or control blocks (per sflag).
 * Namelist checks - Omitted
 */
static void
printproto(struct protox *tp, const char *name)
{
    void (*pr)(const char *);

    if (sflag) {
        pr = tp->pr_stats;
    } else {
        pr = tp->pr_cblocks;
    }
    if (pr != NULL)
        (*pr)(name);
}

/*
 * Read kernel memory - Omitted
 */

const char *
plural(int n)
{
    return (n != 1 ? "s" : "");
}

/*
 * Find the protox for the given "well-known" name.
 */
static struct protox *
knownname(const char *name)
{
    struct protox **tpp, *tp;

    for (tpp = protoprotox; *tpp; tpp++)
        for (tp = *tpp; tp->pr_name; tp++)
            if (strcmp(tp->pr_name, name) == 0)
                return (tp);
    return (NULL);
}

/*
 * Find the protox corresponding to name.
 */
static struct protox *
name2protox(const char *name)
{
    struct protox *tp;
    char **alias;			/* alias from p->aliases */
    struct protoent *p;

    /*
     * Try to find the name in the list of "well-known" names. If that
     * fails, check if name is an alias for an Internet protocol.
     */
    if ((tp = knownname(name)))
        return (tp);

    setprotoent(1);			/* make protocol lookup cheaper */
    while ((p = getprotoent())) {
        /* netsnmp_assert: name not same as p->name */
        for (alias = p->p_aliases; *alias; alias++)
            if (strcmp(name, *alias) == 0) {
                endprotoent();
                return (knownname(p->p_name));
            }
    }
    endprotoent();
    return (NULL);
}

static void
usage(void)
{
    (void)fprintf(stderr,
                  "usage: %s [snmp_opts] [-Canv] [-Cf address_family]\n", progname);
    (void)fprintf(stderr,
                  "       %s [snmp_opts] [-Cibodnv] [-CI interface] [-Cw wait]\n", progname);
    (void)fprintf(stderr,
                  "       %s [snmp_opts] [-Cs[s]] [-Cp protocol]\n", progname);
    (void)fprintf(stderr,
                  "       %s [snmp_opts] [-Crnv] [-Cf address_family]\n", progname);
}
