#!/usr/bin/perl
#
#  Generate report from output of `zcat ipaudit/data/1999-11-03* | hostpair |`
#  in HTML format
#
#
#  Changes:
#
#  Version 0.1
#    x - Sort Incoming scans by remote/local host in ascending order
#    x - Sort Outgoing scans by local/remote host in ascending order
#    x - Sort Chat sessions by local host
#  Version 0.2
#    x - Mark 0 value for incoming data (as is done for outgoing)
#  Version 0.3
#      - Write both text and html reports
#      - Change command line syntax
#      - Print total number of connections
#  Version 0.4
#      - Correct Scan code error from Verion 0.3
#      - Exempt special hosts from outgoing scans
#  Version 0.5
#      - Change reports to
#          Busiest Connections (old)
#          Busiest Local  Hosts - Traffic
#          Busiest Local  Hosts - Contacts
#          Busiest Remote Hosts - Traffic
#          Busiest Remote Hosts - Contacts
#   Version 0.6
#      -   Put Busiest Connection report last
#   Version 0.7  (30 Jun 1999)
#      -   For scan reports count only *non-responding* hosts
#   Version 0.8  (1 Jul 1999)
#      -   As previously, but only consider 'tcp' packets
#   Version 0.9  (8 Sep 1999)
#      -   Revert to using 'tcp' and 'udp' packets for scan
#      -   Add section on multiport scans
#   Version 0.91 (2 Dec 1999)
#      -   Only store hostpairs (and only those with total traffic exceeding certain value)
#
#  Wish List
#      - Only include Ports <= 1024 in scans.
#      - Print protocol/port info for scan reports
#

#  Daily Report
#
#  Version 0.1
#  Version 0.2
#     IP Names

#
#  Net constants
#
###   $PORT_WWW=80;
###   
###   $PROT_ICMP=1;
###   $PROT_TCP=6;
###   $PROT_UDP=17;


#  For DNS names
use Socket;

#
#  Control constants
#

#  Number of lines in TOP repotes
$NTOP = 20;

#  Number of ports connections to be considered Multiport scan
###   $MULTI = 127;



#
#  Font size for table entries
#
$fnt="<font size=-1>";


#  Find Dir for config file
$Path = &FindConfig("", "ipaudit.cfg");

