/*
 * snmptrapd_log.c - format SNMP trap information for logging
 *
 */
/*****************************************************************
	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.
******************************************************************/
#include <config.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <sys/types.h>
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#if HAVE_WINSOCK_H
#include <winsock.h>
#else
#include <sys/socket.h>
#endif
#if HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include <stdio.h>
#include <ctype.h>
#if HAVE_SYS_TIME_H
# include <sys/time.h>
# if TIME_WITH_SYS_TIME
#  include <time.h>
# endif
#else
# include <time.h>
#endif
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#if HAVE_SYSLOG_H
#include <syslog.h>
#endif
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#if HAVE_NET_IF_H
#include <net/if.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif

#include "asn1.h"
#include "snmp_api.h"
#include "snmp_impl.h"
#include "mib.h"
#include "snmp.h"
#include "tools.h"

#include "snmptrapd_log.h"


#ifndef BSD4_3
#define BSD4_2
#endif

/* These flags mark undefined values in the options structure */
#define UNDEF_CMD '*'
#define UNDEF_PRECISION -1

/* This structure holds the options for a single format command */
typedef struct {
  char cmd;                   /* the format command itself */
  int  width;                 /* the field's minimum width */
  int  precision;             /* the field's precision */
  int  left_justify;          /* if true, left justify this field */
  int  alt_format;            /* if true, display in alternate format */
  int  leading_zeroes;        /* if true, display with leading zeroes */
} options_type;

/* 
 * These symbols define the characters that the parser recognizes.
 * The rather odd choice of symbols comes from an attempt to avoid
 * colliding with the ones that printf uses, so that someone could add
 * printf functionality to this code and turn it into a library
 * routine in the future.  
 */
typedef enum {
  CHR_FMT_DELIM  = '%',       /* starts a format command */
  CHR_LEFT_JUST  = '-',       /* left justify */
  CHR_LEAD_ZERO  = '0',       /* use leading zeroes */
  CHR_ALT_FORM   = '#',       /* use alternate format */
  CHR_FIELD_SEP  = '.',       /* separates width and precision fields */
  CHR_CUR_TIME   = 't',       /* current time, Unix format */
  CHR_CUR_YEAR   = 'y',       /* current year */
  CHR_CUR_MONTH  = 'm',       /* current month */
  CHR_CUR_MDAY   = 'l',       /* current day of month */
  CHR_CUR_HOUR   = 'h',       /* current hour */
  CHR_CUR_MIN    = 'j',       /* current minute */
  CHR_CUR_SEC    = 'k',       /* current second */
  CHR_UP_TIME    = 'T',       /* uptime, Unix format */
  CHR_UP_YEAR    = 'Y',       /* uptime year */
  CHR_UP_MONTH   = 'M',       /* uptime month */
  CHR_UP_MDAY    = 'L',       /* uptime day of month */
  CHR_UP_HOUR    = 'H',       /* uptime hour */
  CHR_UP_MIN     = 'J',       /* uptime minute */
  CHR_UP_SEC     = 'K',       /* uptime second */
  CHR_AGENT_IP   = 'a',       /* agent's IP address */
  CHR_AGENT_NAME = 'A',       /* agent's host name if available */
  CHR_PDU_IP     = 'b',       /* PDU's IP address */
  CHR_PDU_NAME   = 'B',       /* PDU's host name if available */
  CHR_PDU_ENT    = 'N',       /* PDU's enterprise string */
  CHR_PDU_WRAP	 = 'P',       /* PDU's wrapper info (community, security) */
  CHR_TRAP_NUM   = 'w',       /* trap number */
  CHR_TRAP_DESC  = 'W',       /* trap's description (textual) */
  CHR_TRAP_STYPE = 'q',       /* trap's subtype */
  CHR_TRAP_VARS  = 'v'        /* tab-separated list of trap's variables */
} parse_chr_type;

/* These symbols define the states for the parser's state machine */
typedef enum {
  PARSE_NORMAL,              /* looking for next character */
  PARSE_BACKSLASH,           /* saw a backslash */
  PARSE_IN_FORMAT,           /* saw a % sign, in a format command */
  PARSE_GET_WIDTH,           /* getting field width */
  PARSE_GET_PRECISION        /* getting field precision */
} parse_state_type;

/* macros */

#define is_cur_time_cmd(chr) ((((chr) == CHR_CUR_TIME)     \
			       || ((chr) == CHR_CUR_YEAR)  \
			       || ((chr) == CHR_CUR_MONTH) \
			       || ((chr) == CHR_CUR_MDAY)  \
			       || ((chr) == CHR_CUR_HOUR)  \
			       || ((chr) == CHR_CUR_MIN)   \
			       || ((chr) == CHR_CUR_SEC)) ? TRUE : FALSE)
     /*
      * Function:
      *    Returns true if the character is a format command that outputs
      * some field that deals with the current time.
      *
      * Input Parameters:
      *    chr - character to check
      */

