/* 1435, Wed 3 Jun 98

   SNMPAPI.C:  Socket interface library for building SNMP clients
            NeTraMet version, based on CMU SNMPv2 (see below).

   Copyright (C) 1992-2002 by Nevil Brownlee,
   CAIDA | University of Auckland */

/*
 * $Log: snmpapi.c,v $
 * Revision 1.1.1.3.2.9  2002/02/23 01:57:39  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.3.2.5  2000/08/08 19:44:58  nevil
 * 44b8 release
 *
 * Revision 1.1.1.3.2.3  2000/06/06 03:38:28  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.3  1999/10/03 23:23:32  nevil
 * Continue after a recvfrom error.  Frank Strauss, 4 Oct 99
 *
 * Revision 1.1.1.2  1999/10/03 21:06:31  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.4  1999/09/28 01:32:28  nevil
 * Prevent Alpha 'unaligned access' messages: add Counter64 filler to
 *    the internal_variable_list struct so as to align its data buffer.
 *
 * Revision 1.1.1.1.2.3  1999/09/24 02:58:43  nevil
 * Polish up code to get rid of warning messages from Borland (DOS) compiler.
 * Make manager PeerAddress buffers NSAP_ADDR_LEN bytes long.
 * Add asn_lookup variable - only call FindSubnet if ruleset uses ASNs.
 *
 * Revision 1.1.1.1.2.2  1999/09/22 05:38:47  nevil
 * Improve code to work properly on 64-bit machines
 * - Add OS=ALPHA handling to configure.in
 * - Clean up the Alpha compiler warnings
 * - Change all the snmp-related code to use Bit32 instead of unsigned long
 *
 * Revision 1.1.1.1.2.1  1999/01/08 01:38:38  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1  1998/11/16 03:57:32  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1  1998/11/16 03:22:02  nevil
 * Import of release 4.3b3
 *
 * Revision 1.1.1.1  1998/10/28 20:31:31  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2.2.3  1998/10/27 04:39:18  nevil
 * 4.3b1 release
 *
 * Revision 1.1.3.2.2.2  1998/10/22 01:28:09  nevil
 * Community data wasn't being copied when PDUs were cloned, which gave
 * unpredictable results when the cloned pdu was freed.  Added code to
 * copy community data properly.  Also added PDU_MALLOC_CHECK define
 * to trace malloc() and Free() operations on pdus.
 *
 * Revision 1.1.3.2.2.1  1998/10/19 02:30:39  nevil
 * Use log_msg() to report errors instead of fprintf(stderr ..)
 *
 * Revision 1.1.3.2  1998/10/14 04:13:47  nevil
 * Merge Nicolai's patches into 4.2.1 distribution
 *
 * Revision 1.1.3.1  1998/10/13 02:48:57  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1.1.1  1998/08/24 12:09:30  nguba
 * NetraMet 4.2 Original Distribution
 *
 * Revision 1.5  1998/08/25 17:00:00 russell
 * Fix memory leak in snmp_free_pdu(community)
 *
 * Revision 1.4  1998/06/10 21:04:57  rtfm
 * Remove global variable 'Now' (never used)
 *
 * Revision 1.3  1998/06/03 04:42:38  rtfm
 * Include select.h (for snmpapi.h)
 * Fix arithmetic errors in computing select timeouts
 * Flush stdout after packet dumps
 */

/******************************************************************
	Copyright 1989, 1991, 1992 by Carnegie Mellon University

                      All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the name of CMU not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  

CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
******************************************************************/

#if HAVE_CONFIG_H
#include <ntm_conf.h>
#endif

/*
 * snmp_api.c - API for access to snmp.
 */

#define TIME_DUMPS        0
#define TEST_TIMEOUTS     0
#define TESTING           0
#define PDU_MALLOC_CHECK  0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <stdarg.h>

#ifdef DOS
#include <time.h>
#include <bios.h>
#include <mem.h>
#include "tcp.h"
#include <sys\timeb.h>
#endif

#ifndef DOS
#include <sys/param.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/socket.h>
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <netdb.h>
#endif

#include "ausnmp.h"

#include "asn1.h"
#include "snmp.h"
#include "snmpimpl.h"
#include "snmpapi.h"

#if PDU_MALLOC_CHECK
char mc_buf[64];
#endif

#if TIME_DUMPS
void print_time()
{
   time_t t;  char *ts, ftb[9];
   struct timeval tv;
   time(&t);  ts = ctime(&t);
   strncpy(ftb,  &ts[11], 8);  /* 17:31:42 */
   ftb[8] = '\0';
   gettimeofday(&tv, (struct timezone *)0);
   printf("%s, tv=(%lu,%lu),  ", ftb, tv.tv_sec,tv.tv_usec);
   }   
#endif

#if defined(DOS)
void gettimeofday(struct timeval *tp, struct timezone *tzp)
{
   struct timeb buf;
   ftime(&buf);
   tp->tv_sec = buf.time;
   tp->tv_usec = buf.millitm*1000L;
   }
#endif

#if defined(DOS) || defined(CYGWIN32)
#define	timerisset(tvp)		((tvp)->tv_sec || (tvp)->tv_usec)
#define	timercmp(tvp, uvp, cmp)	\
	((tvp)->tv_sec cmp (uvp)->tv_sec || \
	 (tvp)->tv_sec == (uvp)->tv_sec && (tvp)->tv_usec cmp (uvp)->tv_usec)
#define	timerclear(tvp)		(tvp)->tv_sec = (tvp)->tv_usec = 0
#endif

#define PACKET_LENGTH	8000  /* Was 4500 !!! Nevil */

oid default_enterprise[] = {1, 3, 6, 1, 4, 1, 3, 1, 1};
/* enterprises.cmu.systems.cmuSNMP */

#define DEFAULT_COMMUNITY   "public"
#if 0  /* CMU values */
#define DEFAULT_RETRIES	    4
#define DEFAULT_TIMEOUT	    1000000L
#else  /* UA values to cope with 5s rtts */
#define DEFAULT_RETRIES	    4
#define DEFAULT_TIMEOUT	    2500000L
#endif
#define DEFAULT_REMPORT	    SNMP_PORT
#define DEFAULT_ENTERPRISE  default_enterprise
#define DEFAULT_TIME	    0

/*
 * Internal information about the state of the snmp session.
 */
struct snmp_internal_session {
#ifdef DOS
   udp_Socket sock;  /* Socket for this connection */
#else
   int	    sd;	     /* Socket descriptor for this connection */
#endif
   ipaddr  addr;     /* Address of connected peer */
   struct request_list *requests;/* Info about outstanding requests */
   struct request_list *requestsEnd; /* Pointer to end of list */
   };

/*
 * A list of all the outstanding requests for a particular session.
 */
struct request_list {
   struct request_list *next_request;
   Bit32 request_id;	   /* request id */
   int retries;  	   /* Number of retries */
   Bit32 timeout;         /* length to wait for timeout */
   struct timeval time;    /* Time this request was made */
   struct timeval expire;  /* time this request is due to expire */
   struct snmp_pdu *pdu;   /* The PDU for this request
			       (saved so it can be retransmitted */
   };

