# logshares 1.0
# Module to check for invalid users
#########################################################################################
# logshares 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 shares.txt as input, and will match it against the 	#
# open shares on the machine in order to detect rogue shares.				#
#########################################################################################

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

print "LogShares 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(SHARESFILE,"<shares.txt") || die "Can't open shares.txt";
@allowedshares = <SHARESFILE>;
@allowedshares = parse (@allowedshares);
close (SHARESFILE) || die "Can't close shares.txt";

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

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

reportshares();

#########################################################################################
# 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 reportshares()								#
# This procedure queries the opend shares and report it to shares.log.			#
#########################################################################################
sub reportshares
{
my $server = Win32::NodeName;
my @shares;
my @enumshare;
my $message;
if (Win32::Lanman::NetShareEnum("\\\\$server",\@shares))
	{
	 foreach my $share (@shares)
		{
		 push(@enumshare,{name => ${$share}{'netname'},path => ${$share}{'path'},
		                  remark => ${$share}{'remark'},max => ${$share}{'max_uses'},
		                  current => ${$share}{'current_uses'}});
		}
	
	 foreach my $share (sort ({lc $a->{name} cmp lc $b->{name}}@enumshare))
		{
		 my @perms = getPerms("\\\\$server\\".$share->{name});
		 if (@perms) {
				my $elements = @perms;
				my ($group,$permlist) = (split(/:/,$perms[0]))[0,2];
				$message = $share->{name}.",".$group.",".$permlist.",".$share->{path}.",".$share->{remark};
				$message = "Non-allowed share detected,".$message;
				analyzeshares ($message);
				if ($elements > 1) 
				  {
				   foreach my $tag (1..$elements-1) 
					{
					 my ($group,$permlist) = (split(/:/,$perms[$tag]))[0,2];
				$message = $share->{name}.",".$group.",".$permlist.",".$share->{path}.",".$share->{remark};
				$message = "Non-allowed share detected,".$message;
				analyzeshares ($message);
					}
				   }

				if ($share->{path} =~ m/^\w:\\/) {
					my $file = "\\\\$server\\".$share->{name}."\\autorun.inf";
					if (-e $file) {
						$message = $file.",".$group.",".$permlist.",".$share->{path}.",".$share->{remark};
						$message = "Warning:  Autorun.inf file detected on drive root $share->{name}!!,".$message;
				analyzeshares ($message);
#						print "Warning:  Autorun.inf file detected on drive root $share->{name}!!\n";
					}
				}


			    }
		else {
			my $err = Win32::FormatMessage Win32::Lanman::GetLastError();
			$err = Win32::Lanman::GetLastError() if ($err eq "");
			$err = "Server service not enabled, no shares available" if ($err == 2114);
			print "NetShareEnum error: ".$err."\n";	
			}
		}
	}
}

#########################################################################################
# procedure getPerms()									#
# This procedure queries the permissions set on the object recevied as arg.		#
# Used by reportshares ();								#
#########################################################################################
sub getPerms 
{
  my($obj) = $_[0];
  my($Acct,@List,$iTotal,$p);
  my(@perms) = ();
  my($Perm) = new Win32::Perms($obj);
  
  my (%PERM) = (R   =>  0,
  							W   =>  1,
  							X   =>  2,
  							D   =>  3,
   							P   =>  4,
   							O   =>  5,
   							A   =>  6,);

   my(%MAP) = (	'FILE_READ_DATA'    =>  'R',
  							'GENERIC_READ'      =>  'R',
  							'KEY_READ'          =>  'R',
  							'DIR_READ'          =>  'R',
  							'KEY_QUERY_VALUE'   =>  'R',
  							
  							'FILE_WRITE_DATA'   =>  'W',
  							'KEY_WRITE'         =>  'W',
  							'KEY_SET_VALUE'     =>  'W',
  							'GENERIC_WRITE'     =>  'W',
  							'FILE_APPEND_DATA'  =>  'W',
  							'DIR_ADD_SUBDIR'    =>  'W',
  							'DIR_ADD_FILE'      =>  'W',
  
  							'DELETE'            =>  'D',
  							'FILE_DELETE_CHILD' =>  'D',
  
  							'FILE_EXECUTE'      =>  'X',
  							'FILE_TRAVERSE'     =>  'X',
  							'GENERIC_EXECUTE'   =>  'X',
  							'DIR_TRAVERSE'      =>  'X',
  							'DIR_EXECUTE'       =>  'X',
  
  							'CHANGE_PERMISSION' =>  'P',
  
  							'TAKE_OWNERSHIP'    =>  'O',
  
  							'FILE_ALL_ACCESS'   =>  'A',
  							'GENERIC_ALL'       =>  'A',
  							'DIR_ALL_ACCESS'    =>  'A',
  							'STANDARD_RIGHTS_ALL' => 'A');

   if(!$Perm) {	return undef; }

   $Perm->Dump(\@List);
    
   foreach $Acct (@List) 
    {
     my($Perm);
     my(@String) = split(//, "-" x scalar(keys(%PERM)));
     my($Mask,@M,@F);
     my($DaclType);
     
     next if($Acct->{Entry} ne "DACL");
     $iTotal++;

     DecodeMask($Acct,\@M,\@F);
     foreach $Mask (@M) {
       $Perm |= 2**$PERM{$MAP{$Mask}};}

     foreach $Mask (keys(%PERM)) { $String[$PERM{$Mask}] = $Mask if ($Perm & 2**$PERM{$Mask}) ; }

     $DaclType = $Acct->{ObjectName};
     if( 2 == $Acct->{ObjectType} ) 
      {
# We have either a file or directory. Therefore we need to figure out if this 
# DACL represents an object (file) or a container (dir)...
       ($Acct->{Flag} & DIR) ? ($DaclType = "Directory") : ($DaclType = "File");
      }

     $p = join('',@String);
     push(@perms,"$Acct->{Account}:$DaclType:$p");
    }
    
  if(!$iTotal) { push(@perms,"Everyone::------A");}
    
  return @perms;
} 

#########################################################################################
# procedure analyzeshares								#
# This procedure analyzes the output from shares.log.					#
#########################################################################################

sub analyzeshares
{ my @message = @_;
  my $OK = 0;
my $message = join ('', @message);

my @share = split (/,/,$message);

  foreach my $line (@allowedshares)
	{
	 if ($share[1] eq $line) {$OK = 1;}
	}  
  if (!$OK) 
	{ sendoutput ('shares.log',$message,@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=[];
				   } 
         }
  
 }
}

