#!/usr/bin/perl
#
# SNMPTTHANDLER-EMBEDDED 1.5beta2
#
# Copyright 2009-2021 Alex Burger
# alex_b@users.sourceforge.net
# 5/17/2009
#
# 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 an embedded snmptrapd trap handler for use with the NET-SNMP 
# / UCD-SNMP and SNMPTT.  ** It can only be run directly from snmptrapd.  **
#
# The script is called by defining a perl handler snmptrapd.conf.  
# For example:
#
# perl do "/sbin/snmptthandler-embedded";
# 
# SNMPTTHANDLER-EMBEDDED dumps the received traps into a directory to be 
# processed by the SNMPTT daemon.
# 
##############################################################################
use strict;
#use warnings;
use Getopt::Long;
use Time::HiRes qw(gettimeofday);
use Config::IniFiles;
use File::Copy;

my $debug = '';         # Override debug level setting in snmptt.ini
                        # Set to '' to use the snmptt.ini setting or set to 1 to enable.

my $ini = '';           # Override snmptt.ini file location.
                        # Set to '' to use the default locations of /etc/snmptt/snmptt.ini
                        # /etc/snmp/snmptt.ini and /etc/snmptt.ini for Unix and 
                        # %SYSTEMROOT%\snmptt.ini for Windows.

##############################################################################
# Don't modify anything below this line
#
my $DEBUGGING;
my $DEBUGGING_FILE;
my $configfile;
my $spool_directory;
my $locking_method = 0;         # 0 = flock, 1 = rename

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

eval 'require Fcntl;';
if ($@) {
  $locking_method = 1;
  warn $@;
  print "\nFile locking not available.  Rename method will be used for spool file.\n";
}
else {
  require Fcntl;
}

&loadsnmpttini;         # Load snmptt.ini

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

# Register the trap receiver
NetSNMP::TrapReceiver::register("all", \&snmptt_trap_receiver) ||
warn "failed to register our perl trap handler\n";

print STDERR "Loaded the SNMPTT embedded snmptrapd handler\n";

