#!/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;
use IPC::Open2;
require "/etc/aLid/sendmail";
require "/etc/aLid/sendlog";
require "/etc/aLid/time";

#	arpCheck range bound redemption fileBlocked notify filter freqEmail interface

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

#print "ArpCheck\n";
$range = $ARGV[0];
#print "Range: $range\n";
$evt_bound = $ARGV[1];
#print "Event Bound: $evt_bound\n";
$red_bound = $ARGV[2];
#print "Redemption Bound: $red_bound\n";
$fileBlocked = $ARGV[3];
#print "Ip Blocked's File: $fileBlocked\n";
$mode = uc($ARGV[4]);
die "Error Option MODE: Insert mail, log, all or nothing\n" if ( $mode ne 'MAIL' && $mode ne 'LOG' && $mode ne 'ALL' && $mode ne 'NOTHING');
print "Mode: $mode\n";
$drop = uc($ARGV[5]);
die "Error Option DROP: Insert drop or nodrop\n" if ($drop ne 'DROP' && $drop ne 'NODROP');
print "Drop: $drop\n";
if( $mode eq 'ALL' || $mode eq 'MAIL' ) {
	$freqEmail = $ARGV[6];
	#print "Mail's Frequency (sec): $freqEmail\n";
	$interface = $ARGV[7];
	#print "Interface: $interface\n";
}
else {
	$interface = $ARGV[6];
	#print "Interface: $interface\n";
}

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

# load email from file
@emails = qx { cat $fileEmails };

$mailPreparingTime = 0;

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

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

qx{ rm $fileBlocked };

$pid = open2(ARPOUT, CHLD_IN, "arpwatch -i $interface -d");
print INFO "$pid\n";
close INFO;