#define is_up_time_cmd(chr) ((((chr) == CHR_UP_TIME)     \
			      || ((chr) == CHR_UP_YEAR)  \
			      || ((chr) == CHR_UP_MONTH) \
			      || ((chr) == CHR_UP_MDAY)  \
			      || ((chr) == CHR_UP_HOUR)  \
			      || ((chr) == CHR_UP_MIN)   \
			      || ((chr) == CHR_UP_SEC)) ? TRUE : FALSE)
     /*
      * Function:
      *    Returns true if the character is a format command that outputs
      * some field that deals with up-time.
      *
      * Input Parameters:
      *    chr - character to check
      */

#define is_agent_cmd(chr) ((((chr) == CHR_AGENT_IP) \
			    || ((chr) == CHR_AGENT_NAME)) ? TRUE : FALSE)
     /*
      * Function:
      *    Returns true if the character outputs information about the
      * agent.
      *
      * Input Parameters:
      *    chr - the character to check
      */

#define is_pdu_ip_cmd(chr) ((((chr) == CHR_PDU_IP)   \
			  || ((chr) == CHR_PDU_NAME)) ? TRUE : FALSE)
     /*
      * Function:
      *    Returns true if the character outputs information about the PDU's
      * host name or IP address.
      *
      * Input Parameters:
      *    chr - the character to check
      */

#define is_trap_cmd(chr) ((((chr) == CHR_TRAP_NUM)      \
			   || ((chr) == CHR_TRAP_DESC)  \
			   || ((chr) == CHR_TRAP_STYPE) \
			   || ((chr) == CHR_TRAP_VARS)) ? TRUE : FALSE)

     /*
      * Function:
      *    Returns true if the character outputs information about the trap.
      *
      * Input Parameters:
      *    chr - the character to check
      */

#define is_fmt_cmd(chr) ((is_cur_time_cmd (chr)     \
			  || is_up_time_cmd (chr)   \
			  || is_agent_cmd (chr)     \
			  || is_pdu_ip_cmd (chr)    \
                          || ((chr) == CHR_PDU_ENT) \
                          || ((chr) == CHR_PDU_WRAP) \
			  || is_trap_cmd (chr)) ? TRUE : FALSE)
     /*
      * Function:
      *    Returns true if the character is a format command.
      * 
      * Input Parameters:
      *    chr - character to check
      */

#define is_numeric_cmd(chr) ((is_cur_time_cmd(chr)   \
			      || is_up_time_cmd(chr) \
			      || (chr) == CHR_TRAP_NUM) ? TRUE : FALSE)
     /*
      * Function:
      *    Returns true if this is a numeric format command.
      *
      * Input Parameters:
      *    chr - character to check
      */

#define reference(var) ((var) == (var))

     /*
      * Function:
      *    Some compiler options will tell the compiler to be picky and
      * warn you if you pass a parameter to a function but don't use it.
      * This macro lets you reference a parameter so that the compiler won't
      * generate the warning. It has no other effect.
      *
      * Input Parameters:
      *    var - the parameter to reference
      */

/* prototypes */
extern const char * trap_description (int trap);

static void
init_options (options_type *options)

     /*
      * Function:
      *    Initialize a structure that contains the option settings for
      * a format command.
      *
      * Input Parameters:
      *    options - points to the structure to initialize
      */
{
  /* initialize the structure's fields */
  options->cmd = '*';
  options->width = 0;
  options->precision = UNDEF_PRECISION;
  options->left_justify = FALSE;
  options->alt_format = FALSE;
  options->leading_zeroes = FALSE;
  return;
}


static int
realloc_output_temp_bfr(u_char **buf, size_t *buf_len, size_t *out_len,
			int allow_realloc,
			u_char **temp_buf, options_type *options)

     /*
      * Function:
      *    Append the contents of the temporary buffer to the specified
      * buffer using the correct justification, leading zeroes, width,
      * precision, and other characteristics specified in the options
      * structure.
      *
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    temp_buf - pointer to string to append onto output buffer.  THIS
      *               STRING IS free()d BY THIS FUNCTION.
      *    options  - what options to use when appending string
      */
{
  size_t temp_len;           /* length of temporary buffer */
  size_t temp_to_write;      /* # of chars to write from temp bfr */
  size_t char_to_write;      /* # of other chars to write */
  size_t zeroes_to_write;    /* fill to precision with zeroes for numbers */

  if (temp_buf == NULL || *temp_buf == NULL) {
    return 1;
  }

  /* 
   * Figure out how many characters are in the temporary buffer now,
   * and how many of them we'll write.
   */
  temp_len = strlen((char*)*temp_buf);
  temp_to_write = temp_len;

  if (options->precision != UNDEF_PRECISION && 
      temp_to_write > options->precision) {
    temp_to_write = options->precision;
  }

  /*  Handle leading characters.  */
  if ((!options->left_justify) && (temp_to_write < options->width)) {
    zeroes_to_write = options->precision - temp_to_write;
    if (!is_numeric_cmd(options->cmd)) {
      zeroes_to_write = 0;
    }

    for (char_to_write = options->width - temp_to_write;
	 char_to_write > 0;
	 char_to_write--) {
      if ((*out_len + 1) >= *buf_len) {
	if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	  *(*buf + *out_len) = '\0';
	  free(*temp_buf);
	  return 0;
	}
      }
      if (options->leading_zeroes || zeroes_to_write-- > 0) {
	*(*buf + *out_len) = '0';
      } else {
	*(*buf + *out_len) = ' ';
      }
      (*out_len)++;
    }
  }

  /*  Truncate the temporary buffer and append its contents.  */
  *(*temp_buf + temp_to_write) = '\0';
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, *temp_buf)) {
    free(*temp_buf);
    return 0;
  }

  /*  Handle trailing characters.  */
  if ((options->left_justify) && (temp_to_write < options->width)) {
    for (char_to_write = options->width - temp_to_write;
	 char_to_write > 0;
	 char_to_write--) {
      if ((*out_len + 1) >= *buf_len) {
	if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	  *(*buf + *out_len) = '\0';
	  free(*temp_buf);
	  return 0;
	}
      }
      *(*buf + *out_len) = '0';
      (*out_len)++;
    }
  }

  /*  Slap on a trailing \0 for good measure.  */

  *(*buf + *out_len) = '\0';  
  free(*temp_buf);
  *temp_buf = NULL;
  return 1;
}


