#!/usr/bin/perl

# Licensed under the GNU GPL
# Author - Brett L Simpson

# Features
# Count the number of times a virus was sent in descending order
# Count the number of times an IP address sent a virus in descending order
# Show each unique virus that was sent for each IP address.
# Populate a complex data structure with message id's, ip address's, and virus names. This is necesary since the ip address and virus name are on separate lines but have the same message id.
# Capable of sending email based reports

# Changelog
# 01/31/05   - Made change to account for milter logging changes in ClamAV 0.81
# 01/10/05   - Made Email portion better formatted - Requires MIME::Lite, Recent Net::SMTP, and CGI
# 08/31/2004 - Put error handling into opening of log files and pull the hostname of the system for mailed reports.
# 08/31/2004 - Released version 0.20
# 08/30/2004 - Released version 0.10
# 08/30/2004 - Added command line arguments
# 08/27/2004 - Corrected a bug were multiple Senders or virus's per IP address would produce errors
# 08/20/2004 - Commented out Recipient information

# TODO
# Review and cleanup of code
# Add command line argument for only showing specific hosts who sent virus's

use warnings;
use diagnostics;
use strict;
use vars qw/ $opt_h $opt_f $opt_l $opt_r $opt_s $opt_c $opt_v $opt_m $opt_V /;
use Getopt::Std;

	my $Version = "0.36";
	my $host_count = "10";
	my $virus_count = "1";
	my $mail_server = 'localhost';
	my $mail_user = "webadmin\@hillsboroughcounty.org";
	my $log_file = '/var/log/maillog';


getopts( 'hf:l:sc:v:rmV' );	

if ($opt_h) {
	print "Options: \n";
	print "-h Help \n";
	print "-f Log file \n";
	print "-l Log type - valid types are: amavis and milter - Defaults to milter \n";
	print "-r Show recipients \n";
	print "-s Show senders - Milter only \n";
	print "-c Minimum virus count for unique hosts \n";
	print "-v Minimum virus type count \n";
	print "-m Email report to predefined values set in this perl script  \n";
	print "-V Version \n";
	exit 0
}

if ($opt_f) {
	$log_file = $opt_f;
}


my $log_type;
if ($opt_l) {
	if ($opt_l eq "amavis") {
	$log_type = $opt_l;
	} elsif ($opt_l eq "milter") {
	$log_type = $opt_l;
	} else {
	print "-l Log type - valid types are: amavis and milter  \n";
	exit 1
	}
} else {
	$log_type = "milter";
}

my $show_senders;
if ($opt_s) {
	if ($log_type eq "amavis") {
		print "Showing of Senders not yet supported for Amavis\n";
		exit 1
	} else {
		$show_senders = "1";
	}
}

$host_count = $opt_c if ($opt_c);

$virus_count = $opt_v if ($opt_v);

my $show_recipients = "1" if ($opt_r);

my $send_email = "1" if ($opt_m);

if ($opt_V) {
	print "Version $Version \n";
	exit 0
}

	our ($ip_addr, $sender);
	my ($email, %ip_addr, $virus, %virus, $Virus, $Sender, $recipient, $Recipient, $message_id, @text_body);

open(FILE, "$log_file") or die("Error opening $log_file: $!\n");
while(<FILE>) {

	if ($log_type eq "milter") {

		if (/(?:\d|\D)+sendmail\[(?:\d)+\]:\s((?:\w)+):(?:\d|\D)+\[(\d+\.\d+\.\d+\.\d+)\]/) {
			$message_id = $1;
			$ip_addr = $2;
			$email->{$message_id}{ip_addr} = $ip_addr;
		} 
		elsif (/(?:\d|\D)+clamav-milter\[(?:\d)+\]:\s((?:\w)+):\s(?:\d|\D)+:\s((?:\d|\D)+)\sIntercepted virus from \<((?:\d|\D)+)\> to \<((?:\d|\D)+)\>/) {
        		$message_id = $1;
			$virus = $2;
			$sender = $3;
			$recipient = $4;
			$email->{$message_id}{virus} = $virus;
			$email->{$message_id}{sender} = $sender;
			$email->{$message_id}{recipient} = $recipient;
		}
	} elsif ($log_type eq "amavis") {
		if (/(?:\d|\D)+\s\(((?:\d|\D)+)\)\sBlocked INFECTED\s\(((?:\d|\D)+)\)\,\s\<\?\@((?:\d|\D)+)\>\s\-\>\s\<((?:\d|\D)+)\>\,\squar(?:\d|\D)+/) {
			$message_id = $1;
			$virus = $2;
			$ip_addr = $3;
			$recipient = $4;
                                                                                                                                                             
			$email->{$message_id}->{virus} = $virus;
			$email->{$message_id}->{recipient} = $recipient;
			$email->{$message_id}->{ip_addr} = $ip_addr;                                                                                         
		}
	}

}
close(FILE);

