#  priv.pl
#  Example :
#  ----------------------------------------
#  From "Win32 Perl Programming: Administrators Handbook" by Dave Roth
#  Published by Macmillan Technical Publishing.
#  ISBN # 1-57870-215-1
#

use Win32::API;
use Win32::Lanman;

@PRIVILEGES = qw(
    SE_CREATE_TOKEN_NAME
    SE_ASSIGNPRIMARYTOKEN_NAME
    SE_LOCK_MEMORY_NAME
    SE_INCREASE_QUOTA_NAME
    SE_UNSOLICITED_INPUT_NAME
    SE_MACHINE_ACCOUNT_NAME
    SE_TCB_NAME
    SE_SECURITY_NAME
    SE_TAKE_OWNERSHIP_NAME
    SE_LOAD_DRIVER_NAME
    SE_SYSTEM_PROFILE_NAME
    SE_SYSTEMTIME_NAME
    SE_PROF_SINGLE_PROCESS_NAME
    SE_INC_BASE_PRIORITY_NAME
    SE_CREATE_PAGEFILE_NAME
    SE_CREATE_PERMANENT_NAME
    SE_BACKUP_NAME
    SE_RESTORE_NAME
    SE_SHUTDOWN_NAME
    SE_DEBUG_NAME
    SE_AUDIT_NAME
    SE_SYSTEM_ENVIRONMENT_NAME
    SE_CHANGE_NOTIFY_NAME
    SE_REMOTE_SHUTDOWN_NAME
    SE_INTERACTIVE_LOGON_NAME
    SE_NETWORK_LOGON_NAME
    SE_BATCH_LOGON_NAME
    SE_SERVICE_LOGON_NAME
);

$LPDN = new Win32::API( 'advapi32.dll', 'LookupPrivilegeDisplayName', [P,P,P,P,P], I ) || die "Unable to locate the LookupPrivilegeDisplayName().\n";
foreach my $Privilege ( @PRIVILEGES )
{
    my $Size = 256;
    my $szDisplayName = "\x00" x $Size;
    my $dwSize = pack( "L", $Size );
    my $dwLangId = pack( "L", 0 );
    my $PrivString = eval "$Privilege";
    $LPDN->Call( $Config{machine}, $PrivString, $szDisplayName, $dwSize, $dwLangId );
    $szDisplayName =~ s/\x00//g;
    $PRIVILEGES{$Privilege} = {
        comment =>  $szDisplayName,
        display =>  $PrivString,
        name    =>  $Privilege,
    };
    $PRIVILEGE_VALUES{uc $PrivString} = $Privilege;
}

Configure( \%Config, @ARGV );
if( $Config{help} )
{
    Syntax();
    exit( 0 );
}

if( "" eq $Config{domain} )
{
    Win32::Lanman::NetGetDCName( '', $Config{domain}, \$Config{machine} );
}
elsif( "" eq $Config{machine} )
{
    Win32::Lanman::NetGetDCName( '', Win32::DomainName(), \$Config{machine} );
}