static int
realloc_handle_time_fmt (u_char **buf, size_t *buf_len, size_t *out_len,
			 int allow_realloc,
			 options_type *options, struct snmp_pdu *pdu)

     /*
      * Function:
      *    Handle a format command that deals with the current or up-time.
      * Append the correct time information to the buffer subject to the
      * buffer's length limit.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    options - options governing how to write the field
      *    pdu     - information about this trap
      */
{
  time_t        time_val;           /* the time value to output */
  unsigned long	time_ul;	    /* u_long time/timeticks */
  struct tm *   parsed_time;        /* parsed version of current time */
  char	*safe_bfr = NULL;
  char          fmt_cmd = options->cmd; /* the format command to use */
  int           offset = 0;         /* offset into string to display */
  int           year_len;           /* length of year string */

  if ((safe_bfr = (char *)calloc(30, 1)) == NULL) {
      return 0;
  }

  /*  Get the time field to output.  */
  if (is_up_time_cmd(fmt_cmd)) {
    time_ul = pdu->time;
  } else {
    /*  Note: a time_t is a signed long.  */
    time(&time_val);
    time_ul = (unsigned long)time_val;
  }

  /*  Handle output in Unix time format.  */
  if (fmt_cmd == CHR_CUR_TIME) {
    sprintf(safe_bfr, "%lu", time_ul);
  } else if (fmt_cmd == CHR_UP_TIME && !options->alt_format) {
    sprintf(safe_bfr, "%lu", time_ul);
  } else if (fmt_cmd == CHR_UP_TIME) {
    unsigned int centisecs, seconds, minutes, hours, days;

    centisecs = time_ul % 100;
    time_ul /= 100;
    days = time_ul / (60 * 60 * 24);
    time_ul %= (60 * 60 * 24);

    hours = time_ul / (60 * 60);
    time_ul %= (60 * 60);

    minutes = time_ul / 60;
    seconds = time_ul % 60;

    switch (days) {
    case 0:
      sprintf(safe_bfr, "%u:%02u:%02u.%02u",
	      hours, minutes, seconds, centisecs);
      break;
    case 1:
      sprintf(safe_bfr, "1 day, %u:%02u:%02u.%02u",
	      hours, minutes, seconds, centisecs);
      break;
    default:
      sprintf(safe_bfr, "%u days, %u:%02u:%02u.%02u",
	      days, hours, minutes, seconds, centisecs);
    }
  } else {
    /*  Handle other time fields.  */

    if (options->alt_format) {
      parsed_time = gmtime(&time_val);
    } else {
      parsed_time = localtime(&time_val);
    }

    switch (fmt_cmd) {

      /* 
       * Output year. The year field is unusual: if there's a restriction 
       * on precision, we want to truncate from the left of the number,
       * not the right, so someone printing the year 1972 with 2 digit 
       * precision gets "72" not "19".
       */
    case CHR_CUR_YEAR:
    case CHR_UP_YEAR:
	sprintf(safe_bfr, "%d", parsed_time->tm_year + 1900);
	if (options->precision != UNDEF_PRECISION) {
	    year_len = (unsigned long)strlen(safe_bfr);
	    if (year_len > options->precision)
		offset = year_len - options->precision;
	}
	break;

	/* output month */
    case CHR_CUR_MONTH:
    case CHR_UP_MONTH:
	sprintf(safe_bfr, "%d", parsed_time->tm_mon + 1);
	break;

	/* output day of month */
    case CHR_CUR_MDAY:
    case CHR_UP_MDAY:
	sprintf(safe_bfr, "%d", parsed_time->tm_mday);
	break;

	/* output hour */
    case CHR_CUR_HOUR:
    case CHR_UP_HOUR:
	sprintf(safe_bfr, "%d", parsed_time->tm_hour);
	break;

	/* output minute */
    case CHR_CUR_MIN:
    case CHR_UP_MIN:
	sprintf(safe_bfr, "%d", parsed_time->tm_min);
	break;

      /* output second */
    case CHR_CUR_SEC:
    case CHR_UP_SEC:
	sprintf(safe_bfr, "%d", parsed_time->tm_sec);
	break;

	/* unknown format command - just output the character */
    default:
	sprintf(safe_bfr, "%c", fmt_cmd);
    }
  }

  /*  Output with correct justification, leading zeroes, etc.  */
  return realloc_output_temp_bfr(buf, buf_len, out_len, allow_realloc,
				 (u_char**)&safe_bfr, options);
}


