#!/usr/bin/perl -w 
#
# Description: Port Scan Attack Detector (psad)
# - uses ipchains/iptables logs.  Uses input from /var/log/psad/fwdata 
# generated by kmsgsd
#
# version 0.9.5
# Copyright (C) 1999-2001 Michael B. Rash (mbr@cipherdyne.com)
#
# Credits:  (see the CREDITS file)
#
#    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
#    USA
#
# TODO: 
#	- Make use of other logging options available in iptables to detect
#	  more tcp signatures.  (E.g. --log-tcp-options, --log-ip-options,
#	  --log-tcp-sequence, etc.) for better signature recognition.
#	- Allow ipchains to use udp signatures as well as tcp signatures that
#	  only require a syn packet to a port.
#	- Deal with the possibility that psad could eat lots of memory over
#	  time if $ENABLE_PERSISTENCE="Y". This should involve periodically
#	  undef'ing entries in %Scan (or maybe the entire hash), but this 
#	  should be done in a way that allows some scan data to persist.
#	- Put source and destination ip addresses back into psad_signatures.
#	- Ipfilter support on *BSD platforms.
#	- Re-write significant components (kmsgsd, diskmond, psadwatchd) in C.
#	- Possibly add a daemon to take into account ACK PSH, ACK FIN, RST etc.
#	  packets that the client may generate after the ip_conntrack module
#	  is reloaded.  Without anticipating such packets psad will interpret
#	  them as a belonging to a port scan.  NOTE: This problem is mostly
#	  corrected by the conntrack patch to the kernel.
#	- Improve check_firewall_rules() to check for a state rule (iptables) 
#	  since having such a rule greatly improves the quality of the data 
#	  stream provided to psad by kmsgsd since more packet types will be 
#	  denied without requiring overly complicated firewall rules to detect 
#	  odd tcp flag combinations.
#	- Investigate the possibility of passive OS fingerprinting by looking
#	  at TTL and other fields in the headers (good idea Jay).
#	- Enhance the check_firewall_rules() routine to correct any problems
#	  with the firewall ruleset.  This is part of the integration of psad 
#	  with Bastille Linux.
#	- (psad-init) make sure the init script will work on different Linux
#	  distros.
#	- perldoc
#
# Sample packet (rejected by ipchains)
# Dec 19 11:54:07 orthanc kernel: Packet log: input REJECT lo PROTO=1
# 10.0.0.4:3127.0.0.1:3 L=88 S=0xC0 I=49513 F=0x0000 T=255
#
# Sample tcp packet (rejected by iptables... --log-prefix = "DENY")
# Mar 11 13:15:52 orthanc kernel: DENY IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00
# SRC=127.0.0.1 DST=127.0.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=44847 
# DPT=35 WINDOW=32304 RES=0x00 SYN URGP=0

# Sample icmp packet rejected by iptables
# Nov 27 15:45:51 orthanc kernel: DENY IN=eth1 OUT= MAC=00:a0:cc:e2:1f:f2:00:20:78:10:70:e7:08:00
# SRC=192.168.10.20 DST=192.168.10.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 
# CODE=0 ID=61055 SEQ=256
#
# There may be a bug in iptables, since occasionally log entries are generated 
# by a long port scan like this (note there is no 'DPT' field): 
#   Mar 16 23:50:25 orthanc kernel: DENY IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 
#   SRC=127.0.0.1 DST=127.0.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=39935 
#   DINDOW=32304 RES=0x00 SYN URGP=0
#################################################################################################

### modules used by psad
use Psad;
use File::stat "stat";
use File::Copy;
use Getopt::Long "GetOptions";
use autouse 'Data::Dumper' => "Dumper";  ### this one is not used very much so don't load immediately
use autouse 'Socket';       ### not used until a scan is detected so don't load until it is needed
use Sys::Hostname "hostname";
use POSIX;

# use Sys::Syslog qw(openlog syslog closelog);   ### this does not seem to play nice with perl-5.005_03
# use POSIX qw(:signal_h :errno_h :sys_wait_h setsid);

### psad does not yet support 'strict refs' since several hashes have 
### $hsh{key} = "someval" and $hsh{key1}{key2} = "anotherval"... should
### probably fix this.
# use strict;
# no strict "refs";

### globals
use vars qw($VERSION %Scan $PRINT_SCAN_HASH $USE_IPCHAINS $USE_IPTABLES $DEBUG $HOSTNAME);

#==================== config ======================= ## do not remove this line (used by install.pl to preserve configs) ##
### Initialize config variables.  Some can be overriden with command line args
my $PSAD_DIR = "/var/log/psad";
my $PSAD_LOGFILE = "${PSAD_DIR}/scanlog";
my $FW_DATA = "${PSAD_DIR}/fwdata";
my $ERROR_LOG = "${PSAD_DIR}/fwerrorlog";
my $EMAIL_ALERTFILE = "${PSAD_DIR}/email_alert";
$PRINT_SCAN_HASH = "${PSAD_DIR}/scan_hash";  # global for sub print_scan
my $CHECK_INTERVAL = 15;  ### (seconds)
my $PORT_RANGE_SCAN_THRESHOLD = 1;
my $ENABLE_PERSISTENCE = "Y";	# if "Y", means that scans will never timeout
my $SCAN_TIMEOUT = 3600;	# this is used only if $ENABLE_PERSISTENCE = "N";
my $SHOW_ALL_SIGNATURES = "N";	# if "Y", means that all signatures will be shown since the scan started instead of just the current ones.
my %DANGER_LEVELS;
$DANGER_LEVELS{'1'} = 5;  ### number of packets 
$DANGER_LEVELS{'2'} = 50;
$DANGER_LEVELS{'3'} = 1000;
$DANGER_LEVELS{'4'} = 5000;
$DANGER_LEVELS{'5'} = 10000;
my $ENABLE_EMAIL_ALERTS = "Y";
my $EMAIL_ALERT_DANGER_LEVEL = 1;  # Send email alert if danger level >= to this value
my $EMAIL_LIMIT = 50;   ### send no more than this number of emails for a single scanning source ip
my @EMAIL_ADDRESSES = qw(root@localhost);  # supports multiple email addresses
my $ALERT_ALL = "Y";	# if "Y", send email for all new bad packets instead of just when a danger level increases
my $ENABLE_AUTO_IDS = "N"; 	# if "Y", enable automated IDS response (auto manages firewall rulesets). 
my $AUTO_IDS_DANGER_LEVEL = 5;  # Block all traffic from offending IP if danger level >= to this value 
my $WHOIS_TIMEOUT = 60;  ### (seconds)

### The following variable can be modified to look for logging messages
### that are specific to iptables firewalls (specified by the "--log-prefix"
### option).  For example, if your firewall uses the string "Audit" for
### packets that have been blocked, then you could set $IPTABLES_MSG_SEARCH = "Audit";
my $IPTABLES_MSG_SEARCH = "DROP|DENY|REJECT";

### system binaries ###
my $ipchainsCmd = "/sbin/ipchains";
my $iptablesCmd = "/usr/local/bin/iptables";
my $mknodCmd = "/bin/mknod";
my $psCmd = "/bin/ps";
my $wcCmd = "/usr/bin/wc";
my $tailCmd = "/usr/bin/tail";
my $mailCmd = "/bin/mail";
my $ifconfigCmd = "/sbin/ifconfig";
my $grepCmd = "/bin/grep";
my $sysloginitCmd = "/etc/rc.d/init.d/syslog";
my $netstatCmd = "/bin/netstat";
my $unameCmd = "/bin/uname";
my $whoisCmd = "/usr/bin/whois.psad";
my $psadwatchdCmd = "/usr/sbin/psadwatchd";
my $kmsgsdCmd = "/usr/sbin/kmsgsd";
my $diskmondCmd = "/usr/sbin/diskmond";
my $psadCmd = "/usr/sbin/psad";
#================== end config ==================== ## do not remove this line (used by install.pl to preserve configs) ##
#===================== main =======================

### a few more configuration variables that should not really be in the config section
$VERSION = "0.9.5";

### pid files
my @PIDFILES = qw(/var/run/psadwatchd.pid
		  /var/run/psad.pid
		  /var/run/kmsgsd.pid
		  /var/run/diskmond.pid);

### file used to store the psad command line
my $cmdline_file = "/var/run/psad.cmd";

### initialize some of the globals 
$USE_IPCHAINS = 0;
$USE_IPTABLES = 0;
$DEBUG = 0;
$HOSTNAME = hostname;

### initialize and scope some default variables (command line args can override some default values)
my $Scan_href;  # main psad data structure; contains ips, port ranges, tcp flags, and danger levels
my $daemon = 0;
my $output = 0;
my $errors = 0;
my $dnslookups = 0;
my $whoislookups = 0;
my $signatures = 0;
my $auto_ips = 0;
my $netstat_lookup = 0;
my $fwcheck = 0;
my $Syslog_server = 0;
my $kill = 0;
my $restart = 0;
my $status = 0;
my $usr1 = 0;
my $print_version = 0;
my $help = 0;
my $config = 0;

### save a copy of the command line arguments
my @args_cp = @ARGV;

Getopt::Long::Configure("no_ignore_case");  # make Getopts case sensitive

&usage_and_exit(1) unless (GetOptions (
	'help'		=> \$help,		# display help
	'auto_ips=s'	=> \$auto_ips,		# enable automatic ip danger level assignment
	'output'	=> \$output,		# write scanlog messages to STDOUT
	'Daemon'	=> \$daemon,		# do not run as a daemon
	'debug'		=> \$DEBUG,		# run in debug mode
	'interval=s'	=> \$CHECK_INTERVAL,	# set $CHECK_INTERVAL from the command line
	'firewallcheck'	=> \$fwcheck,		# do not check firewall rules
	'config=s'	=> \$config,		# specify configuration file
	'reversedns'	=> \$dnslookups,	# do not issue dns lookups against scanning ip address
	'signatures=s'	=> \$signatures,	# scan signatures
	'localport'	=> \$netstat_lookup,	# do not check to see if the firewall is listening on localport that has been scanned
	'errors'	=> \$errors,		# do not write malformed packet messages to error log
	'Logging_server'=> \$Syslog_server,	# we are running psad on a syslog logging server
	'Kill'		=> \$kill,		# kill all running psad processes (psadwatchd, psad, kmsgsd, diskmond)
	'Restart'	=> \$restart,		# restart psad with all options of the currently running psad process
	'Status'	=> \$status,		# display status of any currently running psad processes
	'USR1'		=> \$usr1, 		# send an existing psad process a USR1 signal (useful for debugging)
	'Version'	=> \$print_version,		# print the psad version and exit
	'whois'		=> \$whoislookups		# do not issue whois lookups against the scanning ip
));
&usage_and_exit(0) if ($help);

