/* *************************************************************************
   snmpsniff.c, a promiscuous SNMP PDU sniffer.
   v.1.0
   Copyright, May 1998, Nuno Leitao (nuno.leitao@convex.pt), 
   Convex Informatica e Sistemas Portugal, Tel.: +351 1 4221040

   COPYRIGHT NOTICE:
   This code is provided *as is*, the author stands no responsability for 
   it's use. You may copy, distribute or modify this code, as long as you
   keep this copyright notice intact.
   This code uses the CMU-SNMP (v.1.10) API,  Copyright 1998 by Carnegie 
   Mellon University.
   ************************************************************************* */

/* Autoconf and portability changes by Ryan Troll <ryan+@andrew.cmu.edu */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <sys/types.h>

#ifdef HAVE_STRINGS_H
#include <strings.h>
#else /* HAVE_STRINGS_H */
#include <string.h>
#endif /* HAVE_STRINGS_H */

#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */

#include <signal.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif /* HAVE_NET_IF_H */

#ifdef HAVE_NETINET_IF_ETHER_H
#include <netinet/if_ether.h>
#endif /* HAVE_NETINET_IF_ETHER_H */

#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/udp.h>

#include <errno.h>

#include <pcap.h>		/* Packet Capture Library */
#include <snmp/snmp.h>		/* CMU SNMP Library */

#include "snmpsniff.h"

/* ----------------------------------------------------------------------- */

/* That's the end of the includes.  Now let's take care of some functions
 * that aren't available on all platforms
 *
 * -Ryan
 */

#ifndef HAVE_STRDUP
char *
strdup (char *src)
{
  char *dst;

  dst = (char *) malloc (strlen (src) + 1);
  sprintf (dst, "%s", src);
  return (dst);
}
#endif /* HAVE_STRDUP */


#ifndef HAVE_SNPRINTF

#include <stdarg.h>

/* Not as safe, but a quick hack */
int
snprintf (char *buf, int len, char *fmt,...)
{
  va_list args;
  int ret;

  va_start (args, fmt);
  ret = vsprintf (buf, fmt, args);
  va_end (args);
  return (ret);
}

#endif /* HAVE_SNPRINTF */

/* ----------------------------------------------------------------------- */

/* Different OS's define this differently.  They all mean the same thing,
 * but use different names.
 *
 * So, we use our own names, and define things based on system.
 *
 * This is really ugly.
 */

/* Linux has this include */
#ifdef HAVE_LINUX_IF_ETHER_H
#include <linux/if_ether.h>
#define EthernetHeader  struct ethhdr
#define IPHeader        struct iphdr
#define UDPHeader       struct udphdr
#define UDP_SrcPort(x) (x->source)
#define UDP_DstPort(x) (x->dest)
#define IP_SrcAddr(x)  (x->saddr)
#define IP_DstAddr(x)  (x->daddr)
#endif /* HAVE_LINUX_IF_ETHER_H */

/* The default case is Solaris. */

#ifndef EthernetHeader

#define EthernetHeader     struct ether_header
#define IPHeader           struct ip
#define UDPHeader          struct udphdr

#define UDP_SrcPort(x) (x->uh_sport)
#define UDP_DstPort(x) (x->uh_dport)
#define IP_SrcAddr(x)  (x->ip_src)
#define IP_DstAddr(x)  (x->ip_dst)

#endif /* EthernetHeader */

/* An ethernet packet */
struct etherpacket
{
  EthernetHeader eth;
  IPHeader ip;
  UDPHeader udp;

  char buff[BUFFER_SIZE];	/* For our purposes should be good enough... */
}
eth_packet;


/* The following are hooks to the positions in the struct. 
 */
IPHeader *ip;
UDPHeader *udp;

/* ----------------------------------------------------------------------- */

/* **************************************************************************
   First, here's a function to open a socket in promiscuous mode.
   ************************************************************************** */

pcap_t *
open_intf_promisc_pcap (char *intf, int snaplen, int promisc, int to_ms)
{
  pcap_t *ret;
  char ebuf[PCAP_ERRBUF_SIZE];

  ret = pcap_open_live (intf, snaplen, promisc, to_ms, ebuf);
  if (!ret)
    {
      fprintf (stderr, "Unable to open interface (%s).\n%s", intf, ebuf);
      exit (0);
    }
  return (ret);
}

