#!/usr/bin/perl

# DEMARC 2000-2001, DEMARC Organization
#
#
# 1. REDISTRIBUTION
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer. You may not sell DEMARC,
# nor sell any of the functionality it provides without first obtaining a
# commercial license or receiving direct written authorization from DENARC
# Organization.
#
#
# 2. FREE FOR NON-COMMERCIAL USE
#
# No part of DEMARC may be used  by any commercial entity without
# having first obtained a commercial license from DEMARC Organization unless
# exempt by meeting one of the following conditions:
#
# a. Your company's primary business is as an ISP (Internet Service Provider)
#    and has a customer base of fewer than 1000 users.
#
# b. Your company has fewer than 25 employees and is not an ISP.
#
# c. You have extenuating circumstances and have received written authorization
#    from DEMARC Organization to use this software free of charge.
#
# A free evaluation period of 60 days is granted for any commercial entity who does
# not meet the conditions above but wishes to first try out the product.
#
#
# 3. TRADEMARKS AND NOTICES
#
# The software, graphics and documentation which make up DEMARC are Copyright
# 2000-2001 DEMARC Organization. You agree to respect these rights and leave all
# notices and product references intact.
#
#
# 4. LIMITATION OF LIABILITY AND DISCLAIMER OF WARRANTY
#
# THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
# DEMARC ORGANIZATION PROVIDE THIS PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND,
# EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
# QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
# DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
#
# DEMARC Organization reserves the right to modify this license at any time.
#
# Any questions or comments regarding this license or the official evaluation of
# individual circumstances may be directed to license@demarc.org
#
#

use IO::Socket;
use DBI;
use DBD::mysql;
use Digest::MD5  qw(md5_hex);
use POSIX;
use Getopt::Std;

#FYI : "strict" is not used in this script because it doesn't like the way 
# a function is checked for existence:
# eval {&$sub_string();};
# but all vars are scoped and clean coding practices are used 
#use strict;


use vars qw( 		$continue $ltime $ctime $ftime $old_time $every_seconds $every_minutes 
					$elapsed $since $count $DEBUG $lfh $db $send_continual_notices
					%opts 
					$alert_uid @local_immune_array $md5_run_count $snort_pid $demarcd_pid $pid
					@binary_dirs %ignore_files $elapsed_seconds_for_auto_update
					$snort_interface %conf
			);

# Get all options from the command line / conf file:
getopts("hDi:c:Ff:SkI",\%opts) || &print_usage;
&print_usage if $opts{'h'};
my $config_file = $opts{'f'} || "/etc/demarcd/demarcd.conf";
%conf = &parse_config;

############
my $db_user                			= &get_required_config_value("db_user");
my $db_password            			= &get_required_config_value("db_password");
my $db_host                			= &get_required_config_value("db_host");
my $db_name                			= &get_required_config_value("db_name");
##########

##########
my $sid								= &get_required_config_value("sid");
##########

##########
my $this_is_the_main_client			= &get_boolean_config_value("this_is_the_main_client",0);
my $main_monitor_sid        		= &get_boolean_config_value("main_monitor_sid",1);
##########

##########
my $base_path 						= &get_required_config_value("base_path");
my $pid_path						= &get_required_config_value("pid_path");
##########

##########
# Path/filename of logfile
my $logfile							= &get_config_value("logfile") || "/var/log/demarcd";
##########

##########
# Path/filename of allowed commands file
my $cmdfile							= &get_config_value("cmdfile") || "/etc/demarcd/regen.cmds";
##########

my $temp_binary_directories 		= &get_config_value("binary_dirs");
@binary_dirs						= split(/,/,$temp_binary_directories);

if (!(@binary_dirs)){
	@binary_dirs = qw ( /sbin /usr/sbin /usr/local/sbin
	                    /bin  /usr/bin  /usr/local/bin
	                    /usr/libexec /usr/local/libexec
        );
}


my $grep_binary            		= &binary_search($conf{'grep_binary'},"grep");
my $w_binary					= &binary_search($conf{'w_binary'},"w");
my $su_binary					= &binary_search($conf{'su_binary'},"su");
my $ping_binary					= &binary_search($conf{'ping_binary'},"ping");
my $mailprog					= &binary_search($conf{'mailprog'},"/usr/lib/sendmail");
my $ps_binary					= &binary_search($conf{'ps_binary'},"ps");
my $df_binary					= &binary_search($conf{'df_binary'},"df");
my $lynx_binary					= &binary_search($conf{'lynx_binary'},"lynx",1);
my $lynx_options				= &get_config_value("lynx_options") || "-width=1000000000000000";
$lynx_binary 					.= " $lynx_options" if ($lynx_binary);
my $tar_binary					= &binary_search($conf{'tar_binary'},"tar",1);


my $from_email_address		= &get_config_value("from_email_address") || "DEMARC_client_CHANGE_THIS_IN_CONFIG\@demarc.org";
##########

##########
# Should this host run snort?
my $run_snort_locally;
if (!$opts{'S'}){
	$run_snort_locally 		= &get_boolean_config_value("run_snort_locally",1);
}

my $snort_binary_path;
my $snort_conf_file;
my $snort_options;

if ($run_snort_locally){
	$snort_binary_path      		= &binary_search($conf{'snort_binary'},"snort");
	$snort_conf_file  = $opts{'c'} || &get_config_value("snort_conf_file") || "/var/lib/demarcd/snort.conf";
	$snort_options               	= &get_config_value("snort_options") || " -o -D -q ";#Special options for snort
}


my $auto_update_snort_rules	= &get_config_value("auto_update_snort_rules");
my $auto_update_minutes		= &get_config_value("auto_update_minutes") || 1440;

##########
# Should this host check local services such as disk, load, etc?
my $check_local_services		= &get_boolean_config_value("check_local_services",1);
##########

##########
# MD5 / File Integrity Checking?
my $check_md5s						= &get_boolean_config_value("check_md5s",1);

# ie if client runs every 5 mins, a "6" here would make the file check run every 30 minutes: 
my $check_every_n_inerations	= &get_config_value("check_every_n_inerations") || 6;
$md5_run_count					= -1; #this way it won't run the first time through 

#ONLY set this to 1 if you do NOT want the monitor to email md5 alerts, has no effect on clients:
my $IF_monitor_dont_email_md5_alerts	=   &get_boolean_config_value("IF_monitor_dont_email_md5_alerts",0);
##########

##########
# Time between iterations, ie check all services every n minutes:
$every_minutes 				= &get_config_value("every_minutes") || 5;

##########
# IF this host is the monitor,
# Should this host check for IDS alerts (should never have to unselect this)?
my $check_ids_alerts				= 1; # 0 if this client should NOT check alerts
my $default_priority_level 			= 2; # if you change this, you must also change it in the main program 

# High alert level, should never have to change this, but
# if you do you'll have to change it in all clients + main monitor
my $high_alert_level				= 1;

# Are portscans treated as high level alerts? #not needed since upgrade from snort 1.7
my $portscans_trigger_alerts	= 1;
##########

my $warn_on_duplicates_in_ping	= &get_boolean_config_value("warn_on_duplicates_in_ping",1);


# change previously defined minutes value to seconds for easy reference:
$every_seconds					= ($every_minutes * 60); # ie 5 minutes, DO NOT CHANGE PAST 10 MIN!
##########



###################
# See if we should be installing a new client
if ($opts{'I'}){
	&install_sensor;
	exit;
}
#######
if ($opts{'i'}){
	print "snort set to interface $opts{'i'}\n";
	$snort_interface = $opts{'i'};
	$snort_options .= " -i $opts{'i'} ";
}
#check for snort_interface in conf file:
elsif($snort_interface = &get_config_value("snort_interface")){
	$snort_options .= " -i $snort_interface ";
}
#######
if ($opts{'k'}){
	#Then all they really want to do it kill the current demarcd,
	#but we waited till here to be after the snort_interface definition
	&check_for_and_kill_demarcd;
	exit;
}
#######
if ($opts{'D'}){
	print "Running in DEBUG mode...";
	$DEBUG	= 1;
}
else{
	#make sure demarcd isn't already running (at least on this interface)
	&check_for_multiple_demarcds;
}
if ($opts{'F'}){
	print "Running in foreground mode...";
}
#######

##########
# Program info
my $program_name 		= "DEMARC";
my $program_version		= "1.05-RC1";

#############################################
# Control Section
#############################################

# Check for a few key things before starting

if (!$opts{'F'} && !$opts{'D'}){
##################################################
#
# FORK example template taken from O'Reilly Programming Perl (ch03_040)
# Done this way instead of using Proc::Daemon to avoid requiring more modules
#
# Fork to let parent exit:
FORK: {
    if ($pid = fork) {
		#record new demarcd PID:
		my $pid_file = "$pid_path/demarcd" . $snort_interface . ".pid";
		open (PIDFILE,">$pid_file") || return(&error("Can't open pid file for writing: $pid_file"));
		print PIDFILE $pid;
		close PIDFILE;
        # parent exits
		exit;
    } elsif (defined $pid) { # $pid is zero here if defined
        # Then it forked just fine ... great!
    } elsif ($! =~ /No more process/i) {     
        # EAGAIN, supposedly recoverable fork error
        sleep 5;
        redo FORK;
    } else {
        # wierd fork error
        die "Can't fork: $!\n";
    }
}

# dissociate from controlling tty
POSIX::setsid()
    or die "Can't start a new session: $!";
}


# OK, we're on our own (if desired), now lets change our process name to something informative:
$0 = "DEMARC Client";


$continue = 1;
$elapsed_seconds_for_auto_update = 0;

# see if we should freshen the rulesets
if ($run_snort_locally && ($auto_update_snort_rules=~/sourcefire/)){
	&update_rulesets_from_sourcefire($sid,"http://snort.sourcefire.com/downloads/snortrules.tar.gz");
}
if ($run_snort_locally && ($auto_update_snort_rules=~/whitehats/)){
	&update_rulesets_from_whitehats($sid,"http://www.whitehats.com/ids/vision18.rules.gz","2-whitehat_rules");
}


#make sure we have the latest copy the first time through:
&update_local_rules if ($run_snort_locally);


while ($continue){
	$alert_uid	= time() . &rand_string(6);
	$old_time	= $ctime; 
	$ctime 		= time();
	$since		= $ctime - $old_time;
	$ltime 		= localtime($ctime);
	$count++;
	if ($since > 900000){
		&debug("Starting first iteration at $ltime");
		&log("Starting first iteration at $ltime");
	}
	else{
		&debug("Starting iteration # $count at $ltime ( $since seconds since the last run)");
		&log("Starting iteration # $count at $ltime ( $since seconds since the last run)");
		$elapsed_seconds_for_auto_update += $since;#to keep track of when we should auto-update rules
	}
&update_blues if ($this_is_the_main_client);

# see if we should freshen the rulesets
if ($run_snort_locally && (($elapsed_seconds_for_auto_update / 60) >= $auto_update_minutes)){

	if ($auto_update_snort_rules=~/^sourcefire$/){
	    &update_rulesets_from_sourcefire($sid,"http://snort.sourcefire.com/downloads/snortrules.tar.gz");
	}
	if ($run_snort_locally && ($auto_update_snort_rules=~/whitehats/)){
		&update_rulesets_from_whitehats($sid,"http://www.whitehats.com/ids/vision18.rules.gz","2-whitehat_rules");
	}

$elapsed_seconds_for_auto_update = 0;
}



if ($run_snort_locally){
	&debug("Checking if Snort is running and if rules have been updated");
	&make_sure_snort_is_running || &big_error("Snort won't start!");
	if (&rules_have_been_updated){
		&debug("Updating local rules");
		&update_local_rules;
		&debug("Sending HUP to Snort process");
		&HUP_snort || error("Could not restart snort with new ruleset");
	}
	&debug("Finished checking snort rules/process");
}


&debug("Starting host service check");
	&update_host_services($sid) 	if ($this_is_the_main_client);
&debug("Finished host service check");

&debug("Starting local service check");
	&update_local_services($sid,$main_monitor_sid)  if ($check_local_services);
&debug("Finished local service check");

&debug("Starting IDS alert service check");
	&update_ids_alerts($sid) 		if (($this_is_the_main_client) && ($check_ids_alerts));
&debug("Finished IDS alert service check");

&debug("Starting to parse/send alerts");
	&parse_alerts if ($this_is_the_main_client);
&debug("Finished parsing/sending alerts");


if ($check_md5s){
	#check if it's our time to run

	if ($md5_run_count == 0){
		&debug("Starting MD5 Check");
		#this takes a while and increases the load on the machine, 
		#so lets report it so a "ps" will show why the load is high
		$0 = "DEMARC Client - CHECKING MD5s";
			&check_md5_controller($sid) if ($check_md5s);
		$0 = "DEMARC Client";
		&debug("Ending MD5 Check");
		&debug("Starting to parse/send MD5 alerts");
			&parse_md5_alerts if ($this_is_the_main_client && (!$IF_monitor_dont_email_md5_alerts));
		&debug("Finished parsing/sending MD5 alerts");
	}
$md5_run_count++;
if ($check_every_n_inerations <= $md5_run_count){
	# reset the count so it runs next time
	$md5_run_count = 0;
}

}


	$ftime = time();
	$elapsed = $ftime - $ctime;
&log("Finishing iteration # $count at " . localtime() . ", that took $elapsed seconds.");

	next if ($elapsed >= $every_seconds); #LONG run, lets more right on

	sleep ($every_seconds - $elapsed); #sleep until it's time to do it again
}

