/* 1645, Tue 8 Oct 96

   SNMPAGNT.C:  Routines to implement SNMP, i.e. parse an SNMP packet,
                   perform the requested operations,
	           and build a proper SNMP response packet.
            NeTraMet version, based on CMU SNMPv2 (see below).

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

/*
 * $Log: snmpagnt.c,v $
 * Revision 1.1.1.2.2.8  2002/02/23 01:57:38  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.4  2000/06/06 03:38:27  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2.2.1  1999/11/29 00:17:29  nevil
 * Make changes to support NetBSD on an Alpha (see version.history for details)
 *
 * Revision 1.1.1.2  1999/10/03 21:06:31  nevil
 * *** empty log message ***
 *
 * 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.2  1998/10/22 01:28:08  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:38  nevil
 * Use log_msg() to report errors instead of fprintf(stderr ..)
 *
 * Revision 1.1.3.2  1998/10/14 04:13:46  nevil
 * Merge Nicolai's patches into 4.2.1 distribution
 *
 * Revision 1.1.1.1  1998/10/13 01:35:01  nevil
 * Import of NeTraMet 4.2.1
 *
 * Revision 1.1.1.1  1998/08/24 12:09:30  nguba
 * NetraMet 4.2 Original Distribution
 *
 * Revision 1.3  1998/06/03 04:42:38  rtfm
 * Include select.h (for snmpapi.h)
 */

/***********************************************************
	Copyright 1988, 1989 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

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#if !defined(DOS)
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#endif

#define TESTING     0
#define NEVIL_TEST  0

#include "ausnmp.h"
#include "asn1.h"
#include "snmp.h"
#include "snmpimpl.h"
#include "mib.h"

#if defined(DOS) && !defined(OCX)
void snmp_error(char *msg);   /* In netramet\meter_pc */
/* This warning usually means that an SNMPv1 MIB browser has tried
   to read a MIB-1 system.* variable which NeTraMet doesn't implement.
   meter_pc displays the MIB browser IP address, as well as a
   text message.  This makes the MIB browser easier to find!
   (This patch added Mar 98 to get rid of unix compiler warnings) */
#endif

void setVariable(u_char *var_val, u_char var_val_type, int var_val_len,
   u_char far *statP, int statLen);

int parse_var_op_list(u_char *data, int length,
   u_char *out_data, int out_length, Bit32 *index,
   struct packet_info *pi, int action);
int bulk_var_op_list(u_char *data, int length,
   u_char *out_data, int out_length,
   int non_repeaters, int max_repetitions, Bit32 *index,
   struct packet_info *pi);
int create_response(u_char *snmp_in, u_char *snmp_out,
   int snmp_length, Bit32 errstat, Bit32 errindex,
   struct packet_info *pi);
int snmp_access(u_short acl, int community, int rw);
int get_community(u_char *community, int community_len);
int goodValue(u_char inType, int inLen,
   u_char actualType, int actualLen);

#define NUM_COMMUNITIES	5
char *communities[NUM_COMMUNITIES] = {
/*   "public", "private", "regional", "proxy", "core"  Changed in CMU v2 */
   "public", "proxy", "private", "regional", "core"
      /* NeTraMet sticks with the original (CMU v1) order !! */
   };

#if TESTING
void sn_db(unsigned char *buf, char *msg)
{
   int j;
   printf("%02x",buf[0]);
   for (j = 1; j != 16; ++j) printf(" %02x",buf[j]);
   printf("\n%02x",buf[16]);
   for (j = 17; j != 32; ++j) printf(" %02x",buf[j]);
   printf("  %s\n", msg);
   }
#endif