pcap_t *
open_file_pcap (char *cap_file)
{
  pcap_t *ret;
  char ebuf[PCAP_ERRBUF_SIZE];

  ret = pcap_open_offline (cap_file, ebuf);
  if (!ret)
    {
      fprintf (stderr, "Unable to open file (%s).\n%s\n", cap_file, ebuf);
      exit (0);
    }
  return (ret);
}


/* **************************************************************************
   Now, a function to place the interface back in normal mode.
   ************************************************************************** */

void
close_intf_promisc_pcap (void)
{
  pcap_close (pcaphandler);
}

/* **************************************************************************
   A function to hook the anchors to their predetermined positions, they'll
   be called at the initialization.
   These variables are mere pointers to the place in the etherpacket structure 
   where IP and UDP start.
   ************************************************************************** */

void
hook_protos (void)
{
  ip = (IPHeader *) (((unsigned long) &eth_packet.ip) - 2);
  udp = (UDPHeader *) (((unsigned long) &eth_packet.udp) - 2);
}

/* **************************************************************************
   This function returns the description of a particular trap.             
   ************************************************************************** */

/* This only works for SNMPv1 Traps. -Ryan 
 */
char *
trap_description (int trap)
{
  switch (trap)
    {
    case SNMP_TRAP_COLDSTART:
      return "Cold Start";
    case SNMP_TRAP_WARMSTART:
      return "Warm Start";
    case SNMP_TRAP_LINKDOWN:
      return "Link Down";
    case SNMP_TRAP_LINKUP:
      return "Link Up";
    case SNMP_TRAP_AUTHENTICATIONFAILURE:
      return "Authentication Failure";
    case SNMP_TRAP_EGPNEIGHBORLOSS:
      return "EGP Neighbor Loss";
    case SNMP_TRAP_ENTERPRISESPECIFIC:
      return "Enterprise Specific";
    default:
      return "Unknown Type";
    }
}


/* **************************************************************************
   CMU has no sprint_type function, so we'll make one. 
   ************************************************************************** */

char *
my_snprint_type (struct variable_list *var, char *buff, int length)
{
  switch (var->type)
    {
    case SMI_INTEGER:
      snprintf (buff, length, "Integer");
      break;
    case SMI_STRING:
      snprintf (buff, length, "Octet String");
      break;
    case SMI_OPAQUE:
      snprintf (buff, length, "Opaque");
      break;
    case SMI_OBJID:
      snprintf (buff, length, "Object Identifier");
      break;
    case SMI_TIMETICKS:
      snprintf (buff, length, "Timeticks");
      break;
    case SMI_GAUGE32:
      snprintf (buff, length, "Gauge");
      break;
    case SMI_COUNTER32:
      snprintf (buff, length, "Counter");
      break;
    case SMI_IPADDRESS:
      snprintf (buff, length, "IP Address");
      break;
    case SMI_NULLOBJ:
      snprintf (buff, length, "NULL");
      break;
    default:
      snprintf (buff, length, "Unknown type %d\n", var->type);
      break;
    }
  return (buff);
}

/* **************************************************************************
   Convert an error status value to a descriptive string.
   ************************************************************************** */

/* This could use snmp_errstring() from the CMU SNMP Library.  However, the
 * strings returned by that function are rather long.
 *
 * -Ryan
 */
