#!/usr/bin/perl -w
#    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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# For any questions related to this software, please write to:
#
# Alessandro Dotti
# v. Verne, 6
# 40128 Bologna ITALY
#
# or email to: alessandro.dotti@libero.it
#==============================================================================
#
# adcfw-log: firewall logs analyzer/summarizer
#
# Copyright (C) 2002-2003 Alessandro Dotti Contra <alessandro.dotti@libero.it>
#
# usage: adcfw-log [OPTIONS] [logfile]
#
# OPTIONS:
#	-h, --help			prints help
#	--destination-host <host>	analyze only entries with <host> as destination host
#	--detailed			prints a more detailed <summary> (requires -s)
#	--in-interface <interface>	analyze only packets received from <interface>
#	--out-interface <interface>	analyze only packets transmitted through <interface>
#	--prefix <prefix>		analyze only entries which match user defined <prefix>
#	--protocol <protocol>		analyze only tcp|udp|icmp related entries	
#	-r, --report <report>		prints report <report>
#	-s, --summary <summary>		prints summary <summary>
#	--service <port num.>		analyze only entries with <port num.> as destination
#					port
#	--source-host <host>		analyze only entries with <host> as source host
#	--version			show version number
#
# AVAILABLE REPORTS
#	raw:			protocol indipendent raw report (default)
#	source_host:		source host based report
#	destination_host:	destination host based report
#	service:		service based report (tcp and udp only)
#
# AVAILABLE SUMMARIES
#	source_host:		source host based summary
#	destination_host:	destination host based summary
#	service:		service based summary (tcp and udp only)
#	prefix:			prefix based summary
#==============================================================================

use strict;

#
# Modules
#

use Getopt::Long;

# Modules configuration

Getopt::Long::Configure qw(no_ignore_case no_auto_abbrev);


#==============================================================================
# Functions
#==============================================================================

sub print_help();		# Prints help
sub print_version();		# Prints version number
sub print_report($$);		# Prints output report
sub get_packet_info($);		# Gets packet informations from a log entry
sub build_header($$;$);		# Builds header for reports/summaries
sub filter_entry($$$);		# Test if a log entry should be filtered or not
sub build_report_infos($$$);	# Rearranges packet informations for specific report
sub brief_tcp_flags($);		# Format tcp flags information for printing
sub print_summary($$);		# Prints output summary
sub build_summary_infos($$$$);	# Rearranges packet informations for specific summary

#==============================================================================
# Constants
#==============================================================================

my $VERSION = "0.10.0";	# Version number

# Regexp for netfilter log lines

my $NF_REGEXP      = '(.*\d+:\d+:\d+)\s.*\skernel:\s(.*)IN=(.*)\sOUT=(.*?)\s(.*?)SRC=(\d+\.\d+\.\d+\.\d+)\sDST=(\d+\.\d+\.\d+\.\d+)\s.*\sID=(\d+)\s.*PROTO=(TCP|UDP|ICMP)';
my $NF_TCP_REGEXP  = 'SPT=(\d+)\sDPT=(\d+)\sWINDOW=(.*)\sRES=(0x\w+)\s(.+)\sURGP=(.*)';
my $NF_UDP_REGEXP  = 'SPT=(\d+)\sDPT=(\d+)';
my $NF_ICMP_REGEXP = 'TYPE=(\d+)';

# Descriptions of icmp packet types

my %ICMP_DESC =	(
		"0"	=> "Echo reply",
		"3"	=> "Destination unreachable",
		"4"	=> "Source quench",
		"5"	=> "Redirect",
		"8"	=> "Echo",
		"11"	=> "Time exceeded",
		"12"	=> "Parameter problem",
		"13"	=> "Timestamp",
		"14"	=> "Timestamp reply",
		"15"	=> "Information request",
		"16"	=> "Information reply"
);

#==============================================================================
# Options parsing
#==============================================================================

my $DEST_HOST     = 0;		# Destination host
my $DETAILED	  = 0;		# Detail level for summaries
my $IN_IFACE	  = "";		# Input interface
my $OUT_IFACE	  = "";		# Output interface
my $PREFIX	  = 0;		# User defined prefix
my $PRINT_HELP    = 0;		# Print help
my $PRINT_VERSION = 0;		# Print version number
my $PROTOCOL_ONLY = 0;		# Report only tcp|udp|icmp entries (default all)
my $SERVICE	  = 0;		# Service (destination port)
my $SOURCE_HOST   = 0;		# Source host
my $REPORT        = "";		# Report to print (raw as default)
my $SUMMARY	  = "";		# Summary to print (source host as default)

my $LOGFILE	  = "-";	# Logfile to parse (stdin as default)

unless ( GetOptions (
		"destination-host=s"	=> \$DEST_HOST,
		"detailed"		=> \$DETAILED,
		"h"			=> \$PRINT_HELP,
		"help"			=> \$PRINT_HELP,
		"in-interface=s"	=> \$IN_IFACE,
		"out-interface=s"	=> \$OUT_IFACE,
		"prefix=s"		=> \$PREFIX,
		"protocol=s"		=> \$PROTOCOL_ONLY,
		"r=s"			=> \$REPORT,
		"report=s"		=> \$REPORT,
		"s=s"			=> \$SUMMARY,
		"summary=s"		=> \$SUMMARY,
		"service=i"		=> \$SERVICE,
		"source-host=s"		=> \$SOURCE_HOST,
		"version"		=> \$PRINT_VERSION
	))
	{ print_help(); exit 1; }

if ($PRINT_HELP)    { print_help(); exit 0; }
if ($PRINT_VERSION) { print_version(); exit 0; }

