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