char *
errstatus_description (int errstat, char *buff, int length)
{
  switch (errstat)
    {
    case SNMP_ERR_NOERROR:
      snprintf (buff, length, "noError(%d)", errstat);
      break;
    case SNMP_ERR_TOOBIG:
      snprintf (buff, length, "tooBig(%d)", errstat);
      break;
    case SNMP_ERR_NOSUCHNAME:
      snprintf (buff, length, "noSuchName(%d)", errstat);
      break;
    case SNMP_ERR_BADVALUE:
      snprintf (buff, length, "badValue(%d)", errstat);
      break;
    case SNMP_ERR_READONLY:
      snprintf (buff, length, "readOnly(%d)", errstat);
      break;
    case SNMP_ERR_GENERR:
      snprintf (buff, length, "genError(%d)", errstat);
      break;
    case SNMP_ERR_NOACCESS:
      snprintf (buff, length, "noAccess(%d)", errstat);
      break;
    case SNMP_ERR_WRONGTYPE:
      snprintf (buff, length, "wrongType(%d)", errstat);
      break;
    case SNMP_ERR_WRONGLENGTH:
      snprintf (buff, length, "wrongLength(%d)", errstat);
      break;
    case SNMP_ERR_WRONGENCODING:
      snprintf (buff, length, "wrongEncoding(%d)", errstat);
      break;
    case SNMP_ERR_WRONGVALUE:
      snprintf (buff, length, "wrongValue(%d)", errstat);
      break;
    case SNMP_ERR_NOCREATION:
      snprintf (buff, length, "noCreation(%d)", errstat);
      break;
    case SNMP_ERR_INCONSISTENTVALUE:
      snprintf (buff, length, "inconsistentValue(%d)", errstat);
      break;
    case SNMP_ERR_RESOURCEUNAVAILABLE:
      snprintf (buff, length, "resourceUnavailable(%d)", errstat);
    case SNMP_ERR_COMMITFAILED:
      snprintf (buff, length, "commitFailed(%d)", errstat);
      break;
    case SNMP_ERR_UNDOFAILED:
      snprintf (buff, length, "undoFailed(%d)", errstat);
    case SNMP_ERR_AUTHORIZATIONERROR:
      snprintf (buff, length, "authorizationError(%d)", errstat);
      break;
    case SNMP_ERR_NOTWRITABLE:
      snprintf (buff, length, "notWritable(%d)", errstat);
      break;
    case SNMP_ERR_INCONSISTENTNAME:
      snprintf (buff, length, "inconsistentName(%d)", errstat);
      break;
    default:
      snprintf (buff, length, "Unknown Error(%d)", errstat);
      break;
    }
  return (buff);
}

/* **************************************************************************
   Our own function for printing variable bindings. WARNING: CMU code has 
   *A LOT* of potential dangerous code (strcpy, sprintf, etc.) -- this
   should *REALLY* be corrected. 
   ************************************************************************** */

void
my_print_variable (struct variable_list *var)
{
  char oid[512];
  char type[64];
  char value[1024];

  sprint_objid (oid, var->name, var->name_length);
  my_snprint_type (var, type, 64);
  sprint_value (value, var->name, var->name_length, var);
  printf ("<%s> (%s) = %s\n", oid, type, value);
}

/* **************************************************************************
   Print the variable bindings on a PDU.
   ************************************************************************** */

void
print_var_bindings (struct variable_list *var)
{
  printf ("Variable Bindings:\n");
  while (var)
    {
      printf ("\t");
      my_print_variable (var);
      var = var->next_variable;
    }
}

/* **************************************************************************
   Parse the incoming PDU, show all pertinent data. 
   This is the main function of the sniffer.
   ************************************************************************** */

