#  priv.pl
#  Example 2.9:
#  ----------------------------------------
#  From "Win32 Perl Scripting: Administrators Handbook" by Dave Roth
#  Published by New Riders Publishing.
#  ISBN # 1-57870-215-1
#
#  This script manages user privileges.
#
#
print "From the book 'Win32 Perl Scripting: The Administrator's Handbook' by Dave Roth\n\n";


use Win32::API;
use Win32::AdminMisc;
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( "" ne $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 =~ /\*$/ )
    {
      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}
~                                  ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                   $Priv{comment}
. 