struct internal_variable_list {
   struct variable_list *next_variable;  /* NULL for last variable */
   oid *name;              /* Object identifier of variable */
   int name_length;        /* Number of subid's in name */
   u_char type;            /* ASN type of variable */
   union {                 /* Value of variable */
      Int32 *integer;
      Bit32 *u_int;
      u_char *string;
      oid *objid;
      u_char *bitstring;
      counter64 *counter64;
      } val;
   int val_len;
   oid name_loc[MAX_NAME_LEN];
#if ALPHA
   counter64 filler;  /* Prevents 'unaligned access' messages when we
                           read Counter64 objects from buf (below) */
#endif
   u_char buf[32];
   int usedBuf;
   };

struct internal_snmp_pdu {
   int version;
   int pdu_len;            /* Nevil, 7 Jun 00 */
   ipaddr address;         /* Address of peer */
   u_char *community;      /* Community for outgoing requests. */
   int community_len;      /* Length of community name. */

   int command;            /* Type of this PDU */

   Int32 reqid;            /* Request id */
   Int32 errstat;          /* Error status (non_repeaters in GetBulk) */
   Int32 errindex;         /* Error index (max_repetitions in GetBulk) */

   /* Trap information */
   oid *enterprise;        /* System OID */
   int enterprise_length;
   ipaddr agent_addr;      /* Address of object generating trap */
   Bit32 trap_type;        /* Trap type */
   Bit32 specific_type;    /* Specific type */
   Bit32 time;             /* Uptime */

   struct variable_list *variables;

/* The remaining variables in internal_snmp_pdu support things like
   authentication.  The original CMU v2 variables (implementing
   party-based authentication) have been removed.  One day we should
   implement v3 user-based authentication.          Nevil, Oct 98 */
   };

/*
 * The list of active/open sessions.
 */
struct session_list {
   struct session_list *next;
   struct snmp_session *session;
   struct snmp_internal_session *internal;
   };

struct session_list *Sessions = NULL;

static char *api_errstring(int snmp_errnumber);
static void init_snmp(void);
static void free_request_list(struct request_list *rp);
static int snmp_build(struct snmp_session *session,
   struct snmp_pdu *pdu, u_char *packet, int *out_length);
static int snmp_parse(struct snmp_session *session,
   struct internal_snmp_pdu *pdu, u_char *data, int length);

Bit32 Reqid = 0;
int snmp_errno = 0;

char *api_errors[4] = {
   "Unknown session",
   "Unknown host",
   "Invalid local port",
   "Unknown Error"
   };

struct snmp_pdu *SavedPdu = NULL;
struct internal_variable_list *SavedVars = NULL;

static char *api_errstring(int snmp_errnumber)
{
   if (snmp_errnumber <= SNMPERR_BAD_SESSION
         && snmp_errnumber >= SNMPERR_GENERR) {
      return api_errors[snmp_errnumber + 4];
      }
   else {
      return "Unknown Error";
      }
   }


/*
 * Gets initial request ID for all transactions.
 */
static void init_snmp(void)
{
#ifdef DOS
   srand(time(NULL) % 37);
   Reqid = rand();
#else
   struct timeval tv;

   gettimeofday(&tv, (struct timezone *)0);
   srandom(tv.tv_sec ^ tv.tv_usec);
   Reqid = random();
#endif
   }

/*
 * Sets up the session with the snmp_session information provided
 * by the user.  Then opens and binds the necessary UDP port.
 * A handle to the created session is returned (this is different than
 * the pointer passed to snmp_open()).  On any error, NULL is returned
 * and snmp_errno is set to the appropriate error code.
 */
struct snmp_session *snmp_open(struct snmp_session *session)
{
   struct session_list *slp;
   struct snmp_internal_session *isp;
   u_char *cp;
   oid *op;
   Bit32 addr;
#ifdef DOS
   udp_Socket sock;
   u_short rem_port;
#else
   int sd;
   struct sockaddr_in	me;
   struct hostent *hp;
   struct servent *servp;
#endif

   if (Reqid == 0)
      init_snmp();

   /* Copy session structure and link into list */
   slp = (struct session_list *)malloc(sizeof(struct session_list));
   slp->internal = isp = (struct snmp_internal_session *)malloc(
      sizeof(struct snmp_internal_session));
   memset((char *)isp, 0, sizeof(struct snmp_internal_session));
#ifndef DOS
   slp->internal->sd = -1; /* mark it not set */
#endif
   slp->session = (struct snmp_session *)malloc(sizeof(struct snmp_session));
   memcpy((char *)slp->session, (char *)session, sizeof(struct snmp_session));
   session = slp->session;
   /* now link it in. */
   slp->next = Sessions;
   Sessions = slp;
   /*
    * session now points to the new structure that still contains
    * pointers to data allocated elsewhere.  Some of this data is
    * copied to space malloc'd here, and the pointer replaced with
    * the new one.
    */
   if (session->peername != NULL) {
      cp = (u_char *)malloc((unsigned)strlen(session->peername) + 1);
      strcpy((char *)cp, session->peername);
      session->peername = (char *)cp;
      }

   /* Fill in defaults if necessary */
   if (session->community_len != SNMP_DEFAULT_COMMUNITY_LEN) {
      cp = (u_char *)malloc((unsigned)session->community_len);
      memcpy((char *)cp, (char *)session->community, session->community_len);
      }
   else {
      session->community_len = strlen(DEFAULT_COMMUNITY);
      cp = (u_char *)malloc((unsigned)session->community_len);
      memcpy((char *)cp, (char *)DEFAULT_COMMUNITY, session->community_len);
      }
   session->community = cp;  /* Replace pointer with pointer to new data */

   if (session->retries == SNMP_DEFAULT_RETRIES)
      session->retries = DEFAULT_RETRIES;
   if (session->timeout == SNMP_DEFAULT_TIMEOUT)
      session->timeout = DEFAULT_TIMEOUT;
   isp->requests = isp->requestsEnd = NULL;