static int
realloc_handle_ip_fmt(u_char **buf, size_t *buf_len, size_t *out_len,
		      int allow_realloc,
		      options_type *options, struct snmp_pdu *pdu)

     /*
      * Function:
      *     Handle a format command that deals with an IP address 
      * or host name.  Append the information to the buffer subject to
      * the buffer's length limit.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    options   - options governing how to write the field
      *    pdu       - information about this trap 
      */
{
  struct sockaddr_in * ip_addr;       /* IP address to output */
  struct hostent *     host;          /* corresponding host name */
  char                 fmt_cmd = options->cmd; /* what we're formatting */
  u_char *temp_buf = NULL;
  size_t temp_buf_len = 64, temp_out_len = 0;

  if ((temp_buf = (u_char*)calloc(temp_buf_len, 1)) == NULL) {
    return 0;
  }

  /*  Figure out which IP address to write.  */
  if (is_agent_cmd(fmt_cmd)) {
    ip_addr = (struct sockaddr_in *) &(pdu->agent_addr);
  } else {
    ip_addr = (struct sockaddr_in *) &(pdu->address);
  }

  /*  Decide exactly what to output.  */
  switch (fmt_cmd) {
  case CHR_AGENT_IP:
  case CHR_PDU_IP:
    /*  Write a numerical address.  */
    if (!snmp_strcat(&temp_buf, &temp_buf_len, &temp_out_len, 1,
		     (u_char*)inet_ntoa(ip_addr->sin_addr))) {
      if (temp_buf != NULL) {
	free(temp_buf);
      }
      return 0;
    }
    break;

  case CHR_AGENT_NAME:
  case CHR_PDU_NAME:
    /*  Write a host name.  */
    host = gethostbyaddr((char *) &(ip_addr->sin_addr),
			 sizeof(ip_addr->sin_addr), AF_INET);
    if (host != NULL) {
      if (!snmp_strcat(&temp_buf, &temp_buf_len, &temp_out_len, 1,
		       (u_char*)host->h_name)) {
	if (temp_buf != NULL) {
	  free(temp_buf);
	}
	return 0;
      }
    } else {
      if (!snmp_strcat(&temp_buf, &temp_buf_len, &temp_out_len, 1,
		       (u_char*)inet_ntoa(ip_addr->sin_addr))) {
	if (temp_buf != NULL) {
	  free(temp_buf);
	}
	return 0;
      }
    }
    break;

    /*  Don't know how to handle this command - write the character itself.  */
    default:
      temp_buf[0] = fmt_cmd;
    }

  /*  Output with correct justification, leading zeroes, etc.  */
  return realloc_output_temp_bfr(buf, buf_len, out_len, allow_realloc,
				 &temp_buf, options);
}


static int
realloc_handle_ent_fmt (u_char **buf, size_t *buf_len, size_t *out_len,
			int allow_realloc, 
			options_type *options, struct snmp_pdu *pdu)

     /*
      * Function:
      *     Handle a format command that deals with the enterprise 
      * string.  Append the information to the buffer subject to the
      * buffer's length limit.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    options - options governing how to write the field
      *    pdu     - information about this trap 
      */
{
  char fmt_cmd = options->cmd;   /* what we're formatting */
  u_char *temp_buf = NULL;
  size_t temp_buf_len = 64, temp_out_len = 0;

  if ((temp_buf = (u_char*)calloc(temp_buf_len, 1)) == NULL) {
    return 0;
  }

  /*  Decide exactly what to output.  */
  switch (fmt_cmd) {
  case CHR_PDU_ENT:
    /*  Write the enterprise oid.  */
    if (!sprint_realloc_objid(&temp_buf, &temp_buf_len, &temp_out_len, 1,
			      pdu->enterprise, pdu->enterprise_length)) {
      free(temp_buf);
      return 0;
    }
    break;

    /*  Don't know how to handle this command - write the character itself.  */
    default:
      temp_buf[0] = fmt_cmd;
    }

  /*  Output with correct justification, leading zeroes, etc.  */
  return realloc_output_temp_bfr(buf, buf_len, out_len, allow_realloc,
				 &temp_buf, options);
}


