#!/usr/bin/perl
# ---------------------------------------------------------------------------- #
# program: send_trap_data.pl version 1.2                                       #
# purpose: forwards selected SNMP traps to nagios for alerting.                #
# author:  @2008 frank4dd - 20090727                                           #
#                                                                              #
# This script generates the following Nagios service names:                    #
#   check_trap_coldstart (OID: SNMPv2-MIB::coldStart)                          #
#   check_trap_winpatch  (OID: RFC1155-SMI::enterprises.2854.0.1)              #
#   check_aix_patchtrap  (OID: RFC1155-SMI::enterprises.2.0.1)                 #
# ---------------------------------------------------------------------------- #

# ---------------------------------------------------------------------------- #
# Nagios event notification format:                                            #
# Unix timestamp enclosed in [] brackets, one space, the nagios command type   #
# PROCESS_SERVICE_CHECK_RESULT, the hostname the service is registered with,   #
# the service name registered under this host, the event severity where 0 = OK,#
# 1 = warning, 2 = critical, and finally the text message to submit. Example:  #
# [1141163054] PROCESS_SERVICE_CHECK_RESULT;ml08460;check_trap_ml08460;1;text1 #
# ---------------------------------------------------------------------------- #

use strict;
use Getopt::Long;
use vars qw($PROGNAME $opt_V $opt_t $opt_h $numArgs $testlog $cmdfile $utstamp
    $trap_uptime $trap_oid $trap_ip $trap_com $trap_other $trap_source $trap_shost
            $traphost $hostname $servicename $eventstr $snmp_rocommunity
            $severity $okdelay $textdata $DEBUG);

# ---------------------------------------------------------------------------- #
# Global variables to be adjusted as needed                                    #
# ---------------------------------------------------------------------------- #

use lib "/srv/app/nagios/libexec";            # the path to nagios utils.pm

$cmdfile="/srv/app/nagios/var/rw/nagios.cmd"; # the named pipe to submit events
$snmp_rocommunity="SECro";                     # SNMP read community
$okdelay=30;                                   # delay to auto-OK the service

$DEBUG=1;                                      # if =1, write output to testlog
$testlog="/tmp/test5";                         # debug file to write a copy into

# ---------------------------------------------------------------------------- #
# No changes needed below this line                                            #
# ---------------------------------------------------------------------------- #

use utils qw(%ERRORS &print_revision &support &usage);

$PROGNAME = "send_trap_data.pl";
$ENV{'PATH'}='';
$ENV{'BASH_ENV'}='';
$ENV{'ENV'}='';

sub print_help();
sub print_usage();
sub ip_to_name();

Getopt::Long::Configure('bundling');
GetOptions ("V"   => \$opt_V, "version"    => \$opt_V,
            "h"   => \$opt_h, "help"       => \$opt_h );

if($opt_V) {
        print_revision($PROGNAME,'$Revision: 120 $');
        exit $ERRORS{'OK'};
}
if($opt_h) {print_help(); exit $ERRORS{'OK'};}

# ---------------------------------------------------------------------------- #
# main program starts here                                                     #
# ---------------------------------------------------------------------------- #
$hostname="unset";               # hostname initial value
$eventstr="empty";               # cmdstring initial value
$severity="0";                   # severity initial value


if($DEBUG == 1) { open(TMPFILE, ">$testlog"); }
if($DEBUG == 1) { print TMPFILE "Start parsing trap.\n"; }

# ---------------------------------------------------------------------------- #
# 1. parse the received SNMP trap data                                         #
# ---------------------------------------------------------------------------- #
&parse_snmptrap;

# ---------------------------------------------------------------------------- #
# 2. Traps from localhost are not handled yet, if used we need to set hostname #
# ---------------------------------------------------------------------------- #
if($trap_ip =~ /127\.0\.0\.1/) {
  if($DEBUG == 1) { print TMPFILE "Received trap from localhost, no submission to nagios, exit.\n"; }
  exit;
}