foreach $message_id ( keys  %{ $email } ) {
		if ( $email->{$message_id}{virus} ) {
			$virus = $email->{$message_id}{virus};
			$ip_addr = $email->{$message_id}{ip_addr};
			$recipient = $email->{$message_id}{recipient};
			$sender = $email->{$message_id}{sender};
			#Counts total number of times a virus was sent
			$ip_addr{$virus}++;
			#Counts total number of virus's sent by IP address
			$virus{$ip_addr}++;
			#Counts total number of unique virus's per IP address
			$email->{$ip_addr}{virus}{$virus}++;
			#Counts total number of unique senders per IP address
			if ($show_senders) {
				$email->{$ip_addr}{sender}{$sender}++;
			}
			#Counts total number of unique recipients per IP address
			if ($show_recipients) {
				$email->{$ip_addr}{recipient}{$recipient}++;
			}
		}
}

sub hashValueDescendingVirus {
	   $email->{$ip_addr}{virus}{$b} <=> $email->{$ip_addr}{virus}{$a};
}
		
sub hashValueDescendingRecipient {
	   $email->{$ip_addr}{recipient}{$b} <=> $email->{$ip_addr}{recipient}{$a};
}

sub hashValueDescendingSender {
	   $email->{$ip_addr}{sender}{$b} <=> $email->{$ip_addr}{sender}{$a};
}

sub hashValueDescendingNum {
   $ip_addr{$b} <=> $ip_addr{$a};
}

sub hashValueDescendingIp {
   $virus{$b} <=> $virus{$a};
}

push(@text_body, "Shows a count of each virus type:\n");

foreach $virus (sort hashValueDescendingNum (keys(%ip_addr))) {
	if ($ip_addr{$virus} >=  "$virus_count") {
		push(@text_body, "Count is $ip_addr{$virus} for $virus\n");
	}
}

if ($host_count) {
	push(@text_body, "\nShows uniques hosts with a virus count over $host_count:\n");
}


for $ip_addr (sort hashValueDescendingIp (keys(%virus))) {
#	add check to only show specific unique hosts -u address, address2
	if ($virus{"$ip_addr"} >= "$host_count") {


		push(@text_body, "\n$ip_addr sent the following virus's a total of $virus{$ip_addr} times: \n");
		
		foreach $Virus (sort hashValueDescendingVirus (keys (%{$email->{$ip_addr}{virus}}) )) {
			push(@text_body, "$Virus was transmitted $email->{$ip_addr}{virus}{$Virus} times.\n");
		}

		push(@text_body, "\n");

		if ($show_senders) {
#			modify to show -s address, address2 from command line			
#			if ($ip_addr eq "$ip_address") {		
				foreach $Sender (sort hashValueDescendingSender (keys( %{ $email->{$ip_addr}{sender} } ))) {
					push(@text_body, "Possibly spoofed sender address $Sender was seen $email->{$ip_addr}{sender}{$Sender} times.\n");
				}
#			}
		}

		if ($show_recipients) {
#			modify to show -r address, address2 from command line			
#			if ($ip_addr eq "$ip_address") {		
				foreach $Recipient (sort hashValueDescendingRecipient (keys( %{ $email->{$ip_addr}{recipient} } ))) {
					push(@text_body, "Recipient address $Recipient was seen $email->{$ip_addr}{recipient}{$Recipient} times.\n");
				}
#			}
		}
	}
}


	

#Email handling section

if ($send_email) {
	#Find hostname
	use Sys::Hostname;
	my $hostname = hostname();

use CGI qw(:standard);  
use MIME::Lite;
          
my $from_address = "$hostname <$hostname\@hillsboroughcounty.org>";

my $subject = "Subject: Analysis of $log_type logs for Virus's on $hostname\n";

# Create the initial text of the message
my $mime_msg = MIME::Lite->new(
   From => $from_address,
   To   => $mail_user,
   Subject => $subject,
   Type => 'TEXT',
   Data => "@text_body"
   )
  or die "Error creating MIME body: $!\n";

MIME::Lite->send('smtp', "$mail_server", Timeout=>60);

$mime_msg->send or die "Problems sending mail $!\n";

}

#Print out data
unless ($send_email) {
	for(my $counter=0 ; $counter < @text_body ; $counter++) {
		print "$text_body[$counter]" ;
	}
}