# Check for (optional) parameters

if ((scalar @ARGV) > 1) { die "Wrong number of parameters\n"; }

# Which file to parse (specified or stdin?)

$LOGFILE = $ARGV[0] if scalar(@ARGV);

#
# Report or summary (not both!)
#

if ($REPORT && $SUMMARY) { die "Can't print reports and summaries at the same time!\n"; }

# If nor summary neither report are specified, raw report will be printed

$REPORT = "RAW" unless ($REPORT || $SUMMARY);

#
# Protocol based filtering
#

if ($PROTOCOL_ONLY)
{
	$PROTOCOL_ONLY !~ /^(tcp|udp|icmp)$/i and die "$PROTOCOL_ONLY: unrecognized protocol\n";
	$PROTOCOL_ONLY = uc($PROTOCOL_ONLY);
}

#
# Source host based fitering
#

if ($SOURCE_HOST && ($SOURCE_HOST !~ /^\d+\.\d+\.\d+\.\d+$/)) { die "Source host specification is not in the correct format\n"; }

#
# Destination host based filtering
#

if ($DEST_HOST && ($DEST_HOST !~ /^\d+\.\d+\.\d+\.\d+$/)) { die "Destination host specification is not in the correct format\n"; }

#
# Destination port (service) based filtering
#

if ($SERVICE && ! ($SERVICE > 0)) { die "Invalid service specified\n"; }

#
# Choose specified report
#

if ($REPORT && $REPORT !~ /^(	raw|
				raw_icmp|
				source_host|
				source_host_icmp|
				destination_host|
				destination_host_icmp|
				service
			)$/ix)
			{ die "$REPORT: unknown report\n"; }

$REPORT = uc($REPORT);

# ICMP filter requires specific reports

$REPORT = "RAW_ICMP"         		if (($REPORT eq "RAW") && ($PROTOCOL_ONLY eq "ICMP"));
$REPORT = "SOURCE_HOST_ICMP" 		if (($REPORT eq "SOURCE_HOST") && ($PROTOCOL_ONLY eq "ICMP"));
$REPORT = "DESTINATION_HOST_ICMP" 	if (($REPORT eq "DESTINATION_HOST") && ($PROTOCOL_ONLY eq "ICMP"));

#
# Choose specified summary...
#

if ($SUMMARY && $SUMMARY !~ /^(	source_host|
				destination_host|
				service|
				prefix
			)$/ix)
			{ die "$SUMMARY: unknown summary\n"; }

$SUMMARY = uc($SUMMARY);

# ... and check details level

if ($DETAILED)
{
	die "Option --detailed can only be used with summaries.\n" unless $SUMMARY;
	$SUMMARY .= "_DETAILED";
}

#==============================================================================
# Parse log file and gather informations
#==============================================================================

my @packets;
my %packet;

open (LOG, "$LOGFILE") or die "Unable to open $LOGFILE: $!\n";

my $counter = 0;	# Packets counter

