/* ---------------------------------------------------------------------------
 *
 * snmptrapfmt.c - receive snmp trapd from a trap handler and log them
 *
 * formatted with `indent -gnu -di16 -nut -bls -bli0 -cli2 -cdb -sc -ts2 -ncdb -c33 -cs -npcs`
 * ---------------------------------------------------------------------------
 *  Copyright (C) 2001 Uwe Maier <Uwe.Maier@nice.de>
 *  Copyright (C) 2001 - 2005 Bernd Schumacher Hewlett Packard Consulting
 *                            <bernd.schumacher@hp.com>
 *  Copyright (C) 2005 - 2009 Jochen Friedrich <jochen@scram.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 *  See the GNU General Public License in the COPYING file at the
 *  root directory of this project for more details.
 *
 */

/* --- imprt section --- */
#include "snmptrapfmt.h"
#include "log.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/time.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>

#include <netinet/in.h>
#include <syslog.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/library/asn1.h>
#include <net-snmp/library/snmp.h>
#include <net-snmp/library/snmp_impl.h>
#include <time.h>
#include <ctype.h>

/* --- local defines and type --- */
#define FREEMEM(x) if(x){free(x);}

#define PNAME   "snmptrapfmt"
#define LOGFILE "/var/tmp/snmptrapfmt.trc"
#define USAGE   "Usage: snmptrapfmt [-f cfgfile] [-D] [-d level]\n"
#define CFGNAME "/etc/snmp/snmptrapfmt.conf"
#define PIDFILE "/var/run/snmptrapfmt.pid"

/* definitions of standard OIDs */
#define OID_SYM_SNMPTRAPADDRESS    ".iso.org.dod.internet.snmpV2.snmpModules.snmpCommunityMIB.snmpCommunityMIBObjects.snmpTrapAddress.0"
#define OID_NUM_SNMPTRAPADDRESS    ".1.3.6.1.6.3.18.1.3.0"

#define OID_SYM_SNMPTRAPCOMMUNITY  ".iso.org.dod.internet.snmpV2.snmpModules.snmpCommunityMIB.snmpCommunityMIBObjects.snmpTrapCommunity.0"
#define OID_NUM_SNMPTRAPCOMMUNITY  ".1.3.6.1.6.3.18.1.4.0"

#define OID_SYM_SNMPTRAPENTERPRISE ".iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTrap.snmpTrapEnterprise.0"
#define OID_NUM_SNMPTRAPENTERPRISE ".1.3.6.1.6.3.1.1.4.3.0"

typedef struct
{                               /* --- a trap variable --- */
  char           *v_oid;        /* the oid */
  char           *v_val;        /* the value */
}
t_var;

typedef struct
{                               /* --- a received trap --- */
  char           *s_agentname;  /* hostname of agent (in PDU) */
  char           *s_agentaddr;  /* IP of agent (in PDU) */
  char           *s_senderaddr; /* IP of trap sender (real) */
  char           *s_uptime;     /* uptime of agent */
  char           *s_trapoid;    /* trap OID (snmpV2 notation) */
  char           *s_community;  /* trap community */
  char           *s_enterprise; /* trap enterprise */
  int             n_var;        /* number of trap variables in the array */
  t_var          *a_var;        /* array of trap variables */
}
t_trap;

/* --- local variables --- */
int             o_loglev = 0;   /* log level */
char           *o_cfgname = CFGNAME;  /* configuration file name */
FILE           *o_logfile = NULL; /* log file handle */
long            pid;            /* value of already running process */

/* --- logfile configuration section --- */

/* logfile configuration constants */
#define OIDFMT_OID   0          /* print OIDs numerical */
#define OIDFMT_TEXT  1          /* print OIDs symbolically */
#define NODEFMT_IP   0          /* print nodenames as IP address */
#define NODEFMT_FQDN 1          /* print nodenames as FQDN */
#define NODEFMT_NAME 2          /* print short nodenames */

/* logfile configuration tokens */
#define TOK_SUBST   "SUBST"
#define TOK_OIDFMT  "OIDFMT"
#define TOK_NODEFMT "NODEFMT"
#define TOK_VARFMT  "VARFMT"
#define TOK_LOGFMT  "LOGFMT"
#define TOK_LOGFILE "LOGFILE"
#define TOK_CMUSNMP "CMUSNMP"

/* logfile configuration variables */
char            cfg_subst1 = ';'; /* subst this char in var values */
char            cfg_subst2 = ' '; /* by this char */
int             cfg_oidfmt = OIDFMT_OID;  /* format for OID's */
int             cfg_nodefmt = NODEFMT_FQDN; /* format for nodenames */
char           *cfg_varfmt = ";[%s] %n (%t) : %v";  /* format string for vars in '$*' */
char           *cfg_logfmt = "$x;$A;$e;$G;$S$*";  /* format for the logfile entry */
char           *cfg_logname = NULL;

/* --- local functions --- */
static void     lg_print_config();
static FILE    *lg_parse_config();
static char    *lg_format_buffer();
static void     lg_sprint_node();
static void     lg_sprint_oid();
static void     lg_sprint_int();
static void     lg_sprint_ulong();
static void     lg_sprint_ASNtype();
static void     lg_sprint_var();
static void     lg_sprint_string();

static void     free_trap(t_trap * p_trap);
static void     process_trapdata(FILE * f);
static int      read_trapdata(FILE * f, t_trap * p_trap);
static char    *copy_string(char *buf);
static int      read_oidval(FILE * f, char **pp_oid, char **pp_val,
                            char *msg);
static char    *get_generic_id(char *trapoid);
static char    *get_specific_id(char *trapoid);