void
parse_pdu (struct filters *user_data, const struct pcap_pkthdr *p_header,
	   const char *packet)
{
  struct snmp_pdu *pdu;
  struct sockaddr_in from;
  struct sockaddr_in to;
  struct hostent *host;
  u_char *community;
  char date_string[128];
  char from_str[128], to_str[128];
  char enterprise_txt[1024];
  char errstatus_txt[256];
  char *pdu_command_descr;
  struct tm *curr_time;
  time_t kernel_time;
  unsigned int packet_size = p_header->caplen;
  int i = 0, process_pdu = 0;
  struct variable_list *var_ptr;
  char oid[512], toid[512];
  struct oid_filt *oid_filter_pos = NULL;
  int send_result = 0;

  /* First, let's look up the time..., this might not be trivial to
   * port on BSD systems... 
   */
  kernel_time = time (NULL);
  curr_time = localtime (&kernel_time);

  /* memcpy conforms to BSD *and* SYSV, much more portable than bcopy. */
  memcpy (&eth_packet, packet, packet_size);

  /* First, let's fill in the sockaddr_in structure. */
  from.sin_family = AF_INET;
  from.sin_port = UDP_SrcPort (udp);
  to.sin_family = AF_INET;
  to.sin_port = UDP_DstPort (udp);
  memcpy ((void *) &(from.sin_addr), (void *) &(IP_SrcAddr (ip)),
	  sizeof (struct in_addr));
  memcpy ((void *) &(to.sin_addr), (void *) &(IP_DstAddr (ip)),
	  sizeof (struct in_addr));

  /* Now that we're done, let's create an empty pdu. */
  pdu = (struct snmp_pdu *) snmp_pdu_create (0);
  if (!pdu)
    {
      perror ("malloc");
      return;
    }
  memcpy (&(pdu->address), (&from), sizeof (struct in_addr));
  pdu->reqid = 0;

  /* Let's avoid a stack overun here... */
  if (!resolve_flag)
    {
      strncpy (from_str, (char *) inet_ntoa (from.sin_addr), 127);
      strncpy (to_str, (char *) inet_ntoa (to.sin_addr), 127);
    }
  else
    {
      /* If we need to, let's resolve the host names. */
      if ((host = gethostbyaddr ((char *) &from.sin_addr,
				 sizeof (from.sin_addr), AF_INET)))
	{
	  strncpy (from_str, host->h_name, 127);
	}
      if ((host = gethostbyaddr ((char *) &to.sin_addr, sizeof (to.sin_addr),
				 AF_INET)))
	{
	  strncpy (to_str, host->h_name, 127);
	}
    }

  /* Now, let's parse the PDU itself. */
  {
    /* This is needed to get snmp_parse to work. */
    struct snmp_session session;

    community = snmp_parse (&session, pdu, (u_char *) ((eth_packet.buff) - 2),
	    packet_size - sizeof (eth_packet.ip) - sizeof (eth_packet.udp));
    if (!community)
      {
	fprintf (stderr, "Unparsable packet, received from %s\n", from_str);
	snmp_free_pdu (pdu);
	return;
      }
  }

  /* Now, let's see if this PDU matches any of the filter community strings.
     If not, imeadiatly leave the function and step to the next PDU. */
  /* user_data->cf actualy is a list of strings to check. */
  while ((user_data->cf)[i])
    {
      if (!strcmp ((user_data->cf)[i++], (char *) community))
	{
	  process_pdu = 1;
	}
    }
  if (!process_pdu && (user_data->cf)[0])
    {
      snmp_free_pdu (pdu);
      free (community);
      return;
    }

  /* Now, let's try to determine if this PDU is a trap, and in that case,
     if it originates from our machine, we'll ignore it, avoiding a trap resend
     loop. */
  if (user_data->proxy_dest_on)
    {
      if (myhostaddr.sin_addr.s_addr == from.sin_addr.s_addr)
	{
	  snmp_free_pdu (pdu);
	  free (community);
	  return;
	}
    }

  /* Ok, now we can check inside the PDU to see what's inside... */
  pdu_command_descr = snmp_pdu_type (pdu);

  /* Now, let's match the PDU type with the PDU type filter. */
  if (!(user_data->pf)[pdu->command - (ASN_CONTEXT | ASN_CONSTRUCTOR)])
    {
      snmp_free_pdu (pdu);
      free (community);
      return;
    }

  /* Finally, let's see if the current packet has any var bindings in the
     the exclude list. */
  var_ptr = pdu->variables;
  i = 0;
  while (var_ptr)
    {
      if (!mib_OidToTxt (var_ptr->name, var_ptr->name_length, oid, 512))
	{
	  fprintf (stderr, "Fatal: unexpected unsolved OID.\n");
	  exit (1);
	}
      oid_filter_pos = user_data->of;
      while (oid_filter_pos)
	{
	  mib_OidToTxt (oid_filter_pos->oid_f, oid_filter_pos->oid_l, toid, 512);
	  if (strstr (oid, toid) - oid <= 0)
	    {
	      snmp_free_pdu (pdu);
	      free (community);
	      return;
	    }
	  oid_filter_pos = oid_filter_pos->next;
	}
      var_ptr = var_ptr->next_variable;
    }

  printf ("(%.2d:%.2d:%.2d) ", curr_time->tm_hour, curr_time->tm_min, curr_time->tm_sec);
  switch (pdu->command)
    {
      /* This is a bit obsolete, it's the PDU identifier for a SNMPv1
         trap, should be substituted by SNMP_PDU_V2TRAP eventualy */

    case TRP_REQ_MSG:

      ENTERPRISE2TXT (pdu->variables, enterprise_txt);

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s %s %d: %s\n", from_str, community, to_str, pdu_command_descr, pdu->specific_type, enterprise_txt);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);

	  /* Let's show the system OID (enterprise) of the trap. 
	     This identifies the type of system that sent the trap. 
	   */
	  printf ("Agent: %s (%s) sent %s trap (%d) to %s, Uptime: %s\n",
		  from_str, community, trap_description (pdu->trap_type),
		  pdu->specific_type, to_str, uptime_string (pdu->time, date_string));

	  printf ("System OID: %s\n", enterprise_txt);

	  /* Now, we'll see all the variables inside the trap. */
	  print_var_bindings (pdu->variables);

	  printf ("[/%s]\n", pdu_command_descr);
	}

      /* Are we going to proxy the trap PDU's ? If we do, let's send the packet
         to the destination. */
      if (user_data->proxy_dest_on)
	{
	  send_result = pdu_send (user_data->proxy_dest, 162, (u_char *) ((eth_packet.buff) - 2), packet_size - sizeof (eth_packet.ip) - sizeof (eth_packet.udp));
	  if (!send_result)
	    {
	      fprintf (stderr, "Unable to proxy packet.\n");
	    }
	}
      break;

      /* The following is the code to parse an SNMPv2 Trap. */
    case SNMP_PDU_V2TRAP:

      ENTERPRISE2TXT (pdu->variables, enterprise_txt);
      if (!verbose_out)
	{
	  printf ("%s(%s)->%s %s %d: %s\n", from_str, community, to_str, pdu_command_descr, pdu->specific_type, enterprise_txt);
	}
      else
	{

	  printf ("[%s]\n", pdu_command_descr);

	  printf ("Agent: %s (%s) sent %s trapv2 to %s, Uptime: %s\n",
		  from_str, community, trap_description (pdu->trap_type),
		  to_str, uptime_string (pdu->time, date_string));
	  printf ("System OID: %s\n", enterprise_txt);

	  /* Now, we'll see all the variables inside the trap. */
	  print_var_bindings (pdu->variables);

	  printf ("[/%s]\n", pdu_command_descr);
	}
      break;

    case SNMP_PDU_GET:

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s (ReqID:%d) %s: ", from_str, community, to_str, pdu->reqid, pdu_command_descr);
	  my_print_variable (pdu->variables);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);
	  printf ("Manager: %s (%s) sent get request (ReqID: %d) to %s\n",
		  from_str, community, pdu->reqid, to_str);

	  /* Now, we'll see all the variable bindings. */
	  print_var_bindings (pdu->variables);

	  printf ("[/%s]\n", pdu_command_descr);
	}
      break;

    case SNMP_PDU_GETNEXT:

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s (ReqID:%d) %s: ", from_str, community, to_str, pdu->reqid, pdu_command_descr);
	  my_print_variable (pdu->variables);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);
	  printf ("Manager: %s (%s) sent getnext request (ReqID: %d) to %s\n",
		  from_str, community, pdu->reqid, to_str);

	  /* Now, we'll see all the variable bindings. */
	  print_var_bindings (pdu->variables);

	  printf ("[/%s]\n", pdu_command_descr);
	}
      break;

    case SNMP_PDU_RESPONSE:

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s (ReqID:%d) %s (Err:%d): ", from_str, community, to_str, pdu->reqid, pdu_command_descr, pdu->errstat);
	  my_print_variable (pdu->variables);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);
	  printf ("Agent: %s (%s) sent response (ReqID: %d) to %s\n", from_str,
		  community, pdu->reqid, to_str);

	  /* Now, we'll see all the variable bindings. */

	  errstatus_description (pdu->errstat, errstatus_txt, 64);
	  printf ("\t");
	  printf ("Error Status: %s\n\t", errstatus_txt);
	  printf ("Error Index: %d\n", pdu->errindex);

	  print_var_bindings (pdu->variables);
	  printf ("[/%s]\n", pdu_command_descr);
	}
      break;

    case SNMP_PDU_SET:

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s (ReqID:%d) %s: ", from_str, community, to_str, pdu->reqid, pdu_command_descr);
	  my_print_variable (pdu->variables);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);
	  printf ("Manager: %s (%s) sent set request (ReqID: %d) to %s\n",
		  from_str, community, pdu->reqid, to_str);

	  /* Now, we'll see all the variable bindings. */
	  print_var_bindings (pdu->variables);
	  printf ("[/%s]\n", pdu_command_descr);
	}
      break;

    case SNMP_PDU_GETBULK:

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s (ReqID:%d) %s (NR:%d,MR:%d): ", from_str, community, to_str, pdu->reqid, pdu_command_descr, pdu->non_repeaters, pdu->max_repetitions);
	  my_print_variable (pdu->variables);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);
	  printf ("Manager: %s (%s) sent getbulk request (ReqID: %d) to %s\n",
		  from_str, community, pdu->reqid, to_str);

	  /* Now, we'll see all the variable bindings. */
	  printf ("\t");
	  printf ("Non Repeaters: %d\n\t", pdu->non_repeaters);
	  printf ("Max Repetitions: %d\n", pdu->max_repetitions);
	  print_var_bindings (pdu->variables);
	  printf ("[%s]\n", pdu_command_descr);
	}
      break;

    case SNMP_PDU_INFORM:

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s (ReqID:%d) %s: ", from_str, community, to_str, pdu->reqid, pdu_command_descr);
	  my_print_variable (pdu->variables);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);
	  printf ("Manager: %s (%s) sent inform (ReqID: %d) to %s\n",
		  from_str, community, pdu->reqid, to_str);

	  /* Now, we'll see all the variable bindings. */
	  print_var_bindings (pdu->variables);
	  printf ("[%s]\n", pdu_command_descr);
	}
      break;

      /* This PDU command isn't really quite defined... RFC1905
         actually doesn't defines what it's use is for. */
    case SNMP_PDU_REPORT:

      if (!verbose_out)
	{
	  printf ("%s(%s)->%s (ReqID:%d) %s: ", from_str, community, to_str, pdu->reqid, pdu_command_descr);
	  my_print_variable (pdu->variables);
	}
      else
	{
	  printf ("[%s]\n", pdu_command_descr);
	  printf ("Manager: %s (%s) sent report (ReqID: %d) to %s\n",
		  from_str, community, pdu->reqid, to_str);

	  /* Now, we'll see all the variable bindings. */
	  print_var_bindings (pdu->variables);
	  printf ("[%s]\n", pdu_command_descr);
	}
      break;

    default:
      printf ("[%s]\n", pdu_command_descr);
      break;
    }

  /* Finaly, we'll clean up a bit... */
  snmp_free_pdu (pdu);
  free (community);
}