#############################################
# Functions
#############################################
sub update_host_services{
my ($SID) = @_;
my $sql_query;
my $db_ptr;
my $hash_ref;

$sql_query = "	SELECT \
						port,service,ip_addr,grouping,current_status,host_name, \
						UNIX_TIMESTAMP(last_changed) AS UNIX_LAST_CHANGED,check_dns \
					FROM \
						dm_monitor_current \
					WHERE (sid = '$SID') AND \
						((client_sid IS NULL) OR (client_sid = ''))";

$db_ptr = &run_query($sql_query);

# Cycle through the services to update
while ($hash_ref = $db_ptr->fetchrow_hashref){
 my $service 		= "\L$$hash_ref{'service'}\E";
 my $um_service	= "$$hash_ref{'service'}";
# my $host 			= &convert_long_ip($$hash_ref{'ip_addr'});
 my $long_ip		= $$hash_ref{'ip_addr'};
 my $grouping		= $$hash_ref{'grouping'};
 my $current_status	= $$hash_ref{'current_status'};
 my $sub_string 	= "check_$service";
 my $port			= $$hash_ref{'port'};
 my $last_changed = $$hash_ref{'UNIX_LAST_CHANGED'};
 my $host_name 	= $$hash_ref{'host_name'};
 my $check_dns = ($$hash_ref{'check_dns'}) ? 1 : ''; 
 my $status;
 my $detail;
my $changed;


# First check DNS for this host :
my ($new_long_ip,@address_list) = &get_dns($host_name,$check_dns);
my $host = &convert_long_ip($new_long_ip);

 if (!$new_long_ip){
	#then we couldn't get DNS for this host:
		($status,$detail) = &red("Couldn't get address for host: $host_name");
		$changed = ($current_status eq $status) 
					? 0
					: 1;
		&update_service ($sid,'',$um_service,$status,$detail,$changed,$host_name);
 }
 else{

if ($check_dns){
	#lets see if it matches what we had before:
	if ($long_ip){
		#then we have a past value and should check it:
		if (! (&compare_dns($long_ip,@address_list)) ){
			#HIJACKERS!!! (maybe ;)
			#&report_dns_hijacking($host_name,$long_ip,@address_list);
			my $detail2 = "$host_name\nOLD IP ADDRESS:" . &convert_long_ip($long_ip) . "\nNEW ADDRESS(es):" ;
			foreach (@address_list){
				$detail2 .= &convert_long_ip($_) . "\n";
			}
			($status,$detail) = &red("DNS HAS CHANGED!\nPossible DNS hijacking of $host_name : $service",$detail2);
			$changed = ($current_status eq $status) 
						? 0
						: 1;
			&update_service ($sid,$new_long_ip,$um_service,$status,$detail,$changed,$host_name);
			next;
		}
	}
}

 eval {&$sub_string();};
	if ($@){ 
		($status,$detail) = &red("Service not supported: $service");
		$changed = ($current_status eq $status) 
					? 0
					: 1;
		&update_service ($sid,$new_long_ip,$um_service,$status,$detail,$changed,$host_name);
		#no alerts for this, bc there's no real way to recover... it's
		# just a config problem.
	}
	else{ 
		&debug("Checking SERVICE:$service on HOST:$host PORT:$port (will show \"0\" if its default)");

		($status,$detail) = &$sub_string($host,$port);
		if ($status){
			$changed = ($current_status eq $status) 
						? 0
						: 1;
			&update_service ($sid,$new_long_ip,$um_service,$status,$detail,$changed,$host_name);
		}
		else{
			($status,$detail) = &red("No response from module: $service");
			$changed = ($current_status=~/^red$/i) 
						? 0
						: 1;
			&update_service ($sid,$new_long_ip,$um_service,$status,$detail,$changed,$host_name);
		}
		if (($status =~/^red$/i) || ($status =~/^yellow$/i) || ($current_status ne $status)){
			&push_alert(		new_status		=> $status,
								old_status		=> $current_status,
								long_ip			=> $new_long_ip,
								grouping		=>	$grouping,
								service			=> $service,
								old_unix_timestamp	=> $last_changed,
								host_name		=> $host_name,
								alert_uid		=> $alert_uid,
			);
		}
	}
 }
}
 
}
#############################################
sub update_local_services{
my ($SID,$main_monitor_sid) = @_;

my $sql_query;
my $db_ptr;
my $hash_ref;

$sql_query = "	SELECT \
						service,ip_addr,grouping,current_status,host_name,ext1,ext2,ext3, \
						UNIX_TIMESTAMP(last_changed) AS UNIX_LAST_CHANGED \
					FROM \
						dm_monitor_current \
               WHERE \
                         (sid = '$main_monitor_sid') AND \
                  (client_sid = '$SID')";

$db_ptr = &run_query($sql_query);

# Cycle through the local services to update
while ($hash_ref = $db_ptr->fetchrow_hashref){
 my $service 	= "\L$$hash_ref{'service'}\E";
 my $um_service= "$$hash_ref{'service'}";
 my $host 		= &convert_long_ip($$hash_ref{'ip_addr'});
 my $grouping	= $$hash_ref{'grouping'};
 my $current_status	= $$hash_ref{'current_status'};
 my $sub_string = "check_local_$service";
 my $port		= $$hash_ref{'port'};
 my $status;
 my $detail;
 my $last_changed = $$hash_ref{'UNIX_LAST_CHANGED'};
 my $host_name 	= $$hash_ref{'host_name'};
 my $changed;
 eval {&$sub_string();};
	if ($@){ 
		($status,$detail) = &red("Local Service not supported: $service");
		$changed = ($current_status eq $status) 
						? 0
						: 1;
		&update_service ($main_monitor_sid,'',$um_service,$status,$detail,$changed,$host_name);
	}
	else{ 
		&debug("Checking SERVICE:$service on HOST:$host PORT:$port");
		($status,$detail) = &$sub_string("CHECK",$$hash_ref{'ext1'},$$hash_ref{'ext2'},$$hash_ref{'ext3'});
		if ($status){
			$changed = ($current_status eq $status) 
							? 0
							: 1;
			&update_service ($main_monitor_sid,'',$um_service,$status,$detail,$changed,$host_name);
		}
		else{
			($status,$detail) = &red("No response from module: $service");
			$changed = ($current_status=~/^red$/i) 
							? 0
							: 1;
			&update_service ($main_monitor_sid,'',$um_service,$status,$detail,$changed,$host_name);
		}
	}
		if (($status =~/^red$/i) || ($status =~/^yellow$/i) || ($current_status ne $status)){
			&push_alert(	new_status		=> $status,
								old_status		=> $current_status,
								long_ip			=> '',
								grouping			=>	$grouping,
								service			=> $service,
								old_unix_timestamp	=> $last_changed,
								host_name		=> $host_name,
								alert_uid		=> $alert_uid,
			);
		}

}

 
}
#####################
sub convert_long_ip{
my ($long) = @_;
my $t1;
my $new_ip;

for (my $i=3;$i>=0;$i--){
        $t1 = sprintf ("%d",($long / (256 ** $i)));
        $new_ip .= $t1.".";
        $long = $long - ($t1 * (256 ** $i))
}
$new_ip =~s/\.$//;

return $new_ip;
}
####################
sub convert_dotted_ip{
my ($ip) = @_;

my @IP = split(/\./,$ip);

my $long_ip =(
         ($IP[0]*(256**3))
       + ($IP[1]*(256**2))
       + ($IP[2]*(256**1))
       + ($IP[3])
        );
return $long_ip;
}


########################
sub check_telnet{
my ($host,$port) = @_;
($host) || return;

my %green;
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "23");
my $connect_only			= 1;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
	);

}
########################
sub check_nntp{
my ($host,$port) = @_;
($host) || return;

my %green;
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "119");
my $connect_only			= 1;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
	);

}
########################
sub check_dns{
my ($host,$port) = @_;
($host) || return;

my %green;
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "53");
my $connect_only			= 1;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
	);

}
########################
sub check_https{
my ($host,$port) = @_;
($host) || return;

my %green;
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "443");
my $connect_only			= 1;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
							debug=>			0,
	);

}
#######################
sub check_ssh{
my ($host,$port) = @_;
($host) || return;

my %green = (
'SSH OK' 	=> 'SSH',
);
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "22");
my $connect_only			= 0;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
							lines_to_read=>1,
	);

}
#######################
sub check_ftp{
my ($host,$port) = @_;
($host) || return;

my %green = (
'FTP OK' 	=> 'ftp',
'FTP OK ' 	=> 'FTP',
);
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "21");
my $connect_only			= 0;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							green=>			\%green,
							yellow=>			\%yellow,
							red=>				\%red,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
							lines_to_read=>1,
	);

}
#######################
sub check_smtp{
my ($host,$port) = @_;
($host) || return;

my %green = (
'SMTP OK' 	=> '^220.+SMTP',
);
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "25");
my $connect_only			= 0;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
							lines_to_read=>1,
	);

}
#######################
sub check_http{
my ($host,$port) = @_;
($host) || return;

my %green = (
'HTTP OK' 	=> '200',
);
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "80");
my $connect_only			= 0;
my $timeout_seconds		= 10;
my $connection_request	= "HEAD / HTTP/1.0 OK\n\r\n\r";

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
							lines_to_read=>10,
	);

}
#######################
sub check_pop3{
my ($host,$port) = @_;
($host) || return;

my %green = (
'POP3 OK' 	=> 'OK',
'POP3 OK ' 	=> 'ok',
);
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "110");
my $connect_only			= 0;
my $timeout_seconds		= 10;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=> "QUIT\n",
							lines_to_read=>1,
	);

}
#######################
sub check_imap4{
my ($host,$port) = @_;
($host) || return;

my %green = (
'IMAP4 OK' 	=> 'OK',
'IMAP4 OK ' 	=> 'ok',
);
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "143");
my $connect_only			= 0;
my $timeout_seconds		= 10;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=> "abcd LOGOUT\n",
							lines_to_read=>1,
	);

}
#######################
sub check_mysql{
my ($host,$port) = @_;
($host) || return;

my %green = (
'MYSQL OK' 	=> '3\.',
);
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "3306");
my $connect_only			= 0;
my $timeout_seconds		= 10;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							lines_to_read=>2,
	);

}
##################
sub check_ping{
my ($host) = @_;
($host)             	|| return;
my $timeout				= 10;
my $count_argument   	= "-c";
my $count				= 4;
my $ping_warning_level 	= ".75"; # ie ".75" would mean below 75%
my $ping_critical_level	= ".5"; 

my $p_rec;
my $p_sent;
my $p_loss;
my $errors;
my $dupes;
my $min;
my $max;
my $avg;
my $result;
my $bad_result;

eval {
   local $SIG{ALRM} = sub { die"timed_out\n"};
   alarm $timeout;
   my $pid =  open (PING,"$ping_binary $count_argument 1 $host |") || die "Couldn't start ping program! : $! \n";
	while (<PING>){
		$result .= $_;
	}
   close PING; 
};
if ($@){
   alarm 0;
   close PING; 
	$bad_result = 1;
}
alarm 0;

if ($result=~/(\d+)\s+packets\s+received/){
   $p_rec = $1;
}
else{
   $bad_result = 1;
}
if ($result=~/(\d+)\s+packets\s+transmitted/){
   $p_sent = $1;
}
else{
   $bad_result = 1;
}
if ($result=~/([\d\.]+)\/([\d\.]+)\/([\d\.]+)/){
   $min = $1;
   $max = $2;
   $avg = $3;
}
else{
   $bad_result = 1;
}

if ($p_sent){
	if (($p_rec/$p_sent) < $ping_critical_level){
	   $bad_result = 1;
	}
	elsif (($p_rec/$p_sent) < $ping_warning_level){
	   $bad_result = 1;
	}
}
else{
	#error... no packets sent?  well at least this saved us from an illegal division by 0 error...
	$bad_result = 1;
}

#check for dupes
if ($warn_on_duplicates_in_ping && ($result=~/(\d+)\s+duplicates/)){
   $bad_result = 1;
}


######### Recheck with more pings?

if ($bad_result){
	$result = ();#clear last result and try again
	eval {
	   local $SIG{ALRM} = sub { die"timed_out\n"};
	   alarm $timeout;
	   my $pid =  open (PING,"$ping_binary $count_argument $count $host |") || die "Couldn't start ping program! : $! \n";
		while (<PING>){
			$result .= $_;
		}
	   close PING; 
	};
	if ($@){
	   alarm 0;
	   close PING; 
	   return &red("Timed out waiting for response from ping command: $@ ",$result);
	}
	alarm 0;
}
else{
	# then everything was okay on the first try:
	# lets report back that all is OK
	return &green("Ping successful.",$result);
}


if ($result=~/(\d+)\s+packets\s+received/){
   $p_rec = $1;
}
else{
   &red("Couldn't determine packets received.",$result);
}
if ($result=~/(\d+)\s+packets\s+transmitted/){
   $p_sent = $1;
}
else{
   return &red("Couldn't determine packets transmitted.",$result);
}
if ($result=~/([\d\.]+)\/([\d\.]+)\/([\d\.]+)/){
   $min = $1;
   $max = $2;
   $avg = $3;
}
else{
   return &red("Couldn't determine min/max/avg packet stats.",$result);
}

if ($p_sent){
	if (($p_rec/$p_sent) < $ping_critical_level){ 
	   return &red("Critical packet loss",$result);
	}
	elsif (($p_rec/$p_sent) < $ping_warning_level){ 
	   return &yellow("Packet loss at warning level.",$result);
	}
}
else{
    #error... no packets sent?  well at least this saved us from an illegal division by 0 error...
	   return &red("No packets sent?!?!?",$result);
}

#check for dupes
if ($warn_on_duplicates_in_ping && ($result=~/(\d+)\s+duplicates/)){
   return &yellow("Duplicate packets detected.",$result);
}

# OK, lets report back that all is OK
return &green("Ping successful.",$result);

}
######################