static u_long   uptime_ticks(char *uptime);

static long     create_pidfile(void);

/* ------------------------------------------------------------------------- */

char           *
trap_description(trap)
     int             trap;
{
  switch (trap)
  {
    case 0:
      return "Cold Start";
    case 1:
      return "Warm Start";
    case 2:
      return "Link Down";
    case 3:
      return "Link Up";
    case 4:
      return "Authentication Failure";
    case 5:
      return "EGP Neighbor Loss";
    case 6:
      return "Enterprise Specific";
    default:
      return "Unknown Type";
  }
}

/* ------------------------------------------------------------------------- */

static          u_long
uptime_ticks(char *uptime)
{
  int             days;
  int             hours;
  int             mins;
  int             secs;
  int             csecs;
  u_long          ticks = 0;

  if (sscanf(uptime, "%d:%d:%d:%d.%d",
             &days, &hours, &mins, &secs, &csecs) == 5)
  {
    ticks = (60 * 60 * 24 * days +
             60 * 60 * hours + 60 * mins + secs) * 100 + csecs;
  }

  return ticks;
}

/* ------------------------------------------------------------------------- */

static long
create_pidfile(void)
{
  long            retval = -1;
  int             pidfile;

  /* create new pidfile - or open existing one without destroying it */
  if ((pidfile = open(PIDFILE, O_WRONLY | O_CREAT, 0644)) != -1)
  {
    /* File created or opened, now try to lock */
    if (flock(pidfile, LOCK_EX | LOCK_NB) == 0)
    {
      char            pid[11];

      /* we have been too nice when opening file - now start at beginning */
      ftruncate(pidfile, 0);
      lseek(pidfile, 0, SEEK_SET);

      sprintf(pid, "%d", getpid());
      write(pidfile, pid, strlen(pid));

      /* we don't close the file - so try to get output to file */
      fsync(pidfile);

      retval = 0;
    }
    else
    {
      /* file is locked by other process */
    }
  }
  else
  {
    retval = errno;
  }

  return retval;
}

/* ------------------------------------------------------------------------- */

int
main(argc, argv)
     int             argc;
     char           *argv[];
{
  int             fd_pipe;
  FILE           *f_pipe;
  fd_set          fdset;
  struct timeval  timeout;
  int             numfds;
  char            is_daemon = FALSE;
  int             c;

  /* Check the arguments */
  while (1)
  {
    int             option_index = 0;
    static struct option long_options[] = {
      {"D", 0, 0, 0},
      {"d", 1, 0, 0},
      {"f", 1, 0, 0},
      {0, 0, 0, 0}
    };

    c = getopt_long(argc, argv, "Dd:f:", long_options, &option_index);
    if (c == -1)
      break;

    switch (c)
    {
      case 'd':
        o_loglev = atoi(optarg);
        break;
      case 'D':
        is_daemon = TRUE;
        break;
      case 'f':
        o_cfgname = optarg;
        break;
      default:
        printf("Unknown argument %c", c);
    }
  }

  /* now go into daemon mode if commandline parameter is set */
  if (is_daemon)
  {
    if (daemon(0, 1) != 0)
    {
      /* error occured while going into daemon mode */
      fprintf(stderr, "Error while daemonizing (%s)\n", strerror(errno));
      fprintf(stderr, "Aborting ...\n");
      exit(1);
    }
  }

  /* check for existing process - allocate PID file */
  if (create_pidfile() != 0)
  {
    fprintf(stderr, "Error occured while creating pid file\n");
    fprintf(stderr,
            "      not running as root or other snmptrapfmt running?\n");
    fprintf(stderr, "Aborting ...\n");

    /* that's it - drop off now */
    exit(1);
  }

  /* In daemon mode, now redirect std* to /dev/null */
  if (is_daemon)
  {
    freopen("/dev/null", "r", stdin);
    freopen("/dev/null", "w", stdout);
    freopen("/dev/null", "w", stderr);
  }

  /* Init the logfile, non append mode, i.e. create a new one */
  loginit(PNAME, LOGFILE, o_loglev, 0);
  logging(1, "Starting ...");

  /* Parse the configuration file */
  o_logfile = lg_parse_config(o_cfgname);

  /* Print the logfile output specification */
  if (o_loglev > 0)
  {
    lg_print_config();
  }

  /* Create and open the pipe */
  logging(1, "Create the pipe '%s' ...", PIPENAME);
  if ((mkfifo(PIPENAME, S_IWUSR | S_IRUSR) == -1) && (errno != EEXIST))
  {
    logging(0, "Cannot create the pipe '%s' [%s]", PIPENAME, strerror(errno));

    exit(1);
  }

  fd_pipe = open(PIPENAME, O_RDWR, S_IWUSR | S_IRUSR);
  if (fd_pipe < 0)
  {
    logging(0, "Cannot open the pipe '%s' [%s]", PIPENAME, strerror(errno));

    exit(1);
  }

  f_pipe = fdopen(fd_pipe, "w+");
  if (f_pipe == NULL)
  {
    logging(0, "Cannot associate stream with file handle for pipe '%s' [%s]",
        PIPENAME, strerror(errno));

    exit(1);
  }
  logging(1, "Pipe opened for reading");

  /* Main loop: sit and wait until data arrives on the pipe */
  while (1)
  {
    FD_ZERO(&fdset);            /* wait for data on the pipe handle */
    FD_SET(fd_pipe, &fdset);
    timeout.tv_sec = 5;         /* wait 5 seconds for data */
    timeout.tv_usec = 0;

    numfds = select(fd_pipe + 1, &fdset, 0, 0, &timeout);
    switch (numfds)
    {
      case 0:
        logging(1, "Timeout occurred ...");
        break;
      case -1:
        if (errno == EINTR)
        {
          logging(1, "Interrupted select() - continue");
          continue;
        }
        else
        {
          logging(0, "Error from select() [%s]", strerror(errno));
          exit(1);
        }
        break;
      default:
        logging(1, "Data available ...");
        process_trapdata(f_pipe);
        break;
    }
  }

  /* not reached */
}

