#!/usr/local/bin/perl
# 
# $Header: esaWin32Util.pl 06-jan-2007.08:05:10 dsukhwal Exp $
#
# esaWin32Util.pl
# 
# Copyright (c) 2005, 2007, Oracle. All rights reserved.  
#
#    NAME
#      esaWin32Util.pl - <one-line expansion of the name>
#
#    DESCRIPTION
#      This module is to expose the APIs printACLs, fileType, win32_file_perm and win32_file_owner
#
#    NOTES
#      <other useful comments, qualifications, etc.>
#
#    MODIFIED   (MM/DD/YY)
#    dsukhwal    01/06/07 - XbranchMerge manosing_ntpol from main
#    manosing    12/13/06 - modify GetVolumeInformation call 
#    sejain      09/20/06 - bug-5362371 : fixing issue with volume-name length
#    rrawat      04/26/06 - Bug-5158358
#    dsukhwal    01/12/06 - use TieRegistry 
#    dsukhwal    12/13/05 - add trailing slash to driveletter for 
#                           filesystemtype 
#    dsukhwal    11/16/05 - Check NTFS filesystem 
#    dsukhwal    08/23/05 - fix windows errors 
#    dsukhwal    06/17/05 - dsukhwal_win32_policies_3
#    dsukhwal    05/01/05 - Creation
# 


use strict;
if(!($^O eq "MSWin32")){
	exit(0);
}

use Win32::FileSecurity qw(Get EnumerateRights);
use Win32API::File qw( :ALL ); 
use Win32::NetAdmin qw(LocalGroupGetMembersWithDomain);
use Win32::TieRegistry ;

my $oldDirCmd = $ENV{'DIRCMD'}; #to be restored later
$ENV{'DIRCMD'} = "";


sub fileType{ #first argument is the file whose type you want to know.
#returns "file" if it is not a directory, and "directory" if it is a directory
#returns "error" if it does not exist, or there is some other error
#Would work in UNIX like systems as well
	my $fileName = shift;
	if(opendir(FH, $fileName)){
		close FH;
		return "directory";
	}
	else{
		if(open(FH, $fileName)){
			close FH;
			return "file";
		}
		else{
			return "error";
		}
	}
}

sub win32_file_owner{#first argument is the file whose owner you want to know
	my $fileName = shift;
	my $owner = "";
	my $i = 0;
	if(fileType($fileName) eq "error"){#some error occurred, or the file does not exist
		return -1;
	}
        # / is not an allowed character in an NTFS filename
        # substitute all / with \ to get the correct filename
        $fileName =~ s/\//\\/g;

	if(fileType($fileName) eq "file"){#it is not a directory
		my $systemCommand = "cmd /c \"dir /q $fileName \" |";
		if (!open(CHECKER, "$systemCommand"))
		{
			return -1;
		}
		my @lines = <CHECKER>;
		my $usefulLine = $lines[5];
		my @tokens = split(/\s+/, $usefulLine);
		if(@tokens == 5){#for now, it will be assumed that only the owner and filename can get stuck together.
				#as and when new cases are discovered, they will be rectified
			my $onlyName = winFileName($fileName);
			my $givenName = $tokens[4];
			$owner;
			if($givenName =~ /^(.*)$onlyName$/i){
				$owner = $1;
			}
			else{
			}

		}
		elsif(@tokens == 6){
			$owner = $tokens[4];
		}
		else{
		}	
	}
	elsif(fileType($fileName) eq "directory"){#it is a directory
		my $onlyName = winFileName($fileName);
		my $systemCommand = qq(cmd /c "dir /q /ad $fileName " |);
		if (!open(CHECKER, "$systemCommand"))
		{
			return -1;
		}
		$systemCommand =~ s/\|/\!/g;
		my @lines = <CHECKER>; #contains the lines generated by dir
		shift (@lines);#assuming that 5 lines are header, and two are tail
		shift (@lines);
		shift (@lines);
		shift (@lines);
		shift (@lines);
		pop(@lines);
		pop(@lines);
		#assuming that . will be the first entry in the directory listing
		$_ = shift(@lines);
		if(m/(.*)(\s+)(\S+)(\s*)\.$/){
			s/(.*)(\s+)(\S+)(\s*)\.$/$3/;
			$owner = $_;
		}
		else{#the dir command gave some UNIMAginable output. Giving up.
			return -1;
		}
	}
    chomp $owner;
	return $owner;
}

sub winFileName{#first arguments is the file path
#returns the last part of the filename(after removal of the containing directory)
	$_ = shift;
	s/^(.*)\\(.*)$/$2/;
	return $_;
}

sub printACLs{
#first argument is the filename
#second argument is the property
#third(optional) argument is the maximum number of rows to print(defaults to 200)
#returns the number of rows it printed

	my $fileName = shift;
	my $prop = shift;
	my $maxCount = shift;#will not print more than $maxCount rows, no matter what
    if(!defined($maxCount)){
        $maxCount=200;
    }
	my ($res, $numRows) = getACLs($fileName, $maxCount);
	if($res == -1){
		return -1;
	}
	my %hash = %$res;
	my $user;my $right;
	foreach $user (keys %hash){
		my $arrRef = $hash{$user};
		my @rightsArray = @$arrRef;
		foreach $right (@rightsArray){
			my $translatedRight = translate($right);
			print "em_result=$prop|$fileName|$user:$translatedRight\n";
		}
	}
    return $numRows;
}