### print the version number and exit if -V given on the command line
print "psad version $VERSION, by Michael B. Rash (mbr\@cipherdyne.com)\n" and exit 0 if $print_version;

### everthing after this point must be executed as root.
$< == 0 && $> == 0 or die "\n@@@@@  psad: You must be root (or equivalent UID 0 account) to execute psad!  Exiting.\n\n";

### the --Kill command line switch was given
if ($kill) {
        &kill_psad(\@PIDFILES);
        exit 0;
}

### the --USR1 command line switch was given
if ($usr1) {
	my $rv = &psad_usr1(\@PIDFILES);
	exit $rv;
}

my %Cmds = (
	"ipchains"	=> $ipchainsCmd,
	"iptables"	=> $iptablesCmd,
	"mknod"		=> $mknodCmd,
	"wc"		=> $wcCmd,
	"ps"		=> $psCmd,
	"tail"		=> $tailCmd,
	"mail"		=> $mailCmd,
	"ifconfig"	=> $ifconfigCmd,
	"grep"		=> $grepCmd,
	"syslog_init"	=> $sysloginitCmd,
	"netstat"	=> $netstatCmd,
	"uname"		=> $unameCmd,
	"whois"		=> $whoisCmd,
	"psadwatchd"	=> $psadwatchdCmd,
	"kmsgsd"	=> $kmsgsdCmd,
	"diskmond"	=> $diskmondCmd,
	"psad"		=> $psadCmd
);

### check to make sure the commands specified in the config section 
### are in the right place, and attempt to correct automatically if not.
%Cmds = &Psad::check_commands(\%Cmds);

### the --Status command line switch was given
if ($status) {
        my $rv = &psad_status(\@PIDFILES, $cmdline_file, \%Cmds);
        exit $rv;
}


### make sure $PSAD_DIR, $FW_DATA, and /var/log/psadfifo, etc. actually exist
&psad_setup($PSAD_DIR, $FW_DATA, $PSAD_LOGFILE, $ERROR_LOG, \%Cmds);

### the --Restart command line switch was given
if ($restart) {
	&restart_psad(\@PIDFILES, $cmdline_file, \%Cmds);
	exit 0;
}

### check to make sure another psad process is not already running.
&Psad::unique_pid($PIDFILES[1]);

### make sure the permissions on these files is 0600
&check_permissions($PSAD_LOGFILE, $FW_DATA, $ERROR_LOG, $EMAIL_ALERTFILE);

### get the ip addresses that are local to this machine
my $local_ips_href = &get_local_ips(\%Cmds);

### disable whois lookups if for some reason the whois client that is 
### bundled with psad can't be found
$whoislookups = 1 if ($Cmds{'whois'} !~ /psad/);

### if psad is running on a syslog server, don't check the firewall 
### rules since they may not be local.
unless ($fwcheck || $Syslog_server) {
	unlink "/var/log/psad/fw_check.txt";
	&Psad::check_firewall_rules($IPTABLES_MSG_SEARCH, \@EMAIL_ADDRESSES, ["/var/log/psad/fw_check.txt"], \%Cmds);
}

### daemonize psad unless running with --Daemon or --debug
unless ($daemon || $DEBUG) {
	my $pid = fork;
	exit if $pid;
	die "@@@@@  $0: Couldn't fork: $!" unless defined($pid);
	POSIX::setsid() or die "@@@@@  $0: Can't start a new session: $!\n";
}

### write the current pid associated with psad to the psad pid file
&Psad::writepid($PIDFILES[1]);

### write the command line args used to start psad to $cmdline_file
&Psad::writecmdline(\@args_cp, $cmdline_file);

### psad _requires_ that kmsgsd is running to receive any data, so let's 
### start it here for good measure (as of 0.9.2 it makes use of the pid 
### files and unique_pid(), so we don't have to worry about starting a 
### duplicate copy).  While we're at it, start psadwatchd and diskmond too.
### Note that this is the best place to start the other daemons since we 
### just wrote the psad pid to $PIDFILES[1] above.
system "$Cmds{'kmsgsd'}";
system "$Cmds{'diskmond'}";
system "$Cmds{'psadwatchd'}" unless ($DEBUG);	### if running in debug mode, starting psadwatchd can start unwanted
						### copies of psad after the debugging process is killed.

