/* 1700, Mon 9 Sep 96

   MIB.C:  Read an SNMPv2 MIB, build a MIB tree.
           NeTraMet version, based on CMU SNMPv2 (see below).

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

/*
 * $Log: mib.c,v $
 * Revision 1.1.1.2.2.7  2002/02/23 01:57:36  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.3  2000/06/06 03:38:25  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2  1999/10/03 21:06:29  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.3  1999/09/24 02:58:42  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:46  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:36  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1  1998/11/16 03:57:31  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:30  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2.2.2  1998/10/22 01:28:05  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:36  nevil
 * Use log_msg() to report errors instead of fprintf(stderr ..)
 *
 * Revision 1.1.3.2  1998/10/14 04:13:43  nevil
 * Merge Nicolai's patches into 4.2.1 distribution
 *
 * Revision 1.2  1998/10/08 09:22:33  nguba
 * -Added -b switch to NeMaC so that MIBs can be loaded from the command
 *  line instead of relying on the current working directory or an
 *  environment variable.  I have a mib in /etc/mib.txt but it gives me
 *  errors.  Being able to give a MIB via command line helps.
 * -I also had to increase the size of fn[64] to 256, which is --I
 *  belive-- the maximum a Unix command line can take.  I wonder whether
 *  Unix systems define this one somewhere...  Anyway, this variable is
 *  #define'd via MAXLINE.
 * -Use the function set_mibfile(char*) to set the mibfile via
 *  command-line.  This function is defined in parse.h, implemented in
 *  mib.c and is available via libsnmp.a
 *
 * Revision 1.1.1.1  1998/10/13 01:35:00  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, 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

#define noTEST_MAIN  /* main() to test mib.c and parse.c */

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

#ifdef DOS
#include <time.h>
#include <mem.h>
#include "tcp.h"
#else
#include <sys/time.h>
#include <netinet/in.h>
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#endif

#include "ausnmp.h"

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