static int
realloc_handle_trap_fmt(u_char **buf, size_t *buf_len, size_t *out_len,
			int allow_realloc,
			options_type *options, struct snmp_pdu *pdu)

     /*
      * Function:
      *     Handle a format command that deals with the trap itself. 
      * Append the information to the buffer subject to the buffer's 
      * length limit.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    options - options governing how to write the field
      *    pdu     - information about this trap 
      */
{
  struct variable_list * vars;                  /* variables assoc with trap */
  char                   fmt_cmd = options->cmd; /* what we're outputting */
  u_char 	         *temp_buf = NULL;
  size_t		 tbuf_len = 64, tout_len = 0;

  if ((temp_buf = (u_char *)malloc(tbuf_len)) == NULL) {
    return 0;
  }

  /*  Decide exactly what to output.  */
  switch (fmt_cmd) {
  case CHR_TRAP_NUM:
    /*  Write the trap's number.  */
      tout_len = sprintf((char*)temp_buf, "%ld", pdu->trap_type);
    break;

  case CHR_TRAP_DESC:
    /*  Write the trap's description.  */
    tout_len = sprintf((char*)temp_buf, "%s", trap_description(pdu->trap_type));
    break;

  case CHR_TRAP_STYPE:
    /*  Write the trap's subtype.  */
    if (pdu->trap_type != SNMP_TRAP_ENTERPRISESPECIFIC) {
      tout_len = sprintf((char*)temp_buf, "%ld", pdu->specific_type);
    } else {
      /*  Get object ID for the trap.  */
      size_t obuf_len = 64, oout_len = 0, trap_oid_len = 0;
      oid trap_oid[MAX_OID_LEN + 2] = { 0 };
      u_char *obuf = NULL;
      char *ptr = NULL;

      if ((obuf = (u_char *)calloc(obuf_len, 1)) == NULL) {
	free(temp_buf);
	return 0;
      }

      trap_oid_len = pdu->enterprise_length;
      memcpy(trap_oid, pdu->enterprise, trap_oid_len*sizeof(oid));
      if (trap_oid[trap_oid_len - 1] != 0) {
	trap_oid[trap_oid_len] = 0;
	trap_oid_len++;
      }
      trap_oid[trap_oid_len] = pdu->specific_type;
      trap_oid_len++;

      /*  Find the element after the last dot.  */
      if (!sprint_realloc_objid(&obuf, &obuf_len, &oout_len, 1,
				trap_oid, trap_oid_len)) {
	if (obuf != NULL) {
	  free(obuf);
	}
	free(temp_buf);
      }

      ptr = strrchr((char *)obuf, '.');
      if (ptr != NULL) {
	if (!snmp_strcat(&temp_buf, &tbuf_len, &tout_len, 1, (u_char*)ptr)) {
	  free(obuf);
	  if (temp_buf != NULL) {	
	    free(temp_buf);
	  }
	  return 0;
	}
	free(obuf);
      } else {
	free(temp_buf);
	temp_buf = obuf;
	tbuf_len = obuf_len;
	tout_len = oout_len;
      }
    }
    break;

  case CHR_TRAP_VARS:
    /*  Write the trap's variables.  */
    for (vars = pdu->variables; vars != NULL; vars = vars->next_variable) {
      if (options->alt_format) {
	if (!snmp_strcat(&temp_buf, &tbuf_len, &tout_len, 1, (const u_char*)", ")) {
	  if (temp_buf != NULL) {
	    free(temp_buf);
	  }
	  return 0;
	}
      } else {
	if (!snmp_strcat(&temp_buf, &tbuf_len, &tout_len, 1, (const u_char*)"\t")) {
	  if (temp_buf != NULL) {
	    free(temp_buf);
	  }
	  return 0;
	}
      }
      if (!sprint_realloc_variable(&temp_buf, &tbuf_len, &tout_len, 1,
				   vars->name, vars->name_length, vars)) {
	if (temp_buf != NULL) {
	  free(temp_buf);
	}
	return 0;
      }
    }
    break;

  default:
    /*  Don't know how to handle this command - write the character itself.  */
    temp_buf[0] = fmt_cmd;
  }

  /*  Output with correct justification, leading zeroes, etc.  */
  return realloc_output_temp_bfr(buf, buf_len, out_len, allow_realloc,
				 &temp_buf, options);
}


static int
realloc_handle_wrap_fmt(u_char **buf, size_t *buf_len, size_t *out_len,
			int allow_realloc, struct snmp_pdu *pdu)
{
  size_t i = 0;
  
  switch (pdu->command) {
  case SNMP_MSG_TRAP:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"TRAP")) {
      return 0;
    }
    break;
  case SNMP_MSG_TRAP2:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"TRAP2")) {
      return 0;
    }
    break;
  case SNMP_MSG_INFORM:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"INFORM")) {
      return 0;
    }
    break;
  }

  switch (pdu->version) {
  case SNMP_VERSION_1:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)", SNMP v1")) {
      return 0;
    }
    break;
  case SNMP_VERSION_2c:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)", SNMP v2c")) {
      return 0;
    }
    break;
  case SNMP_VERSION_3:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)", SNMP v3")) {
      return 0;
    }
    break;
  }

  switch (pdu->version) {
  case SNMP_VERSION_1:
  case SNMP_VERSION_2c:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)", community ")) {
      return 0;
    }

    while ((*out_len + pdu->community_len + 1) >= *buf_len) {
      if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	return 0;
      }
    }

    for (i = 0; i < pdu->community_len; i++) {
      if (isprint(pdu->community[i])) {
	*(*buf + *out_len) = pdu->community[i];
      } else {
	*(*buf + *out_len) = '.';
      }
      (*out_len)++;
    }
    *(*buf + *out_len) = '\0';
    break;
  case SNMP_VERSION_3:
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)", user ")) {
      return 0;
    }

    while ((*out_len + pdu->securityNameLen + 1) >= *buf_len) {
      if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	return 0;
      }
    }

    for (i = 0; i < pdu->securityNameLen; i++) {
      if (isprint(pdu->securityName[i])) { 
	*(*buf + *out_len) = pdu->securityName[i];
      } else {
	*(*buf + *out_len) = '.';
      }
      (*out_len)++;
    }
    *(*buf + *out_len) = '\0';

    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)", context ")) {
      return 0;
    }

    while ((*out_len + pdu->contextNameLen + 1) >= *buf_len) {
      if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	return 0;
      }
    }

    for (i = 0; i < pdu->contextNameLen; i++) {
      if (isprint(pdu->contextName[i])) {
	*(*buf + *out_len) = pdu->contextName[i];
      } else {
	*(*buf + *out_len) = '.';
      }
      (*out_len)++;
    }
    *(*buf + *out_len) = '\0';
  }
  return 1;
}


