#!/usr/bin/perl -w
# snmp-trapper - catch SNMP traps and do the right thing
# CVS $Id: snmp-trapper.pl,v 1.13 2003/05/20 19:28:05 remstats Exp $
# from remstats 1.0.13a
# Copyright 1999 - 2003 (c) Thomas Erskine <terskine@users.sourceforge.net>
# See the COPYRIGHT file with the distribution.

# - - -   Configuration   - - -

use strict;

# What is this program called, for error-messages and file-names
$main::prog = 'snmp-trapper';
# Where is the default config-dir
$main::config_dir = '/etc/remstats/config';
# What class to log it as, if logging?
$main::event_class = 'TRAP';
# Which events to ignore
$main::default_ignore_pattern = 'ZZxxZZ';

# - - -   Version History   - - -

$main::version = (split(' ', '$Revision: 1.13 $'))[1];

# - - -   Setup   - - -

use lib '.', '/usr/lib/remstats/lib', '/usr/lib/perl5/';
use Getopt::Std;
require "remstats.pl";
require "snmpstuff.pl";
use SNMP_Session "0.60";
use BER;
use Socket;

# Parse the command-line
# XXX use \%opt
getopts('c:d:e:f:hiI:l:o');

if (defined $main::opt_h) { &usage; } # no return
if (defined $main::opt_c) { $main::default_community = $main::opt_c; }
if (defined $main::opt_d) { $main::debug = $main::opt_d; } else { $main::debug = 0; }
if (defined $main::opt_e) { $main::create_events = 1; } else { $main::create_events = 0; }
if (defined $main::opt_f) { $main::config_dir = $main::opt_f; }
if (defined $main::opt_i) { $main::do_ifname = 1; } else { $main::do_ifname = 0; }
if (defined $main::opt_I) { $main::ignore_pattern = $main::opt_I; }
if (defined $main::opt_l) { $main::log_file = $main::opt_l; } else { $main::log_file = '-'; }
if (defined $main::opt_o) { $main::show_oid = 1; } else { $main::show_oid = 0; }

my %generic_traps = (
	'0' => 'coldStart',
	'1' => 'warmStart',
	'2' => 'linkDown',
	'3' => 'linkUp',
	'4' => 'authFail',
	'5' => 'peerLoss',
	'6' => 'enterprise',
);

# Reverse map for OID -> name mapping
my %roids = (
	'ifIndex' => '1.3.6.1.2.1.2.2.1.1'
);

$|=1; # no buffering on write, please

&reload;
&debug("trapignore = '$main::ignore_pattern'") if ($main::debug);
&snmp_load_oids;
$SIG{'HUP'} = \&reload;

# No buffering on output
$|=1;

# - - -   Mainline   - - -

my ($trap, $sender, $sender_port, $time, $sec, $min, $hour, $mday,
	$mon, $year, $now, $it, $community, $ent, $agent, $gen, $spec, $dt,
	$bindings, $binding, $oid, $value, $trap_type, $ip, @temp, 
	$instance, $suboid, $ifname, $comhost );

$SNMP_Session::suppress_warnings = 0;
my $session = SNMPv1_Session->open_trap_session ();
exit 1 unless (defined $session);
$SNMP_Session::suppress_warnings = 2;

open (OUTPUT, ">>$main::log_file") or
	die "$main::prog: can't open $main::log_file: $!\n";
