#!/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.

# arpProtect FILENAME RED_BOUND MODE DROP INTERFACE

use IPC::Open2;
require "/etc/aLid/converter";
require "/etc/aLid/sendmail";
require "/etc/aLid/sendlog";

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

#print "arpProtect\n";
$filename = $ARGV[0];
#print "File: $filename\n";
$red_bound = $ARGV[1];
#print "Redemption Bound: $red_bound\n";
$mode = uc($ARGV[2]);
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[3]);
die "Error Option DROP: Insert drop or nodrop\n" if ($drop ne 'DROP' && $drop ne 'NODROP');
#print "Mode: $mode\n";
#print "Drop: $drop\n";
$interface = $ARGV[4];
#print "Interface: $interface\n";

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

# load email's list
@emails = qx { cat $fileEmails };
@file = qx{ cat $filename };

# load ip-mac associations
foreach $line ( @file ) {
	#print "Linea: $line";
	# get data
	if( $line =~ /^(((\d+\.\d+\.\d+\.\d+)(\s|\t)+)+)((\w\w\:\w\w\:\w\w\:\w\w\:\w\w\:\w\w(\s|\t|\,)+)+)$/ ) {
		#print "Read $1\n";
		@ip_list = split( /\s|\t|\,|\-/, $1 );

		# save mac list of associated ip - they have same index
		@mac_list = split( /\s|\t|\,|\-/, $5 );

		for( $i = 0; $i < @ip_list; $i++ ) {
			for( $j = 0; $j < @mac_list; $j++ ) {
				$tmp = uc($mac_list[$j]);
				$allowed{"$ip_list[$i]-$tmp"} = 1;
				#print "Loaded $ip_list[$i]-$tmp\n";
			}
		}
	}
	elsif( $line =~ /^(((\d+\.\d+\.\d+\.\d+)(\s|\t)+)+)\*/ ) {
		#print "Read $1\n";
		@ip_list = split( /\s|\t|\,|\-/, $1 );

		for( $i = 0; $i < @ip_list; $i++ ) {
			$subnet{"$ip_list[$i]\/32"} = "*";
			#print "Loaded $ip_list[$i]\/32\n";
		}
	}
	elsif( $line =~ /^(\d+\.\d+\.\d+\.\d+\/\d+)(\s|\t)+((\w\w\:\w\w\:\w\w\:\w\w\:\w\w\:\w\w(\s|\t|\,)*)+)/ ) {
		print STDERR "SUBNET WITH LIST $1\nLIST $3";
		@macList = split( /\s|\t|\,/, $3 );
		#print "Mac list @macList\n";
		for( $i = 0; $i < @macList; $i++ ) {
			$macList[$i] = uc($macList[$i]);
		}
		$subnet{$1} = \@macList;
	}
	elsif( $line =~ /^(\d+\.\d+\.\d+\.\d+\/\d+)(\s|\t)+\*(\s|\t)*$/ ) {
		print STDERR "FREE SUBNET $1\n";
		$subnet{$1} = "*";
	}
}

print "SubNet\n";
while( ($k,$v) = each (%subnet) ) {
	if( $v eq '*' ) {
		print "$k - *\n";
	}
	else {
		print "$k - @{$v}\n";
	}
}

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

for( $i = 0; $i < @file_ip_blocked; $i++ ){
	print "Read 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 };
		&sendlog( $fileLog, "ArpProtect Initialization: Redemption $1" );
	}
}

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

#open ARPOUT, "arpwatch -i $interface -d 2>&1 |";

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