/* **************************************************************************
   This is a generic signal handler.
   ************************************************************************** */

void
close_and_exit (int signal)
{
  if (pcap_stats ((pcap_t *) pcaphandler, (struct pcap_stat *) &pcap_intf_stats) != 0)
    {
      pcap_perror ((pcap_t *) pcaphandler, "Error:");
    }
  else
    {
      fprintf (stderr, "%u packets received by decoder\n", pcap_intf_stats.ps_recv);
      fprintf (stderr, "%u packets droped by kernel\n", pcap_intf_stats.ps_drop);
    }
  close_intf_promisc_pcap ();
  exit (0);
}

/* **************************************************************************
   The following function will define the needed BPF filter in case we use
   libpcap.
   ************************************************************************** */

void
build_bpf_filter (pcap_t * p, unsigned short snmp_port,
		  unsigned short trap_port, char *filter_expression,
		  char *cap_file)
{
  struct bpf_program program;
  bpf_u_int32 netp, maskp;
  char ebuf[PCAP_ERRBUF_SIZE];
  char bpf_program_string[1024];

  /* First, we'll get the interface net number and mask (unless we are
     reading data from a file). */
  if (!cap_file)
    {
      if (pcap_lookupnet (intf_name, &netp, &maskp, ebuf) == -1)
	{
	  fprintf (stderr, "Couldn't lookup device on interface %s\n%s\n", intf_name, ebuf);
	  exit (1);
	}
    }

  /* This is a default filter, to capture SNMP traffic. */
  snprintf (bpf_program_string, 1024, "(udp and (dst port %d or dst port %d or src port %d) %s)", ntohs (trap_port), ntohs (snmp_port), ntohs (snmp_port), filter_expression);

  /* Now, let's compile our filter into a BPF program. */
  if (pcap_compile (p, &program, bpf_program_string, 1, maskp) == -1)
    {
      fprintf (stderr, "Couldn't build BPF filter on interface %s\n%s\n", intf_name, bpf_program_string);
      exit (1);
    }

  /* Finally, we'll apply the filter to our pcap handler. */
  if (pcap_setfilter (p, &program) == -1)
    {
      fprintf (stderr, "Couldn't apply filter to interface %s\n", intf_name);
      exit (1);
    }
}