static int
realloc_dispatch_format_cmd(u_char **buf, size_t *buf_len, size_t *out_len,
			    int allow_realloc,
			    options_type *options, struct snmp_pdu *pdu)

     /*
      * Function:
      *     Dispatch a format command to the appropriate command handler.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    options   - options governing how to write the field
      *    pdu       - information about this trap
      */
{
  char fmt_cmd = options->cmd;          /* for speed */

  /* choose the appropriate command handler */

  if (is_cur_time_cmd(fmt_cmd) || is_up_time_cmd(fmt_cmd)) {
    return realloc_handle_time_fmt(buf, buf_len, out_len, allow_realloc,
				   options, pdu);
  } else if (is_agent_cmd(fmt_cmd) || is_pdu_ip_cmd(fmt_cmd)) {
    return realloc_handle_ip_fmt(buf, buf_len, out_len, allow_realloc, 
				 options, pdu);
  } else if (is_trap_cmd(fmt_cmd)) {
    return realloc_handle_trap_fmt(buf, buf_len, out_len, allow_realloc, 
				   options, pdu);
  } else if (fmt_cmd == CHR_PDU_ENT) {
    return realloc_handle_ent_fmt(buf, buf_len, out_len, allow_realloc,
				  options, pdu);
  } else if (fmt_cmd == CHR_PDU_WRAP) {
    return realloc_handle_wrap_fmt(buf, buf_len, out_len, allow_realloc,
				   pdu);
  } else {
    /* unknown format command - just output the character */
    char fmt_cmd_string[2] = { 0, 0 };
    fmt_cmd_string[0] = fmt_cmd;

    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)fmt_cmd_string);
  }
}


static int
realloc_handle_backslash(u_char **buf, size_t *buf_len, size_t *out_len,
			 int allow_realloc, char fmt_cmd)

     /*
      * Function:
      *     Handle a character following a backslash. Append the resulting 
      * character to the buffer subject to the buffer's length limit.
      *     This routine currently isn't sophisticated enough to handle
      * \nnn or \xhh formats.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    fmt_cmd - the character after the backslash
      */
{
  char temp_bfr[3];                     /* for bulding temporary strings */

  /* select the proper output character(s) */
  switch (fmt_cmd) {
  case 'a':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\a");
  case 'b':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\b");
  case 'f':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\f");
  case 'n':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\n");
  case 'r':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\r");
  case 't':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\t");
  case 'v':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\v");
  case '\\':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\\");
  case '?':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\?");
  case '\'':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\'");
  case '"':
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\"");
  default:
    sprintf(temp_bfr, "\\%c", fmt_cmd);
    return snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)temp_bfr);
  }
}


