# LogIDS 0.5 Pro
# Log file analysis and intrusion detection system running on LogAgent engine
# 0.1
# Network map definition module 0.01
# First attempt at building a network definition module that will link with objects on the network map
# Network map loading module 0.01
# First attempt at building a network map display module that will link with objects on the network map
# Graphical interface module 0.01
# First attempt at building a graphical interface to display the network map
# All modules integrated and functionning properly
# 0.2 Code aims to offer analyzed data to the appropriate window and creation of the rules module
# 0.3 Code simple rule base functioning (ALERT, DISPLAY, ICON)
######
# Things to do for 0.4
# Have a wildcard check on IPs where necessary = DONE
# Have wildcards on rules conditions = DONE -> works on 'eq' only
# Break down IP and port info where applicable for src and dst DONE -> src, dest, date, time, ip, host, user
# Do the same for DATE-TIME DONE -> see line above
# Handle multi-words values in rules.txt using _ to replace ' ' = DONE
# Handle multiple-element reporting of the same event = DONE
# Have a testrules function = DONE
####
# Things to do for 0.5
# Remove extraneous commented lines of code = DONE
# Add proper header information = DONE
# Add a reset_icon and reset_texts functions menu = DONE
# Add DROP, WARN and REJECT rules = DONE
# Corrected buffer overflow with local variable declarations (my) = DONE
# Add basic Snort and Event Viewer support = DONE with rules
# Remove archaic support for MONDIR = DONE
# Add rule condition 'ct' and 'nc' (for 'contains' and 'not contains') string (eqv. to =~m// and !(=~m//)) = DONE
# Add wildcard * in rules for Application and Condition fields = DONE
# Freeze code for free version 1.0
#########################################################################################
# LogIDS 0.5 Pro									#
# by Floydman  floydian_99@yahoo.com							#
# Copyright 2002-2003 SecurIT Informatique Inc.  http://securit.iquebec.com		#
#											#
#########################################################################################


#########################################################################################
# Main Program										#
# This is the main structure of LogIDS.							#
#########################################################################################

# Using Win32::AdvNotify 
# By Amine Moulay Ramdane <aminer@generation.net>
# Website: http://www.generation.net/~aminer/Perl/ 
# This module is now available at CPAN.org
# This Perl module is the core engine of LogAgent.  This module contains all the funtionalities
# For monitoring the changes made to files and folders on the system
# You will also need to install the Win32 API Perl module in order to use AdvNotify

use Win32::AdvNotify qw(FILE_NAME SIZE Yes No 
                        All %ActionName %ActionColor %EventName);

# Declaration of needed components for machine identification
use Socket;
use Sys::Hostname;
use File::Basename;
use Win32::GUI;
use Win32::Sound;
my $element;

# Creation of the AdvNotify object
my $obj  = new Win32::AdvNotify()|| die "Can't create object\n";

# Filelocking of the config files
lockconfig();
print "Configuration files locked\n";

### Network definition table
# Network items definition table declaration
@netdeftable = ();
definetable();
print "Network definition file loaded OK\n";

###
# Load Rules
loadrules();
print "Rules loaded OK\n";
# Declaration of window layout table and elements table
# They are separated because I don't want to sift through all the empty bmp to find a network node
@layout = (); @elements = ();

### Map loading module
@map = loadmap();
print "Network map file loaded OK\n";