int snmp_agent_parse(
   u_char *data,
   int     length,
   u_char *out_data,
   int    *out_length,
   Bit32   sourceip)  /* Possibly for authentication */
{
   struct packet_info packet, *pi = &packet;
   u_char   type;
   Bit32 zero = 0;
   Bit32 reqid, errstat, errindex, dummyindex;
   u_char   *out_auth, *out_header, *out_reqid;
   u_char   *startData = data;
   int	    startLength = length;
   int	    startOutLength = *out_length;
   int	    header_shift, auth_shift;
   int	    packet_len, len;

#if TESTING
   u_char *buf = out_data;
   for (type = 0; type != 32; ++type) buf[type] = 0;
#endif
   len = length;
   (void)asn_parse_header(data, &len, &type);

   if (type == (ASN_SEQUENCE | ASN_CONSTRUCTOR)) {
      /* Authenticates message and returns length if valid */
      pi->community_len = COMMUNITY_MAX_LEN;
      data = snmp_auth_parse(data, &length,
         pi->community, &pi->community_len, &pi->version);
      }
   else {
      ERROR("unknown auth header type");
      return 0;
      }

   if (data == NULL) {
      ERROR("bad authentication");
      /* Send auth fail trap */
      return 0;
      }
   if (pi->version == SNMP_VERSION_2C || pi->version == SNMP_VERSION_1) {
      pi->community_id = get_community(pi->community, pi->community_len);
      if (pi->community_id == -1)
         return 0;
      }
   else {
      ERROR("Bad Version");
      return 0;
      }
   data = asn_parse_header(data, &length, &pi->pdutype);
   if (data == NULL) {
      ERROR("bad header");
      return 0;
      }
   if (pi->pdutype != GET_REQ_MSG && pi->pdutype != GETNEXT_REQ_MSG
         && pi->pdutype != SET_REQ_MSG && pi->pdutype != BULK_REQ_MSG) {
      return 0;
      }

   /*
    * Now creaate the auth_header for the output packet.  The final
    * lengths are not known now, so they will have to be recomputed
    * later
    */
   out_auth = out_data;
   out_header = snmp_auth_build(out_auth, out_length,
      pi->community, &pi->community_len, &pi->version, 0);
   if (out_header == NULL) {
      ERROR("snmp_auth_build failed");
      return 0;
      }

   data = asn_parse_int(data, &length, &type, &reqid, sizeof(reqid));
   if (data == NULL) {
      ERROR("bad parse of reqid");
      return 0;
      }
   data = asn_parse_int(data, &length, &type, &errstat, sizeof(errstat));
   if (data == NULL) {
      ERROR("bad parse of errstat");
      return 0;
      }
   data = asn_parse_int(data, &length, &type, &errindex, sizeof(errindex));
   if (data == NULL) {
      ERROR("bad parse of errindex");
      return 0;
      }
#if TESTING
   printf("snmp_agent_parse(): reqid=%x, errstat=%d, errindex=%d\n",
      reqid,errstat,errindex);
#endif

   /* Create the requid, errstatus, errindex for the output packet */
   out_reqid = asn_build_sequence(  /* Always uses 4 bytes */
      out_header, out_length, (u_char)GET_RSP_MSG, 0);
   if (out_reqid == NULL) {
      ERROR("couldn.t create out_reqid");
      return 0;
      }

   type = (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER);
   /* Return identical request id */
   out_data = asn_build_int(out_reqid, out_length,
      type, &reqid, sizeof(reqid));
   if (out_data == NULL) {
      ERROR("build reqid failed");
      return 0;
      }

   /* Assume that error status will be zero */
   out_data = asn_build_int(out_data, out_length, type, &zero, sizeof(zero));
   if (out_data == NULL) {
      ERROR("build errstat failed");
      return 0;
      }

   /* Assume that error index will be zero */
   out_data = asn_build_int(out_data, out_length, type, &zero, sizeof(zero));
   if (out_data == NULL) {
      ERROR("build errindex failed");
      return 0;
      }

#if TESTING
   printf("snmp_agent_parse(): about to call parse_var_op_list\n");
#endif
   if (pi->pdutype == BULK_REQ_MSG)
      errstat = bulk_var_op_list(data, length, out_data, *out_length,
         errstat, errindex, &errindex, pi);
   else
      errstat = parse_var_op_list(data, length, out_data, *out_length,
         &errindex, pi, RESERVE1);
   if (pi->pdutype == SET_REQ_MSG) {
      if (errstat == SNMP_ERR_NOERROR)
	 errstat = parse_var_op_list(data, length, out_data, *out_length,
	    &errindex, pi, RESERVE2);
      if (errstat == SNMP_ERR_NOERROR) {
         /*
	  * SETS require 3-4 passes through the var_op_list.  The
	  * first two passes verify that all types, lengths, and
	  * values are valid and may reserve resources and the third
	  * does the set and a fourth executes any actions.  Then the
	  * identical GET RESPONSE packet is returned.
          *
	  * If either of the first two passes returns an error,
	  * another pass is made so that any reserved resources can be
	  * freed.  
          */
	 parse_var_op_list(data, length, out_data, *out_length,
				&dummyindex, pi, COMMIT);
	 parse_var_op_list(data, length, out_data, *out_length,
				&dummyindex, pi, ACTION);
	 if (create_response(
               startData, out_auth, startOutLength, 0L, 0L, pi)) {
	    *out_length = pi->packet_end - out_auth;
	    return 1;
	    }
         return 0;
	 }
      else {
         parse_var_op_list(data, length, out_data, *out_length,
				&dummyindex, pi, FREE);
         }
      }
#if TESTING
   printf("errstat=%lu, _auth=%p,  hdr=%p, _rqid=%p, _data=%p\n",
       errstat, out_auth, out_header, out_reqid, out_data);
#endif
   switch((short)errstat) {
   case SNMP_ERR_NOERROR:
      /* Re-encode the headers with the real lengths */
      *out_length = pi->packet_end - out_header;
      out_data = asn_build_sequence(  /* Second 4 bytes */
	 out_header, out_length, GET_RSP_MSG, pi->packet_end - out_reqid);
      if (out_data != out_reqid) {
	 ERROR("internal error: header");
	 return 0;
	 }

      *out_length = pi->packet_end - out_auth;
      out_data = snmp_auth_build(out_auth, out_length,  /* First 4 bytes */
	 pi->community, &pi->community_len, &pi->version,
         pi->packet_end - out_header);
      if (out_data != out_header) {
	 ERROR("internal error");
	 return 0;
	 }
      break;
   case SNMP_ERR_TOOBIG:
      errindex = 0;  /* Fall through */
   case SNMP_ERR_NOACCESS:
   case SNMP_ERR_WRONGTYPE:
   case SNMP_ERR_WRONGLENGTH:
   case SNMP_ERR_WRONGENCODING:
   case SNMP_ERR_WRONGVALUE:
   case SNMP_ERR_NOCREATION:
   case SNMP_ERR_INCONSISTENTVALUE:
   case SNMP_ERR_RESOURCEUNAVAILABLE:
   case SNMP_ERR_COMMITFAILED:
   case SNMP_ERR_UNDOFAILED:
   case SNMP_ERR_AUTHORIZATIONERROR:
   case SNMP_ERR_NOTWRITABLE:
   case SNMP_ERR_NOSUCHNAME:
   case SNMP_ERR_BADVALUE:
   case SNMP_ERR_READONLY:
   case SNMP_ERR_GENERR:
      if (!create_response(startData, out_auth, startOutLength,
            errstat, errindex, pi)) {
	 ERROR("couldn't create err response");
	 return 0;
	 }
      break;
   default:
      return 0;
      }
   *out_length = pi->packet_end - out_auth;
   return 1;
   }