/* **************************************************************************
   Send something, to an IP/UDP port.
   This will be used for "proxying" of SNMP PDU's.
   ************************************************************************** */

int
pdu_send (struct hostent *dst_addr, short int dst_port, u_char * data, int length)
{
  int sockfd;
  struct sockaddr_in their_addr;
  int numbytes;

  if ((sockfd = socket (AF_INET, SOCK_DGRAM, 0)) == -1)
    {
      perror ("socket");
      return (0);
    }

  their_addr.sin_family = AF_INET;	/* host byte order */
  their_addr.sin_port = htons (dst_port);	/* short, network byte order */
  their_addr.sin_addr = *((struct in_addr *) dst_addr->h_addr);
  bzero (&(their_addr.sin_zero), 8);	/* zero the rest of the struct */
  if ((numbytes = sendto (sockfd, (char *) data, length, 0, (struct sockaddr *) &their_addr, sizeof (struct sockaddr))) == -1)
    {
      perror ("sendto");
      return (0);
    }
  close (sockfd);
  return (numbytes);
}

int
main (int argc, char *argv[])
{
  struct servent *servp;
  char *getoptfilter = "vri:c:e:f:l:t:d:p:";
  char *filter_expression = "";
  char ebuf[PCAP_ERRBUF_SIZE];
  char *cap_file = NULL;
  int optch, errflg;
  int packets_to_capture = -1;
  char *comm_filter[MAX_COMMF];
  int n_comm = 0;
  /* The type of PDU's is pretty much a constant... */
  int pdutype_filter[9];
  struct oid_filt *oid_filter = NULL;
  struct oid_filt *oid_filter_entry = NULL;
  struct oid_filt *oid_filter_previous = NULL;
  struct filters filt;
  int oid_filter_first = 0;
  extern int opterr;
  extern char *optarg;

  pdutype_filter[SNMP_PDU_GET - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[SNMP_PDU_GETNEXT - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[SNMP_PDU_RESPONSE - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[SNMP_PDU_SET - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[TRP_REQ_MSG - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[SNMP_PDU_GETBULK - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[SNMP_PDU_INFORM - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[SNMP_PDU_V2TRAP - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  pdutype_filter[SNMP_PDU_REPORT - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = 1;
  comm_filter[0] = NULL;
  filt.proxy_dest_on = 0;
  /* We will use getopt to parse command line options -- it is POSIX.1
     complient. If your OS isn't complient... bad luck... */
  errflg = 0;
  opterr = 0;
  while ((optch = getopt (argc, argv, getoptfilter)) != -1)
    {
      switch (optch)
	{
	case 'r':
	  resolve_flag = 1;
	  break;
	case 'i':
	  intf_name = strdup (optarg);
	  break;
	case 'c':
	  packets_to_capture = atoi (optarg);
	  if (packets_to_capture <= 0)
	    {
	      fprintf (stderr, "Bad value of packets to capture: %s\n", optarg);
	      exit (-1);
	    }
	  break;
	case 'e':
	  filter_expression = strdup (optarg);
	  break;
	case 'f':
	  cap_file = optarg;
	  break;
	case 'l':
	  if (n_comm == MAX_COMMF)
	    {
	      fprintf (stderr, "Too many communities specified.\n");
	      exit (-1);
	    }
	  comm_filter[n_comm] = calloc (1, strlen (optarg));
	  strcpy (comm_filter[n_comm++], optarg);
	  /* Just to avoid un-needed trouble, I'll terminate the list with a NULL. */
	  comm_filter[n_comm] = NULL;
	  break;
	case 't':
	  pdutype_filter[SNMP_PDU_GET - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("get", optarg) ? 0 : pdutype_filter[SNMP_PDU_GET - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[SNMP_PDU_GETNEXT - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("getnext", optarg) ? 0 : pdutype_filter[SNMP_PDU_GETNEXT - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[SNMP_PDU_RESPONSE - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("response", optarg) ? 0 : pdutype_filter[SNMP_PDU_RESPONSE - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[SNMP_PDU_SET - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("set", optarg) ? 0 : pdutype_filter[SNMP_PDU_SET - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[TRP_REQ_MSG - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("trap", optarg) ? 0 : pdutype_filter[TRP_REQ_MSG - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[SNMP_PDU_GETBULK - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("getbulk", optarg) ? 0 : pdutype_filter[SNMP_PDU_GETBULK - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[SNMP_PDU_INFORM - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("inform", optarg) ? 0 : pdutype_filter[SNMP_PDU_INFORM - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[SNMP_PDU_V2TRAP - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("trapv2", optarg) ? 0 : pdutype_filter[SNMP_PDU_V2TRAP - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  pdutype_filter[SNMP_PDU_REPORT - (ASN_CONTEXT | ASN_CONSTRUCTOR)] = !strcmp ("report", optarg) ? 0 : pdutype_filter[SNMP_PDU_REPORT - (ASN_CONTEXT | ASN_CONSTRUCTOR)];
	  break;
	case 'd':
	  oid_filter_entry = calloc (1, sizeof (struct oid_filt));
	  if (!oid_filter_first)
	    {
	      oid_filter = oid_filter_entry;
	      oid_filter_first = 1;
	    }
	  if (!mib_TxtToOid (optarg, &(oid_filter_entry->oid_f), &(oid_filter_entry->oid_l)))
	    {
	      fprintf (stderr, "Invalid OID: %s, use the numeric form instead.\n", optarg);
	      exit (-1);
	    }
	  if (oid_filter_previous)
	    {
	      oid_filter_previous->next = oid_filter_entry;
	      oid_filter_previous = oid_filter_entry;
	    }
	  else
	    oid_filter_previous = oid_filter_entry;
	  oid_filter_entry->next = NULL;
	  break;
	case 'p':
	  filt.proxy_dest_on = 1;
	  gethostname (myhostname, 79);
	  myhostaddr.sin_family = AF_INET;	/* host byte order */
	  myhostaddr.sin_port = 0;	/* short, network byte order */
	  myhostaddr.sin_addr.s_addr = INADDR_ANY;	/* auto-fill with my IP */
	  bzero (&(myhostaddr.sin_zero), 8);	/* zero the rest of the struct */
	  memcpy (&(myhostaddr.sin_addr.s_addr), gethostbyname (myhostname)->h_addr, sizeof (struct in_addr));
	  if ((filt.proxy_dest = gethostbyname (optarg)) == NULL)
	    {			/* get the host info */
	      herror ("gethostbyname");
	      exit (-1);
	    }
	  break;
	case 'v':
	  verbose_out = 1;
	  break;
	default:
	  errflg = 1;
	  break;
	}
    }

  /* Set the filter structure with the filter pointers. */
  filt.cf = comm_filter;
  filt.pf = pdutype_filter;
  filt.of = oid_filter;

  if (errflg)
    {
      fprintf (stderr, "Usage: snmpsniff [-v] [-r] [-i interface] [-f filename] [-c count] [-e expression] [-l comm1 [-l comm2 ...]] [-t pdutype1 [-t pdutype2 ...]] [-p hostname] \n(C)Convex Informatica e Sistemas, Portugal -- All disclaimers apply.\n");
      exit (1);
    }

  /* If we didn't got a suitable interface name as an argument, let's
     find one. */
  if (!intf_name && !cap_file)
    {
      intf_name = pcap_lookupdev (ebuf);
      if (!intf_name)
	{
	  fprintf (stderr, "No suitable devices found, please specify one with -i\n%s\n", ebuf);
	  exit (-1);
	}
    }

  /* Lets start by initializing the MIB for CMU, hook some protocol
     addresses in the ethernet packet data structure, install some signal
     handlers, and finaly opening the network interface in promiscuous 
     mode. */

  init_mib ();

  hook_protos ();

  signal (SIGINT, close_and_exit);
  signal (SIGTERM, close_and_exit);
  signal (SIGKILL, close_and_exit);
  signal (SIGQUIT, close_and_exit);

  if (!cap_file)
    pcaphandler = open_intf_promisc_pcap (intf_name, BUFFER_SIZE, 1, 0);
  else
    pcaphandler = open_file_pcap (cap_file);

  /* Check what's the snmp-trap port. */
  servp = getservbyname ("snmp-trap", "udp");
  servp ? (trap_port = servp->s_port) : (trap_port = htons (162));

  /* Check what's the snmp port. */
  servp = getservbyname ("snmp", "udp");
  servp ? (snmp_port = servp->s_port) : (snmp_port = htons (161));

  /* Parse and apply the default filter + any user supplied filter. */
  build_bpf_filter (pcaphandler, snmp_port, trap_port, filter_expression, cap_file);

  /* Finaly, let's handle any packet we receive. */
  if (filt.proxy_dest_on)
    {
      fprintf (stderr, "Proxying traps to %s\n", filt.proxy_dest->h_name);
    }
  if (intf_name)
    {
      fprintf (stderr, "snmpsniffer: listening on %s\n", intf_name);
    }

  /* Let's make STDOUT line buffered */
  setlinebuf(stdout);
  pcap_loop (pcaphandler, packets_to_capture, (pcap_handler) parse_pdu, (u_char *) & filt);

  if (pcap_stats ((pcap_t *) pcaphandler, (struct pcap_stat *) &pcap_intf_stats) != 0)
    {
      pcap_perror ((pcap_t *) pcaphandler, "Error:");
    }
  else
    {
      fprintf (stderr, "%u packets received by decoder\n", pcap_intf_stats.ps_recv);
      fprintf (stderr, "%u packets droped by kernel\n", pcap_intf_stats.ps_drop);
    }

  close_intf_promisc_pcap ();
}