int
realloc_format_plain_trap (u_char **buf, size_t *buf_len, size_t *out_len,
			   int allow_realloc,
			   struct snmp_pdu *pdu)

     /*
      * Function:
      *    Format the trap information in the default way and put the results
      * into the buffer, truncating at the buffer's length limit. This
      * routine returns 1 if the output was completed successfully or
      * 0 if it is truncated due to a memory allocation failure.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    pdu       - the pdu information
      */
{
  time_t                 now;                   /* the current time */
  struct tm *            now_parsed;            /* time in struct format */
  char                   safe_bfr[200];         /* holds other strings */
  struct sockaddr_in *   agent_ip;              /* agent's IP info */
  struct sockaddr_in *   pdu_ip;                /* PDU's IP info */
  struct hostent *       host;                  /* host name */
  struct variable_list * vars;                  /* variables assoc with trap */

  if (buf == NULL) {
    return 0;
  }

  /* 
   * Print the current time. Since we don't know how long the buffer is,
   * and snprintf isn't yet standard, build the timestamp in a separate
   * buffer of guaranteed length and then copy it to the output buffer.
   */
  time(&now);
  now_parsed = localtime(&now);
  sprintf(safe_bfr, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d ",
	  now_parsed->tm_year + 1900, now_parsed->tm_mon + 1,
	  now_parsed->tm_mday, now_parsed->tm_hour,
	  now_parsed->tm_min, now_parsed->tm_sec);
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)safe_bfr)) {
    return 0;
  }

  /*  Get info about the sender.  */
  agent_ip = (struct sockaddr_in *)&(pdu->agent_addr);
  host = gethostbyaddr((char *)&(agent_ip->sin_addr),
			sizeof(agent_ip->sin_addr), AF_INET);
  if (host != (struct hostent *)NULL) {
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)host->h_name)) {
      return 0;
    }
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)" [")) {
      return 0;
    }
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc,
		     (const u_char*)inet_ntoa(agent_ip->sin_addr))) {
      return 0;
    }
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"] ")) {
      return 0;
    }
  } else {
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc,
		     (const u_char*)inet_ntoa(agent_ip->sin_addr))) {
      return 0;
    }
  }

  /*  Append PDU IP info if necessary.  */
  pdu_ip = (struct sockaddr_in *) &(pdu->address);
  if (agent_ip->sin_addr.s_addr != pdu_ip->sin_addr.s_addr) {
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"(via ")) {
      return 0;
    }
    host = gethostbyaddr((char *) &(pdu_ip->sin_addr),
			  sizeof(pdu_ip->sin_addr), AF_INET);
    if (host != (struct hostent *) NULL) {
      if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)host->h_name)) {
	return 0;
      }
    } else {
      if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)inet_ntoa(pdu_ip->sin_addr))) {
	return 0;
      }
    }
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)" [")) {
      return 0;
    }
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)inet_ntoa(pdu_ip->sin_addr))) {
      return 0;
    }
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"]) ")) {
      return 0;
    }
  }

  /*  Add security wrapper information.  */
  if (!realloc_handle_wrap_fmt(buf, buf_len, out_len, allow_realloc, pdu)) {
    return 0;
  }

  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\n\t")) {
    return 0;
  }

  /*  Add enterprise information.  */
  if (!sprint_realloc_objid(buf, buf_len, out_len, allow_realloc,
			    pdu->enterprise, pdu->enterprise_length)) {
    return 0;
  }

  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)" ")) {
    return 0;
  }
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc,
		   (const u_char*)trap_description(pdu->trap_type))) {
    return 0;
  }
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*) " Trap (")) {
    return 0;
  }

  /*  Handle enterprise specific traps.  */
  if (pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC) {
    size_t obuf_len = 64, oout_len = 0, trap_oid_len = 0;
    oid trap_oid[MAX_OID_LEN + 2] = { 0 };
    char *ent_spec_code = NULL;
    u_char *obuf = NULL;

    if ((obuf = (u_char *)calloc(obuf_len, 1)) == NULL) {
      return 0;
    }

    /*  Get object ID for the trap.  */
    trap_oid_len = pdu->enterprise_length;
    memcpy(trap_oid, pdu->enterprise, trap_oid_len*sizeof(oid));
    if (trap_oid[trap_oid_len - 1] != 0) {
      trap_oid[trap_oid_len] = 0;
      trap_oid_len++;
    }
    trap_oid[trap_oid_len] = pdu->specific_type;
    trap_oid_len++;

    /*  Find the element after the last dot.  */
    if (!sprint_realloc_objid(&obuf, &obuf_len, &oout_len, 1,
			      trap_oid, trap_oid_len)) {
      if (obuf != NULL) {
	free(obuf);
      }
      return 0;
    } 
    ent_spec_code = strrchr((char *)obuf, '.');
    if (ent_spec_code != NULL) {
      ent_spec_code++;
    } else {
      ent_spec_code = (char*)obuf;
    }

    /*  Print trap info.  */
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)ent_spec_code)) {
      free(obuf);
      return 0;
    }
    free(obuf);
  } else {
    /*  Handle traps that aren't enterprise specific.  */
    sprintf(safe_bfr, "%ld", pdu->specific_type);
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)safe_bfr)) {
      return 0;
    }
  }

  /*  Finish the line.  */
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)") Uptime: ")) {
    return 0;
  }
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc,
		   (const u_char*)uptime_string(pdu->time, safe_bfr))) {
    return 0;
  }
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\n")) {
    return 0;
  }

  /*  Finally, output the PDU variables. */
  for (vars = pdu->variables; vars != NULL; vars = vars->next_variable) {
    if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\t")) {
      return 0;
    }
    if (!sprint_realloc_variable(buf, buf_len, out_len, allow_realloc,
				 vars->name, vars->name_length, vars)) {
      return 0;
    }
  }
  if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, (const u_char*)"\n")) {
    return 0;
  }

  /*  String is already null-terminated.  That's all folks!  */
  return 1;
}


