#!/usr/bin/perl

# Copyright (C) 2010 Andrea Martire
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

use DateTime;

require "/etc/aLid/sendmail";
require "/etc/aLid/sendlog";
require "/etc/aLid/time";

qx{ touch /etc/aLid/run.info };
open INFO, ">>/etc/aLid/run.info";
print INFO "$$\n";
close INFO;

$evt_name = $ARGV[0];
$range = $ARGV[1];
$evt_bound = $ARGV[2];
$red_bound = $ARGV[3];
$freqEmail = $ARGV[4];
$fileAllowed = $ARGV[5];
$mode = uc($ARGV[6]);
die "Error Option MODE: Insert mail, log, all or nothing\n" if ( $mode ne 'MAIL' && $mode ne 'LOG' && $mode ne 'ALL' && $mode ne 'NOTHING' );
$drop = uc($ARGV[7]);
die "Error Option DROP: Insert drop or nodrop\n" if ($drop ne 'DROP' && $drop ne 'NODROP');

$fileEmails = "/etc/aLid/emails.conf";
$fileLog = "/etc/aLid/log";
$fileBlocked = "/etc/aLid/ip_blocked_$evt_name";

for( $i = 8; $i < @ARGV; $i = $i + 2 ) {
	#print STDERR "ER=$ARGV[$i] TARGET=$ARGV[$i+1]\n";
	if( $ARGV[$i+1] eq 'I' || $ARGV[$i+1] eq 'O' ) {
		push ( @expr, $ARGV[$i] );
		push ( @target, $ARGV[$i+1] );
	}
}

#print "Event: $evt_name\n";
#print "Range (sec): $range\n";
#print "Event's Bound: $evt_bound\n";
#print "Redemption's Bound: $red_bound\n";
#print "Mail's Frequency: $freqEmail\n";
#print "File allowed: $fileAllowed\n";
#print "File Blocked: $fileBlocked\n";
#print "File Emails: $fileEmails\n";
#print "File Log: $fileLog\n";
#print "Mode: $mode\n";
#print "Drop: $drop\n";

#for( $i = 0; $i < @expr; $i++ ) {
#	print STDERR "Reg. Expr. = $expr[$i] \t Target = $target[$i]\n";
#}

# load blocked ip 
qx{ touch $fileBlocked };
@file_ip_blocked = qx{ cat $fileBlocked };

for( $i = 0; $i < @file_ip_blocked; $i++ ){
	print "Reading from file: $file_ip_blocked[$i]\n";
	if( $file_ip_blocked[$i] =~ /^(.*)\s(\d+)/ ) {
		qx{ iptables -D FORWARD -s $1 -j DROP };
		qx{ iptables -D FORWARD -d $1 -j DROP };
		if( $mode eq 'ALL' || $mode eq 'LOG' ) {
			&sendlog( $fileLog, "$evt_name Initialization: Redemption ip=$1" );
		}
	}
}

qx{ rm $fileBlocked };
qx{ touch $fileBlocked };

# load allowed ip 
qx{ touch $fileAllowed };
@file_ip_allowed = qx{ cat $fileAllowed };

for( $i = 0; $i < @file_ip_allowed; $i++ ){
	#print "Reading from file: $file_ip_allowed[$i]\n";
	if( $file_ip_allowed[$i] =~ /^(.*)/ ) {
		$ip_allowed{$1} = 1;
		#print "ALLOWED $1\n";
	}
}

# create checking string
$checkString = '.*\s((\d+\.\d+\.\d+\.\d+\:\d+\-\d+\.\d+\.\d+\.\d+\:\d+)\-\d+\-0)';
for( $i = 1; $i < @expr; $i++ ) {
	$checkString .= '.*\s(\2\-\d+\-'."$i".')';
}
$checkString .= '.*';

#open LOGFILE, "tail --sleep-interval=0 -f /var/log/iptables.log|" || die "Error Opening the file: $fileInput\n";

$modified_blocked = 0;