/* ------------------------------------------------------------------------- */

/* Parse the configuration file:
 * - check for the tokens and set the config variables
 * - open the logfile and return the file pointer (or NULL if no logfile)
 * - syntax of configuration file (fixed text in '')
 *   comment lines begin with '#'
 *     file    ::= line | file line
 *     line    ::= 'SUBST'   '=' '\'char'\'char'\'
 *               | 'OIDFMT'  '=' ( 'text' | 'oid' )
 *               | 'NODEFMT' '=' ( 'ip' | 'fqdn' | 'name' )
 *               | 'VARFMT'  '=' stringV
 *               | 'LOGFMT'  '=' stringL
 *               | 'LOGFILE' '=' string
 *               | 'CMUSNMP' '=' string                 >>>OBSOLETE<<<
 *     string  ::= '"' { char | '\"' } '"'
 *     stringV ::= '"' { char | '%s' | '%n' | '%t' | '%v' | '%%' } '"'
 *     stringL ::= '"' { char | '$#' | '$*' | '$x' | '$r' | '$R'
 *                            | '$e' | '$A' | '$G' | '$S' | '$T'
 *                            | '$$'
 *                            | '\n' | '\r' | '\t' | '\\' } '"'
 *
 * - Tokens and associated config variables are:
 *     SUBST       ... cfg_subst1, cfg_subst2   (both char)
 *     OIDFMT      ... cfg_oidfmt               (OIDFMT_TEXT,_OID)
 *     NODEFMT     ... cfg_nodefmt              (NODEFMT_IP, _FQDN, _NAME)
 *     VARFMT      ... cfg_varfmt               (char*)
 *     LOGFMT      ... cfg_logfmt               (char*)
 *     LOGFILE     ... cfg_logname              (char*)
 *     CMUSNMP     ... cfg_cmusnmp              (char*)    >>>OBSOLETE<<<
 *     
 */
static FILE    *
lg_parse_config(cfgname)
     char           *cfgname;
{
  FILE           *cfg;
  FILE           *f_log = NULL;
  char            lbuf[1024];
  int             lno = 0;


  logging(1, "Parsing config file '%s'", cfgname);

  if (!(cfg = fopen(cfgname, "r")))
  {
    logging(0, "Cannot open config file '%s' - %s\n", cfgname, strerror(errno));

    return NULL;
  }

  while (fgets(lbuf, sizeof(lbuf), cfg))
  {
    char           *p;

    logging(1, "Read line '%s'", lbuf);

    p = strtok(lbuf, "=\n");

    /* check for comment or empty lines */
    lno++;
    if (!p || (*p == '#'))
    {
      continue;
    }

    /* check for tokens */
    if (strcasecmp(TOK_SUBST, p) == 0)
    {
      char           *p1 = strtok(NULL, "\n");  /* the substitution */
      int             ok = 1;

      if (!p1 || !*p1 || (*p1 != '\\'))
      {
        ok = 0;
      }
      else
      {
        p1++;
        cfg_subst1 = *p1++;

        if (*p1++ == '\\' && *p1)
        {
          cfg_subst2 = *p1;
        }
        else
        {
          ok = 0;
        }
      }
      if (!ok)
      {
        logging(0, "Expected: %s=\\<char>\\<char>\\ in file '%s',%d",
            TOK_SUBST, cfgname, lno);
      }
    }
    else if (strcasecmp(TOK_OIDFMT, p) == 0)
    {
      char           *p1 = strtok(NULL, " \n");
      int             ok = 1;

      if (!p1 || !*p1)
      {
        ok = 0;
      }
      else if (strcasecmp("text", p1) == 0)
      {
#if 0
        cfg_oidfmt = OIDFMT_TEXT;
#else
        logging(0, "Error: Format for OID must be set in /etc/snmp/snmp.conf");

        cfg_oidfmt = OIDFMT_OID;
#endif
      }
      else if (strcasecmp("oid", p1) == 0)
      {
        cfg_oidfmt = OIDFMT_OID;
      }
      else
      {
        ok = 0;
      }
      if (!ok)
      {
        logging(0, "Expected: %s='text' | 'oid' in file '%s',%d",
            TOK_OIDFMT, cfgname, lno);
      }
    }
    else if (strcasecmp(TOK_NODEFMT, p) == 0)
    {
      char           *p1 = strtok(NULL, " \n");
      int             ok = 1;

      if (!p1)
      {
        ok = 0;
      }
      else if (strcasecmp("ip", p1) == 0)
      {
        cfg_nodefmt = NODEFMT_IP;
      }
      else if (strcasecmp("fqdn", p1) == 0)
      {
        cfg_nodefmt = NODEFMT_FQDN;
      }
      else if (strcasecmp("name", p1) == 0)
      {
        cfg_nodefmt = NODEFMT_NAME;
      }
      else
      {
        ok = 0;
      }
      if (!ok)
      {
        logging(0, "Expected: %s='ip' | 'fqdn' | 'name' in file '%s',%d",
            TOK_NODEFMT, cfgname, lno);
      }
    }
    else if ((strcasecmp(TOK_VARFMT, p) == 0) ||
             (strcasecmp(TOK_LOGFMT, p) == 0) ||
             (strcasecmp(TOK_LOGFILE, p) == 0) ||
             (strcasecmp(TOK_CMUSNMP, p) == 0))
    {
      char           *p1 = strtok(NULL, "\n");
      int             ok = 1;

      if (!p1)
      {
        ok = 0;
      }
      else if (*p1 == '\"')
      {
        char           *p2 = strrchr(++p1, '\"');

        if (p2)
        {
          char           *pval;

          *p2 = '\0';
          pval = strdup(p1);

          if (strcasecmp(TOK_VARFMT, p) == 0)
          {
            cfg_varfmt = pval;
          }
          else if (strcasecmp(TOK_LOGFMT, p) == 0)
          {
            cfg_logfmt = pval;
          }
          else if (strcasecmp(TOK_LOGFILE, p) == 0)
          {
            cfg_logname = pval;
          }
          else if (strcasecmp(TOK_CMUSNMP, p) == 0)
          {
            logging(0, "The option 'CMUSNMP' is obsolete");
          }
        }
        else
        {
          ok = 0;
        }
      }
      else
      {
        ok = 0;
      }
      if (!ok)
      {
        logging(0, "Expected: %s=\"<string>\" in file '%s',%d", p, cfgname, lno);
      }
    }
    else
    {
      logging(0, "Missing token in file '%s',%d", cfgname, lno);
    }
  }

  fclose(cfg);

  /* now open the log file (if any) */
  if (cfg_logname && *cfg_logname)
  {
    f_log = fopen(cfg_logname, "a");

    if (!f_log)
    {
      logging(0, "Cannot append to logfile '%s' [%s]",
          cfg_logname, strerror(errno));
    }
  }

  return f_log;
}