##############################################################################
# Trap receiver.
sub snmptt_trap_receiver {
  my $pdu_info = $_[0];
  my $varbinds = $_[1];

  my $hostname = $$pdu_info{"receivedfrom"} || "unknown";
  if ($hostname ne "unknown") {
    $hostname =~ /\[(.*?)\].*/;                    # UDP: [127.0.0.1]:41070->[127.0.0.1]
    $hostname = $1 || "unknown";
  }

  my $ip_address = $hostname;

  my $community = $$pdu_info{"community"} || "unknown";

  # contextEngineID in hash = snmpCommunityContextEngineID = .1.3.6.1.6.3.18.1.1.1.4
  my $contextEngineID = $$pdu_info{"contextEngineID"} || "unknown";
  $contextEngineID = unpack('h*', $contextEngineID);

  # contextName in hash = snmpCommunityContextName = .1.3.6.1.6.3.18.1.1.1.5 
  my $contextName = $$pdu_info{"contextName"} || "unknown";

  # snmplib/snmp_api.c:
  # static oid      snmpEngineIDoid[]   = { 1,3,6,1,6,3,10,2,1,1,0};
  # securityEngineID in hash = snmpEngineID = .1.3.6.1.6.3.10.2.1.1.0
  my $securityEngineID = $$pdu_info{"securityEngineID"} || "unknown";
  $securityEngineID = unpack('h*', $securityEngineID);

  # securityName in hash = snmpCommunitySecurityName = .1.3.6.1.6.3.18.1.1.1.3 
  my $securityName = $$pdu_info{"securityName"} || "unknown";
  
  # SNMPTT expects the 3rd item to be the uptime and the 4th to be the trapname
  my $uptime;
  my $trapname;
  my $other_varbinds;

  # Cycle through all the varbinds  
  foreach my $x (@$varbinds) {
    if ($DEBUGGING >= 1) {
      printf "  %-30s type=%-2d value=%s\n", $x->[0], $x->[2], $x->[1];
    }

    my $oid = "$x->[0]";
    my $value = "$x->[1]";
    if ($] >= 5.010000) {   # Needs 5.10 or higher
      $value =~ s/\R//g;    # Unicode line endings including newlines
    }
    $value =~ s/^STRING: //g;
    $value =~ s/^INTEGER: //g;
    $value =~ s/^OID: //g;
    $value =~ s/^IpAddress: //g;
    $value =~ s/^Timeticks: //g;
    $value =~ s/^Gauge32: //g;
    $value =~ s/^Hex-STRING: //g;

    if ($oid eq ".1.3.6.1.2.1.1.3.0") {
#      my $temp = $value;
#      $temp =~ /Timeticks: \(.*?\) (.*)/;
#      $uptime = $oid . " " . $1;
      $uptime = $oid . " " . $value;
    }
    elsif ($oid eq ".1.3.6.1.6.3.1.1.4.1.0") {
#      my $temp = $value;
#      $temp =~ /OID: (.*)/;
#      $trapname = $oid . " " . $1;
      $trapname = $oid . " " . $value;
    }
    else {
      $other_varbinds .= $oid . " " . $value . "\n";
    }
  }

  ##############################################################################
  # Create file in spool directory based on current time
  my ($s, $usec) = gettimeofday;
  
  # Pad the numbers with 0's to make sure they are all the same length.  Sometimes the
  # usec is shorter than 6.
  my $s_pad = sprintf("%09d",$s);
  my $usec_pad = sprintf("%06d",$usec);
  
  my $fh_DEBUGFILE;
  if ($DEBUGGING >= 1)
  {
    if ($DEBUGGING_FILE ne '') 
    {
      open ($fh_DEBUGFILE, ">>$DEBUGGING_FILE")
        or warn "Could not open debug output file ($!)";
      
      select $fh_DEBUGFILE;	# Change default output to debug file
    }
    
    # Print out time
    print "-------------------------------------------------------------\n";
    print "SNMPTTHANDLER-EMBEDDED started: ",scalar(localtime),"\n\n";
    print "s = $s, usec = $usec\n";
    print "s_pad = $s_pad, usec_pad = $usec_pad\n\n";
    print "Data received:\n\n";
  }
  
  my $spoolfile_final = $spool_directory.'#snmptt-trap-'.$s_pad.$usec_pad;
  my $spoolfile = $spoolfile_final;

  if ($locking_method) {
    # Rename
    $spoolfile = $spool_directory.'x#snmptt-trap-'.$s_pad.$usec_pad;
  }
  
  my $fh_SPOOL;
  unless (open $fh_SPOOL, ">$spoolfile")
  {
    if ($DEBUGGING >= 1)
    {
      print "Could not write to file file $spoolfile!  Trap will be lost!\n";
    }
    die "Could not write to file $spoolfile!  Trap will be lost!\n";
  } 

  if (! $locking_method) {
    flock $fh_SPOOL, 2 | 4;           # LOCK_EX | LOCK_NB
  }

  print $fh_SPOOL time()."\n";
  print $fh_SPOOL $hostname . "\n";
  print $fh_SPOOL $ip_address . "\n";
  print $fh_SPOOL $uptime . "\n";
  print $fh_SPOOL $trapname . "\n";
  print $fh_SPOOL ".1.3.6.1.6.3.18.1.4.0 $community\n";
  print $fh_SPOOL $other_varbinds;
  if ($securityEngineID ne "") {
    print $fh_SPOOL ".1.3.6.1.6.3.10.2.1.1.0 \"0x$securityEngineID\"\n";
  }
  if ($securityName ne "") {
    print $fh_SPOOL ".1.3.6.1.6.3.18.1.1.1.3 \"$securityName\"\n";
  }
  if ($contextEngineID ne "") {
    print $fh_SPOOL ".1.3.6.1.6.3.18.1.1.1.4 \"0x$contextEngineID\"\n";
  }
  if ($contextName ne "") {
    print $fh_SPOOL ".1.3.6.1.6.3.18.1.1.1.5 \"$contextName\"\n";
  }

  if ($DEBUGGING >= 1) {
    print time()."\n";
    print $hostname . "\n";
    print $ip_address . "\n";
    print $uptime . "\n";
    print $trapname . "\n";
    print ".1.3.6.1.6.3.18.1.4.0 $community\n";
    print $other_varbinds;
    if ($securityEngineID ne "") {
      print ".1.3.6.1.6.3.10.2.1.1.0 \"0x$securityEngineID\"\n";
    }
    if ($securityName ne "") {
      print ".1.3.6.1.6.3.18.1.1.1.3 \"$securityName\"\n";
    }
    if ($contextEngineID ne "") {
      print ".1.3.6.1.6.3.18.1.1.1.4 \"0x$contextEngineID\"\n";
    }
    if ($contextName ne "") {
      print ".1.3.6.1.6.3.18.1.1.1.5 \"$contextName\"\n";
    }

    # Flush the buffers for debugging.
    $| = 1;
    $| = 0;
  }

  close $fh_SPOOL;

  if ($locking_method) {
    # Rename
    move($spoolfile, $spoolfile_final);
  }

  return NETSNMPTRAPD_HANDLER_OK;
}

