6. Source code


logagent21.pl
#! C:\perl\bin\perl.exe
# LogAgent 2.1 

#########################################################################################
# LogAgent 2.1										#
# by Floydman  [email protected]							#
# Copyright 2002 SecurIT Informatique Inc.  http://securit.iquebec.com			#
#											#
# This program gets its configuration from the file config.txt, and the list of files or#
# directories to be monitored from the file mondir.txt.  These two files have to be in	#
# the same directory as LogAgent.  The config file lets you specify if you want to 	#
# include the IP of the machine, the hostname and the username in the log files, in	#
# these cases where the software generating the log doesn't provide these credentials.  #
# You can also specify to display entries captured by LogAgent on the console or not. 	#
# Then, the program starts the monitoring threads for each directory entry in 		#
# mondir.txt, and then enters in an infinite-loop, waiting for signals from the moni-	#
# toring threads.  When a signal is trigerred (ie: a file as changed in the directory	#
# you are monitoring), it gets the appended lines from the log file, and sends it to  	#
# the specified outputs.  Output dirs can be remote or local, and as many as you want.	#
#											#
# note about config.txt: Do not modify the headers LOGIP, LOGHOST, LOGUSER and 		#
# SHOWCONSOLE, or the program will stop working.  Only change the Y or N at the end of	#
# the line.  Also make sure that pathnames ends with \					#
#											#
# note about mondir.txt: If you know the exact name of the log file you wish to monitor #
# then you should type its whole path, for example c:\Antivirus\virlog.txt.		#
# If you only know the path of the log file, but cannot determine in advance it exact 	#
# name (if the name is random-generated, or date-generated), then put the path name	#
# followed by a dummy filename WITH NO EXTENSION, for exemple C:\WINNT\Logs\dummy	#
#########################################################################################

#########################################################################################
# LICENSE										#
# This software is Open Source.  This means that its source code is open, free and avai-#
# lable for anyone to look into, make modifications, correct bugs (let me know, please) #
# and use for their personal use.  You can create your own binaries with the evaluation #
# version of perl2exe (www.indigostar.com), or download the shareware version install  	#
# package available on my website.  If you own perl2exe Pro, you are allowed to compile #
# LogAgent and distribute it within your organization only.  DO NOT distribute such a 	#
# compiled version to third parties.  The full version of LogAgent is to be distributed	#
# by SecurIT Informatique Inc or its distribution partners only.  Send an e-mail to 	#
# [email protected] if you would like to be a LogAgent distributor.			#
# LogAgent (full version) is available for commercial use at 15$US per copy, volume	#
# discounts are available starting at 100 copies, and any purchase over 10 copies	#
# comes with 1 year of free e-mail support.						#
# LogAgent (full version) is compiled with perl2exe Pro, which means that no banner is	#
# displayed at the end of execution.  Also, it can run as a background process, making  #
# it invisible to desktop users.  The codebaseis the same.				#
#########################################################################################

#########################################################################################
# Main Program										#
# This is the main structure of LogAgent.						#
# This procedure takes note of the machine credentials,	LogAgent's configuration and	#
# the list of directories and files to monitor.	 It also gets the linecount of the 	#
# files to monitor.									#
# Then, we start a thread for each entry in mondir.txt and we enter in the main loop.	#
# This loop waits for signals from the threads, and when a signal is received, it 	#
# captures the last lines of the modified log file.  These linee are then sent to the 	#
# various outputs specified in config.txt.						#
# At the end of the loop (CTRL-C) we destroy our threads and memory objects, for clean 	#
# programming purposes.									#
#########################################################################################

# Using Win32::AdvNotify 
# By Amine Moulay Ramdane 
# Website: http://www.generation.net/~aminer/Perl/ 
# 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 INFINITE Yes No 
                        All %ActionName %ActionColor);

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

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

# Filelocking of the config files
lockconfig();

# Creation of machine ID table
@id = getid();

# Creation of config table
my @config = getconfig();

# Creation of file table
my @filelist = getfilelist();

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

# Creation of mondir table
my @mondir = getmondir();

# 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 "Log Agent 2.1, brought to you by Floydman\n";
print "Copyright 2002 SecurIT Informatique Inc.\n";
print "http://securit.iquebec.com\n";
print "\nPress CTRL-C to quit\n";

# Enters the main monitoring loop
startmonitoringloop();

# Unlocking of the config files
unlockconfig();

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

# destruction of the object
undef $obj;

# End of program#