sub win32_file_perm{
#first argument is the filename
#returns the string made up of the number of users having critical permissions on the file, followed by the 
#user names(upto a maximum of 512 characters)
    my $fileName = shift;
	my ($res, $numRows) = getACLs($fileName, 1000);#1000 is the maximum number of rows getACLs will return. 
                            #This is to prevent overload on the agent
	if($res == -1){
		return -1;
	}
    my @usersWithCritRights;
    my %hash = %$res;
	my $user;
	foreach $user (keys %hash){
		my $arrRef = $hash{$user};
		my @rightsArray = @$arrRef;
		foreach (@rightsArray){
            if( (/DELETE/i) || (/WRITE_DAC/i) || (/WRITE_OWNER/i) || (/CHANGE/i) || (/ADD/i) || (/FULL/i) ){
                push(@usersWithCritRights, $user);
                last;
		    }
	    }
    }
    my $i=0;
    foreach (@usersWithCritRights){
        $i++;
    }
    my $concat = (scalar @usersWithCritRights)."(".join(", ",@usersWithCritRights).")";
    return $concat;
}

sub getACLs{
#first argument is the filename
#second(optional) argument is the maximum number of entries to return in the hash(defaults to 200)
#return -1 if there is any error in getting the ACLs
	my $fileName = shift;
	my $maxCount = shift;#will not return more than $maxCount rows, no matter what
    if(!defined($maxCount)){
        $maxCount=200;
    }
	my $count = 0;
	my $owner = win32_file_owner($fileName);
	if($owner == -1){
		return -1;
	}
    my $driveLetter = $fileName;
    if (!($driveLetter =~ m/(.*):(.*)/)){
        return -1; #Windows NT filenames without colons are not supported
    }
    $driveLetter  =~ s/(.*?)(:)(.*)/$1$2/;
    $driveLetter = $driveLetter."/";#sometimes, GetVolumeInformation prefers the trailing slash
    my $osVolName; my $ouSerialNum; my $ouMaxNameLen; my $ouFsFlags; my $osFsType;
#Removing the third arument to GetVolumeInformation as it is causing issue if volume-name is more than 8 chars        
    GetVolumeInformation( $driveLetter, $osVolName,'', $ouSerialNum, $ouMaxNameLen, $ouFsFlags, $osFsType, 8 );
    if (!($osFsType =~ 'NTFS')){
        return -1;#the filesystem is not NTFS, forget about ACLs
    }
    
	my %hash;
	my $name;my $mask;my $entry;
	my %resultHash;

	if ( Get( $fileName, \%hash ) ) {
		while( ($name, $mask) = each %hash ) {
			#check whether $name is the owner of $filename
			if(lc($name) eq lc($owner)){
				next;
			}
			else{
				my @rights;
				EnumerateRights($mask, \@rights );
                $count+=@rights;
                if($count < $maxCount){
    				$resultHash{$name} = \@rights;
                    next;
                }
                else{
                    splice(@rights, $maxCount - $count + @rights);
    				$resultHash{$name} = \@rights;
                    last;
                }
			}
		}
	}
	return (\%resultHash, $count);
}

sub translate{
#first argument is a windows privilege name. Returns the name in a more readable format
	$_ = shift;
	$_ = uc($_);
	if(/STANDARD_RIGHTS_READ/i)			{return "Standard Rights Read";}
		elsif(/DELETE/i)						{return "Delete";}
		elsif(/READ_CONTROL/i) 				{return "Read File Attribute";}
		elsif(/WRITE_DAC/i)					{return "Change DACL";}
		elsif(/WRITE_OWNER/i) 					{return "Take Ownership";}
		elsif(/SYNCHRONIZE/i) 					{return "Synchronize";}
		elsif(/STANDARD_RIGHTS_REQUIRED/i) 	{return "Standard Rights Required";}
		elsif(/STANDARD_RIGHTS_WRITE/i)		{return "Standard Rights Write";}
		elsif(/STANDARD_RIGHTS_EXECUTE/i) 		{return "Standard Rights Execute";}
		elsif(/STANDARD_RIGHTS_ALL/i) 			{return "Standard Rights All";}
        elsif(/GENERIC_READ/i)                  {return "Generic Read";}
        elsif(/GENERIC_EXECUTE/i)                  {return "Generic Execute";}
		elsif(/READ/i) 						{return "Read";}
		elsif(/CHANGE/i) 						{return "Change";}
		elsif(/ADD/i) 							{return "Add";}
		elsif(/FULL/i) 						{return "Full";}
		else 								{return $_;}
}


sub win32_comma_sep_files_perm{#the subroutine comma_separated_files should suffice for file owner, as the file_owner 
#subroutine can be used with linux as well as MSWin32
#first argument is the comma separated files list
#second argument is the maximum number of rows that are to be printed(if -1, no limit on the number of rows)
#third argument is the property.
#the subroutine prints the permissions of the files
    my $commFilesList = shift;
    my $maxCount = shift;
    my $property = shift;
    my $fileName;
    $commFilesList =~ s/\s+//g;
    my @fileList = split(/,/, $commFilesList);
    if( defined($maxCount) && ($maxCount >= 0) ){
        splice(@fileList, $maxCount);
    }#keep at most  maxCount entries
    foreach $fileName (@fileList){
        my $perm = check_512char(win32_file_perm($fileName));
        if($perm != -1){
            print "em_result=$property|$perm|$fileName\n";
        }
    }
}

$ENV{'DIRCMD'} = $oldDirCmd; 

1;
