#!/usr/sbin/perl -w

use strict ;


my $logfile = "/logs/adm/SYSLOG" ;
my $outfile = "/logs/cuda/summarize" ;

my $debug = 0 ;
my $tail = 1 ; my $rewind = 0 ; 
my $reopen = 0 ; my $mysleep = 0 ;
my $print_transactions = 1 ;
my $print_incomplete = 0 ;
my $print_rbl = 0 ;
my $detail = 0 ;
my $tally = 1 ;
my $print_hostnames = 0 ;
my $show_multi_rcpt = 0 ;

my $cachesize = 500 ;
my $cachepurge = $cachesize/10 ;

my ( %transdata, %rblcounter ) ;
my ( $linecount, $transcount, $j, $incomplete_count, $completed_count, $unmatched_lines, $orphaned_lines, $timestamp, $starttime, $endtime ) ;

my %show ;
$show{"rbl"} = 0 ;
$show{"badrecip"} = 1 ;
$show{"ratelimit"} = 0 ;
$show{"badsender"} = 1 ;
$show{"temperror"} = 0 ;
$show{"miscerror"} = 0 ;
$show{"syntaxerr"} = 0 ;
$show{"spamblock"} = 1 ;
$show{"virusblock"} = 1 ;
$show{"userblacklist"} = 0 ;
$show{"tagged"} = 1 ;
$show{"quarantined"} = 1 ;
$show{"delivered"} = 1 ;

# Normally we don't show these events since that will show a lot of duplicates, with no result yet
$show{"from"} = 0 ;
$show{"to"} = 0 ;
$show{"connect"} = 0 ;
$show{"disconnect"} = 0 ;
$show{"toomanyrecipients"} = 0 ;
$show{"novalidrecipients"} = 0 ;


sub printstats();
sub print_transaction($);

my $intr;
sub catch_intr { $intr=1; }
$SIG{INT} = \&catch_intr ;

sub catch_usr1 { $SIG{USR1} = \&catch_usr1 ; printstats() ; }
$SIG{USR1} = \&catch_usr1 ;

$linecount = 0 ;
$transcount = 0 ;
$incomplete_count = 0 ;
$completed_count = 0 ;
$unmatched_lines = 0 ;
$orphaned_lines = 0 ;

while (@ARGV) {
	if ( $ARGV[0] eq "debug" ) {
		shift @ARGV ;
		$debug = 1 ;
	} elsif ( $ARGV[0] eq "cachetest" ) {
		shift @ARGV ;
		$debug = 1 ;			$cachesize = 200 ;
		$detail = 1 ; 			$tally = 0 ;
		$print_transactions = 0 ;	$print_incomplete = 1 ;  
		foreach my $j ( keys(%show) ) {
			$show{$j} = 0 ;
		}
	} elsif ( $ARGV[0] eq "today" ) {
		shift @ARGV ;
		$tail = 0 ; 
	} elsif ( $ARGV[0] eq "tail") {
		shift @ARGV ;
		$tail = 1 ;
		if ($ARGV[0] && $ARGV[0] > 0) {$rewind = shift(@ARGV);}
	} elsif ( $ARGV[0] eq "rewind") {
		shift @ARGV ;
		$rewind = 100000 ;
		if ($ARGV[0] && $ARGV[0] > 0) {$rewind = shift(@ARGV);}
	} elsif ( $ARGV[0] eq "watcher") {
		shift @ARGV ;
		$tail = 1 ;
		$reopen = 1 ;
		$rewind = 0;
	} elsif ( $ARGV[0] eq "detail") {
		shift @ARGV ;
		$detail = 1 ;
	} elsif ( $ARGV[0] eq "nodetail") {
		shift @ARGV ;
		$detail = 0 ;
	} elsif ( $ARGV[0] eq "tally") {
		shift @ARGV ;
		$tally = 1 ;
	} elsif ( $ARGV[0] eq "notally") {
		shift @ARGV ;
		$tally = 0 ;
	} elsif ( $ARGV[0] eq "show") {
		shift @ARGV ;
		if ($ARGV[0]) {
			$show{shift(@ARGV)} = 1 ;
		}
	} elsif ( $ARGV[0] eq "hide") {
		shift @ARGV ;
		if ($ARGV[0]) {
			$show{shift(@ARGV)} = 0 ;
		}
	} elsif ( $ARGV[0] && $ARGV[0] eq "multi" ) {
		shift @ARGV ;
		$print_transactions = 1 ;	$print_incomplete = 0 ;
		$show_multi_rcpt = 2 ;
		if ($ARGV[0] && $ARGV[0] > 0) {$show_multi_rcpt =  shift(@ARGV);}
	} else {
		print STDERR "Ignoring parameter: ", $ARGV[0], "\n" ;
		shift @ARGV ;
	}
}