/* ------------------------------------------------------------------------- */

/* print the logfile configuration values */
static void
lg_print_config()
{

  char           *oidfmt[2];
  char           *nodefmt[3];

  oidfmt[OIDFMT_OID] = "oid";
  oidfmt[OIDFMT_TEXT] = "text";
  nodefmt[NODEFMT_IP] = "ip";
  nodefmt[NODEFMT_FQDN] = "fqdn";
  nodefmt[NODEFMT_NAME] = "name";

  logging(1, "Configuration variables:");
  logging(1, " cfg_subst1  = '%c'", cfg_subst1);
  logging(1, " cfg_subst2  = '%c'", cfg_subst2);
  logging(1, " cfg_oidfmt  = %s", oidfmt[cfg_oidfmt]);
  logging(1, " cfg_nodefmt = %s", nodefmt[cfg_nodefmt]);
  logging(1, " cfg_varfmt  = \"%s\"", cfg_varfmt);
  logging(1, " cfg_logfmt  = \"%s\"", cfg_logfmt);
  logging(1, " cfg_logname = \"%s\"",
      (cfg_logname && *cfg_logname ? cfg_logname : "<syslog>"));
}

/* ------------------------------------------------------------------------- */

/* Allocate and format the output buffer.
 * Return the pointer to the allocated area or NULL if an error occurred.
 */
static char    *
lg_format_buffer(t_trap * p_trap, time_t trap_time)
{
  static char     buf[4000];    /* the output buffer */
  char           *cPtr;         /* points to next character in output buf */
  int             bRem;         /* number of remaining chars in output buf */
  /* (starting with cPtr) */
  char           *lPtr;         /* points to next char in log format */
  int             seqno;        /* sequence number of variable */


  logging(1, "Formatting the trap data with '%s' ...", cfg_logfmt);

  /* init */
  bRem = sizeof(buf) - 1;       /* leave space for EOS */
  cPtr = buf;

  /* process the log format string */
  for (lPtr = cfg_logfmt; *lPtr; lPtr++)
  {
    int             l;          /* local */

    /* Assertion: bRem > 0 */
    switch (*lPtr)
    {
      case '\\':
        switch (*++lPtr)
        {
          case 'n':
            *cPtr++ = '\n';
            break;
          case 'r':
            *cPtr++ = '\r';
            break;
          case 't':
            *cPtr++ = '\t';
            break;
          default:
            *cPtr++ = *lPtr;
            break;
        }
        bRem--;
        break;
      case '$':
        switch (*++lPtr)
        {
          case 'x':            /* time trap was received */
            l = strftime(cPtr, bRem, "%Y%m%d.%H%M%S", localtime(&trap_time));
            cPtr += l;
            bRem -= l;
            break;
          case 'A':            /* agents address */
            if (p_trap->s_agentaddr == NULL)
              lg_sprint_node(p_trap->s_agentname, cfg_nodefmt, &cPtr, &bRem);
            else
              lg_sprint_node(p_trap->s_agentaddr, cfg_nodefmt, &cPtr, &bRem);
            break;
          case 'r':            /* implied source (agent address) */
            lg_sprint_node(p_trap->s_agentname, cfg_nodefmt, &cPtr, &bRem);
            break;
          case 'R':            /* true source (peer address) */
            lg_sprint_node(p_trap->s_senderaddr, cfg_nodefmt, &cPtr, &bRem);
            break;
          case 'e':            /* enterprise */
            lg_sprint_oid(p_trap->s_enterprise, cfg_oidfmt, &cPtr, &bRem);
            break;
          case 'G':            /* generic trap number */
            lg_sprint_string(get_generic_id(p_trap->s_trapoid), &cPtr, &bRem);
            break;
          case 'S':            /* specific trap number */
            lg_sprint_string(get_specific_id(p_trap->s_trapoid),
                             &cPtr, &bRem);
            break;
          case 'T':            /* agent's sysUpTime in 1/100 seconds */
            lg_sprint_ulong(uptime_ticks(p_trap->s_uptime),
                            "%lu", &cPtr, &bRem);
            break;
          case '#':            /* number of variables in trap */
            lg_sprint_int(p_trap->n_var, "%d", &cPtr, &bRem);
            break;
          case '*':            /* all variables in trap */
            for (seqno = 1; seqno <= p_trap->n_var; seqno++)
            {
              lg_sprint_var(&p_trap->a_var[seqno - 1],
                            cfg_varfmt, seqno, &cPtr, &bRem);
            }
            break;
          case '$':            /* insert '$' */
            *cPtr++ = '$';
            bRem--;
            break;
          default:             /* insert the characters verbatim */
            *cPtr++ = '$';
            *cPtr++ = *lPtr;
            bRem -= 2;
        }
        break;
      default:
        *cPtr++ = *lPtr;
        bRem--;
        break;
    }

    /* leave if no more space is left */
    if (bRem == 0)
    {
      logging(0, "No more internal space left for remaining format '%s'", lPtr);
      break;
    }
  }
  *cPtr = '\0';

  /* Debug output  */
  logging(1, "Output buffer: '%s'", buf);

  /* this is the result */
  return buf;
}