# ---------------------------------------------------------------------------- #
# 2. create the timestamp string, i.e. [1141163054]                            #
# ---------------------------------------------------------------------------- #
$utstamp=`/bin/date +[%s]`;      # the event timestamp
chomp($utstamp);                 # remove newline from timestamp
if($DEBUG == 1) { print TMPFILE "timestamp>$utstamp<\n"; }

# ---------------------------------------------------------------------------- #
# 3. we check what type of trap event we got                                   #
# ---------------------------------------------------------------------------- #

# ---------------------------------------------------------------------------- #
# 3.1: SNMP coldstart traps indicate a server reboot happened                  #
# ---------------------------------------------------------------------------- #
if($trap_oid =~ /SNMPv2-MIB::coldStart/) {
# We got a "reboot" message from a SNMP daemon, we gonna use ip_to_name() to
# get the hostname, we set the severity to WARNING and we 'auto-OK' the event.
  &ip_to_name();
  $servicename="check_trap_coldstart";
  $eventstr="$utstamp PROCESS_SERVICE_CHECK_RESULT;$hostname;$servicename;";
  $severity = 1;
  $textdata="System *reboot* or SNMP service restarted.";
  $eventstr=$eventstr.$severity.";".$textdata;

  # finally submit the data to nagios
  if($DEBUG == 1) { print TMPFILE "eventstr >$eventstr<\n"; }
  unless(open(CMDFILE, "+<$cmdfile")) {
    if($DEBUG == 1) { print TMPFILE "Could not open $cmdfile\n"; }
  }
  print CMDFILE $eventstr;
  if($DEBUG == 1) { close TMPFILE; }
  close CMDFILE;

  sleep $okdelay;
  if($DEBUG == 1) { open(TMPFILE, ">>$testlog"); }
  unless(open(CMDFILE, "+<$cmdfile")) {
    if($DEBUG == 1) { print TMPFILE "Could not open $cmdfile\n"; }
  }
  # we rebuild the notification with severity 0 and add auto-OK
  $eventstr="$utstamp PROCESS_SERVICE_CHECK_RESULT;$hostname;$servicename;";
  $severity = 0;
  $textdata="System *reboot* or SNMP service restarted. auto-OK.";
  $eventstr=$eventstr.$severity.";".$textdata;

  # second submission to nagios
  if($DEBUG == 1) { print TMPFILE "eventstr >$eventstr<\n"; }
  print CMDFILE $eventstr;

  if($DEBUG == 1) { close TMPFILE; }
  close CMDFILE;
  exit;
}

# ---------------------------------------------------------------------------- #
# 3.2: SNMP enterprises.2854 are 'trapgen.exe' generated messages on Windows   #
# to check the patch status. We could receive either RFC1155-SMI or SNMPv2-MIB #
# ---------------------------------------------------------------------------- #
if($trap_oid =~ /::enterprises\.2854\.0\.1/) {
  $servicename="check_trap_winpatch";
  # remove double quotes from our trapgen string
  $trap_other =~ s/"//g;
  ($hostname) = split(":", $trap_other);
  $hostname = lc($hostname);
  if($DEBUG == 1) { print TMPFILE "hostname >$hostname<\n"; }
  $trap_other =~ s/[^:]+: //;
  if($DEBUG == 1) { print TMPFILE "trap_other >$trap_other<\n"; }
  ($severity) = split(":", $trap_other);
  if($severity eq "OK") { $severity = 0; }
  if($severity eq "WARNING") { $severity = 1; }
  if($DEBUG == 1) { print TMPFILE "severity >$severity<\n"; }
  $trap_other =~ s/[^:]+: //;
  $eventstr="$utstamp PROCESS_SERVICE_CHECK_RESULT;$hostname;$servicename;";
  $eventstr=$eventstr.$severity.";".$trap_other;

  # finally submit the data to nagios
  if($DEBUG == 1) { print TMPFILE "eventstr >$eventstr<\n"; }
  unless(open(CMDFILE, "+<$cmdfile")) {
    if($DEBUG == 1) { print TMPFILE "Could not open $cmdfile\n"; }
  }
  print CMDFILE $eventstr;
  if($DEBUG == 1) { close TMPFILE; }
  close CMDFILE;
  exit;
}