while (<LOG>)
{
	# Skip comments and blank lines

	next if /(^#|^$)/;

	if (/$NF_REGEXP/)	# Netfilter log entry
	{
		# Get packet informations

		%packet = get_packet_info($_);
		
		# Apply filter rules

		$PROTOCOL_ONLY 	&& next if filter_entry(\%packet, "PROTO", $PROTOCOL_ONLY);
		$SOURCE_HOST 	&& next if filter_entry(\%packet, "SHOST", $SOURCE_HOST);
		$DEST_HOST 	&& next if filter_entry(\%packet, "DHOST", $DEST_HOST);
		$SERVICE	&& next if filter_entry(\%packet, "DPORT", $SERVICE);
		$PREFIX		&& next if filter_entry(\%packet, "PREFIX", $PREFIX);
		$IN_IFACE	&& next if filter_entry(\%packet, "IN-IFACE", $IN_IFACE);
		$OUT_IFACE	&& next if filter_entry(\%packet, "OUT-IFACE", $OUT_IFACE);
		
		# Store packet informations
		
		%{$packets[$counter]} = %packet;
		$counter++;
	}
}

close (LOG);

#==============================================================================
# Print report/summary
#==============================================================================

print_report ($REPORT, \@packets) if $REPORT;
print_summary ($SUMMARY, \@packets) if $SUMMARY;

exit 0;

#==============================================================================
# Functions
#==============================================================================

sub print_help()
{
#
# Prints help
#

print <<__HELP__;
adcfw-log: firewall logs analyzer/summarizer

usage: adcfw-log [OPTIONS] [logfile]

OPTIONS:
  -h, --help		      prints help
  --destination-host <host>   analyze only entries with <host> as destination
  			      host
  --detailed		      prints a more detailed <summary> (requires -s)
  --in-interface <interface>  analyze only packets received from <interface>
  --out-interface <interface> analyze only packets transmitted through
  			      <interface>
  --prefix <prefix>	      analyze only entries which match user defined
  			      <prefix>
  --protocol <protocol>	      analyze only tcp|udp|icmp related entries
  -r, --report <report>       prints report <report>
  -s, --summary <summary>     prints summary <summary>
  --service <port num.>	      analyze only entries with <port num.> as
  			      destination port
  --source-host <host>	      analyze only entries with <host> as source host
  --version		      show version number

AVAILABLE REPORTS
  raw:			protocol indipendent raw report (default)
  source_host:		source host based report
  destination_host: 	destination host based report
  service:		service based report (tcp and udp only)

AVAILABLE SUMMARIES
  source_host:		source host based summary
  destination_host:	destination host based summary
  service:		service based summary (tcp and upd only)
  prefix:		prefix based summary

Please report bugs to <alessandro\@hyboria.org>
__HELP__
}

sub print_version()
{
#
# Prints version number
#

print "adcfw-log $VERSION\n";
}

sub get_packet_info($)
{
#
# Get packet informations from a log entry
#
# return an hash with the packet informations

my $entry = $_[0];	# Log entry

my %packet; # single packet informations

# DATE		date/time the packet was logged
# PREFIX	prefix for the log line
# IN-IFACE	input interface
# OUT-IFACE	output interface
# MAC		mac adresses informations
# SHOST		source host
# DHOST		destination host
# ID		packet unique ID (shared by fregments if fragmented)
# PROTO		protocol
# SPORT		source port (tcp and upd only)
# DPORT		destination port (tcp and upd only)
# ICMP-TYPE	type of icmp packet
# TCP-FLAGS	additional tcp flags

%packet = ();

$entry =~ /$NF_REGEXP/;

#
# Gather protocol indipendent informations
#

$packet{"DATE"}		= $1;
$packet{"PREFIX"}	= $2;
$packet{"IN-IFACE"}	= $3;
$packet{"OUT-IFACE"}	= $4;
$packet{"MAC"}		= $5;
$packet{"SHOST"}	= $6;
$packet{"DHOST"}	= $7;
$packet{"ID"}		= $8;
$packet{"PROTO"}	= $9;

#
# Safe defaults for protocol specific informations
#

$packet{"SPORT"}	= "";
$packet{"DPORT"}	= "";
$packet{"ICMP-TYPE"}	= "";
$packet{"TCP-FLAGS"}	= "";

#
# Gather protocol specific informations
#

SWITCH:
{
	if ($packet{"PROTO"} eq "TCP")		# TCP log entry
	{
		$_ =~ /$NF_TCP_REGEXP/;
		$packet{"SPORT"}     = $1;
		$packet{"DPORT"}     = $2;
		$packet{"TCP-FLAGS"} = $5;

		last SWITCH;
	}

	if ($packet{"PROTO"} eq "UDP")		# UDP log entry
	{
		$_ =~ /$NF_UDP_REGEXP/;
		$packet{"SPORT"} = $1;
		$packet{"DPORT"} = $2;

		last SWITCH;
	}

	if ($packet{"PROTO"} eq "ICMP")		# ICMP log entry
	{
		$_ =~ /$NF_ICMP_REGEXP/;
		$packet{"ICMP-TYPE"} = $1;

		last SWITCH;
	}
}

return %packet;
}

sub build_header($$;$)
{
#
# Builds header
#

my $header_type = $_[0];	# Report or summary
my $header_desc = $_[1];	# Report/Summary description
my $entries     = $_[2];	# Number of entries

my $header = "adcfw-log - " . $header_desc . " " . $header_type;
$header .= ": $entries entries" if $entries;
$header .= "\n";

# Protocol based filtering

if ($PROTOCOL_ONLY) { $header .= "* Protocol " . $PROTOCOL_ONLY . "\n"; }

# Source host based filtering

if ($SOURCE_HOST) { $header .= "* Source host: " . $SOURCE_HOST . "\n"; }

# Destination host based filtering

if ($DEST_HOST) { $header .= "* Destination host: " . $DEST_HOST . "\n"; }

# Service based filtering

if ($SERVICE) { $header .= "* Service (destination port): " . $SERVICE . "\n"; }

# Prefix based filtering

if ($PREFIX) { $header .= "* Prefix: " . $PREFIX . "\n"; }

# Input interface based filtering

if ($IN_IFACE) { $header .= "* Input interface: " . $IN_IFACE . "\n"; }

# Output interface based filtering

if ($OUT_IFACE) { $header .= "* Output interface: " . $OUT_IFACE . "\n"; }

return $header;
}

sub filter_entry($$$)
{
#
# Tests if a log entry should be filtered or not
#
# return 1 if the entry should be filtered

# Legal filter types:
#	PROTO:		protocol
#	SHOST:		source host
#	DHOST:		destination
#	DPORT:		destination port (service)
#	PREFIX:		user defined prefix
#	IN-IFACE:	input interface
#	OUT-IFACE:	output interface

my $packetref	 = $_[0];	# Reference to packet informations (hash)
my $filter_type  = $_[1];
my $filter_value = $_[2];

return 1 if ($filter_type eq "PROTO"     && $$packetref{"PROTO"}     ne $filter_value);
return 1 if ($filter_type eq "SHOST"     && $$packetref{"SHOST"}     ne $filter_value);
return 1 if ($filter_type eq "DHOST"     && $$packetref{"DHOST"}     ne $filter_value);
return 1 if ($filter_type eq "DPORT"     && $$packetref{"DPORT"}     ne $filter_value);
return 1 if ($filter_type eq "PREFIX"    && $$packetref{"PREFIX"}    ne $filter_value);
return 1 if ($filter_type eq "IN-IFACE"  && $$packetref{"IN-IFACE"}  ne $filter_value);
return 1 if ($filter_type eq "OUT-IFACE" && $$packetref{"OUT-IFACE"} ne $filter_value);

return 0;
}

sub build_report_infos($$$)
{
#
# Rearranges packet informations for specific report
#

my $report_type = $_[0];	# Type of report
my $packets_ref = $_[1];	# Packets informations (array)

my $infos_ref	= $_[2];

# Report specific informations (hash)
#
# Informations are store in the form of: item{key}[infos]
#
# key can be:
#	source host (for source_host report)
#	destination host (for destination based report)
#	service (for service based report)
#
# info is an array of (complete) packets informations

my $entries = scalar @$packets_ref;

SWITCH:
{
	if ($report_type eq "SOURCE_HOST")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $shost = ${@$packets_ref[$entry]}{"SHOST"};
			push (@{$$infos_ref{$shost}}, @{$packets_ref}[$entry]);
		}
		last SWITCH;
	}

	if ($report_type eq "DESTINATION_HOST")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $dhost = ${@$packets_ref[$entry]}{"DHOST"};
			push (@{$$infos_ref{$dhost}}, @{$packets_ref}[$entry]);
		}
		last SWITCH;
	}

	if ($report_type eq "SERVICE")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			next if ${@$packets_ref[$entry]}{"PROTO"} eq "ICMP";

			my $service = ${@$packets_ref[$entry]}{"DPORT"};
			push (@{$$infos_ref{$service}}, @{$packets_ref}[$entry]);
		}
		last SWITCH;
	}
}
}