sub loadsnmpttini {
  ##############################################################################
  # Load snmptt.ini
  #
  # For Linux / Unix, try /etc/snmp/snmptt.ini first, /etc/snmptt.ini second.
  #
  # For Windows, try %SystemRoot%\snmptt.ini only.
  #

  my $cfg;

  if ($ini ne '') {
    $configfile = $ini;
  }
  else {	
    if ($^O ne "MSWin32") {
      if (-f '/etc/snmptt/snmptt.ini' && -r '/etc/snmptt/snmptt.ini') {
        $configfile = '/etc/snmptt/snmptt.ini';    
      }
      elsif (-f '/etc/snmp/snmptt.ini' && -r '/etc/snmp/snmptt.ini') {
        $configfile = '/etc/snmp/snmptt.ini';    
      }
      elsif (-f '/etc/snmptt.ini' && -r '/etc/snmptt.ini') {
        $configfile = '/etc/snmptt.ini';    
      }
      elsif (-f '/usr/local/etc/snmp/snmptt.ini' && -r '/usr/local/etc/snmp/snmptt.ini') {
        $configfile = '/usr/local/etc/snmp/snmptt.ini';    
      }
      elsif (-f '/usr/local/etc/snmptt.ini' && -r '/usr/local/etc/snmptt.ini') {
        $configfile = '/usr/local/etc/snmptt.ini';    
      }
      else {
        if ($DEBUGGING >= 1) {
          print "Could not load snmptt.ini from the default file locations (/etc/snmp/ or /etc/).\n";
        }
        warn "Could not load snmptt.ini from the default file locations (/etc/snmp/ or /etc/).\n";
        exit(1);
      }
    }
    else {
      $configfile = $ENV{'SystemRoot'}."\\snmptt.ini";
    }
  }

  my $fh_CONFIG;
  if( open( $fh_CONFIG, $configfile ) ) {
    close $fh_CONFIG;
    $cfg = new Config::IniFiles( -file => $configfile);
  }
  else
  {
    if ($DEBUGGING >= 1) {
    	print "Config file ($configfile) could not be loaded\n";
    }
    warn "Config file ($configfile) could not be loaded\n";
    exit(1);
  }
  
  if (! $cfg)
  {
    if ($DEBUGGING >= 1) {
  	  print "Error in config file - please check the syntax in the config file\n";
    }
    exit(1);
  }
  
  # DaemonMode
  $spool_directory = $cfg->val('DaemonMode', 'spool_directory');
  
  # Debugging
  if ($debug eq '') {
    $DEBUGGING = $cfg->val('Debugging', 'DEBUGGING');
  }
        $DEBUGGING_FILE = $cfg->val('Debugging', 'DEBUGGING_FILE_HANDLER');
  
  $cfg->Delete;
  
  # Defaults Start
  if (! defined ($spool_directory)) { $spool_directory = ''} ;
  if (! defined ($DEBUGGING)) { $DEBUGGING = 0} ;
  if (! defined ($DEBUGGING_FILE)) { $DEBUGGING_FILE = ''} ;
  # Defaults End
}

