#!/usr/bin/perl
# idea: monitor /var/log/maillog and decide what to do by ip address. 
# input.. 
# analyze lines
# damn them

# version .002 Dec 29 2002 - EgoWare: Like it? send email to mike@geeklabs.com


$linespercycle = 5000 ;  # how man SMTP connect lines to process before resetting stats
$threshold = 10 ;         # how many connects in the above lines before damning
$greythreshold = 150 ;   # forces a damning of even whitelisted IP's after this amount
$nodnsthreshold = 1 ;    # Set to 1, allows 1 e-mail from a raw IP with no reverse DNS. 

$maillog = "/var/log/maillog" ;           #file to follow in 'TAIL' mode.
$file = "/spamdamn/maillog" ;             #file to read in 'FILE' mode, 
$actionfile = "/spamdamn/iptables" ;      #file of blocks.. I call this in /etc/rc.local as well.
$history = "$actionfile.history" ; 
$longterm = "$actionfile.longterm" ; 
$whitelist = "/spamdamn/whitelist" ;      #domains and text to whitelist
$whiteip = "/spamdamn/whiteip" ;          #ip addresses to whitelist 
$blacklist = "/spamdamn/blacklist" ;      #domains and text to blacklist/damn 

$mode = "TAIL" ;     # MODE SELECTION:  TAIL  FILE  STD
                     # Tail works if you have File:;Tail installed
                     # otherwise setto STD   and run this by running:
                     # tail -f /var/log/maillog | ./spamdamn.pl


#================================================
#
#  This is the end of the stuff newbies should be playing with.. 
#  if you know perl.. have fun below!
#
#  (apologies: I'm a bad self-taught perl cut and paste artist.. not a real code monkey)
#

&recycle ; 


$mode = "TAIL" ; 

if ($mode eq "FILE") { 
	#this mode is useful for testing against a static file
	#system("tail -n 2000 /var/log/maillog >maillog") ; 
	open(IN,"$file") ; 
	while(<IN>) { 
		$line = $_ ; 
	   &analine ; 
	} ;

} elsif ($mode eq "TAIL") { 
		#the best overall mode. 
		use File::Tail;
  		$logfile=File::Tail->new(name=>$maillog, maxinterval=>10, adjustafter=>2, tail=>10, interval=>2);
    	while (defined($line=$logfile->read)) {
    	    $_ = $line ; 
			 &analine ; 
		if($relines > $linespercycle) { &recycle ; } ; 
      }
} else {
	$lines = 0 ; 
	while(<STDIN>) { 
		$line = $_ ; 
	   &analine ; 
		if($relines > $linespercycle) { &recycle ; } ; 
	} ;
} ; 


sub analine { 
if( $relines =~ /00$/) { 
	print "\n\n     $lines total log lines   Pop: $pop3d  Imap: $imapd SMTP $relines connections\n\n" ; 
   &stats ; $relines++ ; 
} ; 

$_ = $line ; 
if(/pop3d/) { $pop3d++ ; 
} elsif(/imapd/) {  $imapd++ ; 
} elsif(/zimapd/) {  $imapd++ ; 
} else {
	if (/relay/ & /from/) { 
		$session = &getsession ; 
		&breakup ; 
		&countip ; 
		&countname ; 
		$relays = substr($relay,0,45) ; 
		if ($legend == 20) {
		#	print "#   Relay Name[IP]                                     IP Address             IP DOMAIN\n" ; 
			$legend = 0 ; 
		} ; 

		printf "%4.0f %-50s %-18s # %4.0f %4.0f\n",$relines,$relays,$relayip,$countip,$countname ; 
	   &analyze ; 
	   $relines++  ; 
	   $legend++ ; 
	} ; 
} ; 
$lines++ ; 
} ; # end sub



sub getsession{ 
	@d = split(/\: /) ; 
	return $d[1] ; 
} ; 