sub build_summary_infos($$$$)
{
#
# Rearranges packet informations for specific summary
#

my $summary_type = $_[0];	# Type of summary
my $packets_ref  = $_[1];	# Packets informations (array)

my $infos_ref	 = $_[2];	# Summary informations (hash)
 
# Entries counter (as reference).
# Can be a scalar (simple summary) or a hash (detailed summary).
# In the latter case structure is: {key}<n of entries>

my $entries_ref	 = $_[3];	

# Summary specific informations (hash)
#
# Informations are store in the form of: item{key}<value>
# Key is a report specific information, value can be a scalar representing
# packets count, or an array of complex lines of tab separated values (report
# dependent).
# The first form is used in simple summaries, the latter in detailed summaries.
# 
#
# source host summary:
#	key: 	source host
#	scalar:	packets count
#	lines:	<packets count>\t<destination host>\t<service> 
#
# destination host summary:
# 	key:	destination host
# 	scalar:	packets count
# 	lines:	<packets count>\t<source host>\t<service>
#
# prefix summary:
#	key:	prefix
#	scalar:	packets count
#	lines:	<packet count>\t<source host>\t<destination host>
#
# service summary:
#	key:	service
#	scalar:	packets count
#	lines:	<packet count>\t<service>\t<destination host>

my $entries = scalar @$packets_ref;

my %infos;

# Temporary hash for summarizing packets informations when a detailed summary
# is requested.
# The format is {key}{value}[{value}...]
#
# source host detailed summary
# key:		source host
# value #1:	destination host
# value #2:	service/icmp type
#
# destination host detailed summary
# key:		destination host
# value #1:	source host
# value #2:	service/icmp type
#
# prefix detailed summary
# key:		prefix
# value #1:	source host
# value #2:	destination host
#
# service detailed summary
# key:		service
# value #1:	source host
# value #2:	destination host

SWITCH:
{
	if ($summary_type eq "SOURCE_HOST")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $shost = ${@$packets_ref[$entry]}{"SHOST"};
			$$infos_ref{$shost}++;
		}

		$$entries_ref = $entries;

		last SWITCH;
	}

	if ($summary_type eq "SOURCE_HOST_DETAILED")
	{
		# summarize packet informations
		
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $shost = ${@$packets_ref[$entry]}{"SHOST"};
			my $dhost = ${@$packets_ref[$entry]}{"DHOST"};

			my $type;
			
			$type  = ${@$packets_ref[$entry]}{"DPORT"} if ${@$packets_ref[$entry]}{"PROTO"} =~ /^(TCP|UDP)$/;
			$type  = $ICMP_DESC{${@$packets_ref[$entry]}{"ICMP-TYPE"}} if ${@$packets_ref[$entry]}{"PROTO"} =~ /^ICMP$/;

			$infos{$shost}{$dhost}{$type}++;
		}

		#
		# Build lines
		# 

		foreach my $shost (sort keys %infos)
		{
			my $line;		# single line
			my @lines;		# all the lines of the summary
			my $entries = 0;	# N of entries for this host

			foreach my $dhost (sort keys %{$infos{$shost}})
			{
				foreach my $type (sort keys %{$infos{$shost}{$dhost}})
				{
					$line = $infos{$shost}{$dhost}{$type} . "\t" . $dhost . "\t" . $type;
					push(@lines, $line);

					$entries += $infos{$shost}{$dhost}{$type};
				}
			}

			@{$$infos_ref{$shost}} = @lines;

			$$entries_ref{$shost} = $entries
		}
		
		last SWITCH;
	}

	if ($summary_type eq "DESTINATION_HOST")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $dhost = ${@$packets_ref[$entry]}{"DHOST"};
			$$infos_ref{$dhost}++;
		}

		$$entries_ref = $entries;

		last SWITCH;
	}

	if ($summary_type eq "DESTINATION_HOST_DETAILED")
	{
		# summarize packet informations
		
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $dhost = ${@$packets_ref[$entry]}{"DHOST"};
			my $shost = ${@$packets_ref[$entry]}{"SHOST"};

			my $type;
			
			$type  = ${@$packets_ref[$entry]}{"DPORT"} if ${@$packets_ref[$entry]}{"PROTO"} =~ /^(TCP|UDP)$/;
			$type  = $ICMP_DESC{${@$packets_ref[$entry]}{"ICMP-TYPE"}} if ${@$packets_ref[$entry]}{"PROTO"} =~ /^ICMP$/;

			$infos{$dhost}{$shost}{$type}++;
		}

		#
		# Build lines
		# 

		foreach my $dhost (sort keys %infos)
		{
			my $line;		# single line
			my @lines;		# all the lines of the summary
			my $entries = 0;	# N of entries for this host

			foreach my $shost (sort keys %{$infos{$dhost}})
			{
				foreach my $type (sort keys %{$infos{$dhost}{$shost}})
				{
					$line = $infos{$dhost}{$shost}{$type} . "\t" . $shost . "\t" . $type;
					push(@lines, $line);

					$entries += $infos{$dhost}{$shost}{$type};
				}
			}

			@{$$infos_ref{$dhost}} = @lines;

			$$entries_ref{$dhost} = $entries
		}
		
		last SWITCH;
	}

	if ($summary_type eq "PREFIX")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $prefix = ${@$packets_ref[$entry]}{"PREFIX"};
			$prefix = "[No prefix]" unless $prefix;
			$$infos_ref{$prefix}++;
		}

		$$entries_ref = $entries;

		last SWITCH;
	}

	if ($summary_type eq "PREFIX_DETAILED")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			my $prefix = ${@$packets_ref[$entry]}{"PREFIX"};
			my $shost  = ${@$packets_ref[$entry]}{"SHOST"};
			my $dhost  = ${@$packets_ref[$entry]}{"DHOST"};

			$infos{$prefix}{$shost}{$dhost}++;
		}
		
		#
		# Build lines
		# 

		foreach my $prefix (sort keys %infos)
		{
			my $line;		# single line
			my @lines;		# all the lines of the summary
			my $entries = 0;	# N of entries for this host

			foreach my $shost (sort keys %{$infos{$prefix}})
			{
				foreach my $dhost (sort keys %{$infos{$prefix}{$shost}})
				{
					$line = $infos{$prefix}{$shost}{$dhost} . "\t" . $shost . "\t" . $dhost;
					push(@lines, $line);

					$entries += $infos{$prefix}{$shost}{$dhost};
				}
			}

			@{$$infos_ref{$prefix}} = @lines;
			$$entries_ref{$prefix} = $entries
		}
		last SWITCH;
	}
	
	if ($summary_type eq "SERVICE")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			next if ${@$packets_ref[$entry]}{"PROTO"} !~ /^(TCP|UDP)$/;
			my $dport = ${@$packets_ref[$entry]}{"DPORT"};
			$$infos_ref{$dport}++;
		}

		$$entries_ref = $entries;

		last SWITCH;
	}

	if ($summary_type eq "SERVICE_DETAILED")
	{
		for (my $entry = 0; $entry < $entries; $entry++)
		{
			next if ${@$packets_ref[$entry]}{"PROTO"} !~ /^(TCP|UDP)$/;
			
			my $dport = ${@$packets_ref[$entry]}{"DPORT"};
			my $shost = ${@$packets_ref[$entry]}{"SHOST"};
			my $dhost = ${@$packets_ref[$entry]}{"DHOST"};

			$infos{$dport}{$shost}{$dhost}++;
		}
		
		#
		# Build lines
		# 

		foreach my $dport (sort keys %infos)
		{
			my $line;		# single line
			my @lines;		# all the lines of the summary
			my $entries = 0;	# N of entries for this host

			foreach my $shost (sort keys %{$infos{$dport}})
			{
				foreach my $dhost (sort keys %{$infos{$dport}{$shost}})
				{
					$line = $infos{$dport}{$shost}{$dhost} . "\t" . $shost . "\t" . $dhost;
					push(@lines, $line);

					$entries += $infos{$dport}{$shost}{$dhost};
				}
			}

			@{$$infos_ref{$dport}} = @lines;
			$$entries_ref{$dport}  = $entries
		}
		last SWITCH;
	}
}
}