# Get height and width of network map
$map_height = $#map + 1;
@sample = (split (//,$map[0])); $map_width = $#sample + 1;

### Declaration of built-in bitmaps
# Bitmap objects definition
$logo = new Win32::GUI::Bitmap('./bmp/logo.bmp') or die("Can't find bitmap ./bmp/logo.bmp");
$white = new Win32::GUI::Bitmap('./bmp/white.bmp') or die("Can't find bitmap ./bmp/white.bmp");
$vert = new Win32::GUI::Bitmap('./bmp/vert.bmp') or die("Can't find bitmap ./bmp/vert.bmp");
$horz = new Win32::GUI::Bitmap('./bmp/horz.bmp') or die("Can't find bitmap ./bmp/horz.bmp");
$cross = new Win32::GUI::Bitmap('./bmp/cross.bmp') or die("Can't find bitmap ./bmp/cross.bmp");
$toppipe = new Win32::GUI::Bitmap('./bmp/top.bmp') or die("Can't find bitmap ./bmp/top.bmp");
$rightpipe = new Win32::GUI::Bitmap('./bmp/right.bmp') or die("Can't find bitmap ./bmp/right.bmp");
$leftpipe = new Win32::GUI::Bitmap('./bmp/left.bmp') or die("Can't find bitmap ./bmp/left.bmp");
$bottompipe = new Win32::GUI::Bitmap('./bmp/bottom.bmp') or die("Can't find bitmap ./bmp/bottom.bmp");
$topright = new Win32::GUI::Bitmap('./bmp/topright.bmp') or die("Can't find bitmap ./bmp/topright.bmp");
$topleft = new Win32::GUI::Bitmap('./bmp/topleft.bmp') or die("Can't find bitmap ./bmp/topleft.bmp");
$lowright = new Win32::GUI::Bitmap('./bmp/lowright.bmp') or die("Can't find bitmap ./bmp/lowright.bmp");
$lowleft = new Win32::GUI::Bitmap('./bmp/lowleft.bmp') or die("Can't find bitmap ./bmp/lowleft.bmp");
$internet = new Win32::GUI::Bitmap('./bmp/internet.bmp') or die("Can't find bitmap ./bmp/internet.bmp");
$icon = new Win32::GUI::Icon('./securit.ico') or die("Can't find bitmap ./securit.ico");

# Creation of config table
my @config = getconfig();
print "Filters file loaded OK\n";

# Creation of file table
my @filelist = getfilelist();
print "Filelist loaded OK\n";

# Creation of line count table
my @linecount = getlinecount (@filelist);

# Creation of mondir table
my @mondir = ('./log/');

# Creation of threads table.  Threads are started, and then launched, this is the way the AdvNotify module works
my $index=0;
foreach $element (@mondir) 
	{ 
	$threads[$index] = $obj->StartThread(Directory    => $mondir[$index],  
                             Filter       =>  All ,
                             WatchSubtree =>  No ) || die "Can't start thread\n";
	$threads[$index]->EnableWatch() || die "Problem starting EnableWatch()\n"; 
	$index++;
	}
print "Thread started OK\n";

#####
# GUI portion
# Main window menu definition
$mainmenu = new Win32::GUI::Menu(
    "&File"   => "File",
    " > &Quit" => "Quit",

 "&Clear..."   => "Clear",
    " > &Icons" => "Icons",
    " > &Textfields" => "Textfields",
    " > Clear &All" => "Clearall",  

 "&Help" => "Help",
    " > &Documentation" => "Documentation",
    " > &About" => "About",
);

# Main application window definition
$main = Win32::GUI::Window->new(-name => 'Main',
				-width  => ($map_width*60),
				-height => ($map_height*60),
				-text => 'LogIDS 1.0 Open Source version',
				-autohscroll => 1,
				-autovscroll => 1,
				-vscroll   => 1,
				-hscroll   => 1,
				-menu => $mainmenu);
$main->SetIcon ($icon, 0);
### Load Window objects based on network map and network definition table
loadwindowobjects();
print "Window objects loaded OK\n";

# To show the window
$main->Show();
print "Starting GUI\n";

# To maximize
Win32::GUI::Maximize($main);

#### End of GUI definition phase

print "Entering monitoring loop\n";
print "\n";

# Print header
print "LogIDS 1.0 Free, brought to you by Floydman\n";
print "Copyright 2003 SecurIT Informatique Inc.\n";
print "This copy is licensed Open Source and free to use\n\n";
print "http://securit.iquebec.com\n";
print "\nPress CTRL-C to quit\n";

# Enters the main monitoring loop
startmonitoringloop();

closeagent();
# End of program#

#########################################################################################
# procedure lockconfig()								#
# This procedure locks the config files to prevent tampering while LogAgent runs.	#
#########################################################################################
sub lockconfig
{
open(MAPFILE,"<map.txt") || die "Can't open map.txt";
open(NETDEFFILE,"<netdef.txt") || die "Can't open netdef.txt";
open(FILTERFILE,"<filter.txt") || die "Can't open filter.txt";
open(RULESFILE,"<rules.txt") || die "Can't open rules.txt";
}

#########################################################################################
# procedure unlockconfig()								#
# This procedure unlocks the config files.						#
#########################################################################################
sub unlockconfig
{
close (MAPFILE) || die "Can't close map.txt";
close (NETDEFFILE) || die "Can't close netdef.txt";
close (FILTERFILE) || die "Can't close filter.txt";
close (RULESFILE) || die "Can't close rules.txt";
}

#########################################################################################
# procedure getconfig() 								#
# This procedure gets the configuration file config.txt.				#
#########################################################################################

sub getconfig
{ my $item = '';
my @filtertable = <FILTERFILE>;# || die "Can't read from filter.txt";
foreach $item (@filtertable) {$item = lc ($item) ;chomp $item;}
return @filtertable;
}

#########################################################################################
# procedure getfilelist() 								#
# This procedure gets the list of files to check for in mondir.txt. 			#
#########################################################################################

sub getfilelist
{
my @filetable = ();
my $File = ''; my $k = 0;
my $dir = './log/';

opendir(DIR,$dir);
my @Files = readdir(DIR);
close(DIR);

foreach $File (@Files) 
	{ 	next if (($File eq "..") || ($File eq "."));
		$File = $dir.$File; 
		if ((-f $File) && (-T $File )) {$filetable[$k] = $File; $k++;}
	}

return (@filetable);
}

#########################################################################################
# procedure getlinecount (@filelist)							#
# This procedure gets the list of directories to watch in mondir.txt.			#
#########################################################################################
sub getlinecount
{
my @table = @_;
my @linecount;
my @temp = ''; my $file = '';
my $z = 0;

foreach $file (@table)
  {
   open (FILE, $file) || die "Can't open ".$file." for line count.";
   @temp = <FILE>;
   close (FILE);
   @temp = parse(@temp);
   $linecount[$z] = @temp;
   $z++;
  }
return (@linecount);
}

#########################################################################################
# procedure parse(table_file) 								#
# This procedure cleans the files from non-valid and blank characters that could be	#
# placed in the config files.  The procedure returns the file as a table.		#
#########################################################################################

sub parse
{ my @table = @_;
my $element = ''; my @char = (); my $char = '';
#check for invalid characters in table_file
chomp @table;

foreach $element (@table)
 {
  $element=~s%^\s+%%;
  @char = split (//, $element);

  foreach $char (@char)
    { $char=~s%\\%/%; }
  $element = join ('',@char);
 }

my @tabletemp;
my $x = 0;

foreach $element (@table)
   {
    if ($element ne '') {
	$tabletemp[$x]=$element;
	$x++;}
   }
@table = @tabletemp;
return (@table);
}


#########################################################################################
# procedure startmonitoringloop()							#
# This procedure is the main monitoring loop.  When a change is detected in a file	#
# located in a monitored directory (preferably ASCII files), the procedure calls 	#
# getlastline() with the name of the modified file.  The captured line is then sent	#
# via the procedure sendoutput(), along with LogAgent's configuration table.		#
#########################################################################################

sub startmonitoringloop
{
$OK = 1; $quit=0;
while ($OK && !$quit)
 {
  Win32::GUI::DoEvents();
   $test = $threads[0]->Wait(250);
   if ($test==1)
    { while($threads[0]->Read(\@data))# exit when the list is empty
      {
       for($i=0;$i<=$#data;$i++)
	  { getlastline($data[$i]); }
       }
    } elsif (($test == 2) || ($test == 3) || ($test == 4) || ($test == 6)) {$OK=0;}
 }
}

#########################################################################################
# procedure getlastline(filename)							#
# This procedure gets the last line (non-blank) of the file received as the argument.	#
# It returns the filename (whitout path) and the last line of the file.			#
#########################################################################################

sub getlastline
{my ($data) = @_;
my $y = 0;
my @lines = ();
my $filename = ''; my $lastline = '';

if (-T $data->{Directory}.$data->{FileName}){

$filename = lc($data->{Directory}.$data->{FileName});

COUNTER: foreach $file (@filelist) 
         {  $file = lc($file);
            if ($file eq $filename) {last COUNTER;} else {$y++;}
         }

open (LOGFILE, $data->{Directory}.$data->{FileName}) or die "Can't open log file ".$data->{Directory}.$data->{FileName};
flock (LOGFILE, 2) or die "Can't lock file";

@lines = <LOGFILE>;

# if the file contains more than 50 lines, backup and purge the lines
if ($#lines > 50)
	{ 
	open (LOGFILE, ">".$data->{Directory}.$data->{FileName}) or die "Can't open log file ".$data->{Directory}.$data->{FileName};
	print LOGFILE "";
	close LOGFILE or die "Can't close file"; # To unlock the file as fast as possible for new entries
	open (BACKUP, ">>./backup/".$data->{FileName}) || die "Can't open backup log file ".$data->{Directory}.$data->{FileName}.".blg";
        flock (BACKUP, 2) || die "Can't lock file for writing";
	print BACKUP @lines;
        close BACKUP || die "Can't close backup log file";
	} else { close LOGFILE or die "Can't close log file"; }

@lines = parse(@lines);

if ($filelist[$y] eq '') {$filelist[$y] = $filename;}
if ($linecount[$y] > @lines) { $linecount[$y]=0; }

for ($toto=$linecount[$y]; $toto < @lines; $toto++ )
 { 
  $lastline = $lines[$linecount[$y]];
  $linecount[$y]++;
  analyzeoutput(lc($data->{FileName}), $lastline);
 }

# if the linecounter is more than 50 lines, reset it to 0, since they have been purged
if ($linecount[$y]>50)  { $linecount[$y]=0; }
} # End if -T
}

#########################################################################################
# procedure analyzeoutput(filename, line)						#
# This procedure takes a data line, fins out what filters apply to it, and analyze the  #
# data against the according rules.							#
#########################################################################################
sub analyzeoutput
{ my ($filename, $line, ) = @_;

my $delim = ',';
my %rec = ();
my @filter = ();
my $j=0; my $i=0;
my $item = ''; my @items = ();

FILTER: for ($i=0; $i<@config; $i++)
	{ 
	  @filter = split(/,/, $config[$i]);
	  if (samefile($filename, $filter[0])) 
		{ last FILTER;	} 
	}

if ($i >= @config) { print "no filter found for file $filename\n";}

%rec->{'filename'} = $filename; %rec->{'application'} = $filter[1];

if ($filter[-1]=~m/DELIM/i) {@delim = split(//,$filter[-1]); $delim = $delim[-1]; pop @filter;}

@items = split (/$delim/, $line); 

foreach $item (@items) {if (($filename=~m/.clg$/i) && (not defined $filter[$j+2]))
				{$rec{'line'} = $rec{'line'}.",".$items[$j];
				$j++;}
			else 	{%rec->{$filter[$j+2]} = $items[$j];
				$j++;}
			}
### Break down source, dest, ip, host, username, date and time info if applicable
if ((defined $rec{'source'}) &&(not defined $rec{'src_ip'})) {(%rec->{'src_ip'}, %rec->{'src_port'}) = split(/:/,$rec{'source'});}
if ((defined $rec{'dest'}) &&(not defined $rec{'dest_ip'})) {(%rec->{'dest_ip'}, %rec->{'dest_port'}) = split(/:/,$rec{'dest'});}
if ((defined $rec{'src_ip'}) && (not defined $rec{'source'})) {$rec{'source'} = join (':', ($rec{'src_ip'}, $rec{'src_port'}));}
if ((defined $rec{'dest_ip'}) && (not defined $rec{'dest'})) {$rec{'dest'} = join (':', ($rec{'dest_ip'}, $rec{'dest_port'}));}
if ((not defined $rec{'ip'}) && (defined $rec{'logip'})) {%rec->{'ip'} = $rec{'logip'};}
if ((not defined $rec{'logip'}) && (defined $rec{'ip'})) {%rec->{'logip'} = $rec{'ip'};}
if ((not defined $rec{'host'}) && (defined $rec{'loghost'})) {%rec->{'host'} = $rec{'loghost'};}
if ((not defined $rec{'loghost'}) && (defined $rec{'host'})) {%rec->{'loghost'} = $rec{'host'};}
if ((not defined $rec{'username'}) && (defined $rec{'loguser'})) {%rec->{'username'} = $rec{'loguser'};}
if ((not defined $rec{'loguser'}) && (defined $rec{'username'})) {%rec->{'loguser'} = $rec{'username'};}
if ((defined $rec{'date_time'}) && ((not defined $rec{'date'}) && (not defined $rec{'time'}))) {(%rec->{'date'}, %rec->{'time'}) = split (/-/,$rec{'date_time'});}
if ((defined $rec{'time_date'}) && ((not defined $rec{'date'}) && (not defined $rec{'time'}))) {(%rec->{'time'}, %rec->{'date'}) = split (/-/,$rec{'time_date'});}
if ((not defined $rec{'date'}) && (defined $rec{'logdate'})) {%rec->{'date'} = $rec{'logdate'};}
if ((not defined $rec{'logdate'}) && (defined $rec{'date'})) {%rec->{'logdate'} = $rec{'date'};}
if ((not defined $rec{'time'}) && (defined $rec{'logtime'})) {%rec->{'time'} = $rec{'logtime'};}
if ((not defined $rec{'logtime'}) && (defined $rec{'time'})) {%rec->{'logtime'} = $rec{'time'};}

applyrules (%rec);
}

#########################################################################################
# procedure applyrules 									#
# This procedure applies the rules table against the record and take appripriate action.#
#########################################################################################

sub applyrules
{ my %rec = @_;
my $actionline = ''; my @line = (); my $result = 0; my $key = '';

for (my $i=0; $i<@rules; $i++)
	{if ((lc($rules[$i]->{'application'}) eq lc($rec{'application'})) || (lc($rules[$i]->{'application'}) eq '*')) # If the rules apply to the same app
		{@line = split (/ /, $rules[$i]->{'condition'});    # Then check if the condition is met
		 my @vara=(); my @varb=(); my @cond=(); my @megacond=(); my $condindex=0;
		 while (@line)
		   {
		    $vara[$condindex] = lc(shift @line) || die "Missing VarA$condindex argument in line $i in rules.txt";
		    if ($vara[$condindex] ne '*')
			    {
			    $cond[$condindex] = lc(shift @line) || die "Missing COND$condindex argument in line $i in rules.txt";
			    $varb[$condindex] = lc(shift @line) || die "Missing VarB$condindex argument in line $i in rules.txt";
			    $varb[$condindex]=~s/_/ /;
			    if (@line) {$megacond[$condindex] = lc(shift @line); $condindex++;}
			    }
		   }

		 if ($vara[$condindex] ne '*')
			 {
			 $result = evalexpr ($vara[0], $rec{$vara[0]}, $cond[0], $varb[0]);
			 for ($j = 1; $j <= $condindex; $j++)
				{if ($megacond[$j-1] eq 'and') {$result = $result & evalexpr ($vara[$j], $rec{$vara[$j]}, $cond[$j], $varb[$j]);}
				 elsif (($megacond[$j-1] eq 'or')) {$result = $result | evalexpr ($vara[$j], $rec{$vara[$j]}, $cond[$j], $varb[$j]);}
				 else {die "Megacondition $megacond[$j-1] unknown in rule $i in rules.txt"}
				}
			 } else {$result = 1;}
		    
		if ($result) ## If the result is positive, then apply the rule
			{$actionline = $rules[$i]->{'action'};
			 if ($actionline=~m/ALERT/i) {Win32::Sound::Play(SystemExclamation, SND_ASYNC);Win32::Sound::Play(SystemExclamation, SND_ASYNC);Win32::Sound::Play(SystemExclamation, SND_ASYNC);}
			 elsif ($actionline=~m/WARN/i) {Win32::Sound::Play(SystemExclamation, SND_ASYNC);}
			 elsif ($actionline=~m/DISPLAY/i)
				{displayoutput ($actionline, %rec);}
			 elsif ($actionline=~m/DROP/i) {print "Record DROPped : ";foreach $key (%rec){if (exists $rec{$key}) {print "$key=$rec{$key}  ";}}print "\n\n";}
			 elsif ($actionline=~m/REJECT/i) {print "Record REJECTed in $rec{'filename'}\n\n";}
			 elsif ($actionline=~m/ICON/i)
				{$actionline=~s/ICON=//i;
				 $bmp = new Win32::GUI::Bitmap("./bmp/$actionline") or die("Can't find bitmap ./bmp/$actionline\n");
		 FINDELEM:	 for (my $i=0; $i<@elements; $i++)	# Loop to find the related textfield
					{if ((defined $rec{'host'} && (lc($elements[$i]->{'Definition'}->{'host'}) eq lc($rec{'host'}))) || (sameip($elements[$i]->{'Definition'}->{'ip'}, $rec{'ip'})))
					{$elements[$i]->{'Bitmap'}->SetImage($bmp); }
					}
				}
			 else {die "Unknown action specified in rule $i in rules.txt";}
			}
		} # End If 'application'
	} # End For $i<@rules
Win32::GUI::DoEvents();
}

#########################################################################################
# procedure sameip (ip1, ip2)								#
# This procedure returns 1 if both IPs are equivalent, 0 otherwise.			#
# Supports * wildcards.									#
#########################################################################################

sub sameip {
my ($ip1, $ip2)= @_;
my @ip1 = (); my @ip2 = ();
@ip1 = split (/\./, $ip1); @ip2 = split (/\./, $ip2);

for (my $v=0; $v<4; $v++)
{  
   if (($ip1[$v] eq $ip2[$v]) || ($ip1[$v] eq '*') || ($ip2[$v] eq '*'))
	{if ($ip2[$v] eq '*') {return 1;}
	}
   else {return 0;}
}
return 1;
}

#########################################################################################
# procedure evalexpr ()									#
# This procedure evaluates 2 arguments against a condition from rules.			#
#########################################################################################

sub evalexpr
{ my ($ref1, $value1, $eval, $value2) = @_;
my $returnval = 0;
if (($ref1 eq 'ip') || ($ref1 eq 'logip')) 
	{if (($eval eq 'eq') || ($eval eq '==') || ($eval eq '=')) {$returnval = sameip($value1, $value2);}
	 elsif (($eval eq 'ne') || ($eval eq '!=')) {$returnval = !(sameip($value1, $value2));}
	 else {die "Condition $eval cannot be applied on IP addresses. Use eq, ne, == or != only."}
	}
else	{
	 if (($eval eq '=') || ($eval eq '==')) {if ($value1 == $value2) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq '<') {if ($value1 < $value2) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq '>') {if ($value1 > $value2) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq '<=') {if ($value1 <= $value2) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq '>=') {if ($value1 >= $value2) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq '!=') {if ($value1 != $value2) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'eq') {if ((lc($value1) eq lc($value2)) || $value2 eq '*') {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'gt') {if (lc($value1) gt lc($value2)) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'lt') {if (lc($value1) lt lc($value2)) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'ge') {if (lc($value1) ge lc($value2)) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'le') {if (lc($value1) le lc($value2)) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'ne') {if (lc($value1) ne lc($value2)) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'ct') {if (lc($value1)=~m/$value2/i) {$returnval = 1;} else {$returnval = 0;}}
	 elsif ($eval eq 'nc') {if (!(lc($value1)=~m/$value2/i)) {$returnval = 1;} else {$returnval = 0;}}
	 else {die "Condition $eval unknown in file rules.txt\n";} #End if $eval
	}
return $returnval;
}

#########################################################################################
# procedure displayoutput (record, displayfields)					#
# This procedure displays the information contained in record in appropriate window. 	#
#########################################################################################

sub displayoutput
{ my ($displayfields, %rec)= @_;
my $temptext = '';
my $i=0;
$displayfields=~s/DISPLAY=//i; 
@displayfields = split (/#/,$displayfields);

FINDELEMENT:	for (my $i=0; $i<@elements; $i++)	# Loop to find the related textfield
	{	if ((defined $rec{'host'} && (lc($elements[$i]->{'Definition'}->{'host'}) eq lc($rec{'host'}))) || (sameip($elements[$i]->{'Definition'}->{'ip'}, $rec{'ip'})))
			{$temptext = $temptext.$elements[$i]->{'TextField'}->Text."\r\n";
			 if (@displayfields) 
				{ foreach $item (@displayfields) { $temptext = $temptext.uc($item)." = ".$rec{$item}."  ";}
				}
			 else {foreach $key (%rec) {if (exists $rec{$key}) {$temptext = $temptext.uc($key)." = ".$rec{$key}."  ";}}}
			 $elements[$i]->{'TextField'}->Text($temptext);$elements[$i]->{'TextField'}->Scroll("bottom");
			 $temptext = '';
			}
	}
if ($i >=@elements) {die "Object reporting not found on map";} # Don't forget to have a RJCT console for handling these
}

#########################################################################################
# procedure loadrules									#
# This procedure checks if 2 filenames are identical.  Allows for wildcards in the 	#
#########################################################################################

sub loadrules
{
my @temptable = <RULESFILE>;
my $item = 0; 
foreach $item (@temptable) {$item = lc ($item) ;chomp $item;}

for (my $i=0; $i<@temptable; $i++)
	{@line = split (/,/,$temptable[$i]);
	 $rules[$i]->{'application'} = lc($line[0]);
	 $rules[$i]->{'condition'} = lc($line[1]);
	 $rules[$i]->{'action'} = lc($line[2]);
	 if (not defined $rules[$i]->{'application'}) {die "Application field not defined in rule $i in rules.txt\n";}
	 if (not defined $rules[$i]->{'condition'}) {die "Condition field not defined in rule $i in rules.txt\n";}
	 if (not defined $rules[$i]->{'action'}) {die "Action field not defined in rule $i in rules.txt\n";}


	 my @line = split (/ /, $rules[$i]->{'condition'});    # Then check if the condition syntax is met
		 my @vara=();my @varb=();my @cond=();my @megacond=();my $condindex=0;
		 while (@line)
		   {
		    $vara[$condindex] = lc(shift @line) || die "Missing VarA$condindex argument in line $i in rules.txt";
		   if ($vara[$condindex] ne '*') 
		   {$cond[$condindex] = lc(shift @line) || die "Missing COND$condindex argument in line $i in rules.txt"; 
		    $varb[$condindex] = lc(shift @line) || die "Missing VarB$condindex argument in line $i in rules.txt";
		    $varb[$condindex]=~s/_/ /;
		    if (@line) {$megacond[$condindex] = lc(shift @line); 
				if (($megacond[$condindex] ne 'and') && ($megacond[$condindex] ne 'or')) {die "Megacondition $megacond[$condindex] unknown in rule $i in rules.txt";}
				$condindex++;}
		   }
		  }

for (my $x; $x <= $condindex; $x++)
	{ if (!(($cond[$x] eq '=') || ($cond[$x] eq '==') || ($vara[$condindex] eq '*') || ($cond[$x] eq '<=') || ($cond[$x] eq '>=') || ($cond[$x] eq '!=') || ($cond[$x] eq '<') || ($cond[$x] eq '>') || ($cond[$x] eq 'eq') || ($cond[$x] eq 'ne') || ($cond[$x] eq 'gt') || ($cond[$x] eq 'lt') || ($cond[$x] eq 'le') || ($cond[$x] eq 'ge') || ($cond[$x] eq 'nc') || ($cond[$x] eq 'ct')))
		{ die "Unknown condition in rule $i in rules.txt<--\n";}
	}

	my $actionline = $rules[$i]->{'action'};
	if (($actionline=~m/ALERT/i) || ($actionline=~m/WARN/i) || ($actionline=~m/DISPLAY=/i) || ($actionline=~m/ICON=/i) || ($actionline=~m/DROP/i) || ($actionline=~m/REJECT/i) ) 
		{if ($actionline=~m/ICON=/i) {  $actionline=~s/ICON=//i;
						if (!(-e './bmp/'.$actionline)) {die "Bitmap ./bmp/$actionline not found\n";}
					     }
		}
	else {die "Action $actionline unknown in rule $i in rules.txt";}
	}
}

#########################################################################################
# procedure samefile (filename1, filename2)						#
# This procedure checks if 2 filenames are identical.  Allows for wildcards in the 	#
# second filename.									#
#########################################################################################

sub samefile
{ my ($name1, $name2) = @_;

if ($name1 eq $name2) {return 1;} 
else 	{@name_1 = split (/\./,$name1); @name_2 = split (/\./,$name2);
	 if (($name_2[0] eq '*') && ($name_2[1] eq '*')) {die "A file can't be named *.* in filter.txt";}
	 if ($name_2[0] eq '*') {if ($name_2[1] eq $name_1[1]) {return 1;} else {return 0;}}
	 if ($name_2[1] eq '*') {if ($name_2[0] eq $name_1[0]) {return 1;} else {return 0;}}
 	 
	 @prefix1 = split(//,$name_1[0]); @suffix1 = split(//,$name_1[1]);
	 @prefix2 = split(//,$name_2[0]); @suffix2 = split(//,$name_2[1]);

	 if (@suffix1 eq @suffix2) { for (my $k=0; $k<@prefix1; $k++)
					{ if (($prefix1[$k] ne $prefix2[$k]) && ($prefix2[$k] ne '?')) {return 0;} }
				     return 1;
				   }
	 if (@prefix1 eq @prefix2) { for (my $k=0; $k<@suffix1; $k++)
					{ if (($suffix1[$k] ne $suffix2[$k]) && ($suffix2[$k] ne '?')) {return 0;} }
				     return 1;
				   }
	}
}

sub Documentation_Click
{
open (PIPE, "start ".dirname ($^X)."\\docs\\logids.html |") || warn "Error, cannot open documentation files\n";
close PIPE;
}

sub About_Click
{
my $xpos = (($map_width*60)-400)/2; my $ypos = (($map_height*60)-250)/2;

$about = Win32::GUI::Window->new(-name => 'About',
				-width  => 400,
				-height => 250,
				-text => 'About',
				-autohscroll => 0,
				-autovscroll => 0,
				-vscroll   => 0,
				-hscroll   => 0,
				-maximizebox	=> 0,
				-minimizebox	=> 0,
				-pos	=> [ $xpos, $ypos ],
				);

$logofield = $about->AddLabel(	    -left => (0), 
				    -top => (0),
				    -style => 14,
				    -name => "logo",
				    -visible => 1,
				    -text => "",
					);

$aboutstring = "LogIDS 1.0 Pro, Copyright SecurIT Informatique Inc. 2003\r\nThis copy is licensed Open Source\r\nhttp\/\/securIT.iquebec.com";

$logotext = $about->AddTextfield(
				-name     => 'abouttext',
				-multiline => 1,
				-autohscroll => 0,
				-autovscroll => 0,
				-vscroll   => 0,
				-hscroll   => 0,
				-readonly  => 1,
				-pos       => [  50 , 155 ],
				-size      => [ 300, 50 ],
				);

$logotext->Text($aboutstring);

$logofield->SetImage($logo);
$about->SetIcon ($icon, 0);

$about->Show();
}
sub Icons_Click {
for (my $i=0; $i<@elements; $i++)
	{ $elements[$i]->{'Bitmap'}->SetImage($elements[$i]->{'Definition'}->{'bitmap'});}
}
sub Textfields_Click {
for (my $i=0; $i<@elements; $i++)
	{$elements[$i]->{'TextField'}->Text("");
	}
}

sub Clearall_Click {
Icons_Click;
Textfields_Click;
}

sub Quit_Click {
    Main_Terminate();
}

sub Main_Terminate {
    $quit=1;-1;
}

#####
# Network items definition portion
sub definetable
{
my $item = '';

# Load the network definition file into a temporary table and chomp it
my @temptable = <NETDEFFILE>;# || die "Can't read from netdef.txt";
foreach $item (@temptable) {chomp $item;}

my $z=0;
while (@temptable)
  {
   while (($temptable[0] eq '') &&(@temptable)) {shift @temptable; }

   $netdeftable[$z]->{'objid'} = shift @temptable || die "No object ID found for this network item in netdef.txt";

   $item = shift @temptable || die "IP field missing in netdef.txt";
   @item = split (/=/, $item);
   if (lc($item[0]) eq 'ip') {$netdeftable[$z]->{'ip'} = $item[1];} else {die "IP field undefined in netdef.txt";}

   $item = shift @temptable || die "HOST field missing in netdef.txt";
   @item = split (/=/, $item);
   if (lc($item[0]) eq 'host') {$netdeftable[$z]->{'host'} = $item[1];} else {die "HOST field undefined in netdef.txt";}

   $item = shift @temptable || die "LABEL field missing in netdef.txt";
   @item = split (/=/, $item);
   if (lc($item[0]) eq 'label') {$netdeftable[$z]->{'label'} = $item[1];} else {die "LABEL field undefined in netdef.txt";}

   $item = shift @temptable || die "DESC field missing in netdef.txt";
   @item = split (/=/, $item);
   if (lc($item[0]) eq 'desc') {$netdeftable[$z]->{'desc'} = $item[1];} else {die "DESC field undefined in netdef.txt";}

   $item = shift @temptable || die "ICON field missing in netdef.txt";
   @item = split (/=/, $item);
   if (lc($item[0]) eq 'icon') {$netdeftable[$z]->{'icon'} = $item[1];} else {die "ICON field undefined in netdef.txt";}
   while (($temptable[0] eq '') && (@temptable)){shift @temptable;}
   $z++;
  }
}

#####
# Map loading module portion
# Load the map in @map and chomp it
sub loadmap {
my @map = (); my $item = '';

@map = <MAPFILE>;# || die "Can't read from map.txt";
foreach $item (@map) {chomp $item;}
return @map;
}

#####
# Window objects loading module
# Draws the network map and associate the network items definition to the according object
sub loadwindowobjects
{
# beginning of the double-loop to fill the window with the proper bitmaps and textfields into @layout
my $n=0; my $elemindex = 0;

foreach $line (@map)
	{ @char = split (//,$line);
	 for (my $m=0; $m < $map_width; $m++)
		{if ($char[$m] eq '0') # if the char is 0 (empty space)
			{$layout[$m][$n] = $main->AddLabel(
							    -left => ($m*60), 
							    -top => ($n*60),
							    -style => 14,
							    -name => "White",
							    -visible => 1,
							    -text => "blank",
								);
			$layout[$m][$n]->SetImage($white);
			}
		 elsif ($char[$m] eq '|')  # if the char is | (vertical wire)
			{$layout[$m][$n] = $main->AddLabel(
							    -left => ($m*60), 
							    -top => ($n*60),
							    -style => 14,
							    -name => "Vertical",
							    -visible => 1,
							    -text => "vert",
								);
			$layout[$m][$n]->SetImage($vert);
			}
		 elsif ($char[$m] eq '-')  # if the char is - (horizontal wire)
			{$layout[$m][$n] = $main->AddLabel(
							    -left => ($m*60), 
							    -top => ($n*60),
							    -style => 14,
							    -name => "Horizontal",
							    -visible => 1,
							    -text => "horz",
								);
			$layout[$m][$n]->SetImage($horz);
			}
		 elsif ($char[$m] eq '+')  # if the char is + (cross wire)
			{$layout[$m][$n] = $main->AddLabel(
							    -left => ($m*60), 
							    -top => ($n*60),
							    -style => 14,
							    -name => "Crosswire",
							    -visible => 1,
							    -text => "cross",
								);
			}
		 # If the # character is found, make sure there are 4 of them and associate it with internet.bmp
		 elsif ($char[$m] eq '#') { $string=$char[$m].$char[$m+1].$char[$m+2].$char[$m+3].$char[$m+4];
					 if ($string eq '#####') 
						{$layout[$m][$n] = $main->AddLabel(
								    -left => ($m*60), 
								    -top => ($n*60),
								    -style => 14,
								    -name => "Internet",
								    -visible => 1,
								    -text => "internet",
									);
						 $layout[$m][$n]->SetImage($internet); $m=$m+4;
						}
					 else { die "Internet object (#####)-($string) wrongly defined in map.txt";}
					}
		# else it is a network node item, and we try to find it in @netdeftable
		else 	{$string=$char[$m].$char[$m+1].$char[$m+2].$char[$m+3].$char[$m+4];
INDEXLOOP:		 for ($indexloop=0; $indexloop<=$#netdeftable; $indexloop++) 
				{if (lc($netdeftable[$indexloop]->{'objid'}) eq lc($string)) 
					{ last INDEXLOOP;}
				} # End of INDEXLOOP

				 if ($indexloop > $#netdeftable) {die "Object $string located in map.txt not defined in netdef.txt";}

				 $elements[$elemindex]->{'TextField'} = $main->AddTextfield(
								-name     => $netdeftable[$indexloop]->{'objid'},
								-multiline => 1,
								-autohscroll => 0,
								-autovscroll => 1,
								-vscroll   => 1,
								-hscroll   => 0,
								-readonly  => 0,
								-pos       => [  (($m+1)*60)-15, $n*60 ],
								-size      => [ (4*60)+15, 60 ],
								);

				$elements[$elemindex]->{'Bitmap'} = $main->AddLabel(
								    -left => ($m*60)+5, 
								    -top => ($n*60)+5,
								    -style => 14,
								    -name => $netdeftable[$indexloop]->{'objid'},
								    -visible => 1,
								    -text => "Icon",
									);
				 $elements[$elemindex]->{'Label'} = $main->AddLabel(
								    -left => ($m*60)+2, 
								    -top => ($n*60)+45,
								    -name => "Label",
								    -visible => 1,
								    -text => $netdeftable[$indexloop]->{'label'},
									);

				$elements[$elemindex]->{'Definition'} = $netdeftable[$indexloop];
				$elements[$elemindex]->{'Definition'}->{'bitmap'} = new Win32::GUI::Bitmap('./bmp/'.$netdeftable[$indexloop]->{'icon'}) or die("Can't find bitmap ./bmp/$netdeftable[$indexloop]->{'icon'}");

				$elements[$elemindex]->{'Bitmap'}->SetImage($elements[$elemindex]->{'Definition'}->{'bitmap'});
				$elements[$elemindex]->{'TextField'}->Text($string." object definition loaded");

			 $m+=4; $elemindex++;
			} # End of if..elsif..else statement
		} # End of inside FOR loop
	 $n++; 
	} # End of outside FOREACH loop

for ($n=0; $n< $map_height; $n++)
	{for ($m=0; $m < $map_width; $m++)
		{
			 if ($layout[$m][$n]->{-name} eq 'Crosswire') 
			{
			 $top=1;$bottom=1;$left=1;$right=1;
			 if (($m == 0) || ($layout[$m-1][$n]->{-name} eq 'White')) {$left = 0;}
			 if (($m == $map_width-1) || ($layout[$m+1][$n]->{-name} eq 'White')) {$right = 0;}
			 if (($n == 0) || ($layout[$m][$n-1]->{-name} eq 'White')) {$top = 0;}
			 if (($n == $map_height-1) || ($layout[$m][$n+1]->{-name} eq 'White')) {$bottom = 0;}

			 if ($top && $left && $bottom && $right) {$layout[$m][$n]->SetImage($cross);}
			 elsif ($top && $left && $bottom && !$right) {$layout[$m][$n]->SetImage($leftpipe);}
			 elsif ($top && !$left && $bottom && $right) {$layout[$m][$n]->SetImage($rightpipe);}
			 elsif ($top && $left && !$bottom && $right) {$layout[$m][$n]->SetImage($toppipe);}
			 elsif (!$top && $left && $bottom && $right) {$layout[$m][$n]->SetImage($bottompipe);}
			 elsif (!$top && !$left && $bottom && $right) {$layout[$m][$n]->SetImage($lowright);}
			 elsif (!$top && $left && $bottom && !$right) {$layout[$m][$n]->SetImage($lowleft);}
			 elsif ($top && !$left && !$bottom && $right) {$layout[$m][$n]->SetImage($topright);}
			 elsif ($top && $left && !$bottom && !$right) {$layout[$m][$n]->SetImage($topleft);}
			 else {$layout[$m][$n]->SetImage($white);print "Cross wire lost alone in map coordinates H=$m  V=$n\n";}
			} #End  if crosswire
		} #End For $m
	} # End For $n
}


#########################################################################################
# procedure closeagent									#
# This procedure closes any handles and destroy any objects that need to be		#
# closed before shuting down LogAgent.							#
#########################################################################################

sub closeagent
{ 

# Empty the lines buffer if LINEPRINT is on
if ($config[4]) { 
	     open(LINEPRINT,">LPT1") || die "Can't open pipe to LPT1";
	     foreach $line (@buffer){
		print LINEPRINT $line;}
	     close (LINEPRINT) || die "Can't close pipe to LPT1";
	     @buffer=();
	 }

# Unlocking of the config files
unlockconfig();

# termination of the threads. 
for (my $a; $a<$index; $a++)
   { $threads[$a]->Terminate(); }

# destruction of the object
undef $obj;
exit;
}

#EOF