/*
 * Parse_var_op_list goes through the list of variables and retrieves
 * each one, placing it's value in the output packet.  In the case of
 * a set request, if action is RESERVE, the value is just checked for
 * correct type and value, and resources may need to be reserved.  If
 * the action is COMMIT, the variable is set.  If the action is FREE,
 * an error was discovered somewhere in the previous RESERVE pass, so
 * any reserved resources should be FREE'd.  If any error occurs, an
 * error code is returned.
 */
int parse_var_op_list(
   u_char    *data,
   int	      length,
   u_char    *out_data,
   int	      out_length,
   Bit32     *index,
   struct packet_info  *pi,
   int	      action)
{
   u_char  type;
   oid	    var_name[MAX_NAME_LEN];
   int	    var_name_len, var_val_len;
   u_char  var_val_type, *var_val, statType;
   u_char far *statP;
   int	    statLen;
   u_short acl;
   int	    rw, exact, err;
   write_fn_type write_method;
   u_char  *headerP, *var_list_start;
   int	    dummyLen;
   int	    header_shift;
   int	    noSuchObject;
#if TESTING
   int j;
#endif

   if (pi->pdutype == SET_REQ_MSG)
      rw = WRITE;
   else
      rw = READ;
   if (pi->pdutype == GETNEXT_REQ_MSG) {
      exact = FALSE;
      }
   else {
      exact = TRUE;
      }
   data = asn_parse_header(data, &length, &type);
   if (data == NULL) {
      ERROR("not enough space for varlist");
      return PARSE_ERROR;
      }
   if (type != (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR)) {
      ERROR("wrong type");
      return PARSE_ERROR;
      }
   headerP = out_data;
   out_data = asn_build_sequence(out_data, &out_length,
		(u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), 0);
   if (out_data == NULL) {
      ERROR("not enough space in output packet");
      return BUILD_ERROR;
      }
   var_list_start = out_data;

   *index = 1;
   while ((int)length > 0) {
      /* Parse the name, value pair */
      var_name_len = MAX_NAME_LEN;
      data = snmp_parse_var_op(data, var_name, &var_name_len,
         &var_val_type, &var_val_len, &var_val, &length);
      if (data == NULL)
         return PARSE_ERROR;
      /* Now attempt to retrieve the variable on the local entity */
      statP = getStatPtr(var_name, &var_name_len, &statType, &statLen,
         &acl, exact, &write_method, pi, &noSuchObject);
#if TESTING
      scpos(0,24);
      printf("parse_var_op_list(1): statP=%p, statLen=%d, var_name=%d",
         statP, statLen, var_name[0]);
      for (j = 1; j != var_name_len; ++j) printf(".%d",var_name[j]);
      if (statP != NULL) {
         printf(", value=%0.2x", statP[0]);
         for (j = 1; j != 4; ++j) printf("-%0.2x",statP[j]);
         printf("\n");
         }
#endif
      if (statP == NULL && statLen != 0)
         return SNMP_ERR_NOSUCHNAME;
      if (pi->version == SNMP_VERSION_1 && statP == NULL
            && (pi->pdutype != SET_REQ_MSG || !write_method)) {
         /* V1, no statP, reading or no write proc */
#if defined(DOS) && !defined(OCX)
         snmp_error("SNMPv1 r/w fail");
#else
         ERROR("SNMPv1 r/w fail");
#endif
         return SNMP_ERR_NOSUCHNAME;
	 }

      /* Effectively, check if this variable is read-only or read-write
	   (in the MIB sense). */
      if (pi->pdutype == SET_REQ_MSG  /* Writing, no write access */
	    && !snmp_access(acl, pi->community_id, rw)) {
#if TESTING
         log_msg(LOG_INFO, 0, "ACL=%X, community=%d, rw=%d",
		 /* LOG_INFO only writes to console */
            acl, pi->community_id, rw);
#endif
	 if (pi->version == SNMP_VERSION_2C) {
	    ERROR("no write access");
	    return SNMP_ERR_NOTWRITABLE;
	    }
	 else {
	    ERROR("no such name");
	    return SNMP_ERR_NOSUCHNAME;
	    }
	 }

      /* Its bogus to check here on getnexts - the whole packet shouldn't
	 be dumped - this should should be the loop in getStatPtr.
	 Luckily no objects are set unreadable.  This can still be
	 useful for sets to determine which are intrinsically writable */

      if (pi->pdutype == SET_REQ_MSG) {
	 if (write_method == NULL) {
	    if (statP != NULL) {
	       /* See if the type and value is consistent with this
                     entity's variable */
	       if (!goodValue(var_val_type,var_val_len, statType,statLen)) {
                  if (pi->version == SNMP_VERSION_2C)
                     return SNMP_ERR_WRONGTYPE; /* Poor approximation */
	          else
		     return SNMP_ERR_BADVALUE;
		  }
	       /* Actually do the set if necessary */
	       if (action == COMMIT)
		  setVariable(var_val, var_val_type, var_val_len,
                     statP, statLen);
	       }
            else {
	       if (pi->version == SNMP_VERSION_2C)
		  return SNMP_ERR_NOCREATION;
	       else
	          return SNMP_ERR_NOSUCHNAME;
	       }
	    }
         else {
	    err = write_method(action, var_val, var_val_type,
               var_val_len, statP, var_name, var_name_len);
	    if (err != SNMP_ERR_NOERROR) {
	       if (pi->version != SNMP_VERSION_2C)
		  return SNMP_ERR_BADVALUE;
	       else
                  return err;
	       }
            }
         }
      else {  /* GET, GETNEXT */
         /* Place the value of the variable into the outgoing packet */
         if (statP == NULL) {
            statLen = 0;
	    if (exact) {
	       if (noSuchObject == TRUE) {
	          statType = SNMP_NOSUCHOBJECT;
	          }
               else {
	          statType = SNMP_NOSUCHINSTANCE;
	          }
               return SNMP_ERR_NOSUCHNAME;
	       }
            else {
	       statType = SNMP_ENDOFMIBVIEW;
	       }
	    }
         out_data = snmp_build_var_op(out_data, var_name, &var_name_len,
            statType, statLen, statP, &out_length);
#if TESTING
      scpos(0,24);
      printf("parse_var_op_list(2): statType=%d, out_data=%p\n",
         statType, out_data);
      printf("\n");
#endif
	 if (out_data == NULL) {
	    return SNMP_ERR_TOOBIG;
	    }
         }

      (*index)++;
      }
   if (pi->pdutype != SET_REQ_MSG) {
      /* Save a pointer to the end of the packet */
      pi->packet_end = out_data;

      /* Now rebuild header with the actual lengths */
      dummyLen = pi->packet_end - var_list_start;
      if (asn_build_sequence(headerP, &dummyLen,
            (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), dummyLen) == NULL) {
	 return SNMP_ERR_TOOBIG;  /* Bogus error ???? */
         }
      }
   *index = 0;
   return SNMP_ERR_NOERROR;
   }