static char *uptimeString(Bit32 timeticks, char *buf);
static void sprint_hexstring(char *buf, u_char *cp, int len);
static void sprint_asciistring(char *buf, u_char *cp, int len);
static void sprint_octet_string(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_opaque(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_object_identifier(char *buf, struct variable_list *var,
   struct enum_list *enums);
void print_variable(oid *objid, int objidlen,
  struct variable_list *variable);
void sprint_variable(char *buf, oid *objid, int objidlen,
  struct variable_list *variable);
void sprint_value(char *buf, oid *objid, int objidlen,
   struct variable_list *variable);
static void sprint_timeticks(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_integer(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_uinteger(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_gauge(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_counter(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_networkaddress(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_ipaddress(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_unsigned_short(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_null(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_bitstring(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_nsapaddress(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_counter64(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_unknowntype(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_badtype(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void sprint_by_type(char *buf, struct variable_list *var,
   struct enum_list *enums);
static void set_functions(struct tree *subtree);
static struct tree *find_rfc1213_mib(struct tree *root);
static parse_subtree(struct tree *subtree,
   char *input, oid *output, int *out_len);
static int lc_cmp(char *s1, char *s2);

static char *uptimeString(Bit32 timeticks, char *buf)
{
   int	seconds, minutes, hours, days;
   
   timeticks /= 100;
   days = timeticks / (60 * 60 * 24);
   timeticks %= (60 * 60 * 24);
   
   hours = timeticks / (60 * 60);
   timeticks %= (60 * 60);
   
   minutes = timeticks / 60;
   seconds = timeticks % 60;
   
   if (days == 0) {
      sprintf(buf, "%d:%02d:%02d", hours, minutes, seconds);
      }
   else if (days == 1) {
      sprintf(buf, "%d day, %d:%02d:%02d", days, hours, minutes, seconds);
      }
   else {
      sprintf(buf, "%d days, %d:%02d:%02d", days, hours, minutes, seconds);
      }
   return buf;
   }

static void sprint_hexstring(char *buf, u_char *cp, int len)
{

   for (; len >= 16; len -= 16) {
      sprintf(buf, "%02X %02X %02X %02X %02X %02X %02X %02X ",
	  cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
      buf += strlen(buf);
      cp += 8;
      sprintf(buf, "%02X %02X %02X %02X %02X %02X %02X %02X\n",
	  cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
      buf += strlen(buf);
      cp += 8;
      }
   for (; len > 0; len--) {
      sprintf(buf, "%02X ", *cp++);
      buf += strlen(buf);
      }
   *buf = '\0';
   }

static void sprint_asciistring(char *buf, u_char  *cp, int len)
{
   int	x;

   for (x = 0; x < len; x++) {
      if (isprint(*cp)) {
	 *buf++ = *cp++;
	 }
      else {
	 *buf++ = '.';
	 cp++;
	 }
#if 0
      if ((x % 48) == 47)
	 *buf++ = '\n';
#endif
      }
   *buf = '\0';
   }

/*
   0
   < 4
   hex
   
   0 ""
   < 4 hex Hex: oo oo oo
   < 4     "fgh" Hex: oo oo oo
   > 4 hex Hex: oo oo oo oo oo oo oo oo
   > 4     "this is a test"
   
   */
static void sprint_octet_string(char *buf, struct variable_list *var,
				struct enum_list *enums)
{
   int hex, x;
   u_char *cp;

   if (var->type != ASN_OCTET_STR) {
      sprintf(buf, "Wrong Type (should be OCTET STRING): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   hex = 0;
   for (cp = var->val.string, x = 0; x < var->val_len; x++, cp++) {
      if (!(isprint(*cp) || isspace(*cp)))
	 hex = 1;
      }
   if (var->val_len == 0) {
      strcpy(buf, "\"\"");
      return;
      }
   if (!hex) {
      *buf++ = '"';
      sprint_asciistring(buf, var->val.string, var->val_len);
      buf += strlen(buf);
      *buf++ = '"';
      *buf = '\0';
      }
   if (hex || var->val_len <= 4) {
      sprintf(buf, " Hex: ");
      buf += strlen(buf);
      sprint_hexstring(buf, var->val.string, var->val_len);
      }
   }

static void sprint_opaque(char *buf, struct variable_list *var,
			  struct enum_list *enums)
{

   if (var->type != OPAQUE) {
      sprintf(buf, "Wrong Type (should be Opaque): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "OPAQUE: ");
   buf += strlen(buf);
   sprint_hexstring(buf, var->val.string, var->val_len);
   }

static void sprint_object_identifier(char *buf, struct variable_list *var,
				     struct enum_list *enums)
{
   if (var->type != ASN_OBJECT_ID) {
      sprintf(buf, "Wrong Type (should be OBJECT IDENTIFIER): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "OID: ");
   buf += strlen(buf);
   sprint_objid(buf, (oid *)(var->val.objid), var->val_len / sizeof(oid));
   }

static void sprint_timeticks(char *buf, struct variable_list *var,
			     struct enum_list *enums)
{
   char timebuf[32];

   if (var->type != TIMETICKS) {
      sprintf(buf, "Wrong Type (should be Timeticks): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "Timeticks: (%d) %s", *(Bit32 *)(var->val.u_int), uptimeString(*(Bit32 *)(var->val.u_int), timebuf));
   }

static void sprint_integer(char *buf, struct variable_list *var,
			   struct enum_list *enums)
{
   char *enum_string = NULL;

   if (var->type != ASN_INTEGER) {
      sprintf(buf, "Wrong Type (should be INTEGER): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   for (; enums; enums = enums->next)
      if (enums->value == *var->val.integer) {
	 enum_string = enums->label;
	 break;
	 }
   if (enum_string == NULL)
      sprintf(buf, "%d", *var->val.integer);
   else
      sprintf(buf, "%s(%d)", enum_string, *var->val.integer);
   }

static void sprint_uinteger(char *buf, struct variable_list *var,
			    struct enum_list *enums)
{
   char    *enum_string = NULL;

   if (var->type != UINTEGER) {
      sprintf(buf, "Wrong Type (should be UInteger32): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   for (; enums; enums = enums->next)
      if (enums->value == *var->val.integer) {
	 enum_string = enums->label;
	 break;
	 }
   if (enum_string == NULL)
      sprintf(buf, "%d", *var->val.integer);
   else
      sprintf(buf, "%s(%d)", enum_string, *var->val.integer);
   }

static void sprint_gauge(char *buf, struct variable_list *var,
			 struct enum_list *enums)
{
   if (var->type != GAUGE) {
      sprintf(buf, "Wrong Type (should be Gauge): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "Gauge: %lu", *var->val.integer);
   }

static void sprint_counter(char *buf, struct variable_list *var,
			   struct enum_list *enums)
{
   if (var->type != COUNTER) {
      sprintf(buf, "Wrong Type (should be Counter): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "%lu", *var->val.integer);
   }

static void sprint_networkaddress(char *buf, struct variable_list *var,
				  struct enum_list *enums)
{
   int x, len;
   u_char *cp;

   sprintf(buf, "Network Address: ");
   buf += strlen(buf);
   cp = var->val.string;    
   len = var->val_len;
   for (x = 0; x < len; x++) {
      sprintf(buf, "%02X", *cp++);
      buf += strlen(buf);
      if (x < (len - 1))
	 *buf++ = ':';
      }
   }

static void sprint_ipaddress(char *buf, struct variable_list *var,
			     struct enum_list *enums)
{
   u_char *ip;

   if (var->type != IPADDRESS) {
      sprintf(buf, "Wrong Type (should be Ipaddress): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   ip = var->val.string;
   sprintf(buf, "IpAddress: %d.%d.%d.%d",ip[0], ip[1], ip[2], ip[3]);
   }

static void sprint_unsigned_short(char *buf, struct variable_list *var,
				  struct enum_list *enums)
{
   if (var->type != ASN_INTEGER) {
      sprintf(buf, "Wrong Type (should be INTEGER): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "INT: %lu", *var->val.integer);
   }

static void sprint_null(char *buf, struct variable_list *var,
			struct enum_list *enums)
{
   if (var->type != ASN_NULL) {
      sprintf(buf, "Wrong Type (should be NULL): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "NULL");
   }

static void sprint_bitstring(char *buf, struct variable_list *var,
			     struct enum_list *enums)
{
   int len, bit;
   u_char *cp;
   char *enum_string;

   if (var->type != ASN_BIT_STR) {
      sprintf(buf, "Wrong Type (should be BIT STRING): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "BIT_STRING: ");
   buf += strlen(buf);
   sprint_hexstring(buf, var->val.bitstring, var->val_len);
   buf += strlen(buf);

   cp = var->val.bitstring + 1;
   for (len = 0; len < var->val_len - 1; len++) {
      for (bit = 0; bit < 8; bit++) {
	 if (*cp & (0x80 >> bit)) {
	    enum_string = NULL;
	    for (; enums; enums = enums->next)
	       if (enums->value == (len * 8) + bit) {
		  enum_string = enums->label;
		  break;
		  }
	    if (enum_string == NULL)
	       sprintf(buf, "%d ", (len * 8) + bit);
	    else
	       sprintf(buf, "%s(%d) ", enum_string, (len * 8) + bit);
	    buf += strlen(buf);
	    }
	 }
      cp ++;	    
      }
   }

static void sprint_nsapaddress(char *buf, struct variable_list *var,
			       struct enum_list *enums)
{
   if (var->type != NSAP) {
      sprintf(buf, "Wrong Type (should be NsapAddress): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   sprintf(buf, "NsapAddress: ");
   buf += strlen(buf);
   sprint_hexstring(buf, var->val.string, var->val_len);
   }

static void sprint_counter64(char *buf, struct variable_list *var,
			     struct enum_list *enums)
{
   counter64 *cp;  /* Bug: was struct counter64.  March 98 */

   if (var->type != COUNTER64) {
      sprintf(buf, "Wrong Type (should be Counter64): ");
      buf += strlen(buf);
      sprint_by_type(buf, var, (struct enum_list *)NULL);
      return;
      }
   /* XXX */
   sprintf(buf, "Counter64: ");
   buf += strlen(buf);
    
   /* Hexstring prints in memory order ! */
#if SIZEOF_LONG_LONG == 8 || SIZEOF_LONG == 8
   sprint_hexstring(buf, (u_char *)&var->val.counter64,
		    sizeof(var->val.counter64));
#else
   sprint_hexstring(buf, (u_char *)&var->val.counter64->high,
		    sizeof(var->val.counter64->high));
   buf += strlen(buf);
   sprint_hexstring(buf, (u_char *)&var->val.counter64->low,
		    sizeof(var->val.counter64->low));
#endif
   }


static void sprint_unknowntype(char *buf, struct variable_list *var,
			       struct enum_list *enums)
{
   /*    sprintf(buf, "Variable has bad type"); */
   sprint_by_type(buf, var, NULL);
   }

static void sprint_badtype(char *buf, struct variable_list *var,
			   struct enum_list *enums)
{
   sprintf(buf, "Variable has bad type");
   }

static void sprint_by_type(char *buf, struct variable_list *var,
			   struct enum_list *enums)
{
   switch (var->type) {
   case ASN_INTEGER:
      sprint_integer(buf, var, enums);
      break;
   case ASN_OCTET_STR:
      sprint_octet_string(buf, var, enums);
      break;
   case OPAQUE:
      sprint_opaque(buf, var, enums);
      break;
   case ASN_OBJECT_ID:
      sprint_object_identifier(buf, var, enums);
      break;
   case TIMETICKS:
      sprint_timeticks(buf, var, enums);
      break;
   case GAUGE:
      sprint_gauge(buf, var, enums);
      break;
   case COUNTER:
      sprint_counter(buf, var, enums);
      break;
   case IPADDRESS:
      sprint_ipaddress(buf, var, enums);
      break;
   case ASN_NULL:
      sprint_null(buf, var, enums);
      break;
   case UINTEGER:
      sprint_uinteger(buf, var, enums);
      break;
   default:
      sprint_badtype(buf, var, enums);
      break;
      }
   }

oid RFC1213_MIB[] = { 1, 3, 6, 1, 2, 1 };
unsigned char RFC1213_MIB_text[] = ".iso.org.dod.internet.mgmt.mib-2";
unsigned char EXPERIMENTAL_MIB_text[] = ".iso.org.dod.internet.experimental";
unsigned char PRIVATE_MIB_text[] = ".iso.org.dod.internet.private";
unsigned char PARTY_MIB_text[] = ".iso.org.dod.internet.snmpParties";
unsigned char SECRETS_MIB_text[] = ".iso.org.dod.internet.snmpSecrets";
struct tree *Mib;

char Standard_Prefix[] = ".1.3.6.1.2.1.";
char Prefix[128];
int Suffix;

/* mibfile set by command-line arg.  Use set_mibfile for
   setting this one */
static char* mibfile = NULL;

/* Function to read in custom mibfile */
char *set_mibfile(char* filename)
{
   int flen;
   if (filename != NULL) {
      flen = strlen(filename)+1;
      if ((mibfile = malloc(flen)) == NULL)
         log_msg(LOG_INFO, 99, "Error: malloc failed!\n");
      strncpy(mibfile, filename, flen);
      }
   return filename;
   }

/* Maximum command-line length */
#define MAXLINE 256

void init_mib(void)
{
   char *file, fn[MAXLINE], *prefix;
  
   /* If given as command line arg [-b in NeMaC], then read in
      mibfile, otherwise check whether it doesn't exists in
      current working directory */
   Mib = read_mib(strncpy(fn, 
      mibfile != NULL ? mibfile : "mib.txt", MAXLINE));
   if (!Mib) {
      /* NB, 5 Jan 94:  Change to match NeTraMet documentation */
      file = getenv("MIBTXT");
      if (file) Mib = read_mib(strcpy(fn,file));
      }
   if (!Mib) Mib = read_mib(strcpy(fn,"/etc/mib.txt"));
   if (!Mib) log_msg(LOG_ERR, 2, "Couldn't find MIB file\n");
   log_msg(LOG_INFO, 0, "Using MIB file: %s\n", fn);

   prefix = getenv("PREFIX");
   if (!prefix)
      prefix = Standard_Prefix;
   if (prefix[strlen(prefix) - 1] != '.')
      strcat(prefix, ".");	/* add a trailing dot in case user didn't */ 
   prefix++;			/* get past leading dot. */
   strcpy(Prefix, prefix);

   if (getenv("SUFFIX"))
      Suffix = TRUE;
   else
      Suffix = FALSE;
   set_functions(Mib);
   }

static void set_functions(struct tree *subtree)
{
   for (; subtree; subtree = subtree->next_peer) {
      switch(subtree->type) {
      case TYPE_OBJID:
	 subtree->printer = sprint_object_identifier;
	 break;
      case TYPE_OCTETSTR:
	 subtree->printer = sprint_octet_string;
	 break;
      case TYPE_INTEGER:
	 subtree->printer = sprint_integer;
	 break;
      case TYPE_NETADDR:
	 subtree->printer = sprint_networkaddress;
	 break;
      case TYPE_IPADDR:
	 subtree->printer = sprint_ipaddress;
	 break;
      case TYPE_COUNTER:
	 subtree->printer = sprint_counter;
	 break;
      case TYPE_GAUGE:
	 subtree->printer = sprint_gauge;
	 break;
      case TYPE_TIMETICKS:
	 subtree->printer = sprint_timeticks;
	 break;
      case TYPE_OPAQUE:
	 subtree->printer = sprint_opaque;
	 break;
      case TYPE_NULL:
	 subtree->printer = sprint_null;
	 break;
      case TYPE_BITSTRING:
	 subtree->printer = sprint_bitstring;
	 break;
      case TYPE_NSAPADDRESS:
	 subtree->printer = sprint_nsapaddress;
	 break;
      case TYPE_COUNTER64:
	 subtree->printer = sprint_counter64;
	 break;
      case TYPE_UINTEGER:
	 subtree->printer = sprint_uinteger;
	 break;
      case TYPE_OTHER:
      default:
	 subtree->printer = sprint_unknowntype;
	 break;
	 }
      set_functions(subtree->child_list);
      }
   }

#ifdef TEST_MAIN
int snmp_dump_packet = 0;

main(argc, argv)
   int argc;
   char *argv[];
{
   oid objid[64];
   int objidlen = sizeof (objid);
   int count;
   /*    struct variable variable;  $$$ */

   init_mib();
   if (argc < 2)
      print_subtree(Mib, 0);
#if 0				/* $$$ */
   variable.type = ASN_INTEGER;
   variable.val.integer = 3;
   variable.val_len = 4;
   for (argc--; argc; argc--, argv++) {
      objidlen = sizeof (objid);
      printf("read_objid(%s) = %d\n",
	     argv[1], read_objid(argv[1], objid, &objidlen));
      for (count = 0; count < objidlen; count++)
	 printf("%d.", objid[count]);
      printf("\n");
      print_variable(objid, objidlen, &variable);
      }
#endif
   }
#endif /* TEST_MAIN */


static struct tree *find_rfc1213_mib(struct tree *root)
{
   oid *op = RFC1213_MIB;
   struct tree *tp;
   int len;

   for (len = sizeof(RFC1213_MIB)/sizeof(oid); len; len--, op++) {
      for (tp = root; tp; tp = tp->next_peer) {
	 if (tp->subid == *op) {
	    root = tp->child_list;
	    break;
	    }
	 }
      if (tp == NULL)
	 return NULL;
      }
   return root;
   }

int read_objid(char *input, oid *output, 
	       int	*out_len) /* number of subid's in "output" */
{
   struct tree *root = Mib;
   oid *op = output;
   char buf[512];


   if (*input == '.')
      input++;
   else {
      strcpy(buf, Prefix);
      strcat(buf, input);
      input = buf;
      }

   if (root == NULL)
      log_msg(LOG_ERR, 1, "Mib not initialized.  Exiting\n");
   if ((*out_len = parse_subtree(root, input, output, out_len)) == 0)
      return 0;
   *out_len += output - op;
   return 1;
   }

static parse_subtree(struct tree *subtree,
		     char *input, oid *output, int *out_len) /* number of subid's */
{
   char buf[128], *to = buf;
   Bit32 subid = 0;
   struct tree *tp;

   /*
    * No empty strings.  Can happen if there is a trailing '.'
    * or two '.'s in a row, i.e. "..".
    */
   if (*input == '\0' || *input == '.')
      return 0;

   if (isdigit(*input)) {
      /*
       * Read the number, then try to find it in the subtree.
       */
      while (isdigit(*input)) {
	 subid *= 10;
	 subid += *input++ - '0';
	 }
      for (tp = subtree; tp; tp = tp->next_peer) {
	 if (tp->subid == subid)
	    goto found;
	 }
      tp = NULL;
      }
   else {
      /*
       * Read the name into a buffer.
       */
      while ((*input != '\0') &&
	     (*input != '.')) {
	 *to++ = *input++;
	 }
      *to = '\0';

      /*
       * Find the name in the subtree;
       */
      for (tp = subtree; tp; tp = tp->next_peer) {
	 if (lc_cmp(tp->label, buf) == 0) {
	    subid = tp->subid;
	    goto found;
	    }
	 }

      /*
       * If we didn't find the entry, punt...
       */
      if (tp == NULL) {
	 log_msg(LOG_ERR, 0, "Sub-identifier not found: %s\n", buf);
	 return 0;
	 }
      }

found:
   if (subid > MAX_SUBID) {
      log_msg(LOG_ERR, 0, "Sub-identifier too large: %s\n", buf);
      return 0;
      }

   if ((*out_len)-- <= 0) {
      log_msg(LOG_ERR, 0, "Object identifier too long\n");
      return 0;
      }
   *output++ = subid;

   if (*input != '.') return 1;
   if ((*out_len = parse_subtree(tp ? tp->child_list : NULL,
         ++input, output, out_len)) == 0)
      return 0;
   return ++*out_len;
   }

void sprint_objid(char *buf, oid *objid,
		  int objidlen)  /* Number of subidentifiers */
{
   char tempbuf[2048], *cp;
   struct tree *subtree = Mib;

   *tempbuf = '.';  /* This is a fully qualified name */
   get_symbol(objid, objidlen, subtree, tempbuf + 1);
   if (Suffix) {
      for (cp =tempbuf; *cp; cp++)
	 ;
      while (cp >= tempbuf) {
	 if (isalpha(*cp))
	    break;
	 cp--;
	 }
      while (cp >= tempbuf) {
	 if (*cp == '.')
	    break;
	 cp--;
	 }
      cp++;
      if (cp < tempbuf)
	 cp = tempbuf;

      }
   else {
      cp = tempbuf;
      if ((strlen(tempbuf) > strlen((char *)RFC1213_MIB_text))
            && !memcmp(tempbuf, (char *)RFC1213_MIB_text,
	    strlen((char *)RFC1213_MIB_text))) {
	 cp += sizeof(RFC1213_MIB_text);
	 }
      if ((strlen(tempbuf) > strlen((char *)EXPERIMENTAL_MIB_text))
	    && !memcmp(tempbuf, (char *) EXPERIMENTAL_MIB_text,
            strlen((char *)EXPERIMENTAL_MIB_text))) {
	 cp += sizeof(EXPERIMENTAL_MIB_text);
	 }
      if ((strlen(tempbuf) > strlen((char *)PRIVATE_MIB_text))
	    && !memcmp(tempbuf, (char *) PRIVATE_MIB_text,
	    strlen((char *)PRIVATE_MIB_text))) {
	 cp += sizeof(PRIVATE_MIB_text);
	 }
      if ((strlen(tempbuf) > strlen((char *)PARTY_MIB_text))
	    && !memcmp(tempbuf, (char *) PARTY_MIB_text,
	    strlen((char *)PARTY_MIB_text))) {
	 cp += sizeof(PARTY_MIB_text);
	 }
      if ((strlen(tempbuf) > strlen((char *)SECRETS_MIB_text))
	    && !memcmp(tempbuf, (char *) SECRETS_MIB_text,
	    strlen((char *)SECRETS_MIB_text))) {
	 cp += sizeof(SECRETS_MIB_text);
	 }
      }
   strcpy(buf, cp);
   }

void print_objid(oid *objid,
		 int objidlen)	/* Number of subidentifiers */
{
   char    buf[256];

   sprint_objid(buf, objid, objidlen);
   printf("%s\n", buf);
   }


void print_variable(oid *objid, int objidlen,
		    struct variable_list *variable)
{
   char buf[2048];
   sprint_variable(buf, objid, objidlen, variable);
   printf("%s", buf);
   }

void sprint_variable(char *buf, oid *objid, int objidlen,
		     struct variable_list *variable)
{
   char    tempbuf[2048], *cp;
   struct tree    *subtree = Mib;

   sprint_objid(buf, objid, objidlen);
   buf += strlen(buf);
   strcat(buf, " = ");
   buf += strlen(buf);

   if (variable->type == SNMP_NOSUCHOBJECT)
      sprintf(buf, "No Such Object available on this agent\n");
   else if (variable->type == SNMP_NOSUCHINSTANCE)
      sprintf(buf, "No Such Instance currently exists\n");
   else if (variable->type == SNMP_ENDOFMIBVIEW)
      sprintf(buf, "No more variables left in this MIB View\n");
   else {
      *tempbuf = '.';  /* This is a fully qualified name */
      subtree = get_symbol(objid, objidlen, subtree, tempbuf + 1);
      buf += strlen(buf);
      if (subtree->printer)
	 (*subtree->printer)(buf, variable, subtree->enums);
      else {
	 sprint_by_type(buf, variable, subtree->enums);
	 }
      strcat(buf, "\n");
      }
   }

void sprint_value(char *buf, oid *objid, int objidlen,
		  struct variable_list *variable)
{
   char    tempbuf[2048];
   struct tree    *subtree = Mib;

   if (variable->type == SNMP_NOSUCHOBJECT)
      sprintf(buf, "No Such Object available on this agent\n");
   else if (variable->type == SNMP_NOSUCHINSTANCE)
      sprintf(buf, "No Such Instance currently exists\n");
   else if (variable->type == SNMP_ENDOFMIBVIEW)
      sprintf(buf, "No more variables left in this MIB View\n");
   else {
      subtree = get_symbol(objid, objidlen, subtree, tempbuf);
      if (subtree->printer)
	 (*subtree->printer)(buf, variable, subtree->enums);
      else {
	 sprint_by_type(buf, variable, subtree->enums);
	 }
      }
   }

void print_value(oid *objid, int objidlen,
		 struct variable_list *variable)
{
   char    tempbuf[2048];
   sprint_value(tempbuf, objid, objidlen, variable);
   printf("%s\n", tempbuf);
   }

struct tree *get_symbol(oid *objid, int objidlen,
			struct tree *subtree, char *buf)
{
   struct tree    *return_tree = NULL;

   for (; subtree; subtree = subtree->next_peer) {
      if (*objid == subtree->subid) {
	 strcpy(buf, subtree->label);
	 goto found;
	 }
      }

   /* subtree not found */
   while (objidlen--) {  /* Output rest of name, uninterpreted */
      sprintf(buf, "%u.", *objid++);
      while (*buf)
	 buf++;
      }
   *(buf - 1) = '\0';  /* Remove trailing dot */
   return NULL;

found:
   if (objidlen > 1) {
      while (*buf)
	 buf++;
      *buf++ = '.';
      *buf = '\0';
      return_tree = get_symbol(objid + 1, objidlen - 1,
	 subtree->child_list, buf);
      } 
   if (return_tree != NULL)
      return return_tree;
   else
      return subtree;
   }


static int lc_cmp(char *s1, char *s2)
{
   char c1, c2;

   while (*s1 && *s2) {
      if (isupper(*s1)) c1 = tolower(*s1);
      else c1 = *s1;
      if (isupper(*s2)) c2 = tolower(*s2);
      else c2 = *s2;
      if (c1 != c2)
	 return (c1 - c2) > 0 ? 1 : -1;
      s1++;  s2++;
      }

   if (*s1) return -1;
   if (*s2) return 1;
   return 0;
   }

/*
 * Clone of get_symbol that doesn't take a buffer argument
 */
struct tree *get_tree(oid *objid, int objidlen,
   struct tree *subtree)
{
   struct tree *return_tree = NULL;
   
   for (; subtree; subtree = subtree->next_peer) {
      if (*objid == subtree->subid)
	 goto found;
      }
   return NULL;
   
found:
   if (objidlen > 1)
      return_tree = get_tree(objid+1, objidlen-1, subtree->child_list);
   if (return_tree != NULL)
      return return_tree;
   else
      return subtree;
   }

char *get_description(oid *objid,
   int objidlen)   /* Number of subidentifiers */
{
   struct tree *subtree = Mib;
   
   subtree = get_tree(objid, objidlen, subtree);
   if (subtree) return subtree->description;
   else return NULL;
   }

void print_description(oid *objid, int objidlen)
{
   char *desc = get_description(objid, objidlen);

   if (desc && desc[0] != '\0')
      printf("Description: \"%s\"\n", desc);
   else
      printf("No description\n");
   }

#ifdef DOS
#define strcasecmp(s1,s2)  strcmpi(s1,s2)
#endif

struct tree *find_node(char *name, struct tree *subtree)
{
   struct tree *tp, *ret;

   for (tp = subtree; tp; tp = tp->next_peer) {
      if (!strcasecmp(name, tp->label))
	 return tp;
      ret = find_node(name, tp->child_list);
      if (ret)
	 return ret;
      }
   return 0;
   }


int get_node(char *name, oid *objid, int *objidlen)
{
   struct tree *tp;
   oid newname[64], *op;

   tp = find_node(name, Mib);
   if (tp) {
      for (op = newname+63; op >= newname; op--) {
	 *op = tp->subid;
	 tp = tp->parent;
	 if (tp == NULL)
	    break;
	 }
      if (newname + 64 - op > *objidlen)
	 return 0;
      *objidlen = newname + 64 - op;
      memcpy(objid, op, (newname + 64 - op) * sizeof(oid));
      return 1;
      }
   else return 0;
   }