# ---------------------------------------------------------------------------- #
# 3.3: SNMP enterprises.2.6.191.1.6.1 are 'snmptrap' generated messages sent   #
# from AIX systems to report the patch status.                                 #
#trapline ><UNKNOWN><                                                          #
#trapline >UDP: [192.168.251.40]:32895->[192.168.103.32]<                      #
#trapline >DISMAN-EVENT-MIB::sysUpTimeInstance 18:5:41:08.00<                  #
#trapline >SNMPv2-MIB::snmpTrapOID.0 RFC1155-SMI::enterprises.2.0.1<           #
#trapline >RFC1155-SMI::enterprises.2.6.191.1.6.1 "test56789 "<                #
#trapline >SNMP-COMMUNITY-MIB::snmpTrapAddress.0 192.168.103.32<               #
#trapline >SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 "NBNtrap"<                  #
# ---------------------------------------------------------------------------- #
if($trap_oid =~ /RFC1155-SMI::enterprises\.2\.0\.1/) {
  $servicename="check_aix_patchtrap";
  
  # in the case of the AIX snmptrap command, trap_ip is not correctly sent.
  # we will cut it out from the initial protol string
  my @fields=split('^\[',$trap_source);
  @fields=split('\]', $fields[1]);
  $trap_ip=$fields[0];
  if($DEBUG == 1) { print TMPFILE "trap_ip >$trap_ip<\n"; }
  &ip_to_name();

  # remove the enclosing doublequotes from the transmitted string (AIX issue)
  $trap_other =~ s/ "$//;
  $trap_other =~ s/^"//;
  if($DEBUG == 1) { print TMPFILE "trap_other >$trap_other<\n"; }
  ($severity) = split(" ", $trap_other);
  if($severity eq "OK") { $severity = 0; }
  if($severity eq "WARNING") { $severity = 1; }
  if($DEBUG == 1) { print TMPFILE "severity >$severity<\n"; }
  $eventstr="$utstamp PROCESS_SERVICE_CHECK_RESULT;$hostname;$servicename;";
  $eventstr=$eventstr.$severity.";".$trap_other;

  # finally submit the data to nagios
  if($DEBUG == 1) { print TMPFILE "eventstr >$eventstr<\n"; }
  unless(open(CMDFILE, "+<$cmdfile")) {
    if($DEBUG == 1) { print TMPFILE "Could not open $cmdfile\n"; }
  }
  print CMDFILE $eventstr;
  if($DEBUG == 1) { close TMPFILE; }
  close CMDFILE;
  exit;
}

if($DEBUG == 1) { close TMPFILE; }
exit;
# ---------------------------------------------------------------------------- #
# End of main(), subroutine definitions below.                                 #
# ---------------------------------------------------------------------------- #


sub print_usage() {
        print "Usage: $PROGNAME host ip trapdata";
}

sub print_help() {
        print_revision($PROGNAME,'$Revision: 110 $');
        print "Copyright (c) 2008 Frank4DD

This program translates SNMP traps into a Nagios external command.

";
}