/* ------------------------------------------------------------------------- */

/* print a nodes ip-address, fully qualified domain name or
 * short hostname according to the specified format into the
 * output buffer
 */
static void
lg_sprint_node(name, nodeFmt, cPPtr, bRemP)
     char           *name;      /* node name (or ip) */
     int             nodeFmt;   /* NODEFMT_IP/_FQDN/_NAME */
     char          **cPPtr;     /* address of pointer to current outchar */
     int            *bRemP;     /* address of counter of remaining chars */
{
  char            lbuf[80];     /* local buffer for result */
  int             l;
  struct hostent *hent = gethostbyname(name);

  logging(1, "lg_sprint_node(%s, ...)", name);

  switch (nodeFmt)
  {
    case NODEFMT_FQDN:         /* use FQDN (resolver) */
    case NODEFMT_NAME:         /* use the short name (hostname) */
      if (hent)
      {
        sprintf(lbuf, "%s", hent->h_name);
      }
      else
      {
        /* no result, use the passed name instead */
        sprintf(lbuf, "%s", name);
      }

      if (nodeFmt == NODEFMT_NAME)
      {
        /* strip the domain part */
        char           *p = strchr(lbuf, '.');

        if (p)
        {
          *p = '\0';
        }
      }
      break;
    case NODEFMT_IP:           /* use ip address */
    default:                   /* shouldn't happen */
      if (hent)
      {
        struct in_addr  in;

        memcpy(&in.s_addr, hent->h_addr, sizeof(in.s_addr));
        sprintf(lbuf, "%s", inet_ntoa(in));
      }
      else
      {
        sprintf(lbuf, "%s", name);
      }
      break;
  }

  /* insert the local buffer into the output buffer */
  l = strlen(lbuf);
  if (l <= *bRemP)
  {
    strncpy(*cPPtr, lbuf, l);
    (*cPPtr) += l;
    (*bRemP) -= l;
  }
  else
  {
    logging(0, "No more internal space left for '%s'", lbuf);
  }
}

/* ------------------------------------------------------------------------- */

/* print an oid according to the specified format into the output buffer 
 */
static void
lg_sprint_oid(oidP, oidFmt, cPPtr, bRemP)
     char           *oidP;      /* the OID */
     int             oidFmt;    /* OIDFMT_TEXT/_OID */
     char          **cPPtr;     /* address of pointer to current outchar */
     int            *bRemP;     /* address of counter of remaining chars */
{
  char            lbuf[1024];   /* local buffer */
  int             l;

  logging(1, "lg_sprint_oid(%s, ...)", oidP);

  switch (oidFmt)
  {
    case OIDFMT_TEXT:          /* use textual format for the oid */
      sprintf(lbuf, "%s", oidP ? oidP : "(null)");
      break;
    case OIDFMT_OID:           /* usenumerical format for the oid */
    default:                   /* shouldn't happen */
      sprintf(lbuf, "%s", oidP ? oidP : "(null)");
      break;
  }

  /* insert the local buffer into the output buffer */
  l = strlen(lbuf);
  if (l <= *bRemP)
  {
    strncpy(*cPPtr, lbuf, l);
    (*cPPtr) += l;
    (*bRemP) -= l;
  }
  else
  {
    logging(0, "No more internal space left for '%s'", lbuf);
  }
}

/* ------------------------------------------------------------------------- */

/* print a string according to the specified format into the output buffer 
 */
static void
lg_sprint_string(stringP, cPPtr, bRemP)
     char           *stringP;   /* the string */
     char          **cPPtr;     /* address of pointer to current outchar */
     int            *bRemP;     /* address of counter of remaining chars */
{
  int             l;

  logging(1, "lg_sprint_string(%s, ...)", stringP);

  /* insert the string into the output buffer */
  l = strlen(stringP);
  if (l <= *bRemP)
  {
    strncpy(*cPPtr, stringP, l);
    (*cPPtr) += l;
    (*bRemP) -= l;
  }
  else
  {
    logging(0, "No more internal space left for '%s'", stringP);
  }
}

/* ------------------------------------------------------------------------- */

/* print an integer according to the specified format into the output buffer
 */