#ifdef REPEATER_ARRAY
#define MAX_REPEATERS    20

struct repeater {
   oid	name[MAX_NAME_LEN];
   int length;
   };
struct repeater repeaterList[MAX_REPEATERS];
#endif

/*
 * Bulk_var_op_list goes through the list of variables and retrieves
 * each one, placing it's value in the output packet.  In the case of
 * a set request, if action is RESERVE, the value is just checked for
 * correct type and value, and resources may need to be reserved.  If
 * the action is COMMIT, the variable is set.  If the action is FREE,
 * an error was discovered somewhere in the previous RESERVE pass, so
 * any reserved resources should be FREE'd.  If any error occurs, an
 * error code is returned.
 */
int bulk_var_op_list(
   u_char *data,
   int	   length,
   u_char *out_data,
   int	   out_length,
   int	   non_repeaters,
   int	   max_repetitions,
   Bit32  *index,
   struct packet_info *pi)
{
   u_char  type;
   oid	    var_name[MAX_NAME_LEN];
   int	    var_name_len, var_val_len;
   u_char  var_val_type, *var_val, statType;
   u_char far *statP;
   int	    statLen;
   u_short acl;
   write_fn_type write_method;
   u_char  *headerP, *var_list_start;
   int	    dummyLen;
   int	    header_shift;
   u_char  *repeaterStart, *out_data_save;
   int	    repeatCount, repeaterLength, indexStart, out_length_save;
   int	    full = FALSE;
   int	    noSuchObject, useful;
   int repeaterIndex, repeaterCount;
#if NEVIL_TEST
int j,k;
#endif
#ifdef REPEATER_ARRAY
   struct repeater *rl;
#endif