sub brief_tcp_flags($)
{
#
# Format tcp flags informations for printing
#

my $tcp_flags = $_[0];	# Tcp flags

my @flags = split (' ', $tcp_flags);

my $flags_info = "";

foreach my $flag (@flags) { $flags_info = " " . substr ($flag, 0, 1) . $flags_info; }

return $flags_info;
}

sub print_report($$)
{
#
# Prints output report
#
# returns 1 unless there is something to print

my $report_type   = $_[0];	# Type of report
my $packets_ref   = $_[1];	# Packets informations (array)

my $entries = scalar @$packets_ref;	# Number of entries

return 1 unless $entries;

my $OUTPUT_CHANNEL = "REP_" . $report_type;
my $OLDFH = select($OUTPUT_CHANNEL);

#
# Choose report type
#

my $header;	# Report description
my $packet;	# Single packet informations (reference)
my %infos;	# Rearranged report specific informations

my $shost;	# Source host
my $dhost;	# Destination host
my $service;	# Service

SWITCH:
{  
	if (($report_type eq "RAW") || ($report_type eq "RAW_ICMP"))	# RAW report
	{
		open (REP_RAW, "> /dev/stdout")      if ($report_type eq "RAW");
		open (REP_RAW_ICMP, "> /dev/stdout") if ($report_type eq "RAW_ICMP");

		# Build header

		$header = build_header("REPORT", $REPORT, $entries);

		for (my $index = 0; $index < $entries; $index++) { $packet = @{$packets_ref}[$index]; write; $- = 100; }
		last SWITCH;
	}

	if (($report_type eq "SOURCE_HOST") || ($report_type eq "SOURCE_HOST_ICMP"))	# Source host based report
	{
		open (REP_SOURCE_HOST, "> /dev/stdout")      if ($report_type eq "SOURCE_HOST");
		open (REP_SOURCE_HOST_ICMP, "> /dev/stdout") if ($report_type eq "SOURCE_HOST_ICMP");
		
		# Gather report informations

		build_report_infos("SOURCE_HOST",$packets_ref, \%infos); 
		
		foreach my $key (sort keys %infos)
		{
			$shost = $key;

			$- = 0; # Print report header for each host
			
			my $entries = scalar @{$infos{$shost}};

			# Build header

			$header = build_header("REPORT", $REPORT, $entries);
		
			for (my $index = 0; $index < $entries; $index++) { $packet = @{$infos{$shost}}[$index]; write; $- = 100; }
		}
		last SWITCH;
	}

	if (($report_type eq "DESTINATION_HOST") || ($report_type eq "DESTINATION_HOST_ICMP"))	# Destination host based report
	{
		open (REP_DESTINATION_HOST, "> /dev/stdout")      if ($report_type eq "DESTINATION_HOST");
		open (REP_DESTINATION_HOST_ICMP, "> /dev/stdout") if ($report_type eq "DESTINATION_HOST_ICMP");
		
		# Gather report informations

		build_report_infos("DESTINATION_HOST",$packets_ref, \%infos); 
		
		foreach my $key (sort keys %infos)
		{
			$dhost = $key;

			$- = 0; # Print report header for each host
			
			my $entries = scalar @{$infos{$dhost}};

			# Build header

			$header = build_header("REPORT", $REPORT, $entries);
		
			for (my $index = 0; $index < $entries; $index++) { $packet = @{$infos{$dhost}}[$index]; write; $- = 100; }
		}
		last SWITCH;
	}

	if ($report_type eq "SERVICE") # Service based report (tcp and udp only)
	{
		open (REP_SERVICE, "> /dev/stdout");

		# Gather report informations

		build_report_infos("SERVICE", $packets_ref, \%infos);

		foreach my $key (sort keys %infos)
		{
			$service = $key;

			$- = 0; # Print report header for each service
			
			my $entries = scalar @{$infos{$service}};

			# Build header

			$header = build_header("REPORT", $REPORT, $entries);
		
			for (my $index = 0; $index < $entries; $index++) { $packet = @{$infos{$service}}[$index]; write; $- = 100; }
		}
		last SWITCH;
	}
}

select($OLDFH);
print "\n";

return 0;

#
# Reports definitions
#

format REP_RAW_TOP =

@*
$header

DATE/TIME       IN     OUT    SRC HOST        SPORT DST HOST        DPORT  PROTO
================================================================================
.

format REP_RAW =
@<<<<<<<<<<<<<< @<<<<< @<<<<< @<<<<<<<<<<<<<< @>>>> @<<<<<<<<<<<<<< @>>>>  @>>>>
$$packet{"DATE"},$$packet{"IN-IFACE"},$$packet{"OUT-IFACE"},$$packet{"SHOST"},$$packet{"SPORT"},$$packet{"DHOST"},$$packet{"DPORT"},$$packet{"PROTO"}
.

format REP_RAW_ICMP_TOP =

@*
$header

DATE/TIME       IN     OUT    SRC HOST        DST HOST        ICMP TYPE
================================================================================
.

format REP_RAW_ICMP =
@<<<<<<<<<<<<<< @<<<<< @<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<
$$packet{"DATE"},$$packet{"IN-IFACE"},$$packet{"OUT-IFACE"},$$packet{"SHOST"},$$packet{"DHOST"},$ICMP_DESC{$$packet{"ICMP-TYPE"}}
.

format REP_SOURCE_HOST_TOP =

@*
$header

Source host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
             $shost

DATE/TIME       SPORT IN     OUT    DST HOST        DPORT PROTO   TCP FLAGS/TYPE
================================================================================
.

format REP_SOURCE_HOST =
@<<<<<<<<<<<<<< @>>>> @<<<<< @<<<<< @<<<<<<<<<<<<<< @>>>> @>>>> @>>>>>>>>>>>>>>>
$$packet{"DATE"},$$packet{"SPORT"},$$packet{"IN-IFACE"},$$packet{"OUT-IFACE"},$$packet{"DHOST"},$$packet{"DPORT"},$$packet{"PROTO"},($$packet{"PROTO"} eq "ICMP")?$ICMP_DESC{$$packet{"ICMP-TYPE"}}:brief_tcp_flags($$packet{"TCP-FLAGS"})
.

format REP_SOURCE_HOST_ICMP_TOP =

@*
$header

Source host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
             $shost

DATE/TIME       IN     OUT    DST HOST        PROTO ICMP TYPE
================================================================================
.

format REP_SOURCE_HOST_ICMP =
@<<<<<<<<<<<<<< @<<<<< @<<<<< @<<<<<<<<<<<<<< @>>>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$$packet{"DATE"},$$packet{"IN-IFACE"},$$packet{"OUT-IFACE"},$$packet{"DHOST"},$$packet{"PROTO"},$ICMP_DESC{$$packet{"ICMP-TYPE"}}
.

format REP_DESTINATION_HOST_TOP =

@*
$header

Destination host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
             	  $dhost

DATE/TIME       DPORT SRC HOST        SPORT IN     OUT    PROTO   TCP FLAGS/TYPE
================================================================================
.                               
                                
format REP_DESTINATION_HOST =
@<<<<<<<<<<<<<< @>>>> @<<<<<<<<<<<<<< @>>>> @<<<<< @<<<<< @>>>> @>>>>>>>>>>>>>>>
$$packet{"DATE"},$$packet{"DPORT"},$$packet{"SHOST"},$$packet{"SPORT"},$$packet{"IN-IFACE"},$$packet{"OUT-IFACE"},$$packet{"PROTO"},($$packet{"PROTO"} eq "ICMP")?$ICMP_DESC{$$packet{"ICMP-TYPE"}}:brief_tcp_flags($$packet{"TCP-FLAGS"})
.

format REP_DESTINATION_HOST_ICMP_TOP =

@*
$header

Destination host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                  $dhost

DATE/TIME       SRC HOST        IN     OUT    PROTO ICMP TYPE
================================================================================
.                               
                                
format REP_DESTINATION_HOST_ICMP =
@<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<< @<<<<< @>>>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<
$$packet{"DATE"},$$packet{"SHOST"},$$packet{"IN-IFACE"},$$packet{"OUT-IFACE"},$$packet{"PROTO"},$ICMP_DESC{$$packet{"ICMP-TYPE"}}
.

format REP_SERVICE_TOP =

@*
$header

Service: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
         $service

                                                                            TCP
DATE/TIME       IN     OUT    SRC HOST        SPORT DST HOST       PROTO   FLAGS
================================================================================
.
                                
format REP_SERVICE =
@<<<<<<<<<<<<<< @<<<<< @<<<<< @<<<<<<<<<<<<<< @>>>> @<<<<<<<<<<<<<< @>>> @>>>>>>
$$packet{"DATE"},$$packet{"IN-IFACE"},$$packet{"OUT-IFACE"},$$packet{"SHOST"},$$packet{"SPORT"},$$packet{"DHOST"},$$packet{"PROTO"},brief_tcp_flags($$packet{"TCP-FLAGS"})
.
}

