/*
 * 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 <net-snmp/net-snmp-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 <net-snmp/net-snmp-includes.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 */
    size_t          width;      /* the field's minimum width */
    size_t          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;

char            separator[32];

/*
 * 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_VARSEP = 'V',      /* character (or string) to separate variables */
    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_GET_SEPARATOR         /* getting field separator */
} 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, netsnmp_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 */
    size_t          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 = (size_t) 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, netsnmp_pdu *pdu,
                      netsnmp_transport *transport)

     /*
      * 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 
      *    transport - the transport descriptor
      */
{
    struct in_addr *agent_inaddr = (struct in_addr *) pdu->agent_addr;
    struct hostent *host = NULL;       /* 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 = calloc(temp_buf_len, 1)) == NULL) {
        return 0;
    }

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

    case CHR_AGENT_NAME:
        /*
         * Try to resolve the agent_addr field as a hostname; fall back
         * to numerical address.  
         */
        if (!netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
                                    NETSNMP_DS_APP_NUMERIC_IP)) {
            host = gethostbyaddr((char *) pdu->agent_addr, 4, AF_INET);
        }
        if (host != NULL) {
            if (!snmp_strcat(&temp_buf, &temp_buf_len, &temp_out_len, 1,
                             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,
                             inet_ntoa(*agent_inaddr))) {
                if (temp_buf != NULL) {
                    free(temp_buf);
                }
                return 0;
            }
        }
        break;

    case CHR_PDU_IP:
        /*
         * Write the numerical transport information.  
         */
        if (transport != NULL && transport->f_fmtaddr != NULL) {
            char           *tstr =
                transport->f_fmtaddr(transport, pdu->transport_data,
                                     pdu->transport_data_length);
            if (!snmp_strcat
                (&temp_buf, &temp_buf_len, &temp_out_len, 1, tstr)) {
                if (tstr != NULL) {
                    free(tstr);
                }
                if (temp_buf != NULL) {
                    free(temp_buf);
                }
                return 0;
            }
            if (tstr != NULL) {
                free(tstr);
            }
        } else {
            if (!snmp_strcat
                (&temp_buf, &temp_buf_len, &temp_out_len, 1,
                 "<UNKNOWN>")) {
                if (temp_buf != NULL) {
                    free(temp_buf);
                }
                return 0;
            }
        }
        break;

        /*
         * Write a host name.  
         */
    case CHR_PDU_NAME:
        /*
         * Right, apparently a name lookup is wanted.  This is only reasonable
         * for the UDP and TCP transport domains (we don't want to try to be
         * too clever here).  
         */