# ---------------------------------------------------------------------------- #
# we always receive 7 lines from snmptrapd                                     #
# 1. trapsource DNS or IP: i.e. "192.168.98.128\n"                             #
# 2. trapsource protocol, IP and port: "UDP: [192.168.98.128]:1847\n"          #
# 3. remote uptime: i.e. "DISMAN-EVENT-MIB::sysUpTimeInstance 27:22:18:01.00\n"#
# 4. OID: i.e. "SNMPv2-MIB::snmpTrapOID.0 SNMPv2-SMI::enterprises.2854.0.1"    #
# 5. other trap data: SNMPv2-MIB::snmpTrapOID.0, or                            #
# SNMPv2-SMI::enterprises.2854 "JPNHOAP016: OK: No critical updates..."        #
# 6. trap address: i.e. "SNMP-COMMUNITY-MIB::snmpTrapAddress.0 192.168.98.128" #
# 7. trap community: SNMP-COMMUNITY-MIB::snmpTrapCommunity.0                   #
# Sometimes, a 8. trapline is generated as shown below:                        #
# 8. "SNMPv2-MIB::snmpTrapEnterprise.0 SNMPv2-SMI::enterprises.2854"           #
# not necessarily in this order, so we need to loop and parse.                 #
# ---------------------------------------------------------------------------- #
sub parse_snmptrap() {
  my @lines = <STDIN>;                 # pick up snmptrapd data
  my $line = "";
  my $prefix = "";

  foreach $line (@lines) {
    chomp($line);
    if($DEBUG == 1) { print TMPFILE "trapline >$line<\n" }
    if($line =~ /^DISMAN-EVENT-MIB/) { ($prefix, $trap_uptime) = split(" ", $line); next; }
    if($line =~ /^SNMPv2-MIB::snmpTrapOID/) { ($prefix, $trap_oid) = split(" ", $line); next; }
    if($line =~ /^SNMP-COMMUNITY-MIB::snmpTrapAddress/) { ($prefix, $trap_ip) = split(" ", $line); next; }
    if($line =~ /^SNMP-COMMUNITY-MIB::snmpTrapCommunity/) { ($prefix, $trap_com) = split(" ", $line); next; }
    if($line =~ /^SNMPv2-MIB::snmpTrapEnterprise\.0/) { next; }
    if($line =~ /::/) { $trap_other = $line; $trap_other =~ s/[^ ]+ //; next; }
    if($line =~ /^UDP:/) { ($prefix, $trap_source) = split(" ", $line); next; }
    else { $trap_shost = $line; }
  }
}

# ---------------------------------------------------------------------------- #
# 1. determine the hostname, must match the Nagios service host name           #
# If the trap origin is either 127.0.0.1 or localhost, we disregard it.        #
# SNMPtrapd's first line is either a DNS name or a IP. The second line always  #
# has the IP together with the source port. Our DNS is highly unreliable, not  #
# always matching the real hostname. I am determining the name in 2 ways:      #
# If we generate the SNMP trap with the Windows tool TrapGen.exe, we place the #
# system name into the trap string. For these trap OID's, we parse the string  # 
# data where the hostname is coded into, to return the hostname for Nagios.    #
# For traps generated by a SNMP daemon, we use ip_to_name() to determine the   #
# hostname by a SNMP reverse check, using the source IP received by the SNMP   #
# trap daemon.                                                                 #
# ---------------------------------------------------------------------------- #

# ---------------------------------------------------------------------------- #
# ip_to_name() converts $trap_ip IP address to a hostname by making a snmpget  #
# request back to the trap sender. We assume the trap sender has SNMP enabled  #
# and responds to the SNMPv2-MIB::sysName.0 request with its correct hostname  #
# (in uppercase). We also assume a common SNMP community name for all systems. #
# This routine could also do a /etc/hosts or DNS lookup instead or in addition.#
# ---------------------------------------------------------------------------- #
sub ip_to_name() {
  my $snmp_sysname="";
  my @fields;

  $snmp_sysname=`/usr/bin/snmpget -c $snmp_rocommunity -v 1 -r 1 -t 1 $trap_ip SNMPv2-MIB::sysName.0`;
# ---------------------------------------------------------------------------- #
# snmpget -c SECro -v 1 -r1 -t 1 $trapip SNMPv2-MIB::sysName.0                 #
# returns: SNMPv2-MIB::sysName.0 = STRING: MLP09325                            #
# or:      Timeout: No Response from 192.168.203.145.                          #
# (if no SNMP is running, wrong community set, etc)                            #
# ---------------------------------------------------------------------------- #
  chomp($snmp_sysname);

  if($DEBUG == 1) { print TMPFILE "snmpname >$snmp_sysname<\n"; }

  if($snmp_sysname =~ /::sysName\.0/) {
    @fields=split(" = ", $snmp_sysname);
    @fields=split(": ", $fields[1]);
    $hostname = lc($fields[1]);
    $hostname =~ s/"//g;
    if($DEBUG == 1) { print TMPFILE "hostname >$hostname<\n"; }
  }
}