##################
sub generic_connect{
my ($green_ref,$yellow_ref,$red_ref,%args) = @_;

my $banner;
my $eval_return;
my $eval_status;
my $sock;

($args{'host'}=~/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) || return &red("No or invalid IP address specified.");
($args{'port'}=~/^\d+$/) || return &red("No port specified.");

eval {
	local $SIG{ALRM} = sub { die"timed_out\n"};
	$args{'timeout'} =  ($args{'timeout'}=~/^\d+$/) ? $args{'timeout'} : 10;
	alarm $args{'timeout'};


	$sock = new IO::Socket::INET (   PeerAddr => $args{'host'},
                                    	PeerPort => $args{'port'},
											);
	if (!$sock){
		($eval_status,$eval_return) =  &red("Unable to create socket connection");
		alarm 0;
		return;
	}
	$sock->autoflush(1);
};
if ($@){
	alarm 0;
	return &red("Timed out waiting for response from connection: $@");
}
alarm 0;

if ($eval_return){
	return ($eval_status,$eval_return);
}
&debug( "status is GREEN! \n") if ($args{'debug'} && $args{'connect_only'});
return  &green("Successful connection") if ($args{'connect_only'});

eval {
	local $SIG{ALRM} = sub { die"timed_out\n"};
	$args{'timeout'} =  ($args{'timeout'}=~/^\d+$/) ? $args{'timeout'} : 10;
	alarm $args{'timeout'};
	print $sock "$args{'request'}" if ($args{'request'});
	my $line_count;
	while (<$sock>){
		$banner .= $_;
		$line_count++;
		if ($args{'lines_to_read'} && ($line_count >= $args{'lines_to_read'})){	
			#stop waiting for more input if we already have all the lines we need
			last;
		}
	}
};
if ($@){
   # Don't need to do anything BC we expected an alarm
}
alarm 0;

# Take out any not ASCII chars (specifically inserted for MySQL)
$banner=~tr/\!-~\n\t //dc;
	$sock->close if ($sock);

if ($eval_return){
	return ($eval_status,$eval_return);
}

if ($green_ref){
	foreach (keys %$green_ref){
		&debug("CHECKING FOR $$green_ref{$_} STATUS:$_ in $banner");
		if ($banner=~/$$green_ref{$_}/){
			return &green($_,$banner);
		}
	}
}

return &red("No matching response!",$banner);

}
##################
sub green{
my ($g,$r) = @_;

my $string = localtime() . "\n" . $g . "\n";
$string .= "$r\n" if ($r);
return ("GREEN",$string);
}
##################
sub yellow{
my ($g,$r) = @_;

my $string = localtime() . "\n" . $g . "\n";
$string .= "$r\n" if ($r);
return ("YELLOW",$string);
}
##################
sub red{
my ($e,$r) = @_;
my $error_string = localtime() . "\n" . "$e\n";
$error_string .= "$r\n" if ($r);
return ("RED",$error_string);
}
##################
sub big_error{
my ($e) = @_;
	&log($e);
	die "$e\n";
}
##############
sub run_query{
my ($sql_query) = @_;
&connect_to_db();

my $prepared = $db->prepare($sql_query);
if (!$prepared){
        #Then there's an error with the syntax of the query:
        &big_error("Syntax Error in SQL Query:<BR><B>$sql_query</B><P>".$db->errstr);
}
my $dbquery = $prepared->execute();
if (my $error_string = $prepared->errstr){
        &big_error("$error_string : $sql_query");
}
return $prepared;
}
#####################
sub connect_to_db{
if (!$db || !$db->ping){

# function args override defaults, but error out if neither are there.
 $db_user         ||  &red("No database user specified.");
 $db_password     ||  &red("No database password specified.");
 $db_host         ||  &red("No database host specified.");
 $db_name         ||  &red("No database specified.");

 $db = DBI->connect("dbi:mysql:host=$db_host;database=$db_name", $db_user , $db_password) || &red("Connection Problem! ". $DBI::errstr);

}
return $db;
}
####################
sub update_service{
my ($sid,$long_ip,$service,$status,$detail,$changed,$host_name) = @_;
my $sql_query;
my $db_ptr;
my $hash_ref;
my $current_status;

&safe_slash(\$service);
&safe_slash(\$status);
&safe_slash(\$detail);

# Get current status first:
$sql_query = "	SELECT current_status \
					FROM \
						dm_monitor_current \
					WHERE \
						sid      = '$sid' AND \
                  host_name  = '$host_name' AND \
                  service  = '$service'";
$db_ptr 				= &run_query($sql_query);
$hash_ref 			= $db_ptr->fetchrow_hashref;
$current_status 	= $$hash_ref{'current_status'}; 

if ($current_status ne $status){
&debug("CHANGE! : $current_status ne $status for $service on " . &convert_long_ip($long_ip));
	#then there's been a change in status and
	#we should record that
	$sql_query = "	INSERT INTO \
							dm_monitor_events \
						VALUES( \
							'$sid', '', '$service',\
							'$long_ip','$status', \
							'$detail',NOW(),'$host_name' \
						)";
&connect_to_db;
($db->do($sql_query)) || &local_error("oooh, not so good, errored out on this INSERT:\n$sql_query");

}
else{
&debug( "CHECKS OUT: $long_ip,$service,NEW: $status, OLD: $current_status\n");
}

$changed = 	($changed) 
			? ", last_changed 	= NOW() " 
			: '';

$sql_query = "	UPDATE dm_monitor_current \
					SET \
						last_checked 	= NOW() , \
						current_status	= '$status' , \
						ip_addr			= '$long_ip' , \
						current_detail	= '$detail'  $changed \
					WHERE \
						sid 		= '$sid' AND \
						host_name	= '$host_name' AND \
						service 	= '$service'";

&connect_to_db;
($db->do($sql_query)) || &local_error("oooh, not so good, errored out on this UPDATE:\n$sql_query");

#&debug($sql_query);
#print "X"x25 . "\n$sql_query\n";

}
#####################
sub local_error{
my ($le) = @_;
print STDERR $le . "\n";
}
##################
sub debug{
my ($debug_string) = @_;
	if ($DEBUG){
		print STDOUT $debug_string . "\n";
	}
}
#################
sub safe_slash{
my ($string_ref) = @_;

$$string_ref=~s/(['\\])/\\$1/g;

}
################
sub update_blues{
my $sql_query;
my $db_ptr;
my $hash_ref;

my $blue_detail = localtime() . "\nNo report from client in over 10 minutes.";

# Record the new no-shows to report to dm_monitor_events
$sql_query = "	SELECT \
						sid,ip_addr,host_name,service,last_checked \
					FROM \
						dm_monitor_current \
					WHERE \
						sid = '$sid' AND \
                  last_checked < DATE_ADD(NOW(), INTERVAL (-11) MINUTE) AND \
                  current_status != 'BLUE' AND \
					current_status != ''";#last one is to avoid marking a blue when it's a new service
$db_ptr = &run_query($sql_query);
#&debug($sql_query);

while ($hash_ref = $db_ptr->fetchrow_hashref){
my $time_val = ($$hash_ref{'last_checked'}=~/[1-9]/)? "'$$hash_ref{'last_checked'}'" : "NOW()";
	$sql_query = "	INSERT \
							INTO dm_monitor_events \
                  VALUES( \
                     '$sid', '', '$$hash_ref{'service'}',\
                     '$$hash_ref{'ip_addr'}','BLUE', \
                     '$blue_detail',DATE_ADD($time_val, INTERVAL (10) MINUTE),'$$hash_ref{'host_name'}' \
                  )";
	&connect_to_db;
	$db->do($sql_query);
}

# Now update the "current" table
$sql_query = "	UPDATE dm_monitor_current \
					SET \
						current_status = 'BLUE', \
						current_detail = '$blue_detail', \
						last_checked	= NOW(), \
						last_changed	= NOW() \
					WHERE \
						last_checked < DATE_ADD(NOW(), INTERVAL (-11) MINUTE) AND \
						sid = '$sid' AND \
						current_status != 'BLUE'";
&connect_to_db;
$db->do($sql_query);
						
}
#################
sub log{
my ($to_log) = @_;

&debug($to_log);
#####
# First open handle to logfile:
	open (LOG,">>$logfile") || return;
	print LOG $to_log . "\n";
	#print $to_log . "\n";
	$| = 1;
	close LOG;
}
###################
sub check_local_load{
my ($check,$ext1,$ext2,$ext3) = @_;

my $warning_load;
my $critical_load;

($check) || return; #for determining existence of function
$critical_load = ($critical_load=~/^[\d\.]+$/)
					? $critical_load
					: 3;
$warning_load = ($warning_load=~/^[\d\.]+$/)
					? $warning_load
					: 1.7;

my $result;
my $untouched_result;
my @loads;
my $status;
my $detail;
my $critical;
my $warning;

$result = `$w_binary`;
$result =~tr/\!-~\n\t //dc;#Make sure there's no funky characters in the response
$untouched_result = $result;#save a copy to report to DB

#take out all but first line:
$result =~s/\n[\s\S]*$//;
#take out all but load averages:
$result=~s/^.*load[\s\S]*?(\d)/$1/;
#take out leading whitespace,commas
$result =~s/^\s+|,//g;
#convert tabs to spaces and squash spaces
$result=~s/\t/ /g;
$result=~tr/ //s;

@loads = split(/ /,$result);

foreach (@loads){
	if ($_ > $critical_load){
		$critical = 1; #report a critical load level
	}
	elsif ($_ > $warning_load){
		$warning = 1;
	}
}
if ($critical){
	$untouched_result .= "\n\nPROCESS LIST:\n" . `$ps_binary auxwwwwwwwww`;
	return &red("System load at critical level",$untouched_result);
}
elsif ($warning){
	$untouched_result .= "\n\nPROCESS LIST:\n" . `$ps_binary auxwwwwwwwww`;
	return &yellow("System load moderately high",$untouched_result);
}
else{
	return &green("system load within specified parameters",$untouched_result);
}

}
#######################
sub check_local_disks{
my ($check,$ext1,$ext2,$ext3) = @_;

my $warning_pct;
my $critical_pct;

($check) || return; #for determining existence of function
$critical_pct = ($critical_pct=~/^[\d\.]+$/)
               ? $critical_pct
               : 95;
$warning_pct = ($warning_pct=~/^[\d\.]+$/)
               ? $warning_pct
               : 85;

my $result;
my $untouched_result;
my @pcts;
my @pcts2;
my $status;
my $detail;
my $critical;
my $warning;

$result = `$df_binary`;
$result =~tr/\!-~\n\t //dc;#Make sure there's no funky characters in the response
$untouched_result = $result;#save a copy to report to DB

#truncate all spaces
$result=~s/\t/ /g;
$result=~tr/ //s;

@pcts = split(/\n/,$result);

foreach (@pcts){
   if (($_ !~/\/proc|cdrom/) && ($_=~/\%/)){
      my @tmp_array = split(/ /,$_);
      $tmp_array[4]=~s/\%//;

      ($tmp_array[4]=~/^[\d\.]+$/) || next; #skip it if ist not a digit
      if ($tmp_array[4]>=$critical_pct){
         $critical = 1; #report a critical pct level
      }
      elsif ($tmp_array[4]>=$warning_pct){
         $warning = 1; #report a warning pct level
      }
   }
last if ($critical);#  we already know theres a critical problem, so forget further checks
}


if ($critical){
   return &red("Disk Space at critical level\n",$untouched_result);
}
elsif ($warning){
   return &yellow("Disk Space at warning level\n",$untouched_result);
}
else{
   return &green("Disk Space within specified parameters\n",$untouched_result);
}

}
###################
sub check_local_proc{
my ($check,$ext1,$ext2,$ext3) = @_;

($check) || return; #for determining existence of function

my $result;
my @results;
my @current_stats;
my @procs_to_test;
my $rule;
my $ps_line;
my @red_alerts = ();
my @yellow_alerts = ();
my @green_alerts  = ();#oxymoron?
my %allowed_commands;

$ext3=~s/\r//g;
@procs_to_test = split(/\n/,$ext3);
(@procs_to_test) || return &yellow("No processes to monitor.");

$result = `$ps_binary auxwwwwwwwwwwwwwwwwwwwwwwww`;

@results = split(/\n/,$result);


foreach $rule(@procs_to_test){
	my @rule_array = split(/;/,$rule);
	($rule_array[1]) || return;
	my $rule_count;
	my $matched_count 	= 0;
	my $regen_status 	= ();

	#test the process list for matching processes
	foreach $ps_line(@results){
		($matched_count++) if ($ps_line=~/$rule_array[1]/);		
	}
	#see if we're within established parameters
	($rule_array[2]=~/^[1-9]+\d*$/) || ($rule_array[2]=1);
	if ($matched_count < $rule_array[2]){
		#then there aren't enough processes running... Alert time, + regenerate if requested
		if ($rule_array[5]){
			#then there is a regeneration command
			($rule_array[6]=~/\S/) || ($rule_array[6] = "root");
			(%allowed_commands = &get_allowed_commands) if (!%allowed_commands);
			if ($allowed_commands{"$rule_array[6];$rule_array[5]"}){
				#then we're allowed to run this command:
				$regen_status = "\nAttempted to restart process with supplied command/user";
				&run_command($rule_array[5],$rule_array[6]);
			}
			else{
				#then we're NOT allowed to run this command:
				$regen_status = "\nSupplied regeneration command/user NOT found in regen.cmds file: Permission Denied. ";
				#escalate to RED status:
				$rule_array[0]="RED";
			}
			
		}
		if ($rule_array[0]=~/yellow/i){
			push (@yellow_alerts,"$rule_array[4] /$rule_array[1]/ has $matched_count processes running.$regen_status");
		}
		else{
			#default to red
			push (@red_alerts,"$rule_array[4] /$rule_array[1]/ has $matched_count processes running.$regen_status");
		}
	}
	elsif (($rule_array[3]) && ($matched_count > $rule_array[3])){
		#then there's too many processes running
		if ($rule_array[0]=~/yellow/i){
			push (@yellow_alerts,"$rule_array[4] /$rule_array[1]/ has $matched_count processes running");
		}
		else{
			#default to red
			push (@red_alerts,"$rule_array[4] /$rule_array[1]/ has $matched_count processes running");
		}
	}
	else{
		#"this bed is just right"
		push (@green_alerts,"$rule_array[4] /$rule_array[1]/ has $matched_count processes running");
	}

}

my $status_report;

foreach (@red_alerts){
	$status_report .= "RED: $_\n";
}
foreach (@yellow_alerts){
	$status_report .= "YELLOW: $_\n";
}
foreach (@green_alerts){
	$status_report .= "GREEN: $_\n";
}

if (@red_alerts){
	return &red("Monitored System Processes not within established parameters.\n",$status_report);
}
elsif (@yellow_alerts){
	return &yellow("Monitored System Processes not within established parameters.\n",$status_report);
}
else{
	return &green("Monitored System Processes within established parameters.\n",$status_report);
}

}

########################
sub push_alert{
my (%args) = @_;

($args{'new_status'}) 			|| return;
($args{'old_status'}) 			|| return;
($args{'old_unix_timestamp'})	|| return;
($args{'grouping'}) 				|| return;
($args{'service'}) 				|| return;
($args{'host_name'}) 			|| return;
($args{'alert_uid'}) 			|| return;
my $sql_query;
my $db_ptr;
my $hash_ref;


$sql_query = "	INSERT INTO \
						dm_monitor_alerts_raw \
					VALUES( \
						'$args{'alert_uid'}',NOW(), '$args{'long_ip'}', \
						'$args{'host_name'}', '$args{'service'}', \
						'$args{'grouping'}', UNIX_TIMESTAMP(NOW()) - $args{'old_unix_timestamp'}, \
						'$args{'old_status'}', '$args{'new_status'}' \
					)";
&connect_to_db;
$db->do($sql_query);
}
####################################
sub rand_string{
my ($suggested_length,$exact_length) = @_;
my $randstring;
my $i;

    for($i=0,$randstring="";$i<$suggested_length;$i++){
        if(int(rand(2))){
            $randstring.=chr(65+(int(rand(26))));
        }
        else{
            $randstring.=chr(97+(int(rand(26))));
        }
      if (!$exact_length){
        if(int(rand(2))){
            $randstring.=int(rand(10));
        }
      }
    }
    return $randstring;
}
###################################
sub parse_alerts{
&purge_old_alerts;
my $sql_query;
my $db_ptr;
my $db_ptr_2;
my $hash_ref;
my $hash_ref_2;
my %alerts_sent;

$sql_query = "	SELECT * FROM \
						dm_monitor_alert_rules \
					WHERE \
						only_alert_from_hour <= EXTRACT(HOUR FROM NOW()) AND \
						only_alert_to_hour 	>= EXTRACT(HOUR FROM NOW()) \
					ORDER BY \
						grouping, host_name, service ";
$db_ptr = &run_query($sql_query);

while ($hash_ref = $db_ptr->fetchrow_hashref){
	$sql_query = "	SELECT * FROM \
							dm_monitor_alerts_raw \
						WHERE \
							(auid = '$alert_uid') ";
	$sql_query .= " AND (grouping = '$$hash_ref{'grouping'}') " if ($$hash_ref{'grouping'});
	$sql_query .= " AND ( host_name = '$$hash_ref{'host_name'}') "  if ($$hash_ref{'host_name'});
	$sql_query .= " AND ( service = '$$hash_ref{'service'}') "  if ($$hash_ref{'service'});
	$sql_query .= " AND ( new_status = '$$hash_ref{'status_level'}') "  if ($$hash_ref{'status_level'});
	
	$db_ptr_2 = &run_query($sql_query);
	while ($hash_ref_2 = $db_ptr_2->fetchrow_hashref){

		my $temp_msg;
		my $temp_status;

		if ($$hash_ref_2{'new_status'}=~/^red$/i){
			if ($$hash_ref_2{'old_status'}=~/^red$/i){
				#then this is another red alert	(not the 1st one)
				# first see if they want to get the continuous messages:
				if ($$hash_ref{'continual_reds'}){
					#now lets see if they wanted to limit the response:
					if ((!$$hash_ref{'limit_alerts'}) 
							|| (($$hash_ref_2{'duration'} % (60 * $$hash_ref{'limit_alerts'})) < (1 * $every_seconds))
						){
							$temp_status = "CRITICAL (repeat)";
							$temp_msg = "$$hash_ref_2{'service'} on $$hash_ref_2{'host_name'} still at CRITICAL level";
					}
				}
			}
			else{
				#NEW red alert
				$temp_status = "CRITICAL";
				$temp_msg = "$$hash_ref_2{'service'} on $$hash_ref_2{'host_name'} is now at CRITICAL level";
			}
		}
		elsif ($$hash_ref_2{'new_status'}=~/^yellow$/i){
			if ($$hash_ref_2{'old_status'}=~/^yellow$/i){
				#then this is another yellow alert	(not the 1st one)
				# first see if they want to get the continuous messages:
				if ($$hash_ref{'continual_yellows'}){
					#now lets see if they wanted to limit the response:
					if ((!$$hash_ref{'limit_alerts'}) 
							|| (($$hash_ref_2{'duration'} % (60 * $$hash_ref{'limit_alerts'})) < (1 * $every_seconds))
						){
							$temp_status = "WARNING (repeat)";
               		$temp_msg = "$$hash_ref_2{'service'} on $$hash_ref_2{'host_name'} still at WARNING level";
					}
				}
			}
			else{
				#changed status to yellow (warning)
				$temp_status = "WARNING";
				$temp_msg = "$$hash_ref_2{'service'} on $$hash_ref_2{'host_name'} is now at WARNING level";
			}
		}
		elsif ($$hash_ref_2{'new_status'}=~/^green$/i){
			#recovered to green status
			$temp_status = "RECOVERED";
			$temp_msg = "$$hash_ref_2{'service'} on $$hash_ref_2{'host_name'} has RECOVERED!";
		}
		else{
			#nothing but blue is left, so lets assume (aka catch-all)...
			$temp_status = "NO REPORT";
			$temp_msg = "$$hash_ref_2{'service'} on $$hash_ref_2{'host_name'} has NOT REPORTED IN!";
		}
	########
		if ($temp_status){
			my $temp_subject;
			#Then we should send the alert
				my $dotted_ip = &convert_long_ip($$hash_ref_2{'ip_addr'});
				$temp_subject = "$temp_status - $$hash_ref_2{'host_name'} - $$hash_ref_2{'service'} - $dotted_ip - $program_name - $program_version";
my $duration_minutes	= sprintf("%d",($$hash_ref_2{'duration'}/60));
my $duration_seconds	= sprintf("%d",($$hash_ref_2{'duration'}%60));
$duration_minutes = ($duration_minutes)
				? $duration_minutes
				: "0";
$duration_seconds = ($duration_seconds)
				? $duration_seconds
				: "0";

				my $message = << "EOF";
HOST: $$hash_ref_2{'host_name'}
SERVICE: $$hash_ref_2{'service'}
NEW STATUS: $$hash_ref_2{'new_status'}
GROUP: $$hash_ref_2{'grouping'}
DURATION: $duration_minutes mins $duration_seconds secs
IP ADDRESS: $dotted_ip
CHANGED FROM: $$hash_ref_2{'old_status'}
TIMESTAMP: $$hash_ref_2{'alert_timestamp'}
EOF
		#Make sure we haven't already sent a message about this to this address:
		if(!$alerts_sent{$$hash_ref{'email_address'}.";".$$hash_ref_2{'grouping'}.";".$$hash_ref_2{'ip_addr'}.";".$$hash_ref_2{'service'}}){

			&send_message($$hash_ref{'email_address'},$temp_subject,$message);
		}
			#mark this alert to this email address so it doesn't get sent out again:
				$alerts_sent{$$hash_ref{'email_address'}.";".$$hash_ref_2{'grouping'}.";".$$hash_ref_2{'ip_addr'}.";".$$hash_ref_2{'service'}} = 1;
		}

	}

}

}
###################################
sub purge_old_alerts{
my $sql_query;
my $db_ptr;
my $hash_ref;

$sql_query = "	DELETE FROM \
						dm_monitor_alerts_raw \
					WHERE \
						auid != '$alert_uid'";
#						alert_timestamp < DATE_ADD(NOW(), INTERVAL (-30) MINUTE) ";
&connect_to_db;
$db->do($sql_query);

}
##################################
sub send_message{
my ($to,$subject,$message) = @_;
($to) 		|| return &big_error("No email address specified to send message to.");
($subject) 	|| return &big_error("No subject for message.");
($message) 	|| return &big_error("No message body for message.");

open(MAIL,"|$mailprog -t") || die "cant find mail program: $!\n";
print MAIL "To: $to\n";
print MAIL "From: $from_email_address\n";
print MAIL "Subject: $subject\n";
print MAIL "\n";
print MAIL "$message\n";
close MAIL;

}
#################################
sub update_ids_alerts{
my ($SID) = @_;

my $sql_query;
my $db_ptr;
my $hash_ref;
my $db_ptr_2;
my $hash_ref_2;

$sql_query = " SELECT \
						alert_uid, signature_is, \
						signature_contains, priority_level, \
						email_address, only_alert_from_hour, \
						only_alert_to_hour \
					FROM \
						dm_ids_alert_rules";

$db_ptr = &run_query($sql_query);
while ($hash_ref = $db_ptr->fetchrow_hashref){
my $alert_msg;	
my $alert_count;

	$sql_query = "	SELECT \
							event.sid,event.cid,sig_name,timestamp,hostname  \
						FROM event \
						LEFT JOIN signature ON (event.signature = signature.sig_id) \
						LEFT JOIN sensor ON (event.sid = sensor.sid) \
						WHERE (timestamp >= DATE_ADD(NOW(), INTERVAL (-$every_minutes) MINUTE)) "; 


if ($$hash_ref{'priority_level'}){
	$sql_query .= " AND ((sig_name LIKE 'P-$$hash_ref{'priority_level'}-%') ";
	if ($$hash_ref{'priority_level'} == $default_priority_level){
			#Then we also have to check for signatures withOUT a P-#- in front of it
			$sql_query .= " OR ( sig_name NOT LIKE 'P-%')) ";
	}
	else{
			#just cap it
			$sql_query .= ")";
	}
}
$sql_query .= " AND (sig_name = '$$hash_ref{'signature_is'}') " 
		if ($$hash_ref{'signature_is'}); 
$sql_query .= " AND (sig_name LIKE '%$$hash_ref{'signature_contains'}%') " 
		if ($$hash_ref{'signature_contains'}); 


#&debug($sql_query);

$db_ptr_2 = &run_query($sql_query);
 while ($hash_ref_2 = $db_ptr_2->fetchrow_hashref){
	$alert_msg .= "IDS ALERT at: $$hash_ref_2{'timestamp'}\n";
	$alert_msg .= "SIGNATURE: $$hash_ref_2{'sig_name'}\n";
	$alert_msg .= "HOST: $$hash_ref_2{'hostname'}\n";
	$alert_msg .= "SID: $$hash_ref_2{'sid'}\n";
	$alert_msg .= "CID: $$hash_ref_2{'cid'}\n";
	$alert_msg .= "_"x30 . "\n";
	$alert_count++;

 }
if ($alert_msg){
	my $alert_subject = "IDS ALERT - $alert_count in past $every_minutes mins - $program_name - $program_version";
	&send_message($$hash_ref{'email_address'},$alert_subject,$alert_msg);
}
}

}
################################
sub make_array_unique{
my (@array) = @_;
my @unique;
@array || return;
my %seen = ();
foreach (@array) {
    push(@unique, $_) unless $seen{$_}++;
}

return @unique;
}
##############################
sub check_md5_dir{
my (%args) = @_;

my $sql_query;
my $db_ptr;
my $hash_ref;

(my $rule_uid		= $args{'rule_uid'}) || return;
my $starting_dir	= $args{'path'};
my $recurse			= $args{'recursive'};
my $initial_add 	= 1 
	if $args{'initial_add'};
my $session_uid	= &rand_string(4,1);

return unless (-r $starting_dir);

my @dirs;
push (@dirs,$starting_dir);
my $dir;
my $file;
my $count;
my $total_bytes_read;
my $md5_sum;
my ($inode,$mode,$uid,$gid,$size,$mtime,$ctime);


foreach $dir(@dirs){
 opendir(DIR, $dir) or &error("Can't open directory $dir: $!");
 while (defined($file = readdir(DIR))) {
    # do something with "$dirname/$file"
	next if ($file=~/^\.\.$|^\.$/);
		$count++;
		&check_md5_file("$dir/$file",$rule_uid,$session_uid,$initial_add,$args{'sid'});


		if (-d "$dir/$file"){
			if (-l "$dir/$file"){
			}
			elsif($recurse){
				push (@dirs,"$dir/$file");
			}
		}
 }
 closedir(DIR);
}

# mark files as removed if we didn't find them during this run:
$sql_query = "	UPDATE dm_md5_data \
					SET \
						last_changed 	= NOW(), \
						out_of_sync		= 1, \
						deleted_flag	= 1 \
					WHERE \
						rule_uid			=  '$rule_uid' AND \
						session_uid		!= '$session_uid'";
&connect_to_db;
$db->do($sql_query);

&debug("$count Files Checked in $starting_dir (recurse:$recurse).\n");
}
####################################
sub check_md5_file{
my ($path,$rule_uid,$session_uid,$initial_add,$c_sid) = @_;


	#Check if this is a file we're supposed to ignore:
	return if ($ignore_files{$path});

		#Stat file:
		my ($inode,$mode,$uid,$gid,$size,$mtime,$ctime,$u_mode) = &stat_file($path);
		my $md5_sum;
		#if ((-f $path) || (-d $path)){
		if ((S_ISREG($u_mode)) || (S_ISDIR($u_mode))){
			$md5_sum = &get_md5($path);
		}
		&update_md5_record(	rule_uid		=> $rule_uid,
									path 			=> $path,
									inode			=> $inode,
									mode			=> $mode,
									uid			=>	$uid,
									gid			=>	$gid,
									size			=>	$size,
									mtime			=>	$mtime,
									ctime			=>	$ctime,
									md5_sum		=>	$md5_sum,
									initial_add	=>	$initial_add,
									session_uid	=>	$session_uid,
									sid			=>	$c_sid,
								);
}
#####################################
sub update_md5_record{
my (%args) = @_;
my $sql_query;
my $db_ptr;
my $hash_ref;

#(-r $args{'path'}) || return;
return unless ($args{'rule_uid'} =~/^\d+$/);

#safety first:
&safe_slash(\$args{'path'});
&safe_slash(\$args{'md5_sum'});


if ($args{'session_uid'} ne "HTTP"){
	return unless ($args{'inode'} && $args{'mtime'} && $args{'ctime'} && $args{'md5_sum'} && $args{'session_uid'});
}

if ($args{'initial_add'}){
	# then this is a new record and should be added as trusted
	# so we should add the k_* section too (known/trusted values)
	$sql_query = "	INSERT INTO dm_md5_data \
						VALUES( \
							'','$args{'rule_uid'}', \
							'$args{'path'}','$args{'inode'}', \
							'$args{'mode'}','$args{'uid'}','$args{'gid'}', \
							'$args{'size'}','$args{'mtime'}','$args{'ctime'}', '$args{'md5_sum'}', \
							'$args{'inode'}', \
							'$args{'mode'}','$args{'uid'}','$args{'gid'}', \
							'$args{'size'}','$args{'mtime'}','$args{'ctime'}', '$args{'md5_sum'}', \
							NOW(),NOW(),NOW(),'0','0','0','0','$args{'session_uid'}','$args{'sid'}' \
						)";					
&connect_to_db;
$db->do($sql_query);
}
else{
# then the rule already existed, but we still have to find out if this 
# file already existed or if it's new:
	$sql_query = " SELECT \
							c_inode, \
							c_perms, \
							c_uid, \
							c_gid, \ 
							c_size, \
							c_mtime, \
							c_ctime, \
							c_md5sum, \
							md5_uid \
						FROM dm_md5_data \
						WHERE \
							rule_uid = '$args{'rule_uid'}' AND \
							path 		= '$args{'path'}' ";
	$db_ptr = &run_query($sql_query);
	if (!($hash_ref = $db_ptr->fetchrow_hashref)){
		# Then its a new file:
  		 $sql_query = " INSERT INTO dm_md5_data \
  	                VALUES( \
  	                   '','$args{'rule_uid'}', \
  	                   '$args{'path'}','', \
  	                   '','','', \
  	                   '','','', '', \
  	                   '$args{'inode'}', \
  	                   '$args{'mode'}','$args{'uid'}','$args{'gid'}', \
  	                   '$args{'size'}','$args{'mtime'}','$args{'ctime'}', '$args{'md5_sum'}', \
  	                   NOW(),NOW(),NOW(),'1','1','0','0','$args{'session_uid'}','$args{'sid'}' \
                  )";
		&connect_to_db;
		$db->do($sql_query);

	}
	else{
		#we've already seen the file, so lets check it for consistency
		if (
				($$hash_ref{'c_inode'}!~/^$args{'inode'}$/) ||
				($$hash_ref{'c_perms'}!~/^$args{'mode'}$/) ||
				($$hash_ref{'c_uid'}!~/^$args{'uid'}$/) ||
				($$hash_ref{'c_gid'}!~/^$args{'gid'}$/) ||
				($$hash_ref{'c_size'}!~/^$args{'size'}$/) ||
				($$hash_ref{'c_mtime'}!~/^$args{'mtime'}$/) ||
				($$hash_ref{'c_ctime'}!~/^$args{'ctime'}$/) ||
				($$hash_ref{'c_md5sum'}!~/^$args{'md5_sum'}$/) 
			){
				#Then it's changed:
					$sql_query = "	UPDATE dm_md5_data \
										SET \
											out_of_sync 	= '1',
											modified_flag 	= '1',
											c_inode			= '$args{'inode'}',
											c_perms			= '$args{'mode'}',
											c_uid				= '$args{'uid'}',
											c_gid				= '$args{'gid'}',
											c_mtime			= '$args{'mtime'}',
											c_ctime			= '$args{'ctime'}',
											c_size			= '$args{'size'}',
											c_md5sum			= '$args{'md5_sum'}',
											last_checked	= NOW(),
											session_uid		= '$args{'session_uid'}',
											last_changed	= NOW()
										WHERE \
											md5_uid			= '$$hash_ref{'md5_uid'}'";
					&connect_to_db;
					$db->do($sql_query);
	
			}
			else{
				#No changes, all clear, just report that
					$sql_query = "	UPDATE dm_md5_data \
										SET \
											last_checked	= NOW(),
											session_uid		= '$args{'session_uid'}'
										WHERE \
											md5_uid			= '$$hash_ref{'md5_uid'}'";
					&connect_to_db;
					$db->do($sql_query);
	
			}


	}
}

}
##############################
sub get_md5{
my ($file) = @_;
my $md5_sum;

#(-r $file) || return;

       open(FILE, "$file") || (
                               &error("Can't open '$file': $!")
                 					&& return "0" 
               );

      binmode(FILE);
      $md5_sum =  Digest::MD5->new->addfile(*FILE)->hexdigest;
      close FILE;

return $md5_sum;
}
###########################
sub md5_string{
my ($string_ref) = @_;
my $md5;

$md5 = md5_hex($$string_ref);

return $md5;
}
##################
sub stat_file{
my ($file) = @_;
my ($inode,$mode,$uid,$gid,$size,$mtime,$ctime)
		= (stat ($file))[1,2,4,5,7,9,10,11];

	my $untouched_mode = $mode;
      $mode = sprintf("%o",$mode);
      $mode=~s/^.*(\d\d\d\d)$/$1/;
return ($inode,$mode,$uid,$gid,$size,$mtime,$ctime,$untouched_mode);

}
################
sub update_new_md5s{
my $sql_query;
my $db_ptr;
my $hash_ref;
my @new_records;
my @new_deletions;

$sql_query =  "	SELECT \
							sid,rule_uid,path,recursive \
						FROM dm_md5_rules \
						WHERE \
							new_record = 1";

$db_ptr = &run_query($sql_query);
while ($hash_ref = $db_ptr->fetchrow_hashref){
		push (@new_records,"$$hash_ref{'rule_uid'};$$hash_ref{'path'};$$hash_ref{'recursive'};$$hash_ref{'sid'}");
}

#now read the directories and put the initial values into the dm_md5_data table:

foreach (@new_records){
	my @array = split(/;/,$_);
	#make sure we have a valid id:
	next unless ($array[0]=~/^\d+$/);
	push (@new_deletions,$array[0]);

	#First lets see if its a web address instead of a file:
	if ($array[1]=~/^http:\/\//i){
		&debug("New http:// md5 record detected");
		my $hostname;
		my $uri;

		#try and parse the url into hostname and uri
		if ($array[1]=~/^http:\/+([a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+)$/i){
			#then it's just the main site, so a "GET /" is in order
			$hostname 	= $1;
			$uri 		= "/";
		}
		elsif ($array[1]=~/^http:\/+([a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+)(\/.*)$/i){
			#then we should have a hostname and uri
			$hostname 	= $1;
			$uri 		= $2;
		}
		else{
			#BAD FORMAT... ignore
			next;
		}
		($hostname && $uri) || next;
		my $html = &get_http_page($hostname,$uri,$hostname,1000);
		my $md5_sum = &md5_string(\$html);
		&debug("New http:// md5 record for $hostname $uri is $md5_sum");
        &update_md5_record( 		rule_uid        => $array[0],
                                    path            => $array[1],
                                    inode           => '1',
                                    mode            => '1',
                                    uid         	=>  '1',
                                    gid         	=>  '1',
                                    size            =>  length($html),
                                    mtime           =>  '1',
                                    ctime           =>  '1',
                                    md5_sum     	=>  $md5_sum,
                                    initial_add 	=>  '1',
                                    session_uid 	=>  'HTTP',
                                    sid         	=>  $array[3],
                                );


	}
	elsif (-d $array[1]){
		#then its a directory
		#lets see if we should recurse it:
		if ($array[2]){
			&check_md5_dir(			rule_uid 	=> $array[0],
									path			=> $array[1],
									recursive 	=> 1,
									initial_add	=> 1,
									sid			=> $array[3],
								);
		}
		else{
			#then just get the files in this directory
			&check_md5_dir(			rule_uid 	=> $array[0],
									path			=> $array[1],
									recursive 	=> 0,
									initial_add	=> 1,
									sid			=> $array[3],
								);
		}
	}
	elsif (-e $array[1]){
		#Then its some sort of file:
		&check_md5_file($array[1],$array[0],"NEW",'1',$array[3]);
	}
	else{
		#nothing, it doesn't exist!
	}
}
if (@new_deletions){
	# Now mark them as not new anymore:
	$sql_query = "	UPDATE dm_md5_rules \
						SET new_record = '' WHERE (";
	
	foreach (@new_deletions){
		$sql_query .= " (rule_uid = '$_') OR ";
	}
	#replace the last OR with an endcap
	$sql_query =~s/OR $/\)/;
	&connect_to_db;
	$db->do($sql_query);
}


}
############################################
sub check_md5_controller{
my ($SID) = @_;
($SID=~/^\d+$/) || &error("Invalid SID passed: $SID");

my $sql_query;
my $db_ptr;
my $hash_ref;

#########
# First get a hash of files that we should ignore:
&update_ignore_files;

&update_new_md5s;
$sql_query = "	SELECT \
						rule_uid, \
						path, \
						sid, \
						recursive \
					FROM dm_md5_rules \
					WHERE \
						sid = '$SID'";
$db_ptr = &run_query($sql_query);
while ($hash_ref = $db_ptr->fetchrow_hashref){


	if ($$hash_ref{'path'}=~/^http:\/\//i){
		&debug("http:// md5 entry detected : $$hash_ref{'path'}");
		my $hostname;
		my $uri;

		#try and parse the url into hostname and uri
		if ($$hash_ref{'path'}=~/^http:\/+([a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+)$/i){
			#then it's just the main site, so a "GET /" is in order
			$hostname 	= $1;
			$uri 		= "/";
		}
		elsif ($$hash_ref{'path'}=~/^http:\/+([a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+)(\/.*)$/i){
			#then we should have a hostname and uri
			$hostname 	= $1;
			$uri 		= $2;
		}
		else{
			#BAD FORMAT... ignore
			next;
		}
		($hostname && $uri) || next;
		my $html = &get_http_page($hostname,$uri,$hostname,1000);
		my $md5_sum = &md5_string(\$html);
		&debug("http:// md5 record for $hostname $uri is $md5_sum");
        &update_md5_record( 		rule_uid        => $$hash_ref{'rule_uid'},
                                    path            => $$hash_ref{'path'},
                                    inode           => '1',
                                    mode            => '1',
                                    uid         	=>  '1',
                                    gid         	=>  '1',
                                    size            =>  length($html),
                                    mtime           =>  '1',
                                    ctime           =>  '1',
                                    md5_sum     	=>  $md5_sum,
                                    initial_add 	=>  '',
                                    session_uid 	=>  'HTTP',
                                    sid         	=>  $$hash_ref{'sid'},
                                );


	}
	elsif (-d $$hash_ref{'path'}){						
		&check_md5_dir(	rule_uid 	=> $$hash_ref{'rule_uid'},
								path			=> $$hash_ref{'path'},
								recursive 	=> $$hash_ref{'recursive'},
								sid			=> $$hash_ref{'sid'},
							);
	}
	elsif (-r $$hash_ref{'path'}){
		&check_md5_file($$hash_ref{'path'},$$hash_ref{'rule_uid'},"FILE",'',$$hash_ref{'sid'});
	}
	else{
		&error("PATH DOESN'T EXIST OR IS NOT READABLE: $$hash_ref{'path'}");
	}
}



}

###############################################
sub parse_md5_alerts{
my $sql_query;
my $db_ptr;
my $db_ptr_2;
my $hash_ref;
my $hash_ref_2;
my %alerts_sent;
my @r_alert_array;
my @y_alert_array;

## Get MD5 alerts:
$sql_query = "	SELECT \
						dm_md5_data.md5_uid, dm_md5_data.rule_uid, dm_md5_data.path, \
						dm_md5_data.k_inode, dm_md5_data.k_perms, dm_md5_data.k_uid, \
						dm_md5_data.k_gid, dm_md5_data.k_size, dm_md5_data.k_mtime, \
						dm_md5_data.k_ctime, dm_md5_data.k_md5sum, dm_md5_data.c_inode, \
						dm_md5_data.c_perms, dm_md5_data.c_uid, dm_md5_data.c_gid, \
						dm_md5_data.c_size, dm_md5_data.c_mtime, dm_md5_data.c_ctime, \
						dm_md5_data.c_md5sum, dm_md5_data.last_confirmed, \
						dm_md5_data.last_checked , dm_md5_data.last_changed, \
						dm_md5_data.out_of_sync, dm_md5_data.added_flag, \
						dm_md5_data.modified_flag, dm_md5_data.deleted_flag, \
						dm_md5_data.session_uid, dm_md5_rules.sid, dm_md5_rules.priority, \
						dm_md5_rules.description \
					FROM \	
						dm_md5_data \
					LEFT JOIN dm_md5_rules \
        			ON dm_md5_data.rule_uid = dm_md5_rules.rule_uid \
					WHERE \
        				out_of_sync = 1 \
					ORDER BY \ 
						dm_md5_rules.sid,dm_md5_data.deleted_flag,dm_md5_data.added_flag, dm_md5_data.modified_flag ";

$db_ptr = &run_query($sql_query);
my $alert_count;
while ($hash_ref = $db_ptr->fetchrow_hashref){
# parse alerts and load them into array
my $this_one_modified = 0;
  $alert_count++;

   if ($$hash_ref{'added_flag'}){
			if ($$hash_ref{'priority'}=~/yellow/i){
				$y_alert_array[$$hash_ref{'sid'}] .= "ADDED FILE: $$hash_ref{'path'} (SID: $$hash_ref{'sid'} Priority: YELLOW)\n";
			}
			else{
				$r_alert_array[$$hash_ref{'sid'}] .= "ADDED FILE: $$hash_ref{'path'} (SID: $$hash_ref{'sid'} Priority: RED)\n";
			}
			$this_one_modified = 1;
   }
   if ($$hash_ref{'deleted_flag'}){
			if ($$hash_ref{'priority'}=~/yellow/i){
				$y_alert_array[$$hash_ref{'sid'}] .= "DELETED FILE: $$hash_ref{'path'} (SID: $$hash_ref{'sid'} Priority: YELLOW)\n";
			}
			else{
				$r_alert_array[$$hash_ref{'sid'}] .= "DELETED FILE: $$hash_ref{'path'} (SID: $$hash_ref{'sid'} Priority: RED)\n";
			}
			$this_one_modified = 1;
   }
   if ($$hash_ref{'modified_flag'}){
			$this_one_modified = 1;
				if ($$hash_ref{'priority'}=~/yellow/i){
					$y_alert_array[$$hash_ref{'sid'}] .= "MODIFIED FILE: $$hash_ref{'path'} (SID: $$hash_ref{'sid'} Priority: YELLOW)\n";
				}
				else{
					$r_alert_array[$$hash_ref{'sid'}] .= "MODIFIED FILE: $$hash_ref{'path'} (SID: $$hash_ref{'sid'} Priority: RED)\n";
				}

            if ($$hash_ref{'k_inode'} != $$hash_ref{'c_inode'}){
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [INODE] Known: $$hash_ref{'k_inode'} Observed: $$hash_ref{'c_inode'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [INODE] Known: $$hash_ref{'k_inode'} Observed: $$hash_ref{'c_inode'}\n";
					}
            }
            if ($$hash_ref{'k_perms'} ne $$hash_ref{'c_perms'}){
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [PERMS] Known: $$hash_ref{'k_perms'} Observed: $$hash_ref{'c_perms'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [PERMS] Known: $$hash_ref{'k_perms'} Observed: $$hash_ref{'c_perms'}\n";
					}
            }
            if ($$hash_ref{'k_uid'} ne $$hash_ref{'c_uid'}){
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [UID] Known: $$hash_ref{'k_uid'} Observed: $$hash_ref{'c_uid'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [UID] Known: $$hash_ref{'k_uid'} Observed: $$hash_ref{'c_uid'}\n";
					}
            }
            if ($$hash_ref{'k_gid'} ne $$hash_ref{'c_gid'}){
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [GID] Known: $$hash_ref{'k_gid'} Observed: $$hash_ref{'c_gid'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [GID] Known: $$hash_ref{'k_gid'} Observed: $$hash_ref{'c_gid'}\n";
					}
            }
            if ($$hash_ref{'k_size'} != $$hash_ref{'c_size'}){
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [SIZE] Known: $$hash_ref{'k_size'} Observed: $$hash_ref{'c_size'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [SIZE] Known: $$hash_ref{'k_size'} Observed: $$hash_ref{'c_size'}\n";
					}
            }
            if ($$hash_ref{'k_mtime'} != $$hash_ref{'c_mtime'}){
               $$hash_ref{'k_mtime'} = localtime($$hash_ref{'k_mtime'});
               $$hash_ref{'c_mtime'} = localtime($$hash_ref{'c_mtime'});
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [MTIME] Known: $$hash_ref{'k_mtime'} Observed: $$hash_ref{'c_mtime'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [MTIME] Known: $$hash_ref{'k_mtime'} Observed: $$hash_ref{'c_mtime'}\n";
					}
            }
            if ($$hash_ref{'k_ctime'} != $$hash_ref{'c_ctime'}){
               $$hash_ref{'k_ctime'} = localtime($$hash_ref{'k_ctime'});
               $$hash_ref{'c_ctime'} = localtime($$hash_ref{'c_ctime'});
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [CTIME] Known: $$hash_ref{'k_ctime'} Observed: $$hash_ref{'c_ctime'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [CTIME] Known: $$hash_ref{'k_ctime'} Observed: $$hash_ref{'c_ctime'}\n";
					}
            }
            if ($$hash_ref{'k_md5sum'} ne $$hash_ref{'c_md5sum'}){
					if ($$hash_ref{'priority'}=~/yellow/i){
						$y_alert_array[$$hash_ref{'sid'}] .= "   [MD5] Known: $$hash_ref{'k_md5sum'} Observed: $$hash_ref{'c_md5sum'}\n";
					}
					else{
						$r_alert_array[$$hash_ref{'sid'}] .= "   [MD5] Known: $$hash_ref{'k_md5sum'} Observed: $$hash_ref{'c_md5sum'}\n";
					}
            }
   }
	if ($this_one_modified){
			if ($$hash_ref{'priority'}=~/yellow/i){
				$y_alert_array[$$hash_ref{'sid'}] .= "\n";
			}
			else{
				$r_alert_array[$$hash_ref{'sid'}] .= "\n";
			}
	}

}

return 1 if (!$alert_count); #no sense in checking the rules if there are no alerts

$sql_query = " SELECT * FROM \
                  dm_md5_alert_rules \
               WHERE \
                  only_alert_from_hour <= EXTRACT(HOUR FROM NOW()) AND \
                  only_alert_to_hour   >= EXTRACT(HOUR FROM NOW()) AND \
						suspend_until 			<= NOW() \
				";
$db_ptr = &run_query($sql_query);

while ($hash_ref = $db_ptr->fetchrow_hashref){
	my $temp_alert_message;
	if ($$hash_ref{'alert_level'}){
		#then they want only yellow or red alerts
		if ($$hash_ref{'alert_level'} == 2){
			#Only yellows:
			if ($$hash_ref{'monitored_sid'}){
				#then they only want to see a certain sid:
				$temp_alert_message = $y_alert_array[$$hash_ref{'monitored_sid'}];
			}
			else{
				#they want alerts for all sids:
				$temp_alert_message = join ('', @y_alert_array);
			}
		}
		else{
			# Only reds
			if ($$hash_ref{'monitored_sid'}){
				#then they only want to see a certain sid:
				$temp_alert_message = $r_alert_array[$$hash_ref{'monitored_sid'}];
			}
			else{
				#they want alerts for all sids:
				$temp_alert_message = join ('', @r_alert_array);
			}
		}
	}
	else{
		#then they want all levels of alerts
			if ($$hash_ref{'monitored_sid'}){
				#then they only want to see a certain sid:
				$temp_alert_message = $r_alert_array[$$hash_ref{'monitored_sid'}];
				$temp_alert_message .= $y_alert_array[$$hash_ref{'monitored_sid'}];
			}
			else{
				#they want alerts for all sids:
				$temp_alert_message = join ('', @r_alert_array);
				$temp_alert_message .= join ('', @y_alert_array);
			}
	}

next unless ($temp_alert_message);

&send_message($$hash_ref{'email_address'},"File Integrity Check Alert - " . localtime() ,$temp_alert_message);
}


return 1;
}
################
sub error{
my ($error_string) = @_;
&log($error_string);
return;
}

###################
sub rules_have_been_updated{
my $sql_query;
my $db_ptr;
my $hash_ref;

$sql_query = "SELECT last_implemented FROM dm_conf WHERE sid = '$sid' AND last_updated != last_implemented";
$db_ptr = &run_query($sql_query);
return ($hash_ref = $db_ptr->fetchrow_hashref)? $$hash_ref{'last_implemented'} : 0;
}
################
sub make_sure_snort_is_running{
my ($last_chance) = @_;
my $result;
my $pid_file = "$pid_path/snort" . $snort_interface . ".pid";
my $pid2test;
my $pid_line;

open (PIDFILE,$pid_file) || return (&start_snort());
while (<PIDFILE>){
	$pid_line = $_; #only last line is needed if there are multiple
}
close PIDFILE;
#return &error("Invalid line in PID file: $pid_line") unless ($pid_line=~/(\d+)/);
if ($pid_line=~/(\d+)/){
	$pid2test = $1;
	if (!$last_chance){
		&error("Found snort pid file, checking to see if snort is really running...");
	}
}
else{
	#invalid format... 
	return ($last_chance)? 0 : &start_snort;#only try once, or fail
}

&debug("Checking if snort is running at PID: $pid2test");

$result = `$ps_binary $pid2test`;

&debug("PS output: $result");
if ($result=~/snort/){
	&debug("snort IS running at PID $pid2test");
	$snort_pid = $pid2test;
	return 1;#snort is running
}
else{
	&debug("snort is NOT running");
	#print ("snort is NOT running:\n$result \n");
	return ($last_chance)? 0 : &start_snort;#only try once, or fail
}


}
##############

##############
sub start_snort{
my $s_pid;
&debug("Attempting to start snort");

$s_pid =  open (SNORT,"$snort_binary_path $snort_options -c $snort_conf_file |") || &big_error("Couldn't snart snort! : $!");
#close SNORT; # It should have become a daemon

#record the pid:
my $pid_file = "$pid_path/snort" . $snort_interface . ".pid";
open (PIDFILE,">$pid_file") || return(&error("Can't open pid file for writing: $pid_file"));
print PIDFILE $s_pid;
close PIDFILE;
return &make_sure_snort_is_running("LAST_CHANCE");

}
############
sub update_local_rules{
my $sql_query;
my $db_ptr;
my $hash_ref;
my $current_ruleset;

&debug("updating local rules");
$sql_query = "SELECT snort_conf FROM dm_conf WHERE sid = '$sid'";
$db_ptr = &run_query($sql_query);
($hash_ref = $db_ptr->fetchrow_hashref) 
	|| &error("Database does not yet contain a snort.conf entry for this SID!");
# take out all include statements!:
$$hash_ref{'snort_conf'} =~s/\n\s*include/\n# include/g;

&debug("Adding snort.conf to current ruleset");
########
$$hash_ref{'snort_conf'}=~tr/\!-~\n\t //dc;
#for some reason a binary character was creaping into the conf file, but this takes care of it... 
# I'd love to know if someone can tell me why that was there?!
#######

$current_ruleset = $$hash_ref{'snort_conf'} . "\n";

### Now get all the rules:

$sql_query = "SELECT snort_rules,rules_type FROM dm_rules WHERE sid = '$sid' ORDER BY UPPER(rules_type)";
$db_ptr = &run_query($sql_query);
while ($hash_ref = $db_ptr->fetchrow_hashref){
	&debug("Adding $$hash_ref{'rules_type'} to current_ruleset");
	$current_ruleset .= "$$hash_ref{'snort_rules'}\n";
}

# take out ctrl-M s
$current_ruleset=~s/\r//g;

&debug("Writing new local rules file of length " . length($current_ruleset));
open (OUT,">$snort_conf_file") || &big_error("Couldn't open $snort_conf_file for writing: $!");
print OUT $current_ruleset;
close OUT;
# change perms so that only root can read it:
chmod 0600, $snort_conf_file;
&touch_last_implemented 
	|| &error("Error while updating Database with last_implemented value after retrieving new ruleset");
}
##############
sub HUP_snort{
	&debug("Attempting to HUP snort process");
	($snort_pid) || return (&error("Can't find snort pid!\n"));
	system ('kill','-HUP',"$snort_pid");
	return &make_sure_snort_is_running;
}
###############
sub touch_last_implemented{
my $sql_query;
my $db_ptr;

&debug("Touching last_* fields for sid $sid");
$sql_query = "UPDATE dm_conf SET last_updated=NOW(),last_implemented = NOW() WHERE sid = '$sid'";
$db_ptr = &run_query($sql_query);

return 1;
}
########################################
sub find_binary{
my ($program,$dont_die) = @_;
my $path;

foreach $path(@binary_dirs){
    return "$path/$program" if (-x "$path/$program");
}

return if ($dont_die);

&big_error("Couldn't find needed binary: \"$program\" in following directories: \n" . join(', ',@binary_dirs) );
}

########################################
sub is_binary{
my ($binary) = @_;

# Don't stat it id there's nothing there:
return if (!$binary);
# Return true if its a binary
return 1 if (-x $binary);
# If we're here - we stated what they gave us, and it's not a binary
print "Invalid path to binary: $binary\n";
return;
}
###########################
sub get_dns{
my ($host_name,$dns_check) = @_;
my $first_address;
my $long_ip;
my @m_addresses;

($host_name) || return;

&debug("Searching for IP address for host: $host_name");

my ($name,$aliases,$addrtype,$length,@addresses) = gethostbyname($host_name);

my $count;
foreach my $ip(@addresses){
		last if ($count++ > 10);  #Don't allow it to loop here
		my $dotted_ip = join(".", unpack('C4',$ip));
        $long_ip = &convert_dotted_ip($dotted_ip);
		&debug("Got $dotted_ip for Host: $host_name");
		push (@m_addresses, $long_ip);
		if (!$first_address){
			$first_address = $long_ip;
			return ($first_address,@m_addresses) if (!$dns_check);
		}
}

return ($long_ip,@m_addresses);
}
###########################
sub compare_dns{
my ($long_ip,@address_list) = @_;
my $different;
my @long_ip_array = split(/,/,$long_ip);
my %seen;

&debug("Checking DNS for $long_ip");
foreach (@long_ip_array) { $seen{$_} = 1 }

foreach (@address_list) {
	&debug("Checking DNS to see if $_ has been seen");
        return if (!$seen{$_});
}

return 1;
}
##############################
sub report_dns_hijacking{
my ($host_name,$long_ips,@address_list) = @_;
my @long_ip_array = split(/,/,$long_ips);

		&log("DNS CHANGE DETECTED:");

print STDERR "DNS HIJACKING!:\n";

&log("OLD:");
print STDERR "OLD:\n";
foreach (@long_ip_array){
	&log("$_");
	print STDERR "$_\n";
}
print STDERR "NEW:\n";
foreach (@address_list){
	&log("$_");
	print STDERR "$_\n";
}

}
##################################
sub update_ignore_files{
my $sql_query;
my $db_ptr;
my $hash_ref;

# %ignore_files is declared global at top of file

#clear out the old hash:
%ignore_files = ();

$sql_query = "  SELECT \
                        rule_uid, \
                        path \
                    FROM dm_md5_rules \
                    WHERE \
                        sid = '$sid' AND \
						priority = 'IGNORE'";
$db_ptr = &run_query($sql_query);
while ($hash_ref = $db_ptr->fetchrow_hashref){
	$ignore_files{$$hash_ref{'path'}} = 1;
}

}
###############
sub print_usage{

print << "EOF";
Usage: $0 [-dh] [-c path] [-i interface] [-f path]

-S         Run demarcd without snort on this sensor
-c <path>  Custom path to snort.conf file
-D         Put in debug mode (will not background process)
-f <path>  Custom path to demarcd.conf file
-F         Put in foreground mode (will not background process)
-h         Print this help screen
-i <if>    Run snort on this specific interface
-I         Register new sensor with database + DEMARC
-k         Kill demarcd process running (on specified interface if -i is specified)
-?         Print this help screen

EOF
exit;
}

########################################
sub update_rulesets_from_sourcefire{
my ($sid,$rules_url) = @_;

&debug("Trying to update rules from sourcefire.com");

($sid=~/^\d+$/) || return;

my $timeout_seconds = 20;
my $current_filename;
my $current_ruleset;
my $config_ruleset;

my $sql_query;
my $db_ptr;
my $hash_ref;
my %db_rules;
my $config_filename = "1-classifications";
my $just_saw_div;

&connect_to_db;

#get list of current ruleset for use later:
$sql_query = "SELECT rules_type FROM dm_rules WHERE sid='$sid'";
$db_ptr = &run_query($sql_query);
while ($hash_ref=$db_ptr->fetchrow_hashref){
	$db_rules{$$hash_ref{'rules_type'}} = 1;
}

eval {
    local $SIG{ALRM} = sub { die"timed_out\n"};
    alarm $timeout_seconds;

&debug("$lynx_binary -dump $rules_url | $tar_binary Oxvzf - rules/*rules  rules/*config 2>/dev/null |");
open (RULES,"$lynx_binary -dump $rules_url | $tar_binary Oxvzf - rules/*rules  rules/*config 2>/dev/null |") || die "Couldn't retrieve rules from $rules_url! : $! \n";

while (<RULES>){
	#skip the #--- lines:
	if ($_=~/^\s*#\s*-----/){
		$just_saw_div = 1;
		next;
	}
	if ($_=~/^\s*config/){
		#then this is part of the config file:
		$config_ruleset .= $_;
		next;
	}

	if ($just_saw_div && ($_=~/^#\s*([A-Z0-9\-\_][A-Z0-9\-\_\s]+)[\r\n]*$/)){
		my $temp_name = $1;
		$temp_name=~s/[\r\n]//g;
&debug("1 - New filename : $temp_name");
		#then this is the start of a new ruleset
		# lets see if we were already recording a ruleset
		if ($current_filename){
			#then write out this ruleset
			&debug( $current_filename . " is " . length($current_ruleset) . " long");
			&safe_slash(\$current_ruleset);
			&safe_slash(\$current_filename);
			#check to see if ruleset already exists
			if ($db_rules{$current_filename}){
				#then this is just an update:
				&debug( "Updating ruleset (auto): $current_filename");
				$sql_query = "UPDATE dm_rules SET snort_rules = '$current_ruleset' WHERE sid = '$sid' AND rules_type = '$current_filename'";
			}
			else{
				#new ruleset
				&debug( "Adding ruleset (auto): $current_filename");
				$sql_query = "INSERT INTO dm_rules VALUES('$sid','$current_filename','$current_ruleset')";
			}
			$db_ptr = &run_query($sql_query);

		}
		$temp_name=~s/^\s*//;
		$temp_name=~s/[\s\r\n]*$//;
&debug("New filename : $temp_name");
		$current_filename = $temp_name;
		$current_ruleset = ();
		
	}
	$just_saw_div = 0;
	next unless ($current_filename);#don't read until a file is found

	$current_ruleset .= $_;
}
};
close RULES;
alarm 0;

if ($@){
	#then there was an error:
	return;
}
if ($current_filename && $current_ruleset){
	#then we have to "flush the buffer"
	  &debug( $current_filename . " is " . length($current_ruleset) . " long");
      &safe_slash(\$current_ruleset);
      &safe_slash(\$current_filename);
      #check to see if ruleset already exists
      if ($db_rules{$current_filename}){
       	#then this is just an update:
		&debug( "Updating ruleset (auto): $current_filename");
        $sql_query = "UPDATE dm_rules SET snort_rules = '$current_ruleset' WHERE sid = '$sid' AND rules_type = '$current_filename'";
      }
      else{
           #new ruleset
			&debug( "Adding ruleset (auto): $current_filename");
           $sql_query = "INSERT INTO dm_rules VALUES('$sid','$current_filename','$current_ruleset')";
      }
      $db_ptr = &run_query($sql_query);

}

#check for config stuff:
if ($config_ruleset){
	&debug( "Writing classification ruleset: $config_filename");
	if ($db_rules{$config_filename}){
		#then this is just an update:
		$sql_query = "UPDATE dm_rules SET snort_rules = '$config_ruleset' WHERE sid = '$sid' AND rules_type = '$config_filename'";
	}
	else{
		#new ruleset
		$sql_query = "INSERT INTO dm_rules VALUES('$sid','$config_filename','$config_ruleset')";
	}
$db_ptr = &run_query($sql_query);

}

#update the timestamp on this sid's rules
$sql_query = "UPDATE dm_conf SET last_updated=NOW() WHERE sid='$sid'";
$db_ptr = &run_query($sql_query);

}
########################################
sub update_rulesets_from_whitehats{
my ($sid,$rules_url,$current_filename) = @_;

&debug("Trying to update rules from whitehats.com");

($sid=~/^\d+$/) || return;

my $timeout_seconds = 20;
my $current_ruleset;
my $config_ruleset;

my $sql_query;
my $db_ptr;
my $hash_ref;
my $valid;

&connect_to_db;

eval {
    local $SIG{ALRM} = sub { die"timed_out\n"};
    alarm $timeout_seconds;

open (RULES,"$lynx_binary -dump http://www.whitehats.com/ids/vision18.rules.gz 2>/dev/null |") || die "Couldn't retrieve rules from $rules_url! : $! \n";

while (<RULES>){
	#skip the #--- lines:
	next if ($_=~/^\s*#\s*-----/);
	if (!$valid && ($_=~/^\s*alert/)){
		#then this is a valid response
		$valid = 1;
	}

	$current_ruleset .= $_;
}
};
alarm 0;
close RULES;

if ($@){
	#then there was an error:
	return;
}

&safe_slash(\$current_ruleset);

#Now update the new rule file:
#first check to see if it already exists:
$sql_query = "SELECT rules_type FROM dm_rules WHERE sid='$sid' AND rules_type='$current_filename'";
$db_ptr = &run_query($sql_query);

if ($hash_ref=$db_ptr->fetchrow_hashref){
	#then this is just an update:
	&debug( "Updating ruleset (auto wh): $current_filename");
	$sql_query = "UPDATE dm_rules SET snort_rules = '$current_ruleset' WHERE sid = '$sid' AND rules_type = '$current_filename'";
}
else{
	#new ruleset
	&debug( "Adding ruleset (auto wh): $current_filename");
	$sql_query = "INSERT INTO dm_rules VALUES('$sid','$current_filename','$current_ruleset')";
}
$db_ptr = &run_query($sql_query);

#update the timestamp on this sid's rules
$sql_query = "UPDATE dm_conf SET last_updated=NOW() WHERE sid='$sid'";
$db_ptr = &run_query($sql_query);

}
########################
sub check_tcp{
my ($host,$port) = @_;
($host) || return;

#SUPER generic script that ONLY checks for tcp connect... used for misc connect tests
#Default to 80... just because it's the most standard port open on servers
#

my %green;
my %yellow;
my %red;

($port=~/^[1-9]+\d*$/) || ($port	= "80");
my $connect_only			= 1;
my $timeout_seconds		= 10;
my $connection_request	;

return
	&generic_connect(	\%green,\%yellow,\%red,
							host=>			$host,
							port=>			$port,
							connect_only=> $connect_only,
							timeout=>		$timeout_seconds,
							request=>		$connection_request,
	);

}
######################
sub check_tcp1{
#ALIAS for check_tcp

my ($host,$port) = @_;
return &check_tcp($host,$port);
}
######################
sub check_tcp2{
#ALIAS for check_tcp

my ($host,$port) = @_;
return &check_tcp($host,$port);
}
######################
sub check_tcp3{
#ALIAS for check_tcp

my ($host,$port) = @_;
return &check_tcp($host,$port);
}
######################
sub check_tcp4{
#ALIAS for check_tcp

my ($host,$port) = @_;
return &check_tcp($host,$port);
}
#############################################
sub get_http_page{
my ($host,$uri,$host_name,$line_limit) = @_;

my $sock;
my $timeout_seconds = 20;
my $html;
my $ip_address;

($line_limit=~/^\d+$/) || ($line_limit = 100000);

if ($host=~/^[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}$/){
	#then the host is a valid IP address
	$ip_address = $host;
}
else{
	 $host=~tr/a-zA-Z0-9\.\-//dc;#take out invalid dns characters
	#try and resolve it:
	$ip_address = &resolve_host($host);
#print "ADDRESS: $ip_address\n";
	#check if it worked:
	($ip_address=~/^[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}$/)
		|| return;
}
#now $ip_address should be a valid ip address

#lets make sure uri only has standard characters:
$uri=~tr/\!-~\n\t //dc;


($uri) || return;

eval {
    local $SIG{ALRM} = sub { die"timed_out\n"};
    alarm $timeout_seconds;


    $sock = new IO::Socket::INET (   	PeerAddr => $host,
                                        PeerPort => 80,
                                            );
    if (!$sock){
        die "Can't establish socket connection\n";
    }
    $sock->autoflush(1);
};
if ($@){
    alarm 0;
    return;
}
alarm 0;

eval {
    local $SIG{ALRM} = sub { die"timed_out\n"};
    alarm $timeout_seconds;
    print $sock "GET $uri HTTP/1.0 OK\n";
	print $sock "Host: $host_name\n" if ($host_name);
	print $sock "Referer: http://www.demarc.org\n";
	print $sock "User-Agent: DEMARC Network Monitor $program_version\n";
	print $sock "\n";

    my $line_count;
	my $header_finished;

	#get rid of the header
	while (<$sock>){
        $line_count++;
        #stop waiting for more input if we have reached the line limit 
		if ($line_count >=  $line_limit){
			close $sock; 
			alarm 0;
			return; 
		}
		last if ($_=~/^[\r\n]+$/);
	}
	
	#Now read the rest of the file:
    while (<$sock>){
        $html .= $_;
        $line_count++;

        #stop waiting for more input if we have reached the line limit 
		if ($line_count >=  $line_limit){
			close $sock; 
			alarm 0;
			return; 
		}
    }
};
alarm 0;

return $html;
}
###########################
sub resolve_host{
my ($host_name) = @_;

($host_name) || return;


#my ($name,$aliases,$addrtype,$length,@addresses) = gethostbyname($host_name);
my $address = gethostbyname($host_name);

my $dotted_ip = join(".", unpack('C4',$address));

return $dotted_ip;
}
########################################
sub binary_search{
my ($path,$binary,$dont_die_if_not_found) = @_;

my $correct = &is_binary($path);

return ($correct) ? $correct : &find_binary($binary,$dont_die_if_not_found);
}
########################################
sub get_boolean_config_value{
my ($key,$default_value) = @_;

return $default_value if (!$conf{$key});

return 1 if ($conf{$key}=~/^yes$/i);

return 1 if ($conf{$key}=~/^1$/i);

return 0;
}
########################################
sub is_binary{
my ($binary) = @_;

return if (!$binary);

return ($binary) if (-x $binary);

&error("Invalid path to binary in config file: $binary");

return;
}
#########################################
sub parse_config{
my %config_hash;

open (CONF,$config_file) || die "Can't open config file \"$config_file\" : $!\n";

while (<CONF>){
    #get rid of comment lines
    next if ($_=~/^\s*#/);

    $_=~s/[\n\r]//g;
    next unless ($_=~/(^.*[a-zA-Z0-9]+.*?)=(.*[a-zA-Z0-9]+.*)$/);
    my $name    = $1;
    my $value   = $2;
    #take starting/trailing spaces off name/value
    #(starting)
    $name   =~s/^\s+//;
    $value  =~s/^\s+//;
    #(trailing)
    $name   =~s/\s+$//;
    $value  =~s/\s+$//;
    #check if value is quoted or is a single value
    if ($value=~/^"(.+)?"/){
        $value  = $1;
    }
    elsif ($value=~/^(\S+)?#/){
        $value  = $1;
    }
    elsif ($value=~/^(\S+)?\s/){
        $value  = $1;
    }

    $config_hash{$name} = $value;

}

#foreach (keys %config_hash){
#	print "$_ : $config_hash{$_}\n";
#}
return %config_hash;
}
##############################################
sub get_required_config_value{
my ($key) = @_;

return $conf{$key} if ($conf{$key});

&big_error("Required configuration value missing: \"$key\"");
}
##############################################
sub get_config_value{
my ($key) = @_;

return $conf{$key} if ($conf{$key});

return;
}
################
sub check_for_multiple_demarcds{
my $result;
my $pid_file = "$pid_path/demarcd" . $snort_interface . ".pid";
my $pid2test;
my $pid_line;

open (PIDFILE,$pid_file) || return;
while (<PIDFILE>){
	$pid_line = $_; #only last line is needed if there are multiple
}
close PIDFILE;

if ($pid_line=~/(\d+)/){
	$pid2test = $1;
}
else{
	#invalid format... 
	return;
}

&debug("Checking if demarcd is running at PID: $pid2test");

$result = `$ps_binary $pid2test`;

&debug("PS output: $result");
if ($result=~/demarc/i){
	$demarcd_pid = $pid2test;
	&big_error("demarcd is already running at PID $pid2test\n\n$result");
}
else{
	&debug("demarcd is NOT running yet");
	return  1;
}


}
################
sub check_for_and_kill_demarcd{
my $result;
my $pid_file = "$pid_path/demarcd" . $snort_interface . ".pid";
my $pid2test;
my $pid_line;

open (PIDFILE,$pid_file) || &big_error("demarcd is not running");
while (<PIDFILE>){
	$pid_line = $_; #only last line is needed if there are multiple
}
close PIDFILE;

if ($pid_line=~/(\d+)/){
	$pid2test = $1;
}
else{
	#invalid format... 
	return;
}

&debug("Checking if demarcd is running at PID: $pid2test");

$result = `$ps_binary $pid2test`;

&debug("PS output: $result");
if ($result=~/demarc/i){
	$demarcd_pid = $pid2test;
	print STDOUT "X"x40 . "\n";
	print STDOUT "Attemping to kill PID $pid2test\n";
	print STDOUT "X"x40 . "\n";
	system ("kill $pid2test");
	print STDOUT "\n" . "X"x40 . "\n";
	print STDOUT "Checking if PID $pid2test is alive:\n";
	print STDOUT "X"x40 . "\n";
	print STDOUT "$ps_binary $pid2test\n";
	system ("$ps_binary $pid2test");
	print STDOUT "\n\n";
	return  1;
}
else{
	&big_error("demarcd is not running");
}


}
####################
sub run_command{
my ($command,$user) = @_;

my $timeout = 10;

eval {
   local $SIG{ALRM} = sub { die"timed_out\n"};
   alarm $timeout;
	&safe_slash(\$command);
	&safe_slash(\$user);#just to be PARANOID!
	# we just call it as-is and wait 10 seconds for it to return...
	system ("$su_binary - $user -c '$command'");
};
alarm 0;
if ($@){
	return;
}

return 1;
}
###################
sub get_allowed_commands{
my %cmds;

open (CONF,$cmdfile) || return (&error("Could not open allowed regeneration command file ($cmdfile) : $!"));

while (<CONF>){
    #get rid of comment lines
    next if ($_=~/^\s*#/);
	#skip blank lines
	next unless ($_=~/\S/);
	$_=~s/[\n\r]//g;
    $cmds{$_} = 1;
}

return %cmds;
}

#my $inode = (&stat_file($path))[0];
###################
sub check_local_logs{
my ($check,$ext1,$ext2,$ext3) = @_;

($check) || return; #for determining existence of function

my $sql_query;
my $db_ptr;
my $hash_ref;

my $result;
my @results;
my @current_stats;
my @rules_to_test;
my $rule;
my @red_alerts = ();
my @yellow_alerts = ();
my @green_alerts = ();#oxymoron?
my $line;
my @new_rules = ();

$ext3=~s/\r//g;
@rules_to_test = split(/\n/,$ext3);
(@rules_to_test) || return &yellow("No log rules to monitor.");

foreach $rule(@rules_to_test){
	my @rule_array = split(/;/,$rule);
	($rule_array[1]) || return;
	my $rule_count;
	my $matched_count 	= 0;
	my $matched_lines	= ();
	my $regen_status 	= ();
	my $path			= $rule_array[1];
	my $regex			= $rule_array[2];
#print "PATH:$path\nREGEX : $regex\n";
	my $threshhold		= $rule_array[3];
	my $old_inode;
	my $previous_matches;
	my $inode;

	$inode = (&stat_file($path))[0];
	if (!$inode){
		# then the path to the file was incorrect, let's let them know
		push (@new_rules,$rule);
		push (@red_alerts,"$rule_array[4] ($regex) Path is INVALID: $path ");
		next;#don't continue
	}

	if ($rule_array[0]=~/PREV_MATCHES=(\d+)/){
		$previous_matches = $1;
		&debug("$previous_matches previous matches for $regex  in $path");
		if ($rule_array[0]=~/INODE=(\d+)/){
			$old_inode = $1;
			if ($old_inode != $inode){
				&debug("INODES have changed: $old_inode $inode");
				&debug("Inodes for file $path have changed... resetting previous_matches counter");
				$previous_matches = 0;
			}
			else{
				&debug("INODES stayed the same: $old_inode $inode");
			}
		}
	}

	#test the file line by line for matching lines (not the most efficient, but will work for now)
	if (!(open (LOGFILE,$path))){
		push (@red_alerts,"$rule_array[4] ($regex) Can not open file for reading: $path $!");
		push (@new_rules,$rule);
        next;#don't continue
    }

	while (<LOGFILE>){
		$_=~s/[\r\n]//g;
		if ($_=~/$regex/){
			$matched_count++;
			if ($matched_count > $previous_matches){
				$matched_lines .= $_ . "\n";
			}
		}
	}
	close LOGFILE;
	my $MATCHED = ($matched_count-$previous_matches);
	my $a_level = ($rule_array[0]=~/yellow/i) ? "YELLOW" : "RED";

	$previous_matches =  ($MATCHED >= $rule_array[3]) ? $matched_count : $previous_matches;

	push (@new_rules,"[INODE=$inode][PREV_MATCHES=$previous_matches]$a_level;$path;$regex;$threshhold;$rule_array[4]");

	#see if we're within established parameters
	($rule_array[3]=~/^[1-9]+\d*$/) || ($rule_array[3]=1);
	if ($MATCHED >= $rule_array[3]){
		#ALERT status
		if ($a_level eq "YELLOW"){
			push (@yellow_alerts,"$rule_array[4] /$regex/ has $MATCHED matching lines in $path.\n$matched_lines");
		}
		else{
			#default to red
			push (@red_alerts,"$rule_array[4] /$regex/ has $MATCHED matching lines in $path.\n$matched_lines");
		}
	}
	else{
		#"this porridge is just right"
		push (@green_alerts,"$rule_array[4] /$regex/ has $MATCHED matching lines in $path.\n$matched_lines");
	}

}

my $status_report;

foreach (@red_alerts){
	$status_report .= "RED: $_\n";
}
foreach (@yellow_alerts){
	$status_report .= "YELLOW: $_\n";
}
foreach (@green_alerts){
	$status_report .= "GREEN: $_\n";
}

################# Insert the updated rules back into the DB:
my $new_rules_string = join ("\n",@new_rules);
&safe_slash(\$new_rules_string);

    $sql_query = "  UPDATE dm_monitor_current \
                    SET \
                        ext3 = '$new_rules_string' \
                    WHERE \
                        sid         = '$main_monitor_sid' AND \
                        service LIKE 'Logs' AND \
						client_sid = '$sid'";
&connect_to_db;
($db->do($sql_query)) || &error("Couldn't update DB with new log rules: $sql_query");

########## Return worst status we've got
if (@red_alerts){
	return &red("Monitored System Logs not within established parameters.\n",$status_report);
}
elsif (@yellow_alerts){
	return &yellow("Monitored System Logs not within established parameters.\n",$status_report);
}
else{
	return &green("Monitored System Logs within established parameters.\n",$status_report);
}


}
#######################################################
sub install_sensor{

my $sql_query;
my $db_ptr;
my $hash_ref;

my $dbu;
my $dbp;
my $dbh;
my $dbn;
my $dbs;
my $my_sid;
my $N_pid;

my $min_conf;

print "THIS WILL ATTEMPT TO INSTALL A NEW SNORT SENSOR\n\n";
print "Continue ? [Yn] ";
chomp(my $answer = <STDIN>);

die "OK, thanks anyway.\n" unless ($answer=~/^y/i);

print "Database Username snort should use: ";
chomp($dbu = <STDIN>);
print "Database Password snort should use: ";
chomp($dbp = <STDIN>);
print "Database Host snort should connect to (please use IP address, even 127.0.0.1 for localhost): ";
chomp($dbh = <STDIN>);
print "Database name snort should use: ";
chomp($dbn = <STDIN>);
print "Name of this sensor (should contain no spaces): ";
chomp($dbs = <STDIN>);

($dbu && $dbp && $dbh && $dbn && $dbs) ||
	die "You missed at least one value... you'll have to run it again.\n";


$min_conf.= << 'EOF';

# NOTE:
# This snort.conf file has been automatically generated for you
# in order to quickly bring a new snort/DEMARC sensor online.
# This is BY NO MEANS a list of all options availible to you
# from a properly optimized snort.conf file.
#
# Once your sensor is online, and you are able to control it from
# the DEMARC web interface, please go to http://snort.sourcefire.com/
# to download the sample snort.conf file which you can then customize
# to fit the needs of your network.


var HOME_NET any
var EXTERNAL_NET any
var SMTP $HOME_NET
var HTTP_SERVERS $HOME_NET
var SQL_SERVERS $HOME_NET
var DNS_SERVERS $HOME_NET

preprocessor defrag
preprocessor stream2: timeout 10, ports 21 23 80 110 143, maxbytes 16384
preprocessor unidecode: 80
preprocessor rpc_decode: 111
preprocessor bo: -nobrute
preprocessor telnet_decode
preprocessor portscan: $HOME_NET 4 3 portscan.log
preprocessor portscan-ignorehosts: $DNS_SERVERS

EOF

$min_conf .=  "output database: log, mysql, user=$dbu dbname=$dbn password=$dbp host=$dbh sensor_name=$dbs\n";


open (OUT,">$snort_conf_file") || die "Can't open conf file for writing ($snort_conf_file) : $!\n";
print OUT $min_conf;
close OUT;

select STDOUT;$| = 1;
print "\n\nAbout to start snort so that it registers with the database, press Ctrl-C to exit...";
sleep(3);
eval {
   local $SIG{ALRM} = sub { die"timed_out\n"};
   alarm 10;
	system ("$snort_binary_path -c $snort_conf_file");
#$N_pid =  open (SNORT,"$snort_binary_path -c $snort_conf_file |");

};
alarm 0;

system ("killall snort");

print "\n\nChecking to see if it registered  correctly...\n";

$sql_query = "SELECT sid FROM sensor WHERE hostname = '$dbs'";

$db_ptr = &run_query($sql_query);
($hash_ref=$db_ptr->fetchrow_hashref) || die "It appears something went wrong... sorry, I quit. (try checking DB authentication variables and running it again)\n";

print "SUCCESS!!!\n\nThis sensor has received SID # $$hash_ref{'sid'}\n\nPlease write that down, as you will have to manually put this in your demarcd.conf file.\n";
$my_sid =  $$hash_ref{'sid'};

#put this temp file into the DB:
&safe_slash(\$min_conf);
$sql_query = "SELECT sid FROM dm_conf WHERE sid='$my_sid'";
$db_ptr = &run_query($sql_query);
if (!($hash_ref=$db_ptr->fetchrow_hashref)){
	$sql_query = "INSERT INTO  dm_conf VALUES ('$my_sid',NOW(),NOW(),'$min_conf')";
	($db->do($sql_query)) || die"Couldn't update DB with new snort.conf file\n";
}



print "Would you like me to download your first set of rules from http://snort.sourcefire.com/ for you? \n";
print "Continue (you'll have to have lynx and tar installed for this to work) ? [Yn] ";
chomp(my $answer = <STDIN>);

die "OK, thanks anyway.\n" unless ($answer=~/^y/i);

&update_rulesets_from_sourcefire($my_sid,"http://snort.sourcefire.com/downloads/snortrules.tar.gz");


print "YOU'RE DONE!!  Don't forget, your new SID is $my_sid\n\nPlease Enjoy DEMARC.\n\n\n\n";

}