while( $line = <ARPOUT> ) {
	#print "Read = $line";
	while( ( $k, $v ) = each ( %ip_blocked ) ){
		if( time - $v > $red_bound ) {
			qx{ iptables -D FORWARD -s $k -j DROP };
			qx{ iptables -D FORWARD -d $k -j DROP };
			delete $ip_blocked{$k};
			&sendlog( $fileLog, "ArpProtect: Redemption $k" );
		}
	}
	
	if( $line =~ /^arpwatch\:\sreused\sold\sethernet\saddress\s(\d+\.\d+\.\d+\.\d+)\s(\w+\:\w+\:\w+\:\w+\:\w+\:\w+)\s/ ) {
		$ip = $1;
		$mac = $2;
		$step = 5;
	}

	if( $step == 0 && $line =~ /^From:/ ) {
		#print "0: $line";
		$step = 1;
	}
	elsif( $step == 1 && $line =~ /^To:/ ) {
		#print "1: $line";
		$step = 2;
	}
	elsif( $step == 2 && $line =~ /^Subject:\s(.*)/ ) {
		print "2: $1\n";
		$val = $1;
		if($1 =~ /flip flop/ || $1 =~ /changed ethernet address/ || $1 =~ /new station/) {
			print STDERR "Detected $val\n";
			$step = 3;
		}
		else {
			$step = 0;
		}
	}
	elsif( $step == 3 && $line =~ /ip address: (\d+\.\d+\.\d+\.\d+)/ ) {
		#print "3: Ip $1\n";
		$ip = $1;
		$step = 4;
	}    
	elsif( $step == 4 && $line =~ /ethernet address:\s(\w+\:\w+\:\w+\:\w+\:\w+\:\w+)/ ) {
		#print "4: Mac $1\n";
		$mac = $1;
		$step = 5;
	}
	elsif( $step == 5 ) {
		$step = 0;
		$ip_search = $ip;
		$mac_search = $mac;
		$mac_search = uc($mac_search);

		print "BEFORE IP = $ip_search, MAC = $mac_search\n";

		$mac_search =~ s/^0:/00:/;
		$mac_search =~ s/:0:/:00:/;
		$mac_search =~ s/:0$/:00/;
		$mac_search =~ s/^(\w):/0\1:/;
		$mac_search =~ s/:(\w):/:0\1:/;
		$mac_search =~ s/:(\w)$/:0\1/;

		print "IP = $ip_search, MAC = $mac_search\n";

		if ( exists($ip_blocked{$ip_search}) && $allowed{"$ip_search-$mac_search"} == 1 ) {
			qx{ iptables -D FORWARD -s $ip_search -j DROP };
			qx{ iptables -D FORWARD -d $ip_search -j DROP };
			delete $ip_blocked{$ip_search};
			&sendlog( $fileLog, "ArpProtect: Redemption $ip_search" );
		}

		if ( $allowed{"$ip_search-$mac_search"} != 1 ){
			$ipOk = 0;
			$ip_specified = 0;
			while (($k, $v) = each (%allowed)) {
				if( $k =~ /^(.*)\-/ ) {
					if( $1 eq $ip_search ) {
						$ip_specified = 1;
					}
				}
			}			
			if( $ip_specified == 0 ) {
				#print STDERR "SUBNET\n";
				while( ($k,$v) = each (%subnet) ) {
					#print "$k - @{$v}\n";
					if( $k =~ /(.*)\/(\d+)$/ ) {
						#print STDERR "Net $1 - $2\n";
					
						if( &ipMatchNet( $ip_search, $1, $2) ) {
							print "Check $v eq '*'\n";
							if( $v eq '*' ){
								print STDERR "IP matches its network: $k Free network\n";
								$ipOk = 1;
							}
							else {
								foreach $el (@{$v}){
									#print "MAC CHECKING $el - search $mac_search\n";
									if( $el eq $mac_search ){
										print STDERR "Found MAC $mac_search in a list: (@{$v})\n";
										$ipOk = 1;
									}
								}
							}
						}
					}
				}
			}
			if ( $ipOk == 1 && exists($ip_blocked{$ip_search}) ) {
				qx{ iptables -D FORWARD -s $ip_search -j DROP };
				qx{ iptables -D FORWARD -d $ip_search -j DROP };
				delete $ip_blocked{$ip_search};
				&sendlog( $fileLog, "ArpProtect: Redemption $ip_search" );
			}
			elsif ( $ipOk == 0 && not exists($ip_blocked{$ip_search}) ) {
				# ip with a new mac
				print STDERR "Mac spoofing: Ip $ip_search with $mac_search isn't in list\n";
				# scrivere sul log
				if ($drop eq 'DROP' ) {
					$ip_blocked{$ip_search} = time;
					open(IPBLOCK, ">$fileBlocked");
					# refresh ip_blocked
					while (($k, $v) = each(%ip_blocked)){
						print STDERR "WRITE ON FILE: $k $time_blocked{$k}\n";	
						print IPBLOCK "$k $ip_blocked{$k}\n";
					}
					close(IPBLOCK);
					qx{ iptables -I FORWARD -d $ip_search -j DROP };
					qx{ iptables -I FORWARD -s $ip_search -j DROP };
				}
				if( $mode eq 'LOG' || $mode eq 'ALL' ) {
					&sendlog( $fileLog, "ArpProtect: Ip-Mac's association not found. MAC=$mac_search IP=$ip_search" );
				}
				if( $mode eq 'MAIL' || $mode eq 'ALL' ) {
					$message = "ArpProtect\n\n";
					$currDate = &getTime();
					$message .= "Data Detection: $currDate\n";
					$message .= "Event: Mac Spoofing\n";
					$message .= "Redemption Bound: $red_bound\n\n";
					$message .= "Ip:$ip_search Mac:$mac_search isn't in list\n";
					# send email
					foreach $email( @emails ){
						$mail = $email;
						chomp($mail);
						&sendmail( "root\@mat.unical.it", $email, "Attempt of Mac Spoofing", $message);
						if( $mode eq 'ALL' ) {
							&sendlog( $fileLog, "ArpProtect: Sent email to $mail" );
						}
					}
				}
			}
		}
	}
}
