/* <TITLE>SNMPSNOOP dumping remote Internet node SNMP database  </TITLE> */
/*
SNMPSNOOP is a quick-and-dirty little program for dumping everything a 
remote Internet node knows in its SNMP database to your system in 
(supposedly) human-readable format. It is written in C, and will 
theoretically run under DEC UCX and VAX C (has not been tested); it 
has been tested to work under TGV MultiNet after compilation with Gnu C.
and with the standard Sun cc compiler under SunOS 4.1. Note that under 
MultiNet, it is possible to "hang" the program if a response packet gets 
lost in the Internet (the MultiNet socket library does not support the 
BSD select call, so a timed-out wait for the response message cannot be 
done).

The program is invoked as a DCL foreign command. Once it is started, it 
reads one or two parameters from the command line; the first (required) 
is the target host to interrogate (either numeric or symbolic host 
address), the second (optional) is the SNMP community name to use for 
the query (defaults to "public"). The program then connects to the 
local Internet software (the linker options file needed for VAX/VMS 
only is set up for TGV MultiNet) for a UDP connection, then starts 
sending SNMP GetNextReq packets to the specified remote host and waiting 
for the replies. The replies are formatted and displayed on standard 
output. The program continues to query until a timeout occurs (except 
under MultiNet) or until an error is returned (generally indicating no 
more SNMP object ID's known to this agent).

The utility is built in one of three ways:

  1) with TGV MultiNet:
            $ [g]CC/DEFINE=MULTINET SNMPSNOOP.C
            $ LINK SNMPSNOOP/OPTIONS

  2) with DEC UCX:
            $ [g]CC SNMPSNOOP.C
            $ LINK/EXE=SNMPSNOOP SYS$INPUT:/OPTIONS
            SNMPSNOOP,SYS$SHARE:VAXCRTL/SHAREABLE

  3) under SunOS:
            % cc snmpsnoop.c
            % mv a.out snmpsnoop

  4) presumably, under Ultrix (untested):
            % cc -Dsun snmpsnoop.c
            % mv a.out snmpsnoop

After compiling and linking under VMS, a foreign command must be defined:

            $ SNMPSNOOP :== $disk:[dir]SNMPSNOOP

Under any of the operating systems, the utility is then invoked as:

            $ SNMPSNOOP target [community-name]
            % snmpsnoop target {community-name}

This little utility is nice for testing what SNMP capability you have in 
your hosts before spending oodles of money on an SNMP-based network 
management console, and just general debugging. Beware though: some 
hosts can be VERY verbose in the quantities of replies they send back!

Andrew Pavlin
Ocean & Radar Systems Department
General Electric Co.
Syracuse, NY 13221
phone: (315) 456-1875
enail: pavlin@syr.ge.com

*/


#include <ctype.h>
#include <stdio.h>
#include <string.h>
#ifdef   sun
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#undef   MULTINET /* can't exist on a Sun */
#else    /* must be VAX */
#ifdef   MULTINET
#include "MULTINET_ROOT:[MULTINET.INCLUDE]NETDB.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE.SYS]TYPES.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE.SYS]SOCKET.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE.SYS]TIME.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE.NETINET]IN.H"
#else    /* not MultiNet, use conventional VAX C set-up */
#include <netdb.h>
#include <types.h>
#include <socket.h>
#include <time.h>
#include <in.h>
#endif
#endif

#define OBJIDLEN (40)
#define IOBUFLEN (512)

long		hostAddress (string)

char		*string;

{
	struct		hostent		*hp;
	long		host;

	host = (long) inet_addr (string);
	if (host == -1L) {
		hp = gethostbyname (string);
		if (hp == NULL) {
			return (-1);
		}
		else if (hp->h_addrtype != AF_INET) {
			return (-1);
		}
		else {
			host = 0L;
			bcopy (hp->h_addr, (char *) & host,
				sizeof (host));
		}
	}
	return (host);
}