open (INPUTFILE, "< $logfile" )  or die "Unable to open $logfile : $!" ;
open (OUTFILE, ">> $outfile" )  or die "Unable to open $logfile : $!" ;

if ($tail) { 
	seek (INPUTFILE, 0, 2) ; 
}
if ($rewind eq "top")  { 
	seek (INPUTFILE, 0, 0) ;
} elsif ($rewind)  { 
	print STDERR "Rewinding $rewind characters\n" ; 
	seek (INPUTFILE, -($rewind), 2) ; 
	my $line = <INPUTFILE> ;
	$line = <INPUTFILE> ;
	if ($line =~ /^\w+\s+\d+ (\d+:\d+:\d+) / ) {
		print STDERR "Time period starts: $1\n" ;
	}
	seek (INPUTFILE, -($rewind), 2) ; 
	$line = <INPUTFILE> ;
}

$intr = 0;
my $line ; my $partial ;
LINE: while ( !$intr ) {
	my ( $hashref ) ;
	my ( $hostname, $program, $pid, $transaction, $event, $data ) ;

	$line = <INPUTFILE> ;
	
	if ( defined($line) && ! ($line =~ /\n/) ) {
		$partial = $partial . $line ;
		$line = undef ;
	}
	if ( !defined($line) ) {
		if (!$tail) { $intr = 1; next LINE ;}
		sleep ($mysleep++) ;
		if ($mysleep > 4) {
			# If our input file (name) is more recent than the open file (handle)
			# then it must have moved.
			my ( $mtime1, $mtime2 ) ;
			$mtime1 = (stat(INPUTFILE))[9] ;
			$mtime2 = (stat($logfile))[9] ;
			if ( defined($mtime2) && ($mtime2>$mtime1) ) {
				if (!$reopen) {
					($debug>=2) && print "File seems to have moved, exiting\n";
					$intr=1 ;
					next LINE ;
				}
				($debug>=2) && print "File seems to have moved, reopening\n" ;
				close(INPUTFILE) ;
				close(OUTFILE) ;
				rename("$outfile","${outfile}.old") ;
				open (INPUTFILE, "< $logfile" )  or die "Unable to reopen $logfile : $!" ;
				open (OUTFILE, ">> $outfile" )  or die "Unable to reopen $logfile : $!" ;
			}
				
		}
		if ($mysleep > 30) {
			$mysleep = 30 ;
		}
		next LINE ;
	} else {
		$mysleep=0 ;
	}
	if ($partial) { $line = $partial . $line ; $partial = "" ; }

	$linecount ++ ;
	$debug && ($linecount%10000 == 0) &&  print STDERR "\t line=$linecount cache=", scalar(keys(%transdata)), " completed=$completed_count incomplete=$incomplete_count(", int($incomplete_count/($completed_count+$incomplete_count)*100), "%) orphaned=$orphaned_lines(", int($orphaned_lines/$linecount*100), "%) unmatched=$unmatched_lines", "\n" ;

	if (scalar(keys(%transdata)) >= $cachesize) {
		my @keyslist ;
		($debug>=1) && print STDERR "Cache is ", $cachesize, ", trimming ", $cachepurge, " oldest entries\n" ;
		@keyslist = sort {$transdata{$a}->{"lastline"} <=> $transdata{$b}->{"lastline"}} (keys(%transdata));
		@keyslist = @keyslist[0..($cachepurge-1)] ;
		my $save = $print_transactions ;
		$print_transactions = $print_incomplete ;
		foreach $j (@keyslist) {
			$hashref = $transdata{$j} ;
			print_transaction($hashref) ;
			if (!($hashref->{"result"})) {$incomplete_count++} ;
		}
		$print_transactions = $save ;
		delete @transdata{@keyslist} ;
	}

	if (scalar(keys(%transdata)) >= $cachesize+10) {
		print STDERR "Cache is ", $cachesize+10, ", stopping\n" ;
		last LINE ;
	}

	# Nov  3 00:00:01 7C:cuda2.sgi.com inbound/pass1[31460]: 1099468800-31460-297-0:error: 550 <kenn.sinclair@sgi.com>: Recipient address rejected: No such user (kenn.sinclair@sgi.com)
	if ( $line =~ /^(\w+\s+\d+ \d+:\d+:\d+) \w+:(\S+) (\S+)\[(\d+)\]: (\d+-\d+-\d+)-\d+:([^:]+):\s*(.*)$/ ) {
		$timestamp = $1 ;
		$hostname = $2 ;
		$program = $3 ;
		$pid = $4 ;
		$transaction = $5 ;
		$event = $6;
		$data = $7 ;
		if ( $data =~ /(.*)\^\J/ ) { $data = $1 } ;
		if ( !defined($starttime) ) { $starttime = $timestamp; }
		$endtime = $timestamp ;
		
		($debug>=2) && print STDERR "timestamp=$timestamp hostname=$hostname program=$program pid=$pid transaction=$transaction event=$event data=$data\n" ;
	
	} else {
		($debug>=3) && print STDERR "Unmatched line:\n$line\n" ;
		$unmatched_lines++ ;
		next LINE ;
	}

	if ( $event eq "connect" ) {
		($debug>=2) && print STDERR "\tconnect, data=$data\n" ;

		$transcount ++ ;

		#Create a new anonymous hash to hold data about the transaction (see man perlref)
		$transdata{$transaction} = {} ;
		$hashref = $transdata{$transaction} ;
		$hashref->{"transaction"} = $transaction;
		$hashref->{"lines"} = 1;
		$hashref->{"errorcount"} = 0;
		$hashref->{"lastline"} = $linecount ;
		$hashref->{"to"} = [] ;
		$hashref->{"eventlist"} = [] ;
		$hashref->{"from_email"} = "" ;
		$hashref->{"result"} = "" ;
		$hashref->{"badrecip"} = 0 ;

		if ( $data =~ /(\S+)\[(\d+.\d+.\d+.\d+)\]/ ) {
			${$hashref}{"hostname"} = $1;
			${$hashref}{"ip"} = $2;
		} else {
			${$hashref}{"hostname"} = "??" ;
			${$hashref}{"ip"} = "??" ;
		}
		if ($show{"connect"}) { print_transaction($hashref) ; }
		next LINE ;
	}

	if ( !defined($transdata{$transaction}) ) {
		# don't record lines if we haven't seen "connect"
		if ( ($data =~ /Sender Abort/) || ($data =~ /Sender Quit/) ) { next LINE ; }
		($debug>=2) && print STDERR "\tIgnoring, haven't seen connect, event=$event, transaction=$transaction\n" ;
		$orphaned_lines++ ;
		next LINE ;
	}

	$hashref = $transdata{$transaction} ;
	$hashref->{"lines"} ++ ;
	$hashref->{"lastline"} = $linecount ;
	push (@{$hashref->{"eventlist"}} , $event ) ;




	if ( $event eq "whatever" ) {

	} elsif ( $event eq "disconnect" ) {
		delete $transdata{$transaction} ;
		$completed_count++ ;
		if ($show{"disconnect"}) { print_transaction($hashref) ; }
	} elsif ( $event eq "error" ) {
		$hashref->{"errorcount"} ++ ;
		if ( $data =~ /blocked using (\S*)/ ) {
			my $rbl=$1;
			$rblcounter{$rbl} ++ ;
			$hashref->{"result"} = "Blocked: using $rbl";
			if ($show{"rbl"})  { print_transaction($hashref) ; }

		} elsif ( $data =~ /^(4[0-9][0-9] .*)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"temperror"}) { print_transaction($hashref) ; }

		} elsif ( ($data =~ /(No such user) \((.*)\)/) || ($data =~ /(Recipient address rejected):*\s*(.*)/) ) {
			$hashref->{"result"} = "$1: $2" ;
			$hashref->{"badrecip"} ++ ;
			if ($show{"badrecip"})  { print_transaction($hashref) ; }

		# These three signal the end of the transaction, so delete it from the cache
		# Don't print these if there is a result already
		} elsif ( ($data =~ /(Sender Abort)/)  
				|| ($data =~ /(Sender Quit)/)
				|| ($data =~ /(timeout exceeded)/)
			) {
			delete $transdata{$transaction} ;
			$completed_count++ ;
			next LINE if $hashref->{"result"} ;  
			$hashref->{"result"} = "$1" ;
			if ($show{"disconnect"}) { print_transaction($hashref) ; }

		} elsif ( $data =~ /(Client host rejected: too many connections)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"ratelimit"}) { print_transaction($hashref) ; }

		} elsif ( $data =~ /(Sender address rejected: .*)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"badsender"}) { print_transaction($hashref) ; }

		} elsif ( $data =~ /^(Error: too many recipients)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"toomanyrecipients"}) { print_transaction($hashref) ; }

		} elsif ( $data =~ /^(Error: no valid recipients)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"novalidrecipients"}) { print_transaction($hashref) ; }

		} elsif ( $data =~ /(Bad address syntax)/ 
				|| $data =~ /(Syntax: .*)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"syntaxerr"}) { print_transaction($hashref) ; }

		} elsif ( $data =~ /^(Error: .*)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"miscerror"}) { print_transaction($hashref) ; }

		} elsif ( $data =~ /^(5[0-9][0-9] .*)/ ) {
			$hashref->{"result"} = "$1" ;
			if ($show{"miscerror"}) { print_transaction($hashref) ; }

		} else {
			print STDERR "unrecognized error data=$data\n" ;
			$hashref->{"result"} = "error: $data" ;
			if ($show{"miscerror"}) { print_transaction($hashref) ; }
		}

	} elsif ( $event eq "from_email" ) {
		$hashref->{"from_email"} = $data ;
		$hashref->{"to"} = [] ;
		if ($show{"from"}) { print_transaction($hashref) ; }
	} elsif ( $event eq "to" ) {
		push (@{$hashref->{"to"}} , $data ) ;
		if ($show{"to"}) { print_transaction($hashref) ; }
	} elsif ( $event eq "subject" ) {
	} elsif ( ($event eq "msg_file") || ($event eq "pu_msg_file") ) {
	} elsif ( $event eq "spam_score" ) {
	} elsif ( $event eq "spam_block" ) {
		$hashref->{"result"} = "Blocked: spam" ;
		delete $transdata{$transaction} ;
		$completed_count++ ;
		if ($show{"spamblock"}) { print_transaction($hashref) ; }
	} elsif ( $event eq "virus_block" ) {
		$hashref->{"result"} = "Blocked: virus" ;
		delete $transdata{$transaction} ;
		$completed_count++ ;
		if ($show{"virusblock"}) { print_transaction($hashref) ; }
	} elsif ( $event eq "whitelist" ) {
	} elsif ( $event eq "blacklist" ) {
		$hashref->{"result"} = "Blocked: user blacklist" ;
		if ($show{"userblacklist"}) { print_transaction($hashref) ; }
		print_transaction($hashref) ;
	} elsif ( $event eq "tag" ) {
		$hashref->{"result"} = "Tagged" ;
		if ($show{"tagged"}) { print_transaction($hashref) ; }
		delete $transdata{$transaction} ;
	} elsif ( ($event eq "quarantine") || ($event eq "pu_quarantine") ) {
		$hashref->{"result"} = "Quarantined" ;
		if ($show{"quarantined"}) { print_transaction($hashref) ; }
		delete $transdata{$transaction} ;
	} elsif ( $event eq "message_delivered" ) {
		$hashref->{"result"} = "Delivered" ;
		if ($show{"delivered"}) { print_transaction($hashref) ; }
		delete $transdata{$transaction} ;
		$completed_count++ ;
	} else {
		print STDERR "Unparsed event type:\nevent=$event data=$data\n" ;
	}		


}