### import config variables from the config file if running with 
### --config=<config file>
if ($config) {
	open CONF, "< $config";
	while(<CONF>) {
		### don't allow system calls and other mischief in the config file... 
		### just variable assignments.  Thanks Jay Beale (Bastille Linux).
		next and print $_ if ($_ =~ /system/ && $_ !~ /\#.*?system/);
		next if ($_ =~ /\`.*?\`/);
		next if ($_ =~ /open/ && $_ !~ /\#.*?open/);
		next if ($_ =~ /qx/);
		next unless ($_ =~ /my\s+\$|\@|\%.*?\=.*?\;/ || $_ =~ /\$|\@|\%.*?\=.*?\;/);
		eval $_;
	}
	close CONF;
}	

unless (-e $FW_DATA) {
	open F, ">> $FW_DATA";
	close F;
}
my $Sigs_href = &import_signatures($signatures) if $signatures;
my $Auto_ips_href;
if ($auto_ips) {  ### support automatic ip danger level increases/decreases.
	$Auto_ips_href = &import_auto_ips($auto_ips);
} else {
	$Auto_ips_href = 0;
}

### install signal handlers for debugging %Scan with Data::Dumper, 
### and for reaping zombie whois processes
$SIG{'USR1'} = \&print_scan;
$SIG{'CHLD'} = \&REAPER;

### initialize fwdata_start_lines
my $fwdata_start_lines = (split /\s+/, `$Cmds{'wc'} -l $FW_DATA`)[1];
#=================================== main loop ======================================
for (;;) {
	my $auto_ips_mtime_start = stat($auto_ips)->mtime if $auto_ips;
	my $sigs_mtime_start = stat($signatures)->mtime if $signatures;
	sleep $CHECK_INTERVAL;
	my $fwdata_end_lines = (split /\s+/, `$Cmds{'wc'} -l $FW_DATA`)[1];
	if ($signatures) {  # scan $signatures for any signature updates
		my $sigs_mtime_end = stat($signatures)->mtime;
		if ($sigs_mtime_start != $sigs_mtime_end) {  # the signatures were updated... import the new signatures.
			$Sigs_href = &import_signatures($signatures);
                	for my $email_address (@EMAIL_ADDRESSES) {
                        	system "$Cmds{'mail'} $email_address -s \"psad: re-read $signatures file on $HOSTNAME\" < /dev/null";
                	}
		}
	}
	### scan $auto_ips for any ips that should automatically have a certain danger threshold set.
	if ($auto_ips) {
		my $auto_ips_mtime_end = stat($auto_ips)->mtime;
		if ($auto_ips_mtime_start != $auto_ips_mtime_end) {  # the auto ips file was updated... import the new ip/danger level pairs.
			$Auto_ips_href = &import_auto_ips($auto_ips);
			# need to set $Scan_href->{$srcip}->{$dstip}->{'AUTO'} = "N" since $Auto_ips_href was updated.
			$Scan_href = &reset_auto_tags($Scan_href);
			for my $email_address (@EMAIL_ADDRESSES) {
                                system "$Cmds{'mail'} $email_address -s \"psad: re-read $auto_ips file on $HOSTNAME\" < /dev/null";
                        }
		}
	}
	if ($fwdata_end_lines - $fwdata_start_lines > 0) {   # new packets have been written to $FW_DATA by kmsgsd for psad analysis
		my $grabnum = $fwdata_end_lines - $fwdata_start_lines;
		my @process_lines = `$Cmds{'tail'} -$grabnum $FW_DATA`;
		print "MAIN: calling check_scan()\n" if $DEBUG;
		$Scan_href = &check_scan(\@process_lines, $Sigs_href, $signatures, $netstat_lookup, $local_ips_href, $errors, 
									$ERROR_LOG, $ENABLE_PERSISTENCE, $SHOW_ALL_SIGNATURES, $SCAN_TIMEOUT, \%Cmds);
		print "MAIN: calling assign_danger_level()\n" if $DEBUG;
		$Scan_href = &assign_danger_level($Scan_href, $Auto_ips_href, $PORT_RANGE_SCAN_THRESHOLD, $ALERT_ALL, \%DANGER_LEVELS);
		print "MAIN: calling scan_logr()\n" if $DEBUG;
		$Scan_href = &scan_logr($Scan_href, $PSAD_LOGFILE, $output, $dnslookups, $whoislookups, $WHOIS_TIMEOUT, $ENABLE_EMAIL_ALERTS,
					$EMAIL_ALERT_DANGER_LEVEL, $EMAIL_ALERTFILE, $EMAIL_LIMIT, \@EMAIL_ADDRESSES, $SHOW_ALL_SIGNATURES, \%Cmds);
		if ($ENABLE_AUTO_IDS eq "Y" && ! $Syslog_server) {  # don't manage the firewall rules if psad is running on a syslog server.
			$Scan_href = &auto_psad_response($Scan_href, $AUTO_IDS_DANGER_LEVEL, \%Cmds, \@EMAIL_ADDRESSES);
		}
	}
	print "MAIN: number of lines in $FW_DATA: $fwdata_end_lines\n" if $DEBUG;
	### reset fwdata_start_lines to where we just left off so that we don't miss any packets
	$fwdata_start_lines = $fwdata_end_lines;
}
exit 0;

#==================================== end main =========================================
sub check_scan() {  # keeps track of scanning ip's, increments packet counters, keep track of tcp flags for each
		    # scan (iptables only)
	my ($process_lines_aref, $sigs_href, $signatures, $netstat_lookup, $local_ips_href, $errors, 
			$error_log, $enable_persistence, $show_all_signatures, $scan_timeout, $Cmds_href) = @_;
	my @bad_packets;
	my $local_listening_ports_href;
	my ($srcip, $dstip, $len, $ttl, $proto, $srcport, $dstport, $flags, $type, $code, $id, $seq);
	# if necessary, check which firewall (ipchains vs. iptables)
	&check_fw($process_lines_aref->[0]) unless ($USE_IPCHAINS || $USE_IPTABLES);
	unless ($netstat_lookup) {
		$local_listening_ports_href = &get_listening_ports($Cmds_href);
	}
	READPKT: for my $l (@$process_lines_aref) {
		chomp $l;
		if ($USE_IPTABLES) {
			# sometimes the log entry is messed up by iptables so we write it to the error log
			if ($l =~ /SRC\=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\sDST\=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\sLEN\=(\d+)\s.+?TTL\=(\d+)\s.+?PROTO\=(\w{3,4})\sSPT\=(\d+)\sDPT\=(\d+)/) {
				($srcip, $dstip, $len, $ttl, $proto, $srcport, $dstport) = ($1,$2,$3,$4,$5,$6,$7,$8);
				if ($proto ne "TCP" && $proto ne "UDP") {  ### it was some weird non-tcp/udp packet that has source and destination ports
					push @bad_packets, $l;
					next READPKT;
				}
				if ($proto eq "TCP") {
					if ($l =~ /RES\S+\s(.*?)URGP/ || $l =~ /RES(\s)URGP/) {
						$flags = $1;
						chop $flags;
						$flags = "NULL" if ($flags eq "");
						# per page 595 of the Camel book, "if /blah1|blah2/" can be slower than "if /blah1/ || /blah2/
						unless (($flags =~ /SYN/ || $flags =~ /FIN/ || $flags =~ /URG/ || $flags =~ /PSH/ || $flags =~ /ACK/ 
									 || $flags =~ /RST/ || $flags =~ /NULL/) && ($flags !~ /LEN/ || $flags !~ /TOS/
									 || $flags !~ /PREC/ || $flags !~ /PROTO/ || $flags !~ /WINDOW/ ||
									    $flags !~ /RES/)) {
							push @bad_packets, $l;
							next READPKT;
						}
					} else {
						push @bad_packets, $l;
						next READPKT;
					}
				} # else it is UDP, but there are no more fields we need to defined since we already have the ports, etc.
			} elsif ($l =~ /SRC\=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\sDST\=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\sLEN\=(\d+)\s.+?TTL\=(\d+)\s.+?PROTO\=ICMP\sTYPE\=(\d+)\sCODE\=(\d+)\sID\=(\d+)\sSEQ\=(\d+)/) {
				($srcip, $dstip, $len, $ttl, $type, $code, $id, $seq) = ($1,$2,$3,$4,$5,$6,$7,$8);
				$proto = "ICMP";
			} else {
				push @bad_packets, $l;
				next READPKT;
			}
		} elsif ($USE_IPCHAINS) {
			# could implement source port checking here
			if ($l =~ /PROTO\=(\d+)\s(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\:(\d+)\s(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\:(\d+)/) {
				($proto, $srcip, $srcport, $dstip, $dstport) = ($1,$2,$3,$4,$5);
				$flags = "NONE";
			} else {
				push @bad_packets, $l;
				next READPKT;
			}
		}
		if ($enable_persistence eq "N") {
			my $currtime = time();
			if (defined $Scan{$srcip}{$dstip}{'START_TIME'}{'EPOCH_SECONDS'}) {
				my $tmp = $currtime - $Scan{$srcip}{$dstip}{'START_TIME'}{'EPOCH_SECONDS'};
				if (($currtime - $Scan{$srcip}{$dstip}{'START_TIME'}{'EPOCH_SECONDS'}) >= $scan_timeout) {
					undef $Scan{$srcip}{$dstip};
				}
			}
		} 
		# hash initialization
		$Scan{$srcip}{$dstip}{'LOGR'} = "Y";
#		if ($proto eq "TCP") {
#			$Scan{$srcip}{$dstip}{$proto}{'FLAGS'}{$flags} = 0 unless (defined $Scan{$srcip}{$dstip}{$proto}{'FLAGS'}{$flags});
#		}
		$Scan{$srcip}{$dstip}{'AUTO'} = "N" unless (defined $Scan{$srcip}{$dstip}{'AUTO'});
		$Scan{$srcip}{$dstip}{'CURRENT_DANGER_LEVEL'} = 0 unless (defined $Scan{$srcip}{$dstip}{'CURRENT_DANGER_LEVEL'});
		unless (defined $Scan{$srcip}{$dstip}{'START_PORT'} || $proto eq "ICMP") {  # this is the absolute starting port since the first packet was detected
			$Scan{$srcip}{$dstip}{'START_PORT'} = 65535; # make sure the initial start port is not too low
			$Scan{$srcip}{$dstip}{'END_PORT'} = 0; # make sure the initial end port is not too high
		}
		my $epoch_seconds = time() if ($enable_persistence eq "N");
		my @time = split / /, scalar localtime;   # Get the current time as a nice ASCII string.
		pop @time; shift @time;    # Get rid of the day and the year to make the time consistent with syslog
		my $time = join ' ', @time;
		unless (defined $Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}) {  ### initialize values for the current interval
			$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'START_TIME'} = $time;
			$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'PACKETS'} = 0;
			unless ($proto eq "ICMP") {
				### make sure the initial start port is not too low
				$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'START_PORT'} = 65535;
				### make sure the initial end port is not too high
				$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'END_PORT'} = 0;
			}
			if ($proto eq "TCP") {
				$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'FLAGS'}{$flags} = 0;
			}
		}
		unless (defined $Scan{$srcip}{$dstip}{'START_TIME'}{'READABLE'}) {
			$Scan{$srcip}{$dstip}{'START_TIME'}{'READABLE'} = $time;
			$Scan{$srcip}{$dstip}{'START_TIME'}{'EPOCH_SECONDS'} = $epoch_seconds if ($enable_persistence eq "N");
		}
		$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'PACKETS'}++;
		$Scan{$srcip}{$dstip}{'END_TIME'} = $time;
		$Scan{$srcip}{$dstip}{'END_TIME'}{'EPOCH_SECONDS'} = $epoch_seconds if ($enable_persistence eq "N");
		### increment hash values
		### if $Scan{$srcip}{$dstip}{'ABSNUM'} is not yet defined, incrementing it here will make it equal to 1 anyway
		$Scan{$srcip}{$dstip}{'ABSNUM'}++;  # if $Scan{$srcip}{$dstip}{'ABSNUM'} is not yet defined, incrementing it here will make it equal to 1 anyway
		if ($proto eq "TCP") {
			$Scan{$srcip}{$dstip}{'TCP'}{'CURRENT_INTERVAL'}{'FLAGS'}{$flags}++;
		}
		# see if this port lies outside our current range
		unless ($proto eq "ICMP") {
			($Scan{$srcip}{$dstip}{'START_PORT'}, $Scan{$srcip}{$dstip}{'END_PORT'}) =
				&check_range($dstport, $Scan{$srcip}{$dstip}{'START_PORT'}, $Scan{$srcip}{$dstip}{'END_PORT'});
			($Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'START_PORT'}, $Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'END_PORT'}) =
				&check_range($dstport, $Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'START_PORT'},
								$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'END_PORT'});
		} 
		if ($DEBUG) {
			print "check_scan():\n";
			print "     srcip: $srcip, dstip: $dstip\n";
			print "     Scan{srcip} = $Scan{$srcip}\n";
			print "     Scan{srcip}{dstip} = $Scan{$srcip}{$dstip}\n";
			print "     Scan{srcip}{dstip}{'LOGR'} = $Scan{$srcip}{$dstip}{'LOGR'}\n";
			print "     Scan{srcip}{dstip}{'ABSNUM'} = $Scan{$srcip}{$dstip}{'ABSNUM'}\n";
			print "     Scan{srcip}{dstip}{'TCP'}{'FLAGS'}{flags} = $Scan{$srcip}{$dstip}{'TCP'}{'FLAGS'}{$flags}\n" if ($proto eq "TCP");
			print "     flags: $flags\n" if ($proto eq "TCP");
			print "     Scan{srcip}{dstip}{'AUTO'} $Scan{$srcip}{$dstip}{'AUTO'}\n";
			print "     Scan{srcip}{dstip}{'CURRENT_DANGER_LEVEL'} = $Scan{$srcip}{$dstip}{'CURRENT_DANGER_LEVEL'}\n";
			print "     Scan{srcip}{dstip}{'START_PORT'} = $Scan{$srcip}{$dstip}{'START_PORT'}\n" unless ($proto eq "ICMP");
			print "     Scan{srcip}{dstip}{'END_PORT'} = $Scan{$srcip}{$dstip}{'END_PORT'}\n" unless ($proto eq "ICMP");
			print "     Scan{srcip}{dstip}{'START_TIME'}{'READABLE'} = $Scan{$srcip}{$dstip}{'START_TIME'}{'READABLE'}\n";
			print "     Scan{srcip}{dstip}{'START_TIME'}{'EPOCH_SECONDS'} = $Scan{$srcip}{$dstip}{'START_TIME'}{'EPOCH_SECONDS'}\n" if ($enable_persistence eq "N");
			print "     Scan{srcip}{dstip}{'END_TIME'} = $Scan{$srcip}{$dstip}{'END_TIME'}\n";
			print "     Scan{srcip}{dstip}{'END_TIME'}{'EPOCH_SECONDS'} = $Scan{$srcip}{$dstip}{'END_TIME'}{'EPOCH_SECONDS'}\n" if ($enable_persistence eq "N");
			unless ($proto eq "ICMP") {
				print "     Scan{srcip}{dstip}{$proto}{'CURRENT_INTERVAL'}{'START_PORT'} = "
					. "$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'START_PORT'}\n";
				print "     Scan{srcip}{dstip}{$proto}{'CURRENT_INTERVAL'}{'END_PORT'} = "
					. "$Scan{$srcip}{$dstip}{$proto}{'CURRENT_INTERVAL'}{'END_PORT'}\n";
			}
		}
		if ($signatures && $USE_IPTABLES) {  # might try to use ipchains also, but then cannot use tcp flags except for -y -l rules
			for my $msg (keys %{$sigs_href->{$proto}}) {   # need to iterate through all signatures since a packet may match several
				my $dstport_criteria = 0;
				my $srcport_criteria = 0;
				my $rv = 0;
				if ($proto eq "TCP") {
					if (&check_port($sigs_href, $msg, $srcport, $dstport, $proto)
						&& &check_misc_fields($sigs_href, $msg, $proto, $len, $ttl)
							&& &check_tcp_flags($sigs_href, $msg, $flags, $proto)) {   ### tripped a tcp signature
						$Scan{$srcip}{$dstip}{'TCP'}{'SIGMATCH'}{'SIGDL'} = $sigs_href->{'TCP'}->{$msg}->{'DANGERLEVEL'};
						$rv = 1;
					}
				} elsif ($proto eq "UDP") {
					if (&check_port($sigs_href, $msg, $srcport, $dstport, $proto)
						&& &check_misc_fields($sigs_href, $msg, $proto, $len, $ttl)) {   ### tripped a udp signature
						$Scan{$srcip}{$dstip}{'UDP'}{'SIGMATCH'}{'SIGDL'} = $sigs_href->{'UDP'}->{$msg}->{'DANGERLEVEL'};
						$rv = 1;
					}
				} elsif ($proto eq "ICMP") {
					if (&check_icmp_sigs($sigs_href, $msg, $ttl, $type, $code, $id, $seq)) {
						$Scan{$srcip}{$dstip}{'ICMP'}{'SIGMATCH'}{'SIGDL'} = $sigs_href->{'ICMP'}->{$msg}->{'DANGERLEVEL'};
						$rv = 1;
					}
				}
				if ($rv) {   ### we matched a signature
					if ($DEBUG) {
						unless ($proto eq "ICMP") {
							print "     SIGMATCH on dstport: $dstport\n";
						} else {
							print "     ICMP SIGMATCH\n";
						}
					}
					my $listening_port = "";
					unless ($netstat_lookup || $proto eq "ICMP") {
						my $lprot;
						$lprot = "tcp" if ($proto eq "TCP");
						$lprot = "udp" if ($proto eq "UDP");
						my $dstip_is_local = 0;
						### check to see if the scan destination ip is directed at the firewall.  If yes,
						### then check to see if a server is listening on the DSTPORT by parsing netstat
						### output.  If not, psad would have to connect to the deestination port on the
						### remote machine, but it should not do this so it is not implemented.
						$dstip_is_local = 1 if defined $local_ips_href->{$dstip};
						if ($dstip_is_local) {
							my $key = $lprot . $dstport;
							if (defined $local_listening_ports_href->{$key}) {
								$listening_port = "YOUR MACHINE IS LISTENING ON ($lprot) PORT: $dstport";
							} else {
								$listening_port = "There is no server listening on $lprot port $dstport";
							}
						} # else $listening_port is already ""
					}
					### including sp almost always changes the hash key: 	\
					### my $alert_string = "$msg  sp=$srcport, dp=$dstport, flags=$flags.  $listening_port";
					my $alert_string;
					if ($proto eq "TCP") {
						$alert_string = "$msg  dp=$dstport, flags=$flags. $listening_port";
					} elsif ($proto eq "UDP") {
						$alert_string = "$msg  dp=$dstport. $listening_port";
					} else {
						$alert_string = "$msg";
					}
					if (defined $Scan{$srcip}{$dstip}{$proto}{'SIGMATCH'}{$alert_string}) {
						$Scan{$srcip}{$dstip}{$proto}{'SIGMATCH'}{$alert_string}++;
					} else {
						$Scan{$srcip}{$dstip}{$proto}{'SIGMATCH'}{$alert_string} = 1;
					}
#					unless (defined $Scan{$srcip}{$dstip}{'CURRENT_SIGMATCH'}{'START_TIME'}) {
					unless (defined $Scan{$srcip}{$dstip}{'CURRENT_SIGMATCH'}) {
#						$Scan{$srcip}{$dstip}{'CURRENT_SIGMATCH'}{'START_TIME'} = $time;
						$Scan{$srcip}{$dstip}{$proto}{'CURRENT_SIGMATCH'}{$alert_string} = 0;
						$Scan{$srcip}{$dstip}{$proto}{'CURRENT_SIGMATCH'}{'PACKETS'} = 0;
					}
					$Scan{$srcip}{$dstip}{$proto}{'CURRENT_SIGMATCH'}{$alert_string}++;
					$Scan{$srcip}{$dstip}{$proto}{'CURRENT_SIGMATCH'}{'PACKETS'}++;
				}
			} 
		}
	}
	&collect_errors(\@bad_packets, $error_log) unless $errors;
	return \%Scan;
}
# check_tcp_flags will eventually need to include all possible permutations of the six 
# tcp flags so that any signature can be written, but for now this should be most 
# of the important ones.
sub check_tcp_flags() {
	my ($sigs_href, $msg, $flags_to_check, $proto) = @_;
	return 0 if ($proto ne "TCP");
	my $msgflags = $sigs_href->{$proto}->{$msg}->{'FLAGS'};
	return 1 if ($msgflags eq "S" && $flags_to_check eq "SYN");		# syn scan
	return 1 if ($msgflags eq "F" && $flags_to_check eq "FIN");		# fin scan
	return 1 if ($msgflags eq "SF" && $flags_to_check eq "SYN FIN"); 	# "syn/fin" scan
	return 1 if ($msgflags eq "UPF" && $flags_to_check eq "URG PSH FIN");	# nmap Xmas scan
	return 1 if ($msgflags eq "NULL" && $flags_to_check eq "NULL");		# nmap NULL scan
	return 1 if ($msgflags eq "UPSF" && $flags_to_check eq "URG PSH SYN FIN");  # nmap fingerprint scan
	return 1 if ($msgflags eq "AP" && $flags_to_check eq "ACK PSH");	# see the signatures for these
	return 1 if ($msgflags eq "AS" && $flags_to_check eq "ACK SYN");
	return 0;
}
sub check_port() {
	my ($sigs_href, $msg, $srcport, $dstport, $proto) = @_;
	print "check_port(): msg: $msg, srcport: $srcport, dstport: $dstport\n" if $DEBUG;
	### check dst port first
	if (defined $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'START'}) {
		my $start = $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'START'};
		my $end = $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'END'};
		return 0 if ($dstport < $start || $dstport > $end);
	}
	if (defined $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NOT'}) {
		return 0 if ($dstport == $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NOT'});
	}
	if (defined $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NEGSTART'}) {
		my $start = $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NEGSTART'};
		my $end = $sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NEGEND'};
		return 0 if ($dstport > $start || $dstport < $end);
	}
	if (defined $sigs_href->{$proto}->{$msg}->{'DSTPORT'}) {
		unless ($sigs_href->{$proto}->{$msg}->{'DSTPORT'} eq "any") {
			return 0 if ($dstport != $sigs_href->{$proto}->{$msg}->{'DSTPORT'});
		}
	}
	### check src port
	if (defined $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'START'}) {
		my $start = $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'START'};
		my $end = $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'END'};
		return 0 if ($dstport < $start || $dstport > $end);
	}
	if (defined $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NOT'}) {
		return 0 if ($dstport == $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NOT'});
	}
	if (defined $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NEGSTART'}) {
		my $start = $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NEGSTART'};
		my $end = $sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NEGEND'};
		return 0 if ($dstport > $start || $dstport < $end);
	}
	if (defined $sigs_href->{$proto}->{$msg}->{'SRCPORT'}) {
		unless ($sigs_href->{$proto}->{$msg}->{'SRCPORT'} eq "any") {
			return 0 if ($dstport != $sigs_href->{$proto}->{$msg}->{'SRCPORT'});
		}
	}
	return 1;   ### if we made it to here, then we matched both the src and dst port criteria
}
sub check_icmp_sigs() {
	my ($sigs_href, $msg, $ttl, $type, $code, $icmp_id, $icmp_seq) = @_;
	### check icmp type first
	if (defined $sigs_href->{'ICMP'}->{$msg}->{'TYPE'}) {
		return 0 if ($sigs_href->{'ICMP'}->{$msg}->{'TYPE'} != $type);
	}
	if (defined $sigs_href->{'ICMP'}->{$msg}->{'TTL'}) {
		return 0 if ($sigs_href->{'ICMP'}->{$msg}->{'TTL'} != $ttl);
	}
	if (defined $sigs_href->{'ICMP'}->{$msg}->{'CODE'}) {
		return 0 if ($sigs_href->{'ICMP'}->{$msg}->{'CODE'} != $code);
	}
	if (defined $sigs_href->{'ICMP'}->{$msg}->{'ID'}) {
		return 0 if ($sigs_href->{'ICMP'}->{$msg}->{'ICMP_ID'} != $icmp_id);
	}
	if (defined $sigs_href->{'ICMP'}->{$msg}->{'SEQ'}) {
		return 0 if ($sigs_href->{'ICMP'}->{$msg}->{'ICMP_SEQ'} != $icmp_seq);
	}
	return 1; ### if we got to this point, then we matched the signature
}
sub check_misc_fields() {
	my ($sigs_href, $msg, $proto, $len, $ttl) = @_;
	if (defined $sigs_href->{$proto}->{$msg}->{'LEN'}) {
		return 0 if ($sigs_href->{$proto}->{$msg}->{'LEN'} != $len);
	}
	if (defined $sigs_href->{$proto}->{$msg}->{'TTL'}) {
		return 0 if ($sigs_href->{$proto}->{$msg}->{'TTL'} != $ttl);
	}
	return 1;
}
sub import_signatures() {
	my $sigfile = shift;
	my ($proto, $srcport, $dstport, $msg, $flags, $dangerlevel);
	my %Sigs;
	my ($start, $end, $tmpport);
	open SIGS, "< $sigfile" or die "Could not open the signatures file $sigfile: $!\n";
	my @sigs = <SIGS>;
	close SIGS;
	for my $sig (@sigs) {
		chomp $sig;
		next if ($sig =~ /^#/);
		&get_signature_fields(\%Sigs, $sig);
	}
	print STDOUT Dumper %Sigs if $DEBUG;
	return \%Sigs;
}
sub get_signature_fields() {
	my ($Sigs_href, $sig) = @_;
	my ($len, $ttl, $proto, $srcport, $dstport, $flags, $type, $code, $id, $seq);
	my $msg;
	if ($sig =~ /^tcp/) {
		$proto = "TCP";
	} elsif ($sig =~ /^udp/) {
		$proto = "UDP";
	} elsif ($sig =~ /^icmp/) {
		$proto = "ICMP";
	} else {
		return;
	}
	my @fields = split /\;/, $sig;
	for my $f (@fields) {  ### get the msg first
		if ($f =~ /msg\:\s*?(\".*?\")/) {
			$msg = $1;
		}
	}
	if ($msg) {
		if (defined $Sigs_href->{$proto}->{$msg}) {
			$msg .= " ";   ### make sure we have a unique signature by apending whitespace if necessary
		}
		if ($proto ne "ICMP") {   ### it is either tcp or udp
			&get_signature_ports($Sigs_href, $sig, $proto, $msg);
		}
		for my $f (@fields) {
			if ($f =~ /flags\:\s*?(\w+)/i) {
				$Sigs_href->{$proto}->{$msg}->{'FLAGS'} = $1;
			} elsif ($f =~ /ttl\:\s*(\d+)/i) {
				$Sigs_href->{$proto}->{$msg}->{'TTL'} = $1;
			} elsif ($f =~ /itype\:\s*?(\d+)/i) {
				$Sigs_href->{$proto}->{$msg}->{'TYPE'} = $1;
			} elsif ($f =~ /icode\:\s*?(\d+)/i) {
				$Sigs_href->{$proto}->{$msg}->{'CODE'} = $1;
			} elsif ($f =~ /icmp_seq\:\s*?(\d+)/i) {
				$Sigs_href->{$proto}->{$msg}->{'ICMP_SEQ'} = $1;
			} elsif ($f =~ /icmp_id\:\s*?(\d+)/i) {
				$Sigs_href->{$proto}->{$msg}->{'ICMP_ID'} = $1;
			} elsif ($f =~ /dlevel\:\s*?(\d{1})/i) {
				$Sigs_href->{$proto}->{$msg}->{'DANGERLEVEL'} = $1;
			}
		}
	}
	return;
}
sub get_signature_ports() {
	my ($Sigs_href, $sig, $proto, $msg) = @_;
	my ($srcport, $dstport);
	my ($start, $end, $tmpport);
	if ($sig =~ /^\w{3,4}\s+(\S+)\s+\-\>\s+(\S+)\s/) {
		($srcport, $dstport) = ($1, $2);
	} else {
		return;
	} 
	if ($srcport =~ /\:/ && $srcport !~ /\!/) {
		($start, $end) = split /:/, $srcport;
		$start = 1 if ($start eq '');
		$end = 65535 if ($end eq '');
		$Sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'START'} = $start;
		$Sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'END'} = $end;
	} elsif ($srcport =~ /\!/ && $srcport !~ /\:/) {
		$tmpport = (split /\!/, $srcport)[1];
		$Sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NOT'} = $tmpport;
	} elsif ($srcport =~ /\:/ && $srcport =~ /\!/) {
		($start, $end) = split /:/, $srcport;
		$start = 1 if ($start !~ /\d/);
		$end = 65535 if ($end !~ /\d/);
		$Sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NEGSTART'} = $start;
		$Sigs_href->{$proto}->{$msg}->{'SRCPORT'}->{'NEGEND'} = $end;
	} else {
		$Sigs_href->{$proto}->{$msg}->{'SRCPORT'} = $srcport;
	}
	if ($dstport =~ /\:/ && $dstport !~ /\!/) {
		($start, $end) = split /:/, $dstport;
		$start = 1 if ($start eq '');
		$end = 65535 if ($end eq '');
		$Sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'START'} = $start;
		$Sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'END'} = $end;
	} elsif ($dstport =~ /\!/ && $dstport !~ /\:/) {
		$tmpport = (split /\!/, $dstport)[1];
		$Sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NOT'} = $tmpport;
	} elsif ($dstport =~ /\:/ && $dstport =~ /\!/) {
		($start, $end) = split /:/, $dstport;
		$start = 1 if ($start !~ /\d/);
		$end = 65535 if ($end !~ /\d/);
		$Sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NEGSTART'} = $start;
		$Sigs_href->{$proto}->{$msg}->{'DSTPORT'}->{'NEGEND'} = $end;
	} else {
		$Sigs_href->{$proto}->{$msg}->{'DSTPORT'} = $dstport;
	}
	return;
}
sub import_auto_ips() {
	my $auto_ips_file = shift;
	my %Auto_ips;
	open AUTO, "< $auto_ips_file";
	my @lines = <AUTO>;
	close AUTO;
	for my $l (@lines) {
		next if ($l =~ /^#/);
		if ($l =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+([0-5]|\-1)/) {
			$Auto_ips{$1} = $2;
		}
	}
	return \%Auto_ips;
}
sub reset_auto_tags() {
	my $Scan_href = shift;
	for my $srcip (keys %$Scan_href) {
		for my $dstip (keys %{$Scan_href->{$srcip}}) {
			$Scan_href->{$srcip}->{$dstip}->{'AUTO'} = "N";
		}
	}
	return $Scan_href;
}
sub check_range() {
	my ($port, $start, $end) = @_;
	$start = $port if ($port < $start);
	$end = $port if ($port > $end);
	return $start, $end;
}
sub assign_danger_level() {
	my ($Scan_href, $Auto_ips_href, $port_range_scan_threshold, $alert_all, $danger_levels_href) = @_;
	$Scan_href = &automatic_ip_danger_assignment($Scan_href, $Auto_ips_href) if ($Auto_ips_href); 
	for my $srcip (keys %$Scan_href) {
		print "assign_danger_level(): source ip: $srcip\n" if $DEBUG;
		DST: for my $dstip (keys %{$Scan_href->{$srcip}}) {
			my $absnum = $Scan_href->{$srcip}->{$dstip}->{'ABSNUM'};
			my $range;
			if (defined $Scan_href->{$srcip}->{$dstip}->{'START_PORT'}) {
				$range = $Scan_href->{$srcip}->{$dstip}->{'END_PORT'} - $Scan_href->{$srcip}->{$dstip}->{'START_PORT'};
			} else {
				$range = $absnum;
			}
			if ($DEBUG) {
				print "assign_danger_level(): destination ip: $dstip\n";
				print "assign_danger_level(): ABSNUM: $Scan_href->{$srcip}->{$dstip}->{'ABSNUM'}\n";
				if (defined $Scan_href->{$srcip}->{$dstip}->{'START_PORT'}) {
					print "assign_danger_level(): START_PORT: $Scan_href->{$srcip}->{$dstip}->{'START_PORT'}, "
						. "END_PORT: $Scan_href->{$srcip}->{$dstip}->{'END_PORT'}\n";
				}
				print "assign_danger_level(): CURRENT_DANGER_LEVEL (before assignment) = "
					. "$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'}\n";
			}
			# If a scan signature packet has been detected but no other packets are detected, assign a danger
			# level of SIGDL.
			my $sigmatch = 0;
			my $sigproto = "";
			if (defined $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'SIGMATCH'}) {
				$sigmatch = 1;
				$sigproto = "TCP";
				print "assign_danger_level(): sigmatch = $sigmatch\n" if $DEBUG;
			}
			if (defined $Scan_href->{$srcip}->{$dstip}->{'UDP'}->{'SIGMATCH'}) {
				$sigmatch = 1;
				$sigproto = "UDP";
				print "assign_danger_level(): sigmatch = $sigmatch\n" if $DEBUG;
			}
			if (defined $Scan_href->{$srcip}->{$dstip}->{'ICMP'}->{'SIGMATCH'}) {
				$sigmatch = 1;
				$sigproto = "ICMP";
				print "assign_danger_level(): sigmatch = $sigmatch\n" if $DEBUG;
			}
			if ($sigmatch && $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} <
							$Scan_href->{$srcip}->{$dstip}->{$sigproto}->{'SIGMATCH'}->{'SIGDL'}) {
				$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} =
								$Scan_href->{$srcip}->{$dstip}->{$sigproto}->{'SIGMATCH'}->{'SIGDL'};
				$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "N";
				print "assign_danger_level(): danger level: "
					. "$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'}\n" if $DEBUG;
			}
			### if $port_range_scan_threshold is >= 1, then psad will not assign a
			### danger level to repeated packets to the same port
			if ($absnum < $danger_levels_href->{'1'}) {
				### we don't have enough packets to even reach danger level 1 yet.
				next DST;
			} elsif ($absnum < $danger_levels_href->{'2'} && $range >= $port_range_scan_threshold) {
				if ($Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} < 1
						&& $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} != -1) {
					$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "N";
					$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} = 1;
				}
			} elsif ($absnum < $danger_levels_href->{'3'} && $range >= $port_range_scan_threshold) {
				if ($Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} < 2
						&& $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} != -1) {
					$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "N";
					$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} = 2;
				}
			} elsif ($absnum < $danger_levels_href->{'4'} && $range >= $port_range_scan_threshold) {
				if ($Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} < 3
						&& $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} != -1) {
					$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "N";
					$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} = 3;
				}
			} elsif ($absnum < $danger_levels_href->{'5'} && $range >= $port_range_scan_threshold) {
				if ($Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} < 4
						&& $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} != -1) {
					$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "N";
					$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} = 4;
				}
			} elsif ($range >= $port_range_scan_threshold) {
				if ($Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} < 5
						&& $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} != -1) {
					$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "N";
					$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} = 5;
				}
			}
			### we will always send an alert email for any new "bad" packet if $alert_all eq "Y"...
			### Else email sent only if the scan increments its D.L.
			if ($alert_all eq "Y") {
				$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "N";
			}
			print "assign_danger_level(): CURRENT_DANGER_LEVEL (after assignment) = "
				. "$Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'}\n" if $DEBUG;
		}
	}
	return $Scan_href;
}
sub automatic_ip_danger_assignment() {
	my ($Scan_href, $Auto_ips_href) = @_;
	for my $scan_ip (keys %$Scan_href) {
		for my $dstip (keys %{$Scan_href->{$scan_ip}}) {
			for my $auto_ip (keys %$Auto_ips_href) {
				if ($scan_ip eq $auto_ip && $Scan_href->{$scan_ip}->{$dstip}->{'AUTO'} eq "N") {
					$Scan_href->{$scan_ip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} = $Auto_ips_href->{$auto_ip};
					$Scan_href->{$scan_ip}->{$dstip}->{'ALERTED'} = "N";
					$Scan_href->{$scan_ip}->{$dstip}->{'AUTO'} = "Y";
				}
			}
		}
	}
	return $Scan_href;
}
sub collect_errors() {
	my ($bad_packets_aref, $error_log) = @_;
	open ERRORS, ">> $error_log";
	for my $l (@$bad_packets_aref) {
		print ERRORS "$l\n";
	}
	close ERRORS;
}
sub scan_logr() {
	my ($Scan_href, $logfile, $output, $dnslookups, $whoislookups, $whois_timeout,
		$enable_email_alerts, $email_alert_danger_level, $email_alertfile, 
				$email_limit, $email_addresses_aref, $show_all_signatures, $Cmds_href) = @_;
	my $flags_msg;
	my $hndl;
	my $range;
	my ($tcp_newrange, $udp_newrange, $tcp_new_start_range, $tcp_new_end_range);
	my ($udp_new_start_range, $udp_new_end_range);
	my ($tcp_new_start_time, $udp_new_start_time, $icmp_new_start_time);
	my $dnsstring = "";
	my $whois_info_aref;
	if ($output || $DEBUG) {
		$hndl = "STDOUT";
	} else {
		$hndl = "LOG"; 
		open $hndl, ">> $logfile";
	}
	for my $srcip (keys %$Scan_href) {
		print "scan_logr(): source ip: $srcip\n" if $DEBUG;
		DST: for my $dstip (keys %{$Scan_href->{$srcip}}) {
			unless (defined $Scan_href->{$srcip}->{$dstip}->{'EMAIL_LIMIT'}) {
				$Scan_href->{$srcip}->{$dstip}->{'EMAIL_LIMIT'} = 0;
			} elsif ($Scan_href->{$srcip}->{$dstip}->{'EMAIL_LIMIT'} > $email_limit) {
				unless (defined $Scan_href->{$srcip}->{$dstip}->{'EMAIL_STOPPED'}) {
					&email_limit_reached($email_addresses_aref, $srcip, $Cmds_href);
					$Scan_href->{$srcip}->{$dstip}->{'EMAIL_STOPPED'} = 1;
				}
				next DST;
			}
			print "scan_logr(): dst ip: $dstip\n" if $DEBUG;
			my ($tcp, $udp, $icmp) = (0,0,0);
			my $current_danger_level = $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'};
			if ($current_danger_level >= 1 && $Scan_href->{$srcip}->{$dstip}->{'LOGR'} eq "Y") {
				my ($abs_start_range, $abs_end_range);
				my ($tcp_new_start_range, $tcp_new_end_range, $tcp_new_start_time, $tcp_new_num_pkts);
				my ($udp_new_start_range, $udp_new_end_range, $udp_new_start_time, $udp_new_num_pkts);
				my ($icmp_new_start_time, $icmp_new_num_pkts);
				if (defined $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'}) {
					$tcp = 1;
					$tcp_new_start_range = $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'}->{'START_PORT'};
					$tcp_new_end_range = $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'}->{'END_PORT'};
					$tcp_new_start_time = $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'}->{'START_TIME'};
					$tcp_new_num_pkts = $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'}->{'PACKETS'};
				}
				if (defined $Scan_href->{$srcip}->{$dstip}->{'UDP'}->{'CURRENT_INTERVAL'}) {
					$udp = 1;
					$udp_new_start_range = $Scan_href->{$srcip}->{$dstip}->{'UDP'}->{'CURRENT_INTERVAL'}->{'START_PORT'};
					$udp_new_end_range = $Scan_href->{$srcip}->{$dstip}->{'UDP'}->{'CURRENT_INTERVAL'}->{'END_PORT'};
					$udp_new_start_time = $Scan_href->{$srcip}->{$dstip}->{'UDP'}->{'CURRENT_INTERVAL'}->{'START_TIME'};
					$udp_new_num_pkts = $Scan_href->{$srcip}->{$dstip}->{'UDP'}->{'CURRENT_INTERVAL'}->{'PACKETS'};
				}
				if (defined $Scan_href->{$srcip}->{$dstip}->{'ICMP'}->{'CURRENT_INTERVAL'}) {
					$icmp = 1;
					$icmp_new_start_time = $Scan_href->{$srcip}->{$dstip}->{'ICMP'}->{'CURRENT_INTERVAL'}->{'START_TIME'};
					$icmp_new_num_pkts = $Scan_href->{$srcip}->{$dstip}->{'ICMP'}->{'CURRENT_INTERVAL'}->{'PACKETS'};
				}
				my $start_time = $Scan_href->{$srcip}->{$dstip}->{'START_TIME'}->{'READABLE'};
				my $end_time = $Scan_href->{$srcip}->{$dstip}->{'END_TIME'};
				my @time = split /\s/, scalar localtime;   ### Get the current time as a nice ASCII string.
				pop @time; shift @time;    ### Get rid of the day and the year to make the time consistent with syslog
				my $time = join ' ', @time;
				unless ($dnslookups) {
					my $ipaddr = gethostbyname($srcip);
					# my $rdns = gethostbyaddr($ipaddr, AF_INET);
					my $rdns = gethostbyaddr($ipaddr, 2);
					$rdns = "No reverse dns info available" unless $rdns;
					$dnsstring = "$srcip -> $rdns";
				}
				unless ($whoislookups) {
					$whois_info_aref = &get_whois_data($whois_timeout, $srcip, $Cmds_href);
				}
				if ($tcp || $udp) {
					$abs_start_range = $Scan_href->{$srcip}->{$dstip}->{'START_PORT'};
					$abs_end_range = $Scan_href->{$srcip}->{$dstip}->{'END_PORT'};
					if ($abs_start_range == $abs_end_range) {
						$range = $abs_start_range;
					} else {
						$range = "$abs_start_range-$abs_end_range";
					}
				}
				my @tcp_flags;
				my $flags_msg;
				if ($tcp) {
					if ($tcp_new_start_range == $tcp_new_end_range) {
						$tcp_newrange = $tcp_new_start_range;
					} else {
						$tcp_newrange = "$tcp_new_start_range-$tcp_new_end_range";
					}
					for my $flags (keys %{$Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'}->{'FLAGS'}}) {
						my $nmapOpts = "";
						$nmapOpts = "-sT or -sS" if ($flags eq "SYN");
						$nmapOpts = "-sF" if ($flags eq "FIN");
						$nmapOpts = "-sX" if ($flags eq "URG PSH FIN");
						$nmapOpts = "-O" if ($flags eq "URG PSH SYN FIN");
						if ($nmapOpts) {
							$nmapOpts = "  Nmap: [$nmapOpts]";
						}
						my $num_pkts = $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'}->{'FLAGS'}->{$flags};
						$flags_msg = "TCP flags:                   [$flags: $num_pkts packets]${nmapOpts}\n";
						push @tcp_flags, $flags_msg;
					}
					### need to undef the current interval so it won't show up in the next alert
					undef $Scan_href->{$srcip}->{$dstip}->{'TCP'}->{'CURRENT_INTERVAL'};
				}
				if ($udp) {
					if ($udp_new_start_range == $udp_new_end_range) {
						$udp_newrange = $udp_new_start_range;
					} else {
						$udp_newrange = "$udp_new_start_range-$udp_new_end_range";
					}
					### need to undef the current interval so it won't show up in the next alert
					undef $Scan_href->{$srcip}->{$dstip}->{'UDP'}->{'CURRENT_INTERVAL'};
				}
				if ($icmp) {
					undef $Scan_href->{$srcip}->{$dstip}->{'ICMP'}->{'CURRENT_INTERVAL'};
				}
				print STDOUT "scan_logr():  generating email......\n" if $DEBUG;
				open EMAILALERT, "> $email_alertfile";
				&markerline($hndl);
				### could use logr() here, but perhaps the subroutine call is slower?
				print $hndl "$time:     Portscan Detected by psad (pid $$) on $HOSTNAME.\n";
				print $hndl "\n";
				print $hndl "Source:                     $srcip\n";
				print $hndl "Destination:                $dstip\n";
				print $hndl "Newly scanned TCP ports:    [$tcp_newrange]  (since: $tcp_new_start_time)\n" if $tcp;
				print $hndl "Newly Blocked TCP packets:  [$tcp_new_num_pkts] (since: $tcp_new_start_time)\n" if $tcp;
				if (@tcp_flags) {
					print $hndl $_ for (@tcp_flags);
				}
				print $hndl "Newly scanned UDP ports:     [$udp_newrange]  (since: $udp_new_start_time)\n" if $udp;
				print $hndl "Newly Blocked UDP packets:   [$udp_new_num_pkts] (since: $udp_new_start_time)\n" if $udp;
				print $hndl "Newly Blocked ICMP packets:  [$icmp_new_num_pkts] (since: $icmp_new_start_time)\n" if $icmp;
				print $hndl "Complete port range:         [$range]  (since: $start_time) TCP and/or UDP\n" unless $icmp;
				print $hndl "Total blocked packets:       $Scan_href->{$srcip}->{$dstip}->{'ABSNUM'}\n";
				print $hndl "Start time:                  $start_time\n";
				print $hndl "End time:                    $end_time\n";
				print $hndl "Danger level:                $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} out of 5\n";
				print $hndl "DNS info:                    $dnsstring\n" unless $dnslookups;
				&markerline("EMAILALERT");
				print EMAILALERT "$time:     Portscan Detected by psad (pid $$) on $HOSTNAME.\n";
				print EMAILALERT "\n";
				print EMAILALERT "Source:                      $srcip\n";
				print EMAILALERT "Destination:                 $dstip\n";
				print EMAILALERT "Newly scanned TCP ports:     [$tcp_newrange]  (since: $tcp_new_start_time)\n" if $tcp;
				print EMAILALERT "Newly Blocked TCP packets:   [$tcp_new_num_pkts] (since: $tcp_new_start_time)\n" if $tcp;
				if (@tcp_flags) {
					print EMAILALERT $_ for (@tcp_flags);
				}
				print EMAILALERT "Newly scanned UDP ports:     [$udp_newrange]  (since: $udp_new_start_time)\n" if $udp;
				print EMAILALERT "Newly Blocked UDP packets    [$udp_new_num_pkts] (since: $udp_new_start_time)\n" if $udp;
				print EMAILALERT "Newly Blocked ICMP packets:  [$icmp_new_num_pkts] (since: $icmp_new_start_time)\n" if $icmp;
				print EMAILALERT "Complete port range:         [$range]  (since: $start_time) TCP and/or UDP\n" unless $icmp;
				print EMAILALERT "Total blocked packets:       $Scan_href->{$srcip}->{$dstip}->{'ABSNUM'}\n";
				print EMAILALERT "Start time:                  $start_time\n";
				print EMAILALERT "End time:                    $end_time\n";
				print EMAILALERT "Danger level:                $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} out of 5\n";
				print EMAILALERT "DNS info:                    $dnsstring\n" unless $dnslookups;

				### write a syslog message
#				openlog("psad", "ndelay", "daemon");
#				my $syslog_print_range = "";
#				if ($tcp) {
#					$syslog_print_range .= "TCP ports: [$tcp_newrange] ";
#				}
#				if ($udp) {
#					$syslog_print_range .= "UCP ports: [$udp_newrange] ";
#				}
#				my $syslog_num_pkts = $Scan_href->{$srcip}->{$dstip}->{'ABSNUM'};
#				my $syslog_danger_level = $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'};
#
#				syslog("warning", "Port scan detected: src: $srcip, dst: $dstip, $syslog_print_range, pkts: $syslog_num_pkts, dangerlevel: $syslog_danger_level");
#				closelog();
				if ($DEBUG) {
					print STDOUT "scan_logr(): printing email..................\n";
					print STDOUT "     $time: Portscan Detected by psad (pid $$) on $HOSTNAME.\n";
					print STDOUT "     Source:                   $srcip\n";
					print STDOUT "     Destination:              $dstip\n";
					print STDOUT "     Newly scanned TCP ports:  [$tcp_newrange]  (since: $tcp_new_start_time)\n" if $tcp;
					print STDOUT "     Blocked TCP packets       [$tcp_new_num_pkts] (since: $tcp_new_start_time)\n" if $tcp;
					if (@tcp_flags) {
						print STDOUT $_ for (@tcp_flags);
					}
					print STDOUT "     Newly scanned UDP ports:  [$udp_newrange]  (since: $udp_new_start_time)\n" if $udp;
					print STDOUT "     Blocked UDP packets       [$udp_new_num_pkts] (since: $udp_new_start_time)\n" if $udp;
					print STDOUT "     Blocked ICMP packets:     [$icmp_new_num_pkts] (since: $icmp_new_start_time)\n" if $icmp;
					print STDOUT "     Complete port range:      [$range]  (since: $start_time) TCP and/or UDP\n" unless $icmp;
					print STDOUT "     Total number of packets:  $Scan_href->{$srcip}->{$dstip}->{'ABSNUM'}\n";
					print STDOUT "     Start time:               $start_time\n";
					print STDOUT "     End time:                 $end_time\n";
					print STDOUT "     Danger level:             $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'} out of 5\n";
					print STDOUT "     DNS info:                 $dnsstring\n" unless $dnslookups;
				}
				if ($USE_IPTABLES) {
					SIGS: for my $proto ("TCP", "UDP", "ICMP") {
						if (defined $Scan_href->{$srcip}->{$dstip}->{$proto}->{'CURRENT_SIGMATCH'}) {
							my $sig_start_time = $tcp_new_start_time;
							print $hndl "\n";
							print EMAILALERT "\n";
							print $hndl "=-=-= $proto alert signatures found since [$sig_start_time]\n";
							print EMAILALERT "=-=-= $proto alert signatures found since [$sig_start_time]\n";
							for my $sigmatch (keys %{$Scan_href->{$srcip}->{$dstip}->{$proto}->{'CURRENT_SIGMATCH'}}) {
								next if ($sigmatch eq "PACKETS" || $sigmatch eq "START_TIME");
								my $sig_num_packets = $Scan_href->{$srcip}->{$dstip}->{$proto}->{'CURRENT_SIGMATCH'}->{$sigmatch};
								print $hndl "$sigmatch  Packets=$sig_num_packets\n";
								print EMAILALERT "$sigmatch  Packets=$sig_num_packets\n";
							}
							### need to undef the current interval so it won't show up in the next alert
							undef $Scan_href->{$srcip}->{$dstip}->{$proto}->{'CURRENT_SIGMATCH'};
						}
						if (defined $Scan_href->{$srcip}->{$dstip}->{$proto}->{'SIGMATCH'} && $show_all_signatures eq "Y") {
							print $hndl "\n";
							print EMAILALERT "\n";
							print $hndl "=-=-= ALL $proto alert signatures found since [$start_time]\n";
							print EMAILALERT "=-=-= ALL $proto alert signatures found since [$start_time]\n";
							for my $sigmatch (keys %{$Scan_href->{$srcip}->{$dstip}->{$proto}->{'SIGMATCH'}}) {
								my $sig_num_packets = $Scan_href->{$srcip}->{$dstip}->{$proto}->{'SIGMATCH'}->{$sigmatch};
								print $hndl "$sigmatch  Packets=$sig_num_packets\n";
								print EMAILALERT "$sigmatch  Packets=$sig_num_packets\n";
							}
						}
					}
				} else {
					$flags_msg = 0;
				}
				unless ($whoislookups) {
					print $hndl "\n\n";
					print $hndl "   =-=-=-=-=-=-= Whois Information: =-=-=-=-=-=-=\n";
					print EMAILALERT "\n\n";
					print EMAILALERT "   =-=-=-=-=-=-= Whois Information =-=-=-=-=-=-=\n";
					print $hndl $_ for (@$whois_info_aref);
					print EMAILALERT $_ for (@$whois_info_aref);
					print $hndl "\n";
					print EMAILALERT "\n";
				}
				if ($USE_IPCHAINS) {
					&markerline($hndl);
					&markerline("EMAILALERT");
				}
				&markerline("EMAILALERT");
				close EMAILALERT;
				if ($Scan_href->{$srcip}->{$dstip}->{'ALERTED'} eq "N" && $current_danger_level
											>= $EMAIL_ALERT_DANGER_LEVEL) {
					$Scan_href = &send_email_alert($Scan_href, $srcip, $dstip, $flags_msg,
								$email_alertfile, $email_addresses_aref, $Cmds_href);
				}
				&markerline($hndl);
				$Scan_href->{$srcip}->{$dstip}->{'LOGR'} = "N";
				$Scan_href->{$srcip}->{$dstip}->{'EMAIL_LIMIT'}++;
			}
		}
	}
	close $hndl unless ($output || $DEBUG);
	return $Scan_href;
}
sub auto_psad_response(){
	my ($Scan_href, $auto_psad_level, $Cmds_href, $email_addresses_aref) = @_;
	SOURCEIP: for my $srcip (keys %$Scan_href) {
		for my $dstip (keys %{$Scan_href->{$srcip}}) {
			my $current_danger_level = $Scan_href->{$srcip}->{$dstip}->{'CURRENT_DANGER_LEVEL'};
			### We only want to block the IP once.  Currently this will block all traffic from the host to _all_ destinations
			### that are protected by the firewall if the ip trips the $auto_psad_level threshold for _any_ destination. 
			if ($current_danger_level >= $auto_psad_level && !(defined $Scan_href->{$srcip}->{$dstip}->{'BLOCKED'})) {
				if ($USE_IPCHAINS) {
					my @chains = &get_input_chains($Cmds_href->{'ipchains'});
					for my $inchain (@chains) {
						`$Cmds_href->{'ipchains'} -I $inchain 1 -s $srcip -l -j DENY`;
					}
					for my $dst (keys %{$Scan_href->{$srcip}}) {
						$Scan_href->{$srcip}->{$dst}->{'BLOCKED'} = "Y";
					}
					for my $email_address (@$email_addresses_aref) {
						system "$Cmds_href->{'mail'} $email_address -s \"psad: All traffic from $srcip has been BLOCKED on $HOSTNAME\" < /dev/null";
					}
					next SOURCEIP;
				} elsif ($USE_IPTABLES) {
					my @chains = &get_input_chains($Cmds_href->{'iptables'});
					for my $inchain (@chains) {
						`$Cmds_href->{'iptables'} -I $inchain 1 -s $srcip -j DROP`;
					}
					for my $dst (keys %{$Scan_href->{$srcip}}) {
						$Scan_href->{$srcip}->{$dst}->{'BLOCKED'} = "Y";
					}
					for my $email_address (@$email_addresses_aref) {
						system "$Cmds_href->{'mail'} $email_address -s \"psad: All traffic from $srcip has been BLOCKED on $HOSTNAME\" < /dev/null";
					}
					next SOURCEIP;
				}
			}
		}
	}
	return $Scan_href;
}
sub email_limit_reached() {
	my ($email_addresses_aref, $srcip, $Cmds_href) = @_;
	for my $email_address (@$email_addresses_aref) {
		system "$Cmds_href->{'mail'} $email_address -s \"psad: Email message limit for $srcip has been reached on $HOSTNAME!!!\" < /dev/null";
	}
	return;
}
sub get_input_chains() {
	my $fwCmd = shift;
	my @rules;
	my @chains;
	@rules = `$fwCmd -L`;
	for my $r (@rules) {
		next unless ($r =~ /Chain/);
		my ($cname) = ($r =~ /Chain\s(\w+)/);
		next unless ($cname =~ /in/i);  # we don't have an input chain
		push @chains, $cname;
	}
	return @chains;
}
sub send_email_alert() {
	my ($Scan_href, $srcip, $dstip, $flags_msg, $email_alertfile, $email_addresses_aref, $Cmds_href) = @_;
	for my $email_address (@$email_addresses_aref) {
		print "send_email_alert(): sending scan alert email to: $email_address\n" if $DEBUG;
		system("$Cmds_href->{'mail'} $email_address -s \"psad WARNING: $HOSTNAME has been scanned!\" < $email_alertfile");
	}
	$Scan_href->{$srcip}->{$dstip}->{'ALERTED'} = "Y";
	return $Scan_href;
}
sub print_scan() {  # this should primarily be used for debugging
	my $scanfile = $PRINT_SCAN_HASH . ".$$";
	open PSCAN, "> $scanfile";
	print PSCAN Dumper $Scan_href;
	close PSCAN;
	chmod 0600, $scanfile;
	return;
}
sub check_fw() {
	my $line = shift;
	if ($line !~ /MAC/) {  # ipchains log messages do not have a MAC address field
		$USE_IPCHAINS = 1;
	} else {
		$USE_IPTABLES = 1;
	}
}
sub get_local_ips() {
	my $Cmds_href = shift;
	my %localips;
	my @ips = `$Cmds_href->{'ifconfig'} -a |$Cmds_href->{'grep'} -w inet`;
	for my $iptmp (@ips) {
		my $ip = (split /:/, (split /\s+/, $iptmp)[2])[1];
		$localips{$ip} = "";
	}
	return \%localips;
}
sub get_listening_ports() {
	my $Cmds_href = shift;
	my %listening_ports;
	my @ports = `$Cmds_href->{'netstat'} -an |$Cmds_href->{'grep'} \"LISTEN\\b\"`;
	for my $port_tmp (@ports) {
		my ($proto, $p_tmp) = (split /\s+/, $port_tmp)[0,3];
		my $port = (split /:/, $p_tmp)[1];
		my $key = $proto . $port;
		$listening_ports{$key} = "";
	}
	return \%listening_ports;
}
sub get_whois_data() {
	my ($whois_timeout, $ip, $Cmds_href) = @_;
	my @whois_data;
	$whois_timeout = 1;
	eval {
		local $SIG{'ALRM'} = sub {die "alarm\n"};
		alarm $whois_timeout;
		@whois_data = `$Cmds_href->{'whois'} $ip`;
		alarm 0;
	};
	if ($@) {
		die unless $@ eq "alarm\n";
		$#whois_data = 0;
		@whois_data = ("Whois data not available!\n");
		return \@whois_data;
	} else {
		return \@whois_data;
	}
}
sub REAPER {
	my $pid;
	$pid = waitpid(-1, WNOHANG);
#	if (WIFEXITED($?)) {
#		print STDERR "@@@@@  Process $pid exited.\n";
#	}
	$SIG{'CHLD'} = \&REAPER;
	return;
}
sub check_permissions() {
	my @files = @_;
	for my $f (@files) {
		if (-e $f) {
			chmod 0600, $f;
		} else {
			open T, "> $f";
			close T;
			chmod 0600, $f;
		}
	}
	return;
}
sub kill_psad() {
	my $pidfiles_aref = shift;

	### must kill psadwatchd first since if not, it might try to restart any of the other three daemons; 
	### (it's the first element of $pidfiles_aref)
	for my $pidfile (@$pidfiles_aref) {
		# my $pidname = (split /\./, $pidfile)[1];
		my $pidname = (split /\./, (split /\//, $pidfile)[$#_])[0];
		if (-e $pidfile) {
			open PIDFILE, "< $pidfile";
			my $pid = <PIDFILE>;
			close PIDFILE;
			chomp $pid;
			if (kill 0, $pid) {
				print " ----  Killing $pidname, pid: $pid  ----\n";
				kill 15, $pid or print "@@@@@  psad: Could not kill $pidname, pid: $pid\n";
			} else {
				print "@@@@@  psad: $pidname is not running on $HOSTNAME.\n";
			}
		} else {
			print "@@@@@  psad: pid file $pidfile does not exist for $pidname on $HOSTNAME\n";
		}
	}
	return;
}
sub restart_psad() {
	my ($pidfiles_aref, $cmdline_file, $Cmds_href) = @_;
	my $cmdline;
	if (-e $cmdline_file) {
		open CMD, "< $cmdline_file";
		$cmdline = <CMD>;
		close CMD;
		chomp $cmdline;
	} else {
		die "@@@@@  psad:  No other psad process is currently running on $HOSTNAME!\n";
	}
	&kill_psad($pidfiles_aref);
	print " ----  Restarting the psad daemons on $HOSTNAME.  ----\n";
	system "$Cmds_href->{'psad'} $cmdline";
	return;
}
sub psad_status() {
	my ($pidfiles_aref, $cmdline_file, $Cmds_href) = @_;
	my $cmdline;
	### only the psad daemon runs with command line arguments
	if (-e $cmdline_file) {
		open CMD, "< $cmdline_file";
		$cmdline = <CMD>;
		chomp $cmdline;
	}
	my $rv = 0;   ### assume psad is not running and test...
	for my $pidfile (@$pidfiles_aref) {
#		my $pidname = (split /\./, (split /\//, $pidfile)[$#_])[0];
		my @p_tmp = split /\//, $pidfile;
		my $pname_tmp = $p_tmp[$#p_tmp];
		my $pidname = (split /\./, $pname_tmp)[0];
		if (-e $pidfile) {
			open PIDFILE, "< $pidfile";
			my $pid = <PIDFILE>;
			close PIDFILE;
			chomp $pid;
			if (kill 0, $pid) {
				print " ----  $pidname is running on $HOSTNAME as pid: $pid\n";
				my @grep_output = `$Cmds_href->{'ps'} -auxww |$Cmds_href->{'grep'} $pid`;
				GP: for my $g (@grep_output) {
					if ($g =~ /^\S+\s+$pid\s+(\S+)\s+(\S+)/) {
						print "       %CPU: $1  %MEM: $2\n";
						if ($pidname eq "psad" && $cmdline) {
							print "       CMDL ARGS: $cmdline\n";
						}
						last GP;
					}
				}
				$rv = 1;
			} else {
				print " ----  $pidname is not currently running on $HOSTNAME  ----\n";
			}
		} else {
			print "@@@@@  psad: pid file $pidfile does not exist for $pidname on $HOSTNAME\n";
		}
	}
	return $rv;
}
sub psad_usr1() {
	my $pidfiles_aref = shift;
	my $rv = 0;
	my $psad_pidfile = $pidfiles_aref->[1];
	if (-e $psad_pidfile) {
		open PIDFILE, "< $psad_pidfile" or die "@@@@@  Could not open $psad_pidfile: $!\n";
		my $pid = <PIDFILE>;
		close PIDFILE;
		chomp $pid;
		if (kill 0, $pid) {  ### make sure psad is actually running
			if (kill "USR1", $pid) {
				$rv = 1;
				sleep 1;  ### sleep to give time for the USR1 signal to be delivered and for the file to be created.
				open U, "< /var/log/psad/scan_hash.${pid}" 
				or print "@@@@@  Sent psad pid $pid a USR1 signal, but could not open\n" . 
					 "\"/var/log/psad/scan_hash.${pid}\n\"" and return $rv;
				print while(<U>);
				close U;
			} else {
				print "@@@@@  Could not send psad the USR1 signal on $HOSTNAME\n";
			}
		} else {
			print " ----  psad is not currently running on $HOSTNAME  ----\n";
		}
	}
	return $rv;
}
sub psad_setup() {
	my ($psad_dir, $fw_data, $psad_logfile, $error_log, $Cmds_href) = @_;
	unless (-d $psad_dir) {
		mkdir $psad_dir, 400;
	}
	unless (-e $fw_data) {
		open F, "> $fw_data";
		close F;
	}
	unless (-e $psad_logfile) {
		open L, "> $psad_logfile";
		close L;
	}
	unless (-e $error_log) {
		open E, "> $error_log";
		close E;
	}
	unless (-e "/var/log/psadfifo") {
		`$Cmds_href->{'mknod'} -m 600 /var/log/psadfifo p`;
	}
	unless (`$Cmds_href->{'grep'} psadfifo /etc/syslog.conf`) {
		copy("/etc/syslog.conf", "/etc/syslog.conf.orig") unless (-e "/etc/syslog.conf.orig");
		open SYSLOG, ">> /etc/syslog.conf" or die "@@@@@  Unable to open /etc/syslog.conf: $!\n";
		print SYSLOG "kern.info  |/var/log/psadfifo\n\n";  #reinstate kernel logging to our named pipe
		close SYSLOG;
		system("$Cmds_href->{'syslog_init'} restart");
	}
	return;
}
sub markerline() {
        my $hndl = shift;
	print $hndl "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
        return;
}
sub usage_and_exit() {
        my $exitcode = shift;
        print <<_HELP_;

psad; the Port Scan Attack Detector
Version: $VERSION
By Michael B. Rash (mbr\@cipherdyne.com, http://www.cipherdyne.com)

USAGE: psad [-D] [-d] [-o] [-e] [-L] [-f] [-r] [-w] [-l] [-i <interval>] [-h]
       [-V] [-K] [-R] [-U] [-S] [-c <config file>] [-s <signature file>] 
       [-a <auto ips file>]

OPTIONS:
        -D   --Daemon                   - do not run as a daemon.
        -e   --errors                   - do not write errors to the error
                                          log.
        -d   --debug                    - run psad in debugging mode.
        -w   --whois                    - disable whois lookups.
        -i   --interval                 - configure the check interval from
                                          the command line to override the 15
                                          second default.
        -f   --firewallcheck            - disable firewall rules verification.
        -o   --output                   - print all messages to STDOUT (this
                                          does not include bad packet messages
                                          that are printed to the error log).
        -c   --config <config file>     - use config file instead of the
                                          values contained within the psad
                                          script.
	-L   --Logging_server		- psad is being run on a syslog
					  logging server.
        -r   --reversedns               - disable name resolution against
                                          scanning ips.
        -s   --signatures <sig file>    - import scan signatures.
        -a   --auto_ips <ips file>      - import auto ips file for automatic
                                          ip danger level increases/decreses.
        -l   --local_port_lookup        - disable local port lookups for scan
                                          signatures.
	-K   --Kill			- kill all running psad processes.
	-R   --Restart			- restart all running psad processes.
	-S   --Status			- displays the status of any
					  currently running psad processes.
	-U   --USR1			- send a running psad process a USR1
					  signal.
	-V   --Version			- print the psad version and exit.
        -h   --help                     - prints this help message.

_HELP_
        exit $exitcode;
}
