#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use Sys::Syslog;
use xCAT::Table;
use xCAT::Utils;
use xCAT::TableUtils;
use xCAT::NetworkUtils;
use xCAT_plugin::ipmi;
use xCAT_monitoring::monitorctrl;
use Socket;
use Data::Dumper;

#use strict;


#-------------------------------------------------------------------------------

=head1  xcat_traphandler
=head2   Description
  Script for SNMP trap handling.

=cut

#-------------------------------------------------------------------------------
# admin needs to create a mail aliase called alerts
# put "alerts: emailadd,emailaddr.." to /etc/aliases file
# then run newaliases command
my $MAILTO = "alerts";

my $message;
my $briefmsg;
my $node1;
my $info;
my $severity_type;
my $severity;
my $appname;
my $id;
my $message1;
my $errsrc;

#get settings
my $EMAIL    = 0;
my $LOG      = 0;
my $IGNORE   = 0;
my $DB       = 0;
my %hashI    = ();
my %hashE    = ();
my %hashL    = ();
my %hashR    = ();
my %hashD    = ();
my %hashRUN  = ();
my %settings = xCAT_monitoring::monitorctrl->getPluginSettings("snmpmon");
my $i        = $settings{'ignore'};
if ($i) { %hashI = parseSettings($i); }
my $e = $settings{'email'};
if ($e) { %hashE = parseSettings($e); }
my $l = $settings{'log'};
if ($l) { %hashL = parseSettings($l); }
my $d = $settings{'db'};
if ($d) { %hashD = parseSettings($d); }

foreach my $k (keys %settings) {
    if ($k =~ /runcmd(\d*)/) {
        my $r        = $settings{$k};
        my %hashTemp = parseSettings($r);
        $hashR{$k}   = \%hashTemp;
        $hashRUN{$k} = 0;
    }
}

# read host name
my $host = <STDIN>;
chomp($host);

# read the host ip
my $ip = <STDIN>;
chomp($ip);

# read uptime
my $uptimeline = <STDIN>;
chomp($uptimeline);
my @a     = split(/ /, $uptimeline);
my $oid   = shift @a;
my $value = join(' ', @a);
$message .= "    $oid=$value\n";

# read trapid
my $trapidline = <STDIN>;
chomp($trapidline);
@a     = split(/ /, $trapidline);
$oid   = shift @a;
$value = join(' ', @a);
$message .= "    $oid=$value\n";
checkWithOid($oid, $value);

#print "I=$IGNORE,E=$EMAIL, L=$LOG, R=$RUNCMD D=$DB\n";
if ($IGNORE) { exit 0; }

#for ipmi traps, the values is: SNMPv2-SMI::enterprises.3183.1.1.0.x or
# RFC1155-SMI::enterprises.3183.1.1.0.x, where x is the ipmi trap id.
my $trapid;
if ($value =~ /enterprises\.3183\.1\.1\.0\.(.*)/) { $trapid = $1; }

#print "trapid=$trapid\n";