#########################################################################################
# procedure lockconfig()								#
# This procedure locks the config files to prevent tampering while LogAgent runs.	#
#########################################################################################
sub lockconfig
{
open(CONFIGFILE," || die "Can't read  logip from config.txt";
($logip=~m/LOGIP/i) || die "LOGIP entry missing in config.txt";

$loghost =  || die "Can't read loghost from config.txt";
($loghost=~m/LOGHOST/i) || die "LOGHOST entry missing in config.txt";

$loguser =  || die "Can't read loguser from config.txt";
($loguser=~m/LOGUSER/i) || die "LOGUSER entry missing in config.txt";

$showconsole =  || die "Can't showconsole read from config.txt";
($showconsole=~m/SHOWCONSOLE/i) || die "SHOWCONSOLE entry missing in config.txt";

while (defined($dir = ))
	{
	 $dirtable[$j]=$dir;
	 $j++;
	}
($j==0) && die "No destination directory specifed in config.txt.";


@configtable = ($logip, $loghost, $loguser, $showconsole, @dirtable);
@configtable = parse(@configtable);

(($numarg=@configtable)<5) && die "Not enough parameters in config.txt.  Check file for errors.";

# Tranformation of the first 4 lines of configtable to boolean value
$configtable[0]=$configtable[0]=~m/Y/i;
$configtable[1]=$configtable[1]=~m/Y/i;
$configtable[2]=$configtable[2]=~m/Y/i;
$configtable[3]=$configtable[3]=~m/Y/i;

return (@configtable);
}

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

sub getfilelist
{
my @filetable;
my $k = 0;
@temptable;

while (defined($file = )) 
	{
	 $temptable[$k]=$file;
	 $k++;
	}
($k==0) && die "No file to monitor in mondir.txt.";


@temptable = parse(@temptable);
$k=0;
foreach $file (@temptable) 
{ if ($file=~m/.*\w+\.\w{0,3}/) {$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;
$z = 0;

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

return (@linecount);
}

#########################################################################################
# procedure getmondir() 								#
# This procedure gets the list of directories to watch in mondir.txt.			#
#########################################################################################

sub getmondir
{
my @dirtable =(), temptable;
my $w = 0;

sysseek MONDIRFILE,0,0;
while (defined($dir = )) 
	{
	 $temptable[$w]=$dir;
	 $w++;
	}
($w==0) && die "No directory to monitor in mondir.txt.";


@temptable = parse(@temptable);

$w=0;
foreach $entry (@temptable)
 { 
  $entry = dirname($entry)."/";
TAG:  for ($t=0; $t <= $w; $t++)
    {
       if ($entry eq $dirtable[$t]) { last TAG;}
       if ($t >= $w) { $dirtable[$w] = $entry; $w++; last TAG;}
    }
 }
return (@dirtable);
}


#########################################################################################
# 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) = @_;

#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
{

while($threads[0]->Wait(INFINITE))# exit with [Ctrl-C] signal 
  {
   while($threads[0]->Read(\@data))# exit when the list is empty
    {
     for($i=0;$i<=$#data;$i++)
      {
       getlastline($data[$i]);
      }
    }
  }
}

#########################################################################################
# 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) = @_;
$y = 0;

open (LOGFILE, $data->{Directory}.$data->{FileName}) or die "Can't open log file ".$data->{Directory}.$data->{FileName};
flock (LOGFILE, 1) or die "Can't lock file";
@lines = ;
close (LOGFILE) or die "Can't close file"; # To unlock the file as fast as possible for new entries

@lines = parse(@lines);

COUNTER: foreach $file (@filelist) 
         {  
            if ($file ne $data->{Directory}.$data->{FileName}) {$y++;}
            if ($file eq $data->{Directory}.$data->{FileName}) {last COUNTER;}
         }
if ($filelist[$y] eq '') {$filelist[$y] = $data->{Directory}.$data->{FileName}}
if ($linecount[$y] > @lines) { $linecount[$y]=0; }

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

#########################################################################################
# procedure sendoutput(line, config)							#
# This procedure receives as arguments: the name of the modified file, the last line	#
# of the logfile, and then the config table (LOGIP, LOGHOST, LOGUSER, SHOWCONSOLE, and 	#
# the various destination directories).  The procedure checks the configuration to see	#
# if it has to append any information to the original line or not.  If SHOWCONSOLE in 	#
# enabled, then the line is printed on the screen, if not it simply passes to the next	#
# step which is to forward this line to all mentionned destinations in config.txt.	#
#########################################################################################

sub sendoutput
{ my ($filename, $line, $logip, $loghost, $loguser, $showconsole, @dest) = @_;

my $newline="";

if ($logip) {$newline=$newline.$id[0]." ";}
if ($loghost) {$newline=$newline.$id[1]." ";}
if ($loguser) {$newline=$newline.$id[2]." ";}

$newline=$newline.$line;

if ($showconsole) {print "$newline\n";}

foreach $destdir (@dest)
   {
    $destination=$destdir.$filename;
    open (DEST, ">>".$destination) || die "Can't open master log file $destination";
    flock (DEST, 2) || die "Can't lock file for writing";
    print DEST "$newline\n" || die "Can't write to file";
    close (DEST) || die "Can't close master log file";
   }
}

#EOF





5. To install
7. Sample config.txt

Table of contents
Hosted by www.Geocities.ws

1