if (open (INFILE, "$Path")) {
	while (<INFILE>) {
 		next if (/^\s*#/);
		chop;
		($Name,$Value) = split(/[= ]+/);
		#  Remove double and single quotes
		if ($Value=~/^"(.+)"$/) { $Value = $1; }
		if ($Value=~/^'(.+)'$/) { $Value = $1; }
		$Conf{$Name} = $Value;
	}
	close (INFILE);
} else {
	print "Cannot open file ipaudit.cfg.\n";
}


#
#  Reformat list of local nets
#
$Conf{'LOCAL_NET'} =~ s/(["'])(.*)\1/$2/;            #  Strip quote marks
@LocalNet = split /[^0-9\.]+/, $Conf{"LOCAL_NET"};
die "$0: No local nets found, did you set up the Envinronment Variables in \"$Path/ipaudit.cfg\"?\n" if ($#LocalNet<0);
for (@LocalNet) {
	@Octet = split /\./;
	$_ = sprintf "%03d", $Octet[0];
	for ($i=1; $i<=$#Octet; $i++) {
		$_ = $_ . "." . sprintf "%03d", $Octet[$i];
	}
}


#
#  Get current date string from comand line
#
if (@ARGV>=1) {
	$date = $ARGV[0];
} else {
	$date = "";
}

#
#  Read output from text dump file
#

while (<STDIN>) {

	#  Skip comments
	next if /^\s*#/;

	#  truncate \n
	chomp;

	#  Split line by :
	($Loc,$Rem,$In,$Out,undef,undef) = split(/[ \t]+/);

	#  Switch info for ip1,ip2 to to get ip1 as local
	$Location1 = &GetLocation($Loc);
	$Location2 = &GetLocation($Rem);
	($Loc,$Rem,$In,$Out,$Location1,$Location2) = ($Rem,$Loc,$Out,$In,$Location2,$Location1) if ("L" eq $Location2);

	# If locations pair isn't local-remote (ie. remote-remote or local-local then reject)
	next if ($Location1 eq "R" || $Location2 eq "L");

	$Total = $In + $Out;


	#  Store traffic by single host (local and remote)
	$LocalTrafficOut {$Loc} += $Out;
	$RemoteTrafficOut{$Rem} += $Out;
	$LocalTrafficIn  {$Loc} += $In;
	$RemoteTrafficIn {$Rem} += $In;

	$LocalCount {$Loc} ++ if ($In ==0);  #  Increment local  host -> remote host count if no response
	$RemoteCount{$Rem} ++ if ($Out==0);  #  Increment remote host -> local  host count if no response

}
	#  End of input loop

#  Get total traffic Local and Remote
for ( keys %LocalTrafficOut)  { 
	$Outgoing          += $LocalTrafficOut{$_};
	$LocalTraffic {$_} += $LocalTrafficOut{$_}; 
	}
for ( keys %LocalTrafficIn )  { 
	$Incoming          += $LocalTrafficIn {$_};
	$LocalTraffic {$_} += $LocalTrafficIn {$_}; 
	}
for ( keys %RemoteTrafficOut) { 
	$RemoteTraffic{$_} += $RemoteTrafficOut{$_}; 
	}
for ( keys %RemoteTrafficIn ) { 
	$RemoteTraffic{$_} += $RemoteTrafficIn {$_}; 
	}


# Number of local / remote hosts
$NumLocalHosts  = scalar keys %LocalTraffic;
$NumRemoteHosts = scalar keys %RemoteTraffic;

&PrintHTMLReport;

#
#  END OF MAIN ROUTINE
#



########################################################################
########################################################################
#
#  HTML Report
#
sub PrintHTMLReport {

	#  Print header and summary
	&PrintSummary;


	#  REPORT:  Busiest Hosts
	#  List 'NTOP' Top Local/Remote hosts by amount of traffic

	#  Group two Local and Remote busiest host reports into one table

	print STDOUT "<table cellspacing=20><tr><td>\n" if defined($DoubleUp);


	@SortLocal = sort { $LocalTraffic{$b} <=> $LocalTraffic{$a} } keys %LocalTraffic;

	$#SortLocal = ($NTOP-1) if ($#SortLocal>$NTOP-1);

	#  Print links to anchors
	&PrintTraffic 
		(
		"Busiest LOCAL Machines - Traffic (bytes)", 
		\@SortLocal, 
		\%LocalTraffic,
		\%LocalTrafficIn,
		\%LocalTrafficOut
		);


	print STDOUT "</td><td>\n" if defined ($DoubleUp);

	@SortRemote = sort { $RemoteTraffic{$b} <=> $RemoteTraffic{$a} } keys %RemoteTraffic;



	$#SortRemote = ($NTOP-1) if ($#SortRemote>$NTOP-1);


	&PrintTraffic 
	(
	"Busiest REMOTE Machines - Traffic (bytes)", 
	\@SortRemote, 
	\%RemoteTraffic,
	\%RemoteTrafficIn,
	\%RemoteTrafficOut
	);

	print STDOUT "</td></tr></table>\n" if defined($DoubleUp);


	#  REPORT:  Possible Incoming/Outgoing Multi-Host scans

	#  Group two Local and Remote scan host reports into one table
	print STDOUT "<table cellspacing=20><tr><td>\n" if defined($DoubleUp);


	#  Incoming Scans
	@SortRemoteCount  = 
		sort { $RemoteCount{$b} <=> $RemoteCount{$a} } keys %RemoteCount;
	$#SortRemoteCount = ($NTOP-1) if ($#SortRemoteCount>$NTOP-1);
	&PrintScan ("Possible Incoming-Scan Hosts", \@SortRemoteCount, \%RemoteCount);

	print STDOUT "</td><td>\n" if defined($DoubleUp);

	#  Outgoing scans
	@SortLocalCount   = 
		sort { $LocalCount {$b} <=> $LocalCount {$a} } keys %LocalCount;
	$#SortLocalCount = ($NTOP-1) if ($#SortLocalCount>$NTOP-1);
	&PrintScan ("Possible Outgoing-Scan Hosts", \@SortLocalCount, \%LocalCount);

	print STDOUT "</td></tr></table>\n" if defined($DoubleUp);

}



sub PrintSummary
{
print  STDOUT "<pre><b>\n";
printf STDOUT 
  "Hosts:    Local   [%14s]   Remote  [%14s]\n", 
   &ic($NumLocalHosts), &ic($NumRemoteHosts);
printf STDOUT
  "Traffic:  Total   [%14s]   Incoming[%14s]   Outgoing[%14s]",
  &ic($Incoming+$Outgoing), &ic($Incoming), &ic($Outgoing);
print STDOUT "</b></pre>\n";
}



sub PrintScan
	{
	my ($Message, $Key, $Count) = @_;

	%IPName = &GetIPName (@$Key);

	print STDOUT "<br><b><tt>$Message</tt></b>\n";
	print STDOUT "<table cellpadding=2 cellspacing=0 border=2>\n";
	print STDOUT "<tr><th><tt>IP Address</tt></th>";
	print STDOUT "<th><tt>IP Name</tt></th>";
	print STDOUT "<th><tt>Contacted Hosts</tt></th></tr>\n";
	for (@$Key)
		{

		print STDOUT "<tr><td>$fnt", &iplink($date,$_), "</td>";
		print STDOUT "<td>$fnt$IPName{$_}</td>";
		print STDOUT "<td>$fnt$$Count{$_}</td></tr>\n";
		}
	print STDOUT "</table>";
	}


sub PrintTraffic
	{
	my ($Message, $KeyArray, $TotalHash, $IncomingHash, $OutgoingHash) = @_;


	print STDOUT "<br><b><tt>$Message</tt></b>\n";
	print STDOUT "<table cellpadding=2 cellspacing=0 border=2>\n";
	print STDOUT "<tr>\n";
	print STDOUT "<th><tt>IP Address</tt></th>\n";
	print STDOUT "<th><tt>IP Name</tt></th>\n";
	print STDOUT "<th><tt>Total Traffic</tt></th>\n";
	print STDOUT "<th><tt>Incoming</tt></th>\n";
	print STDOUT "<th><tt>Outgoing</tt></th></tr>\n";

	%IPName = &GetIPName (@$KeyArray);

	for (@$KeyArray)
		{
		$Total = &ic ($$TotalHash{$_});
		if (!defined($$IncomingHash{$_})) {
			$Incoming = 0;
		} else {
			$Incoming = &ic($$IncomingHash{$_});
			}
		if (!defined($$OutgoingHash{$_})) {
			$Outgoing = 0;
		} else {
			$Outgoing = &ic($$OutgoingHash{$_});
		}
			
		print STDOUT "<tr><td>$fnt ", &iplink($date,$_), "</td>";
		print STDOUT "<td>$fnt $IPName{$_}</td>";
		print STDOUT "<td align=right>$fnt$Total</td>";
		print STDOUT "<td align=right>$fnt$Incoming</td>";
		print STDOUT "<td align=right>$fnt$Outgoing</td></tr>\n";
		}
	print STDOUT "</table>";
	}


#
#  Convert positive whole number from nnnnnn to n,nnn,nnn etc.
#
sub ic
	{
	my ($string) = @_;
	my ($i,$pre,$n);

	return "" if (!defined($string));

	$n=length $string;
	
	#  No commas needed 
	return $string if ($n<4);

	#  Get between 1-2 digits before first comma
	$i = (($n-1) % 3) + 1;
	$pre = substr($string,0,$i);
	for (; $i<$n; $i+=3)
		{
		$pre .= "," . substr($string,$i,3);
		}
	$pre;
	}

sub GetIPName {
	my (@iplist) = @_;
	my (%ipname);

	for (@iplist) {
		$ipname{$_} = &getdns($_);
	}
	return %ipname;
}


sub GetLocation {
        my ($IP) = @_;

        #  Test for local network
        for (@LocalNet) {
                return "L" if (substr($IP,0,length) eq $_);
        }

        #  Test for 0.0.0.0  or 255.255.255.255 or 224.x.x.x    
        #  These are "Unknown" addresses
        return "U" if ($IP eq 000.000.000.000);
        return "U" if ($IP eq 255.255.255.255);
        return "U" if ($IP =~ /^224\./);

        #  Anything else is remote address
        return "R";
}


#  Convert ip to link to cgi-script to look up traffic for that ip
sub iplink {
	my ($date,$ip) = @_;
	return $ip if $date eq "";
	return "<a href=$Conf{CGI_BIN}/SearchIpauditData?date=$date&ip=$ip&sort=0>$ip</a>";
}


#  Convert ip to dns name
sub getdns {
	my ($name) = @_;
	$name = sprintf "%d.%d.%d.%d", split(/\./,$name);
	$name = inet_aton($name);
	$name = gethostbyaddr($name, AF_INET) if defined($name);
	return defined($name) ? $name : "&nbsp";
}


#  Search upward from $Dir looking for $File
sub FindConfig {
        my ($Dir, $File) = @_;

        $Dir  = `pwd`         if $Dir  eq "";
        $File = "ipaudit.cfg" if $File eq "";

        chomp $Dir;

        while (! -f "$Dir/$File" && $Dir ne "") {
                $Dir=~s/\/[^\/]+$//;
        }

        die "Cannot find config file\n"
                if ! -f "$Dir/$File";
        return "$Dir/$File";
}