sub print_summary($$)
{
#
# Prints output summary
#
# returns 1 unless there is something to print

my $summary_type  = $_[0];	# Type of summary
my $packets_ref   = $_[1];	# Packets informations (array)

return 1 unless scalar @$packets_ref;

my $OUTPUT_CHANNEL = "SUM_" . $summary_type;
my $OLDFH = select($OUTPUT_CHANNEL);

#
# Choose summary type
#

my $header;	# Summary description
my $packet;	# Single packet informations (reference)
my %infos;	# Rearranged summary specific informations

my $shost;	# Source host
my $dhost;	# Destination host
my $prefix;	# Prefix
my $type;	# Service/icmp type
my $dport;	# Destination port aka tcp/udp service
my $count;	# Packets count

SWITCH:
{  
	if ($summary_type eq "SOURCE_HOST")	# SOURCE_HOST summary
	{
		open (SUM_SOURCE_HOST, "> /dev/stdout");
		
		my $entries = 0;	# Number of entries

		# Gather summary informations

		build_summary_infos("SOURCE_HOST",$packets_ref, \%infos, \$entries); 

		# Build summary header

		$header = build_header("SUMMARY", $SUMMARY, $entries);

		# Print summary
		
		foreach my $key (sort { $infos{$b} <=> $infos{$a} } keys %infos) { $shost = $key; write; }
		
		last SWITCH;
	}
	
	if ($summary_type eq "SOURCE_HOST_DETAILED")	# SOURCE_HOST detailed summary
	{
		open (SUM_SOURCE_HOST_DETAILED, "> /dev/stdout");
		
		my %entries;	# Number of entries (per source host)

		# Gather summary informations

		build_summary_infos("SOURCE_HOST_DETAILED",$packets_ref, \%infos, \%entries); 

		foreach my $key (sort keys %infos)
		{
			$shost = $key;

			# Build summary header
		
			$header = build_header("SUMMARY", $SUMMARY, $entries{$shost});
		
			$- = 0; # Print summary header for each host

			# Lines must be sorted numerically, so we need to consider only the beginning
			# of the line (which is numeric for sure -- packet count)

			foreach my $line (sort {substr($b, 0, index($b, "\t")) <=> substr($a, 0, index($a, "\t"))} @{$infos{$shost}})
			{
				($count, $dhost, $type) = split ("\t", $line);
				write; $- = 100;
			}
		}
		last SWITCH;
	}

	if ($summary_type eq "DESTINATION_HOST")	# DESTINATION_HOST summary
	{
		open (SUM_DESTINATION_HOST, "> /dev/stdout");
		
		my $entries = 0;	# Number of entries

		# Gather summary informations

		build_summary_infos("DESTINATION_HOST",$packets_ref, \%infos, \$entries); 

		# Build summary header

		$header = build_header("SUMMARY", $SUMMARY, $entries);

		# Print summary
		
		foreach my $key (sort { $infos{$b} <=> $infos{$a} } keys %infos) { $dhost = $key; write; }
		
		last SWITCH;
	}
	
	if ($summary_type eq "DESTINATION_HOST_DETAILED")	# DESTINATION_HOST detailed summary
	{
		open (SUM_DESTINATION_HOST_DETAILED, "> /dev/stdout");
		
		my %entries;	# Number of entries (per source host)

		# Gather summary informations

		build_summary_infos("DESTINATION_HOST_DETAILED",$packets_ref, \%infos, \%entries); 

		foreach my $key (sort keys %infos)
		{
			$dhost = $key;

			# Build summary header
		
			$header = build_header("SUMMARY", $SUMMARY, $entries{$dhost});
		
			$- = 0; # Print summary header for each host

			# Lines must be sorted numerically, so we need to consider only the beginning
			# of the line (which is numeric for sure -- packet count)

			foreach my $line (sort {substr($b, 0, index($b, "\t")) <=> substr($a, 0, index($a, "\t"))} @{$infos{$dhost}})
			{
				($count, $shost, $type) = split ("\t", $line);
				write; $- = 100;
			}
		}
		last SWITCH;
	}

	if ($summary_type eq "PREFIX")	# PREFIX summary
	{
		open (SUM_PREFIX, "> /dev/stdout");
		
		my $entries = 0;	# Number of entries

		# Gather summary informations

		build_summary_infos("PREFIX", $packets_ref, \%infos, \$entries); 

		# Build summary header
		
		$header = build_header("SUMMARY", $SUMMARY, $entries);
		 
		# Print summary
		
		foreach my $key (sort { $infos{$b} <=> $infos{$a} } keys %infos) { $prefix = $key; write; }
		
		last SWITCH;
	}

	if ($summary_type eq "PREFIX_DETAILED")	# PREFIX detailed summary
	{
		open (SUM_PREFIX_DETAILED, "> /dev/stdout");
		
		my %entries;	# Number of entries (per prefix)

		# Gather summary informations

		build_summary_infos("PREFIX_DETAILED",$packets_ref, \%infos, \%entries); 

		foreach my $key (sort keys %infos)
		{
			$prefix = $key;
		
			# Build summary header
		
			$header = build_header("SUMMARY", $SUMMARY, $entries{$prefix});

			$- = 0; # Print summary header for each prefix

			# Lines must be sorted numerically, so we need to consider only the beginning
			# of the line (which is numeric for sure -- packet count)

			foreach my $line (sort {substr($b, 0, index($b, "\t")) <=> substr($a, 0, index($a, "\t"))} @{$infos{$prefix}})
			{
				($count, $shost, $dhost) = split ("\t", $line);
				write; $- = 100;
			}
		}
		last SWITCH;
	}
	
	if ($summary_type eq "SERVICE")		# SERVICE summary
	{
		open (SUM_SERVICE, "> /dev/stdout");
		
		my $entries = 0;	# Number of entries

		# Gather summary informations

		build_summary_infos("SERVICE", $packets_ref, \%infos, \$entries); 

		# Build summary header
		
		$header = build_header("SUMMARY", $SUMMARY, $entries);
		 
		# Print summary
		
		foreach my $key (sort { $infos{$b} <=> $infos{$a} } keys %infos) { $dport = $key; write; }
		
		last SWITCH;
	}

	if ($summary_type eq "SERVICE_DETAILED")	# SERVICE detailed summary
	{
		open (SUM_SERVICE_DETAILED, "> /dev/stdout");
		
		my %entries;	# Number of entries (per prefix)

		# Gather summary informations

		build_summary_infos("SERVICE_DETAILED",$packets_ref, \%infos, \%entries); 

		foreach my $key (sort keys %infos)
		{
			$dport = $key;
		
			# Build summary header
		
			$header = build_header("SUMMARY", $SUMMARY, $entries{$dport});

			$- = 0; # Print summary header for each prefix

			# Lines must be sorted numerically, so we need to consider only the beginning
			# of the line (which is numeric for sure -- packet count)

			foreach my $line (sort {substr($b, 0, index($b, "\t")) <=> substr($a, 0, index($a, "\t"))} @{$infos{$dport}})
			{
				($count, $shost, $dhost) = split ("\t", $line);
				write; $- = 100;
			}
		}
		last SWITCH;
	}
}

select($OLDFH);
print "\n";

return 0;

#
# Summary definitions
#

format SUM_SOURCE_HOST_TOP =

@*
$header

TOTAL PACKETS     SOURCE HOST
=================================================
.

format SUM_SOURCE_HOST =
@>>>>>>>>>>>>     @<<<<<<<<<<<<<<
$infos{$shost},$shost
.

format SUM_SOURCE_HOST_DETAILED_TOP =

@*
$header

Source host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
             $shost

TOTAL PACKETS     DESTINATION HOST   SERVICE/ICMP TYPE
===============================================================================
.

format SUM_SOURCE_HOST_DETAILED =
@>>>>>>>>>>>>     @<<<<<<<<<<<<<<<   @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$count,$dhost,$type
.

format SUM_DESTINATION_HOST_TOP =

@*
$header

TOTAL PACKETS     DESTINATION HOST
=================================================
.

format SUM_DESTINATION_HOST =
@>>>>>>>>>>>>     @<<<<<<<<<<<<<<
$infos{$dhost},$dhost
.

format SUM_DESTINATION_HOST_DETAILED_TOP =

@*
$header

Destination host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                  $dhost

TOTAL PACKETS     SOURCE HOST        SERVICE/ICMP TYPE
===============================================================================
.

format SUM_DESTINATION_HOST_DETAILED =
@>>>>>>>>>>>>     @<<<<<<<<<<<<<<<   @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$count,$shost,$type
.

format SUM_PREFIX_TOP =

@*
$header

TOTAL PACKETS     PREFIX
===========================================================
.

format SUM_PREFIX =
@>>>>>>>>>>>>     @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$infos{$prefix},$prefix
.

format SUM_PREFIX_DETAILED_TOP =

@*
$header

Prefix: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        $prefix

TOTAL PACKETS    SOURCE HOST         DESTINATION HOST
=====================================================
.

format SUM_PREFIX_DETAILED =
@>>>>>>>>>>>>    @<<<<<<<<<<<<<<<    @<<<<<<<<<<<<<<<
$count,$shost,$dhost
.

format SUM_SERVICE_TOP =

@*
$header

TOTAL PACKETS     SERVICE
===========================================================
.

format SUM_SERVICE =
@>>>>>>>>>>>>     @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$infos{$dport},   $dport
.

format SUM_SERVICE_DETAILED_TOP =

@*
$header

Service: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
         $dport

TOTAL PACKETS    SOURCE HOST         DESTINATION HOST
=====================================================
.

format SUM_SERVICE_DETAILED =
@>>>>>>>>>>>>    @<<<<<<<<<<<<<<<    @<<<<<<<<<<<<<<<
$count,$shost,$dhost
.
}