sub breakup { 
	$_ = $d[2] ; 
	
	@junk = split(/\s+/,$d[0]) ; 
	$timestamp = $junk[0] . " " . $junk[1] . " " . $junk[2] ; 
	
	chop($_) ; 
	@d = split(/\, /) ; 
	foreach(@d) { 
		 if (/\=/) { 
		 ($var,$stuff) = split(/\=/) ; 
		 $string = "\$$var = \"$stuff\"" ; 
		 eval($string) ; 
		 } ; 
	} 
	$relayname = "" ; $relayip = "" ; 

	if(length($relay) > 2) {
	$_ = $relay ; 
	s/\s+//g ; #strips whitespace
	if(/\[(.*)\]/) { $relayip = $1 ; } ; 
	if(/(.*)\[(.*)\](.*)/) { $relayname = $1 ; $relayip = $2 ; } ; 
		} ;
	$relayname = "\L$relayname" ; 	
} ; 


sub analyze { 

   if ($sourceip{"$relayip"} > $threshold & $sourceip{"$relayip"} < $threshold + 4  ) { print "     IP VELOCITY Overload $relay - Count: $countip\n" ; &deny ; } ; 

   if ($sourceip{"$relayip"} > $nodnsthreshold & length($relayname) < 4 ) { print "     NO DNS Velocity Overload $relay - Count: $countip\n" ; &deny ; } ; 

#   if ($sourceip{"$relayip"} > $greythreshold ) { $forcedeny = "T" ; print "     GREY FORCE Velocity Overload $relay - Count: $countip\n" ; &deny ; } ; 

 #  if ($countname > ($threshold * 3) ) { print "     ROOT VELOCITY Overload $countname -  Count: $countip\n" ; &deny ; } ; 

	   foreach(@blacklist) {
			if ( $relay =~ /$_/) { print "     BLACKLISTED = '$_' $bip\n" ; &deny ; last ;   } 
	   } ;
} ; # End Sub Analyze


 

sub deny { 
$blocked = "F" ; 

      $bip = $relayip ; 
      if ($forcedeny eq "T") { } else {
			$_ = $relayip ; 
			if(/(.*)\.(.*)\.(.*)\.(.*)/) { $bip = "$1" . "." . "$2" . "." . "$3" . "." . "0" ; } else { $bip = $relayip ; } ; 
		   foreach(@whitelist) {
				if ( $relay =~ /$_/) { print "     whitelisted $relay $bip\n" ; $blocked = "T" ;  } 			
		   } ; 

			   foreach(@whiteip) {
				if ( $relayip =~ /$_/) { print "     whitelisted $relay $bip\n" ; $blocked = "T" ;  } 			
			   } ; 
	   } ; # end force 
	   $forcedeny = "F" ; 

		   foreach(@whiteip) {
				if ( $relayip =~ /$_/) { print "     whitelisted IP $relay $bip\n" ; $blocked = "T" ;   } 			
			   } ; 

			# check file for currently blocking? 		
  			open(ACTION,"$actionfile") ; 
			while(<ACTION>) { 
				if($_ =~ /$bip/) { print "     already blocking $bip\n" ; $blocked = "T" ; last ;   } 			
			} ; 
			

	      if($blocked eq "F") { 
				print "     blocking $bip\n" ; 
				$block = "/sbin/iptables -p tcp -A INPUT -s $bip/24  -d 0/0 --dport 25 -j DROP" ; 

	 			system("$block") ; 	
				open(ACTION,">>$actionfile") ; 
				printf ACTION "$block  # |$timestamp|$relayname\n",$relayname ;
				close ACTION ; 
				open(ACTION,">>$actionfile.history") ; 
				printf ACTION "$block  # |$timestamp|$basename|$relayname|\n",$relayname ;
				close ACTION ; 
				
				&killip($relayip) ; 
				
				print "\n" ; 
				
		  } ; 

			  
		  
		  
} ;



sub countip {
$sourceip{"$relayip"} = $sourceip{"$relayip"} + 1 ; 
$countip = $sourceip{"$relayip"} ; 
} ; 

sub countname {
   $basename = "" ; 
if (length($relayname) > 2) { 
	@n = split(/\./,$relayname) ; 
	
	foreach(@n) {
		if($relayname =~ /.com$/ | $relayname =~ /.net$/  | $relayname =~ /.org$/  | $relayname =~ /.edu$/ ) {
			$basename = $lastn . "." . $_ ; 
		} else {
			$basename = $lastnn . "." . $lastn . "." . $_ ; 
		}
		$lastnn = $lastn ; 
		$lastn = $_ ; 
	} ; 

	$sourcename{"$basename"} = $sourcename{"$basename"} + 1 ; 
	$countname = $sourcename{"$basename"} ; 

} ; 
} ; # end sub