struct ObjectID {
       unsigned char type;
       unsigned char length;
       unsigned char str[OBJIDLEN];
       };

unsigned char *buildSNMP( community, id )
   unsigned char community[];
   struct ObjectID *id;
{
   static unsigned char buf[IOBUFLEN];
   int i;

   buf[0] = 0x30;
   buf[1] = -1; /* filler for now */
   buf[2] = 0x02; /* version: INTEGER1 */
   buf[3] = 1;
   buf[4] = 0;
   buf[5] = 0x04; /* community name: OCTET STRING */
   buf[6] = strlen(community);
   strcpy(&buf[7],community);
   i = 7 + buf[6];
   buf[i++] = 0xa1; /* GetNextReq PDU */
   buf[i++] = id->length + 17;
   buf[i++] = 0x02; /* RequestID */
   buf[i++] = 1;
   buf[i++] = 0;
   buf[i++] = 0x02; /* error status */
   buf[i++] = 1;
   buf[i++] = 0;
   buf[i++] = 0x02; /* error index */
   buf[i++] = 1;
   buf[i++] = 0;
   buf[i++] = 0x30; /* VarBindList (SEQUENCE) */
   buf[i++] = id->length + 6;
   buf[i++] = 0x30; /* VarBind (SEQUENCE) */
   buf[i++] = id->length + 4;
   buf[i++] = id->type;
   buf[i++] = id->length;
   bcopy(&(id->str[0]), &buf[i], (int) id->length);
   i += id->length;
   buf[i++] = 0x04; /* null value: zero-length OCTET STRING */
   buf[i] = 0;
   buf[1] = i - 1; /* correct length */
   return buf;
}

unsigned char *eval_ASN1_length( cp, lenptr )
   unsigned char *cp;
   int *lenptr;
{
   *lenptr = 0;
   if (*cp == 0x81) {
      *cp++;
      *lenptr = 256 + (unsigned int) *cp++;
   } else if (*cp == 0x82) {
      *cp++;
      *lenptr = ((unsigned int) *cp++) << 8;
      *lenptr += (unsigned int) *cp++;
   } else if (*cp <= 0x7f) {
      *lenptr = (unsigned int) *cp++;
   } else {
      fprintf(stderr,"eval_ASN1_length: illegal length code 0x%2x\n",*cp);
   }
   return cp;
}

unsigned char *eval_ASN1( cp, typecode )
   unsigned char *cp;
   int typecode;
{
   int len, i;
   long ASNint;

   if (*cp != typecode && typecode != 0) {
      fprintf(stderr,"eval_ASN1: expected type 0x%2x, got 0x%2x\n",
              typecode, *cp);
   } else if (*cp == 0x02) { /* ASN.1 INTEGER */
      ASNint = 0;
      cp = eval_ASN1_length(++cp, &len);
      for (; len > 0; len--) {
         ASNint = (ASNint << 8) + *cp++;
      }
      printf("%d",ASNint);
   } else if (*cp == 0x04) { /* ASN.1 OCTET STRING */
      cp = eval_ASN1_length(++cp, &len);
      for (i = 0; i < len && isprint(cp[i]); i++) ;
      if (i < len) {
         for (i = 0; i < len; i++) {
            printf("%02x ",*cp++);
         }
      } else {
         putchar('\"');
         for (i = 0; i < len; i++) {
            putchar(*cp++);
         }
         putchar('\"');
      }
   } else if (*cp == 0x06) { /* ASN.1 OBJECT IDENTIFIER */
      cp = eval_ASN1_length(++cp, &len);
      if (*cp == 0x2b) { /* screwy way of doing prefix 1.3. */
         printf("1.3.");
         cp++;
         len--;
      }
      while (len > 0) {
         if (*cp <= 0x7f) {
            printf("%d",*cp++);
         } else {
            i = *cp++ & 0x7f;
            len--;
            printf("%d",(i << 7) + *cp++);
         }
         len--;
         if (len > 0)
            putchar('.');
      }
   } else if (*cp == 0x40) { /* ASN.1 IP ADDRESS */
      printf("ip ");
      cp = eval_ASN1_length(++cp, &len);
      if (len != 4) {
         fprintf(stderr,"eval_ASN1: illegal IP address length (%d instead of 4)\n",
                 len);
         cp += len;
      } else {
         while (len > 0) {
            printf("%d",*cp++);
            len--;
            if (len)
               putchar('.');
         }
      }
   } else if (*cp == 0x41) { /* ASN.1 COUNTER */
      ASNint = 0;
      cp = eval_ASN1_length(++cp, &len);
      for (; len > 0; len--) {
         ASNint = (ASNint << 8) + *cp++;
      }
      printf("cnt %d",ASNint);
   } else if (*cp == 0x42) { /* ASN.1 GAUGE */
      ASNint = 0;
      cp = eval_ASN1_length(++cp, &len);
      for (; len > 0; len--) {
         ASNint = (ASNint << 8) + *cp++;
      }
      printf("gauge %d",ASNint);
   } else if (*cp == 0x43) { /* ASN.1 TIMETICK */
      ASNint = 0;
      cp = eval_ASN1_length(++cp, &len);
      for (; len > 0; len--) {
         ASNint = (ASNint << 8) + *cp++;
      }
      printf("%d.%02d",ASNint/100,ASNint % 100);
   } else {
      fprintf(stderr,"eval_ASN1: unrecognized ASN.1 data type 0x%2x\n",
              *cp);
   }
   return cp;
}