int
realloc_format_trap (u_char **buf, size_t *buf_len, size_t *out_len,
		     int allow_realloc, const char *format_str,
		     struct snmp_pdu *pdu)

     /*
      * Function:
      *    Format the trap information for display in a log. Place the results
      *    in the specified buffer (truncating to the length of the buffer).
      *    Returns the number of characters it put in the buffer.
      *
      * Input Parameters:
      *	   buf, buf_len, out_len, allow_realloc - standard relocatable
      *                                           buffer parameters
      *    format_str - specifies how to format the trap info
      *    pdu        - the pdu information
      */
{
  unsigned long    fmt_idx = 0;          /* index into the format string */
  options_type     options;              /* formatting options */
  parse_state_type state = PARSE_NORMAL; /* state of the parser */
  char             next_chr;             /* for speed */
  int              reset_options = TRUE; /* reset opts on next NORMAL state */

  if (buf == NULL) {
    return 0;
  }

  /*  Go until we reach the end of the format string:  */
  for (fmt_idx = 0;  format_str[fmt_idx] != '\0'; fmt_idx++) {
    next_chr = format_str[fmt_idx];
    switch (state) {
    case PARSE_NORMAL:
      /*  Looking for next character.  */
      if (reset_options) {
	init_options(&options);
	reset_options = FALSE;
      }
      if (next_chr == '\\') {
	state = PARSE_BACKSLASH;
      } else if (next_chr == CHR_FMT_DELIM) {
	state = PARSE_IN_FORMAT;
      }	else {
	if ((*out_len + 1) >= *buf_len) {
	  if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	    return 0;
	  }
	}
	*(*buf + *out_len) = next_chr;
	(*out_len)++;
      }
      break;

    case PARSE_BACKSLASH:
      /*  Found a backslash.  */
      if (!realloc_handle_backslash(buf, buf_len, out_len, allow_realloc,
				    next_chr)) {
	return 0;
      }
      state = PARSE_NORMAL;
      break;
	
    case PARSE_IN_FORMAT:
      /*  In a format command.  */
      reset_options = TRUE;
      if (next_chr == CHR_LEFT_JUST) {
	options.left_justify = TRUE;
      } else if (next_chr == CHR_LEAD_ZERO) {
	options.leading_zeroes = TRUE;
      } else if (next_chr == CHR_ALT_FORM) {
	options.alt_format = TRUE;
      } else if (next_chr == CHR_FIELD_SEP) {
	state = PARSE_GET_PRECISION;
      } else if ((next_chr >= '1') && (next_chr <= '9')) {
	options.width = ((unsigned long) next_chr) - ((unsigned long) '0');
	state = PARSE_GET_WIDTH;
      } else if (is_fmt_cmd(next_chr)) {
	options.cmd = next_chr;
	if (!realloc_dispatch_format_cmd(buf, buf_len, out_len, allow_realloc,
					 &options, pdu)) {
	  return 0;
	}
	state = PARSE_NORMAL;
      }	else {
	if ((*out_len + 1) >= *buf_len) {
	  if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	    return 0;
	  }
	}
	*(*buf + *out_len) = next_chr;
	(*out_len)++;
	state = PARSE_NORMAL;
      }
      break;

    case PARSE_GET_WIDTH:
      /*  Parsing a width field.  */
      reset_options = TRUE;
      if (isdigit(next_chr)) {
	options.width *= 10;
	options.width += (unsigned long) next_chr - (unsigned long) '0';
      }	else if (next_chr == CHR_FIELD_SEP) {
	state = PARSE_GET_PRECISION;
      } else if (is_fmt_cmd(next_chr)) {
	options.cmd = next_chr;
	if (!realloc_dispatch_format_cmd(buf, buf_len, out_len, allow_realloc,
					 &options, pdu)) {
	  return 0;
	}
	state = PARSE_NORMAL;
      } else {
	if ((*out_len + 1) >= *buf_len) {
	  if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	    return 0;
	  }
	}
	*(*buf + *out_len) = next_chr;
	(*out_len)++;
	state = PARSE_NORMAL;
      }
      break;

    case PARSE_GET_PRECISION:
      /*  Parsing a precision field.  */
      reset_options = TRUE;
      if (isdigit(next_chr)) {
	if (options.precision == UNDEF_PRECISION) {
	  options.precision = (unsigned long) next_chr - (unsigned long) '0';
	} else {
	  options.precision *= 10;
	  options.precision += (unsigned long) next_chr - (unsigned long) '0';
	}
      } else if (is_fmt_cmd(next_chr)) {
	options.cmd = next_chr;
	if (options.width < options.precision) {
	  options.width = options.precision;
	}
	if (!realloc_dispatch_format_cmd(buf, buf_len, out_len, allow_realloc,
					 &options, pdu)) {
	  return 0;
	}
	state = PARSE_NORMAL;
      }	else {
	if ((*out_len + 1) >= *buf_len) {
	  if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	    return 0;
	  }
	}
	*(*buf + *out_len) = next_chr;
	(*out_len)++;
	state = PARSE_NORMAL;
      }
      break;

    default:
      /*  Unknown state.  */
      reset_options = TRUE;
      if ((*out_len + 1) >= *buf_len) {
	if (!(allow_realloc && snmp_realloc(buf, buf_len))) {
	  return 0;
	}
      }
      *(*buf + *out_len) = next_chr;
      (*out_len)++;
      state = PARSE_NORMAL;
    }
  }
  
  *(*buf + *out_len) = '\0';
  return 1;
}