static void
lg_sprint_int(ival, iFmt, cPPtr, bRemP)
     int             ival;      /* the integer value */
     char           *iFmt;      /* the format string */
     char          **cPPtr;     /* address of pointer to current outchar */
     int            *bRemP;     /* address of counter of remaining chars */
{
  char            lbuf[20];     /* local buffer */
  int             l;

  logging(1, "lg_spring_int(%d, ...)", ival);

  /* fill the local buffer */
  sprintf(lbuf, iFmt, ival);

  /* insert the local buffer into the output buffer */
  l = strlen(lbuf);
  if (l <= *bRemP)
  {
    strncpy(*cPPtr, lbuf, l);
    (*cPPtr) += l;
    (*bRemP) -= l;
  }
  else
  {
    logging(0, "No more internal space left for '%s'", lbuf);
  }
}

/* ------------------------------------------------------------------------- */

/* print an u_long according to the specified format into the output buffer
 */
static void
lg_sprint_ulong(uval, uFmt, cPPtr, bRemP)
     unsigned long   uval;      /* the integer value */
     char           *uFmt;      /* the format string */
     char          **cPPtr;     /* address of pointer to current outchar */
     int            *bRemP;     /* address of counter of remaining chars */
{
  char            lbuf[20];     /* local buffer */
  int             l;

  logging(1, "lg_sprint_ulong(%lu, ...)", uval);

  /* fill the local buffer */
  sprintf(lbuf, uFmt, uval);

  /* insert the local buffer into the output buffer */
  l = strlen(lbuf);
  if (l <= *bRemP)
  {
    strncpy(*cPPtr, lbuf, l);
    (*cPPtr) += l;
    (*bRemP) -= l;
  }
  else
  {
    logging(0, "No more internal space left for '%s'", lbuf);
  }
}

/* ------------------------------------------------------------------------- */

/* print an ASN.1 type ito the output buffer
 */
static void
lg_sprint_ASNtype(asntype, cPPtr, bRemP)
     u_char          asntype;   /* ASN.1 type */
     char          **cPPtr;     /* address of pointer to current outchar */
     int            *bRemP;     /* address of counter of remaining chars */
{
  char           *p;
  int             l;

  logging(1, "lg_sprint_ASNtype(%c, ...)", asntype);

  switch (asntype)
  {
    case ASN_BOOLEAN:
      p = "Boolean";
      break;
    case ASN_INTEGER:
      p = "Integer";
      break;
    case ASN_BIT_STR:
      p = "BitString";
      break;
    case ASN_OCTET_STR:
      p = "OctetString";
      break;
    case ASN_NULL:
      p = "Null";
      break;
    case ASN_OBJECT_ID:
      p = "ObjectIdentifier";
      break;
    case ASN_OPAQUE:
      p = "Opaque";
      break;
    case ASN_TIMETICKS:
      p = "Timeticks";
      break;
    case ASN_GAUGE:
      p = "Gauge";
      break;
    case ASN_COUNTER:
      p = "Counter";
      break;
    case ASN_IPADDRESS:
      p = "IpAddress";
      break;
    case ASN_UINTEGER:
      p = "UnsignedInteger32";
      break;
    default:
      p = "BadType";
      break;
  }

  /* insert the local buffer into the output buffer */
  l = strlen(p);
  if (l <= *bRemP)
  {
    strncpy(*cPPtr, p, l);
    (*cPPtr) += l;
    (*bRemP) -= l;
  }
  else
  {
    logging(0, "No more internal space left for '%s'", p);
  }

  /* temporary close the string  */
  **cPPtr = '\0';
}

/* ------------------------------------------------------------------------- */

/* print a snmp variable according to the specified format into the 
 * output buffer
 */
static void
lg_sprint_var(var, vFmt, seqno, cPPtr, bRemP)
     t_var          *var;       /* the snmp variable */
     char           *vFmt;      /* the output format string */
     int             seqno;     /* current sequence number */
     char          **cPPtr;     /* address of pointer to current outchar */
     int            *bRemP;     /* address of counter of remaining chars */
{
  char           *vPtr;         /* current varfmt character */

  logging(1, "lg_sprint_var(%s/%s, %s, %d, ...)",
      var->v_oid, var->v_val, vFmt, seqno);

  /* process the variable format */
  for (vPtr = vFmt; *vPtr; vPtr++)
  {
    switch (*vPtr)
    {
      case '\\':
        if (*bRemP > 0)
        {
          switch (*++vPtr)
          {
            case 'n':
              *(*cPPtr)++ = '\n';
              (*bRemP)--;
              break;
            case 'r':
              *(*cPPtr)++ = '\r';
              (*bRemP)--;
              break;
            case 't':
              *(*cPPtr)++ = '\t';
              (*bRemP)--;
              break;
            default:
              *(*cPPtr)++ = *vPtr;
              (*bRemP)--;
          }
        }
        break;
      case '%':                /* special char follows */
        switch (*++vPtr)
        {
          case 's':            /* the sequence number */
            lg_sprint_int(seqno, "%d", cPPtr, bRemP);
            break;
          case 'n':            /* the name */
            lg_sprint_oid(var->v_oid, cfg_oidfmt, cPPtr, bRemP);
            break;
          case 't':            /* the type */
            logging(1, "Type info not available - using ASN_OCTET_STR");
            lg_sprint_ASNtype(ASN_OCTET_STR, cPPtr, bRemP);
            break;
          case 'v':            /* the value */
            /* special handling for some (distinguishable) types */
            if (var->v_val[0] == '"')
            {
              /* we got a string: strip delims and substitute characters */
              int             l = strlen(var->v_val);
              char           *p;

              if ((l > 0) && (var->v_val[l - 1] == '"'))
              {
                var->v_val[l - 1] = '\0';
                l--;
              }

              if (l > 0)
              {
                for (p = var->v_val + 1; *p; p++)
                {
                  if (*p == cfg_subst1)
                  {
                    *p = cfg_subst2;
                  }
                }
              }

              lg_sprint_string(var->v_val + 1, cPPtr, bRemP);
            }
            else
            {
              /* Standard */
              lg_sprint_string(var->v_val, cPPtr, bRemP);
            }
            break;
          case '%':            /* insert '%' */
            if (*bRemP > 0)
            {
              *(*cPPtr)++ = '%';
              (*bRemP)--;
            }
            break;
          default:             /* insert the two chars verbatim */
            if (*bRemP > 1)
            {
              *(*cPPtr)++ = '%';
              *(*cPPtr)++ = *vPtr;
              (*bRemP) -= 2;
            }
            else
            {
              *bRemP = 0;
            }
            break;
        }
        break;
      default:                 /* insert char verbatim */
        if (*bRemP > 0)
        {
          *(*cPPtr)++ = *vPtr;
          (*bRemP)--;
        }
    }

    if (*bRemP <= 0)
    {
      logging(0, "No more internal space left for variable %d", seqno);
      *bRemP = 0;
      break;
    }
  }
}

