# logservices 1.0
# Module to check for invalid users
#########################################################################################
# logservices 1.0									#
# by Floydman  floydian_99@yahoo.com							#
# Based on previous work from Harlan Carvey						#
# Copyright 2003 SecurIT Informatique Inc.  http://securit.iquebec.com			#
# This program will take the file services.txt as input, and will match it against the 	#
# running services on the machine in order to detect rogue services.			#
#########################################################################################

use Socket;
use Sys::Hostname;
use Win32::Lanman;

print "LogServices 1.0, brought to you by Floydman\nBased on previous work from Harlan Carvey\n";
print "Copyright 2003 SecurIT Informatique Inc.\n";
print "http://securit.iquebec.com\n";

open(SERVICESFILE,"<services.txt") || die "Can't open services.txt";
@allowedservices = <SERVICESFILE>;
@allowedservices = parse (@allowedservices);
close (SERVICESFILE) || die "Can't close services.txt";

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

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

reportservices();

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

sub getconfig
{
my @configtable;
my @dirtable;
$j = 0;
$numarg = 0;
open(CONFIGFILE,"<config.txt") || die "Can't open config.txt";
$logip = <CONFIGFILE> || die "Can't read  logip from config.txt";
($logip=~m/LOGIP/i) || die "LOGIP entry missing in config.txt";

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

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

$logdate = <CONFIGFILE> || die "Can't read logdate from config.txt";
($logdate=~m/LOGDATE/i) || die "LOGDATE entry missing in config.txt";

$logtime = <CONFIGFILE> || die "Can't read logtime from config.txt";
($logtime=~m/LOGTIME/i) || die "LOGTIME entry missing in config.txt";

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

$lp = <CONFIGFILE> || die "Can't read lineprint from config.txt";
($lp=~m/LINEPRINT/i) || die "LINEPRINT entry missing in config.txt";

$lpbuffer = <CONFIGFILE> || die "Can't read lineprint buffer from config.txt";
($lpbuffer=~m/LINEPRINT BUFFER/i) || die "LINEPRINT BUFFER entry missing in config.txt";

while (defined($dir = <CONFIGFILE>))
	{
	 $dirtable[$j]=$dir;
	 $j++;
	}
($j==0) && die "No destination directory specifed in config.txt.";
close (CONFIGFILE) || die "Can't close config.txt";

@configtable = ($logip, $loghost, $loguser, $logdate, $logtime, $showconsole, $lp, $lpbuffer, @dirtable);
@configtable = parse(@configtable);

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

# Tranformation of the first 7 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;
$configtable[4]=$configtable[4]=~m/Y/i;
$configtable[5]=$configtable[5]=~m/Y/i;
$configtable[6]=$configtable[6]=~m/Y/i;

# Get the lineprint buffer
@element=split(/=/, $configtable[7]);
$configtable[7]=$element[1]; ($configtable[7]>=0) || die "LINEPRINT BUFFER entry must be 0 or a positive number in config.txt";
return (@configtable);
}

#########################################################################################
# procedure getid() 									#
# This procedure gets the IP address, the host name and the username of the machine.	#
#########################################################################################

sub getid
{
# Define username, IP address and hostname of the local machine
my $addr = inet_ntoa(scalar(gethostbyname($name)) || 'localhost');
my $host = hostname() || "hostname not defined";
my $login = getlogin || getpwuid($<) || "not logged";
my @id_table = ($addr, $host, $login);
return (@id_table);

}

#########################################################################################
# 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 reportservices()								#
# This procedure queries the running services and report it to services.log.		#
#########################################################################################
sub reportservices
{my(@state) = ( "",
		"Stopped",
		"Start_Pending",
		"Stop_Pending",
		"Running",
		"Continue_Pending",
		"Pause_Pending",
		"Paused");
								
 my(@startup) = ("",
	         "",
	         "Automatic",
	         "Manual",
	         "Disabled");
	              
 my $server = Win32::NodeName;

 my @services;
 if (Win32::Lanman::EnumServicesStatus("\\\\$server","",&SERVICE_WIN32,&SERVICE_STATE_ALL,\@services))
	{
	 foreach my $service (@services)
	 {	my %info;
		if (Win32::Lanman::QueryServiceConfig("\\\\$server","",${$service}{name},\%info))
		{if (($state[${$service}{state}] eq 'Running') || ($startup[$info{start}] eq 'Automatic')) 
		 {
			my $message = "${$service}{display},${$service}{name},$state[${$service}{state}],";
			$message = $message."$info{account},$info{filename},$startup[$info{start}]\n";
			analyzeserv ($message);
		 }
		}
		else {
			my $err = Win32::FormatMessage Win32::Lanman::GetLastError();
			$err = Win32::Lanman::GetLastError() if ($err eq "");
			print "Error in QueryServiceConfig: $err\n";
		}
	 }
	}
 else {
	my $err = Win32::FormatMessage Win32::Lanman::GetLastError();
	$err = Win32::Lanman::GetLastError() if ($err eq "");
	print "Error in EnumServicesStatus: $err\n";
      }
}

#########################################################################################
# procedure analyzeserv									#
# This procedure analyzes the output from services.log.					#
#########################################################################################

sub analyzeserv
{ my @rec = @_;
  my $OK = 0;
  my $rec = join ('',@rec);

my @service = split (/,/,$rec);

  foreach my $line (@allowedservices)
	{
	 if (lc($service[0]) eq lc($line)) {$OK = 1;}
	}
  if (!$OK) 
	{sendoutput ('services.log',"Unknown service detected,".$rec,@config);
	}
}


#########################################################################################
# procedure sendoutput(filename, 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, $lines, $logip, $loghost, $loguser, $logdate, $logtime, $showconsole, $lp, $lpbuffer, @dest) = @_;

my $line = '';
my @newlines = split (/\n/,$lines);
foreach $line (@newlines)
 {
 my $newline=""; 

 if ($logip) {$newline=$newline.$id[0].",";}
 if ($loghost) {$newline=$newline.$id[1].",";}
 if ($loguser) {$newline=$newline.$id[2].",";}
 if ($logdate || $logtime) {($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);} 
 if ($logdate) {$newline=$newline.($year+=1900)."/".($mon+1)."/".$mday.",";}
 if ($logtime) {$newline=$newline.$hour.":".$min.":".$sec.",";}

$newline=$newline.$line."\n";

  if ($showconsole) {print $newline;}

  if (lc($dest[0])ne"null") {
    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 || die "Can't write to file";
        close (DEST) || die "Can't close master log file";
       }
    }


  if ($lp) { push @buffer, $newline; $buffer=@buffer;
	   if ($buffer>=$lpbuffer+1) {
				     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=[];
				   } 
         }
  
 }
}