$step = 0;
while( $line = <ARPOUT> ) {
	@file_ip_blocked = (); 
	undef @file_ip_blocked;

	$now_time = qx{ date -u };
	#print "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+)/ ) {
		#print "Month $1 day $2 hour $3 minute $4 sec $5 year $6\n";
		$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();
	}

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

	$modified = 0;

	for( $i = 0; $i < @ip_blocked; $i++ ){
		$diff = $utc_t - $time_blocked[$i];

		#print STDERR "IP=$ip_blocked[$i] TEMPO=$time_blocked[$i] NOW=$utc_t DIFF=$diff\n";
		if( $diff > $red_bound ) {
			print "Redemption $ip_blocked[$i] ArpCheck\n";
			qx{ iptables -D FORWARD -s $ip_blocked[$i] -j DROP };
			delete $ip_blocked[$i];
			delete $time_blocked[$i];
			if( $mode eq 'ALL' || $mode eq 'LOG' ) {
				&sendlog( $fileLog, "ArpCheck Redemption IP=$ip_blocked[$i]" );
			}
			$modified = 1;
		}
	}
	
	if ( $modified == 1 ) {
		open(IPBLOCK, ">$fileBlocked");
		for( $i = 0; $i < @ip_blocked; $i++ ){
			#print "WRITE on file: $ip_blocked[$i] $time_blocked[$i]\n";	
			print IPBLOCK "$ip_blocked[$i] $time_blocked[$i]\n";
		}
		close(IPBLOCK);	
	}
	
	if( $step == 0 && $line =~ /^From:/ ) {
		#print "0: $line";
		$step = 1;
	}
	if( $step == 1 && $line =~ /^To:/ ) {
		#print "1: $line";
		$step = 2;
	}
	if( $step == 2 && $line =~ /^Subject:\s(.*)/ ) {
		#print "2: $1\n";
		if($1 =~ /flip flop/) {
			#print "Detected flip flop\n";
			$step = 3;
		}
		else {
			$step = 0;
		}
	}
	if( $step == 3 && $line =~ /ip address: (\d+\.\d+\.\d+\.\d+)/ ) {
		#print "3: Ip $1\n";
		$ip = $1;
		$step = 4;
	}    
	if( $step == 4 && $line =~ /ethernet address:\s(\w+\:\w+\:\w+\:\w+\:\w+\:\w+)/ ) {
		#print "4: Mac $1\n";
		$newmac = $1;
		$step = 5;
	}
	if( $step == 5 && $line =~ /old ethernet address:\s(\w+\:\w+\:\w+\:\w+\:\w+\:\w+)/ ) {
		#print "5: Mac Old $1\n";
		$oldmac = $1;
		$step = 6;
	}
	if( $step == 6 && $line =~ /timestamp:\s(.*)\s\+/ ) {
		#print "6: TimeStamp $1\n";
		$time = $1;

		if( $time =~ /\,\s(\w+)\s(\d+)\,\s(\d+)\s(\d+)\:(\d+)\:(\d+)$/ ) {
			#print " Month $1 day $2 year $3 hour $4 minute $5 sec $6\n";
			$logMonth = $1; $logDay = $2; $logYear = $3; $hours = $4; $minutes = $5; $seconds = $6;
			$curr_time = DateTime->new( year => $logYear, month => &month_index($logMonth), day => $logDay, 
											hour => $hours, minute => $minutes, second => $seconds, 
											time_zone => 'Europe/Rome' )->utc_rd_as_seconds();
		}
		$step = 0;
		$dati{"$curr_time $ip $newmac $oldmac"}++;
		if( $mode eq 'LOG' || $mode eq 'ALL' ) {
			&sendlog( $fileLog, "ArpCheck: Detected Mac Address Flip Flop. MAC=$newmac OLD_MAC=$oldmac IP=$ip" );#	?
		}
	}

	# Rimuovo dati obsoleti
	while (($chiave, $valore) = each(%dati))
	{
		if( $chiave =~ /^(\d+)\s/ ) {
			$diff = $curr_time - $1;
			#print "$diff = $curr_time - $1\n";
			if( $diff > $range ) {
				delete ($dati{$chiave});
			}
		}
	}

	#while (($k, $v) = each(%dati)){
		#print "($k,$v)\n";
	#}
	#print "---------$utc_t--------------\n";freqEmail

	# new consumptive structure
	%report = (); # delete old structure
	undef %report;
	while (($chiave, $valore) = each(%dati)){
		if( $chiave =~ /(\d+\.\d+\.\d+\.\d+)/ ) {
			$report{"$1"}+=$valore;
		}
	}

	# delete old data
	$bad_ip_source = ''; 
	$n_requests = 0; 

	while (($k, $v) = each(%report)){
		#print "$k = $v flip flop\n";
		if ($v >= $evt_bound) {
			#print "reached upper bound $k $v\n";
			# delete data of found ip
			while (($kData, $valore) = each(%dati)){
				if( $kData =~ /(\d+\.\d+\.\d+\.\d+)/ && $1 eq $k ) {
					#print "delete $chiave $valore\n";
					delete $dati{$kData};
				}
			}
			# checked if it is already blocked
			$foundBlocked = 0;
			for( $i = 0; $i < @ip_blocked; $i++ ){
				if ( $ip_blocked[$i] eq $k ) {
					$foundBlocked = 1;
					# refresh blocking time
					$time_blocked[$i] = $$utc_t;
					open(IPBLOCK, ">$fileBlocked");
					for( $i = 0; $i < @ip_blocked; $i++ ){
						#print "WRITE on file: $ip_blocked[$i] $time_blocked[$i]\n";	
						print IPBLOCK "$ip_blocked[$i] $time_blocked[$i]\n";
					}
					close(IPBLOCK);	
				}
			}
			# if there isn't in ip blocking's set
			if( $foundBlocked == 0 ) {
				# add to bad ip's list
				$bad_ip_source = $k;
				$n_requests = $v;
				#print "new bad ip $k\n";
			}
		}
	}

	# Add new ip to list
	if ( $bad_ip_source ne '' ){
		print "BLOCK: $bad_ip_source\n";
		push( @ip_blocked, $bad_ip_source );
		push( @time_blocked, $utc_t );
		if( $drop eq 'DROP' ) {
			open(IPBLOCK, ">$fileBlocked");
			for( $i = 0; $i < @ip_blocked; $i++ ){
				#print STDERR "WRITE on file: $ip_blocked[$i] $time_blocked[$i]\n";	
				print IPBLOCK "$ip_blocked[$i] $time_blocked[$i]\n";
			}
			close(IPBLOCK);	
			qx{ iptables -I FORWARD -s $bad_ip_source -j DROP };
			if( $mode eq 'ALL' || $mode eq 'LOG' ) {
				&sendlog( $fileLog, "ArpCheck Blocking IP=$bad_ip_source" );
			}
		}
	}

	# if there is a bad ip
	if( $mode eq 'MAIL' || $mode eq 'ALL' ){
		if ( $bad_ip_source ne '' ) {
			if( $mailPreparing == 0 ) {
				# initialize mail
				$mailPreparingTime = time;
				$mailPreparing = 1;
				$message = "ArpCheck\n\n";
				$currDate = &getTime();
				$message .= "Data Detection: $currDate\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 .= "Flip Flop: $n_requests\t";
			$message .= "Event Date: $logYear $logDay $logMonth $hours:$minutes:$seconds\n";
		}
	}
	$time_now = time;
	#print "($time_now - $mailPreparingTime) >= $freqEmail\n";
	if( $mailPreparing == 1 && ($time_now - $mailPreparingTime) >= $freqEmail ) {
		foreach $email( @emails ){
			print "Send email a $email\n";
			print "Message:\n$message\n";
			&sendmail( "root\@mat.unical.it", $email, "Detected Flip Flop Abuse", $message );
			if ( $mode eq 'ALL' || $mode eq 'LOG') {
				&sendlog( $fileLog, "ArpCheck sent notification to: $email" );
			}
		}
		$mailPreparingTime = 0;
		$mailPreparing = 0;
		$message = "";
	}
}
