#!/usr/bin/perl
#
# SNMPTT v1.4.2
#
# Copyright 2002-2020 Alex Burger
# alex_b@users.sourceforge.net
# 4/11/2002
#
# 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 2 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 for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
##############################################################################
#
# http://www.sourceforge.net/projects/snmptt
#
# This script is a snmp trap handler for use with the NET-SNMP / UCD-SNMP 
# snmptrapd program.
#
# The script is called by defining a 'traphandle' in snmptrapd.conf.  
# For example:
#
# traphandle default /sbin/snmptt
# 
# SNMPTRAPD feeds details about the trap to the launched program's standard
# input in the following format (see snmptrapd.conf man page for a complete
# descriptipon)
#
# HOSTNAME: 	The name of the host in question  that  sent the  trap
# IPADDRESS: 	The IP address of the  host  that  sent  the trap
# VARBINDS: 	A  list  of  variable bindings that describe the trap and 
# 		the variables enclosed  in  it.  
#
# See the SNMPTT documentation (readme.html) for more information.
#
##############################################################################
#
use strict;

my $snmptt_version = "v1.4.2";

sub showversion
{
  print "\nSNMPTT $snmptt_version\n";
  print "(c) 2002-2020 Alex Burger\n";
  print "http://snmptt.sourceforge.net\n\n";
}

##############################################################################
# Process command line arguments

use Getopt::Long;

my $version = 0;
my $daemon = 0;
my $debug = 0;
my $debugfile = '';
my $dump = 0;
my $help = 0;
my $time = 0;
my $ini = '';

GetOptions 	('version' => \$version, 
    'daemon' => \$daemon,
    'debug:i' => \$debug,
    'debugfile=s' => \$debugfile,
    'dump' => \$dump,
    'help' => \$help,
    'ini=s' => \$ini,
    'time' => \$time);  

if ($version == 1)
{
  &showversion;
  exit(0);
}

if ($help == 1)
{
my $USAGE = qq/Usage:
    snmptt [<options>] 
Options:
    --daemon                             Run as a daemon
    --debug=n                            Set debug level (1 or 2)
    --debugfile=filename                 Set debug output file
    --dump                               Dump (display) defined traps
    --help                               Display this message
    --ini=filename                       Set configuration file to load
    --version                            Display author and version information
    --time                               Use to see how long it takes to load and 
                                         process trap file (eg: time snmptt --time)
/;

  &showversion;
  print $USAGE;

  exit(0);
}

my $DEBUGGING;
my $debugcmdline;
my $daemoncmdline;
my $DEBUGGING_FILE;
my $debugfilecmdline;

if ($debug >= 1)
{
  $DEBUGGING = $debug;	
  $debugcmdline = 1;
}
else
{
  $DEBUGGING = 0;	
  $debugcmdline = 0;
}

if ($daemon == 1)
{
    # $daemon = 1;	
  $daemoncmdline = 1;
}
else
{
  $daemoncmdline = 0;
}

if ($dump ==1)
{
  $DEBUGGING = 2;
  $debugcmdline = 1;
  &findsnmpttini;
  &loadsnmpttini;
  &loadsnmpttconf;		# Load SNMPTT.CONF file
  print ("\n\n");
  exit(0);
}

if ($time ==1)
{
  &findsnmpttini;
  &loadsnmpttini;
  &loadsnmpttconf;		# Load SNMPTT.CONF file
  exit(0);
}

if ($debugfile ne '') 
{
  $DEBUGGING_FILE = $debugfile;	# commandline overpowers snmptt script
  $debugfilecmdline = 1;
}
else
{
  $debugfilecmdline = 0;
}

# Global config file variables
my $snmptt_system_name;
my $daemon;
my $multiple_event;
my $dns_enable;
my $strip_domain;
my @strip_domain_list;
my $net_snmp_perl_enable;
my $net_snmp_perl_cache_enable;
my $net_snmp_perl_best_guess;
my $translate_log_trap_oid;
my $translate_value_oids;
my $resolve_value_ip_addresses;
my $translate_enterprise_oid_format;
my $translate_trap_oid_format;
my $translate_varname_oid_format;
my $translate_integers;
my $wildcard_expansion_separator;
my $mibs_environment;
my $allow_unsafe_regex;
my $remove_backslash_from_quotes;
my $dynamic_nodes;
my $description_mode;
my $description_clean;
my $threads_enable;
my $threads_max;
my $date_format;
my $time_format;
my $date_time_format;
my $date_time_format_sql;
my $stat_time_format_sql;

# DaemonMode
my $daemon_fork;
my $daemon_uid;
my $pid_file;
my $spool_directory;
my $sleep;
my $use_trap_time;
my $keep_unlogged_traps;
my $duplicate_trap_window;

# Logging
my $stdout_enable;
my $log_enable;
my $log_file;
my $log_system_enable;
my $log_system_file;
my $unknown_trap_log_enable;
my $unknown_trap_log_file;
my $statistics_interval;
my $syslog_enable;
my $syslog_facility;
my @syslog_level_alert;
my @syslog_level_crit;
my @syslog_level_err;
my @syslog_level_warning;
my @syslog_level_notice;
my @syslog_level_info;
my @syslog_level_debug;
my $syslog_level;
my $syslog_system_enable;
my $syslog_system_facility;
my $syslog_system_level;
my $eventlog_enable;
my @eventlog_type_information;
my @eventlog_type_warning;
my @eventlog_type_error;
my $eventlog_type;
my $eventlog_system_enable;

# Exec
my $exec_enable;
my $pre_exec_enable;
my $unknown_trap_exec;
my $unknown_trap_exec_format;
my $exec_escape;

# SQL
my $db_translate_enterprise;
my $db_unknown_trap_format;
my $mysql_dbi_enable;
my $mysql_dbi_host;
my $mysql_dbi_port;
my $mysql_dbi_database;
my $mysql_dbi_table;
my $mysql_dbi_table_unknown;
my $mysql_dbi_table_statistics;
my $mysql_dbi_username;
my $mysql_dbi_password;
my $mysql_ping_on_insert;
my $mysql_ping_interval;

my $postgresql_dbi_enable;
my $postgresql_dbi_module;
my $postgresql_dbi_hostport_enable;
my $postgresql_dbi_host;
my $postgresql_dbi_port;
my $postgresql_dbi_database;
my $postgresql_dbi_table;
my $postgresql_dbi_table_unknown;
my $postgresql_dbi_table_statistics;
my $postgresql_dbi_username;
my $postgresql_dbi_password;
my $postgresql_ping_on_insert;
my $postgresql_ping_interval;

my $dbd_odbc_enable;
my $dbd_odbc_dsn;
my $dbd_odbc_table;
my $dbd_odbc_table_unknown;
my $dbd_odbc_table_statistics;
my $dbd_odbc_username;
my $dbd_odbc_password;
my $dbd_odbc_ping_on_insert;
my $dbd_odbc_ping_interval;

my $sql_win32_odbc_enable;
my $sql_win32_odbc_dsn;
my $sql_win32_odbc_table;
my $sql_win32_odbc_table_unknown;
my $sql_win32_odbc_table_statistics;
my $sql_win32_odbc_username;
my $sql_win32_odbc_password;

my @sql_custom_columns;
my @sql_custom_columns_unknown;

# TrapFiles
my @snmptt_conf_files;

##############################################################################
#
# Load config file 
#

&findsnmpttini;
&loadsnmpttini;

##############################################################################

use Text::ParseWords;
use POSIX qw(strftime);
use Sys::Hostname;
use File::Basename;
use Text::Balanced qw (extract_bracketed);

my $debug_file_used = 0;

my $g_start_time = time();

my $debug_file_open_error = 1;
if ($DEBUGGING >= 1)
{
  # Open the debug file
  if ($DEBUGGING_FILE ne '') 
  {
    if (open DEBUGFILE, ">>$DEBUGGING_FILE")
    {
      select DEBUGFILE;	# change default output to debug file
      $debug_file_used = 1;
      $debug_file_open_error = 0;
    }
    else
    {
      warn "could not open debug output file ($!)";
    }
  }

  # print out time
  print "********** SNMPTT $snmptt_version started: ",scalar(localtime($g_start_time))," **********\n\n"
}

if ($threads_enable == 1)
{
  eval 'require threads;';
  if ($@) {
    warn $@;
    print "\nThreads have been enabled but the threads module is not available.  To\n";
    print "enable threads you need the threads module (part of ithreads,not Threads).\n";
    print "This is only available in Perl 5.6.0 and higher.\n";
    die "died";
  }
  require threads;

  eval 'require Thread::Semaphore;';
  if ($@) {
    warn $@;
    print "\nThreads have been enabled but the Thread module is not available.\n";
    die "died";
  }
  require Thread::Semaphore;  
}


if ($syslog_enable == 1 || $syslog_system_enable == 1)
{
  eval 'require Sys::Syslog;';
  if ($@) {
    warn $@;
    print "\nCould not load Perl module Sys::Syslog!  If syslog_system_enable or\n";
    print "syslog_enable is enabled then the Sys::Syslog module is required.  Please\n";
    print "see snmptt.html for system requirements.\n\n";
    die "died";
  }
  require Sys::Syslog;
}


# Win32 constants not available when using 'require' (!)
my $eventlog_error = 1;		# EVENTLOG_ERROR_TYPE
my $eventlog_warning = 2;		# EVENTLOG_WARNING_TYPE
my $eventlog_information = 4;	# EVENTLOG_INFORMATION_TYPE

if ($eventlog_system_enable == 1 || $eventlog_enable == 1)
{
  eval 'require Win32::EventLog;';
  if ($@) {
    warn $@;
    print "\nCould not load Perl module Win32::EventLog!  If eventlog_system_enable or\n";
    print "eventlog_enable is enabled, then the Win32::EventLog module is required.  \n";
    print "Please see snmptt.html for system requirements.\n\n";
    die "died";
  }
  require Win32::EventLog;
}


if ($syslog_system_enable == 1 && $daemon == 1)
{
  syslog_system("SNMPTT $snmptt_version started");
  if ($DEBUGGING >= 1 && $debug_file_open_error == 1) {
    syslog_system("Could not open debug output file!");
  }
}
if ($log_system_enable == 1 && $daemon == 1)
{
  log_system("SNMPTT $snmptt_version started");
  if ($DEBUGGING >= 1 && $debug_file_open_error == 1) {
    log_system("Could not open debug output file!");
  }
}

if ($eventlog_system_enable == 1 && $daemon == 1)
{
  eventlog_system("SNMPTT $snmptt_version started",0,$eventlog_information);
  if ($DEBUGGING >= 1 && $debug_file_open_error == 1) {
    eventlog_system("Could not open debug output file!",14,$eventlog_warning);
  }
}

if ($net_snmp_perl_enable == 1)
{
  eval 'require SNMP;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module SNMP!  If net_snmp_perl_enable is\n";
    print "enabled then the SNMP module is required.  Please see snmptt.html\n";
    print "for system requirements.  Note:  SNMPTT uses the Net-SNMP package's\n";
    print "SNMP module, NOT the CPAN Net::SNMP module!\n\n";
    die "died";
  }
  require SNMP;
  if (defined ($mibs_environment))
  {
    $ENV{'MIBS'} = $mibs_environment;
  }
  my $noperlwarning;
  if ($description_mode == 2) {
    $SNMP::save_descriptions = 1;
  }
  &SNMP::initMib();

  $SNMP::best_guess = $net_snmp_perl_best_guess;
  $noperlwarning = $SNMP::best_guess;  # Just to get rid of the Perl warning that var used only once

  $SNMP::use_long_names = 0;	# Set to 0, otherwise getType may fail when using a long name on 4.2.x
  $noperlwarning = $SNMP::use_long_names;  # Just to get rid of the Perl warning that var used only once

  if ($DEBUGGING >= 1)
  {
    print "********** Net-SNMP version $SNMP::VERSION Perl module enabled **********\n\n";
    if (defined ($mibs_environment))
    {
      print "********** MIBS: $mibs_environment **********\n\n";
    }
  }
}