   if (non_repeaters < 0)
      non_repeaters = 0;
   max_repetitions = *index;
   if (max_repetitions < 0)
      max_repetitions = 0;

   data = asn_parse_header(data, &length, &type);
   if (data == NULL) {
      ERROR("not enough space for varlist");
      return PARSE_ERROR;
      }
   if (type != (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR)) {
      ERROR("wrong type");
      return PARSE_ERROR;
      }
   headerP = out_data;
   out_data = asn_build_sequence(out_data, &out_length,
		(u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), 0);
   if (out_data == NULL) {
      ERROR("not enough space in output packet");
      return BUILD_ERROR;
      }
   var_list_start = out_data;

   out_length -= 32;	/* Slop factor */
   *index = 1;
   while ((int)length > 0 && non_repeaters > 0) {
      /* Parse the (name, value) pair */
      var_name_len = MAX_NAME_LEN;
      data = snmp_parse_var_op(data, var_name, &var_name_len,
         &var_val_type, &var_val_len, &var_val, (int *)&length);
      if (data == NULL)
	 return PARSE_ERROR;

      /* Now attempt to retrieve the variable on the local entity */
      statP = getStatPtr(var_name, &var_name_len, &statType, &statLen,
         &acl, FALSE, &write_method, pi, &noSuchObject);
#if NEVIL_TEST
      printf("bvo_list(1): %d", var_name[0]);
      for (j = 1; j != var_name_len; ++j)
         printf(".%d", var_name[j]);
      printf(" statP=%lu, statLen=%d\n", statP, statLen);
#endif
      if (statP == NULL) {
	 if (statLen != 0) return SNMP_ERR_TOOBIG;
         statType = SNMP_ENDOFMIBVIEW;
	 }

      /* Retrieve the value of the variable and place it
       *   into the outgoing packet */
      out_data = snmp_build_var_op(out_data, var_name, &var_name_len,
         statType, statLen, statP, &out_length);
      if (out_data == NULL) {
	 return SNMP_ERR_TOOBIG;
	 }
      (*index)++;
      non_repeaters--;
      }