if( $Config{display_privileges} )
{
    my @PrivList;

    # Display all privileges
    if( scalar @{$Config{items}} )
    {
        foreach my $Priv ( @{$Config{items}} )
        {
            $Priv = MatchPrivilege( $Priv ) || next;
            push( @PrivList, $Priv );
        }
    }
    else
    {
        push( @PrivList, sort( keys( %PRIVILEGES ) ) );
    }
    foreach $Key ( @PrivList )
    {
        print "$Key:\n";
        print "\tDisplay name: $PRIVILEGES{$Key}->{display}\n";
        print "\tComment: $PRIVILEGES{$Key}->{comment}\n" if( "" ne $PRIVILEGES{$Key}->{comment} );
        print "\n"; 
    }
}
elsif( $Config{user_rights} )
{
    # Display who has been enabled for a specific privilege
    foreach $Privilege ( @{$Config{items}} )
    {
        my @SidList;
        my $PrivKey = uc MatchPrivilege( $Privilege ) || next;
        
        print "$PrivKey ($PRIVILEGES{$PrivKey}->{display}):\n";
        if( Win32::Lanman::LsaEnumerateAccountsWithUserRight( $Config{machine}, $PRIVILEGES{$PrivKey}->{display}, \@SidList ) )
        {
            my @SidData;
            Win32::Lanman::LsaLookupSids( $Config{machine}, \@SidList, \@SidData );
            foreach my $Data ( @SidData )
            {
                print "\t", ( ( "" ne $Data->{domain} )? "$Data->{domain}\\" : "" );
                print "$Data->{name}\n";
            }
        }
        print "\n";
    }
}
else
{
    # Display what privilege has been enabled for specific accounts
    my @AccountList;
    my @AccountInfo;
    my %TempAccountList;

    # Expand any wildcards in the user groups...
    foreach my $Account ( @{$Config{items}} )
    {
        if( $Account =~ /\*$/ )
        {
            use Win32::AdminMisc;
            my( $Prefix ) = ( $Account =~ /^(.*)\*$/ );
            my @Accounts;
            Win32::AdminMisc::GetUsers( $Config{machine}, $Prefix, \@Accounts );
            map
            {
                $TempAccountList{lc $_} = $_;
            } @Accounts;
        }
        else
        {
            $TempAccountList{uc $Account} = $Account;
        }
    }
    # Create a non-duplicate list of user accounts from the temp hash
    foreach my $Key ( sort( keys( %TempAccountList ) ) )
    {
        push( @AccountList, $TempAccountList{$Key} );
    }

    if( scalar @{$Config{add_privileges}} 
        || scalar @{$Config{remove_privileges}} )
    {
        my @SidList;
        Win32::Lanman::LsaLookupNames( $Config{machine}, \@AccountList, \@SidList );
        foreach my $Sid ( @SidList )
        {
            if( scalar @{Config{add_privileges}} )
            {
                Win32::Lanman::LsaAddAccountRights( $Config{machine}, $Sid->{sid}, $Config{add_privileges} );
            }
            if( scalar @{Config{remove_privileges}} )
            {
                Win32::Lanman::LsaRemoveAccountRights( $Config{machine}, $Sid->{sid}, $Config{remove_privileges}, $Config{remove_all} );
            }
        }
    }
    ReportAccountPrivileges( @AccountList );
}

sub ReportAccountPrivileges
{
    my( @AccountList ) = @_;

    $~ = PrivilegeDump;
    Win32::Lanman::LsaLookupNames( $Config{machine}, \@AccountList, \@AccountInfo );
    for( $Index = 0; $Index < scalar @AccountInfo; $Index++ )
    {
        my @Rights;
        my $Account = $AccountInfo[$Index];
        $Account->{name} = $AccountList[$Index];
        print "$Account->{domain}\\" if( "" ne $Account->{domain} );
        print "$Account->{name}";

        # Check that the account exists
        if( 8 > $Account->{use} )
        {
            print ":\n";
            if( Win32::Lanman::LsaEnumerateAccountRights( $Config{machine}, $Account->{sid}, \@Rights ) )
            {
                map
                {
                    $Priv{name} = $PRIVILEGE_VALUES{uc $_};
                    $Priv{display} = $_;
                    $Priv{comment} = $PRIVILEGES{$Priv{name}}->{comment};
                    write;
                } @Rights;
            }
        }
        else
        {
            print " ... account does not exist.\n";
        }
        print "\n";
    }
    return;
}

sub MatchPrivilege
{
    my( $PrivRoot ) = uc shift @_;
    my $PrivKey = $PrivRoot;
    # Is the privilege a valid display name privilige?
    if( ! defined ( $PrivKey = $PRIVILEGE_VALUES{$PrivKey} ) )
    {   
        # Is the privilege a normal privilege?
        $PrivKey = $PrivRoot;
        if( ! defined ( $PrivKey = $PRIVILEGES{$PrivKey}->{name} ) )
        {
            # In case the user entered only the base name of the privilege
            $PrivKey = "SE_" . $PrivRoot . "_NAME";
            if( ! defined ( $PrivKey = $PRIVILEGES{uc $PrivKey}->{name} ) )
            {
                # In case the user entered only the base of the display name
                $PrivKey = "Se" . $PrivRoot . "Privilege";
                if( ! defined ( $PrivKey = $PRIVILEGE_VALUES{uc $PrivKey} ) )
                {
                    # One last chance...
                    $PrivKey = "SE" . $PrivRoot . "Right";
                    $PrivKey = $PRIVILEGE_VALUES{uc $PrivKey};
                }
            }
        }
    }
    return( $PrivKey );
}