close INPUTFILE ;

printstats();

if ( $print_incomplete ) {
	$print_transactions = $print_incomplete ;

	CACHEOUTPUT:
	foreach $j ( keys(%transdata) ) {
		print_transaction($transdata{$j}) ;
	}
}

exit 0 ;
exit 0 ;
exit 0 ;
exit 0 ;
exit 0 ;
exit 0 ;
exit 0 ;
exit 0 ;




sub printstats() {

print STDERR "From: $starttime\n" ;
print STDERR "  To: $endtime\n" ;

print STDERR "lines=$linecount orphaned=$orphaned_lines(", int($orphaned_lines/$linecount*100), "%) unmatched=$unmatched_lines", "\n" ;
print STDERR "transcount=$transcount cache=", scalar(keys(%transdata)), " completed=$completed_count incomplete=$incomplete_count(", int($incomplete_count/($completed_count+$incomplete_count)*100), "%)", "\n" ;

if ($show{"rbl"}) {
	RBLOUTPUT: 
	foreach $j ( keys(%rblcounter) ) {
		print STDERR "RBL: $j = ", $rblcounter{$j}, "\n" ;
	}
}

} # end sub printstats()




sub print_transaction ($) {
	
	my $hashref = $_[0] ;
	if ($print_transactions == 0) { return () ; }
	if (scalar(@{$hashref->{"to"}})<$show_multi_rcpt) { return () ; }
	if ($detail) {
		print OUTFILE join(", ", 
				"lastline=".$hashref->{"lastline"},
				$hashref->{"transaction"}, 
				"lines=".$hashref->{"lines"},
				"ip=".$hashref->{"ip"} . ( $print_hostnames?  (" (" .$hashref->{"hostname"}. ")") : "" ),
				"errorcount=".$hashref->{"errorcount"},
				"from_email=".$hashref->{"from_email"},
				"to=".join(",",@{$hashref->{"to"}}),
				"eventlist=".join(",",@{$hashref->{"eventlist"}}),
				"result=".$hashref->{"result"},
				"badrecip=".$hashref->{"badrecip"},
			), "\n" ;
	} 
	if ($tally) {
		print OUTFILE join(", ",
				"time=$timestamp",
				"ip=".$hashref->{"ip"} . ( $print_hostnames?  (" (" .$hashref->{"hostname"}. ")") : "" ),
				"result=".$hashref->{"result"},
			), "\n" ;
	}
		
}