my $holder;
my $begin = 1;
my $pair;
my $temp;
while ($temp = <STDIN>) {
    chomp($temp);
    my $temp1 = $temp;    #save the one with quotes
    $temp1 =~ s/\"//g;
    my $c1 = length($temp);
    my $c2 = length($temp1);


    if (($c1 - $c2) % 2 == 0) {
        if ($begin == 1) {    # single line
            $pair = $temp;
        }
        else {                # middle multi-line
            $pair .= " $temp";
            next;
        }
    } else {    # multi line
        if ($begin == 1) {
            $pair  = $temp;    #start of multi-line
            $begin = 0;
            next;
        } else {
            $pair .= " $temp";    #end of multi-line
            $begin = 1;
        }
    }

    @a     = split(/ /, $pair);
    $oid   = shift @a;
    $value = join(' ', @a);
    $value =~ s/^"//;
    $value =~ s/"$//;

    #for BladeCenter MM traps and RSA II traps,  creat a brief message
    if ($oid =~ /BLADESPPALT-MIB::spTrapAppId|RSASPPALT-MIB::ibmSpTrapAppId/) {
        $briefmsg .= "  App ID: $value\n";
        $appname = $value;
    }
    elsif ($oid =~ /BLADESPPALT-MIB::spTrapAppType|RSASPPALT-MIB::ibmSpTrapAppType/) {
        $briefmsg .= "  App Alert Type: $value\n";
        $id = $value;
    }
    elsif ($oid =~ /BLADESPPALT-MIB::spTrapMsgText|RSASPPALT-MIB::ibmSpTrapMsgText/) {
        $briefmsg .= "  Message: $value\n";
        $message1 = $value;
    }
    elsif ($oid =~ /BLADESPPALT-MIB::spTrapBladeName/) {
        my $temp = "$value";
        $temp =~ /^\"(.*)\"/;
        if ($1) {
            $briefmsg .= "  Blade Name: $value\n";
            $node1 = $1;
        }
    }
    elsif (($oid =~ /BLADESPPALT-MIB::spTrapSourceId/)) {
        $briefmsg .= "  Error Source=$value\n";
        $errsrc = $value;
    }
    elsif ($oid =~ /BLADESPPALT-MIB::spTrapPriority|RSASPPALT-MIB::ibmSpTrapPriority/) {

        # Critical Alert(0), Major(1), Non-Critical Alert(2), System Alert(4),
        # Recovery Alert(8), Informational Only Alert(255)
        if ($value == 0) {
            $severity      = "Critical Alert";
            $severity_type = "Critical";
        } elsif ($value == 1) {
            $severity      = "Major Alert";
            $severity_type = "Critical";
        } elsif ($value == 2) {
            $severity      = "Non-Critical Alert";
            $severity_type = "Warning";
        } elsif ($value == 4) {
            $severity      = "System Alert";
            $severity_type = "Warning";
        } elsif ($value == 8) {
            $severity      = "Recovery Alert";
            $severity_type = "Informational";
        } elsif ($value == 255) {
            $severity      = "Informational Alert";
            $severity_type = "Informational";
        }
    }
    elsif ($oid =~ /enterprises\.3183\.1\.1\.1/) {    #IPMI PRTs (traps)
        $node1 = $host;

        #$node1 =~ s/(-(eth|man)\d+)?(\..*)?$//;

        my $ip1 = $ip;
        $ip1 =~ /(\d+\.\d+\.\d+\.\d+)/;
        $ip1 = $1;

        # get the host name if it is unknown
        if ($node1 =~ /<UNKNOWN>/) {
            my $name = xCAT::NetworkUtils->gethostname($ip1);
            if ($name) {
                $node1 = $name;
                $host  = $name;
                my @shorthost = split(/\./, $node1);
                $node1 = $shorthost[0];
            }
        }

        #print "node1=$node1\n";
        #node1 is the bmc name, we need the node that bmc connects to to call xCAT
        my $realnode;
        my $ipmitab = xCAT::Table->new('ipmi');
        if (defined($ipmitab)) {
            my @tmp1 = $ipmitab->getAllNodeAttribs([ 'node', 'bmc' ]);
            if (@tmp1 && (@tmp1 > 0)) {
                foreach (@tmp1) {
                    if ($_->{bmc} eq $node1) { $realnode = $_->{node}; last; }
                }
            }
            $ipmitab->close();
        }
        if ($realnode) { $node1 = $realnode; }

        #print "node1=$node1\n";


        # make the vlaue into a hex array for decoding
        $value =~ s/\"//g;
        my @varray = split(/\s+/, $value);

        #print "varray=@varray\n";
        foreach (@varray) { $_ = hex($_); }
        my $error = xCAT_plugin::ipmi->decodealert($trapid, $node1, @varray);
        $briefmsg .= $error;
        $message  .= "    decodedipmialert=$error\n";

        #severity value from decodealert are:
        #LOG,INFORMATION,OK,CRITICAL,NON-RECOVERABLE,MONITOR and UNKNOWN-SEVERITY
        $severity_type = "Warning";
        if ($error) {
            my @tempArray = split(/\:/, $error);
            $severity = $tempArray[0];
            if ($severity eq "LOG") {    #in fact this is called "unspecifiled"
                $severity_type = "Warning";
            }
            elsif ($severity eq "INFORMATION") {
                $severity_type = "Informational";
            }
            elsif ($severity eq "OK") {
                $severity_type = "Informational";
            }
            elsif ($severity eq "CRITICAL") {
                $severity_type = "Critical";
            }
            elsif ($severity eq "NON-RECOVERABLE") {
                $severity_type = "Critical";
            }
        }
    }


    $message .= "    $oid=$value\n";

    #check agains the settings
    $value =~ s/\"//g;
    checkWithOid($oid, $value);

    #print "I=$IGNORE,E=$EMAIL, L=$LOG, R=$RUNCMD, D=$DB\n";
    if ($IGNORE) { exit 0; }
}

if (!$severity_type) { $severity_type = "Warning"; }
if ((!$IGNORE) && exists($hashI{$severity_type})) {

    #print "ignore, exit\n";
    exit 0;
}
if ((!$EMAIL) && exists($hashE{$severity_type})) { $EMAIL = 1; }
if ((!$LOG)   && exists($hashL{$severity_type})) { $LOG   = 1; }
if ((!$DB)    && exists($hashD{$severity_type})) { $DB    = 1; }
foreach my $k (keys %hashRUN) {
    if (($hashRUN{$k} == 0) && exists($hashR{$k}->{$severity_type})) { $hashRUN{$k} = 1; }
}