sub loadwhitelist {
print "Loading Whitelist\n" ; 
	$w = 0 ; 
	open(WL,"$whitelist") ; 
	while(<WL>) { 
	chop ; 
	if (/^#/  | length($_) < 2) { } else {  #ignores commented lines 
	$whitelist[$w] = 	$_ ; 
	$w++ ; 
	} ; 	
	} ;
} ; 
sub loadwhiteip {
print "Loading WhiteIP\n" ; 
	$w = 0 ; 
	open(WL,"$whiteip") ; 
	while(<WL>) { 
	chop ; 
	if (/^#/ | length($_) < 2) { } else {  #ignores commented lines 
	$whiteip[$w] = 	$_ ; 
	$w++ ; 
	} ; 
	} ;
} ; 

sub loadblacklist {
print "Loading Blacklist\n" ; 
	$b = 0 ; 
	open(WL,"$blacklist") ; 
	while(<WL>) { 
	chop ; 
	if (/^#/  | length($_) < 2) { } else {  #ignores commented lines 
	$blacklist[$w] = 	$_ ; 
	$w++ ; 
	} ; 
	} ;
} ; # end sub

sub recycle { 
	print "\n--RECYCLING--\n\n" ; 
	%sourceip = () ; 
	%sourcename = () ; 
	&loadwhitelist ; 
	&loadwhiteip ; 
	&loadblacklist ; 


	open(ACTION,">$actionfile") ; 
	print ACTION "/sbin/iptables -F\n/sbin/iptables -F -t nat\n/sbin/iptables -X\n\n\n" ; 

   open(LTFILE,"$longterm") ; 
   while(<LTFILE>) { 
		print ACTION "$_" ; 
	} ; 
	close LTFILE ; 
	print ACTION "\n\n" ; 
	close ACTION ; 

	system("/spamdamn/iptables") ; 
	

	$relines = 0 ; 
} ; 



sub killip {
#Kills SMTP process connected to a blocked IP... freeing processes and ram and cpu
	system("ps -efww >/tmp/ps") ; 
	open(PS,"/tmp/ps") ; 
	while(<PS>) {
	  @p = split(/\s+/) ; 
	  if ($_ =~ /$relayip/) { 
		 print "        -killing process $p[1] - $p[9] $p[10]\n" ; 
		 system ("kill $p[1]") ; 
	  } ; 
	} ; 
	close PS ; 
} ; 


sub stats { 
$hms = 15 ; 
print "================TOP $hms LISTS=========================\n" ; 
%ssourceip = () ; 
%ssourcename = () ; 
open(IN,"$history") ; 
while(<IN>) { 
	chop ; 
	@d = split(/\|/) ; 
	$basename = $d[2] ; 
	$ssourcename{"$basename"} = $ssourcename{"$basename"} + 1 ; 
	$countname = $ssourcename{"$basename"} ; 
} ;
close IN ; 
$i = 0 ; 
print "BASE DOMAIN COUNT\n" ; 
 foreach $key (sort { $ssourcename{$b} <=> $ssourcename{$a} } keys %ssourcename) {
			if ($i < $hms) {      
           printf "%4d %s\n", $ssourcename{$key}, $key;
         } ;   	
         $i++ ; 
        }


%ssourceip = () ; 
%ssourcename = () ; 
%ssourcedomainname = () ; 


open(IN,"$history") ; 
while(<IN>) { 
	chop ; 
	@d = split(/\s+/) ; 
	@n = split(/\|/) ; 
 	$basedomainname = $n[2] ; 

	$basename = $d[6] ; 
	$ssourcename{"$basename"} = $ssourcename{"$basename"} + 1 ; 
	$ssourcedomainname{"$basename"} = $basedomainname; 
	$countname = $ssourcename{"$basename"} ; 
} ;

$i = 0 ; 
print "BASE IP COUNT\n" ; 
		 foreach $key (sort { $ssourcename{$b} <=> $ssourcename{$a} } keys %ssourcename) {
			if ($i < $hms) {      
           printf "%4d %18s %s\n", $ssourcename{$key},$key, $ssourcedomainname{$key};
			} ; 		
		  $i++ ; 
        }
        
} ;         
                                           
                                           

