#! /usr/local/bin/perl
# $Id: linksys_snmptrap.pl,v 1.19 2004/06/12 15:45:31 user Exp $

=head1 NAME

linksys_snmptrap - Capture and decode a Linksys BEFSR41 SNMP trap.

=head1 SYNOPSIS

To use this as a stand-alone program to work on a captured SNMP trap, try this:

    linksys_snmptrap [--logfile /tmp/log_file] [--nosyslog] < snmp_trap

This program is meant to be called by snmptrapd.  I do not understand
snmptrapd entirely.  To use this program, create /etc/snmp/snmptrapd.conf
containing this line:

    traphandle SNMPv2-SMI::enterprises.3093.2.2.1.0.1 /path/linksys_snmptrap

Then, the formatted and decoded data will be logged to syslog.

=head1 COPYRIGHT

Copyright 2004 travis+web@subspacefield.org

This program is free for non-commercial use.
Contact author for terms of commercial use.

=cut

use strict;
use warnings;
$|++;

# If set, log file to which we should dump everything.
my $log_file;

my $syslog = 1;

# By default, don't log this aabbccdd msg added in recent firmware revisions.
my $aabbccdd = 0;

use Getopt::Long;

my $result = GetOptions("log=s" => \$log_file, "syslog!" => \$syslog,
			"aabbccdd!" => \$aabbccdd);

if (defined($log_file)) {
    my $appending = -f $log_file;
    open(FILE, ">>$log_file");
    print FILE "\n" if $appending;
}

sub read_snmp_trap {
    my ($fh) = @_;
    my ($continuation, $key, $value, %entries) = 0;
    while (<$fh>) {
	print FILE $_ if defined($log_file);
	chomp;
	if ($continuation) {
	    $value .= " $_";
	}
	else {
	    ($key, $value) = split / /, $_, 2;
	}
	# If we have an odd number of double-quotes,
	# this continues on the next line of input.
	$continuation = ($value =~ tr/"/"/) % 2;
	unless ($continuation) {
	    $value =~ /"(.*)"/ and $value = $1;
	    $entries{$key} = $value;
	}
    }
    return \%entries;
}

use Sys::Syslog;

my $sender_name = <STDIN>; chomp $sender_name;
my $sender_ip = <STDIN>; chomp $sender_ip;
print FILE "$sender_name\n$sender_ip\n" if defined($log_file);
my $snmp_stuff = read_snmp_trap(*STDIN);

my $data;
$_ = $$snmp_stuff{"SNMPv2-SMI::enterprises.3093.1.1.0"};
if (defined) {
    if ($syslog) {
	openlog "linksys", "cons", "user";
	# If it is in hex, convert from hex to ASCII.
	if (/^([[:xdigit:]][[:xdigit:]] )*$/) {
	    s/([[:xdigit:]][[:xdigit:]]) /chr hex $1/eg;
	    # Remove the trailing newline from the encoded string.
	    chomp;
	}
	# If this is in a five-field format, re-format it slightly.
	if (/@([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)/) {
	    my $dir = $1;
	    my $src_name = $2;
	    my $src_port = $3;
	    my $dst_name = $4;
	    my $dst_port = $5;
	    $_ = "$dir $src_name:$src_port -> $dst_name:$dst_port";
	    syslog "warning", "$sender_name($sender_ip) $_";
	}
	else {
	    syslog "warning", "$sender_name($sender_ip) $_" if $aabbccdd;
	}
	closelog;
    }
    exit 0;
}

# Unexpected input, dump info to somewhere useful.

exit 1;