#print "I=$IGNORE,E=$EMAIL, L=$LOG, R=$RUNCMD, D=$DB\n";

#email 'alerts' aliase
if ($EMAIL || ((keys(%hashE) == 0) && ($severity_type =~ /Critical|Warning/))) {

    #($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
    #$datetime=sprintf "%2d-%02d-%04d %02d:%02d:%02d", $mon+1,$mday,$year+1900,$hour,$min,$sec;
    #my $head="SNMP $severity received from $host($ip) on $datetime\n$briefmsg\n";
    my $head = "SNMP $severity received from $host($ip)\n$briefmsg\n";
    if ($node1) { $info = getMoreInfo($node1); }
    my $middle = "Trap details:\n$message\n";

    #email the full message to the alerts aliase
    my $cmd = "echo \'$info$head\n$middle\' \| mail -s \"$severity_type: Cluster SNMP Alert\!\" $MAILTO";
    $ENV{'XCATCFG'} = "";
    `$cmd`;
}

#log to syslog if needed. default is log all
$ENV{'XCATCFG'} = "";
if ($LOG || (keys(%hashL) == 0)) {
    my $head = "SNMP $severity received from $host($ip).";
    my $body;
    if   ($briefmsg) { $body = $briefmsg; }
    else             { $body = $message; }

    openlog("xcat", "", "local4");
    if ($severity_type eq "Informational") {
        syslog("local4|info", "$head\n$body\n");
    } else {
        syslog("local4|err", "$head\n$body\n");
    }
    closelog();
}

#save to eventlog table in xCAT database
if ($DB) {
    my $head = "SNMP $severity received from $host($ip)\n$briefmsg\n";
    if (($node1) && (!$info)) { $info = getMoreInfo($node1); }
    my $middle = "Trap details:\n$message\n";
    my $event  = {
        eventtype   => 'event',
        monitor     => 'snmpmon',
        monnode     => $host,
        node        => $node1 ? $node1 : "",
        application => $appname,
        component   => $errsrc,
        id          => $id,
        severity    => $severity_type,
        message     => $message1,
        rawdata     => "$info$head\n$middle",
    };
    my @a = ();
    push(@a, $event);
    xCAT::TableUtils->logEventsToDatabase(\@a);
}

#run user defined commands if needed.
foreach my $k (keys %hashRUN) {
    if ($hashRUN{$k} == 1) {
        $k =~ /runcmd(\d*)/;
        my $scripts = $settings{"cmds$1"};
        while ($scripts =~ s/^([^,]+)(,)*//) {
            my $cmd = "echo \'host=$host\nip=$ip\n$message\n\' \| $1";
            `$cmd`;
        }
    }
}


#--------------------------------------------------------------------------------

=head3    getMoreInfo
      This function returns the node module/type, serial number, position etc.
    Arguments:
       node-- name of the node.
    Returns:
       A string with node info ready to display.
=cut

#--------------------------------------------------------------------------------
sub getMoreInfo {
    my $node = shift;
    my $pos;
    my $vpd;

    # get module name and serial number from the xCAT DB.
    my $ref;
    my $table = xCAT::Table->new("vpd", -create => 1);
    if ($table) {
        my $ref = $table->getNodeAttribs($node, [ 'serial', 'mtm' ]);
        if ($ref) {
            $vpd .= "  Type/Mudule: ";
            if ($ref->{mtm}) { $vpd .= $ref->{mtm}; }
            $vpd .= "\n";
            $vpd .= "  Serial Number: ";
            if ($ref->{serial}) { $vpd .= $ref->{serial}; }
            $vpd .= "\n";
        }
        $table->close();
    }

    # get the info from rinv command if nothing in the vpd table
    if (!$vpd) {
        my $result = `XCATBYPASS=Y $::XCATROOT/bin/rinv $node all 2>&1 | egrep -i '(model|serial)' | grep -v Univ`;
        if ($? == 0) {    #success
            chomp($result);
            my @b = split(/\n/, $result);
            foreach (@b) {
                s/^(.*)\:(.*)\:(.*)$/$2: $3/;
                $vpd .= " $_\n";
            }
        }
    }
    if (!$vpd) {
        $vpd .= "  Type/Mudule: \n";
        $vpd .= "  Serial Number: \n";
    }


    #get the position
    my $table1 = xCAT::Table->new("nodepos", -create => 1);
    if ($table1) {
        my $ref1 = $table1->getNodeAttribs($node, [ 'rack', 'u', 'chassis', 'slot', 'room', 'comments' ]);
        if (($ref1) && ($ref1->{room})) { $pos .= "  Room: " . $ref1->{room} . "\n"; }
        if (($ref1) && ($ref1->{rack})) { $pos .= "  Rack: " . $ref1->{rack} . "\n"; }
        if (($ref1) && ($ref1->{u})) { $pos .= "  Unit: " . $ref1->{u} . "\n"; }
        if (($ref1) && ($ref1->{chassis})) { $pos .= "  Chassis: " . $ref1->{chassis} . "\n"; }
        if (($ref1) && ($ref1->{slot})) { $pos .= "  Slot: " . $ref1->{slot} . "\n"; }
        if (($ref1) && ($ref1->{comments})) { $pos .= "  Comments: " . $ref1->{comments} . "\n"; }

        $pos .= "\n";

        $table1->close();
    }

    if (($pos) || ($vpd)) {
        return "  Node: $node\n$vpd$pos\n";
    }

    return "";
}