sub Configure
{
    my( $Config, @Args ) = @_;
    while( my $Arg = shift @Args )
    {
        my( $Prefix ) = ( $Arg =~ /^([+-\/])/ );
        if( "" ne $Prefix )
        {
            $Arg =~ s#^[+-/]##;
            if( "+" eq $Prefix )
            {
                # Adding a privilege
                my $Priv = MatchPrivilege( $Arg ) || next;
                push( @{$Config->{add_privileges}}, $PRIVILEGES{$Priv}->{display} );
            }
            elsif( $Arg =~ /^p$/i )
            {
                # Request to display all rights
                $Config->{display_privileges} = 1;
            }
            elsif( $Arg =~ /^s$/i )
            {
                # Specified displaying user rights
                $Config->{user_rights} = 1;
            }
            elsif( $Arg =~ /^d$/i )
            {
                # Specify a domain to create the account
                $Config->{domain} = shift @Args;
            }
            elsif( $Arg =~ /^m$/i )
            {
                # Specify what machine the account lives on
                $Config->{machine} = "\\\\" . shift @Args;
                $Config->{machine} =~ s/^(\\\\)+/\\\\/;
            }

            elsif( $Arg =~ /^(\?|h|help)/i )
            {
                # Request help
                $Config->{help} = 1;
            }
            else
            {
                if( "/" eq $Prefix )
                {
                    # An unknown switch
                    $Config->{help} = 1;
                }
                else
                {
                    # We get here if the prefix was -
                    # and no valid flag matched the switch therefore...
                    # Removing a privilege
                    if( "*" eq $Arg )
                    {
                        $Config->{remove_all} = 1;
                        # Push * onto the the remove array. It will be ignored
                        # anyway since we will use the "remove all" flag.
                        # This way the script will see the array is not empty
                        # and attempt to remove privileges.
                        push( @{$Config->{remove_privileges}}, $Arg );
                    }
                    else
                    {
                        my $Priv = MatchPrivilege( $Arg ) || ($Config->{help} = 1);
                        push( @{$Config->{remove_privileges}}, $PRIVILEGES{$Priv}->{display} );
                    }
                }
            }
        }
        else
        {
            push( @{$Config->{items}}, $Arg );
        }
    }
    if( 0 == scalar @{$Config->{items}} && ! $Config->{display_privileges} )
    {
        $Config->{help} = 1;
    }
}

sub Syntax
{
    my( $Script ) = ( $0 =~ /([^\\\/]*?)$/ );
    my( $Line ) = "-" x length( $Script );

    print <<EOT;

$Script
$Line
Manages account privileges

Syntax:
    perl $Script [-m Machine | -d Domain] -p
    perl $Script [-m Machine | -d Domain] -s Priv [Priv2 ...]
    perl $Script [-m Machine | -d Domain] [-|+Priv] Account [Account2 ....]
        -m Machine..All accounts and privileges are resident on the specifed
                    machine.
        -d Domain...All accounts and privileges are resident in the specified
                    domain.
        -s Priv.....Show all accounts that have been granted the specified
                    privilege. Some accounts may not show if they are granted
                    the privilege through a group membership.
        -Priv.......Removes the privilege from the specified accounts.
                    Specify as many of these switches as necessary.
                    Specify * to remove ALL privileges.
        +Priv.......Adds the privilege to the specified accounts.
                    Specify as many of these switches as necessary.
        Account.....Show all privileges granted to this specified account.
                    If used in conjunction with the -Priv or +Priv then the
                    privileges are assigned or removed first. The resulting
                    privilege set is then displayed.
                    This account can end with a * char to indicate
                    all accounts that begin with the specified string.
        If no domain or machine is specified then the current domain
        is used.
EOT
}
   
format PrivilegeDump =
    @<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    $Priv{name},                   $Priv{comment}
~                                  ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  
                                   $Priv{comment}
..