while ( $line = <STDIN> ){

		$now_time = qx{ date -u };
		#print STDERR "NOW TIME = $now_time\n";

		if( $now_time =~ /\w\w\w\s(\w\w\w)\s+(\d+)\s(\d+)\:(\d+)\:(\d+)\s\w+\s(\d+)/ ) {
			$utc_t = DateTime->new( year => $6, month => &month_index($1), day => $2, 
									hour => $3, minute=> $4, second => $5, time_zone => 'Europe/Rome' )->utc_rd_as_seconds();
			#print "DATA = $dt\n";
		}
			
		if( keys( %time_blocked ) > 0 ) {
			while (($ip, $ip_time) = each(%time_blocked)){
				if( $utc_t - $ip_time >= $red_bound ) {
					$last_modify = $utc_t;
					delete ($ip_blocked{$ip});
					delete ($time_blocked{$ip});
					print STDERR "$evt_name Redemption $ip $utc_t - $ip_time >= $red_bound\n";
					qx{ iptables -D FORWARD -s $ip -j DROP };
					qx{ iptables -D FORWARD -d $ip -j DROP };
					if( $mode eq 'ALL' || $mode eq 'LOG' ) {
						&sendlog( $fileLog, "$evt_name Redemption ip=$ip" );
					}
				}
			}
		}
	
	$index = -1;
	for( $i = 0; $i < @expr; $i++ ) {
		#print STDERR "VERIFY MATCH: Espr. Reg. = $expr[$i] \t Target = $target[$i]\n$line";
		if( $line =~/$expr[$i]/ ) {
			$index = $i;
			#print STDERR "MATCH VERIFIED $index\n";
			#print STDERR "LINE = $line\n";
		}
	}
	if ( $index != -1 && $line =~ /^(\w+).*?(\d+)\s(\d\d)\:(\d\d)\:(\d\d)\s.*\sSRC=(\d+\.\d+\.\d+\.\d+)\sDST=(\d+\.\d+\.\d+\.\d+)\s.*\sSPT=(\d+)\sDPT=(\d+)\s.*$/ ){
		if( $ip_blocked{$6} == 1 ) {
			$last_modify = $utc_t;
			$time_blocked{$6} = $utc_t;
		}
		elsif ( $ip_allowed{$6} == 0 ) {
			$logMonth =$1; $logDay = $2; $hours = $3; $minutes = $4; $seconds = $5; $ipsource = $6; $ipdest = $7; $portsource = $8; $portdest = $9;
			#print STDERR "Read $ipsource:$portsource-$ipdest:$portdest\n";
			# Calculate system's year
			$system_year = qx< date +%Y >;
			chomp($system_year);
			$current_time = DateTime->new( year => $system_year, month => &month_index($logMonth), day => $logDay, 
											hour => $hours, minute => $minutes, second => $seconds, 
											time_zone => 'Europe/Rome' )->utc_rd_as_seconds();

			if( $target[$index] eq 'I' ){
				$detectedEvent{"$ipsource:$portsource-$ipdest:$portdest-$current_time-$index"} = $current_time;
			}
			elsif( $target[$index] eq 'O' ){
				$detectedEvent{"$ipdest:$portdest-$ipsource:$portsource-$current_time-$index"} = $current_time;
			}
	
			# delete event out temporal range to analyze
			while (($k, $v) = each(%detectedEvent))
			{
				if( $current_time - $v > $range ) {
					#print STDERR "Delete $k - $v - $current_time - $range\n";
					delete ($detectedEvent{$k});
				}
			}

			if( keys( %completeDetected ) > 0 ) {
				# delete event out temporal range to analyze
				while (($k, $v) = each(%completeDetected))
				{
					if( $utc_t - $v > $range ) {
						delete ($completeDetected{$k});
					}
				}
			}
	
			$eventString = '';

			if( $index == @expr - 1 ) {
				# order structure by value for preparing event's string
				foreach $key (sort (keys(%detectedEvent))) {
					#print STDERR "ORDER = $key $detectedEvent{$key}\n";
					$eventString .= " $key";
				}

				#print STDERR "EVENT STRING = $eventString\n";
				#print STDERR "CHECK STRING = $checkString\n";

				if( $eventString =~ /$checkString/ ) {
					#print STDERR "DETECTED IP $1\n";
					$completeDetected{$2} = $utc_t;
					#print STDERR "1 = $1\n";
					delete $detectedEvent{$1};
					for( $i = 3; $i < (@expr + 2); $i++ ){
						#print STDERR "$i = $$i\n";
						delete $detectedEvent{$$i};
					}
				}

				%report = (); # delete old structure
				undef %report;
				while(($info, $v) = each( %completeDetected )) {
					print STDERR "COMPLETED Event $info, $v\n";
					if( $info =~ /^(\d+\.\d+\.\d+\.\d+)\:/ ) {
						$report{$1}++;
					}
				}

				while (($k, $v) = each(%report)){
					print  STDERR "IP = $k Times = $v Bound $evt_bound\n";
					if($v > $evt_bound){
						#print STDERR "$k reached upper bound $evt_bound with $v tries\n";#TODO 
						while (($kData, $vData) = each(%completeDetected)){
							if( $kData =~ /^(\d+\.\d+\.\d+\.\d+)\:/ && $1 eq $k ) {
								delete $completeDetected{$kData};
							}
						}
						$last_modify = $utc_t;
						# add to bad ip's list
						$bad_ip_source = $k;
						$n_requests = $v;
						$time_requests = $current_time;
						#print "New bad ip $k\n";
						print STDERR "BLOCK: $k $evt_name $utc_t\n";
						if( $drop eq 'DROP' ) {
							qx{ iptables -I FORWARD -d $k -j DROP };
							qx{ iptables -I FORWARD -s $k -j DROP };
							if( $mode eq 'ALL' || $mode eq 'LOG' ) {
								&sendlog( $fileLog, "$evt_name Detected ip=$k times=$n_requests" );
							}
							$ip_blocked{$k} = 1;
							# insert with detecting time and not with logging time
							$time_blocked{$k} = $utc_t;
						}

						# if there is a bad ip
						if( $mode eq 'MAIL' || $mode eq 'ALL' ){
							if( $mailPreparing == 0 ) {
								# initialize mail
								$mailPreparingTime = $utc_t;
								$mailPreparing = 1;
								$message = "Event: $evt_name\n\n";
								$currDate = &getTime();
								$message .= "Data Detection: $currDate UTC\n";
								$message .= "Blocking Bound: $evt_bound\n";
								$message .= "Redemption Bound: $red_bound\n";
								$message .= "Range (sec): $range\n\n";
							}
							$message .= "IP Source: $bad_ip_source\t";
							$message .= "Requests: $n_requests\t";
							$message .= "Event Date: $logDay $logMonth $hours:$minutes:$seconds UTC\n";
						}
					}
				}
			}
		}
	}

	# if there is a mail still to send
	if( $mailPreparing == 1 && ($utc_t - $mailPreparingTime) >= $freqEmail ) {
		# loading email's list
		@emails = qx { cat $fileEmails };
		foreach $email( @emails ){
			print "Send EMAIL to $email\n";
			print "Message:\n$message\n";
			&sendmail( "eventdetective\@unical.it", $email, "Detected Event $evt_name", $message );
		}
		if ( $mode eq 'ALL' ) {
			&sendlog( $fileLog, "$evt_name sent an email notification" );
		}
		$mailPreparingTime = 0;
		$mailPreparing = 0;
		$message = "";
	}

	# refresh ip blocked's file
	if( $modified_blocked < $last_modify ) {
		$modified_blocked = $last_modify;
		open(IPBLOCK, ">$fileBlocked");
		while (($k, $v) = each(%ip_blocked)){
			#print "Write on file: $k $time_blocked{$k}\n";	
			print ">";
			print IPBLOCK "$k $time_blocked{$k}\n";
		}
		close(IPBLOCK);
	}

	# Reset data's structure
	$bad_ip_source = ''; 
	$n_requests = 0; 
	$time_requests = 0; 
}