   repeaterStart = out_data;
   indexStart = *index;	 /* Index on input packet */
   repeaterCount = 0;
#ifdef REPEATER_ARRAY
   rl = repeaterList;
#endif
   useful = FALSE;
   while ((int)length > 0) {
      /* Parse the name, value pair */
#ifdef REPEATER_ARRAY
      rl->length = MAX_NAME_LEN;
      data = snmp_parse_var_op(data, rl->name, &rl->length,
#else
      var_name_len = MAX_NAME_LEN;
      data = snmp_parse_var_op(data, var_name, &var_name_len,
#endif
         &var_val_type, &var_val_len, &var_val, (int *)&length);
      if (data == NULL)
	 return PARSE_ERROR;
      /* Now attempt to retrieve the variable on the local entity */
#ifdef REPEATER_ARRAY
      statP = getStatPtr(rl->name, &rl->length, &statType, &statLen,
#else
      statP = getStatPtr(var_name, &var_name_len, &statType, &statLen,
#endif
         &acl, FALSE, &write_method, pi, &noSuchObject);
#if NEVIL_TEST
      printf("bvo_list(2): %d", var_name[0]);
      for (j = 1; j != var_name_len; ++j)
         printf(".%d", var_name[j]);
      printf(" statP=%lu, statLen=%d\n", statP, statLen);
#endif
      if (statP == NULL) {
	 if (statLen != 0) return SNMP_ERR_TOOBIG;
         statType = SNMP_ENDOFMIBVIEW;
	 }
      else
	 useful = TRUE;

      out_data_save = out_data;
      out_length_save = out_length;
      /* Retrieve the value of the variable and place it into the
       * outgoing packet */
#ifdef REPEATER_ARRAY
      out_data = snmp_build_var_op(out_data, rl->name, &rl->length,
#else
      out_data = snmp_build_var_op(out_data, var_name, &var_name_len,
#endif
         statType, statLen, statP, &out_length);
      if (out_data == NULL) {
	 out_data = out_data_save;
	 out_length = out_length_save;
	 full = TRUE;
	 }
      (*index)++;
#ifdef REPEATER_ARRAY
      if (++repeaterCount > MAX_REPEATERS)
	 return SNMP_ERR_TOOBIG;  /* No room in rl[] */
      ++rl;
#else
      ++repeaterCount;
#endif
      }
   repeaterLength = out_data - repeaterStart;
   if (!useful)
      full = TRUE;

   for (repeatCount = 1; repeatCount < max_repetitions; repeatCount++) {
      data = repeaterStart;
      length = repeaterLength;
      *index = indexStart;
      repeaterStart = out_data;
      useful = FALSE;
      repeaterIndex = 0;
#ifdef REPEATER_ARRAY
      rl = repeaterList;
#endif
      while ((repeaterIndex++ < repeaterCount) > 0 && !full) {
	 /* Parse the name, value pair */
#ifndef REPEATER_ARRAY
	 var_name_len = MAX_NAME_LEN;
	 data = snmp_parse_var_op(data, var_name, &var_name_len,
            &var_val_type, &var_val_len, &var_val, &length);
	 if (data == NULL)
	    return PARSE_ERROR;
         /* Now attempt to retrieve the variable on the local entity */
         statP = getStatPtr(var_name, &var_name_len, &statType, &statLen,
#else
         /* Now attempt to retrieve the variable on the local entity */
	 statP = getStatPtr(rl->name, &rl->length, &statType, &statLen,
#endif
            &acl, FALSE, &write_method, pi, &noSuchObject);
#if NEVIL_TEST
         printf("bvo_list(3): %d", var_name[0]);
         for (j = 1; j != var_name_len; ++j)
            printf(".%d", var_name[j]);
         printf(" statP=%lu, statLen=%d\n", statP, statLen);
#endif
         if (statP == NULL) {
	    if (statLen != 0) return SNMP_ERR_TOOBIG;
            statType = SNMP_ENDOFMIBVIEW;
	    }
	 else
	    useful = TRUE;

	 out_data_save = out_data;
	 out_length_save = out_length;
	 /* Retrieve the value of the variable and place it
	  *   into the outgoing packet */
#ifdef REPEATER_ARRAY
	 out_data = snmp_build_var_op(out_data, rl->name, &rl->length,
#else
         out_data = snmp_build_var_op(out_data, var_name, &var_name_len,
#endif
            statType, statLen, statP, &out_length);
	 if (out_data == NULL) {
	    out_data = out_data_save;
	    out_length = out_length_save;
	    full = TRUE;
	    repeatCount = max_repetitions;
	    }
	 (*index)++;
#ifdef REPEATER_ARRAY
	 rl++;
#endif
	 }
      repeaterLength = out_data - repeaterStart;
      if (!useful)  /* All repeaters failed, break outer loop */
	 full = TRUE;
      }
   /* Save a pointer to the end of the packet */
   pi->packet_end = out_data;
    
   /* Now rebuild header with the actual lengths */
   dummyLen = pi->packet_end - var_list_start;
   if (asn_build_sequence(headerP, &dummyLen,
         (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), dummyLen) == NULL) {
      ERROR("internal error: header");
      return SNMP_ERR_TOOBIG;
      }
   *index = 0;
   return SNMP_ERR_NOERROR;
   }

/*
 * Create a packet from the input packet, setting the error
 * status and the error index according to the input variables,
 * and discarding the varbind list for a TOOBIG packet.
 * Returns 1 upon success and 0 upon failure.
 */
int create_response(
   u_char *snmp_in,
   u_char *snmp_out,
   int snmp_length,
   Bit32 errstat, Bit32 errindex,
   struct packet_info *pi)
{
   u_char *data;
   u_char  type;
   Bit32   dummy, reqid;
   int     length, messagelen;
   u_char *headerPtr, *reqidPtr, *errstatPtr, *errindexPtr, *varListPtr;
   u_char *out_data, *out_header, *out_reqid;
   int     out_length;
   int     packet_len;

   length = snmp_length;
   asn_parse_header(snmp_in, &length, &type);
   if (type == (ASN_SEQUENCE | ASN_CONSTRUCTOR)) {
      length = snmp_length;
      /* Authenticates message and returns length if valid */
      pi->community_len = COMMUNITY_MAX_LEN;
      headerPtr = snmp_auth_parse(snmp_in, &length,
         pi->community, &pi->community_len, &pi->version);
      }
   else {
      ERROR("unknown auth header type");
      return 0;
      }
   if (headerPtr == NULL)
      return 0;
   reqidPtr = asn_parse_header(headerPtr, &length, (u_char *)&dummy);
   if (reqidPtr == NULL)
      return 0;
   errstatPtr = asn_parse_int(reqidPtr, &length, &type,
      &reqid, sizeof reqid);  /* Request id */
   if (errstatPtr == NULL)
      return 0;
   errindexPtr = asn_parse_int(errstatPtr, &length, &type,
      &dummy, sizeof dummy);  /* Error status */
   if (errindexPtr == NULL)
      return 0;
   varListPtr = asn_parse_int(errindexPtr, &length, &type,
      &dummy, sizeof dummy);  /* Error index */
   if (varListPtr == NULL)
      return 0;
   messagelen = length;  /* Length of the varbind list */

   out_length = snmp_length;  /* Build auth header for new packet */
   out_header = snmp_auth_build(snmp_out, &out_length,
      pi->community, &pi->community_len, &pi->version, 0);
   if (out_header == NULL) {
      ERROR("response: no auth header");  return 0;
      }
   out_reqid = asn_build_sequence(  /* Always uses 4 bytes */
      out_header, &out_length, (u_char)GET_RSP_MSG, 0);
   if (out_reqid == NULL) {
      ERROR("response: no out_reqid seq");  return 0;
      }
   type = (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER);
   out_data = asn_build_int(out_reqid, &out_length,
      type, &reqid, sizeof reqid);
   if (out_data == NULL) {
      ERROR("response: no out_reqid len");  return 0;
      }
   out_data = asn_build_int(out_data, &out_length,
      type, &errstat, sizeof errstat);
   if (out_data == NULL) {
      ERROR("response: no errstat");  return 0;
      }
   out_data = asn_build_int(out_data, &out_length,
      type, &errindex, sizeof errindex);
   if (out_data == NULL) {
      ERROR("response: no errindex");  return 0;
      }

   if (errstat == SNMP_ERR_TOOBIG && pi->version != SNMP_VERSION_1) {
      /* Construct an empty varbind list */
      out_data = asn_build_header(out_data, &out_length,
         (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), 0);      
      pi->packet_end = out_data;
      }
   else {
      /* Copy varbind list from original packet */
      memcpy(out_data, varListPtr, messagelen);
      pi->packet_end = out_data + messagelen;
      }

   dummy = snmp_length;
   asn_build_sequence(out_header, (int *)&dummy,
      (u_char)GET_RSP_MSG, pi->packet_end - out_reqid);
   dummy = snmp_length;
   snmp_auth_build(snmp_out, (int *)&dummy,
      pi->community, &pi->community_len, &pi->version,
      pi->packet_end - out_header);
   return 1;
   }

int snmp_access(u_short acl, int community, int rw)
{
   /* Each group has 2 bits, the more significant one is for read
    * access, the less significant one is for write access. */
   community <<= 1;  /* Multiply by two to shift two bits at a time */
   if (rw == READ) {
      return (acl & (2 << community));  /* Return the correct bit */
      }
   else {
      return (acl & (1 << community));
      }
   }

int get_community(u_char *community, int community_len)
{
   int count;

   for (count = 0; count < NUM_COMMUNITIES; count++) {
      if ((community_len == strlen(communities[count]) &&
            !memcmp(communities[count], (char *)community, community_len)))
         break;
      }
   if (count == NUM_COMMUNITIES)
      return -1;
   return count + 1;
   }

int goodValue(u_char inType, int inLen,
   u_char actualType, int actualLen)
{
   if (inLen > actualLen)
      return FALSE;
   return (inType == actualType);
   }

void setVariable(u_char *var_val, u_char var_val_type, int var_val_len,
   u_char far *statP, int statLen)
{
   int buffersize = 1000;
   Bit32 intval;

   switch(var_val_type) {
   case ASN_INTEGER:
      asn_parse_int(var_val, &buffersize, 
	 &var_val_type, &intval, sizeof(intval));
      if (statLen == sizeof(Bit32)) *((Bit32 far *)statP) = (Bit32)intval;
      else if (statLen == sizeof(Bit16)) *((Bit16 far *)statP) = (Bit16)intval;
      else if (statLen == sizeof(Bit8)) *((Bit8 far *)statP) = (Bit8)intval;
      break;
   case COUNTER:
   case GAUGE:
   case TIMETICKS:
      asn_parse_unsigned_int(var_val, &buffersize,
         &var_val_type, (Bit32 far *)statP, statLen);
      break;
   case COUNTER64:
      asn_parse_unsigned_int64(var_val, &buffersize,
         &var_val_type, (counter64 far *)statP, statLen);
      break;
   case ASN_OCTET_STR:
   case IPADDRESS:
   case OPAQUE:
   case NSAP:
      asn_parse_string(var_val, &buffersize,
         &var_val_type, (u_char far *)statP, &statLen);
      break;
   case ASN_OBJECT_ID:
      asn_parse_objid(var_val, &buffersize,
         &var_val_type, (oid *)statP, &statLen);
      break;
   case ASN_BIT_STR:
      asn_parse_bitstring(var_val, &buffersize,
         &var_val_type, (u_char far *)statP, &statLen);
      break;
      }
   }