#--------------------------------------------------------------------------------

=head3   parseSettings
      This function takes a setting string which looks like "key1=value1,key2=value2..."
      and returns a hash (key1=>value1, key2=>value2...).
    Arguments:
       setting  the setting string.
    Returns:
       A hash.
=cut

#--------------------------------------------------------------------------------
sub parseSettings {
    my $settings = shift;
    my %ret      = ();
    while (($settings =~ s/^(Informational|Warning|Critical|All|None)()()(,)*//) ||
        ($settings =~ s/^([^\=]+)(=~)(\"[^\"]+\")(,)*//) ||
        ($settings =~ s/^([^\=]+)(=~)([^\"\,]+)(,)*//)   ||
        ($settings =~ s/^([^\=]+)(=)(\"[^\"]+\")(,)*//)  ||
        ($settings =~ s/^([^\=]+)(=)([^\"\,]+)(,)*//)) {
        my $val = 'eq';
        if ($2 eq "=~") { $val = '=~'; }
        if (exists($ret{$1}{$val})) {
            my $pa = $ret{$1}{$val};
            push(@$pa, $3);
        }
        else {
            $ret{$1}{$val} = [$3];
        }
    }

    #print Dumper(%ret);
    return %ret;
}

#--------------------------------------------------------------------------------

=head3   checkWithOid
      This function checks the input strings with the setting to see what
      actions need to be done for this event.
    Arguments:
       oid       the oid string
       value     the value string
    Returns:
       none. The variables $EMAIL, $LOG, $IGNORE and $RUNCMD may be changed.
=cut

#--------------------------------------------------------------------------------
sub checkWithOid {
    my $o = shift;
    my $v = shift;

    sub checking {
        my $hashX = shift;
        my $o     = shift;
        my $v     = shift;

        if (exists($hashX->{'All'}))  { return 1; }
        if (exists($hashX->{'None'})) { return 0; }

        my @a_oid = split('::', $o);
        my $new_o = $o;
        if (@a_oid == 2) { $new_o = $a_oid[1]; }

        #print "o=$o, new_o=$new_o v=$v\n";

        #check for 'contains'
        if (exists($hashX->{$o})) {
            my $pa = $hashX->{$o}{'=~'};
            foreach (@$pa) {
                if ($v =~ /$_/) { return 1; }
            }
        }
        if (exists($hashX->{$new_o})) {
            my $pa = $hashX->{$new_o}{'=~'};
            foreach (@$pa) {
                if ($v =~ /$_/) { return 1; }
            }
        }

        #check for 'equals'
        if (exists($hashX->{$o})) {
            my $pa = $hashX->{$o}{'eq'};
            foreach (@$pa) {
                if ($_ eq $v) { return 1; }
            }
        }
        if (exists($hashX->{$new_o})) {
            my $pa = $hashX->{$new_o}{'eq'};
            foreach (@$pa) {
                if ($_ eq $v) { return 1; }
            }
        }


        return 0;
    }

    if ((!$IGNORE) && (keys(%hashI) > 0)) {
        $IGNORE = checking(\%hashI, $o, $v);
        if ($IGNORE) { return; }
    }

    if ((!$EMAIL) && (keys(%hashE) > 0)) {
        $EMAIL = checking(\%hashE, $o, $v);
    }

    if ((!$LOG) && (keys(%hashL) > 0)) {
        $LOG = checking(\%hashL, $o, $v);
    }

    if ((!$DB) && (keys(%hashD) > 0)) {
        $DB = checking(\%hashD, $o, $v);
    }

    foreach my $k (keys %hashRUN) {
        if ($hashRUN{$k} == 0) { $hashRUN{$k} = checking($hashR{$k}, $o, $v); }
    }

}
























