#!/usr/bin/perl

# This is a snmptrapd handler script to convert snmp traps into email
# messages.

# Usage:
# Put a line like the following in your snmptrapd.conf file:
#  traphandle TRAPOID|default /usr/local/bin/traptoemail [-f FROM] [-s SMTPSERVER]b ADDRESSES
#     FROM defaults to "root"
#     SMTPSERVER defaults to "localhost"

use Net::SMTP;
use Getopt::Std;
use File::Temp;
use Tie::Syslog;

$0 =~ m#([^/]+)$# ;
$i = $&;
tie *STDERR, 'Tie::Syslog', 'daemon.err', $i, 'pid', 'unix';

$learning = 1;
$macs = "/var/local/macs";

$opts{'s'} = "localhost";
$opts{'f'} = 'root@' . `hostname`;
chomp($opts{'f'});
getopts("hs:f:", \%opts);

if ($opts{'h'}) {
    print "
traptoemail [-s smtpserver] [-f fromaddress] toaddress [...]

  traptoemail shouldn't be called interatively by a user.  It is
  designed to be called as an snmptrapd extension via a \"traphandle\"
  directive in the snmptrapd.conf file.  See the snmptrapd.conf file for
  details.

  Options:
    -s smtpserver      Sets the smtpserver for where to send the mail through.
    -f fromaddress     Sets the email address to be used on the From: line.
    toaddress          Where you want the email sent to.

";
    exit;
}

die "no recepients to send mail to" if ($#ARGV < 0);

# process the trap:
$hostname = <STDIN>;
chomp($hostname);
$ipaddress = <STDIN>;
chomp($ipaddress);

$maxlen = 0;
while(<STDIN>) {
    ($oid, $value) = /([^\s]+)\s+(.*)/;
    push @oids, $oid;
    if (substr($value,0,1) eq '"') {
	while (substr($value,-1,1) ne '"') {
		$value .= "\n".<STDIN>;
		chomp($value);
	}
    }
    push @values, $value;
    $maxlen = (length($oid) > $maxlen) ? length($oid) : $maxlen;
}
$maxlen = 60 if ($maxlen > 60);
$formatstr = "%" . $maxlen . "s  %s\n";

die "illegal trap" if ($#oids < 1);

# Handle Mac-address trap special
for($i = 0; $i <= $#oids; $i++) {
	if ($oids[$i] eq "SNMPv2-SMI::enterprises.9.9.215.1.1.8.1.2.0") {
		$rawobject = $values[$i];
		# remove quotes from both ends
		$rawobject =~ s/"(.*)"/$1/s;
		split("[ \n\t]+" ,$rawobject);
		while ($_[0] ne "00") {
			@tuple = splice(@_,0,11);
			if ($tuple[0] eq "01") {
				$operation = "Mac Learnt";
			} elsif ($tuple[0] eq "02") {
				$operation = "Mac Removed";
			} else {
				$operation = "Unknown Operation";
			}
			$vlan = hex "0x".join("", @tuple[1,2]);
			$mac = join(":", @tuple[3..8]);
			$port = join(" ", @tuple[9,10]);
			$port = hex "0x".join("", @tuple[9,10]);
			push @{ $macchanges[$i] }, ( {"Operation:",$operation,"Vlan:",$vlan,"MAC:",$mac,"Port:",$port} );
		}
	}
}

# Check known MACs
open MACS, "<", $macs or die "Can read $macs";
@macs = <MACS> ;
close MACS;
$changes = 0;
for($i = 0; $i <= $#oids; $i++) {
    for($j = 0; $j <= $#{$macchanges[$i]}; $j++) {
	$mac = ${macchanges[$i][$j]}{"MAC:"};
	$found = grep /$mac/, @macs;
	${macchanges[$i][$j]}{"found:"} = $found;
	if ($found == 0) {
		$changes++;
		if ($learning) {
			open MACS, ">>", $macs or die "Can append $macs";
			print MACS $mac, "\n";
			close MACS;
		} 
	}
    }
}

exit if ($changes == 0);

# send the message
$conf = mktemp("/tmp/XXXXXX");
open CONF, ">", $conf or die "Can write open $conf";
$message = Net::SMTP->new($opts{'s'}) || die "can't talk to server $opts{'s'}\n";
$message->mail($opts{'f'});
$message->to(@ARGV) || die "failed to send to the recepients ",join(",",@ARGV),": $!";
$message->data();
$message->datasend("To: " . join(", ",@ARGV) . "\n");
$message->datasend("From: $opts{f}\n");
$message->datasend("Subject: trap received from $hostname: $values[1]\n");
$message->datasend("\n");
$message->datasend("Host: $hostname ($ipaddress)\n");
for($i = 0; $i <= $#oids; $i++) {
# For the moment we do not care about anything but MAC changes
#    $message->datasend(sprintf($formatstr, $oids[$i], $values[$i]));
    for($j = 0; $j <= $#{$macchanges[$i]}; $j++) {
	%changeline = %{$macchanges[$i][$j]};
    	$message->datasend(sprintf("MAC Change %d: %s\n", $j, join(" ", %changeline)));
	if (!$changeline{"found:"}) {
		printf CONF "interface FastEthernet0/%d\n",$changeline{"Port:"};
		print CONF "shutdown\n";
	}
    }
}
print CONF "end\n";
close CONF ;
if (!$learning) {
	system("rcp $conf $hostname:running-config") and warn "rcp failed";
}
$message->datasend("\nConfig Changes:\n");
$message->datasend("NOT ACTIVATED (learning mode)\n") if ($learning);
$message->datasend("rcp $conf $hostname:running-config\n");
open CONF, "<", $conf or die "Can read open $conf";
$message->datasend(<CONF>);
close CONF;
unlink $conf;
$message->dataend();
$message->quit;