#ifdef SNMP_TRANSPORT_TCP_DOMAIN
        if (transport != NULL && (transport->domain == netsnmpUDPDomain ||
                                  transport->domain ==
                                  netsnmp_snmpTCPDomain)) {
#else
        if (transport != NULL && transport->domain == netsnmpUDPDomain) {
#endif
            /*
             * This is kind of bletcherous -- it breaks the opacity of
             * transport_data but never mind -- the alternative is a lot of
             * munging strings from f_fmtaddr.  
             */
            struct sockaddr_in *addr =
                (struct sockaddr_in *) pdu->transport_data;
            if (addr != NULL
                && pdu->transport_data_length ==
                sizeof(struct sockaddr_in)) {
                if (!netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
                                            NETSNMP_DS_APP_NUMERIC_IP)) {
                    host =
                        gethostbyaddr((char *) &(addr->sin_addr),
                                      sizeof(struct in_addr), AF_INET);
                }
                if (host != NULL) {
                    if (!snmp_strcat
                        (&temp_buf, &temp_buf_len, &temp_out_len, 1,
                         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,
                         inet_ntoa(addr->sin_addr))) {
                        if (temp_buf != NULL) {
                            free(temp_buf);
                        }
                        return 0;
                    }
                }
            } else {
                if (!snmp_strcat
                    (&temp_buf, &temp_buf_len, &temp_out_len, 1,
                     "<UNKNOWN>")) {
                    if (temp_buf != NULL) {
                        free(temp_buf);
                    }
                    return 0;
                }
            }
        } else if (transport != NULL && transport->f_fmtaddr != NULL) {
            /*
             * Some other domain for which we do not know how to do a name
             * lookup.  Fall back to the formatted transport address.  
             */
            char           *tstr =
                transport->f_fmtaddr(transport, pdu->transport_data,
                                     pdu->transport_data_length);
            if (!snmp_strcat
                (&temp_buf, &temp_buf_len, &temp_out_len, 1, tstr)) {
                if (tstr != NULL) {
                    free(tstr);
                }
                if (temp_buf != NULL) {
                    free(temp_buf);
                }
                return 0;
            }
            if (tstr != NULL) {
                free(tstr);
            }
        } else {
            /*
             * We are kind of stuck!  
             */
            if (!snmp_strcat(&temp_buf, &temp_buf_len, &temp_out_len, 1,
                             "<UNKNOWN>")) {
                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, netsnmp_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, netsnmp_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 
      */
{
    netsnmp_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;
    const char           *sep = separator;
    const char           *default_sep = "\t";
    const char           *default_alt_sep = ", ";

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

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

    case CHR_TRAP_DESC:
        /*
         * Write the trap's description.  
         */
        tout_len =
            sprintf(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(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);
		return 0;
            }

            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.  
         */
        if (!sep || !*sep)
            sep = (options->alt_format ? default_alt_sep : default_sep);
        for (vars = pdu->variables; vars != NULL;
             vars = vars->next_variable) {
            /*
             * Print a separator between variables,
             *   (plus beforehand if the alt format is used)
             */
            if (options->alt_format ||
                vars != pdu->variables ) {
                if (!snmp_strcat(&temp_buf, &tbuf_len, &tout_len, 1, sep)) {
                    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, netsnmp_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) {
#ifndef DISABLE_SNMPV1
    case SNMP_VERSION_1:
        if (!snmp_strcat
            (buf, buf_len, out_len, allow_realloc,
             (const u_char *) ", SNMP v1")) {
            return 0;
        }
        break;
#endif
#ifndef DISABLE_SNMPV2C
    case SNMP_VERSION_2c:
        if (!snmp_strcat
            (buf, buf_len, out_len, allow_realloc,
             (const u_char *) ", SNMP v2c")) {
            return 0;
        }
        break;
#endif
    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) {
#if !defined(DISABLE_SNMPV1) || !defined(DISABLE_SNMPV2C)
    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;
#endif
    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, netsnmp_pdu *pdu,
                            netsnmp_transport *transport)

     /*
      * 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
      *    transport - the transport descriptor
      */
{
    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, transport);
    } 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 *) "\'");
    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,
                          netsnmp_pdu *pdu, netsnmp_transport *transport)

     /*
      * 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
      *    transport - the transport descriptor
      */
{
    time_t          now;        /* the current time */
    struct tm      *now_parsed; /* time in struct format */
    char            safe_bfr[200];      /* holds other strings */
    struct in_addr *agent_inaddr = (struct in_addr *) pdu->agent_addr;
    struct hostent *host = NULL;       /* host name */
    netsnmp_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.  
     */
    if (!netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
                                NETSNMP_DS_APP_NUMERIC_IP)) {
        host = gethostbyaddr((char *) pdu->agent_addr, 4, 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_inaddr))) {
            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_inaddr))) {
            return 0;
        }
    }

    /*
     * Append PDU transport info.  
     */
    if (transport != NULL && transport->f_fmtaddr != NULL) {
        char           *tstr =
            transport->f_fmtaddr(transport, pdu->transport_data,
                                 pdu->transport_data_length);
        if (!snmp_strcat
            (buf, buf_len, out_len, allow_realloc,
             (const u_char *) "(via ")) {
            if (tstr != NULL) {
                free(tstr);
            }
            return 0;
        }
        if (!snmp_strcat(buf, buf_len, out_len, allow_realloc, tstr)) {
            if (tstr != NULL) {
                free(tstr);
            }
            return 0;
        }
        if (tstr != NULL) {
            free(tstr);
        }
        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,
                     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,
                    netsnmp_pdu *pdu, netsnmp_transport *transport)

     /*
      * 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
      *    transport  - the transport descriptor
      */
{
    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;
    }

    memset(separator, 0, sizeof(separator));
    /*
     * 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_GET_SEPARATOR:
            /*
             * Parse the separator character
             * XXX - Possibly need to handle quoted strings ??
             */
	    {   char *sep = separator;
		size_t i, j;
		i = sizeof(separator);
		j = 0;
		memset(separator, 0, i);
		while (j < i && next_chr && next_chr != CHR_FMT_DELIM) {
		    if (next_chr == '\\') {
			/*
			 * Handle backslash interpretation
			 * Print to "separator" string rather than the output buffer
			 *    (a bit of a hack, but it should work!)
			 */
			next_chr = format_str[++fmt_idx];
			if (!realloc_handle_backslash
			    ((u_char **)&sep, &i, &j, 0, next_chr)) {
			    return 0;
			}
		    } else {
			separator[j++] = next_chr;
		    }
		    next_chr = format_str[++fmt_idx];
		}
	    }
            state = PARSE_IN_FORMAT;
            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 == CHR_TRAP_VARSEP) {
                state = PARSE_GET_SEPARATOR;
            } 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,
                     transport)) {
                    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,
                     transport)) {
                    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,
                     transport)) {
                    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;
}