while (($trap, $sender, $sender_port) = $session->receive_trap ()) {
	$time = time();
	($sec,$min,$hour,$mday,$mon,$year) = localtime($time);
	$now = sprintf("%04d%02d%02d %02d%02d%02d", 
		$year+1900, $mon+1, $mday, $hour, $min, $sec);
	($community, $ent, $agent, $gen, $spec, $dt, $bindings)
			= $session->decode_trap_request ($trap);

	next unless (defined $sender and defined $sender_port and 
		defined $gen and %generic_traps);
	$ip = inet_ntoa ($sender);

	if (defined $generic_traps{$gen}) {
		$trap_type = $generic_traps{$gen};
	}
	else { $trap_type = $gen; }

	$it =  $now .' '. $ip .':'. $sender_port .' '. $community .' '. $trap_type;
	if ($trap_type eq 'enterprise') { $it .= ' '. $spec; }

	while ($bindings) {
		($binding,$bindings) = decode_sequence ($bindings);
		($oid,$value) = decode_by_template ($binding, "%O%@");

		$oid = BER::pretty_oid($oid);
		@temp = split('\.',$oid);
		$instance = pop @temp;
		$suboid = join('.', @temp);

		if (defined $roids{$oid}) {
			$oid = $roids{$oid};
		}
		elsif (defined $roids{$suboid}) {
			$oid = $roids{$suboid} .'.'. $instance;
		}

		$value = pretty_print ($value);
		$value =~ tr/ -~//cd;
		$value =~ tr/ /_/;
		if ($value eq '(null)') {
			&debug("  skipping null oid") if ($main::debug>1);
			next;
		}
		unless (defined $value) {
			&debug("  skipping undefined oid") if ($main::debug>1);
			next;
		}

		if ($main::do_ifname and 
				($trap_type eq 'linkDown' or $trap_type eq 'linkUp')) {
			&debug("  finding ifname for $ip $trap_type $value") 
				if ($main::debug);

# Cached ifname available
			if (defined $main::ifname{$ip}{$value}) {
				$ifname = $main::ifname{$ip}{$value};
				&debug("  got cached ifname = $ifname") if ($main::debug>1);
			}

# We know comhost, lookup ifname and cache it
			elsif (defined $main::comhost{$ip} and $main::comhost{$ip} ne '') {
				$comhost = $main::comhost{$ip};
				$ifname = &get_ifname( $comhost, $value);
				if (defined $ifname) {
					$main::ifname{$ip}{$value} = $ifname;
					&debug("  got known ifname = $ifname") if ($main::debug>1);
				}
				else {
					$ifname = 'ix='. $value;
					$main::ifname{$ip}{$value} = $ifname;
					&debug("  unknown ifname for $value") if ($main::debug>1);
				}
			}

# Try to look up ifname using default community and cache if found
			elsif (defined ($ifname = 
					&get_ifname( $main::default_community .'@'. $ip, $value))) {
				$main::comhost{$ip} = $main::default_community .'@'. $ip;
				$main::ifname{$ip}{$value} = $ifname;
				&debug("  got default unknown ifname = $ifname") if ($main::debug>1);
			}

# No cache and not in config and default didn't work, give ifIndex and cache
			else {
				$main::comhost{$ip} = '';
				$ifname = 'ix='. $value;
				$main::ifname{$ip}{$value} = $ifname;
				&debug("  got new unknown ifname = $ifname") if ($main::debug>1);
			}

			$it .= ' '. $ifname;
		}

# Don't interpret ifIndex or not linkUp nor LinkDown
		else {
			if ($main::show_oid) { $it .= ' '. $oid .'='. $value; }
			else { $it .= ' '. $value; }
		}
	}
	if (defined $main::ignore_pattern and $it =~ m#$main::ignore_pattern#o) {
		&debug("  ignored $ip:$sender_port $trap_type") if ($main::debug>2);
		next;
	}
	&debug("trap from $ip:$sender_port is $trap_type+$spec") if ($main::debug>2);
	print OUTPUT $it . "\n";
	if ($main::create_events) {
		my $host = gethostbyaddr($ip,AF_INET);
		&logit($main::event_class, $host, undef, undef, undef, $it);
	}
}
close (OUTPUT);

exit 0;

#----------------------------------------------------------------- usage ---
sub usage {
	print STDERR <<"EOD_USAGE";
$main::prog version $main::version from remstats 1.0.13a
usage: $0 [options] >>logfile
where options are:
    -d ddd  enable debugging output at level 'ddd'
    -e      create TRAP events in log
    -f fff  use 'fff' for config-dir [$main::config_dir]
    -h      show this help
    -i      get ifNames instead of ifIndex
    -l lll  write to log file 'lll'
    -o      show oids
EOD_USAGE
	exit 0;
}

#----------------------------------------------------------------- debug ---
sub debug {
	print STDERR 'DEBUG: ', @_, "\n";
}

#---------------------------------------------------------- make_comhosts ---
sub make_comhosts {
	%main::comhost = ();
	my ($host, $ip, $comhost);

	foreach $host (keys %{$main::config{HOST}}) {
		$ip = &get_ip($host);
		next unless (defined $ip);
		next unless( &host_collected_by( $host, 'snmp'));
		$comhost = &get_comhost( $host, undef, undef);
		next unless (defined $comhost);
		$main::comhost{$ip} = $comhost;
		&debug(" $host $ip $comhost") if ($main::debug>2);
	}
}

#--------------------------------------------------------------- reload ---
sub reload {

	&debug("$main::prog: reloading config") if ($main::debug);
	undef %main::config;
	&read_config_dir( $main::config_dir, 'general', 'oids', 'groups', 
		'times', 'rrds', 'host-templates', 'hosts');
	&make_comhosts;

	unless (defined $main::default_community) {
		if (defined $main::config{COMMUNITY}) {
			$main::default_community = $main::config{COMMUNITY};
		}
		else { $main::default_community = 'public'; }
	}

	if (defined $main::ignore_pattern and $main::ignore_pattern eq '') {
		$main::ignore_pattern = $main::config{TRAPIGNORE};
	}
	elsif (!defined $main::ignore_pattern) {
		$main::ignore_pattern = $main::default_ignore_pattern;
	}
}

#----------------------------------------------- keep_strict_happy ---
sub keep_strict_happy {
	$main::opt_e = $main::opt_h = $main::opt_i = $main::opt_o = 0;
}

#---------------------------------------------------------- error ---
sub error {
	print STDERR 'ERROR: ', @_, "\n";
}

#---------------------------------------------------------- abort ---
sub abort {
	print STDERR 'ABORT: ', @_, "\n";
	exit 6;
}