/* ------------------------------------------------------------------------- */

/* Read the trap data from the pipe and store the data
 * - Returns 0 if ok, -1 if error
 */
static int
read_trapdata(FILE * f, t_trap * p_trap)
{
  char           *oid;
  char           *val;

  /* Read the constant header */
  if (read_oidval(f, &p_trap->s_agentname, NULL,
                  "Missing AGENTNAME from handler") == -1)
  {
    return -1;
  }
  logging(1, "Store: AGENTNAME := '%s'", p_trap->s_agentname);

  if (read_oidval(f, &p_trap->s_senderaddr, NULL,
                  "Missing SENDERIP from handler") == -1)
  {
    return -1;
  }
  logging(1, "Store: SENDERADDR := '%s'", p_trap->s_senderaddr);

  if (read_oidval(f, NULL, &p_trap->s_uptime,
                  "Missing UPTIME from handler") == -1)
  {
    return -1;
  }
  logging(1, "Store: UPTIME := '%s'", p_trap->s_uptime);

  if (read_oidval(f, NULL, &p_trap->s_trapoid,
                  "Missing TRAPOID from handler") == -1)
  {
    return -1;
  }
  logging(1, "Store: TRAPOID := '%s'", p_trap->s_trapoid);

  /* Now read all remaining OID/VAL pairs */
  while (read_oidval(f, &oid, &val, NULL) == 0)
  {
    if (oid[0] == '\0')
    {
      /* End of data from handler */
      logging(1, "End of handler data reached");
      free(oid);
      free(val);
      break;
    }
    else if ((strcmp(oid, OID_SYM_SNMPTRAPADDRESS) == 0) ||
             (strcmp(oid, OID_NUM_SNMPTRAPADDRESS) == 0))
    {
      free(oid);
      p_trap->s_agentaddr = val;
      logging(1, "Store: SNMPTRAPADDRESS := '%s'", p_trap->s_agentaddr);
    }
    else if ((strcmp(oid, OID_SYM_SNMPTRAPCOMMUNITY) == 0) ||
             (strcmp(oid, OID_NUM_SNMPTRAPCOMMUNITY) == 0))
    {
      free(oid);
      p_trap->s_community = val;
      logging(1, "Store: SNMPTRAPCOMMUNITY := '%s'", p_trap->s_community);
    }
    else if ((strcmp(oid, OID_SYM_SNMPTRAPENTERPRISE) == 0) ||
             (strcmp(oid, OID_NUM_SNMPTRAPENTERPRISE) == 0))
    {
      free(oid);
      p_trap->s_enterprise = val;
      logging(1, "Store: SNMPTRAPENTERPRISE := '%s'", p_trap->s_enterprise);
    }
    else
    {
      /* store as a variable */
      void           *p;

      p_trap->n_var++;
      p = realloc(p_trap->a_var, p_trap->n_var * sizeof(t_var));
      if (p == NULL)
      {
        logging(0, "Out of memory - aborting");
        exit(1);
      }
      p_trap->a_var = p;

      p_trap->a_var[p_trap->n_var - 1].v_oid = oid;
      p_trap->a_var[p_trap->n_var - 1].v_val = val;

      logging(1, "Store var %d: '%s' := '%s'", p_trap->n_var, oid, val);
    }
  }

  return 0;
}

/* ------------------------------------------------------------------------- */

/* Read the trap data from the handlers pipe and format the output buffer
 */
void
process_trapdata(FILE * f)
{
  char           *buf;
  t_trap          trapdata;


  /* Fetch the trap data  */
  memset(&trapdata, '\0', sizeof(trapdata));

  if (read_trapdata(f, &trapdata) == -1)
  {
    logging(0, "Error reading trap data - skip formatting");
    free_trap(&trapdata);

    return;
  }

  /* Format the trap data */
  buf = lg_format_buffer(&trapdata, time(NULL));

  /* And now log the data according to the configuration */
  if (!buf)
  {
    logging(0, "Error: Cannot allocate/format output buffer");
  }
  else if (o_logfile)
  {
    logging(1, "Logging to file '%s'", buf);

    fprintf(o_logfile, "%s\n", buf);
    fflush(o_logfile);
  }
  else
  {
    logging(1, "Logging to syslog 'LOG_WARNING:%s'", buf);

    syslog(LOG_WARNING, "%s\n", buf);
  }

  free_trap(&trapdata);
}

/* ------------------------------------------------------------------------- */

/* Read a OID/VAL pair from the pipe, allocate memory and store the pointers
 * (if specified). If an error occurs, emit msg (if any) and return -1, else
 * return 0.
 */