   /* Set up connections */
#ifdef DOS
   if (session->peername != SNMP_DEFAULT_PEERNAME) {
      if (!(addr = resolve(session->peername))) {
	 log_msg(LOG_ERR, 0, "Unknown host: %s\n", session->peername);
	 snmp_errno = SNMPERR_BAD_ADDRESS;
	 if (!snmp_close(session))
	    log_msg(LOG_ERR, 2, "Couldn't abort session: %s. Exiting\n",
               api_errstring(snmp_errno));
	 return 0;
	 }
      }
   rem_port = session->remote_port == SNMP_DEFAULT_REMPORT ?
      SNMP_PORT : session->remote_port;
   if (!udp_open( &isp->sock, 0, addr, rem_port, NULL )) {
      log_msg(LOG_ERR, 0,"Unable to connect to %s!", session->peername);
      return 0;
      }
   isp->addr.s_ip = addr;
   isp->addr.s_port = rem_port;
#else
   sd = socket(AF_INET, SOCK_DGRAM, 0);
   if (sd < 0) {
      /* Apr 98: Russell Fulton deleted a big block of code below
                 here, which had been accidentally duplicated! 
                 Its effect was to cause NeMaC to fail by running
                 out of file handles */
      perror("socket");
      snmp_errno = SNMPERR_GENERR;
      if (!snmp_close(session))
	 log_msg(LOG_ERR, 1, "Couldn't abort session: %s. Exiting\n",
	    api_errstring(snmp_errno));
      return 0;
      }
   isp->sd = sd;
   if (session->peername != SNMP_DEFAULT_PEERNAME) {
      if ((addr = inet_addr(session->peername)) != -1) {
	 memcpy((char *)&isp->addr.sin_addr, (char *)&addr, 
            sizeof(isp->addr.sin_addr));
         }
      else {
	 hp = gethostbyname(session->peername);
	 if (hp == NULL) {
	    log_msg(LOG_ERR, 0, "Unknown host: %s\n", session->peername);
	    snmp_errno = SNMPERR_BAD_ADDRESS;
	    if (!snmp_close(session))
	       log_msg(LOG_ERR, 2, "Couldn't abort session: %s. Exiting\n",
		  api_errstring(snmp_errno));
	    return 0;
	    }
         else memcpy((char *)&isp->addr.sin_addr, (char *)hp->h_addr, 
            hp->h_length);
         }
      isp->addr.sin_family = AF_INET;
      if (session->remote_port == SNMP_DEFAULT_REMPORT) {
	 servp = getservbyname("snmp", "udp");
	 if (servp != NULL) {
	    isp->addr.sin_port = servp->s_port;
	    }
         else {
	    isp->addr.sin_port = htons(SNMP_PORT);
	    }
	 }
      else {
	 isp->addr.sin_port = htons(session->remote_port);
	 }
      }
   else {
      isp->addr.sin_addr.s_addr = SNMP_DEFAULT_ADDRESS;
      }

   me.sin_family = AF_INET;
   me.sin_addr.s_addr = INADDR_ANY;
   me.sin_port = htons(session->local_port);
   if (bind(sd, (struct sockaddr *)&me, sizeof(me)) != 0) {
      perror("bind");
      snmp_errno = SNMPERR_BAD_LOCPORT;
      if (!snmp_close(session))
	 log_msg(LOG_ERR, 3, "Couldn't abort session: %s. Exiting\n",
	    api_errstring(snmp_errno));
      return 0;
      }
#endif
   return session;
   }


/*
 * Free each element in the input request list.
 */
static void free_request_list(struct request_list *rp)
{
   struct request_list *orp;
   while (rp) {
      orp = rp;
      rp = rp->next_request;
      if (orp->pdu != NULL)
	 snmp_free_pdu(orp->pdu);
      free((char *)orp);
      }
   }

/*
 * Close the input session.  Frees all data allocated for the session,
 * dequeues any pending requests, and closes any sockets allocated for
 * the session.  Returns 0 on error, 1 otherwise.
 */
int snmp_close(struct snmp_session *session)
{
   struct session_list *slp, *oslp = NULL;

   if (Sessions->session == session) {	/* If first entry */
      slp = Sessions;
      Sessions = slp->next;
      }
   else {
      for (slp = Sessions; slp; slp = slp->next) {
	 if (slp->session == session) {
	    if (oslp)  /* If we found entry that points here */
	       oslp->next = slp->next;	/* Link around this entry */
	    break;
	    }
	 oslp = slp;
	 }
      }
   /* If we found the session, free all data associated with it */
   if (slp) {
      if (slp->session->community != NULL)
	 free((char *)slp->session->community);
      if (slp->session->peername != NULL)
	 free((char *)slp->session->peername);
      free((char *)slp->session);
#ifdef DOS
      sock_close(&slp->internal->sock);
#else
      if (slp->internal->sd != -1)
	 close(slp->internal->sd);
#endif
      free_request_list(slp->internal->requests);
      free((char *)slp->internal);
      free((char *)slp);
      }
   else {
      snmp_errno = SNMPERR_BAD_SESSION;
      return 0;
      }
   return 1;
   }

void shift_array(u_char *begin, int length, int shift_amount)
{
   u_char     *old, *new;

   if (shift_amount >= 0) {
      old = begin + length - 1;
      new = old + shift_amount;

      while (length--)
         *new-- = *old--;
      }
   else {
     old = begin;
     new = begin + shift_amount;

     while (length--)
        *new++ = *old++;
     }
   }

/*
 * Takes a session and a pdu and serializes the ASN PDU into the area
 * pointed to by packet.  out_length is the size of the data area
 * available.  Returns the length of the completed packet in
 * out_length.  If any errors occur, -1 is returned.  If all goes
 * well, 0 is returned.
 */