#define COMMNAMELEN (32)

int main ( argc, argv )
   int argc;
   char *argv[];
{
   unsigned char rcvbuf[IOBUFLEN], *rp, 
                 commName[COMMNAMELEN], *rp2;
#ifdef sun
   fd_set readnfs;
#else /* VAX */
   int readnfs;
#endif
   int s, result, fromlen, len, len2;
   struct ObjectID lastobj;
   struct sockaddr_in salocal, *sin, satarget, safrom;
   struct servent *svp;
   struct timeval tmoval;
   u_long target;

   /* initialize initial Object ID */
   lastobj.type = 0x06;
   lastobj.length = 6;
   lastobj.str[0] = 0x2b;
   lastobj.str[1] = 6;
   lastobj.str[2] = 1;
   lastobj.str[3] = 2;
   lastobj.str[4] = 1;
   lastobj.str[5] = 0;

   /* decipher target address */
   if (argc <= 1) {
      fprintf(stderr,"usage: snmpsnoop target\n");
      return 1;
   }
   if (argv[1] == 0) {
      fprintf(stderr,"snmpsnoop: null target address supplied\n");
      return 1;
   }
   if ((target = hostAddress(argv[1])) == (u_long) -1) {
      fprintf(stderr,"snmpsnoop: bad target: %s\n",argv[1]);
      return 1;
   }
   bzero((char *) (& satarget), sizeof (satarget));
   satarget.sin_family = AF_INET;
   satarget.sin_addr.s_addr = target;
   if ((svp = getservbyname("snmp","udp")) == 0) {
      fprintf(stderr,"snmpsnoop: no known service snmp/udp, using default 161\n");
      satarget.sin_port = htons(161);
   } else {
      satarget.sin_port = svp->s_port;
   }

   /* decipher optional community name */
   commName[0] = '\0';
   if (argc >= 3) {
      if (argv[2] != 0) {
         strncat(commName, argv[2], COMMNAMELEN);
      }
   }
   if (commName[0] == 0) 
      strcpy(commName, "public");

   /* get a UDP socket */
   s = socket(AF_INET,SOCK_DGRAM,0);
   if (s < 0) {
      perror("snmpsnoop: couldn't create socket");
      return 1;
   }
   sin = &salocal;
   bzero((char *) sin, sizeof(salocal));
   sin->sin_family = AF_INET;
   sin->sin_addr.s_addr = (u_long) 0;
   sin->sin_port = (u_short) 0;
   result = bind(s, (struct sockaddr *) &salocal, sizeof(salocal));
   if (result < 0) {
      perror("snmpsnoop: couldn't bind socket");
      return 1;
   }

   /* DO */
   result = 1;
   do {

      /* build SNMP request message */
      rp = buildSNMP(commName, &lastobj);

      /* send SNMP request to remote node */
      result = sendto(s, (char *) rp, (int) (rp[1] + 2), 0,
                      (struct sockaddr *) &satarget, sizeof(satarget));
      if (result < (rp[1] + 2)) {
         perror("snmpsnoop: couldn't send request");
         return 2;
      }

      /* wait for reply (for a while) */
#ifndef MULTINET
      tmoval.tv_sec = 10;
      tmoval.tv_usec = 0;
#ifdef sun
      FD_ZERO(&readnfs);
      FD_SET(s, &readnfs);
#else /* VAX */
      readnfs = 1 << s;
#endif
      result = select(s+2, &readnfs, 0, 0, &tmoval);
#endif

      /* IF successful THEN */
      if (result > 0) {

         /* read the reply message */
         fromlen = sizeof(safrom);
         result = recvfrom(s, (char *) rcvbuf, sizeof(rcvbuf), 0,
                           (struct sockaddr *) &safrom, &fromlen);

         /* IF message received successfully THEN */
         if (result > 0) {

            /* print the received results */
            rp = eval_ASN1_length(&rcvbuf[1],&len);
            if (len > result - (rp - &rcvbuf[0])) {
               fprintf(stderr,"snmpsnoop: illegal message length\n");
               return 3;
            }
            if (*rp != 0x02) {
               fprintf(stderr,"snmpsnoop: expected version number, got type 0x%2x instead\n",
                       *rp);
            } else {
               rp = eval_ASN1_length(++rp,&len);
               if (len > 1 || *rp != 0) {
                  fprintf(stderr,
                          "snmpsnoop: expected version 0, got version %d instead\n",
                          *rp);
               }
               rp += len;
            }
            if (*rp != 0x04) {
               fprintf(stderr,"snmpsnoop: expected community name, got type 0x%2x instead\n",
                       *rp);
            } else {
               rp = eval_ASN1_length(++rp,&len);
               rp += len;
            }
            if (*(rp++) != 0xa2) {
               fprintf(stderr,"snmpsnoop: not GetResponse packet\n");
               return 4;
            }
            rp = eval_ASN1_length(rp,&len);
            printf("rqID=");
            rp = eval_ASN1(rp,0x02);
            printf(" err:sts=");
            rp = eval_ASN1(rp,0x02);
            if (*(rp-1) != 0) {
               result = 0;
            }
            printf(",idx=");
            rp = eval_ASN1(rp,0x02);
            if (*(rp++) != 0x30) {
               fprintf(stderr,"snmpsnoop: not valid SEQUENCE for VarBindList\n");
               return 5;
            }
            rp = eval_ASN1_length(rp,&len);
            if (*(rp++) != 0x30) {
               fprintf(stderr,"snmpsnoop: not valid SEQUENCE for VarBind\n");
               return 6;
            }
            rp = eval_ASN1_length(rp,&len2);
            if (len2 + 2 > len) {
               fprintf(stderr,"snmpsnoop: invalid response VarBind entry\n");
               return 7;
            }
            printf(" ID=");
            rp2 = rp;
            rp = eval_ASN1(rp,0x06);
            lastobj.type = *(rp2++); /* save this object ID for next pass */
            lastobj.length = *(rp2++);
            bcopy(rp2, lastobj.str, (int) lastobj.length);
            putchar(' ');
            rp = eval_ASN1(rp,0); /* any type of data for value */
            putchar('\n');

         /* ENDIF */
         }

      /* ENDIF */
      }

   /* WHILE no timeout and no SNMP errors */
   } while (result > 0);

   return 0;
}