static int
read_oidval(FILE * f, char **pp_oid, char **pp_val, char *msg)
{
  char            rbuf[1024];

  if (fgets(rbuf, sizeof(rbuf), f) == NULL)
  {
    if (msg)
    {
      logging(0, msg);
    }

    return -1;
  }
  else
  {
    /* look for a delimiting blank */
    char           *p_del;
    int             l;

    /* strip the trailing EOL */
    l = strlen(rbuf);
    if ((l > 0) && (rbuf[l - 1] == '\n'))
    {
      rbuf[l - 1] = '\0';
    }

    logging(1, "Read from handler: '%s'", rbuf);

    /* split the line (if two components are on the line) */
    p_del = strchr(rbuf, ' ');
    if (p_del)
    {
      *p_del = '\0';
    }

    /* fetch and store the oid */
    if (pp_oid)
    {
      *pp_oid = copy_string(rbuf);
    }

    /* fetch and store the value */
    if (pp_val)
    {
      *pp_val = copy_string(p_del ? p_del + 1 : rbuf);
    }

    logging(1, " Split: OID = '%s', VAL = '%s'", rbuf,
        (p_del ? p_del + 1 : rbuf));
  }

  return 0;
}

/* ------------------------------------------------------------------------- */

/* Copy a string into allocated memory (but do not copy the trailing EOL)
 */
static char    *
copy_string(char *buf)
{
  char           *s;
  int             l = strlen(buf);

  if ((l > 0) && (buf[l - 1] == '\n'))
  {
    buf[--l] = '\0';
  }

  s = strdup(buf);
  if (s == NULL)
  {
    logging(0, "Out of memory - aborting");
    exit(1);
  }

  return s;
}

/* ------------------------------------------------------------------------- */

/* Free a trap data structure
 */
static void
free_trap(t_trap * p_trap)
{
  if (p_trap)
  {
    int             i;

    FREEMEM(p_trap->s_agentname);
    FREEMEM(p_trap->s_agentaddr);
    FREEMEM(p_trap->s_senderaddr);
    FREEMEM(p_trap->s_uptime);
    FREEMEM(p_trap->s_trapoid);
    FREEMEM(p_trap->s_community);
    FREEMEM(p_trap->s_enterprise);

    for (i = 0; i < p_trap->n_var; i++)
    {
      FREEMEM(p_trap->a_var[i].v_oid);
      FREEMEM(p_trap->a_var[i].v_val);
    }

    if (p_trap->n_var > 0)
    {
      FREEMEM(p_trap->a_var);
    }
  }
}

/* ------------------------------------------------------------------------- */

/* extract the Generid ID from the Trap OID by checking the second to last
 * component of the Trap OID
 */
static char    *
get_generic_id(char *trapoidP)
{
  char           *p_delim;
  char            trapoid[2048];
  static char     buf[80];


  /* Save the trap OID */
  strcpy(trapoid, trapoidP);

  /* look for the last period */
  p_delim = strrchr(trapoid, '.');
  if (p_delim)
  {
    char           *p_delim0;

    *p_delim = '\0';

    /* now look for the previous delim */
    p_delim0 = strrchr(trapoid, '.');
    if (p_delim0)
    {
      /* inspect the second to last component */
      if (strcmp(p_delim0 + 1, "0") == 0)
      {
        /* this is a specific trap, thus the GID is 6 */
        sprintf(buf, "%d", SNMP_TRAP_ENTERPRISESPECIFIC);
      }
      else
      {
        /* this is a generic trap, return the last component
         * (but decrement if numeric) 
         */
        if (isdigit(p_delim[1]))
        {
          int             gid;

          sscanf(p_delim + 1, "%d", &gid);
          sprintf(buf, "%d", gid - 1);
        }
        else
        {
          strcpy(buf, p_delim + 1);
        }
      }
    }
    else
    {
      /* only one delimiter in this trap OID, must be generic */
      if (isdigit(p_delim[1]))
      {
        int             gid;

        sscanf(p_delim + 1, "%d", &gid);
        sprintf(buf, "%d", gid - 1);
      }
      else
      {
        strcpy(buf, p_delim + 1);
      }
    }
  }
  else
  {
    /* no delimiter in this trap, must be generic */
    if (isdigit(trapoid[0]))
    {
      int             gid;

      sscanf(trapoid, "%d", &gid);
      sprintf(buf, "%d", gid - 1);
    }
    else
    {
      strcpy(buf, trapoid);
    }
  }

  return buf;

}

/* ------------------------------------------------------------------------- */

/* extract the Specific ID from the Trap OID 
 */
static char    *
get_specific_id(char *trapoidP)
{
  char           *p_delim;
  char            trapoid[2048];
  static char     buf[80];


  /* Save the trap OID */
  strcpy(trapoid, trapoidP);

  /* look for the last period */
  p_delim = strrchr(trapoid, '.');
  if (p_delim)
  {
    char           *p_delim0;

    *p_delim = '\0';

    /* now look for the previous delim */
    p_delim0 = strrchr(trapoid, '.');
    if (p_delim0)
    {
      /* inspect the second to last component */
      if (strcmp(p_delim0 + 1, "0") == 0)
      {
        /* this is a specific trap */
        strcpy(buf, p_delim + 1);
      }
      else
      {
        /* this is a generic trap */
        strcpy(buf, "0");
      }
    }
    else
    {
      /* only one delimiter in this trap OID, must be generic */
      strcpy(buf, "0");
    }
  }
  else
  {
    /* no delimiter in this trap, must be generic */
    strcpy(buf, "0");
  }

  return buf;
}

/* ------------------------------------------------------------------------- */