static int snmp_build(
   struct snmp_session *session,
   struct snmp_pdu *pdu,
   u_char *packet,
   int *out_length)
{
   u_char  *authEnd, *h1, *h1e, *h2;
   u_char  *cp;
   struct variable_list *vp;
   struct  packet_info pkt;
   int length, packet_length, header_length;
#if TESTING > 1
   int j;
#endif

#if TESTING
   printf("snmp_build(1): entry\n");
#endif
   if (session->version == SNMP_VERSION_2C ||
         session->version == SNMP_VERSION_1) {
      cp = snmp_auth_build(packet, out_length, pdu->community,
            &pdu->community_len, &pdu->version, 0);
      if (cp == NULL)
         return -1;
      }
   else {
      return -1;
      }
   authEnd = cp;
#if TESTING
   printf("snmp_build(2): snmp_auth_build OK\n");
#endif

   h1 = cp;
   if (session->version == SNMP_VERSION_1)
      cp = asn_build_header(cp, out_length, (u_char)pdu->command, 0);
   else
      cp = asn_build_sequence(cp, out_length, (u_char)pdu->command, 0);
   if (cp == NULL)
      return -1;
   h1e = cp;
#if TESTING
   printf("snmp_build(3): asn_build_header OK\n");
#endif
    
   if (pdu->command != TRP_REQ_MSG) {
      /* request id */
      cp = asn_build_int(cp, out_length,
         (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
         (Bit32 far *)&pdu->reqid, sizeof(pdu->reqid));
      if (cp == NULL)
         return -1;
      /* error status */
      cp = asn_build_int(cp, out_length,
         (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
         (Bit32 far *)&pdu->errstat, sizeof(pdu->errstat));
      if (cp == NULL)
         return -1;
      /* error index */
      cp = asn_build_int(cp, out_length,
         (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
         (Bit32 far *)&pdu->errindex, sizeof(pdu->errindex));
      if (cp == NULL)
         return -1;
      }
   else {    /* this is a trap message */
      /* enterprise */
      cp = asn_build_objid(cp, out_length,
         (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID),
         (oid *)pdu->enterprise, pdu->enterprise_length);
      if (cp == NULL)
         return -1;
      /* agent-addr */
      cp = asn_build_string(cp, out_length,
         (u_char)(IPADDRESS | ASN_PRIMITIVE),
#ifdef DOS
         (u_char *)&pdu->agent_addr.s_ip,
	 sizeof(pdu->agent_addr.s_ip));
#else
         (u_char *)&pdu->agent_addr.sin_addr.s_addr,
         sizeof(pdu->agent_addr.sin_addr.s_addr));
#endif
      if (cp == NULL)
         return -1;
      /* generic trap */
      cp = asn_build_int(cp, out_length,
         (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
         &pdu->trap_type, sizeof(pdu->trap_type));
      if (cp == NULL)
         return -1;
      /* specific trap */
      cp = asn_build_int(cp, out_length,
         (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
         &pdu->specific_type, sizeof(pdu->specific_type));
      if (cp == NULL)
         return -1;
      /* timestamp  */
      cp = asn_build_unsigned_int(cp, out_length,
         (u_char)(TIMETICKS | ASN_PRIMITIVE),
         &pdu->time, sizeof(pdu->time));
      if (cp == NULL)
         return -1;
      }

   h2 = cp;
   cp = asn_build_sequence(cp, out_length,
         (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), 0);
   if (cp == NULL)
      return -1;

   for (vp = pdu->variables; vp; vp = vp->next_variable) {
#if TESTING > 1
      printf("snmp_build(4): ");
      for (j = 0; j != (int)vp->name_length; ++j) printf(".%d",vp->name[j]);
      printf("\n");
#endif
      cp = snmp_build_var_op(cp, vp->name, &vp->name_length, vp->type,
         vp->val_len, (u_char *)vp->val.string, out_length);
      if (cp == NULL)
         return -1;
      }

   length = PACKET_LENGTH;
   asn_build_sequence(h2, &length,
      (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR),  (cp - h2) - 4);
   if (session->version == SNMP_VERSION_1) {
      if ((cp - h1e) < 0x80)
	 header_length = 2;
      else if ((cp - h1e) <= 0xFF)
	 header_length = 3;
      else
         header_length = 4;
      shift_array(h1e, cp - h1e, header_length + h1 - h1e);
      asn_build_header(h1, &length, (u_char)pdu->command, cp - h1e);
      cp += header_length + h1 - h1e;
      }
   else {
      asn_build_sequence(h1, &length, (u_char)pdu->command, (cp - h1) - 4);
      }
    
   if (session->version == SNMP_VERSION_2C ||
         session->version == SNMP_VERSION_1) {
      snmp_auth_build(packet, &length, pdu->community,
         &pdu->community_len, &pdu->version, cp - authEnd);
      }
   *out_length = cp - packet;
   return 0;
   }

/*
 * Parses the packet recieved on the input session, and places the
 * data into the input pdu.  length is the length of the input packet.
 * If any errors are encountered, -1 is returned.  Otherwise, a 0 is
 * returned.
 */
static int snmp_parse(
   struct snmp_session *session,
   struct internal_snmp_pdu *pdu,
   u_char *data,
   int length)
{
   u_char msg_type;
   u_char type;
   struct packet_info pkt;
   u_char *var_val;
   int version;
   int len, four;
   u_char community[128];
   int community_length = 128;
   struct internal_variable_list *vp;
   oid objid[MAX_NAME_LEN], *op;
   Bit32 dummy;

   len = length;
   (void)asn_parse_header(data, &len, &type);

   if (type == (ASN_SEQUENCE | ASN_CONSTRUCTOR)) {
      if (session->version != SNMP_VERSION_2C &&
            session->version != SNMP_VERSION_1)
	 return -1;
      /* authenticates message and returns length if valid */
      data = snmp_auth_parse(data, &length,
         community, &community_length, &version);
      if (data == NULL)
	 return -1;
      pdu->community_len = community_length;
      pdu->community = (u_char *)malloc(community_length);
      memcpy(pdu->community, community, community_length);
      if (session->authenticator) {
	 data = session->authenticator(data, &length,
	    community, community_length);
	 if (data == NULL)
	    return 0;
	 }
      }
   else {
      ERROR("unknown auth header type");
      return 0;
      }
   pdu->version = version;

   data = asn_parse_header(data, &length, &msg_type);
   if (data == NULL)
      return -1;

   pdu->command = msg_type;
   if (pdu->command != TRP_REQ_MSG) {
      data = asn_parse_int(data, &length, &type,
         (Bit32 far *)&pdu->reqid, sizeof(pdu->reqid));
      if (data == NULL)
	 return -1;
      data = asn_parse_int(data, &length, &type,
         (Bit32 far *)&pdu->errstat, sizeof(pdu->errstat));
      if (data == NULL)
	 return -1;
      data = asn_parse_int(data, &length, &type,
         (Bit32 far *)&pdu->errindex, sizeof(pdu->errindex));
      if (data == NULL)
	 return -1;
      }
   else {
      pdu->enterprise_length = MAX_NAME_LEN;
      data = asn_parse_objid(data, &length, &type, objid,
	 &pdu->enterprise_length);
      if (data == NULL)
	 return -1;
      pdu->enterprise = (oid *)malloc(pdu->enterprise_length * sizeof(oid));
      memcpy((char *)pdu->enterprise, (char *)objid, 
         pdu->enterprise_length * sizeof(oid));

      four = 4;
#ifdef DOS
      data = asn_parse_string(data, &length, &type,
         (u_char *)&pdu->agent_addr, &four);
#else
      data = asn_parse_string(data, &length, &type,
	 (u_char *)&pdu->agent_addr.sin_addr.s_addr, &four);
#endif
      if (data == NULL)
	 return -1;
      data = asn_parse_int(data, &length, &type, 
         (Bit32 far *)&dummy, sizeof dummy);
      if (data == NULL)
	 return -1;
      pdu->trap_type = dummy;
      data = asn_parse_int(data, &length, &type,
         (Bit32 far *)&dummy, sizeof dummy);
      if (data == NULL)
	 return -1;
      pdu->specific_type = dummy;
      data = asn_parse_unsigned_int(data, &length, &type,
         &pdu->time, sizeof(pdu->time));
      if (data == NULL)
	 return -1;
      }
   data = asn_parse_header(data, &length, &type);
   if (data == NULL)
      return -1;
   if (type != (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR))
      return -1;
   while ((int)length > 0) {
      if (pdu->variables == NULL) {
	 if (SavedVars) {
	    pdu->variables = (struct variable_list *)SavedVars;
	    vp = SavedVars;
	    SavedVars =
	       (struct internal_variable_list *)SavedVars->next_variable;
	    }
         else {
	    vp = (struct internal_variable_list *)
	       malloc(sizeof(struct internal_variable_list));
	    pdu->variables = (struct variable_list *)vp;
	    }
         }
      else {
	 if (SavedVars) {
	    vp->next_variable = (struct variable_list *)SavedVars;
	    SavedVars =
	       (struct internal_variable_list *)SavedVars->next_variable;
	    vp = (struct internal_variable_list *)vp->next_variable;
	    }
         else {
            vp->next_variable = (struct variable_list *)
               malloc(sizeof(struct internal_variable_list));
	    vp = (struct internal_variable_list *)vp->next_variable;
	    }
         }
      vp->next_variable = NULL;
      vp->val.string = NULL;
      vp->name = NULL;
      vp->name_length = MAX_NAME_LEN;
      vp->name = vp->name_loc;
      vp->usedBuf = FALSE;
      data = snmp_parse_var_op(data, vp->name, &vp->name_length, &vp->type,
         &vp->val_len, &var_val, (int *)&length);
      if (data == NULL)
         return -1;
      len = PACKET_LENGTH;
      switch((short)vp->type) {
      case ASN_INTEGER:
	 vp->val.integer = (Int32 *)vp->buf;
	 vp->usedBuf = TRUE;
	 vp->val_len = sizeof(Int32);
	 asn_parse_int(var_val, &len, &vp->type, 
            (Bit32 far *)vp->val.integer, sizeof(*vp->val.integer));
	 break;
      case COUNTER:
      case GAUGE:
      case TIMETICKS:
      case UINTEGER:
	 vp->val.u_int = (Bit32 *)vp->buf;
	 vp->usedBuf = TRUE;
	 vp->val_len = sizeof(Bit32);
	 asn_parse_unsigned_int(var_val, &len, &vp->type,
            (Bit32 far *)vp->val.u_int, sizeof(*vp->val.u_int));
	 break;
      case COUNTER64:
	 vp->val.counter64 = (counter64 *)vp->buf;
	 vp->usedBuf = TRUE;
	 vp->val_len = sizeof(counter64);
	 asn_parse_unsigned_int64(var_val, &len, &vp->type,
	    (counter64 *)vp->val.counter64, sizeof(*vp->val.counter64));
	 break;
      case ASN_OCTET_STR:
      case IPADDRESS:
      case OPAQUE:
      case NSAP:
	    if (vp->val_len < 32) {
	       vp->val.string = (u_char *)vp->buf;
            vp->usedBuf = TRUE;
	    }
         else {
	    vp->val.string = (u_char *)malloc((unsigned)vp->val_len);
	    }
	 asn_parse_string(var_val, &len, &vp->type,
            vp->val.string, &vp->val_len);
	 break;
      case ASN_OBJECT_ID:
	 vp->val_len = MAX_NAME_LEN;
	 asn_parse_objid(var_val, &len, &vp->type, objid, &vp->val_len);
	 vp->val_len *= sizeof(oid);
	 vp->val.objid = (oid *)malloc((unsigned)vp->val_len);
	 memcpy((char *)vp->val.objid, (char *)objid, vp->val_len);
	 break;
      case SNMP_NOSUCHOBJECT:
      case SNMP_NOSUCHINSTANCE:
      case SNMP_ENDOFMIBVIEW:
      case ASN_NULL:
	 break;
      case ASN_BIT_STR:
       	 vp->val.bitstring = (u_char *)malloc(vp->val_len);
	 asn_parse_bitstring(var_val, &len, &vp->type,
	    vp->val.bitstring, &vp->val_len);
	 break;
      default:
	 log_msg(LOG_ERR, 0, "Bad type returned (%x)\n", vp->type);
	 break;
         }
      }
   return 0;
   }

/*
 * Sends the input pdu on the session after calling snmp_build to
 * create a serialized packet.  If necessary, set some of the pdu data
 * from the session defaults.  Add a request corresponding to this pdu
 * to the list of outstanding requests on this session, then send the
 * pdu.  Returns the request id of the generated packet if applicable,
 * otherwise 1.  On any error, 0 is returned.  The pdu is freed by
 * snmp_send() unless a failure occured.
 */
int snmp_send(struct snmp_session *session, struct snmp_pdu *pdu)
{
   struct session_list *slp;
   struct snmp_internal_session *isp = NULL;
   u_char packet[PACKET_LENGTH];
   int length = PACKET_LENGTH;
   struct request_list *rp;
   struct timeval tv;

   for (slp = Sessions; slp; slp = slp->next) {
      if (slp->session == session) {
	 isp = slp->internal;
	 break;
	 }
      }
   if (isp == NULL) {
      snmp_errno = SNMPERR_BAD_SESSION;
      return 0;
      }
#if PDU_MALLOC_CHECK
   printf("api:send(1): session=%lu, pdu=%lu, slp=%lu\n",
      session, pdu, slp);
#endif
   if (pdu->command == GET_REQ_MSG || pdu->command == GETNEXT_REQ_MSG ||
         pdu->command == GET_RSP_MSG || pdu->command == SET_REQ_MSG) {
      if (pdu->reqid == SNMP_DEFAULT_REQID)
	 pdu->reqid = ++Reqid;
      if (pdu->errstat == SNMP_DEFAULT_ERRSTAT)
	 pdu->errstat = 0;
      if (pdu->errindex == SNMP_DEFAULT_ERRINDEX)
	 pdu->errindex = 0;
      }
   else if (pdu->command == INFORM_REQ_MSG || pdu->command == TRP2_REQ_MSG) {
      if (session->version != SNMP_VERSION_2C) {
	 log_msg(LOG_ERR, 0, "Cant send SNMP PDU's in SNMPv2C message.\n");
	 snmp_errno = SNMPERR_GENERR;  /* Fix this XXXXX */
	 return 0;
	 }
      if (pdu->reqid == SNMP_DEFAULT_REQID)
	 pdu->reqid = ++Reqid;
      if (pdu->errstat == SNMP_DEFAULT_ERRSTAT)
	 pdu->errstat = 0;
      if (pdu->errindex == SNMP_DEFAULT_ERRINDEX)
	 pdu->errindex = 0;
      }
    else if (pdu->command == BULK_REQ_MSG) {
      if (pdu->reqid == SNMP_DEFAULT_REQID)
	 pdu->reqid = ++Reqid;
      if (pdu->max_repetitions < 0 || pdu->non_repeaters < 0) {
	 log_msg(LOG_ERR, 0,
            "Invalid parameters for max_repetitions or non_repeaters\n");
	 snmp_errno = SNMPERR_GENERR;	/* Fix this XXXXX */
	 return 0;
	 }
      }
   else {
      if (session->version == SNMP_VERSION_2C) {
	 log_msg(LOG_ERR, 0, "Cant send old Trap PDU in SNMP2vC message.\n");
	 snmp_errno = SNMPERR_GENERR;  /* Fix this XXXXX */
	 return 0;
	 }
      /* fill in trap defaults */
      pdu->reqid = 1;	/* give a bogus non-error reqid for traps */
      if (pdu->enterprise_length == SNMP_DEFAULT_ENTERPRISE_LENGTH) {
	 pdu->enterprise = (oid *)malloc(sizeof(DEFAULT_ENTERPRISE));
	 memcpy((char *)pdu->enterprise, (char *)DEFAULT_ENTERPRISE, 
            sizeof(DEFAULT_ENTERPRISE));
	 pdu->enterprise_length = sizeof(DEFAULT_ENTERPRISE)/sizeof(oid);
	 }
      if (pdu->time == SNMP_DEFAULT_TIME)
	 pdu->time = DEFAULT_TIME;
      }
#ifdef DOS
   if (pdu->address.s_ip == SNMP_DEFAULT_ADDRESS) {
      if (isp->addr.s_ip != SNMP_DEFAULT_ADDRESS) {
#else
   if (pdu->address.sin_addr.s_addr == SNMP_DEFAULT_ADDRESS) {
      if (isp->addr.sin_addr.s_addr != SNMP_DEFAULT_ADDRESS) {
#endif
	 memcpy((char *)&pdu->address, (char *)&isp->addr, 
            sizeof(pdu->address));
	 }
      else {
	 log_msg(LOG_ERR, 0, "No remote IP address specified\n");
	 snmp_errno = SNMPERR_BAD_ADDRESS;
	 return 0;
	 }
      }
	
   /* !!!!!!!!!!!!!!!!!!!!!! MAJOR PROBLEM  !!!!!!!!!!!!!!!!!!!!!!!
    * This stuff needs to be cleanly added to the api.
    * currently some applications are passing non-malloc'd data.
    * we can't free this stuff because they would get hosed.
    * Therefore this is a core leak.
    * !!!!!!!!!!!!!!!!!!!!!! MAJOR PROBLEM  !!!!!!!!!!!!!!!!!!!!!!!
    */
   if (pdu->version == SNMP_DEFAULT_VERSION) {
      pdu->version = session->version;
      }
   if (pdu->version == SNMP_DEFAULT_VERSION) {
      log_msg(LOG_ERR, 0, "No version specified\n");
      snmp_errno = SNMPERR_BAD_ADDRESS;
      return 0;
      }
   else if (pdu->version == SNMP_VERSION_2C ||
         pdu->version == SNMP_VERSION_1) {
      if (pdu->community_len == 0) {
	 if (session->community_len == 0) {
            log_msg(LOG_ERR, 0, "No community name specified\n");
	    snmp_errno = SNMPERR_BAD_ADDRESS;
	    return 0;
	    }
         pdu->community = (u_char *)malloc(session->community_len);
	 memcpy((char *)pdu->community, (char *)session->community,
	    session->community_len);
	 pdu->community_len = session->community_len;
#if PDU_MALLOC_CHECK
         memcpy(mc_buf, pdu->community, pdu->community_len);
         mc_buf[pdu->community_len] = '\0';
         printf("api:send(2): pdu=%lu, pdu->community=%lu, pdu[%s]\n",
            pdu, pdu->community, mc_buf);
#endif
	 }
      }

   if (snmp_build(session, pdu, packet, &length) < 0) {
      log_msg(LOG_ERR, 0, "Error building packet (send)\n");
      snmp_errno = SNMPERR_GENERR;
      return 0;
      }
   if (snmp_dump_packet) {
#if TIME_DUMPS
      print_time();
#endif
      printf("sending %d bytes to %s:\n", length,
#ifdef DOS
         session->peername);
#else
         inet_ntoa(pdu->address.sin_addr));
#endif
      xdump(packet, length, "");
      printf("\n\n");
      fflush(stdout);
      }


#ifdef DOS
    sock_write(&isp->sock, (char *)packet, length);
#else
   if (sendto(isp->sd, (char *)packet, length, 0,
         (struct sockaddr *)&pdu->address, sizeof(pdu->address)) < 0) {
      perror("sendto");
      snmp_errno = SNMPERR_GENERR;
      return 0;
      }
#endif
   gettimeofday(&tv, (struct timezone *)0);
#if TEST_TIMEOUTS
      printf(" *** snmp_send(1): tv=(%lu,%lu)\n",
         tv.tv_sec,tv.tv_usec);
#endif
   if (pdu->command == GET_REQ_MSG || pdu->command == GETNEXT_REQ_MSG ||
         pdu->command == SET_REQ_MSG || pdu->command == BULK_REQ_MSG ||
	 pdu->command == INFORM_REQ_MSG) {
      /* set up to expect a response */
      rp = (struct request_list *)malloc(sizeof(struct request_list));
      if (isp->requestsEnd) {
	 rp->next_request = isp->requestsEnd->next_request;
	 isp->requestsEnd->next_request = rp;
	 isp->requestsEnd = rp;
	 }
      else {
	 rp->next_request = isp->requests;
	 isp->requests = rp;
	 isp->requestsEnd = rp;
	 }
      rp->pdu = pdu;
      rp->request_id = pdu->reqid;

#if TEST_TIMEOUTS
      printf(" *** snmp_send(2): tv=(%lu,%lu), timeout=%lu\n",
         tv.tv_sec,tv.tv_usec, session->timeout);
#endif
      rp->retries = 1;
      rp->timeout = session->timeout;
      rp->time = tv;
      tv.tv_usec += rp->timeout;
      tv.tv_sec += tv.tv_usec / 1000000L;
      tv.tv_usec %= 1000000L;
      rp->expire = tv;
#if PDU_MALLOC_CHECK
      memcpy(mc_buf, pdu->community, pdu->community_len);
      mc_buf[pdu->community_len] = '\0';
      printf("api:send(3): rp=%lu, reqid=%lu, pdu=%lu,\n"
            "                pdu->community=%lu, pdu[%s]\n",
         rp, pdu->reqid, pdu, pdu->community, mc_buf);
#endif
#if TEST_TIMEOUTS
      printf(" *** snmp_send(3): rp=%lu, time=(%lu,%lu), expire=(%lu,%lu)\n",
         rp, rp->time.tv_sec,rp->time.tv_usec,
         rp->expire.tv_sec,rp->expire.tv_usec);
#endif
      }
   return pdu->reqid;
   }

/*
 * Frees the pdu and any malloc'd data associated with it.
 */
void snmp_free_pdu(struct snmp_pdu *pdu)
{
   struct variable_list *vp, *ovp;

   vp = pdu->variables;
   while (vp) {
      if (vp->name)
	 free((char *)vp->name);
      if (vp->val.string)
	 free((char *)vp->val.string);
      ovp = vp;
      vp = vp->next_variable;
      free((char *)ovp);
      }
   if (pdu->enterprise)
      free((char *)pdu->enterprise);
#if PDU_MALLOC_CHECK
   memcpy(mc_buf, pdu->community, pdu->community_len);
   mc_buf[pdu->community_len] = '\0';
   printf("api:free_pdu(): pdu=%lu, pdu->community=%lu, pdu[%s]\n",
      pdu, pdu->community, mc_buf);
#endif
   if (pdu->community)  /* RF memory leak fix, 25 Aug 98 */
      free((char *)pdu->community);
   free((char *)pdu);
   }


/*
 * Frees the pdu and any malloc'd data associated with it.
 */
void snmp_free_internal_pdu(struct snmp_pdu *pdu)
{
   struct internal_variable_list *vp, *ovp;

   vp = (struct internal_variable_list *)pdu->variables;
   while (vp) {
      if (vp->val.string && !vp->usedBuf)
	 free((char *)vp->val.string);
      ovp = vp;
      vp = (struct internal_variable_list *)vp->next_variable;
      ovp->next_variable = (struct variable_list *)SavedVars;
      SavedVars = ovp;
      }
   if (pdu->enterprise)
      free((char *)pdu->enterprise);
#if PDU_MALLOC_CHECK
   memcpy(mc_buf, pdu->community, pdu->community_len);
   mc_buf[pdu->community_len] = '\0';
   printf(
      "api:free_internal_pdu(): pdu=%lu, pdu->community=%lu, pdu[%s]\n",
      pdu, pdu->community, mc_buf);
#endif
   if (pdu->community)  /* RF memory leak fix, 25 Aug 98 */
      free((char *)pdu->community);
   if (!SavedPdu)
      SavedPdu = pdu;
   else
      free((char *)pdu);
   }


/*
 * Checks to see if any of the fd's set in the fdset belong to snmp.
 * Each socket with it's fd set has a packet read from it and
 * snmp_parse is called on the packet received.  The resulting pdu is
 * passed to the callback routine for that session.  If the callback
 * routine returns successfully, the pdu and it's request are deleted.
 */
void snmp_read(fd_set *fdset)
{
   struct session_list *slp;
   struct snmp_session *sp;
   struct snmp_internal_session *isp;
   u_char packet[PACKET_LENGTH];
#ifndef DOS
   struct sockaddr_in from;
   int fromlength;
#endif
   int length;
   struct snmp_pdu *pdu;
   struct request_list *rp, *orp;

   for (slp = Sessions; slp; slp = slp->next) {
#ifdef DOS
      if ((length = sock_dataready(&slp->internal->sock)) != 0) {
#else
      if (FD_ISSET(slp->internal->sd, fdset)) {
#endif
	 sp = slp->session;
	 isp = slp->internal;
#ifdef DOS
	 length = length > PACKET_LENGTH ? PACKET_LENGTH : length;
	 sock_read(&slp->internal->sock, (char *)packet, length);
#else
	 fromlength = sizeof from;
	 length = recvfrom(isp->sd, (char *)packet, PACKET_LENGTH, 0,
	    (struct sockaddr *)&from, &fromlength);
#endif
 	 if (length == -1) {
  	    perror("recvfrom");
 	    continue;
            /* Without this continue statement, this function continues
 	       with a pdu never received and e.g. makes NeMaC dump core
 	       if it tries to reach a meter that is not running and leads
               to `recvfrom: Connection refused'.
 			 (strauss@escape.de 03-Oct-1999). */
 	    }

	 if (snmp_dump_packet) {
#if TIME_DUMPS
            print_time();
#endif
	    printf("received %d bytes from %s:\n", length,
#ifdef DOS
               sp->peername);
#else
	       inet_ntoa(from.sin_addr));
#endif
	    xdump(packet, length, "");
            printf("\n\n");
            fflush(stdout);
	    }

#if PDU_MALLOC_CHECK
         printf("api:read(1): slp=%lu, SavedPdu=%lu\n",
            slp, SavedPdu);
#endif
         if (SavedPdu) {
	    pdu = SavedPdu;
	    SavedPdu = NULL;
	    }
         else {
	    pdu = (struct snmp_pdu *)malloc(sizeof(struct internal_snmp_pdu));
	    }
#ifdef DOS
         pdu->address = slp->internal->addr;
#else
         pdu->address = from;
#endif
         pdu->pdu_len = length;  /* Nevil, 7 Jun 00 */
 	 pdu->reqid = 0;
	 pdu->variables = NULL;
	 pdu->enterprise = NULL;
	 pdu->enterprise_length = 0;
	 if (snmp_parse(sp, (struct internal_snmp_pdu *)pdu,  /* !!!!! Nevil */
               packet, length) != SNMP_ERR_NOERROR) {
	    log_msg(LOG_ERR, 0,
               "Unrecognizable or unauthentic packet received\n");
	    snmp_free_internal_pdu(pdu);
	    return;
	    }

#if PDU_MALLOC_CHECK
         memcpy(mc_buf, pdu->community, pdu->community_len);
         mc_buf[pdu->community_len] = '\0';
         printf("api:read(2): pdu=%lu, pdu->community=%lu, pdu[%s]\n",
            pdu, pdu->community, mc_buf);
#endif
         if (pdu->command == GET_RSP_MSG) {
	    for (rp = isp->requests; rp; rp = rp->next_request) {
	       if (rp->request_id == pdu->reqid) {
		  if (sp->callback(RECEIVED_MESSAGE, sp, pdu->reqid,
		        pdu, sp->callback_magic) == 1) {
		     /* successful, so delete request */
#if PDU_MALLOC_CHECK
      memcpy(mc_buf, pdu->community, pdu->community_len);
      mc_buf[pdu->community_len] = '\0';
      printf("api:read(3): rp=%lu, reqid=%lu, pdu=%lu,\n"
           "                community=%lu, pdu[%s]\n",
         rp, rp->request_id, pdu, pdu->community, mc_buf);
#endif
		     orp = rp;
		     if (isp->requests == orp) {
			/* first in list */
			isp->requests = orp->next_request;
			if (isp->requestsEnd == orp)
			isp->requestsEnd = NULL;
			}
                     else {
			for (rp = isp->requests; rp; rp = rp->next_request) {
			   if (rp->next_request == orp) {
			      if (isp->requestsEnd == orp)
			           isp->requestsEnd = rp;
			      /* check logic ^^^: 
				    is this the new "end"? XXX */
			      /* link around it */
			      rp->next_request = orp->next_request;
			      break;
			      }
			   }
                        }
		     snmp_free_pdu(orp->pdu);
		     free((char *)orp);
		     /* there shouldn't be any more requests with the
			       same reqid */
		     break;
		     }
		  }
	       }
	    }
         else if (pdu->command == GET_REQ_MSG
	       || pdu->command == GETNEXT_REQ_MSG
	       || pdu->command == TRP_REQ_MSG
	       || pdu->command == SET_REQ_MSG
	       || pdu->command == BULK_REQ_MSG
	       || pdu->command == INFORM_REQ_MSG
	       || pdu->command == TRP2_REQ_MSG) {
	    sp->callback(RECEIVED_MESSAGE, sp, pdu->reqid, pdu,
	       sp->callback_magic);
	    }
#if PDU_MALLOC_CHECK
         memcpy(mc_buf, pdu->community, pdu->community_len);
         mc_buf[pdu->community_len] = '\0';
         printf("api:read(4): pdu=%lu, SavedPdu=%lu\n", pdu, SavedPdu);
#endif
	 snmp_free_internal_pdu(pdu);
         }
      }
   }

/*
 * Returns info about what snmp requires from a select statement.
 * numfds is the number of fds in the list that are significant.  All
 * file descriptors opened for SNMP are OR'd into the fdset.  If
 * activity occurs on any of these file descriptors, snmp_read should
 * be called with that file descriptor set
 *
 * The timeout is the latest time that SNMP can wait for a timeout.
 * The select should be done with the minimum time between timeout and
 * any other timeouts necessary.  This should be checked upon each
 * invocation of select.  If a timeout is received, snmp_timeout
 * should be called to check if the timeout was for SNMP.
 * (snmp_timeout is idempotent)
 *
 * Block is 1 if the select is requested to block indefinitely, rather
 * than time out.  If block is input as 1, the timeout value will be
 * treated as undefined, but it must be available for setting in
 * snmp_select_info.  On return, if block is true, the value of
 * timeout will be undefined.
 *
 * snmp_select_info returns the number of open sockets.  (i.e. The
 * number of sessions open)
 */
int snmp_select_info(
   int    *numfds,
   fd_set *fdset,
   struct timeval *timeout,
   int	  *block)  /* Should the select block until input arrives
		       (i.e. no input) */
{
   struct session_list *slp;
   struct snmp_internal_session *isp;
   struct request_list *rp;
   struct timeval now, earliest;
   int active = 0, requests = 0;

   timerclear(&earliest);
   /*
    * For each request outstanding, add it's socket to the fdset,
    * and if it is the earliest timeout to expire, mark it as lowest.
    */
   for (slp = Sessions; slp; slp = slp->next) {
      active++;
      isp = slp->internal;
#ifndef DOS
      if ((isp->sd + 1) > *numfds)
	 *numfds = (isp->sd + 1);
      FD_SET(isp->sd, fdset);
#endif
      if (isp->requests) {
	 /* Found another session with outstanding requests */
	 requests++;
	 for (rp = isp->requests; rp; rp = rp->next_request) {
#if TEST_TIMEOUTS
            printf(" *** select_info(1): rp=%lu, expire=(%lu,%lu)\n",
               rp, rp->expire.tv_sec,rp->expire.tv_usec);
#endif
	    if (!timerisset(&earliest) ||
	           timercmp(&rp->expire, &earliest, <))
		earliest = rp->expire;
	    }
	 }
      }
   if (requests == 0)	/* If none are active, skip arithmetic */
      return active;

   /*
    * Now find out how much time until the earliest timeout.  This
    * transforms earliest from an absolute time into a delta time, the
    * time left until the select should timeout.
    */
   gettimeofday(&now, (struct timezone *)0);
#if TEST_TIMEOUTS
   printf(" *** select_info(2): earliest=(%lu,%lu), now=(%lu,%lu)\n",
      earliest.tv_sec,earliest.tv_usec,
      now.tv_sec,now.tv_usec);
#endif
#if 0
   earliest.tv_sec--;	/* Adjust time to make arithmetic easier */
   earliest.tv_usec += 1000000L;
   earliest.tv_sec -= now.tv_sec;
   earliest.tv_usec -= now.tv_usec;
   while (earliest.tv_usec >= 1000000L) {
      earliest.tv_usec -= 1000000L;
      earliest.tv_sec += 1;
      }
#endif
   earliest.tv_sec -= now.tv_sec;
   earliest.tv_usec -= now.tv_usec;
   while (earliest.tv_usec < 0) {
      earliest.tv_usec += 1000000L;  
      --earliest.tv_sec;
      }
   if (earliest.tv_sec < 0) {
      earliest.tv_sec = 0;
      earliest.tv_usec = 0;
      }
#if TEST_TIMEOUTS
   printf(" *** select_info(3): time-to-earliest=(%lu,%lu)\n",
      earliest.tv_sec,earliest.tv_usec);
#endif

   /* If it was blocking before or our delta time is less, reset timeout */
   if (*block == 1 || timercmp(&earliest, timeout, <)) {
      *timeout = earliest;
      *block = 0;
      }
   return active;
   }

/*
 * snmp_timeout should be called whenever the timeout from
 * snmp_select_info expires, but it is idempotent, so snmp_timeout can
 * be polled (probably a cpu expensive proposition).  snmp_timeout
 * checks to see if any of the sessions have an outstanding request
 * that has timed out.  If it finds one (or more), and that pdu has
 * more retries available, a new packet is formed from the pdu and is
 * resent.  If there are no more retries available, the callback for
 * the session is used to alert the user of the timeout.
 */
void snmp_timeout(void)
{
   struct session_list *slp;
   struct snmp_session *sp;
   struct snmp_internal_session *isp;
   struct request_list *rp, *orp, *freeme = NULL;
   struct timeval now;

   gettimeofday(&now, (struct timezone *)0);
   /*
    * For each request outstanding, check to see if it has expired.
    */
   for (slp = Sessions; slp; slp = slp->next) {
      sp = slp->session;
      isp = slp->internal;
      orp = NULL;
      for (rp = isp->requests; rp; rp = rp->next_request) {
	 if (freeme != NULL) {
	    /* Frees rp's after the for loop goes on to the next_request */
	    free((char *)freeme);
	    freeme = NULL;
	    }
         if (timercmp(&rp->expire, &now, <)) {
	    /* This timer has expired */
#if TEST_TIMEOUTS
            printf(" *** snmp_timeout(): rp=%lu, expire=(%lu,%lu)\n",
               rp, rp->expire.tv_sec,rp->expire.tv_usec);
#endif
	    if (rp->retries >= sp->retries) {
	       /* No more chances, delete this entry */
		sp->callback(TIMED_OUT, sp, rp->pdu->reqid, rp->pdu,
		   sp->callback_magic);
	        if (orp == NULL) {
		   isp->requests = rp->next_request;
		   if (isp->requestsEnd == rp)
		      isp->requestsEnd = NULL;
		   }
               else {
		   orp->next_request = rp->next_request;
		   if (isp->requestsEnd == rp)
		      isp->requestsEnd = rp->next_request;
		   /* check logic ^^^: is this the new "end"? XXX */
		   }
               snmp_free_pdu(rp->pdu);
	       freeme = rp;
	       continue;  /* Don't update orp below */
	       }
            else {
	       u_char  packet[PACKET_LENGTH];
	       int length = PACKET_LENGTH;
	       struct timeval tv;

	       /* retransmit this pdu */
	       rp->retries++;
	       if (rp->retries > 3)
		  rp->timeout <<= 1;
	       if (rp->timeout > 30000000L)
		  rp->timeout = 30000000L;
	       if (snmp_build(sp, rp->pdu, packet, &length) < 0)
		  log_msg(LOG_ERR, 0, "Error building packet (timeout)\n");
	       if (snmp_dump_packet) {
#if TIME_DUMPS
                  print_time();
#endif
		  printf("re-sending %d bytes to %s:\n", length,
#ifdef DOS
                     sp->peername);
#else
		     inet_ntoa(rp->pdu->address.sin_addr));
#endif
		  xdump(packet, length, "");
		  printf("\n\n");
                  fflush(stdout);
		  }
#ifdef DOS
               sock_write(&isp->sock, (char *)packet, length);
#else
	       if (sendto(isp->sd, (char *)packet, length, 0,
		     (struct sockaddr *)&rp->pdu->address,
		     sizeof(rp->pdu->address)) < 0) {
		  perror("sendto");
		  }
#endif
	       tv = now;
	       rp->time = tv;
	       tv.tv_usec += rp->timeout;
	       tv.tv_sec += tv.tv_usec / 1000000L;
	       tv.tv_usec %= 1000000L;
	       rp->expire = tv;
	       }
            }
	 orp = rp;
	 }
      if (freeme != NULL) {
	 free((char *)freeme);
	 freeme = NULL;
	 }
      }
   }