if ($dns_enable == 1)
{
  eval 'require Socket;';
  if ($@) {
    warn $@;
    print "\nCould not load the Net-SNMP Perl module Socket!  If dns_enable\n";
    print "is enabled then the Socket module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require Socket;
  if ($DEBUGGING >= 1)
  {
    print "********** DNS enabled **********\n\n";
  }
}

if ($mysql_dbi_enable == 1)
{
  eval 'require DBI;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module DBI!  If mysql_dbi_enable\n";
    print "is enabled then the DBI module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require DBI;

  eval 'require DBD::mysql;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module DBD::mysql!  If mysql_dbi_enable\n";
    print "is enabled then the DBD::mysql module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require DBD::mysql;
}

if ($postgresql_dbi_enable == 1)
{
  eval 'require DBI;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module DBI!  If postgresql_dbi_enable\n";
    print "is enabled then the DBI module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require DBI;

  if ($postgresql_dbi_module == 0)
  {
    eval 'require DBD::PgPP;';
    if ($@) {
      warn $@;
      print "\nCould not load the Perl module DBD::PgPP!  If postgresql_dbi_module\n";
      print "is set to 0 then the DBD::PgPP module is required.  Please see snmptt.html\n";
      print "for system requirements.\n\n";
      die "died";
    }
    require DBD::PgPP;
  }
  else
  {
    eval 'require DBD::Pg;';
    if ($@) {
      warn $@;
      print "\nCould not load the Perl module DBD::Pg!  If postgresql_dbi_module\n";
      print "is set to 1 then the DBD::Pg module is required.  Please see snmptt.html\n";
      print "for system requirements.\n\n";
      die "died";
    }
    require DBD::Pg;
  }
}    

if ($dbd_odbc_enable == 1)
{
  eval 'require DBI;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module DBI!  If dbd_odbc_enable\n";
    print "is enabled then the DBI module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require DBI;

  eval 'require DBD::ODBC;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module DBD::ODBC!  If dbd_odbc_enable\n";
    print "is enabled then the DBD::ODBC module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require DBD::ODBC;
}

if ($sql_win32_odbc_enable == 1)
{
  eval 'require Win32::ODBC;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module Win32::ODBC!  If sql_win32_odbc_enable\n";
    print "is enabled then the Win32::ODBC module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require Win32::ODBC;
}


if ($duplicate_trap_window > 0)
{
  eval 'require Digest::MD5;';
  if ($@) {
    warn $@;
    print "\nCould not load the Perl module Digest::MD5!  If duplicate_trap_window\n";
    print "is set then the Digest::MD5 module is required.  Please see snmptt.html\n";
    print "for system requirements.\n\n";
    die "died";
  }
  require Digest::MD5;
}


##############################################################################
####  MAIN SECTION START

# Global variables
my %event;                    # Holds EVENT entries from all .conf files        
my $receivedtrap_entry;       # Trap received - stored by readtrap()
my $input;                    # For reading in trap from spool folder or STDIN
my @event2;                   # Copy of the matched event
my @var;                      # Variables of trap received by SNMPTRAPD
my @entvar;                   # Enterprise variable values of trap received by SNMPTRAPD
my @entvarname;               # Enterprise variable names of trap received by SNMPTRAPD
my @preexec_var;              # PREEXEC results
my $receivedtrap;             # Received trap
my $receivedtrap_trans;       # Translated version of received trap
my $enterprise_trans;         # Translated enterprise of received trap
my $agent_dns_name;           # DNS name of trap received
my $processed;                # Whether or not the trap was processed (found) to determine
                              # if it should search using wildcards and log to unknown
my $trap_attempted_to_log;    # To keep track of whether or not we attempted to log the trap
my $trap_date_time;           # Date and time of the trap.  Used for log files.
my $trap_date_time_sql;       # Date and time of the trap.  Used for SQL.
my $trap_successfully_logged; # To keep track of whether or not we successfully logged the trap
                              # so we know if we should delete the trap file
my $db_enterprise;
my $trap_date;                # Date of trap
my $trap_time;                # Time of trap
my $trap_date_time_epoch;     # Date / time of trap
my $configfile;               # .ini file to use

my $thread_exec_semaphore;    # Semaphore for EXEC

# Global variables for statistics
my $g_total_traps_received = 0;
my $g_total_traps_translated = 0;
my $g_total_traps_ignored = 0;
my $g_total_traps_unknown = 0;
my $g_last_statistics_logged = $g_start_time;

# Global variables for SQL
my $dbh_mysql;
my $dbh_postgresql;
my $dbh_odbc;
my $dbh_win32_odbc;

# Global variables for SQL ping
my $g_last_mysql_ping = $g_start_time;
my $g_last_postgresql_ping = $g_start_time;
my $g_last_dbd_odbc_ping = $g_start_time;

# Global variables for daemon mode
my $timetoreload;
my $timetodie;
my $timetologstatistics;
my %duplicate_traps;
my %my_translateObj_cache;
my %my_mapEnum_cache;

if ($daemon == 1)
{
  # Check for old pid file.
  my $pid_file_set = 0;
  if ($pid_file eq '') {
    $pid_file = '/var/run/snmptt.pid';
  }
  else {
    $pid_file_set = 1;
  }

  if ($DEBUGGING >= 1) {
    print STDOUT "PID file: $pid_file\n";
  }
  if (-e $pid_file) {
    open(OLDPID, "<$pid_file");
    my $old_pid = <OLDPID>;
    chomp $old_pid;
    close OLDPID;
    
    warn("There seems to be another SNMPTT process (pid $old_pid) running.\n");
    warn("You may want to kill it and delete the .pid file ($pid_file).  Aborting...\n");
    if ($syslog_system_enable == 1) {
      syslog_system("There seems to be another SNMPTT process (pid $old_pid) running.");
      syslog_system("You may want to kill it and delete the .pid file ($pid_file).  Aborting...");
    }
    if ($log_system_enable == 1) {
      log_system("There seems to be another SNMPTT process (pid $old_pid) running.");
      log_system("You may want to kill it and delete the .pid file ($pid_file).  Aborting...");
    }
    die "Exiting";
  }

  # Check to make sure we can create the .pid file if it was set by the user.
  # If the user didn't set it, then we don't really care.
  if ($pid_file_set) {
    if (! (-w dirname($pid_file))) {
      warn("pid file \'$pid_file\' is not writable.  Aborting...");
      
      if ($syslog_system_enable == 1) {
        syslog_system("pid file \'$pid_file\' is not writable.  Aborting...");
      }
      if ($log_system_enable == 1) {
        log_system("pid file \'$pid_file\' is not writable.  Aborting...");
      }
      die "Exiting";
    }
  }

  $SIG{HUP} = \&signal_handler_reload;

  $SIG{TERM} = \&signal_handler_die;

  $SIG{USR1} = \&signal_log_statistics;

  $timetoreload = 0;
  $timetodie = 0;
  $timetologstatistics = 0;

  &loadsnmpttconf;	# Load SNMPTT.CONF file

  # Only fork to the background if not Win32
  if (($^O ne "MSWin32") && ($daemon_fork==1))
  {
    use POSIX qw(setsid);
    use POSIX qw(signal_h);
    use POSIX ":sys_wait_h";
    use Cwd;
    my $pid;
    my $pid2;

    my $working_dir = cwd;
    if ($DEBUGGING >= 1) {
      print "cwd: $working_dir\n";
    }

    chdir '/'
       or die "Can't chdir to /: $!";
    open STDIN, '/dev/null'
       or die "Can't read /dev/null: $!";
    open STDOUT, '>>/dev/null'
       or die "Can't write to /dev/null: $!";
    open STDERR, '>>/dev/null'
       or die "Can't write to /dev/null: $!";

    # We fork so we can return back to the shell or whatever started snmptt
    defined($pid = fork)
      or die "Can't fork: $!";

    # fork returns: child pid to the parent, 0 to the child, undef if it failed.

    # We write PID using the uid of the user that started snmptt.
    if ($pid)   # This is run in the parent process
    {
      if ($daemon_uid eq '') {
        if (open(PID, ">$pid_file") ) {
          print(PID "$pid\n");
          close(PID);
        }
        else {
          $pid_file = "$working_dir/snmptt.pid";
          
          if (open(PID, ">$pid_file") )
          {
            print(PID "$pid\n");
            close(PID);
          }
        }
      }

      exit;
    }

    POSIX:setsid
       or die "Can't start a new session: $!";
    #umask 0;
    umask 002;

    # We fork again so there are two processes.  The first which is run using the uid
    # of the user that started snmptt, and the second which is run as the user as 
    # defined by daemon_uid in snmptt.ini.  We do this so we can sit and wait for the
    # child to finish so we can clean up the snmptt.pid file.
    # We only need to do this if daemon_uid is set..
    if ($daemon_uid ne '') {
      defined($pid2 = fork)
        or die "Can't fork: $!";
      
      if ($pid2)   # This is run in the parent process
      {
        $SIG{TERM} = \&signal_handler_die;        # new signal for parent
        
        if (open(PID, ">$pid_file") ) {
          print(PID "$pid2\n");
          close(PID);
        }
        else {
          $pid_file = "$working_dir/snmptt.pid";
          
          if (open(PID, ">$pid_file") )
          {
            print(PID "$pid2\n");
            close(PID);
          }
        }

        while (1) {
          if ($timetodie == 1 || waitpid($pid2, WNOHANG) != 0) {
            kill SIGTERM, $pid2;
            # Clean up snmptt.pid file
            unlink($pid_file);
            exit;
          }
          sleep 3;
        }
      }
      
      POSIX:setsid
        or die "Can't start a new session: $!";
      #umask 0;
      umask 002;
    }
  } 

  # Change user if not Windows, and daemon_uid ini parameter not blank
  if ($^O ne "MSWin32" && $daemon_uid ne '')
  {
    my $daemon_uid_name = '';
    my $daemon_gid_name = '';

    if ($DEBUGGING >= 1) {
      syslog_system("Configured daemon_uid: $daemon_uid\n");
    }

	# Convert textual daemon_uid to numerical
    if ($daemon_uid =~ /\D/)
    {
      # no numbers found, so assume it's a textual name

      $daemon_uid_name = $daemon_uid;

      $daemon_uid = getpwnam($daemon_uid_name);
      if (!defined($daemon_uid))
      {        
        if ($syslog_system_enable == 1) {
          syslog_system("Could not convert user id \'$daemon_uid_name\' to a numeric UID\n");
          syslog_system("SNMPTT $snmptt_version shutdown");
        }
        if ($log_system_enable == 1) {
          log_system("Could not convert user id \'$daemon_uid_name\' to a numeric UID\n");
          log_system("SNMPTT $snmptt_version shutdown");
        }
        if ($DEBUGGING >= 1)
        {
          print "Could not convert user id \'$daemon_uid_name\' to a numeric UID\n";
          print "SNMPTT $snmptt_version shutdown";
        }

        warn("Could not convert user id \'$daemon_uid_name\' to a numeric UID\n");
        exit 1;
      }
    }

    # Build textual user group list
    
    # Get user primary group
    (my $name, my $passwd, my $uid, my $gid, my $quota, my $comment, my $gcos, my $dir, my $shell, my $expire) = getpwuid($daemon_uid);
    my $daemon_gid = $gid;   # numerical
    $daemon_gid_name = getgrgid($daemon_gid); # textual
    if ($DEBUGGING >= 1) {
      syslog_system("daemon_uid primary group: $daemon_gid_name ($daemon_gid)\n");
    }
    $daemon_gid_name .= " ";
    
    # Get OS groups
    $daemon_gid .= " ";
    while( (my $name, my $passwd, my $gid, my $members_list) = getgrent() ) {
      my @members = split(" ", $members_list);
      
      if ($daemon_uid_name ~~ @members) {
        if ($DEBUGGING >= 1) {
          syslog_system("daemon_uid $daemon_uid_name found in group $name\n");
        }
        $daemon_gid_name .= "$name ";
      }
    }
    chop($daemon_gid_name);
    
    $daemon_gid = $daemon_gid_name;
    my $daemon_gid_list = "";
    my @daemon_gids = split(' ', $daemon_gid);
    if ($DEBUGGING >= 1) {
      syslog_system("daemon_uid group list: @daemon_gids\n");
    }
    
    # Convert textual daemon_gid's to numerical
    foreach my $daemon_gid (@daemon_gids) {   
      if ($daemon_gid =~ /\D/)
      {
        # no numbers found, so assume it's a textual name

        my $daemon_gid_name_temp = $daemon_gid;

        $daemon_gid = getgrnam($daemon_gid_name_temp);
        if (!defined($daemon_gid))
        {        
          if ($syslog_system_enable == 1) {
            syslog_system("Could not convert group id \'$daemon_gid_name_temp\' to a numeric GID\n");
            syslog_system("SNMPTT $snmptt_version shutdown");
          }
          if ($log_system_enable == 1) {
            log_system("Could not convert group id \'$daemon_gid_name_temp\' to a numeric GID\n");
            log_system("SNMPTT $snmptt_version shutdown");
          }
          if ($DEBUGGING >= 1)
          {
            print "Could not convert group id \'$daemon_gid_name_temp\' to a numeric GID\n";
            print "SNMPTT $snmptt_version shutdown";
          }

          warn("Could not convert group id \'$daemon_gid_name_temp\' to a numeric GID\n");
          exit 1;
        }
      }
      $daemon_gid_list .= "$daemon_gid ";
    }
    chop($daemon_gid_list);
   
    $daemon_gid = $daemon_gid_list;
    if ($DEBUGGING >= 1) {
      syslog_system("Final daemon_gid group list: $daemon_gid_name ($daemon_gid)\n");
    }

    # Change current uid to daemon_uid and gid to daemon_gid
    if (defined($daemon_uid))
    {
      if ($daemon_uid_name ne '')
      {
        if ($syslog_system_enable == 1) {
          syslog_system("Changing to UID: $daemon_uid_name \($daemon_uid\), GID: $daemon_gid_name \($daemon_gid\)");
        }
        if ($log_system_enable == 1) {
          log_system("Changing to UID: $daemon_uid_name \($daemon_uid\), GID: $daemon_gid_name \($daemon_gid\)");
        }

        if ($DEBUGGING >= 1)
        {
          print "Changing to UID: $daemon_uid_name \($daemon_uid\), GID: $daemon_gid_name \($daemon_gid\)\n";
        }
      }
      else
      {
        if ($syslog_system_enable == 1) {
          syslog_system("Changing to UID: $daemon_uid, GID: $daemon_gid");
        }
        if ($log_system_enable == 1) {
          log_system("Changing to UID: $daemon_uid, GID: $daemon_gid");
        }

        if ($DEBUGGING >= 1)
        {
          print "Changing to UID: $daemon_uid, GID: $daemon_gid\n";
        }
      }


      if ($DEBUGGING >= 1) {
        print DEBUGFILE "Closing debug file $DEBUGGING_FILE\n";
      }
      # Close debug file (if it is open) before changing users and re-open after
      close(DEBUGFILE);
	  
	    # Change owner of debug file so $daemon_uid can write to it.
	    chown $daemon_uid, -1, $DEBUGGING_FILE;

      # Permanently change to new uid/gid by setting both effetive and real IDs.
      $) = $daemon_gid;
      $( = $);
	  
      $> = $daemon_uid;
      $< = $>;
	  
      if ($< != $daemon_uid || $( != $daemon_gid) {	  
        if ($syslog_system_enable == 1) {
          syslog_system("Could not change to UID: $daemon_uid, GID: $daemon_gid");
          syslog_system("SNMPTT $snmptt_version shutdown");
        }
        if ($log_system_enable == 1) {
          log_system("Could not change to UID: $daemon_uid, GID: $daemon_gid");
          log_system("SNMPTT $snmptt_version shutdown");
        }

        # This won't make it into the debug log..  
        if ($DEBUGGING >= 1)
        {
          print "Could not change to UID: $daemon_uid, GID: $daemon_gid\n";
          print "SNMPTT $snmptt_version shutdown";
        }

        warn "Could not change to UID: $daemon_uid, GID: $daemon_gid";
        exit 1;
	    }

      $debug_file_open_error = 1;
      # Re-open debug file (if needed) as the new user
      if ($DEBUGGING >= 1)
      {        
        &reopen_debug_file;
      }
    }
  }

  # Open connections to MySQL, PostresQL, ODBC etc
  # (Do this after switch user IDs when daemon_uid is defined)
  create_db_connections();

  if ($threads_enable == 1) {
    $thread_exec_semaphore = ();
    $thread_exec_semaphore = Thread::Semaphore->new($threads_max);
  }
  
  while (!$timetodie)
  {
    # If debugging, flush the buffers so we can tail snmptt.debug file.
    if ($DEBUGGING >= 1) {
      $| = 1;
      $| = 0;
    }

    if ($duplicate_trap_window) {
      # Purge traps older than duplicate_trap_window in %duplicate_traps
      my $duplicate_traps_current_time = time();
      foreach my $key (sort keys %duplicate_traps)
      {
        #print "Previous trap digest: $key: " . localtime($duplicate_traps{$key}) . "\n";
        if ($duplicate_traps{$key} < $duplicate_traps_current_time - $duplicate_trap_window) {
          # Purge the record
          delete $duplicate_traps{$key};
          #print "  Deleted...\n";
        }
      }
      #print "\n";
    }

    if ($spool_directory eq '')
    {
      if ($DEBUGGING >= 1)
      {
        print "No spool dir defined\n";
      }
      warn "No spool dir defined\n";
      if ($syslog_system_enable == 1)
      {
        syslog_system("No spool dir defined");
      }
      if ($log_system_enable == 1)
      {
        log_system("No spool dir defined");
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system("No Spool dir defined",3,$eventlog_error);
      } 
    }
    elsif (! (chdir($spool_directory)))
    {
      if ($DEBUGGING >= 1)
      {
        print "Unable to enter spool dir $spool_directory:$!\n";
      }
      warn "Unable to enter spool dir $spool_directory:$!\n";

      if ($syslog_system_enable == 1)
      {
        syslog_system("Unable to enter spool dir $spool_directory");
      }
      if ($log_system_enable == 1)
      {
        log_system("Unable to enter spool dir $spool_directory");
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system("Unable to enter spool dir $spool_directory",3,$eventlog_error);
      }
    }
    elsif (! (opendir(DIR, ".")))
    {
      if ($DEBUGGING >= 1)
      {
        print "Unable to open $spool_directory:$!\n";
      }
      warn "Unable to open spool dir $spool_directory:$!\n";

      if ($syslog_system_enable == 1)
      {
        syslog_system("Unable to open spool dir $spool_directory");
      }
      if ($log_system_enable == 1)
      {
        log_system("Unable to open spool dir $spool_directory");
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system("Unable to open spool dir $spool_directory",4,$eventlog_error);
      }
    }
    elsif (! (my @filenames = readdir(DIR)))
    {
      if ($DEBUGGING >= 1)
      {
        print "Unable to read spool dir $spool_directory:$!\n";
      }
      warn "Unable to read spool dir $spool_directory:$!\n";

      if ($syslog_system_enable == 1)
      {
        syslog_system("Unable to read spool dir $spool_directory");
      }
      if ($log_system_enable == 1)
      {
        log_system("Unable to read spool dir $spool_directory");
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system("Unable to read spool dir $spool_directory",5,$eventlog_error);
      }
    }
    else
    {
      closedir(DIR);

      @filenames = sort (@filenames);   # Sort list of filenames to ensure they
                                        # are processed in the order they were
                                        # received

      foreach my $file (@filenames)
      {
        next if ($file eq ".");
        next if ($file eq "..");

        if (lc($file) eq "!reload")
        {
          $timetoreload = 1;
          unless (unlink($file))
          {
            if ($DEBUGGING >= 1)
            {
              print "Unable to delete !reload file from spool dir:$!\n";
            }
            warn "Unable to delete !reload file from spool dir:$!\n";

            if ($syslog_system_enable == 1)
            {
              syslog_system("Unable to delete !reload file from spool dir");
            }
            if ($log_system_enable == 1)
            {
              log_system("Unable to delete !reload file from spool dir");
            }
            if ($eventlog_system_enable == 1)
            {
              eventlog_system("Unable to delete !reload file from spool dir",20,$eventlog_error);
            }
          }
          next;
        }

        if (lc($file) eq "!statistics")
        {
          $timetologstatistics = 1;
          unless (unlink($file))		      
          {
            if ($DEBUGGING >= 1)
            {
              print "Unable to delete !statistics file from spool dir:$!\n";
            }
            warn "Unable to delete !statistics file from spool dir:$!\n";

            if ($syslog_system_enable == 1)
            {
              syslog_system("Unable to delete !statistics file from spool dir");
            }
            if ($log_system_enable == 1)
            {
              log_system("Unable to delete !statistics file from spool dir");
            }
            if ($eventlog_system_enable == 1)
            {
              eventlog_system("Unable to delete !statistics file from spool dir",21,$eventlog_error);
            }
          }
          next;
        }

        if ($DEBUGGING >= 1)
        {
          print "Processing file: $file\n";
        }

        my $filesuccess = 1;
        unless (open FILE, $spool_directory.$file)
        {
          if ($DEBUGGING >= 1)
          {
            print "Could not open trap file $spool_directory$file: ($!)\n";
          }
          warn "Could not open trap file $spool_directory$file: ($!)\n";

          if ($syslog_system_enable == 1)
          {
            syslog_system("Could not open trap file $spool_directory$file");
          }
          if ($log_system_enable == 1)
          {
            log_system("Could not open trap file $spool_directory$file");
          }
          if ($eventlog_system_enable == 1)
          {
            eventlog_system("Could not open trap file $spool_directory$file",6,$eventlog_error);
          }

          $filesuccess = 0;
        }

        $input = 'FILE';

        my $readtrap_result = &readtrap();      # Read trap from STDIN or file

        if ($readtrap_result == 0) {
          if ($DEBUGGING >= 1) {
          print "  Error processing trap file $file.  Skipping...\n";
          }
          next;
        }

        my $trap_is_a_duplicate = 0;
        if ($readtrap_result == -1) {
          $trap_is_a_duplicate = 1;
          if ($DEBUGGING >= 1) {
            print "  Duplicate trap detected in trap file $file.  Skipping...\n";
          }
        }

        # Search for trap only if it's not a duplicate.
        if (! ($trap_is_a_duplicate)) {			
          &searchfortrap;		# Search for trap snmptt.conf (array)
        }
        close FILE;

        if ($filesuccess == 1)
        {
          if ($keep_unlogged_traps == 0 || $trap_successfully_logged == 1 || $trap_is_a_duplicate)
          {
            unless (unlink($file))
            {
              if ($DEBUGGING >= 1)
              {
                print "Unable to delete trap file $file from spool dir:$!\n";
              }
              warn "Unable to delete trap file $file from spool dir:$!\n";

              if ($syslog_system_enable == 1)
              {
                syslog_system("Unable to delete trap file $file from spool dir");
              }
              if ($log_system_enable == 1)
              {
                log_system("Unable to delete trap file $file from spool dir");
              }
              if ($eventlog_system_enable == 1)
              {
                eventlog_system("Unable to delete trap file $file from spool dir",7,$eventlog_error);
              }
            }
          }
        }
      }
    }

    if ($statistics_interval > 0) {
      if (time() >= ($g_last_statistics_logged + $statistics_interval)) {
        &log_statistics();
      }
    }

    if ($mysql_dbi_enable == 1 && $mysql_ping_interval > 0) {
      if (time() >= ($g_last_mysql_ping + $mysql_ping_interval)) {
        &mysql_ping();
      }
    }

    if ($postgresql_dbi_enable == 1 && $postgresql_ping_interval > 0) {
      if (time() >= ($g_last_postgresql_ping + $postgresql_ping_interval)) {
        &postgresql_ping();
      }
    }

    if ($dbd_odbc_enable == 1 && $dbd_odbc_ping_interval > 0) {
      if (time() >= ($g_last_dbd_odbc_ping + $dbd_odbc_ping_interval)) {
        &dbd_odbc_ping();
      }
    }

    if ($timetologstatistics == 1)
    {
      &log_statistics();

      $timetologstatistics = 0;
    }

    if ($DEBUGGING >= 1)
    {
      print "Sleeping for $sleep seconds\n\n";
    }
    sleep $sleep;

    if ($timetoreload == 1)
    {
      if ($DEBUGGING >= 1)
      {
        print "Reloading configuration file\(s\)\n\n";
      }

      if ($syslog_system_enable == 1)
      {
        syslog_system("Reloading configuration file\(s\)");
      }
      if ($log_system_enable == 1)
      {
        log_system("Reloading configuration file\(s\)");
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system("Reloading configuration file\(s\)",8,$eventlog_information);
      }

      &loadsnmpttini;		# Load ini file
      &loadsnmpttconf;	# Load SNMPTT.CONF file
      $timetoreload = 0;
    }
  }

  # If $daemon_uid was not set, clean up pid file here.  Otherwise it's cleaned up
  # when the child process is finished above.
  if ($^O ne "MSWin32" && $daemon_uid eq '') {
    # Clean up snmptt.pid file
    unlink($pid_file);
  }

  if ($DEBUGGING >= 1)
  {
    print "SNMPTT $snmptt_version shutdown: ",scalar(localtime),"\n\n";
    print "Total traps received:    $g_total_traps_received\n";
    print "Total traps translated:  $g_total_traps_translated\n";                
    print "Total traps ignored:     $g_total_traps_ignored\n";                
    print "Total unknown traps:     $g_total_traps_unknown\n\n";
  }

  if ($syslog_system_enable == 1 &&  $daemon == 1)
  {
    syslog_system("SNMPTT $snmptt_version shutdown");
    syslog_system("Total traps received=$g_total_traps_received,Total traps translated=$g_total_traps_translated,Total traps ignored=$g_total_traps_ignored,Total unknown traps=$g_total_traps_unknown");
    #syslog_system("Total traps received:    $g_total_traps_received");
    #syslog_system("Total traps translated:  $g_total_traps_translated");
    #syslog_system("Total traps ignored      $g_total_traps_ignored");
    #syslog_system("Total unknown traps:     $g_total_traps_unknown");                
  }
  if ($log_system_enable == 1 &&  $daemon == 1)
  {
    log_system("SNMPTT $snmptt_version shutdown");
    log_system("Total traps received=$g_total_traps_received,Total traps translated=$g_total_traps_translated,Total traps ignored=$g_total_traps_ignored,Total unknown traps=$g_total_traps_unknown");
    #syslog_system("Total traps received:    $g_total_traps_received");
    #syslog_system("Total traps translated:  $g_total_traps_translated");
    #syslog_system("Total traps ignored      $g_total_traps_ignored");
    #syslog_system("Total unknown traps:     $g_total_traps_unknown");                
  }
  if ($eventlog_system_enable == 1 &&  $daemon == 1)
  {
    my $message = "SNMPTT $snmptt_version shutdown\n\n" . \
                  "Total traps received:    $g_total_traps_received\n" . \
                  "Total traps translated:  $g_total_traps_translated\n" . \
                  "Total traps ignored:     $g_total_traps_ignored\n" . \
                  "Total unknown traps:     $g_total_traps_unknown\n";

    eventlog_system($message,1,$eventlog_information);
  }
}
else
{
  # NOT daemon mode...

  # Open connections to MySQL, PostresQL, ODBC etc
  create_db_connections();

  $input = 'STDIN';

  &readtrap;		# Read trap from STDIN or file

  &loadsnmpttconf;	# Load SNMPTT.CONF file

  &searchfortrap;		# Search for trap snmptt.conf (array)
}

&close_db_connections();

exit();

####  MAIN SECTION END

sub processtrap
{
  # Variables of trap received by SNMPTRAPD:
  # 
  # $var[0]		hostname
  # $var[1] 		ip address
  # $var[2] 		uptime
  # $var[3] 		trapname / OID
  # $var[4] 		ip address from trap agent
  # $var[5] 		trap community string
  # $var[6] 		enterprise
  # $var[7] 		securityEngineID                (snmptthandler-embedded required)
  # $var[8] 		securityName                    (snmptthandler-embedded required)
  # $var[9] 		contextEngineID                 (snmptthandler-embedded required)
  # $var[10] 		contextName                     (snmptthandler-embedded required)
  #
  # $entvarname[0]	passed variable name 1
  # $entvarname[1]	passed variable name 2
  #
  # $entvar[0]		passed variable 1
  # $entvar[1]		passed variable 2
  # .
  # .
  # etc..
  # 
  # $event hash for each trapped defined in snmptt.conf with following values:
  # 
  # 0: name of trap
  # 1: category
  # 2: severity
  # 3: FORMAT string
  # 4: EXEC string
  # 5: NODES string
  # 6: REGEX array
  # 7: MATCH array
  # 8: DESC array
  # 9: PREEXEC string
  #
  # Example: 	To access FORMAT string for received trap:
  #  $event{"$var[3]"}[3]
  #
  #   or
  #
  #  $event{"$receivedtrap_entry"}[3]	
  ###############################################################################
  # if no nodes list, then $hostmatch = 1 so trap is logged etc, otherwise it is not

  my $l = 1;	# Start with first event entry

  my $multiple_event_passes = 0; # This variable increases each time we match an EVENT.
                              # Below, if =1, then abort because we already found a match for
                              # this trap.  This would only happen if you have a trap
                              # defined multiple times in the config file to allow 
                              # different machines to have different actions based on 
                              # the node name.

  while (defined($event{$receivedtrap_entry}[$l+1]))
  {
    if (($multiple_event == 0) && ($multiple_event_passes > 0))
    {
      if ($DEBUGGING >= 1)
      {
        print "  multiple_event = $multiple_event, multiple_event_passes = " .
              "$multiple_event_passes so don't process any more entries\n";
      }
      last;	# Don't look any further - stop
    }
    if ($DEBUGGING >= 1)
    {
      print  "Working with EVENT entry: $receivedtrap_entry => $event{$receivedtrap_entry}[0+$l],$event{$receivedtrap_entry}[1+$l],$event{$receivedtrap_entry}[2+$l],$event{$receivedtrap_entry}[5+$l]\n";
    }

    my $hostmatch = 0;
    my $nodesmatch = 0;	# Match from NODES
    my $match_found = 0;	# Match from MATCH
    my $preexec_already_run = 0;

    # $event is the hash of events defined in the config file
    # $event2 is a copy of the matching event

    # Flush out @event2
    @event2 = ();

    # Flush out @nodes
    #@nodes = ();
    my @nodes2 = ();

    # $event2[0]=$event{"$receivedtrap_entry"}[0];	# 0: name of trap	
    # $event2[1]=$event{"$receivedtrap_entry"}[1];	# 1: category
    # $event2[2]=$event{"$receivedtrap_entry"}[2];	# 2: severity
    # $event2[3]=$event{"$receivedtrap_entry"}[3];	# 3: FORMAT string
    # $event2[4]=$event{"$receivedtrap_entry"}[4];	# 4: EXEC string
    # $event2[5]=$event{"$receivedtrap_entry"}[5];	# 5: NODES string

    $event2[0]=$event{"$receivedtrap_entry"}[0+$l];	# 0: name of trap	
    $event2[1]=$event{"$receivedtrap_entry"}[1+$l];	# 1: category
    $event2[2]=$event{"$receivedtrap_entry"}[2+$l];	# 2: severity
    $event2[3]=$event{"$receivedtrap_entry"}[3+$l];	# 3: FORMAT string
    $event2[4]=$event{"$receivedtrap_entry"}[4+$l];	# 4: EXEC string
    $event2[5]=$event{"$receivedtrap_entry"}[5+$l];	# 5: NODES string
    $event2[6]=$event{"$receivedtrap_entry"}[6+$l];	# 6: REGEX array
    $event2[7]=$event{"$receivedtrap_entry"}[7+$l];	# 7: MATCH array
    $event2[8]=$event{"$receivedtrap_entry"}[8+$l];	# 8: DESC array
    $event2[9]=$event{"$receivedtrap_entry"}[9+$l];	# 9: PREEXEC array

    $l+=11; # Also update in 'Printing out all the events in hash table'

    if ($event2[5] eq '')
    {
      $nodesmatch = 1;
      #$hostmatch = 1;		# No nodes defined, so default to all nodes
      #$multiple_event_passes++;
      #$processed = 1;
      if ($DEBUGGING >= 1)
      {
        print "  No nodes defined for this entry so all nodes will match\n";
      }
    }
    else
    {
      if ($DEBUGGING >= 1)
      {
        print "  Nodes defined for this entry...\n";
      }

      if ($dynamic_nodes == 0) {
        @nodes2 = split /\s/, $event2[5];
      }
      else {
        @nodes2 = &process_nodes($event2[5]);
      }

      if ($DEBUGGING >= 1)
      {
        print "  NODES entries: @nodes2\n";
      }

       my $nodes_mode = 'pos';     # 0 = POSitive match
                                    # nodes2 contains all the node entries after being 'extracted' from multiple
                                    # NODES lines, NODES files etc.
       foreach my $a (@nodes2)
       {
         # Check for MODE statement.  Default is POSitive.
         if ($a =~ /^mode=(.*)/i) {
           if ($1 =~ /^neg/i) {
             $nodes_mode = "neg";
           }
         next;
       }
       elsif ($a =~ /^(\d+\.\d+\.\d+\.\d+)/) # NODES entry is an IP address / cidr / range
       {
         #if ( $a eq $var[1])     # compare against agent ip
         if ( checkip($var[4], $a) == 1)
         {
           $nodesmatch = 1;
         }
       }
       elsif ($dns_enable == 1)	# Resolve NODES entry to IP address, and compare
       {
         my $temp = gethostbyname($a);
         if (defined($temp))
         {
           $temp = Socket::inet_ntoa(scalar($temp));
           if ($DEBUGGING => 1)
           {
             print "    NODES entry ($a) resolved to: $temp\n";
           }
           if ( checkip($var[4], $temp) == 1)
           {
             $nodesmatch = 1;
           }
         }
         else
         {
           print "    NODES entry ($a) could NOT be resolved.\n";
         }
       }
       elsif (lc $a eq lc $agent_dns_name)	# NODES entry is a host name.  Do lowercase compare
       {
         $nodesmatch = 1;
       }
     }
     # If NODES MODE=NEG, then reverse the result.
     if ($nodes_mode eq 'neg') {
       if ($nodesmatch == 0) {
         $nodesmatch = 1;
       }
       else {
         $nodesmatch = 0;
       }
     }
     if ($DEBUGGING >= 1)
     {
        if ($nodes_mode eq 'pos') {
          if ($nodesmatch == 1) {
            print "  NODES has a positive match (node mode \'pos\' and node found in list)\n";
          }
          else {
            print "  NODES has a negative match (node mode \'pos\' and node NOT found in list)\n";
          }
        }
        else {
          if ($nodesmatch == 1) {
            print "  NODES has a positive match (node mode \'neg\' and node NOT found in list)\n";
          }
          else {
            print "  NODES has a negative match (node mode \'neg\' and node found in list)\n";
       }
       }
     }
   }

    # Check for MATCH entries
    if ($nodesmatch == 1 && defined($event2[7][0]))  # Only if NODES has matched and 
    {                                                 # we have some MATCH statements
      my @match = ();
      my $match_type;

      for (my $i=0; defined($event2[7][$i]); $i++)
      {
        my $match_temp = $event2[7][$i];
        $match_temp =~ s/\s//g;	# Remove any white space from $match
        if ($match_temp =~ /^mode=(.*)/i)
        {
          $match_type = lc($1);
          last;
        }
      }
      if ($match_type ne 'and' && $match_type ne 'or')
      {
        $match_type = 'or';
      }
      if ($DEBUGGING >= 2)
      {
        print "  MATCH statements found\n";
        print "    MATCH mode: " . uc($match_type) . "\n";
      }

      for (my $i=0; defined($event2[7][$i]); $i++)
      {
        my $match_temp = $event2[7][$i];

        # If it's a regex (has ()) then remove white space before and after ()s.  
        # Otherwise, remove all white space
        #print "!$match_temp!\n";
        if ($match_temp =~ /\(|\)/)
        {
          $match_temp =~ s/\s*(\(.*\))\s*/$1/g;  # Remove any white space from before and after ()'s

          $match_temp =~ s/\)\s*(i)\s*/\)$1/g;  # Remove any white space from before and after i modifier
                                                  # if there is one

          $match_temp =~ s/\s*(\!.*)/$1/g;       # Remvoe any white space in front of the ! if there is one
        }
        else
        {
          $match_temp =~ s/\s//g;	# Remove any white space from $match
        }
        #print "!$match_temp!\n";

        # If MATCH statement contains any $p variables (PREEXEC results), then we need to execute PREEXEC first.
        # It's usually run after NODES and MATCH.
        if ($match_temp =~ /\$p/) {
          if ($preexec_already_run == 0) {
            if ($DEBUGGING >= 1)
            {
              print "\nMATCH statement contains \$p value(s).  Executing PREEXEC line(s)...\n";
            }
            &preexec_run;
            $preexec_already_run = 1;
          }
        }

        # DEFAULT= line
        if ($match_temp =~ /^mode=(.*)/i)
        {
          next;
        }

        if (match($match_temp) == 1)
        {
          $match_found = 1;
          if ($match_type eq "or")
          {
            last;
          }
        }
        else
        {
          $match_found = 0;
          if (lc($match_type) eq "and")
          {
            last;
          }
        }
      }
      if ($DEBUGGING >= 1)
      {
        if ($match_found == 1)
        {
          print "  MATCH statement(s) final result = true...\n\n";
        }
        else 
        {
          print "  MATCH statement(s) final result = false...\n\n";
        }
      }
    }
    else
    {
      if ($DEBUGGING >= 1)
      {
        print "  No MATCH entries defined for this entry\n";
      }
      $match_found = 1;	# Default to match_found if no MATCH entries
    }

    if ($nodesmatch == 1 && $match_found == 1)
    {
      $multiple_event_passes++;
      $hostmatch = 1;
      $processed = 1;
    }

    my $message_short;
    my $message;
    if ($hostmatch == 1 && $event2[1] ne "IGNORE")
    {
      $message_short = "";

      # Trap received exists in our list of trap definitions

      # Statistics
      $g_total_traps_translated++;

      if ($DEBUGGING >= 1)
      {
        print "\nTrap defined, processing...\n\n";
      }

      #
      # Variable substitution for PREEXEC string
      #
      if ($preexec_already_run == 0) {
        &preexec_run;
        $preexec_already_run = 1;
      }

      if ($DEBUGGING >= 1)
      {
        print "\n\nFORMAT line:\n";				
      }

      #
      # Variable substitution for FORMAT string
      #

      if ($event2[3] ne '')   # if FORMAT string has been defined
      {		
        # Variable substitution for FORMAT string

        $_ = $event2[3];	# FORMAT string

        # Translate if enabled, and if we can
        $receivedtrap_trans = &translate_log_trap_oid_sub($receivedtrap);

        $enterprise_trans = &translate_enterprise_oid_format_sub($var[6]);

        &substitute;

        $message_short = $_;

        $message = "$receivedtrap_trans $event2[2] \"$event2[1]\" $agent_dns_name - $message_short\n";

        if ($stdout_enable == 1)
        {
          #select STDOUT;
          print STDOUT "$message";

          #if ($debug_file_used == 1)
          #{
          #  select DEBUGFILE;
          #}
        }

        if ($DEBUGGING >= 1)
        {
          print "$message_short\n";
          print "\n$message";
        }

        if ($log_enable == 1)
        {
          $trap_attempted_to_log++;	

          if (open LOGFILE, ">>$log_file")
          {
            print LOGFILE $trap_date_time." $message";
            close LOGFILE;
            $trap_successfully_logged++;
          }
          else
          {
            warn "Can not open log file $log_file: $!";

            if ($syslog_system_enable == 1)
            {
              syslog_system("Can not open log file $log_file");
            }
            if ($log_system_enable == 1)
            {
              log_system("Can not open log file $log_file");
            }
            if ($eventlog_system_enable == 1)
            {
              eventlog_system("Can not open log file $log_file",14,$eventlog_error);
            }
          }
        }

        if ($syslog_enable == 1)
        {
          $trap_attempted_to_log++;

          my $syslog_level_temp = 99;
          my $temp;

          foreach $temp (@syslog_level_debug)
          {
            if (lc($event2[2]) eq lc($temp)) { $syslog_level_temp = 'debug' }
          }
          foreach $temp (@syslog_level_info)
          {
            if (lc($event2[2]) eq lc($temp)) { $syslog_level_temp = 'info' }
          }
          foreach $temp (@syslog_level_notice)
          {
            if (lc($event2[2]) eq lc($temp)) { $syslog_level_temp = 'notice' }
          }
          foreach $temp (@syslog_level_warning)
          {
            if (lc($event2[2]) eq lc($temp)) { $syslog_level_temp = 'warning' }
          }
          foreach $temp (@syslog_level_err)
          {
            if (lc($event2[2]) eq lc($temp)) { $syslog_level_temp = 'err' }
          }
          foreach $temp (@syslog_level_crit)
          {
            if (lc($event2[2]) eq lc($temp)) { $syslog_level_temp = 'crit' }
          }
          foreach $temp (@syslog_level_alert)
          {
            if (lc($event2[2]) eq lc($temp)) { $syslog_level_temp = 'alert' }
          }

          if ($syslog_level_temp == 99)
          {
            $syslog_level_temp = $syslog_level;
          }

          #if (Sys::Syslog::openlog('TRAPD', '',$syslog_facility) )
          #if (Sys::Syslog::openlog('snmptt[' . $> . ']', '',$syslog_facility) )
          eval {
            if (Sys::Syslog::openlog('snmptt', 'pid',$syslog_facility) )
            {
              # Escape all %'s before sending to syslog.  A single % may cause syslog to hang
              $message =~ s/%/%%/g;
              Sys::Syslog::syslog ($syslog_level_temp, $message,1);
              Sys::Syslog::closelog();
              $trap_successfully_logged++;
            }
          };
          warn "Cannot write to syslog: $@\n" if $@;
        }

        if ($eventlog_enable == 1)
        {
          $trap_attempted_to_log++;

          my $eventlog_type_temp = 99;
          my $temp;

          foreach $temp (@eventlog_type_information)
          {
            if (lc($event2[2]) eq lc($temp)) { $eventlog_type_temp = $eventlog_information }
          }

          foreach $temp (@eventlog_type_warning)
          {
            if (lc($event2[2]) eq lc($temp)) { $eventlog_type_temp = $eventlog_warning }
          }

          foreach $temp (@eventlog_type_error)
          {
            if (lc($event2[2]) eq lc($temp)) { $eventlog_type_temp = $eventlog_error }
          }

          if ($eventlog_type_temp == 99)
          # Use the default eventlog type
          {
            if ($eventlog_type =~ /INFORMATION/s)
            {
              if (eventlog_system($message,2,$eventlog_information) == 0)
              {
                $trap_successfully_logged++;
              }
            }
            elsif ($eventlog_type =~ /ERROR/s)
            {
              if (eventlog_system($message,2,$eventlog_error) == 0)
              {
                $trap_successfully_logged++;
              }
            }
            else
            {
              if (eventlog_system($message,2,$eventlog_warning) == 0)
              {
                $trap_successfully_logged++;
              }
            }
          }
          else
          {
            if (eventlog_system($message,2,$eventlog_type_temp) == 0)
            {
              $trap_successfully_logged++;
            }
          }
        }

        if ($net_snmp_perl_enable == 1 && $db_translate_enterprise == 1)
        {
          $db_enterprise = $enterprise_trans;
        }
        else
        {
          $db_enterprise = $var[6];
        }

        if ($mysql_dbi_enable == 1)
        {
          $trap_attempted_to_log++;

          # Backslash any quotes
          my $message_short2 = $message_short;
          $message_short2 =~ s(\')(\\\')g;   #'
          $message_short2 =~ s(\")(\\\")g;   #"
              
          my $community = $var[5];
          $community =~ s(\')(\\\')g;   #'
          $community =~ s(\")(\\\")g;   #"

          my @t_sql_custom_columns = ();
          
          if (@sql_custom_columns) {
            @t_sql_custom_columns = @sql_custom_columns;
            
            for (my $i = 1; $i <= $#t_sql_custom_columns; $i+=2) {
              $_ = $t_sql_custom_columns[$i];
              print "Performing substitution on custom column: $_\n";
              &substitute();
              print "Done performing substitution on custom column: $_\n";
              $t_sql_custom_columns[$i] = $_;
            }
          }

          if (&mysql_insert($mysql_dbi_table,
            "eventname", $event2[0],                # $N
            "eventid", $receivedtrap_entry,         # $i
            "trapoid", $receivedtrap_trans,         # $O
            "enterprise", $db_enterprise,           # $E or $e depending on $db_translate_enterprise
            "community", $community,                # $C
            "hostname", $agent_dns_name,            # $A
            "agentip", $var[4],                     # $aA
            "category", $event2[1],                 # $c
            "severity", $event2[2],                 # $s
            "uptime", $var[2],                      # $T
            "traptime", $trap_date_time_sql,
            "formatline", $message_short2,
            @t_sql_custom_columns) == 1 ) { # 
            $trap_successfully_logged++;
          }
        }

        if ($postgresql_dbi_enable == 1)
        {
          $trap_attempted_to_log++;

          # Backslash any quotes
          my $message_short2 = $message_short;
          $message_short2 =~ s(\')(\\\')g;   #'
          $message_short2 =~ s(\")(\\\")g;   #"

          my $community = $var[5];
          $community =~ s(\')(\\\')g;   #'
          $community =~ s(\")(\\\")g;   #"

          my @t_sql_custom_columns = ();
          
          if (@sql_custom_columns) {
            @t_sql_custom_columns = @sql_custom_columns;
            
            for (my $i = 1; $i <= $#t_sql_custom_columns; $i+=2) {
              $_ = $t_sql_custom_columns[$i];
              print "Performing substitution on custom column: $_\n";
              &substitute();
              print "Done performing substitution on custom column: $_\n";
              $t_sql_custom_columns[$i] = $_;
            }
          }

          if (&postgresql_insert($postgresql_dbi_table,
            "eventname", $event2[0],                # $N
            "eventid", $receivedtrap_entry,         # $i
            "trapoid", $receivedtrap_trans,         # $O
            "enterprise", $db_enterprise,           # $E or $e depending on $db_translate_enterprise
            "community", $community,                # $C
            "hostname", $agent_dns_name,            # $A
            "agentip", $var[4],                     # $aA
            "category", $event2[1],                 # $c
            "severity", $event2[2],                 # $s
            "uptime", $var[2],                      # $T
            "traptime", $trap_date_time_sql,
            "formatline", $message_short2,
            @t_sql_custom_columns) == 1 ) { # 
            $trap_successfully_logged++;
          }
        }

        if ($dbd_odbc_enable == 1)                       
        {
          $trap_attempted_to_log++;

          # Double any single quotes
          my $message_short2 = $message_short;
          $message_short2 =~ s(\')('')g;     #'

          my $community = $var[5];
          $community =~ s(\')('')g;   #'

          my @t_sql_custom_columns = ();
          
          if (@sql_custom_columns) {
            @t_sql_custom_columns = @sql_custom_columns;
            
            for (my $i = 1; $i <= $#t_sql_custom_columns; $i+=2) {
              $_ = $t_sql_custom_columns[$i];
              print "Performing substitution on custom column: $_\n";
              &substitute();
              print "Done performing substitution on custom column: $_\n";
              $t_sql_custom_columns[$i] = $_;
            }
          }

          if (&odbc_insert($dbd_odbc_table,
            "eventname", $event2[0],                # $N
            "eventid", $receivedtrap_entry,         # $i
            "trapoid", $receivedtrap_trans,         # $O
            "enterprise", $db_enterprise,           # $E or $e depending on $db_translate_enterprise
            "community", $community,                # $C
            "hostname", $agent_dns_name,            # $A
            "agentip", $var[4],                     # $aA
            "category", $event2[1],                 # $c
            "severity", $event2[2],                 # $s
            "uptime", $var[2],                      # $T
            "traptime", $trap_date_time_sql,
            "formatline", $message_short2,
            @t_sql_custom_columns) == 1 ) { # 
            $trap_successfully_logged++;
          }
        }

        if ($sql_win32_odbc_enable)
        {
          $trap_attempted_to_log++;

          # Double any single quotes
          my $message_short2 = $message_short;
          $message_short2 =~ s(\')('')g;     #'

          my $community = $var[5];
          $community =~ s(\')('')g;   #'

          my @t_sql_custom_columns = ();
          
          if (@sql_custom_columns) {
            @t_sql_custom_columns = @sql_custom_columns;
            
            for (my $i = 1; $i <= $#t_sql_custom_columns; $i+=2) {
              $_ = $t_sql_custom_columns[$i];
              print "Performing substitution on custom column: $_\n";
              &substitute();
              print "Done performing substitution on custom column: $_\n";
              $t_sql_custom_columns[$i] = $_;
            }
          }

          if (&sql_win32_odbc_insert($sql_win32_odbc_table,
            "eventname", $event2[0],                # $N
            "eventid", $receivedtrap_entry,         # $i
            "trapoid", $receivedtrap_trans,         # $O
            "enterprise", $db_enterprise,           # $E or $e depending on $db_translate_enterprise
            "community", $community,                # $C
            "hostname", $agent_dns_name,            # $A
            "agentip", $var[4],                     # $aA
            "category", $event2[1],                 # $c
            "severity", $event2[2],                 # $s
            "uptime", $var[2],                      # $T
            "traptime", $trap_date_time_sql,
            "formatline", $message_short2,
            @t_sql_custom_columns) == 1 ) { # 
            $trap_successfully_logged++;
          }
        }

      } # end block for if ($event2[3] ne '') if FORMAT string has been defined
      else
      {
        if ($DEBUGGING >= 1)
        {
          print "  FORMAT line not defined\n";
        }
      }

      #
      # Variable substitution for EXEC string
      #

      if ($DEBUGGING >= 1)
      {
        print "\n\nEXEC line(s):\n";
      }

      if (defined ($event2[4][0])) # if EXEC string has been defined
      {
        if ($event2[2] ne "LOGONLY")
        {
          for (my $i=0; defined($event2[4][$i]); $i++)
          {
            $_ = $event2[4][$i];

            &substitute($message_short);

            my $command = $_;

            if ($exec_enable == 1)
            {
              if ($DEBUGGING >= 1)
              {
                print "EXEC command:$command\n";
              }

              # Execute command
              if ($threads_enable == 1  && $daemon == 1) {
                if ($DEBUGGING >= 1)
                {
                  print "EXEC command - creating thread...\n";
                }
                my $exec_thread = threads->new(\&exec_thread_sub, $command);
                $exec_thread->detach;           # Detach and let it clean up after itself

                sub exec_thread_sub {
                  my $command = shift;
                  $thread_exec_semaphore->down;

                  if ($exec_escape == 1) {
                    # Escape wildcard characters
                    $command =~ s/\*/\\\*/g;
                    $command =~ s/\?/\\\?/g;
                  }
                  if ($DEBUGGING >= 1)
                  {
                    print "EXECing command in thread:$command\n";
                  }
                  system $command;
                  $thread_exec_semaphore->up;
                }
              }
              else {
                if ($exec_escape == 1) {
                  # Escape wildcard characters
                  $command =~ s/\*/\\\*/g;
                  $command =~ s/\?/\\\?/g;
                }
                system $command;
              }
            }
          }
        }
        elsif ($DEBUGGING >= 1)
        {
          print "  Trap severity defined as LOGONLY.  Skipping EXEC processing.\n\n";
        }
      } # end of block if ($event2[4] ne '')
      else
      {
        if ($DEBUGGING >= 1)
        {
          print "  EXEC line not defined\n";
        }
      }

    }
    else
    {
      if ($event2[1] eq "IGNORE") {
        # Statistics
        $g_total_traps_ignored++;
      }

      if ($DEBUGGING >= 1)
      {
        if ($event2[1] eq "IGNORE")
        {
          print "\nTrap set to IGNORE...\n\n";
        }
        else
        {
          print "\nTrap defined, but NODES or MATCH check failed - skipping this EVENT entry..\n\n";
        }
      }
    }
  } #while defined..
}

sub substitute
{
  my $format_line = shift;      # Used for $Fz variable substitution for EXEC line.

  # Perform substitution on $_ variable

  # Wildcards - $*, $+*, $-*
  # Expand to $1 $2 $3 etc
  my $temp_wildcard1 = ();
  my $temp_wildcard2 = ();
  my $temp_wildcard3 = ();
  for(my $i=1;$i <= $#entvar+1; $i++)
  {
    $temp_wildcard1 = $temp_wildcard1 . "\$$i" . $wildcard_expansion_separator;
    $temp_wildcard2 = $temp_wildcard2 . "\$\+$i" . $wildcard_expansion_separator;
    $temp_wildcard3 = $temp_wildcard3 . "\$-$i" . $wildcard_expansion_separator;
  }

  # Chop off last space
  if (defined ( $temp_wildcard1) )
  {
    chop ($temp_wildcard1);
    chop ($temp_wildcard2);
    chop ($temp_wildcard3);
  }

  #s(\$\*)($temp_wildcard1)g;
  &substitute2 ("\$\*", $temp_wildcard1);

  s(\$\+\*)($temp_wildcard2)g;
  &substitute2 ("\$\+\*", $temp_wildcard2);

  s(\$-\*)($temp_wildcard3)g;
  &substitute2 ("\$-\*", $temp_wildcard3);

 # $v - Names of variable-bindings 
 # Count down backwards to make sure 10 is not mistaken for $1
  for(my $i=$#entvarname+1;$i > 0; $i--)
  {
    if ($net_snmp_perl_enable == 1)
    {
      my $temp = my_translateObj($entvarname[$i-1],$translate_varname_oid_format);
      if (!defined ($temp) )
      {
        $temp = $entvarname[$i-1];
      }
      #s(\$v$i)($temp)g;
      &substitute2 ("\$v$i", $temp);
    }
    else
    {
      #s(\$v$i)($entvarname[$i-1])g;
      &substitute2 ("\$v$i", $entvarname[$i-1]);
    }
  }

  # $n - Variable-bindings
  # Count down backwards to make sure 10 is not mistaken for $1
  for(my $i=$#entvar+1;$i > 0; $i--)
  {
    my $val = $entvar[$i-1];

    if ($DEBUGGING >= 2)
    {
      print "\nVariable $entvarname[$i-1] with value $entvar[$i-1]\n";
    }

    # This will translate (if enabled) any OIDs found in the VALUE (not the variable name) to the textual  
    # name if possible.  For example, if the value was 'A UPS Alarm (.1.3.6.1.4.1.534.1.7.12) has cleared.', 
    # it could be translated to: 'A UPS Alarm (xupsBuildingAlarm) has cleared.'
    if ($translate_value_oids > 0 && $net_snmp_perl_enable == 1)
    {
      $val = &translate_value_oids_sub($val);
    }

    if ($dns_enable == 1 && $resolve_value_ip_addresses == 1 && $net_snmp_perl_enable == 1)
    {
      $val = &resolve_value_ip_addresses_sub($val);
    }

    if ($translate_integers == 1 && $net_snmp_perl_enable == 1)
    {
      if (! ($entvar[$i-1] =~ /\D+/))				# Check to see if value is numerical
      {
        if ($DEBUGGING >= 2)
        {
          print "  Value is numerical\n";
        }

        my $temp_OID_type = uc (my_getType($entvarname[$i-1]));
        if ($temp_OID_type eq "INTEGER32" || $temp_OID_type eq "INTEGER")  # Make sure it's a SNMP INTEGER type
        {
          if ($DEBUGGING >= 2)
          {
            print "  Value is defined as an INTEGER in the mib - will attempt to translate\n";
          }
          my $val2 = my_mapEnum($entvarname[$i-1], $entvar[$i-1]);
          if ($val2 ne '')					# Make sure we got something
          {
            $val = $val2;

            if ($DEBUGGING >= 2)
            {
               print "    Translated to: $val2\n";
            }
          }
          else
          {
            $val = $entvar[$i-1];
            if ($DEBUGGING >= 2)
            {
              print "    Could not translate\n";
            }
          }
        }
        else
        {
          if ($DEBUGGING >= 2)
          {
            print "  Value is NOT defined as an INTEGER or Integer32 in the mib\n";
          }
        }
      }
    }

    if ($net_snmp_perl_enable == 1)
    {
      #s(\$$i)($val)g;			# $n	      
      &substitute2 ("\$$i", $val);

      # Translate for $+n and $-n
      my $temp = my_translateObj($entvarname[$i-1],$translate_varname_oid_format);
      if (!defined ($temp) )
      {
        $temp = $entvarname[$i-1];
      }

      my $temp2 = &my_getType($entvarname[$i-1]);
      if (!defined ($temp2))
      {
        $temp2 = "unknown";
      }

      #s(\$\+$i)($temp:$val)g;		# $+n
      &substitute2 ("\$\+$i", "$temp:$val");

      #s(\$-$i)($temp ($temp2):$val)g;	# $-n
      &substitute2 ("\$-$i", "$temp ($temp2):$val");
    }
    else
    {
      #s(\$$i)($val)g;				# $n
      &substitute2 ("\$$i", $val);

      #s(\$\+$i)($entvarname[$i-1]:$val)g;		# $+n
      &substitute2 ("\$\+$i", "$entvarname[$i-1]:$val");      

      #s(\$-$i)($entvarname[$i-1] (unknown):$val)g;	# $-n
      &substitute2 ("\$-$i", "$entvarname[$i-1] (unknown):$val");
    }
  }

  # $Be - securityEngineID (snmpEngineID)
  &substitute2 ("\$Be", $var[7]);

  # $Bu - securityName (snmpCommunitySecurityName)
  &substitute2 ("\$Bu", $var[8]);

  # $BE - contextEngineID (snmpCommunityContextEngineID)
  &substitute2 ("\$BE", $var[9]);

  # $Bn - contextName (snmpCommunityContextName)
  &substitute2 ("\$Bn", $var[10]);

  # $c - Category
  #s(\$c)($event2[1])g;
  &substitute2 ("\$c", $event2[1]);

  # $C - Trap community string
  #s(\$C)($var[5])g;
  &substitute2 ("\$C", $var[5]);

  # $E - Enterprise trap (symbolic)
  #s(\$E)($enterprise_trans)g;
  &substitute2 ("\$E", $enterprise_trans);

  # $e - Enterprise trap (numerical)
  #s(\$e)($var[6])g;
  &substitute2 ("\$e", $var[6]);

  # $N - Event name
  #s(\$N)($event2[0])g;
  &substitute2 ("\$N", $event2[0]);

  # $i - Event OID
  #s(\$i)($receivedtrap_entry)g;
  &substitute2 ("\$i", $receivedtrap_entry);

  # $# - Number of variable-bindings in the trap
  my $entvar_temp = $#entvar+1;
  #s(\$#)($entvar_temp)g;
  &substitute2 ("\$#", $entvar_temp);

  # $O - Received trap (symbolic)
  if ($net_snmp_perl_enable == 1)
  {
    if ($DEBUGGING >= 2) {
      print "\nOID of received trap: $receivedtrap.  Will attempt to translate to text\n";
    }
    my $temp = my_translateObj("$receivedtrap",$translate_trap_oid_format);
    if (defined ($temp) ) {
      if ($DEBUGGING >= 2) {
        print "  Translated to $temp\n";
      }
      #s(\$O)($temp)g;
      &substitute2 ("\$O", $temp);
    }
    else 
    {
      # Could not translate default to numeric
      if ($DEBUGGING >= 2) {
        print "  Could not translate - defaulting to numeric\n";
      }
      #s(\$O)($temp)g;
      &substitute2 ("\$O", $temp);
    }
  }
  else
  {
    #s(\$O)($receivedtrap)g;
    &substitute2 ("\$O", $receivedtrap);
  }

  # $o - Received trap (numerical)
  #s(\$o)($receivedtrap)g;
  &substitute2 ("\$o", $receivedtrap);  

  # $s - Severity
  #s(\$s)($event2[2])g;
  &substitute2 ("\$s", $event2[2]);

  # $AR, $ar - IP address
  #s(\$aR)($var[1])g;
  &substitute2 ("\$aR", $var[1]);

  #s(\$ar)($var[1])g;
  &substitute2 ("\$ar", $var[1]);

  # $R, $r - Trap hostname
  #s(\$R)($var[0])g;
  &substitute2 ("\$R", $var[0]);

  # $H - Host name of the system running SNMPTT
  &substitute2 ("\$H", $snmptt_system_name);

  #s(\$r)($var[0])g;
  &substitute2 ("\$r", $var[0]);

  # $A, $a - Trap agent IP address
  #s(\$aA)($var[4])g;		# IP address
  &substitute2 ("\$aA", $var[4]);

  #s(\$A)($agent_dns_name)g;	# hostname
  &substitute2 ("\$A", $agent_dns_name);

  # $T - Uptime
  #s(\$T)($var[2])g;
  &substitute2 ("\$T", $var[2]);

  # $x - Current date
  #s(\$x)($trap_date)g;	
  &substitute2 ("\$x", $trap_date);

  # $X - Current time
  #s(\$X)($trap_time)g;	
  &substitute2 ("\$X", $trap_time);

  # $@ - Current time since epoch (Jan 1, 1904 for Mac, Jan 1, 1900 for most others)
  #s(\$@)($trap_date_time_epoch)g;
  &substitute2 ("\$@", $trap_date_time_epoch);

  # iso(1) org(3) dod(6) internet(1) snmpV2(6) snmpModules(3) snmpMIB(1) snmpMIBObjects(1) 5
  # SNMPv2 Generic?  1=coldStart....5=authenticationFailure, 6=egpNeighborLoss so 0=enterprise?
  if ($receivedtrap =~ /^.1.3.6.1.6.3.1.1.5/)
  {
    # It's a generic trap

    # $S - Specific trap number - 0
    #s(\$S)(0)g;
    &substitute2 ("\$S", "0");

    # $G - Generic trap number - last #
    my $temp = $_; 		# Save old $_
    $_ = $receivedtrap;
    /(\d+)$/;
    my $temp2 = $1;
    $_ = $temp;		# Restore old $_
    #s(\$G)($temp2)g;
    &substitute2 ("\$G", $temp2);
  }
  else
  {
    # It's an enterprise trap

    # $S - Specific trap number - last #
    my $temp = $_; 		# Save old $_
    $_ = $receivedtrap;
    /(\d+)$/;
    my $temp2 = $1;
    $_ = $temp;		# Restore old $_
    #s(\$S)($temp2)g;
    &substitute2 ("\$S", $temp2);

    # $G - Generic trap number - 0
    s(\$G)(0)g;
    &substitute2 ("\$G", "0");
  }

  # $D - Description from MIB file (NOT from snmptt.conf)
  if ($net_snmp_perl_enable == 1 && $description_mode == 2) {
    my $description_temp = $SNMP::MIB{$event2[0]}->{description};
    if ($description_clean == 1) {
      $description_temp =~ s/\n\s+/\n/g;
    }
    s(\$D)($description_temp)g;
    &substitute2 ("\$D", $description_temp);
  }
  elsif ($description_mode == 1) {
    my $description_temp;
    for (my $i=0; defined($event2[8][$i]); $i++)
    {
      $description_temp = $description_temp . $event2[8][$i] . "\n";
    }
    if ($description_clean == 1) {
      $description_temp =~ s/\n\s+/\n/g;
    }
    &substitute2 ("\$D", $description_temp);
  }
  else {
    #s(\$D)()g;
    &substitute2 ("\$D", "");
  }

  # Formatting
  # alarm (bell)          (BEL)
  &substitute2 ("\$Fa", "\a");

  # form feed             (FF)
  &substitute2 ("\$Ff", "\f");

  # newline               (LF, NL)
  &substitute2 ("\$Fn", "\n");

  # return                (CR)
  &substitute2 ("\$Fr", "\r");

  # tab                   (HT, TAB)
  &substitute2 ("\$Ft", "\t");

  # $pn - PREEXEC variables
  # preexec_var
  #&substitute2 ("\$p", "$preexec_var");
  # Count down backwards to make sure 10 is not mistaken for $1
  for(my $i=$#preexec_var+1;$i > 0; $i--)
  {
    my $val = $preexec_var[$i-1];
    if ($DEBUGGING >= 2)
    {
      print "\nPREEXEC variable $i: $val\n";
    }   
    &substitute2 ("\$p$i", $val);
  }

  # FORMAT line (only possible when doing EXEC line)
  &substitute2 ("\$Fz", "$format_line");

  # $$ - Print a $
  s(\$\$)(\$)g;

  # Do user defined REGEX on $message

  if ($DEBUGGING >= 2 && defined($event2[6][0]))
  {
    print "\n$_\n";
  }

  for (my $i=0; defined($event2[6][$i]); $i++)
  {
    my $regex_temp = $event2[6][$i];

    #print "!!!!!!$regex_temp\n";
    # Pull out modifiers
    $regex_temp =~ /^(.*\))(.*)$/;
    $regex_temp = $1;
    my $modifiers = $2;

    # Split using )(
    my @regex_temp2;
    ($regex_temp2[0], $regex_temp2[1]) = extract_bracketed($regex_temp,'()');

    #Remove starting and ending () from each part of the split REGEX
    $regex_temp2[0] =~ s/^\((.*)\)$/$1/;
    $regex_temp2[1] =~ s/^\((.*)\)$/$1/;

    #print "0:$regex_temp2[0]\n";
    #print "1:$regex_temp2[1]\n";
    #print "Modifiers: $modifiers\n";

    if ($regex_temp2[0] ne '')
    {
      # allowed modifiers: any combination of: i, g, e, ee+ not permitted
      my $modifiers2 = 0;
      if ($modifiers =~ /i/) { $modifiers2 = $modifiers2 | 1; }
      if ($modifiers =~ /g/) { $modifiers2 = $modifiers2 | 2; }
      if ($modifiers =~ /e/) 
      {
        if ($allow_unsafe_regex == 1)
        {
          $modifiers2 = $modifiers2 | 4;

          # Append a package declaration to the beginning to prevent global variables from being
          # visible.  Also remove any attempts to access variables from the main:: package.
          $regex_temp2[1] = "package regexisoloate;$regex_temp2[1]";
          $regex_temp2[1] =~ s/\$main::/\$/gi;	# Remove attempts to access the main package
        }
        else
        {
          if ($DEBUGGING >= 2)
          {
            print "  REGEX detected with e modifier, but allow_unsafe_regex is disabled.  Removing e modifier\n";
          }
        }
      }

      #print "modifiers2 = $modifiers2\n";

      # 0	none
      # 1	i
      # 2	g
      # 3	ig
      # 4	e
      # 5	ie
      # 6	ge
      # 7	ige

      if ($DEBUGGING >= 2)
      {
        print "  Doing REGEX: s($regex_temp2[0])($regex_temp2[1])$modifiers\n";
      }

      # If unsafe is off, then don't use the e modifier.  This means that variable interopolation
      # will not work on the right side.  If the user puts a $1 on the right, it will print out $1.
      # 
      # If unsafe is on, then we use the /ee modifier.
      # 
      # With a normal regular expression in a program, the e modifier is not requried for variable 
      # interopolation to work.  If you want to have code executed, then you need at least one e
      # modifier in a regular program.
      # 
      # Because we are passing the right side to a perl program instead of hard-coding the right side
      # *inside* of the program, we need to handle it differently.  These are the rules:
      # 
      # -for interopolation and / or code execution to work, we need to use the /ee modifier inside the 
      #  program (snmptt)
      # -if we only want interop and no code execution (same behaviour as a program with no e modifier),
      #  then we:
      #   -escape all backslashes
      #   -escape all double quotes
      #   -escape all single quotes
      #   -put quotes around the whole thing
      # - One would assume that if we didn't put quotes around everything, and used one /e instead of two, then
      #   it would work, but it does not..
      #   
      # -if we want both interop and code to execute (same behaviour as a program with one e modifier), 
      #  then we:
      #   -do nothing extra (no escaping or putting quotes around it)
      # 
      # With unsafe off, or unsafe no with NO e modifier, whatever the user puts in the right side of the 
      # REGEX will display exactly, with the only exception of the $n variable interop working with unsafe on.
      #
      # With unsafe on and one e modifier, the right side is evaluated so any text needs to be in quotes, and
      # printable quotes need to be escaped etc just like a normal program.  
      #
      # Example1:
      #
      # "5 + 5 is ".(5+5)."and \"this is quoted\""
      # will output:
      # 5+5 is 10 and "this is quoted"
      #
      # If unsafe was off, it would output:
      #
      # "5 + 5 is ".(5+5)."and \"this is quoted\""
      #
      # Example2:
      #
      # "5 + 5 is ".sprintf("%d",(5+5))."and \"this is quoted\""
      # will output:
      # 5+5 is 10 and "this is quoted"
      #
      # If unsafe was off, it would output:
      #
      # "5 + 5 is ".sprintf("%d",(5+5))."and \"this is quoted\""

      if ($allow_unsafe_regex == 0)
      {
        # unsafe_regex is OFF
        if    ($modifiers2 == 0 ) { s($regex_temp2[0])($regex_temp2[1]); }
        elsif ($modifiers2 == 1)  { s($regex_temp2[0])($regex_temp2[1])i; }
        elsif ($modifiers2 == 2)  { s($regex_temp2[0])($regex_temp2[1])g; }
        elsif ($modifiers2 == 3)  { s($regex_temp2[0])($regex_temp2[1])ig; }
      }
      else
      {
        # unsafe_regex is ON

        # If unsafe is on, use two e's.  If user does not specify an e, 
        # escape back slashes and quotes, and put quotes around the whole string
        # to prevent functions from being executed
        if (! ($modifiers =~ /e/))
        {
          # If it contains a \, then escape it
          $regex_temp2[1] =~ s/\\/\\\\/g;
          # Escape each quote
          $regex_temp2[1] =~ s/\"/\\\"/g;	# double quotes
          $regex_temp2[1] =~ s/\'/\\\'/g;	# single quotes
          # put quotes around the whole thing
          $regex_temp2[1] = "\"$regex_temp2[1]\"";
        }
        #print "1:$regex_temp2[1]\n";
        if    ($modifiers2 == 0 ) { s($regex_temp2[0])($regex_temp2[1])ee; }
        elsif ($modifiers2 == 1)  { s($regex_temp2[0])($regex_temp2[1])eei; }
        elsif ($modifiers2 == 2)  { s($regex_temp2[0])($regex_temp2[1])eeg; }
        elsif ($modifiers2 == 3)  { s($regex_temp2[0])($regex_temp2[1])eeig; }
        elsif ($modifiers2 == 4)  { s($regex_temp2[0])($regex_temp2[1])ee; }
        elsif ($modifiers2 == 5)  { s($regex_temp2[0])($regex_temp2[1])eei; }
        elsif ($modifiers2 == 6)  { s($regex_temp2[0])($regex_temp2[1])eeg; }
        elsif ($modifiers2 == 7)  { s($regex_temp2[0])($regex_temp2[1])eeig; }
      }
    }
  }
}

# sub substitute2 (left, right)
#
# left:  inside of double quotes with escapes.  eg: "\$a"
# right: replacement string eg: $name, "hello", "hi$namebye"
#
# Only substitute if there is an odd number of $ or \ symbols before the 
# substitute variable (eg: a in $a).  If there is an even number (0,2,4 etc)
# then do NOT substitute.  If there is an odd number (1,3,5 etc) then
# substitute.  For example, if $a converted to x:
# $a      = x
# $$a     = $a
# $$$a    = $x
# $$$$a   = $$a
# $$$$$a  = $$x
# 
# We need variable length look behind pattern matching to do this, but Perl
# does not support it.  As an alternative we can reverse the string, do a
# substitute with look ahead, and reverse it back.
#
sub substitute2 {
  my $left = shift;
  my $right = shift;

  my $string_r = reverse $_;
  my $left_r = reverse $left;
  $left_r =~ s/\\/\\\\/g;               # escape \
  $left_r =~ s/\$/\\\$/g;               # escape $
  $left_r =~ s/\*/\\\*/g;               # escape *
  $left_r =~ s/\+/\\\+/g;               # escape +
  my $right_r = reverse $right;

  # The format is:
  # s/a\$((?=(\$\$)*(?!\$)))/b/g;
  # where 'a\$' is the reverse of what to look for and 'b' is what to replace 
  # it with (reversed).

  $string_r =~ s/$left_r((?=(\$\$)*(?!\$)))/$right_r/g;

  $_ = reverse $string_r;
}

sub loadsnmpttconf
{
  #
  # snmptt.conf loader.
  # This section loads the snmptt.conf which defines the traps and text strings
  # that will be used for variable substitution

  # Flush variables in case this is a re-load during run
  my @snmpttconf = ();
  # @snmptt_conf_files = ();
  undef %event;
  
  foreach my $snmpttconffile (@snmptt_conf_files)
  {
    if ($DEBUGGING >= 1)
    {
      print "\n\tLoading $snmpttconffile\n";
    }

    if ($syslog_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
    {
      syslog_system("Loading $snmpttconffile");
    }
    if ($log_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
    {
      log_system("Loading $snmpttconffile");
    }
    if ($eventlog_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
    {
      eventlog_system("Loading $snmpttconffile",9,$eventlog_information);
    }

    unless (open SNMPTTCONF, $snmpttconffile)
    {
      warn "Could not open configuration file: $snmpttconffile ($!)";
      if ($DEBUGGING >= 1)
      {
        print "\n\tCould not open configuration file: $snmpttconffile\n";
      }

      if ($syslog_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
      {
        syslog_system("Could not open configuration file: $snmpttconffile");
      }
      if ($log_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
      {
        log_system("Could not open configuration file: $snmpttconffile");
      }
      if ($eventlog_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
      {
        eventlog_system("Could not open configuration file: $snmpttconffile",10,$eventlog_error);
      }

      next;
    }
    my $tempcount = 0;
    while (<SNMPTTCONF>)
    {
      chomp;				#remove <cr> at end of line	
      s/\015//;                       	# Remove any DOS carriage returns
      push(@snmpttconf, $_);		#add to each line to @trapconf array
      $tempcount++;
    }

    if ($DEBUGGING >= 1)
    {
      print "\tFinished loading $tempcount lines from $snmpttconffile\n";
    }
    if ($syslog_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
    {
      syslog_system("Finished loading $tempcount lines from $snmpttconffile");
    }
    if ($log_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
    {
      log_system("Finished loading $tempcount lines from $snmpttconffile");
    }
    if ($eventlog_system_enable == 1 && $daemon == 1 && $dump == 0 && $time == 0)
    {
      eventlog_system("Finished loading $tempcount lines from $snmpttconffile",11,$eventlog_information);
    }
  }

  if ($DEBUGGING >= 1)
  {
    print "\nFinished loading configuration files\n";
  }

  # Process each line of snmptt.conf loaded into @snmpttconf array.
  # Add each EVENT to %event hash

  if ($DEBUGGING >= 1)
  {
    print "\nProcessing memory copy of configuration files\n";
  }

  my $currentline=0;
  my $tempcount=0;
  my $tempcount2=0;

  while ($currentline <= $#snmpttconf)
  {
    my $line = $snmpttconf[$currentline];

    if ($line =~ /^EVENT/)
    {
      #EVENT name .x.x.x.x "category" severity  (note: .x.x. is FULL OID!)
      #0      1       2        3         4

      # use shellwords to divide the two space separated values
      # without messing up quoted text and re-join using :: as the
      # delimiter
      #
      # hash elements:
      # 0: name of trap
      # 1: category
      # 2: severity
      # 3: FORMAT string
      # 4: EXEC string
      # 5: NODES string
                	# 6: REGEX array
                	# 7: MATCH array
                	# 8: DESC array

      $tempcount++;		# Temporary counter of the number of EVENTS found

      # Divide up line taking into consideration strings in quotes
      my $temp = join "::::", shellwords($line);                

      #print "line is: $temp\n";

      # Split line up into multiple elements of an array using the new delimiter
      my @temp = split ("::::",$temp);

      if (! ($temp[2] =~ /^(\.\d+)+$/) && ! ($temp[2] =~ /^(\.\d+)+\.\*$/) && ! 
                            ($temp[2] =~ /^(\.\*)$/) )
      {
        # OID contains text, so it's not fully numerical
        if ($net_snmp_perl_enable == 1)
        {
          # Try to convert to numerical
          if ($DEBUGGING >= 2)
          {
            print "Non-numeric OID: $temp[2].  Will attempt to translate to numerical\n";
          }
          my $temp2 = SNMP::translateObj("$temp[2]",0);
          if (defined ($temp2) )
          {
            if ($DEBUGGING >= 2)
            {
              print "  Translated to $temp2\n";
            }
            $temp[2] = $temp2;
          }
          else
          {
            # Could not translate, so skip this entry
            if ($DEBUGGING >= 2)
            {
              print "  Could not translate - skipping entry\n";
            }
            $currentline++; # Increment to the next line
            next;
          }
        }
        else
        {
          # Net-SNMP Perl module not enabled, and OID is not numeric so skip this entry
          if ($DEBUGGING >= 2)
          {
            print "Non-numeric OID: $temp[2].  Net-SNMP Perl module not enabled - skipping entry\n";
          }
          $currentline++; # Increment to the next line
          next;
        }
      }

      # Clear out the FORMAT, EXEC and NODES lines in case they are not found.  Don't want old
      # data in them..
      my $lineformat = '';
      my $lineexec = '';
      my $linenodes = '';
      my $lineregex = '';
      my @exec = ();
      my @regex = ();
      my @match = ();
      my @desc = ();
      my @preexec = ();

      $currentline++; # Increment to the next line which should be a FORMAT, EXEC or NODES
      my $line2 = $snmpttconf[$currentline];

      while ( ($currentline <= $#snmpttconf) && !($line2 =~ /^EVENT/) )
      {
        # Keep going through the file until the next EVENT or the end of snmptt.conf
        # is reached

        # Check to see if next line is a FORMAT line (it should be!)
        if ($line2 =~ /^FORMAT/)
        {
          # It's a FORMAT line

          $lineformat = substr($line2,7);
        }
        elsif ($line2 =~ /^EXEC/)
        {
          # It's an EXEC line

          $lineexec = substr($line2,5);

          push (@exec,$lineexec);
        }
        elsif ($line2 =~ /^PREEXEC/)
        {
          # It's a PREEXEC line

          my $linematch = substr($line2,8);

          push (@preexec,$linematch);
        }
        elsif ($line2 =~ /^NODES/)
        {
          # It's a NODES line
          # Allow for multiple NODES lines - concatenate them

          $linenodes = $linenodes . substr($line2,6) . " ";
        }
        elsif ($line2 =~ /^REGEX/)
        {
          # It's a REGEX line

          $lineregex = substr($line2,6);

          # Remove spaces before and after
          $lineregex =~ /^\s*(.*?)\s*$/;
          $lineregex = $1;

          # Make sure it looks like ()() before accepting
          if ($lineregex =~ /\(.*?\)\(.*?\)/ )
          {
            push (@regex,$lineregex);
          }
        }
        elsif ($line2 =~ /^MATCH/)
        {
          # It's a MATCH line

          my $linematch = substr($line2,6);

          push (@match,$linematch);
        }
        elsif ( ($line2 =~ /^SDESC/) && ($description_mode== 1) )
        {
          # It's a DESC line

          $currentline++; # Increment to the next line
          $line2 = $snmpttconf[$currentline]; # Get next line

          while ( ($currentline <= $#snmpttconf) && !($line2 =~ /^EVENT/) && 
            !($line2 =~ /^EDESC/)) {
            print "DESC line: $line2\n";
            push (@desc,$line2);
            $currentline++; # Increment to the next line
            $line2 = $snmpttconf[$currentline]; # Get next line
          }
        }

        $currentline++; # Increment to the next line
        $line2 = $snmpttconf[$currentline]; # Get next line
      }
      if ($lineformat ne '' || $exec[0] ne '')
      {
        # At least the mandatory FORMAT line was defined, so we'll keep it
        # Stuff all elements into a one hash
        # $event{@temp[2]} = [@temp[1],@temp[3],@temp[4],$lineformat,$lineexec,$linenodes];

        if (!defined(@{ $event{$temp[2]} }[0])) 
        {
          @{ $event{$temp[2]} }[0] = 0;
        }

        #@regex = qw/one two/;

        my $countx = @{ $event{$temp[2]} }[0];
        #print "countx $countx\n";
        @{ $event{$temp[2]} }[1+$countx]= $temp[1];
        @{ $event{$temp[2]} }[2+$countx]= $temp[3];
        @{ $event{$temp[2]} }[3+$countx]= $temp[4];
        @{ $event{$temp[2]} }[4+$countx]= $lineformat;
        #@{ $event{$temp[2]} }[5+$countx]= $lineexec;
        @{ $event{$temp[2]} }[5+$countx]= [@exec];

        # If dynamic_nodes is disabled, process the nodes list to load any nodes
        # files first so they are not loaded each time a trap is processed
        if ($dynamic_nodes == 0)
        {
          my @nodes2 = &process_nodes($linenodes);
          @{ $event{$temp[2]} }[6+$countx]= "@nodes2";
        }
        else
        {
          @{ $event{$temp[2]} }[6+$countx]= $linenodes;
        }

        @{ $event{$temp[2]} }[7+$countx]= [@regex];
        @{ $event{$temp[2]} }[8+$countx]= [@match];
        @{ $event{$temp[2]} }[9+$countx]= [@desc];
        @{ $event{$temp[2]} }[10+$countx]= [@preexec];
        @{ $event{$temp[2]} }[0]= 11+ @{ $event{$temp[2]} }[0];

        $tempcount2++;    # Temporary counter of the number of EVENTS found with at
                          # least a FORMAT line specified
      }

      # Change currentline back so the next increment will re-evalutate the last line which
      # would have been an EVENT (to get out of the WHILE loop above), or the end of the file
      $currentline--;
    }
    $currentline++;		# Increment current line for next loop through snmpttconf array	
  }
  if ($DEBUGGING >= 1)
  {
    print "$tempcount EVENTs found\n";
    print "$tempcount2 EVENTs found that contain at least the mandatory FORMAT line\n";
    if($tempcount2<$tempcount)
    {
      print "  Warning:  EVENT entries were found that did not contain the mandatory FORMAT line!\n";
      print "  These EVENTS will be ignored!\n";
    }
    print "Finished processing memory copy of configuration files\n\n";
  }

  if ($DEBUGGING >= 2)
  {
    print "==========================================================\n";
    print "Printing out all the events in hash table:\n\n";
    foreach my $key (sort keys %event)
    {
      my $l=0;
      while (defined($event{$key}[$l+1]))
      {
        print  "Event: $key => $event{$key}[1+$l],$event{$key}[2+$l],$event{$key}[3+$l],$event{$key}[4+$l],$event{$key}[7+$l][0]\n";
        $l+=11;
      }
    }
    print "\nFinished printing out all events in hash table\n";
    print "==========================================================\n";

    # Print out duplicates if asking for a dump (snmptt --dump)
    if ($dump == 1) {
      print "Printing out all duplicate events in hash table:\n\n";
      my %temp = ();
      foreach my $key (sort keys %event)
      {
        my $l=0;
        while (defined($event{$key}[$l+1]))
        {
          $temp{$key}++;
          $l+=11;
        }
      }
      foreach my $key (sort keys %temp)
      {
        if ($temp{$key} > 1) {
          print "Duplicate event: $key\n";
        }
      }
      print "\nFinished printing out all duplicate events in hash table\n";
      print "==========================================================\n";
    }
  }

  close SNMPTTCONF;
}

sub readtrap
{
  # Flush out @tempvar, @var and @entvar
  my @tempvar = ();
  @var = ();
  @entvar = ();
  @entvarname = ();
  @preexec_var = ();
  my @rawtrap = ();

  # Statistics
  $g_total_traps_received++;

  if ($DEBUGGING >= 2) 
  {
    print "Reading trap.  Current time: " . scalar(localtime()). "\n";
  }

  if ( $daemon == 1)
  {
    chomp($trap_date_time_epoch = (<$input>));	# Pull time trap was spooled
    push(@rawtrap, $trap_date_time_epoch);
    if ($trap_date_time_epoch eq "") {
      if ($DEBUGGING >= 1) {
        print "  Invalid trap file.  Expected a serial time on the first line but got nothing\n";
        return 0;
      }
    }
     
    $trap_date_time_epoch =~ s/[^0-9]//g;  # Remove non numeric characters
  }
  else
  {
    $trap_date_time_epoch = time();		# Use current time as time trap was received
  }

  my @localtime_array;
  if ( $daemon == 1 && $use_trap_time == 1 )	# Daemon mode only
  {
    @localtime_array = localtime($trap_date_time_epoch);

    if ($date_time_format eq "") {
      $trap_date_time = localtime($trap_date_time_epoch);
    }
    else {
      $trap_date_time = strftime $date_time_format, @localtime_array;
    }

    if ($date_time_format_sql eq "") {
      $trap_date_time_sql = localtime($trap_date_time_epoch);
    }
    else {
      $trap_date_time_sql = strftime $date_time_format_sql, @localtime_array;
    }
    
  }
  else  # Standalone mode
  {
    @localtime_array = localtime();

    if ($date_time_format eq "") {
      $trap_date_time = localtime();
    }
    else {
      $trap_date_time = strftime $date_time_format, @localtime_array;
    }

    if ($date_time_format_sql eq "") {
      $trap_date_time_sql = localtime();
    }
    else {
      $trap_date_time_sql = strftime $date_time_format_sql, @localtime_array;
    }
  }

  $trap_date = strftime $date_format, @localtime_array;
  $trap_time = strftime $time_format, @localtime_array;
  

  # Pull in passed SNMP info from snmptrapd via STDIN and place in the array @tempvar

  chomp($tempvar[0]=<$input>);	# hostname
  push(@rawtrap, $tempvar[0]);
  $tempvar[0]  =~ s/[^a-zA-Z0-9.\-_]//g;  # Remove most non alphanumeric characters
    
  if ($tempvar[0] eq "") {
    if ($DEBUGGING >= 1) {
      print "  Invalid trap file.  Expected a hostname on line 2 but got nothing\n";
      return 0;
    }
  }
    
  chomp($tempvar[1]=<$input>);	# ip address
  push(@rawtrap, $tempvar[1]);
  $tempvar[1]  =~ s/[^a-zA-Z0-9.\-_:\[\]] //g;  # Remove most non alphanumeric characters
  # Example: UDP: [127.0.0.1]:38249->[127.0.0.1]:162
  # Should only be IPs, but also allow for hostname characters, just in case.
  
  if ($tempvar[1] eq "") {
    if ($DEBUGGING >= 1) {
      print "  Invalid trap file.  Expected an IP address on line 3 but got nothing\n";
      return 0;
    }
  }

  # With DNS resolution disabled in snmptrapd, some systems pass the hostname as:
  # UDP: [x.x.x.x]:161->[y.y.y.y]
  # If this is detected, use x.x.x.x as the hostname.
  if ($tempvar[0] =~ /\[(\d+\.\d+\.\d+\.\d+)\].*?->\[(\d+\.\d+\.\d+\.\d+)\]/) {
    $tempvar[0] = $1;
  }

  # Some systems pass the IP address as udp:ipaddress:portnumber.  This will pull
  # out just the IP address
  $tempvar[1] =~ /(\d+\.\d+\.\d+\.\d+)/;
  $tempvar[1] = $1;

  # Net-SNMP 5.4 has a bug which gives <UNKNOWN> for the hostname
  if ($tempvar[0] =~ /<UNKNOWN>/) {
    $tempvar[0] = $tempvar[1];
  }

  #Process varbinds
  #Separate everything out, keeping both the variable name and the value
  my $linenum = 1;
  while (defined(my $line = <$input>))
  {
    push(@rawtrap, $line);

    if ($remove_backslash_from_quotes == 1)  # Remove escape from quotes if enabled
    {
      $line =~ s/\\\"/"/g;
    }

    my $temp1;
    my $temp2;

    ($temp1, $temp2) = split (/ /, $line, 2);

    chomp ($temp1);       # Variable NAME
    chomp ($temp2);       # Variable VALUE
    chomp ($line);

    my $variable_fix;
    if ($linenum == 1)
    {
      if (defined($temp2) )	# Check if line 1 contains 'variable value' or just 'value'  
      {
        $variable_fix = 0;
      }
      else
      {
        $variable_fix = 1;
      }
    }

    if ($variable_fix == 0 )    # We received variable name and value
    {
      # Clean up variable NAME
      $temp1  =~ s/[^a-zA-Z0-9.:\-_]//g;  # Remove most non alphanumeric characters
      # Examples:
      # .1.3.6.1.2.1.1.3.0
      # SNMPv2-SMI::enterprises.9.9.187.0.1 ....
      # OIDs can have -'s.  _'s NOT permitted by Net-SNMP, but leave it in anyway

      # Make sure variable names are numerical
      $temp1 = translate_symbolic_to_oid($temp1);

      # If line begins with a double quote (") but does not END in a double quote then we need to merge
      # the following lines together into one until we find the closing double quote.  Allow for escaped quotes.
      # Net-SNMP sometimes divides long lines into multiple lines..
      if ( ($temp2 =~ /^\"/) && ( ! ($temp2 =~ /[^\\]\"$/)) )
      {
        if ($DEBUGGING >= 2) {
          print "  Multi-line value detected - merging onto one line...\n";
        }
        $temp2 =~ s/[\r\n]//g;			# Remove newline characters
        while (defined(my $line2 = <$input>))
        {
          chomp $line2;
          push(@rawtrap, $line2);
          $temp2 .= " " . $line2;
          if (($line2 =~ /\"$/) && ($line2 !~ /\\\"$/)) # Ends in a non-escaped quote or it's a single line with a quote.
          {
            last;
          }
        }
      }

      # If the value is blank, set it to (null)
      if ($temp2 eq "")
      {
        $temp2 = "(null)";
      }

      if ($temp2 =~ /^\"/ && $temp2 =~ /$\"/)			# Have quotes around it?
      {
        $temp2 = substr($temp2,1,(length($temp2)-2));		# Remove quotes
      }
	  
      # Clean up variable VALUE
      $temp2 =~ s(`)(')g;	#` Replace any back ticks with regular single quote
      $temp2  =~ s/[^a-zA-Z0-9 !"#%'()+,\-_.:\/=\@{}~\\]//g;  # Remove most non alphanumeric characters
	  
        push(@tempvar, $temp1);
        push(@tempvar, $temp2);
      }
    else    # We received only variable value
    {
      # Should have been variable value, but only value found.  Workaround
      # 
      # Normally it is expected that a line contains a variable name
      # followed by a space followed by the value (except for the 
      # first line which is the hostname and the second which is the
      # IP address).  If there is no variable name on the line (only
      # one string), then add a variable string called 'variable' so 
      # it is handled correctly in the next section.
      # This happens with ucd-snmp v4.2.3 but not v4.2.1 or v4.2.5.
      # This appears to be a bug in ucd-snmp v4.2.3.  This works around
      # the problem by using 'variable' for the variable name, although 
      # v4.2.3 should NOT be used with SNMPTT as it prevents SNMP V2 traps 
      # from being handled correctly.

      $temp2 = $temp1;
      $temp1 = "variable";

      if ($DEBUGGING >= 2)
      {
        print "Data passed from snmptrapd is incorrect.  UCD-SNMP v4.2.3 is known to cause this\n";
      }

      # If line begins with a double quote (") but does not END in a double quote then we need to merge
      # the following lines together into one until we find the closing double quote.  Allow for escaped quotes.
      # Net-SNMP sometimes divides long lines into multiple lines..
      if ( ($temp2 =~ /^\"/) && ( ! ($temp2 =~ /[^\\]\"$/)) )
      {
        if ($DEBUGGING >= 2) {
          print "  Multi-line value detected - merging onto one line...\n";
        }
        $temp2 =~ s/[\r\n]//g;			# Remove newline characters
        while (defined(my $line2 = <$input>))
        {
          chomp $line2;
          push(@rawtrap, $line2);
          $temp2 .= " " . $line2;
          if (($line2 =~ /\"$/) && ($line2 !~ /\\\"$/)) # Ends in a non-escaped quote or it's a single line with a quote.
          {
            last;
          }
        }
      }

      # If the value is blank, set it to (null)
      if ($temp2 eq "")
      {
        $temp2 = "(null)";
      }

      if ($temp2 =~ /^\"/ && $temp2 =~ /$\"/)			# Have quotes around it?
      {
        $temp2 = substr($temp2,1,(length($temp2)-2));		# Remove quotes
      }
        	  
      # Clean up variable VALUE
      $temp2 =~ s(`)(')g;	#` Replace any back ticks with regular single quote
      $temp2  =~ s/[^a-zA-Z0-9 !"#%'()+,\-_.:\/=\@{}~\\]//g;  # Remove most non alphanumeric characters

      push(@tempvar, $temp1);
      push(@tempvar, $temp2);	  
    }

    $linenum++;
  }

  if ($DEBUGGING >= 2)
  {
    # Print out raw trap passed from snmptrapd
    print "\nRaw trap passed from snmptrapd:\n";
    for (my $i=0;$i <= $#rawtrap;$i++)
    {
      chomp($rawtrap[$i]);
      print "$rawtrap[$i]\n";
    }

    # Print out all items passed from snmptrapd
    print "\nItems passed from snmptrapd:\n";
    for (my $i=0;$i <= $#tempvar;$i++)
    {
      print "value $i: $tempvar[$i]\n\n";
    }
  }

  # Copy what I need to new variables to make it easier to manipulate later

  # Standard variables
  $var[0] = $tempvar[0];		# hostname
  $var[1] = $tempvar[1];		# ip address
  $var[2] = $tempvar[3];		# uptime
  $var[3] = $tempvar[5];		# trapname / OID - assume first value after uptime is
          # the trap OID (value for .1.3.6.1.6.3.1.1.4.1.0)

  $var[4] = "";	 # Clear ip address from trap agent
  $var[5] = "";	 # Clear trap community string
  $var[6] = "";	 # Clear enterprise
  $var[7] = "";	 # Clear securityEngineID
  $var[8] = "";	 # Clear securityName
  $var[9] = "";	 # Clear contextEngineID
  $var[10] = ""; # Clear contextName

  # Make sure trap OID is numerical as event lookups are done using numerical OIDs only
  $var[3] = translate_symbolic_to_oid($var[3]);

  # Cycle through remaining variables searching for for agent IP (.1.3.6.1.6.3.18.1.3.0),
  # community name (.1.3.6.1.6.3.18.1.4.0) and enterpise (.1.3.6.1.6.3.1.1.4.3.0)
  # All others found are regular passed variables
  my $j=0;
  for (my $i=6;$i <= $#tempvar; $i+=2)
  {
    if ($tempvar[$i] =~ /^.1.3.6.1.6.3.18.1.3.0$/)		# ip address from trap agent
    {
      $var[4] = $tempvar[$i+1];
    }
    elsif ($tempvar[$i] =~ /^.1.3.6.1.6.3.18.1.4.0$/)	# trap community string
    {
      $var[5] = $tempvar[$i+1];
    }
    elsif ($tempvar[$i] =~ /^.1.3.6.1.6.3.1.1.4.3.0$/)	# enterprise
    {
      # $var[6] = $tempvar[$i+1];
      # Make sure enterprise value is numerical
      $var[6] = translate_symbolic_to_oid($tempvar[$i+1]);
    }
    elsif ($tempvar[$i] =~ /^.1.3.6.1.6.3.10.2.1.1.0$/)	# securityEngineID
    {
      $var[7] = $tempvar[$i+1];
    }
    elsif ($tempvar[$i] =~ /^.1.3.6.1.6.3.18.1.1.1.3$/)	# securityName
    {
      $var[8] = $tempvar[$i+1];
    }
    elsif ($tempvar[$i] =~ /^.1.3.6.1.6.3.18.1.1.1.4$/)	# contextEngineID
    {
      $var[9] = $tempvar[$i+1];
    }
    elsif ($tempvar[$i] =~ /^.1.3.6.1.6.3.18.1.1.1.5$/)	# contextName
    {
      $var[10] = $tempvar[$i+1];
    }
    else  # application specific variables
    {
      $entvarname[$j] = $tempvar[$i];
      $entvar[$j] = $tempvar[$i+1];
      $j++;
    }
  }

  if ($dns_enable == 1 && $var[0] =~  /^\d+\.\d+\.\d+\.\d+$/) # Only if it's not already resolved
  {
    my $temp = gethostbyaddr(Socket::inet_aton($var[0]),Socket::AF_INET());
    if (defined ($temp))
    {
      if ($DEBUGGING => 1)
      {
        print "Host IP address ($var[0]) resolved to: $temp\n\n";
      }
      $var[0] = $temp;
    }
    else
    {
      if ($DEBUGGING => 1)
      {
        print "Host IP address ($var[0]) could not be resolved by DNS.  Variable \$r / \$R etc will use the IP address\n\n";
      }
    }
  }

  # If the agent IP is blank, copy the IP from the host IP.
  # var[4] would only be blank if it wasn't passed from snmptrapd, which
  # should only happen with ucd-snmp 4.2.3, which you should be using anyway!
  if ($var[4] eq '')
  {
    $var[4] = $var[1];
    if ($DEBUGGING => 1)
    {
      print "Agent IP address was blank, so setting to the same as the host IP address of $var[1]\n\n";
    }
  }

  # If the agent IP is the same as the host IP, then just use the host DNS name, no need
  # to look up, as it's obviously the same..
  if ($var[4] eq $var[1])
  {
    if ($DEBUGGING => 1)
    {
      print "Agent IP address ($var[4]) is the same as the host IP, so copying the host name: $var[0]\n\n";
    }
    $agent_dns_name = $var[0];
  }
  else
  {
    $agent_dns_name = $var[4];     # Default to IP address
    if ($dns_enable == 1 && $var[4] ne '')
    {
      my $temp = gethostbyaddr(Socket::inet_aton($var[4]),Socket::AF_INET());
      if (defined ($temp))
      {
        if ($DEBUGGING => 1)
        {
          print "Agent IP address ($var[4]) resolved to: $temp\n\n";
        }
        $agent_dns_name = $temp;
      }
      else
      {
        if ($DEBUGGING => 1)
        {
          print "Agent IP address ($var[4]) could not be resolved by DNS.  Variable \$A etc will use the IP address\n\n";
        }
      }
    }
  }

  if ($strip_domain)
  {
    $var[0] = strip_domain_name($var[0], $strip_domain);
    $agent_dns_name = strip_domain_name($agent_dns_name, $strip_domain);
  }

  if ($DEBUGGING >= 1)
  {
    print "Trap received from $tempvar[0]: $tempvar[5]\n";
  }

  if ($DEBUGGING >= 2)
  {
    print "0:		hostname\n";
    print "1:		ip address\n";
    print "2:		uptime\n";
    print "3:		trapname / OID\n";
    print "4:		ip address from trap agent\n";
    print "5:		trap community string\n";
    print "6:		enterprise\n";
    print "7:		securityEngineID        (snmptthandler-embedded required)\n";
    print "8:		securityName            (snmptthandler-embedded required)\n";
    print "9:		contextEngineID         (snmptthandler-embedded required)\n";
    print "10:		contextName             (snmptthandler-embedded required)\n";
    print "0+:		passed variables\n\n";	

    #print out all standard variables
    for (my $i=0;$i <= $#var;$i++)
    {
      print "Value $i: $var[$i]\n\n";
    }

    print "Agent dns name: $agent_dns_name\n\n";

    #print out all enterprise specific variables
    for (my $i=0;$i <= $#entvar;$i++)
    {
      print "Ent Value $i (\$" . ($i+1) . "): $entvarname[$i]=$entvar[$i]\n\n";
    }
  }

  # Generate hash of trap and detect duplicates
  if ($duplicate_trap_window) {
    my $md5 = Digest::MD5->new;
    # All variables except for uptime.
    $md5->add($var[0],$var[1].$var[3].$var[4].$var[5].$var[6].$var[7].$var[8].$var[9].$var[10]."@entvar");
    
    my $trap_digest = $md5->hexdigest;

    if ($DEBUGGING >= 2) {
      print "Trap digest: $trap_digest\n";
    }

    if ($duplicate_traps{$trap_digest}) {
      # Duplicate trap detected.  Skipping trap...
      return -1;
    }

    $duplicate_traps{$trap_digest} = time();
  }

  return 1;

  # Variables of trap received by SNMPTRAPD:
  #
  # $var[0]   hostname
  # $var[1]   ip address
  # $var[2]   uptime
  # $var[3]   trapname / OID
  # $var[4]   ip address from trap agent
  # $var[5]   trap community string
  # $var[6]   enterprise
  # $var[7]   securityEngineID                (snmptthandler-embedded required)
  # $var[8]   securityName                    (snmptthandler-embedded required)
  # $var[9]   contextEngineID                 (snmptthandler-embedded required)
  # $var[10]  contextName                     (snmptthandler-embedded required)
  #
  # $entvarname[0]  passed variable name 1
  # $entvarname[1]  passed variable name 2
  #
  # $entvar[0]    passed variable 1
  # $entvar[1]    passed variable 2
  # .
  # .
  # etc..
  #
  ##############################################################################

}

sub searchfortrap
{

  # Variables of trap received by SNMPTRAPD:
  #
  # $var[0]  hostname
  # $var[1]  ip address
  # $var[2]  uptime
  # $var[3]  trapname / OID
  # $var[4]  ip address from trap agent
  # $var[5]  trap community string
  # $var[6]  enterprise
  # $var[7]  securityEngineID                (snmptthandler-embedded required)
  # $var[8]  securityName                    (snmptthandler-embedded required)
  # $var[9]  contextEngineID                 (snmptthandler-embedded required)
  # $var[10] contextName                     (snmptthandler-embedded required)
  #
  # $entvarname[0] passed variable name 1
  # $entvarname[1] passed variable name 2
  #
  # $entvar[0] passed variable 1
  # $entvar[1] passed variable 2
  # .
  # .
  # etc..
  #
  # $event hash for each trapped defined in snmptt.conf with following values:
  #
  # 0: name of trap
  # 1: category
  # 2: severity
  # 3: FORMAT string
  # 4: EXEC string
  # 5: NODES string
  # 6: REGEX array
  # 7: MATCH array
  # 9: DESC array
  #
  # Example: To access FORMAT string for received trap:
  #  $event{"$var[3]"}[3]
  #
  # or
  #
  #  $event{"$receivedtrap"}[3]	
  ###############################################################################
  # Check to see if received trap is defined in the snmptt.conf, and perform variable
  # substitution, logging etc if found

  # Look for matching OID

  $processed = 0;
  $receivedtrap = $var[3];
  $receivedtrap_entry = $receivedtrap;  # Will be used to processtrap to lookup EVENT info
                                        # Changed to actual EVENT entry if using a wildcard 

  $trap_attempted_to_log = 0;
  $trap_successfully_logged = 0;

  # Check for exact match
  if (exists $event{"$receivedtrap"})
  {
    if ($DEBUGGING >= 1)
    {
      print "Exact match of trap found in EVENT hash table\n\n";
    }

    &processtrap;
  }
  else
  {
    if ($DEBUGGING >= 1)
    {
      print "Exact match of trap NOT found in EVENT hash table\n\n";
    }
  }

  # Check for wildcard match in hash table if not already processed
  if ($processed == 0)
  {
    my $receivedtraptemp = $receivedtrap;

    my $counter = 0;  # Drill down only 40 times.  Should never need this, but
                      # it's here to prevent an infinite loop in this while statement
                      # just in case

    if ($DEBUGGING >= 1)
    {
      print "Looking for wildcards in the EVENT hash table\n";
    }

    while ($counter <= 40 && $receivedtraptemp ne '')
    {
      # Remove last digit of recevied trap, add a * and look for match
      $receivedtraptemp =~ s/(.*)\.\d+$/$1/;

      if ($DEBUGGING >= 2)
      {
        print "Drilling down looking for wildcards in the EVENT hash table\n";
        print $receivedtraptemp.".\*\n\n";
      }

      if (exists $event{$receivedtraptemp.'.*'})
      {
        $receivedtrap_entry = $receivedtraptemp.'.*';	# Set $receivedtrap to matching trap with wildcard
        &processtrap;
        last;  # found, so stop this while loop
      }
      $counter++;
    }
  }

  # Log to unknowntraplog etc if no match was found
  if ($processed == 0)
  {
    if ($DEBUGGING >= 1)
    {
      print "\n\nTrap not defined...\n\n";
    }

    # Statistics
    $g_total_traps_unknown++;

    if ($unknown_trap_exec ne "") {
      if ($DEBUGGING >= 1) {
        print "\n\nUnknown trap EXEC line:\n";
      }
      
      my $command;

      if ($unknown_trap_exec_format ne "") {        
        $_ = $unknown_trap_exec_format;

        &substitute;

        $command = $unknown_trap_exec . " \"$_\"";
      }
      else {
        $command = $unknown_trap_exec . " \"";
        $command .= scalar($trap_date_time) . ": Unknown trap ($var[3]) received from $var[0] at:";

        #print out all standard variables
        for (my $i=0;$i <= $#var;$i++) {
          $command .= " Value $i: $var[$i]";
        }

        #print out all enterprise specific variables
        for (my $i=0;$i <= $#entvar;$i++) {
          $command .= " Ent Value $i: $entvarname[$i]=$entvar[$i]";
        }

        $command .= "\"";
      }
      if ($DEBUGGING >= 1) {
        print "Unknown trap EXEC command:$command\n";
      }

      # Execute command

      if ($exec_escape == 1) {
        # Escape wildcard characters
        $command =~ s/\*/\\\*/g;
        $command =~ s/\?/\\\?/g;
      }
      system $command;
    }

    if ($unknown_trap_log_enable == 1)
    {
      if ($unknown_trap_log_file ne "") {
        if (open LOGFILE, ">>$unknown_trap_log_file")
        {
          print LOGFILE scalar($trap_date_time),": Unknown trap ($var[3]) received from $var[0] at: \n";
          #print out all standard variables
          for (my $i=0;$i <= $#var;$i++)
          {
            print 	LOGFILE "Value $i: $var[$i]\n";
          }
          #print out all enterprise specific variables
          for (my $i=0;$i <= $#entvar;$i++)
          {
            print LOGFILE "Ent Value $i: $entvarname[$i]=$entvar[$i]\n";
          }
          print LOGFILE "\n\n";
          close LOGFILE;
        }
        else
        {
          warn "Can not open log_file: $!";
        }
      }

      my $message_short;
      if ( ($mysql_dbi_enable == 1) ||
        ($postgresql_dbi_enable == 1) ||
        ($dbd_odbc_enable == 1) ||
        ($sql_win32_odbc_enable == 1) ) {

        if ($DEBUGGING >= 1)
        {
          print "Logging unknown trap to SQL\n";
        }

        # Translate if enabled, and if we can
        $receivedtrap_trans = &translate_log_trap_oid_sub($receivedtrap);

        $enterprise_trans = &translate_enterprise_oid_format_sub($var[6]);

        if ($net_snmp_perl_enable == 1 && $db_translate_enterprise == 1)
        {
          $db_enterprise = $enterprise_trans;
        }
        else
        {
          $db_enterprise = $var[6];
        }

        $_ = $db_unknown_trap_format;

        &substitute;

        $message_short = $_;
      }

      if ($mysql_dbi_enable == 1 && defined ($dbh_mysql) && $mysql_dbi_table_unknown ne "")
      {
        # Backslash any quotes
        my $message_short2 = $message_short;
        $message_short2 =~ s(\')(\\\')g;   #'
        $message_short2 =~ s(\")(\\\")g;   #"

        my $community = $var[5];
        $community =~ s(\')(\\\')g;   #'
        $community =~ s(\")(\\\")g;   #"

        my @t_sql_custom_columns_unknown = ();
          
        if (@sql_custom_columns_unknown) {
          @t_sql_custom_columns_unknown = @sql_custom_columns_unknown;
            
          for (my $i = 1; $i <= $#t_sql_custom_columns_unknown; $i+=2) {
            $_ = $t_sql_custom_columns_unknown[$i];
            print "Performing substitution on custom column: $_\n";
            &substitute();
            print "Done performing substitution on custom column: $_\n";
            $t_sql_custom_columns_unknown[$i] = $_;
          }
        }

        &mysql_insert($mysql_dbi_table_unknown,
          "trapoid", $receivedtrap_trans,
          "enterprise", $db_enterprise,
          "community", $community,
          "hostname", $agent_dns_name, 
          "agentip", $var[4],
          "uptime", $var[2],
          "traptime", $trap_date_time_sql,
          "formatline", $message_short2,
          @t_sql_custom_columns_unknown);
      }

      if ($postgresql_dbi_enable == 1 && defined ($dbh_postgresql) && $postgresql_dbi_table_unknown ne "")
      {
        # Backslash any quotes
        my $message_short2 = $message_short;
        $message_short2 =~ s(\')(\\\')g;   #'
        $message_short2 =~ s(\")(\\\")g;   #"

        my $community = $var[5];
        $community =~ s(\')(\\\')g;   #'
        $community =~ s(\")(\\\")g;   #"

        my @t_sql_custom_columns_unknown = ();
          
        if (@sql_custom_columns_unknown) {
          @t_sql_custom_columns_unknown = @sql_custom_columns_unknown;
            
          for (my $i = 1; $i <= $#t_sql_custom_columns_unknown; $i+=2) {
            $_ = $t_sql_custom_columns_unknown[$i];
            print "Performing substitution on custom column: $_\n";
            &substitute();
            print "Done performing substitution on custom column: $_\n";
            $t_sql_custom_columns_unknown[$i] = $_;
          }
        }

        &postgresql_insert($postgresql_dbi_table_unknown,
          "trapoid", $receivedtrap_trans,
          "enterprise", $db_enterprise,
          "community", $community,
          "hostname", $agent_dns_name, 
          "agentip", $var[4],
          "uptime", $var[2],
          "traptime", $trap_date_time_sql,
          "formatline", $message_short2,
          @t_sql_custom_columns_unknown);

      }

      if ($dbd_odbc_enable == 1 && defined ($dbh_odbc) && $dbd_odbc_table_unknown ne "")
      {
        # Double any single quotes
        my $message_short2 = $message_short;
        $message_short2 =~ s(\')('')g;     #'

        my $community = $var[5];
        $community =~ s(\')('')g;   #'

        my @t_sql_custom_columns_unknown = ();
          
        if (@sql_custom_columns_unknown) {
          @t_sql_custom_columns_unknown = @sql_custom_columns_unknown;
            
          for (my $i = 1; $i <= $#t_sql_custom_columns_unknown; $i+=2) {
            $_ = $t_sql_custom_columns_unknown[$i];
            print "Performing substitution on custom column: $_\n";
            &substitute();
            print "Done performing substitution on custom column: $_\n";
            $t_sql_custom_columns_unknown[$i] = $_;
          }
        }

        &odbc_insert($dbd_odbc_table_unknown,
          "trapoid", $receivedtrap_trans,
          "enterprise", $db_enterprise,
          "community", $community,
          "hostname", $agent_dns_name, 
          "agentip", $var[4],
          "uptime", $var[2],
          "traptime", $trap_date_time_sql,
          "formatline", $message_short2,
          @t_sql_custom_columns_unknown);

      }

      if ($sql_win32_odbc_enable == 1 && defined ($dbh_win32_odbc) && $sql_win32_odbc_table_unknown ne "")
      {
        # Double any single quotes
        my $message_short2 = $message_short;
        $message_short2 =~ s(\')('')g;     #'

        my $community = $var[5];
        $community =~ s(\')('')g;   #'

        my @t_sql_custom_columns_unknown = ();
          
        if (@sql_custom_columns_unknown) {
          @t_sql_custom_columns_unknown = @sql_custom_columns_unknown;
            
          for (my $i = 1; $i <= $#t_sql_custom_columns_unknown; $i+=2) {
            $_ = $t_sql_custom_columns_unknown[$i];
            print "Performing substitution on custom column: $_\n";
            &substitute();
            print "Done performing substitution on custom column: $_\n";
            $t_sql_custom_columns_unknown[$i] = $_;
          }
        }

        &sql_win32_odbc_insert($sql_win32_odbc_table_unknown,
          "trapoid", $receivedtrap_trans,
          "enterprise", $db_enterprise,
          "community", $community,
          "hostname", $agent_dns_name, 
          "agentip", $var[4],
          "uptime", $var[2],
          "traptime", $trap_date_time_sql,
          "formatline", $message_short2,
          @t_sql_custom_columns_unknown);

      }
    }
  }

  # If keep_unlogged_traps is set to 0, or if we didn't even try to log, set it as a successful log 
  # so the trap file gets deleted from the spool dir (the trap wasn't found, or a node didn't match)
  if ($trap_attempted_to_log == 0 || $keep_unlogged_traps == 0)	
  {
    $trap_successfully_logged = 1;	
  }
  elsif ($keep_unlogged_traps == 1)	# Delete spool if ONE successfully logged
  {
    if ($trap_successfully_logged >= 1)
    {
      $trap_successfully_logged = 1;
    }
    else
    {
      $trap_successfully_logged = 0;
    }
  }
  elsif ($keep_unlogged_traps == 2)	# Delete spool if ALL successfully logged
  {
    if ($trap_successfully_logged == $trap_attempted_to_log)
    {
      $trap_successfully_logged = 1;
    }
    else
    {
      $trap_successfully_logged = 0;
    }
  }
}

sub findsnmpttini
{
  ##############################################################################
  # Load config file start
  #
  # For Linux / Unix, try in order:
  #   /etc/snmp/snmptt.ini
  #   /etc/snmptt.ini
  #   /usr/local/etc/snmp/snmptt.ini
  #   /usr/local/etc/snmptt.ini
  #
  #
  # For Windows, try %SystemRoot%\snmptt.ini only.
  #
  if ($ini ne '')
  {
    $configfile = $ini;
  }
  else
  {
    if ($^O ne "MSWin32")
    {
      $configfile = '/etc/snmp/snmptt.ini';
      if( open( CONFIG, '/etc/snmp/snmptt.ini' ) )
      {
        $configfile = '/etc/snmp/snmptt.ini';
        close CONFIG;
      }
      elsif ( open( CONFIG, '/etc/snmptt.ini' ) )
      {
        $configfile = '/etc/snmptt.ini';
        close CONFIG;
      }
      elsif ( open( CONFIG, '/usr/local/etc/snmp/snmptt.ini' ) )
      {
        $configfile = '/usr/local/etc/snmp/snmptt.ini';
        close CONFIG;
      }
      elsif ( open( CONFIG, '/usr/local/etc/snmptt.ini' ) )
      {
        $configfile = '/usr/local/etc/snmptt.ini';
        close CONFIG;
      }
    }
    else {
      $configfile = $ENV{'SystemRoot'}."\\snmptt.ini";
    }
  }
}

sub loadsnmpttini {

  ##############################################################################
  #
  # Load config file start
  #
  #
  ##############################################################################

  use Config::IniFiles;
  my $cfg;

  if( open( CONFIG, $configfile ) ) {
    close CONFIG;
    $cfg = new Config::IniFiles( -file => $configfile);
    if ($DEBUGGING >= 1) {
      print "Config file $configfile loaded\n";
    }
  }
  else
  {
    if ($DEBUGGING >= 0) {
      print "Config file could not be loaded\n";
    }
    exit(1);
  }

  if (! $cfg)
  {
    if ($DEBUGGING >= 0) {
      print "Error in config file - please check the syntax in the config file\n";
    }
    exit(1);
  }

  @snmptt_conf_files = '';

  # General
  if ( ($cfg->val('General', 'mode') eq "daemon") || ($daemoncmdline == 1) )
  {
    $daemon = 1;
  }
  else {
    $daemon = 0;
  }

  $snmptt_system_name = $cfg->val('General', 'snmptt_system_name');
  $multiple_event = $cfg->val('General', 'multiple_event');
  $dns_enable = $cfg->val('General', 'dns_enable');
  $strip_domain = $cfg->val('General', 'strip_domain');
  @strip_domain_list = $cfg->val('General', 'strip_domain_list');
  $net_snmp_perl_enable = $cfg->val('General', 'net_snmp_perl_enable');
  $net_snmp_perl_cache_enable = $cfg->val('General', 'net_snmp_perl_cache_enable');
  $net_snmp_perl_best_guess = $cfg->val('General', 'net_snmp_perl_best_guess');
  $translate_log_trap_oid = $cfg->val('General', 'translate_log_trap_oid');
  $translate_value_oids = $cfg->val('General', 'translate_value_oids');
  $resolve_value_ip_addresses = $cfg->val('General', 'resolve_value_ip_addresses');
  $translate_enterprise_oid_format = $cfg->val('General', 'translate_enterprise_oid_format');
  $translate_trap_oid_format = $cfg->val('General', 'translate_trap_oid_format');
  $translate_varname_oid_format = $cfg->val('General', 'translate_varname_oid_format');
  $translate_integers = $cfg->val('General', 'translate_integers');
  $wildcard_expansion_separator = $cfg->val('General', 'wildcard_expansion_separator');
  $mibs_environment = $cfg->val('General', 'mibs_environment');
  $allow_unsafe_regex = $cfg->val('General', 'allow_unsafe_regex');
  $remove_backslash_from_quotes = $cfg->val('General', 'remove_backslash_from_quotes');
  $dynamic_nodes = $cfg->val('General', 'dynamic_nodes');
  $description_mode = $cfg->val('General', 'description_mode');
  $description_clean = $cfg->val('General', 'description_clean');
  $threads_enable = $cfg->val('General', 'threads_enable');
  $threads_max = $cfg->val('General', 'threads_max');
  $date_format = $cfg->val('General', 'date_format');
  $time_format = $cfg->val('General', 'time_format');
  $date_time_format = $cfg->val('General', 'date_time_format');

  # DaemonMode
  $daemon_fork = $cfg->val('DaemonMode', 'daemon_fork');
  $daemon_uid = $cfg->val('DaemonMode', 'daemon_uid');
  $pid_file = $cfg->val('DaemonMode', 'pid_file');
  $spool_directory = $cfg->val('DaemonMode', 'spool_directory');
  $sleep = $cfg->val('DaemonMode', 'sleep');
  $use_trap_time = $cfg->val('DaemonMode', 'use_trap_time');
  $keep_unlogged_traps = $cfg->val('DaemonMode', 'keep_unlogged_traps');	
  $duplicate_trap_window = $cfg->val('DaemonMode', 'duplicate_trap_window');	

  # Logging
  $stdout_enable = $cfg->val('Logging', 'stdout_enable');
  $log_enable = $cfg->val('Logging', 'log_enable');
  $log_file = $cfg->val('Logging', 'log_file');
  $log_system_enable = $cfg->val('Logging', 'log_system_enable');
  $log_system_file = $cfg->val('Logging', 'log_system_file');
  $unknown_trap_log_enable = $cfg->val('Logging', 'unknown_trap_log_enable');
  $unknown_trap_log_file = $cfg->val('Logging', 'unknown_trap_log_file');
  $statistics_interval = $cfg->val('Logging', 'statistics_interval');
  $syslog_enable = $cfg->val('Logging', 'syslog_enable');
  $syslog_facility = $cfg->val('Logging', 'syslog_facility');
  @syslog_level_alert = $cfg->val('Logging', 'syslog_level_alert');
  @syslog_level_crit = $cfg->val('Logging', 'syslog_level_crit');
  @syslog_level_err = $cfg->val('Logging', 'syslog_level_err');	
  @syslog_level_warning = $cfg->val('Logging', 'syslog_level_warning');
  @syslog_level_notice = $cfg->val('Logging', 'syslog_level_notice');
  @syslog_level_info = $cfg->val('Logging', 'syslog_level_info');	
  @syslog_level_debug = $cfg->val('Logging', 'syslog_level_debug');
  $syslog_level = $cfg->val('Logging', 'syslog_level');
  $syslog_system_enable = $cfg->val('Logging', 'syslog_system_enable');
  $syslog_system_facility = $cfg->val('Logging', 'syslog_system_facility');
  $syslog_system_level = $cfg->val('Logging', 'syslog_system_level');
  $eventlog_enable = $cfg->val('Logging', 'eventlog_enable');
  @eventlog_type_information = $cfg->val('Logging', 'eventlog_type_information');
  @eventlog_type_warning = $cfg->val('Logging', 'eventlog_type_warning');
  @eventlog_type_error = $cfg->val('Logging', 'eventlog_type_error');	
  $eventlog_type = $cfg->val('Logging', 'eventlog_type');
  $eventlog_system_enable = $cfg->val('Logging', 'eventlog_system_enable');

  # Exec
  $exec_enable = $cfg->val('Exec', 'exec_enable');
  $pre_exec_enable = $cfg->val('Exec', 'pre_exec_enable');
  $unknown_trap_exec = $cfg->val('Exec', 'unknown_trap_exec');
  $unknown_trap_exec_format = $cfg->val('Exec', 'unknown_trap_exec_format');
  $exec_escape = $cfg->val('Exec', 'exec_escape');

  # SQL
  $db_translate_enterprise = $cfg->val('SQL', 'db_translate_enterprise');
  $db_unknown_trap_format = $cfg->val('SQL', 'db_unknown_trap_format');
  $mysql_dbi_enable = $cfg->val('SQL', 'mysql_dbi_enable');
  $mysql_dbi_host = $cfg->val('SQL', 'mysql_dbi_host');
  $mysql_dbi_port = $cfg->val('SQL', 'mysql_dbi_port');
  $mysql_dbi_database = $cfg->val('SQL', 'mysql_dbi_database');
  $mysql_dbi_table = $cfg->val('SQL', 'mysql_dbi_table');
  $mysql_dbi_table_unknown = $cfg->val('SQL', 'mysql_dbi_table_unknown');
  $mysql_dbi_table_statistics = $cfg->val('SQL', 'mysql_dbi_table_statistics');
  $mysql_dbi_username = $cfg->val('SQL', 'mysql_dbi_username');
  $mysql_dbi_password = $cfg->val('SQL', 'mysql_dbi_password');
  $mysql_ping_on_insert = $cfg->val('SQL', 'mysql_ping_on_insert');
  $mysql_ping_interval = $cfg->val('SQL', 'mysql_ping_interval');

  $postgresql_dbi_enable = $cfg->val('SQL', 'postgresql_dbi_enable');
  $postgresql_dbi_module = $cfg->val('SQL', 'postgresql_dbi_module');
  $postgresql_dbi_hostport_enable = $cfg->val('SQL', 'postgresql_dbi_hostport_enable');
  $postgresql_dbi_host = $cfg->val('SQL', 'postgresql_dbi_host');
  $postgresql_dbi_port = $cfg->val('SQL', 'postgresql_dbi_port');
  $postgresql_dbi_database = $cfg->val('SQL', 'postgresql_dbi_database');
  $postgresql_dbi_table = $cfg->val('SQL', 'postgresql_dbi_table');
  $postgresql_dbi_table_unknown = $cfg->val('SQL', 'postgresql_dbi_table_unknown');
  $postgresql_dbi_table_statistics = $cfg->val('SQL', 'postgresql_dbi_table_statistics');
  $postgresql_dbi_username = $cfg->val('SQL', 'postgresql_dbi_username');
  $postgresql_dbi_password = $cfg->val('SQL', 'postgresql_dbi_password');
  $postgresql_ping_on_insert = $cfg->val('SQL', 'postgresql_ping_on_insert');
  $postgresql_ping_interval = $cfg->val('SQL', 'postgresql_ping_interval');

  $dbd_odbc_enable = $cfg->val('SQL', 'dbd_odbc_enable');
  $dbd_odbc_dsn = $cfg->val('SQL', 'dbd_odbc_dsn');
  $dbd_odbc_table = $cfg->val('SQL', 'dbd_odbc_table');
  $dbd_odbc_table_unknown = $cfg->val('SQL', 'dbd_odbc_table_unknown');
  $dbd_odbc_table_statistics = $cfg->val('SQL', 'dbd_odbc_table_statistics');
  $dbd_odbc_username = $cfg->val('SQL', 'dbd_odbc_username');
  $dbd_odbc_password = $cfg->val('SQL', 'dbd_odbc_password');
  $dbd_odbc_ping_on_insert = $cfg->val('SQL', 'dbd_odbc_ping_on_insert');
  $dbd_odbc_ping_interval = $cfg->val('SQL', 'dbd_odbc_ping_interval');

  $sql_win32_odbc_enable = $cfg->val('SQL', 'sql_win32_odbc_enable');
  $sql_win32_odbc_dsn = $cfg->val('SQL', 'sql_win32_odbc_dsn');
  $sql_win32_odbc_table = $cfg->val('SQL', 'sql_win32_odbc_table');
  $sql_win32_odbc_table_unknown = $cfg->val('SQL', 'sql_win32_odbc_table_unknown');
  $sql_win32_odbc_table_statistics = $cfg->val('SQL', 'sql_win32_odbc_table_statistics');
  $sql_win32_odbc_username = $cfg->val('SQL', 'sql_win32_odbc_username');
  $sql_win32_odbc_password = $cfg->val('SQL', 'sql_win32_odbc_password');

  @sql_custom_columns = $cfg->val('SQL', 'sql_custom_columns');
  @sql_custom_columns_unknown = $cfg->val('SQL', 'sql_custom_columns_unknown');

  $date_time_format_sql = $cfg->val('SQL', 'date_time_format_sql');
  $stat_time_format_sql = $cfg->val('SQL', 'stat_time_format_sql');
  
  # Debugging
  if ($debugcmdline == 0) {
    $DEBUGGING = $cfg->val('Debugging', 'DEBUGGING');
  }
  if ($debugfilecmdline == 0) {
    $DEBUGGING_FILE = $cfg->val('Debugging', 'DEBUGGING_FILE');
  }

  # TrapFiles
  @snmptt_conf_files = $cfg->val('TrapFiles', 'snmptt_conf_files');

  $cfg->Delete;

  #
  # Defaults Start
  #
  if ($snmptt_system_name eq "") {
    if (hostname ne "") {
      $snmptt_system_name = hostname;
    }
  }
  if (! defined ($multiple_event)) { $multiple_event = 0} ;
  if (! defined ($dns_enable)) { $dns_enable = 0} ;
  if (! defined ($strip_domain)) { $strip_domain = 0} ;
  if (! defined ($net_snmp_perl_enable)) { $net_snmp_perl_enable = 0} ;
  if (! defined ($net_snmp_perl_cache_enable)) { $net_snmp_perl_cache_enable = 1} ;
  if (! defined ($net_snmp_perl_best_guess)) { $net_snmp_perl_best_guess = 0} ;
  if (! defined ($translate_log_trap_oid)) { $translate_log_trap_oid = 0} ;
  if (! defined ($translate_value_oids)) { $translate_value_oids = 0} ;
  if (! defined ($resolve_value_ip_addresses)) { $resolve_value_ip_addresses = 0} ;
  if (! defined ($translate_enterprise_oid_format)) { $translate_enterprise_oid_format = 1} ;
  if (! defined ($translate_trap_oid_format)) { $translate_trap_oid_format = 1} ;
  if (! defined ($translate_varname_oid_format)) { $translate_varname_oid_format = 1} ;
  if (! defined ($translate_integers)) { $translate_integers = 0} ;
  if (! defined ($wildcard_expansion_separator)) { $wildcard_expansion_separator = " "} ;
  if (! defined ($allow_unsafe_regex)) { $allow_unsafe_regex = 0} ;
  if (! defined ($remove_backslash_from_quotes)) { $remove_backslash_from_quotes = 0} ;
  if (! defined ($dynamic_nodes)) { $dynamic_nodes = 0} ;
  if (! defined ($description_mode)) { $description_mode = 0} ;
  if (! defined ($description_clean)) { $description_clean = 1} ;
  if (! defined ($threads_enable)) { $threads_enable = 0} ;
  if (! defined ($threads_max)) { $threads_max = 10} ;
  if (! defined ($date_format)) { $date_format = "%a %b %e %Y"} ;
  if (! defined ($time_format)) { $time_format = "%H:%M:%S"} ;
  if (! defined ($date_time_format)) { $date_time_format = ""} ;
  if (! defined ($date_time_format_sql)) { $date_time_format_sql = ""} ;
  if (! defined ($stat_time_format_sql)) { $stat_time_format_sql = ""} ;

  if (! defined ($daemon_fork)) { $daemon_fork = 1} ;
  if (! defined ($daemon_uid)) { $daemon_uid = ''} ;
  if (! defined ($pid_file)) { $pid_file = ''} ;
  if (! defined ($spool_directory)) { $spool_directory = ''} ;
  if (! defined ($sleep)) { $sleep = 5} ;
  if (! defined ($use_trap_time)) { $use_trap_time = 1} ;
  if (! defined ($keep_unlogged_traps)) { $keep_unlogged_traps = 1} ;
  if (! defined ($duplicate_trap_window)) { $duplicate_trap_window = 0} ;

  if (! defined ($stdout_enable)) { $stdout_enable = 0} ;
  if (! defined ($log_enable)) { $log_enable = 1} ;
  if (! defined ($log_file)) { $log_file = ''} ;
  if (! defined ($log_system_enable)) { $log_system_enable = 0} ;
  if (! defined ($log_system_file)) { $log_system_file = ''} ;
  if (! defined ($unknown_trap_log_enable)) { $unknown_trap_log_enable = 0} ;
  if (! defined ($unknown_trap_log_file)) { $unknown_trap_log_file = ''} ;
  if (! defined ($syslog_enable)) { $syslog_enable = 0} ;
  if (! defined ($syslog_facility)) { $syslog_facility = 'local0'} ;
  if (! defined ($statistics_interval)) { $statistics_interval = 0} ;
  if (! defined ($syslog_level)) { $syslog_level = 'warning'} ;
  if (! defined ($syslog_system_enable)) { $syslog_system_enable = 0} ;
  if (! defined ($syslog_system_facility)) { $syslog_system_facility = 'local0'} ;
  if (! defined ($syslog_system_level)) { $syslog_system_level = 'warning'} ;
  if (! defined ($eventlog_enable)) { $eventlog_enable = 0} ;
  if (! defined ($eventlog_enable)) { $eventlog_enable = 'WARNING'} ;
  if (! defined ($eventlog_system_enable)) { $eventlog_system_enable = 0} ;	
  if (! defined ($exec_enable)) { $exec_enable = 1} ;
  if (! defined ($pre_exec_enable)) { $pre_exec_enable = 1} ;
  if (! defined ($unknown_trap_exec)) { $unknown_trap_exec = ''} ;
  if (! defined ($unknown_trap_exec_format)) { $unknown_trap_exec_format = ''} ;
  if (! defined ($exec_escape)) {
    if ($^O =~ /MSWin32/) {
      $exec_escape = 0;
    }
    else {
      $exec_escape = 1;
    }
  }
  if (! defined ($db_translate_enterprise)) { $db_translate_enterprise = 0} ;
  if (! defined ($db_unknown_trap_format)) { $db_unknown_trap_format = '$-*'} ;
  if (! defined ($mysql_dbi_enable)) { $mysql_dbi_enable = 0} ;
  if (! defined ($mysql_dbi_host)) { $mysql_dbi_host = 'localhost'} ;
  if (! defined ($mysql_dbi_port)) { $mysql_dbi_port = '3306'} ;
  if (! defined ($mysql_dbi_database)) { $mysql_dbi_database = ''} ;
  if (! defined ($mysql_dbi_table)) { $mysql_dbi_table = ''} ;
  if (! defined ($mysql_dbi_table_unknown)) { $mysql_dbi_table_unknown = ''} ;
  if (! defined ($mysql_dbi_table_statistics)) { $mysql_dbi_table_statistics = ''} ;
  if (! defined ($mysql_dbi_username)) { $mysql_dbi_username = ''} ;
  if (! defined ($mysql_dbi_password)) { $mysql_dbi_password = ''} ;
  if (! defined ($mysql_ping_on_insert)) { $mysql_ping_on_insert = 1} ;
  if (! defined ($mysql_ping_interval)) { $mysql_ping_interval = 500} ;
  if (! defined ($postgresql_dbi_enable)) { $postgresql_dbi_enable = 0} ;
  if (! defined ($postgresql_dbi_module)) { $postgresql_dbi_module = 0} ;
  if (! defined ($postgresql_dbi_hostport_enable)) { $postgresql_dbi_hostport_enable = 0} ;
  if (! defined ($postgresql_dbi_host)) { $postgresql_dbi_host = 'localhost'} ;
  if (! defined ($postgresql_dbi_port)) { $postgresql_dbi_port = '5432'} ;
  if (! defined ($postgresql_dbi_database)) { $postgresql_dbi_database = ''} ;
  if (! defined ($postgresql_dbi_table)) { $postgresql_dbi_table = ''} ;
  if (! defined ($postgresql_dbi_table_unknown)) { $postgresql_dbi_table_unknown = ''} ;
  if (! defined ($postgresql_dbi_table_statistics)) { $postgresql_dbi_table_statistics = ''} ;
  if (! defined ($postgresql_dbi_username)) { $postgresql_dbi_username = ''} ;
  if (! defined ($postgresql_dbi_password)) { $postgresql_dbi_password = ''} ;
  if (! defined ($postgresql_ping_on_insert)) { $postgresql_ping_on_insert = 1} ;
  if (! defined ($postgresql_ping_interval)) { $postgresql_ping_interval = 500} ;
  if (! defined ($dbd_odbc_enable)) { $dbd_odbc_enable = 0} ;
  if (! defined ($dbd_odbc_dsn)) { $dbd_odbc_dsn = ''} ;
  if (! defined ($dbd_odbc_table)) { $dbd_odbc_table = ''} ;
  if (! defined ($dbd_odbc_table_unknown)) { $dbd_odbc_table_unknown = ''} ;
  if (! defined ($dbd_odbc_table_statistics)) { $dbd_odbc_table_statistics = ''} ;
  if (! defined ($dbd_odbc_username)) { $dbd_odbc_username = ''} ;
  if (! defined ($dbd_odbc_password)) { $dbd_odbc_password = ''} ;
  if (! defined ($dbd_odbc_ping_on_insert)) { $dbd_odbc_ping_on_insert = 1} ;
  if (! defined ($dbd_odbc_ping_interval)) { $dbd_odbc_ping_interval = 500} ;
  if (! defined ($sql_win32_odbc_enable)) { $sql_win32_odbc_enable = 0} ;
  if (! defined ($sql_win32_odbc_dsn)) { $sql_win32_odbc_dsn = ''} ;
  if (! defined ($sql_win32_odbc_table)) { $sql_win32_odbc_table = ''} ;
  if (! defined ($sql_win32_odbc_table_unknown)) { $sql_win32_odbc_table_unknown = ''} ;
  if (! defined ($sql_win32_odbc_table_statistics)) { $sql_win32_odbc_table_statistics = ''} ;
  if (! defined ($sql_win32_odbc_username)) { $sql_win32_odbc_username = ''} ;
  if (! defined ($sql_win32_odbc_password)) { $sql_win32_odbc_password = ''} ;
  if (! (@sql_custom_columns)) { @sql_custom_columns = ()} ;
  if (! (@sql_custom_columns_unknown)) { @sql_custom_columns_unknown = ()} ;
  if (! defined ($DEBUGGING)) { $DEBUGGING = 0} ;
  if (! defined ($DEBUGGING_FILE)) { $DEBUGGING_FILE = ''} ;

  # Make sure it's at least 3 characters, and remove quotes around wildcard_expansion_separator
  if (length ($wildcard_expansion_separator) < 3)
  {
    $wildcard_expansion_separator = "x x";
  }
  $wildcard_expansion_separator = substr($wildcard_expansion_separator,1,(length($wildcard_expansion_separator)-2));	
  #
  # Defaults End
  #

  # print "Config file loaded\n";

  #
  # Load config file end
  #
  ##############################################################################
}

sub signal_handler_reload {
  #
  # Daemon reload
  #
  $timetoreload = 1;
  &reopen_debug_file;
}

sub signal_handler_die {
  #
  # Daemon die
  #
  $timetodie = 1;
}

sub syslog_system {
  my $message = $_[0];
  #if (Sys::Syslog::openlog('snmptt-sys[' . $> . ']', '',$syslog_system_facility) )
  eval {
    if (Sys::Syslog::openlog('snmptt-sys','pid',$syslog_system_facility) )
    {
      # Escape all %'s before sending to syslog.  A single % may cause syslog to hang
      $message =~ s/%/%%/g;
      Sys::Syslog::syslog ($syslog_system_level, $message,1);
      Sys::Syslog::closelog();
    }
  };
  if ($@) {
    warn "Cannot write to syslog: $@\n";
    print "Cannot write to syslog.  Message to be logged: $message\n" if ($DEBUGGING >= 1);
  }
}

sub log_system {
  my $message = $_[0];

  my @localtime_array = localtime();
  my $log_time;

  if ($date_time_format eq "") {
    $log_time = localtime();
  }
  else {
    $log_time = strftime $date_time_format, @localtime_array;
  }

  if (open LOGSYSFILE, ">>$log_system_file")
  {
    print LOGSYSFILE $log_time." $message\n";
    close LOGSYSFILE;
  }
  else
  {
    warn "Can not open log file $log_system_file: $!";
    print "Can not open syslog.  Message to be logged: $message\n" if ($DEBUGGING >= 1);
  }
}

sub eventlog_system {
  my $message = $_[0];
  my $eventid = $_[1];
  my $type = $_[2];
  my $trap_log = $_[3];		# Should be 1 if this is a trap log.  Used
          # to set $trap_successfully_logged variable below.

  my %event_entry = ('Source' => "SNMPTT",
           'EventType' => $type,
         'Category' => '\0',
         'EventID' => $eventid,
         'Strings' => $message);

  if (my $eventlog=Win32::EventLog->new('Application') )
  {
    unless ($eventlog->Report(\%event_entry) )
    {
      warn "Can not create event log entry: $!";
      $eventlog->Close();
      return 1;
    }
    $eventlog->Close();
    return 0;
  }
  else
  {
    warn "Can not open log_file: $!";
    return 1;
  }
}

sub checkip
{
  my $node1 = $_[0];		# Should be a plain IP address
  my $node2 = $_[1];		# Can be a plain IP address, CIDR or range

  # Remove all white space
  $node1 =~ s/\s*//g;
  $node2 =~ s/\s*//g;

  # Check for an exact match
  if ($node1 eq $node2)
  {
    return 1;
  }

  #print "node1:$node1\n";

  # First is an IP address, and second IP address is a CIDR address
  if ($node1 =~ /^\d+\.\d+\.\d+\.\d+$/ && $node2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)/)
  {
    # Get IP address in binary
    $node1 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
    my $node1_binary = dec2bin($1) . dec2bin($2) .dec2bin($3) .dec2bin($4);

    # Get CIDR network address in binary
    $node2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)/;
    my $cidr_binary = dec2bin($1) . dec2bin($2) .dec2bin($3) .dec2bin($4);
    my $net_bits = $5;
    my $host_bits = 32 - $net_bits;
    $cidr_binary = substr($cidr_binary, 0, $net_bits);

    # Get network number for ip_address
    my $node1_binary_net = substr($node1_binary, 0, $net_bits);

    if ($node1_binary_net eq $cidr_binary)
    {
      return 1;
    }
    else
    {
      return 0;
    }
  }
  # First is an IP address, and second IP address is a network range
  elsif ($node1 =~ /^\d+\.\d+\.\d+\.\d+$/ && $node2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.(\d+)\.(\d+)/)
  {
    # Get IP address in binary
    $node1 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
    my $node1_binary = dec2bin($1) . dec2bin($2) .dec2bin($3) .dec2bin($4);

    # Get left network address in binary
    $node2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.(\d+)\.(\d+)/;
    my $left_binary = dec2bin($1) . dec2bin($2) .dec2bin($3) .dec2bin($4);
    my $right_binary = dec2bin($5) . dec2bin($6) .dec2bin($7) .dec2bin($8);

    if (bin2dec($node1_binary) >= bin2dec($left_binary) &&
      bin2dec($node1_binary) <= bin2dec($right_binary))
    {
      return 1;
    }
    else
    {
      return 0;
    }
  }
  # Didn't match at all, so return 0.
  else
  {
    return 0;
  }

}

sub dec2bin {
  my $str = unpack("B8", pack("C", shift));
  #$str =~ s/^0+(?=\d)//;   # otherwise you'll get leading zeros
  return $str;
}

sub bin2dec {
  return unpack("N", pack("B32", substr("0" x 32 . shift, -32)));
}

sub translate_value_oids_sub
{
  my $string = $_[0];

  if ($string =~ /\.\d+/)	# If it appears to contain an OID
  {
    if ($DEBUGGING >= 2)
    {
      print "  Value appears to contain an OID or IP address.\n";
    }
    my $string_temp = $string;
    my @oids = ();
    my $done = 1;
    my $noinf = 0;

    # Find any IP addresses and remove from $string_temp

    $noinf = 0;
    while ($done)
    {
      my $found;
      if ($string_temp =~ /(?<!\.)(?<!\d)(\d+\.\d+\.\d+\.\d+)(?!\.\d)(?!\d)/ ) { $found = 1; }
      #if ($string_temp =~ /(?<!\.)(\d+\.\d+\.\d+\.\d+)(?!\.)/ ) { $found = 1; }
      else { $found = 0; }

      if ($found)
      {
        #print "ip:$&\n";
        $string_temp =~ s/(?<!\.)$1(?!\.)//g;	# Remove IP from string_temp
      }
      else
      {
        $done = 0;
      }
      $noinf++;
      if ($noinf >= 100) # Safe guard to make sure regex pattern search / replace doesn't cause an
                         # infinit loop in the while.  Shouldn't, but just in case..
      {
        $done = 0;
        if ($DEBUGGING >= 2)
        {
          print "  Possible infinit loop (1) in translate_value_oids_sub averted.\n";
        }
      }
    }

    # Find all the OIDs and put them in a @oids array, and remove from $string_temp
    $done = 1;
    my $oid_found = 0;
    $noinf = 0;
    while ($done)
    {
      my $found;
      if ($string_temp =~ /(?<!\d)(\.\d+)+/) {	$found = 1; }  # don't match n.n.n but do match .n.n.n
      else { $found = 0; }

      if ($found)
      {
        my $oid = $&;
        push (@oids, $oid);
        #print "oid:$oid\n";
        #print "\$string_temp before: $string_temp\n";
        $string_temp =~ s/(?<!\d)$oid(?!\.\d)//g;	# Remove OID from string_temp
        #print "\$string_temp after : $string_temp\n";
        $oid_found = 1;
      }
      else
      {
        $done = 0;
      }
      $noinf++;
      if ($noinf >= 100) # Safe guard to make sure regex pattern search / replace doesn't cause an
                         # infinit loop in the while.  Shouldn't, but just in case..
      {
        $done = 0;
        if ($DEBUGGING >= 2)
        {
          print "  Possible infinit loop (2) in translate_value_oids_sub averted.\n";
        }
      }
    }

    if ($oid_found == 1 && $DEBUGGING >= 2)
    {
      print "  Value contains an OID.\n";
    }

    # Reverse sort the list to make sure large OIDs are done first, so we don't get half
    # converted OIDs.  Probably not needed due to look ahead and behind expression below
    @oids = reverse sort(@oids);

    #print "oids: @oids\n";

    # Replace all OIDs in $string with textual names if possible
    foreach my $oid (@oids)
    {
      my $temp = my_translateObj($oid,$translate_value_oids);
      if (defined ($temp))
      {
        $string =~ s/(?<!\.|\d)$oid(?!\.\d)/$temp/g;
        if ($oid_found == 1 && $DEBUGGING >= 2)
        {
          print "    OID: $oid=$temp\n";
        }
      }
    }
  }
  else
  {
    if ($DEBUGGING >= 2)
    {
      print "  Value does not appear to contain an OID\n";
    }
  }
  # Return the new string
  return $string;    
}

sub resolve_value_ip_addresses_sub
{
  my $string = $_[0];

  if ($string =~ /(?<!\.)(\d+\.\d+\.\d+\.\d+)(?!\.)/)	# If it appears to contain an IP address
  {
    if ($DEBUGGING >= 2)
    {
      print "  Value appears to contain an IP address.\n";
    }
    my $string_temp = $string;
    my @hostnames = ();
    my $done = 1;
    my $noinf = 0;

    # Find any IP addresses and put them in a @oids array, and remove from $string_temp

    $done = 1;
    my $hostname_found = 0;
    $noinf = 0;
    while ($done)
    {
      my $found;
      if ($string_temp =~ /(?<!\.)(?<!\d)(\d+\.\d+\.\d+\.\d+)(?!\.\d)(?!\d)/ ) { $found = 1; }
      #if ($string_temp =~ /(?<!\.)(\d+\.\d+\.\d+\.\d+)(?!\.)/ ) { $found = 1; }
      else { $found = 0; }

      if ($found)
      {
        my $hostname = $&;
        push (@hostnames, $hostname);
        #print "ip:$&\n";
        $string_temp =~ s/(?<!\.)$1(?!\.)//g;	# Remove IP from string_temp
        $hostname_found = 1;
      }
      else
      {
        $done = 0;
      }
      $noinf++;
      if ($noinf >= 100) # Safe guard to make sure regex pattern search / replace doesn't cause an
                         # infinit loop in the while.  Shouldn't, but just in case..
      {
        $done = 0;
        if ($DEBUGGING >= 2)
        {
          print "  Possible infinit loop (1) in resolve_value_ip_addresses_sub averted.\n";
        }
      }
    }

    if ($hostname_found == 1 && $DEBUGGING >= 2)
    {
      print "  Value contains an IP address.\n";
    }

    #print "hostnames: @hostnames\n";

    # Replace all ip addresses in $string with textual names if possible
    foreach my $hostname (@hostnames)
    {
      my $temp = gethostbyaddr(Socket::inet_aton($hostname),Socket::AF_INET());
      if (defined ($temp))
      {
        if ($DEBUGGING => 2)
        {
          print "    IP address ($hostname) resolved to: $temp\n";
        }
        if ($strip_domain)
        {
          $temp = strip_domain_name($temp, $strip_domain);
        }
        $string =~ s/(?<!\.|\d)$hostname(?!\.\d)/$temp/g;
      }
      else
      {
        if ($DEBUGGING => 2)
        {
          print "    IP address ($hostname) could not be resolved by DNS.\n";
        }
      }
    }
  }
  else
  {
    if ($DEBUGGING >= 2)
    {
      print "  Value does not appear to contain an IP address\n";
    }
  }
  # Return the new string
  return $string;    
}

sub match
{
  my $match = shift;
  my $value;
  my $not = 0;
  my $result = 0;

  if ($DEBUGGING >= 1) {  print "    MATCH statement: $match\n"; }

  if ($match =~ /^\$(\d+):(.*)/) {
    my $match_var = "\$$1";
    $value = $entvar[$1-1];
    $match = $2;
    if ($DEBUGGING >= 1) {  print "     MATCH variable ($match_var) is an enterprise variable.  No substitution needed\n"; }
  }
  else {
    $match =~ /^(\$.*?):(.*)/;
    my $match_var = $1;
    $_ = $match_var;
    $match = $2;
    if ($DEBUGGING >= 1) {  print "     Performing substitution on MATCH variable: $match_var\n"; }
    &substitute();
    if ($DEBUGGING >= 1) {  print "     Done performing substitution on MATCH variable $match_var (converted to: $_)\n"; }
    $value = $_;
  }

  if ($value eq '' || $match eq '')
  {
    return 0;
  }
  if ($match =~ /^\!(.*)/) 
  {
    $not = 1;
    $match = $1;
  }
  else 
  {
    $not = 0; 
  }

  # Match is a REGEX
  if ($match =~ /\(.*\)/)
  {
    #print "Regex detected\n";

    $result = match_regex($value, $match);
    $result = match_result($result, $not);
    if ($DEBUGGING >= 1)
    {
      print "    REGEX: value=$value, match=" . ($not == 1 ? "!$match" : "$match") . " Result=" .
             ($result == 1 ? "true" : "false") . "\n";
    }
  }
  # Match is an IP address
  elsif ($match =~ /\d+\.\d+\.\d+\.\d+/)
  {
    #print "IP address of some sort\n";
    $result = checkip($value, $match);
    $result = match_result($result, $not);
    if ($DEBUGGING >= 1)
    {
      print "    IP ADDRESS: value=$value, match=" . ($not == 1 ? "!$match" : "$match") . " Result=" . 
             ($result == 1 ? "true" : "false") . "\n";
    }
  }
  # Match is a bitwise and
  elsif ($match =~ /&\d+/)
  {
    #print "Bitwise and detected\n";
    $result = match_bitwise_and($value, $match);
    $result = match_result($result, $not);
    if ($DEBUGGING >= 1)
    {
      print "    BITWISE AND: value=$value, match=" . ($not == 1 ? "!$match" : "$match") . " Result=" . 
             ($result == 1 ? "true" : "false") . "\n";
    }
  }
  # Match is a number
  elsif ($match =~ /\d+/)
  {
    #print "Number detected\n";
    $result = match_number($value, $match);
    $result = match_result($result, $not);
    if ($DEBUGGING >= 1)
    {
      print "    NUMBER: value=$value, match=" . ($not == 1 ? "!$match" : "$match") . " Result=" . 
             ($result == 1 ? "true" : "false") . "\n";
    }
  }
  # Unknown entry - ignore it
  else
  {
    if ($DEBUGGING >= 1)
    {
      print "    IGNORED: value=$value, match=" . ($not == 1 ? "!$match" : "$match") . "\n";
      $result = 0;
    }
  }

  if ($result == 1)
  {
    #print "true\n";
    return 1;
  }
  else
  {
    #print "false\n";
    return 0;
  }
}

sub match_result
{
  my $result = shift;
  my $not = shift;

  if ($not == 0)
  {
    return $result;
  }
  else
  {
    if ($result >= 1)
    {
      return 0;
    }
    else
    {
      return 1;
    }
  }
  return 0;
}

sub match_number
{
  my $value = shift;
  my $match = shift;

  my $result;

  if ($match == $value)
  {
    return 1;
  }
  elsif ($match =~ /,/)
  {
    my @temp = split (",",$match);
    foreach my $a (@temp)
    {
      if ($a == $value)
      {
  return 1;
      }
    }
  }
  elsif ($match =~ /^\>(.*)/)
  {
    if ($value > $1)
    {
      return 1;
    }
  }
  elsif ($match =~ /^\<(.*)/)
  {
    if ($value < $1)
    {
      return 1;
    }
  }
  elsif ($match =~/(\d+)-(\d+)/)
  {
    if ($value >= $1 && $value <= $2)
    {
      return 1;
    }
  }
  return 0;
}

sub match_regex
{
  my $value = shift;
  my $regex_temp = shift;

  my $result;
  my $modifier_i = 0;

  #print "value: $value\n";
  #print "regex: $regex_temp\n";

  if ($regex_temp =~ /i$/)
  {
    #print "! i modifier\n";
    $modifier_i = 1;
  }

  $regex_temp =~ /\((.*)\)/;
  $regex_temp = $1;

  #print ":$regex_temp:\n";

  if ($regex_temp ne '')
  {
    if ($DEBUGGING >= 1)
    {
      print "  Doing REGEX MATCH: ($regex_temp)\n";
    }
    if ($modifier_i == 1)
    {
      if ($value =~ /$regex_temp/i)
      {
        #print "Match!\n";
        return 1;
      }
    }
    else
    {
      if ($value =~ /$regex_temp/)
      {
        #print "Match!\n";
        return 1;
      }
    }
  }
  return 0;
}

sub match_bitwise_and
{
  my $value = shift;
  my $match = shift;

  # Return if value is not a number
  if (! ($value =~ /\d+/)) {
    return 0;
  }   
  
  # Remove & from value
  $match =~ s/^&//;
  
  my $result;

  # Make sure values are passed as numbers by adding 0 to each
  my $temp = 0+$value & 0+$match;

  if ($temp > 0)
  {  
    return 1;    
  }
  return 0;
}

sub my_translateObj
{
  my $oid = shift;
  my $mode = shift;

  # Check cache first
  my $my_translateObj_cache_value = $my_translateObj_cache{$oid};
  if ($my_translateObj_cache_value) {
    if ($DEBUGGING >= 2)
    {      
      print "    OID found in cache:  '$oid' -> '$my_translateObj_cache_value'\n";
    }
    return $my_translateObj_cache_value;
  }

  my $use_long = 0;
  my $include_module = 0;
  my $temp;

  if ($mode == 1) 	# Short text
  {
    $use_long = 0;
    $include_module = 0;
  }
  elsif ($mode == 2)	# Short module::text
  {
    $use_long = 0;
    $include_module = 1;
  }
  elsif ($mode == 3)	# Long text
  {
    $use_long = 1;
    $include_module = 0;
  }
  elsif ($mode == 4)	# Long module::text
  {
    $use_long = 1;
    $include_module = 1;
  }

  $temp = &SNMP::translateObj($oid,$use_long,$include_module);
  if (defined ($temp))
  {
    # If it ends in a single ., chop it off
    if ($temp =~ /\.$/)
    {
      chop $temp;
    }
  }

  # Update cache if net_snmp_perl_cache_enable is enabled.
  if ($net_snmp_perl_cache_enable == 1) {
    $my_translateObj_cache{$oid} = $temp;
  }

  return $temp;	# Will be either the translated trap, or undef if translateObj failed
}

# Strip domain name from hostname
sub strip_domain_name
{
  my $name = shift;
  my $mode = shift;

  # If mode = 1, strip off all domain names leaving only the host
  if ($mode == 1 && !($name =~ /^\d+\.\d+\.\d+\.\d+$/))
  {
    if ($name =~ /\./)      # Contain a . ?
    {
      $name =~ /^([^\.]+?)\./;
      $name = $1;
    }
  }
  # If mode = 2, strip off the domains as listed in strip_domain_list in .ini file
  elsif ($mode == 2 && !($name =~ /^\d+\.\d+\.\d+\.\d+$/))
  {
    if (@strip_domain_list)
    {
      foreach my $strip_domain_list_temp (@strip_domain_list)
      {
        if ($strip_domain_list_temp =~ /^\..*/) # If domain from list starts with a '.' then remove it first
        {
          ($strip_domain_list_temp) = $strip_domain_list_temp =~ /^\.(.*)/;
        }

        if ($name =~ /^.+\.$strip_domain_list_temp/)      # host is something . domain name?	      
        {
          $name =~ /(.*)\.$strip_domain_list_temp/;	# strip the domain name
          $name = $1;
          last;  # Only process once 
        }
      }
    }
  }
  return $name;
}

sub my_getType
{
  my $oid = shift;

  # Problem #1: UCD-SNMP 4.2.3 allows getType and mapEnum to be passed a numerical OID.  
  # Net-SNMP 5.0.8 does not.  Because of this, lets try using the OID first.  If that 
  # fails, then we try the short textual OID instead. 

  my $type = SNMP::getType($oid);
  if (defined($type))
  {
    #print "my_getType: numeric\n";
    return $type;
  }
  else
  {
    my $temp_textOID = &my_translateObj($oid,0);
    #print "temp_textOID:$temp_textOID\n";
    my $type = &SNMP::getType($temp_textOID);
    if (defined($type))
    {
      #print "my_getType: symbolic\n";
      return $type;
    }
    else
    {
      #print "my_getType: failed\n";
      return undef;
    }
  }
}

sub my_mapEnum
{
  my $oid = shift;
  my $value = shift;

  # Check cache first
  my $my_mapEnum_cache_value = $my_mapEnum_cache{$oid};
  if ($my_mapEnum_cache_value) {
    if ($DEBUGGING >= 2)
    {      
      print "    ENUM found in cache:  '$oid' -> '$my_mapEnum_cache_value'\n";
    }
    return $my_mapEnum_cache_value;
  }

  # Problem #1: UCD-SNMP 4.2.3 allows getType and mapEnum to be passed a numerical OID.  
  # Net-SNMP 5.0.8 does not.  Because of this, lets try using the OID first.  If that 
  # fails, then we try the short textual OID instead. 

  my $result;
  my $enum = SNMP::mapEnum($oid, $value);
  if (defined($enum))
  {
    #print "my_mapEnum: numeric\n";
    #return $enum;
    $result = $enum;
  }
  else
  {
    my $temp_textOID = &my_translateObj($oid,0);
    #print "temp_textOID:$temp_textOID\n";
    my $enum = &SNMP::mapEnum($temp_textOID, $value);
    if (defined($enum))
    {
      #print "my_mapEnum: symbolic\n";
      #return $enum;
      $result = $enum;
    }
    else
    {
      #print "my_mapEnum: failed\n";
      #return undef;
      $result = undef;
    }
  }
  # Update cache if net_snmp_perl_cache_enable is enabled.
  if ($net_snmp_perl_cache_enable == 1) {
    $my_mapEnum_cache{$oid} = $result;
  }

  return $result;
}

sub process_nodes
{
  my $nodes_list = shift;
  my @nodes = ();
  my @nodes2 = ();
  
  # print "Processing NODES\n";

  # Put all the NODES entries into @nodes, and then go through them all and put
  # them into @nodes2.  This is done so files that are in the NODES list can be
  # read in and merged into @nodes2 to allow a NODES line to contain both host
  # names / ip addresses AND the entries from nodes files.
  @nodes = split /\s/, $nodes_list;
  foreach my $a (@nodes)
  {
    # Contain a \ or /?  Must be a filename and not an IP address / network number
    if ( ($a =~ /\\|\//) && !($a =~ /\d+\.\d+\.\d+\.\d+/))
    {
      #print "!Dynamic enabled\n";
      if (open NODESFILE, $a)
      {
        while (defined(my $line = <NODESFILE>))
        {
          chomp($line);
          # Allow comment lines starting with a #
          if (!($line =~ /((^#)|(\s+#)).*/))
          {
            my @temp2 = split /\s/, $line;
            foreach my $b (@temp2)
            {
              push (@nodes2, $b);
            }
          }
        }
        close NODESFILE;
      }
      else
      {
        warn "Can not open NODES file: $a $!";
      }
    }
    else 	# Must be a single node
    {
      push (@nodes2, $a);
    }
  }
  return @nodes2;
}

# Used when reading received traps to symbolic names in variable names and
# values to numerical
sub translate_symbolic_to_oid
{
  my $temp = shift;

  # Check to see if OID passed from snmptrapd is fully numeric.  If not, try to translate
  if (! ($temp =~ /^(\.\d+)+$/)) 
  {
    # Not numeric
    # Try to convert to numerical
    if ($DEBUGGING >= 2) {
      print "Symbolic trap variable name detected ($temp).  Will attempt to translate to a numerical OID\n";
    }
    if ($net_snmp_perl_enable == 1)
    {
      my $temp3 = SNMP::translateObj("$temp",0);
      if (defined ($temp3) ) {
        if ($DEBUGGING >= 2) {
          print "  Translated to $temp3\n\n";
        }
        $temp = $temp3;
      }
      else {
        # Could not translate default to numeric
        if ($DEBUGGING >= 2) {
          print "  Could not translate - will leave as-is\n\n";
        }
      }
    }
    else
    {
      if ($DEBUGGING >= 2) {
        print "  Could not translate - Net-SNMP Perl module not enabled - will leave as-is\n\n ";
      }
    }
  }
  return $temp;
}

sub translate_log_trap_oid_sub
{	
  my $trap_oid_temp = shift;

  if ($net_snmp_perl_enable == 1 && $translate_log_trap_oid)
  {
    # Try to convert to text
    if ($DEBUGGING >= 2) {
      print "\nOID of trap: $trap_oid_temp.  Will attempt to translate to text\n";
    }
    my $temp2 = my_translateObj("$trap_oid_temp",$translate_log_trap_oid);
    if (defined ($temp2) ) {
      if ($DEBUGGING >= 2) {
          print "  Translated to $temp2\n";
      }
      $trap_oid_temp = $temp2;
    }
    else {
      # Could not translate default to numeric
      if ($DEBUGGING >= 2) {
          print "  Could not translate - defaulting to numeric\n";
      }
    }
  }
  return $trap_oid_temp;
}

sub translate_enterprise_oid_format_sub
{
  my $enterprise_oid_temp = shift;

  if ($net_snmp_perl_enable == 1 && $var[6] ne '')
  {
    if ($DEBUGGING >= 2) {
      print "\nOID of enterprise: $var[6].  Will attempt to translate to text\n";
    }
    my $temp = my_translateObj("$var[6]",$translate_enterprise_oid_format);
    if (defined ($temp) ) {
      if ($DEBUGGING >= 2) {
          print "  Translated to $temp\n";
      }
      $enterprise_oid_temp = $temp;
    }
    else 
    {
      # Could not translate default to numeric
      if ($DEBUGGING >= 2) {
          print "  Could not translate - defaulting to numeric\n";
      }
    }
  }
  return $enterprise_oid_temp;
}

# Log statistics to syslog, event log etc
sub log_statistics {

  if ($DEBUGGING >= 1)
  {
    print "Total traps received:    $g_total_traps_received\n";
    print "Total traps translated:  $g_total_traps_translated\n";                
    print "Total traps ignored:     $g_total_traps_ignored\n";                
    print "Total unknown traps:     $g_total_traps_unknown\n\n";
  }

  if ($syslog_system_enable == 1)  
  {
    syslog_system("Total traps received=$g_total_traps_received,Total traps translated=$g_total_traps_translated,Total traps ignored=$g_total_traps_ignored,Total unknown traps=$g_total_traps_unknown");                
  }
  if ($log_system_enable == 1)  
  {
    log_system("Total traps received=$g_total_traps_received,Total traps translated=$g_total_traps_translated,Total traps ignored=$g_total_traps_ignored,Total unknown traps=$g_total_traps_unknown");                
  }
  if ($eventlog_system_enable == 1)
  {
    my $message = "" . \
    "Total traps received:    $g_total_traps_received\n" . \
    "Total traps translated:  $g_total_traps_translated\n" . \
    "Total traps ignored:     $g_total_traps_ignored:\n" . \
    "Total unknown traps:     $g_total_traps_unknown\n";

    eventlog_system($message,1,$eventlog_information);
  }

  if ($mysql_dbi_enable == 1 && defined ($dbh_mysql) && $mysql_dbi_table_statistics ne "")
  {
    my $stat_time_temp;

    if ($stat_time_format_sql eq "") {
      $stat_time_temp = localtime();
    }
    else {
      $stat_time_temp = strftime $stat_time_format_sql, localtime();
    }
    
    &mysql_insert($mysql_dbi_table_statistics,
      "stat_time", $stat_time_temp,
      "total_received", $g_total_traps_received,
      "total_translated", $g_total_traps_translated,
      "total_ignored", $g_total_traps_ignored,
      "total_unknown", $g_total_traps_unknown);
  }

  if ($postgresql_dbi_enable == 1 && defined ($dbh_postgresql) && $postgresql_dbi_table_statistics ne "")
  {
    my $stat_time_temp;

    if ($stat_time_format_sql eq "") {
      $stat_time_temp = localtime();
    }
    else {
      $stat_time_temp = strftime $stat_time_format_sql, localtime();
    }

    &postgresql_insert($postgresql_dbi_table_statistics,
      "stat_time", $stat_time_temp,
      "total_received", $g_total_traps_received,
      "total_translated", $g_total_traps_translated,
      "total_ignored", $g_total_traps_ignored,
      "total_unknown", $g_total_traps_unknown);
  }

  if ($dbd_odbc_enable == 1 && defined ($dbh_odbc) && $dbd_odbc_table_statistics ne "")
  {
    my $stat_time_temp;

    if ($stat_time_format_sql eq "") {
      $stat_time_temp = localtime();
    }
    else {
      $stat_time_temp = strftime $stat_time_format_sql, localtime();
    }

    &odbc_insert($dbd_odbc_table_statistics,
      "stat_time", $stat_time_temp,
      "total_received", $g_total_traps_received,
      "total_translated", $g_total_traps_translated,
      "total_ignored", $g_total_traps_ignored,
      "total_unknown", $g_total_traps_unknown);
  }

  if ($sql_win32_odbc_enable == 1 && defined ($dbh_win32_odbc) && $sql_win32_odbc_table_statistics ne "")
  {
    my $stat_time_temp;

    if ($stat_time_format_sql eq "") {
      $stat_time_temp = localtime();
    }
    else {
      $stat_time_temp = strftime $stat_time_format_sql, localtime();
    }
    
    &sql_win32_odbc_insert($sql_win32_odbc_table_statistics,
      "stat_time", $stat_time_temp,
      "total_received", $g_total_traps_received,
      "total_translated", $g_total_traps_translated,
      "total_ignored", $g_total_traps_ignored,
      "total_unknown", $g_total_traps_unknown);
  }

  $g_last_statistics_logged = time();
}

sub signal_log_statistics {
  #
  # Statistics log
  #
  $timetologstatistics = 1;
}

# Open connections to MySQL, PostresQL, ODBC etc
sub create_db_connections {
  &dbh_mysql_connect();
  &dbh_postgresql_connect();
  &dbh_odbc_connect();
  &dbh_win32_odbc_connect();
}

sub close_db_connections {
  &dbh_mysql_close();
  &dbh_postgresql_close();
  &dbh_odbc_close();
  &dbh_win32_odbc_close();
}

sub dbh_mysql_close {
  if (defined $dbh_mysql)
  {
    $dbh_mysql->disconnect;
    $dbh_mysql = undef;
  }
}

sub dbh_postgresql_close {
  if (defined $dbh_postgresql)
  {
    $dbh_postgresql->disconnect;
    $dbh_postgresql = undef;
  }
}

sub dbh_odbc_close {
  if (defined $dbh_odbc)
  {
    $dbh_odbc->disconnect;
    $dbh_odbc = undef;
  }
}

sub dbh_win32_odbc_close {
  if (defined $dbh_win32_odbc)
  {
    $dbh_win32_odbc->Close();
    $dbh_win32_odbc = undef;
  }
}

sub dbh_mysql_connect() {
  if ($mysql_dbi_enable == 1)
  {
    &dbh_mysql_close();

    unless ($dbh_mysql = DBI->connect("DBI:mysql:database=$mysql_dbi_database;host=$mysql_dbi_host;" .
        "port=$mysql_dbi_port",$mysql_dbi_username,$mysql_dbi_password) )
    {
      my $msg = "MySQL error: Unable to connect to database: $DBI::errstr";
      warn $msg, "\n";
      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }
      if ($syslog_system_enable == 1)
      {
        syslog_system($msg);
      }
      if ($log_system_enable == 1)
      {
        log_system($msg);
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,12,$eventlog_error);
      }
    }
  }
}

sub dbh_postgresql_connect() {
  if ($postgresql_dbi_enable == 1)
  {
    my $connect_string;
    my $dbi_module;

    if ($postgresql_dbi_module == 0)
    {
      $dbi_module = "PgPP";
    }
    else
    { 
      $dbi_module = "Pg";
    }

    if ($postgresql_dbi_hostport_enable == 0)
    {
      # No network support
      $connect_string = "DBI:$dbi_module:dbname=$postgresql_dbi_database;";
    }
    else
    {
      # Network support - include host and port
      $connect_string = "DBI:$dbi_module:dbname=$postgresql_dbi_database;host=$postgresql_dbi_host;port=$postgresql_dbi_port";
    }

    &dbh_postgresql_close();

    unless ($dbh_postgresql = DBI->connect($connect_string,$postgresql_dbi_username,$postgresql_dbi_password) )
    {
      my $msg = "Postgres error: Unable to connect to database: $DBI::errstr";
      warn $msg, "\n";
      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }
      if ($syslog_system_enable == 1)
      {
        syslog_system($msg);
      }
      if ($log_system_enable == 1)
      {
        log_system($msg);
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,18,$eventlog_error);
      }
    }
  }
}

sub dbh_odbc_connect() { 
  if ($dbd_odbc_enable == 1)
  {
    &dbh_odbc_close();

    unless ($dbh_odbc = DBI->connect("DBI:ODBC:$dbd_odbc_dsn",$dbd_odbc_username,$dbd_odbc_password) )
    {
      my $msg = "DBI DBD:ODBC error: Unable to connect to DSN: $DBI::errstr";
      warn $msg, "\n";
      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }
      if ($syslog_system_enable == 1)
      {
        syslog_system($msg);
      }
      if ($log_system_enable == 1)
      {
        log_system($msg);
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,13,$eventlog_error);
      }
    }
  }
}

sub dbh_win32_odbc_connect() { 
  if ($sql_win32_odbc_enable == 1)
  {
    &dbh_win32_odbc_close();

    unless ($dbh_win32_odbc = new Win32::ODBC("DSN=$sql_win32_odbc_dsn","UID=$sql_win32_odbc_username","PWD=$sql_win32_odbc_password") )
    {
      my $msg = "SQL error: Unable to connect to DSN $sql_win32_odbc_dsn:" . Win32::ODBC::Error();
      warn $msg, "\n";
      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }     
      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,13,$eventlog_error);
      }
    }
  }
}

sub mysql_ping {
  if ($mysql_dbi_enable == 1) {
    if (defined ($dbh_mysql)) {
      my $rc = $dbh_mysql->ping;
      if ($dbh_mysql->{'errno'} != 0) {
        &dbh_mysql_connect();
      }
    }
    else {
      &dbh_mysql_connect();
    }
  }

  $g_last_mysql_ping = time();

  if ($DEBUGGING >= 2)
  {
    print "MYSQL Ping\n"
  }
}

sub postgresql_ping {
  if ($postgresql_dbi_enable == 1) {
    if (defined ($dbh_postgresql)) {
      my $rc = $dbh_postgresql->ping;
      if (!$rc) { 
        &dbh_postgresql_connect();
      }
    }
    else {
      &dbh_postgresql_connect();
    }
  }

  $g_last_postgresql_ping = time();

  if ($DEBUGGING >= 2)
  {
    print "PostgreSQL Ping\n"
  }
}

sub dbd_odbc_ping {
  if ($dbd_odbc_enable == 1) {
    if (defined ($dbh_odbc)) {
      my $rc = $dbh_odbc->ping;
      if (!$rc) { 
        &dbd_odbc_connect();
      }
    }
    else {
      &dbd_odbc_connect();
    }
  }

  $g_last_dbd_odbc_ping = time();

  if ($DEBUGGING >= 2)
  {
    print "DBD_ODBC Ping\n"
  }
}

sub mysql_insert {
  my $table = shift;
  my @data = @_;

  # If the number of elements in @data is odd, remove the last element
  # Note:  $# returns the last element # so it's reall #$data + 1
  #print "mod :" . $#data % 2 . "\n";
  if ($#data % 2 == 0) {
    pop @data;
  }
  
  #print "------------------ mysql_insert ---------------\n";
    
  my $sql_prepare = "INSERT INTO $table (";
  my @sql_execute;
  
  for (my $i = 0; $i < $#data;) {
    #print $data[$i]. "\n";
    $sql_prepare .= $data[$i];
    push (@sql_execute, $data[$i+1]);
    
    $i+=2;
        
    if ($i < ($#data)) {
      $sql_prepare .= ",";
      }
  }
  $sql_prepare .= ") VALUES (?";
  $sql_prepare .= ",?" x ($#data / 2);
  $sql_prepare .= ")";
  
  #foreach my $x (@sql_execute) {
  #  print "$x\n";
  #}
  
  #print "sql_prepare: $sql_prepare\n";
  #print "sql_execute: @sql_execute\n";

  # Make sure the connection is up
  if ($mysql_ping_on_insert == 1) {
    &mysql_ping();
  }
  
  if (defined ($dbh_mysql)) {
    my $prepare_successful = 0;
    my $do_successful = 0;

    #my $sql_statement = "INSERT INTO $mysql_dbi_table (eventname, eventid, 
    #trapoid, enterprise, community, hostname, agentip, category, severity, 
    #uptime, traptime, formatline) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";

    my $sth_mysql = $dbh_mysql->prepare($sql_prepare);
    unless (defined ($sth_mysql)) {
      my $msg = "MySQL error " . $dbh_mysql->{'errno'} . ": Unable to perform PREPARE: ".$dbh_mysql->errstr;                             
      warn $msg, "\n";

      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }

      if ($syslog_system_enable == 1)
      {
        syslog_system($msg);
      }
      if ($log_system_enable == 1)
      {
        log_system($msg);
      }
      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,23,$eventlog_error);
      }
    }
    else {
      $prepare_successful = 1;
    }

    if ($prepare_successful == 1)
    {
      unless (defined ($sth_mysql->execute(@sql_execute))) {
        my $msg = "MySQL error " . $dbh_mysql->{'errno'} . ": Unable to perform INSERT INTO (EXECUTE): ".$dbh_mysql->errstr;
        warn $msg, "\n";

        if ($DEBUGGING >= 1)
        {
          print $msg, "\n";
        }

        if ($syslog_system_enable == 1)
        {
          syslog_system($msg);
        }

        if ($log_system_enable == 1)
        {
          log_system($msg);
        }

        if ($eventlog_system_enable == 1)
        {
          eventlog_system($msg,15,$eventlog_error);
        }
      }
      else {
        $do_successful = 1;
      }
    }

    if ($do_successful == 1)  
    {
      return 1;
    }
  }
  return 0;
}

sub postgresql_insert {
  my $table = shift;
  my @data = @_;

  # If the number of elements in @data is odd, remove the last element
  # Note:  $# returns the last element # so it's reall #$data + 1
  #print "mod :" . $#data % 2 . "\n";
  if ($#data % 2 == 0) {
    pop @data;
  }
    
  #print "------------------ postgresql_insert ---------------\n";
  
  my $sql_prepare = "INSERT INTO $table (";
  my @sql_execute;
  
  for (my $i = 0; $i < $#data;) {
    $sql_prepare .= $data[$i];
    push (@sql_execute, $data[$i+1]);
    
    $i+=2;
    
    if ($i < ($#data)) {
      $sql_prepare .= ",";
      }    
  }
  $sql_prepare .= ") VALUES (?";
  $sql_prepare .= ",?" x ($#data / 2);
  $sql_prepare .= ")";
  
  #foreach my $x (@sql_execute) {
  #  print "$x\n";
  #}
  
  #print "sql_prepare: $sql_prepare\n";
  #print "sql_execute: @sql_execute\n";

  # Make sure the connection is up
  if ($postgresql_ping_on_insert == 1) {
    &postgresql_ping();
  }
  
  if (defined ($dbh_postgresql)) {
    my $prepare_successful = 0;
    my $do_successful = 0;

#            my $sql_statement = "INSERT INTO $postgresql_dbi_table (eventname, eventid, 
#            trapoid, enterprise, community, hostname, agentip, category, severity, 
#            uptime, traptime, formatline) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";

    my $sth_postgresql = $dbh_postgresql->prepare($sql_prepare);
    unless (defined ($sth_postgresql)) {
      my $msg = "Postgres error: Unable to perform PREPARE: ".$dbh_postgresql->errstr;
      warn $msg, "\n";

      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }

      if ($syslog_system_enable == 1)
      {
        syslog_system($msg);
      }

      if ($log_system_enable == 1)
      {
        log_system($msg);
      }

      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,22,$eventlog_error);
      }
    }
    else {
      $prepare_successful = 1;
    }

    if ($prepare_successful == 1)
    {
      unless (defined ($sth_postgresql->execute(@sql_execute))) {
        my $msg = "Postgres error: Unable to perform INSERT INTO (EXECUTE): ".$dbh_postgresql->errstr;
        warn $msg, "\n";

        if ($DEBUGGING >= 1)
        {
          print $msg, "\n";
        }

        if ($syslog_system_enable == 1)
        {
          syslog_system($msg);
        }
        if ($log_system_enable == 1)
        {
          log_system($msg);
        }

        if ($eventlog_system_enable == 1)
        {
          eventlog_system($msg,19,$eventlog_error);
        }
      }
      else {
        $do_successful = 1;
      }
    }

    if ($do_successful == 1)  
    {
      return 1;
    }
  }
  return 0;
}

sub odbc_insert {
  my $table = shift;
  my @data = @_;

  # If the number of elements in @data is odd, remove the last element
  # Note:  $# returns the last element # so it's reall #$data + 1
  #print "mod :" . $#data % 2 . "\n";
  if ($#data % 2 == 0) {
    pop @data;
  }
    
  #print "------------------ odbc_insert ---------------\n";
  
  my $sql_statement = "INSERT INTO $table (";
  my @sql_values;
  
  for (my $i = 0; $i < $#data;) {
    $sql_statement .= $data[$i];
    push (@sql_values, $data[$i+1]);
    
    $i+=2;
    
    if ($i < ($#data)) {
      $sql_statement .= ",";
      }    
  }
  $sql_statement .= ") VALUES (";

  for (my $i = 0; $i <= $#sql_values;) {
    $sql_statement .= "\'" . @sql_values[$i] . "\'";
    $i++;
    
    if ($i <= ($#sql_values)) {
      $sql_statement .= ",";
     }    
  }

  $sql_statement .= ")";
  
  #print "sql_statement: $sql_statement\n";
  
  # Make sure the connection is up
   if ($dbd_odbc_ping_on_insert == 1) {
    &dbd_odbc_ping();
  }
  
  if (defined ($dbh_odbc)) {
    unless (defined ($dbh_odbc->do($sql_statement)))
    {
      my $msg = warn "DBI DBD::ODBC error: Unable to perform INSERT INTO: ".$dbh_odbc->errstr;
      warn $msg, "\n";

      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }

      if ($syslog_system_enable == 1)
      {
        syslog_system($msg);
      }
      if ($log_system_enable == 1)
      {
        log_system($msg);
      }

      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,16,$eventlog_error);
      }
    }
    else
    {
      return 1;
    }
  return 0;
  }
}

sub sql_win32_odbc_insert {
  my $table = shift;
  my @data = @_;

  # If the number of elements in @data is odd, remove the last element
  # Note:  $# returns the last element # so it's reall #$data + 1
  #print "mod :" . $#data % 2 . "\n";
  if ($#data % 2 == 0) {
    pop @data;
  }
    
  #print "------------------ sql_win32_odbc_insert ---------------\n";
  
  my $sql_statement = "INSERT INTO $table (";
  my @sql_values;
  
  for (my $i = 0; $i < $#data;) {
    $sql_statement .= $data[$i];
    push (@sql_values, $data[$i+1]);
    
    $i+=2;
    
    if ($i < ($#data)) {
      $sql_statement .= ",";
      }    
  }
  $sql_statement .= ") VALUES (";

  for (my $i = 0; $i <= $#sql_values;) {
    $sql_statement .= "\'" . @sql_values[$i] . "\'";
    $i++;
    
    if ($i <= ($#sql_values)) {
      $sql_statement .= ",";
     }    
  }

  $sql_statement .= ")";
  
  #print "sql_statement: $sql_statement\n";
   
  if (defined ($dbh_win32_odbc)) {
    if (defined ($dbh_win32_odbc->Sql($sql_statement)))
    {
      my $msg = "Win32::ODBC error: Unable to perform INSERT INTO: ".Win32::ODBC::Error();
      warn $msg, "\n";

      if ($DEBUGGING >= 1)
      {
        print $msg, "\n";
      }

      if ($eventlog_system_enable == 1)
      {
        eventlog_system($msg,17,$eventlog_error);
      }
    }
    else
    {
      return 1;
    }
  return 0;
  }
}

sub reopen_debug_file {
  if ($DEBUGGING_FILE ne '')
  {
    close DEBUGFILE;
    if (open DEBUGFILE, ">>$DEBUGGING_FILE")
    {
      select DEBUGFILE;	# change default output to debug file
      $debug_file_used = 1;
      $debug_file_open_error = 0;
      print "Debug file $DEBUGGING_FILE re-opened under uid $daemon_uid\n";
      warn "Debug file $DEBUGGING_FILE re-opened under uid $daemon_uid\n";
    }
    else
    {
      warn "could not re-open debug output file ($!)";
      
      if ($syslog_system_enable == 1 && $daemon == 1)
      {
        syslog_system("Could not re-open debug output file!");
      }
      
      if ($log_system_enable == 1 && $daemon == 1)
      {
        log_system("Could not re-open debug output file!");
      }
      
      if ($eventlog_system_enable == 1 && $daemon == 1){
        eventlog_system("Could not re-open debug output file!",14,$eventlog_error);
      }
      return 0;
    }
    return 1;
  }
  return 0;
}

sub preexec_run {
      #
      # Variable substitution for PREEXEC string
      #

      if ($DEBUGGING >= 1)
      {
        print "\n\nPREEXEC line(s):\n";
      }

      if (defined ($event2[9][0])) # if PREEXEC string has been defined
      {
        print "PREEXEC defined\n";
        if (defined ($event2[9][0]))
        {
          for (my $i=0; defined($event2[9][$i]); $i++)
          {
            $_ = $event2[9][$i];

            print "Performing substitution on PREEXEC line: $_\n";
            &substitute();
            print "Done performing substitution on PREEXEC line: $_\n";

            my $command = $_;

            if ($pre_exec_enable == 1)
            {
              if ($DEBUGGING >= 1)
              {
                print "PREEXEC command: $command\n";
              }
              # Execute command

              if ($exec_escape == 1) {
                # Escape wildcard characters
                $command =~ s/\*/\\\*/g;
                $command =~ s/\?/\\\?/g;
              }
              my $result = `$command`;
              chomp $result;
              # Remove spaces before and after
              $result =~ /^\s*(.*?)\s*$/;
              $result = $1;

              if ($result eq '') {
                $result = "(no output from PREEXEC)\n";
              }
              print "        command output: $result\n";
              push (@preexec_var, $result);
            }
          }
        }
        else
        {
          if ($DEBUGGING >= 1)
          {
            print "  PREEXEC line not defined\n";
          }
        }
      }
}



