/* snmp.c
**
** Glen Wiley <gwiley@ieee.org>
**
** this provides the means to decompose an snmp v1 packet (pdu)
**
** NOTE: I am relying on the ucd snmp library for much of the heavy 
** lifting
**
** $Id: snmp.c,v 1.10 2001/09/07 20:59:11 mborella Exp $
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Library General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*/

#include "ipgsnmp.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>
# endif
#endif

/* these are taken from the source dist. of ucd-libsnmp */
#ifdef HAVE_ASN1_H 
# include "asn1.h"
#endif

#ifdef HAVE_SNMP_H
# include "snmp.h"
#endif 

extern struct arg_t *my_args;

#if !defined(HAVE_LIBSNMP) || !defined(HAVE_ASN1_H)
/*---------------------------------------- dump_snmp */
void
dump_snmp(packet_t *pkt)
{
	return;
} /* dump_snmp */

#else /* HAVE_LIBSNMP */

/* we pull this many bytes for the community string (max) */
#define COMMUNITY_LENGTH 40

int snmp_dumpheader(u_char **, u_int32_t *, int *);
int snmp_dumpv1pdu(u_char *, u_int32_t, int);

const int  npdutypes    = 9;
const char *pdutypes[] =
{
	  "get request"
	, "get-next request"
	, "response"
	, "set request"
	, "v1 trap"
	, "get-bulk request"
	, "v2 inform"
	, "v2 trap"
	, "report"
};

/*---------------------------------------- dump_snmp */
void
dump_snmp(packet_t *pkt)
{
	/* we will keep the storage we allocate and grow it as needed
	 * this will be more efficient than repeated malloc/free */
	static u_int8_t   *pktdata  = NULL;
	static u_int32_t  pktdatasz = 0;
	u_char            *data;
	int               snmpver   = -1;
	u_int32_t         nbytes;

  /* Set the layer */
  set_layer(LAYER_APPLICATION);

	/* get all of the remaining data from the packet so that we can
	 * pass it on to the asn parse api */

	nbytes  = get_packet_apparentbytesleft(pkt);

	if(nbytes > pktdatasz)
	{
		pktdata   = (u_int8_t *) realloc(pktdata, nbytes);
		pktdatasz = nbytes;
		if(pktdata == NULL)
		{
			fprintf(stderr, "realloc error, %d: %s", errno, strerror(errno));
			exit(1);
		}
	}

	if(get_packet_bytes(pktdata, pkt, nbytes) == 0)
	{
	  return;
	}

	/* break out the SNMP version, community and pdu */

	if (my_args->m)
	  {
	    display_minimal_string("| SNMPv");
	  }
	else
	  {
	    display_header_banner("SNMP");
	  }

	data = pktdata;
	if(snmp_dumpheader(&data, &nbytes, &snmpver) == 0)
	{
		if(snmpver == SNMP_VERSION_1 || snmpver == SNMP_VERSION_2c)
		{
			snmp_dumpv1pdu(data, nbytes, snmpver);
		}
		else if(snmpver == SNMP_VERSION_3)
		{
		}
		else
		{
			/* TODO: notify user that we can not stomach this pdu */
		}
	}
	else
	{
		/* TODO: notify the user that the pdu type is either not supported
		 * or contains format errors
		 */
	}

	return;
} /* dump_snmp */

/*---------------------------------------- snmp_dumpheader
 * parses and dumps the snmp header
 * the values pointed to by datap, nbytesp and snmpver are updated
 * as a side-effect of this function
 * return 0 on success, 1 if errors occurred in the header
 */
int
snmp_dumpheader(u_char **datap, u_int32_t *nbytesp, int *snmpver)
{
	u_char type;
	int    retval = 1;
	int    length;
	long   version;
	char   *verstr;
	char   community[COMMUNITY_LENGTH + 1];

	/*-------------------- first try to get the snmp version */

	*datap = asn_parse_sequence(*datap, nbytesp, &type
	 , (ASN_SEQUENCE | ASN_CONSTRUCTOR), "version");

	if(*datap)
	{
		*datap = asn_parse_int(*datap, nbytesp, &type, &version, sizeof(version));
		if(*datap)
		{
			*snmpver = version;
			switch(*snmpver)
			{
				case SNMP_VERSION_1:
					verstr = "1";
					break;

				case SNMP_VERSION_2c:
					verstr = "2c";
					break;

				case SNMP_VERSION_sec: /* not supported */
					verstr = "sec";
					break;

				case SNMP_VERSION_2p:
					verstr = "2p";
					break;

				case SNMP_VERSION_2star: /* not supported */
					verstr = "2star";
					break;

				case SNMP_VERSION_2u: /* not supported */
					verstr = "2u";
					break;

				case SNMP_VERSION_3:
					verstr = "3";
					break;

				default:
					verstr = "<invalid>";
					*snmpver = -1;
					break;
			}
			/* get community */
		} /* if(*datap) */
		else
		{
			verstr = "<invalid>";
		}

		if(*snmpver != -1)
			retval = 0;

		if (my_args->m)
		  {
		    display_minimal_string(verstr);
		    display_minimal_string(" ");
		  }
		else
		  {
		    display_string("version", verstr);
		  }

	} /* if(*datap) */

	/*-------------------- get community if version is ok */
	/* TODO: community only shows up here for some versions */

	if(*snmpver == SNMP_VERSION_1 || *snmpver == SNMP_VERSION_2c)
	{
		length = COMMUNITY_LENGTH;
		*datap = asn_parse_string(*datap, nbytesp, &type, community, &length);

		if(*datap)
		{
			community[length] = '\0';
		}
		else
		{
			retval = 1;
			strcpy(community, "<indeterminate>");
		}

		if (my_args->m)
		  {
		    display_minimal_string(community);
		    display_minimal_string(" ");
		  }
		else
		  {
		    display_string("community", community);
		  }
	}

	return retval;
} /* snmp_dumpheader */

/*---------------------------------------- snmp_dumpv1pdu
 * return 0 on success
 */
int 
snmp_dumpv1pdu(u_char *data, u_int32_t nbytes, int snmpver)
{
	int retval = 0;

	return retval;
} /* snmp_dumpv1pdu */

#endif /* HAVE_LIBSNMP */

/* snmp.c */
