#  DumpAccount.pl
#  Example 2.2:
#  ----------------------------------------
#  From "Win32 Perl Scripting: Administrators Handbook" by Dave Roth
#  Published by New Riders Publishing.
#  ISBN # 1-57870-215-1
#
print "From the book 'Win32 Perl Scripting: The Administrator's Handbook' by Dave Roth\n\n";


use Win32;
use Getopt::Long;
use Win32::NetAdmin;
use Win32::AdminMisc;
use Win32::API::Prototype;

%FLAGS = (
    eval UF_TEMP_DUPLICATE_ACCOUNT      =>  "UF_TEMP_DUPLICATE_ACCOUNT",
    eval UF_NORMAL_ACCOUNT              =>  "UF_NORMAL_ACCOUNT",
    eval UF_INTERDOMAIN_TRUST_ACCOUNT   =>  "UF_INTERDOMAIN_TRUST_ACCOUNT",
    eval UF_WORKSTATION_TRUST_ACCOUNT   =>  "UF_WORKSTATION_TRUST_ACCOUNT",
    eval UF_SERVER_TRUST_ACCOUNT        =>  "UF_SERVER_TRUST_ACCOUNT",
    eval UF_DONT_EXPIRE_PASSWD          =>  "UF_DONT_EXPIRE_PASSWD",
    eval UF_SETTABLE_BITS               =>  "UF_SETTABLE_BITS",
    eval UF_SCRIPT                      =>  "UF_SCRIPT",
    eval UF_ACCOUNTDISABLE              =>  "UF_ACCOUNTDISABLE",
    eval UF_HOMEDIR_REQUIRED            =>  "UF_HOMEDIR_REQUIRED",
    eval UF_LOCKOUT                     =>  "UF_LOCKOUT",
    eval UF_PASSWD_NOTREQD              =>  "UF_PASSWD_NOTREQD",
    eval UF_PASSWD_CANT_CHANGE          =>  "UF_PASSWD_CANT_CHANGE",
);

%AUTH_FLAGS = (
    eval AF_OP_PRINT                    =>  "AF_OP_PRINT",
    eval AF_OP_COMM                     =>  "AF_OP_COMM",
    eval AF_OP_SERVER                   =>  "AF_OP_SERVER",
    eval AF_OP_ACCOUNTS                 =>  "AF_OP_ACCOUNTS",
);

%PRIVILEGES = (    
    eval USER_PRIV_GUEST                =>  "USER_PRIV_GUEST",
    eval USER_PRIV_USER                 =>  "USER_PRIV_USER",
    eval USER_PRIV_ADMIN                =>  "USER_PRIV_ADMIN",
);                

@WEEKDAYS = qw(
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
);
                
# Vicious hack to get the time zone
if( ApiLink( 'kernel32.dll', 
             'DWORD GetTimeZoneInformation( PVOID pTZInfo )' ) )
{
   my $pTZInfo = NewString( 200 );
   my $Result = GetTimeZoneInformation( $pTZInfo );
   if( 0 == $Result || 2 == $Result )
   {
      $TZOffset = unpack( "l", $pTZInfo ) / 60;
   }
}

Configure( \%Config );
if( $Config{help} )
{
    Syntax();
    exit();
}

if( "" ne $Config{domain} )
{
    # Find the primary domain controller for the specified domain.
    $Config{machine} = "";
    Win32::NetAdmin::GetDomainController( '', 
                                          $Config{domain}, 
                                          $Config{machine} );
}
elsif( "" eq $Config{machine} )
{
    # Find the primary domain controller for the current domain.
    $Config{machine} = "";
    Win32::NetAdmin::GetDomainController( '', 
                                          Win32::DomainName(), 
                                          $Config{machine} );    
}

# Expand any user account wildcards
foreach my $Account ( @{$Config{accounts}} )
{
    if( $Account =~ /\*$/ )
    {
        my( $Prefix ) = ( $Account =~ /^(.*)\*$/ );
        my @Users;
        Win32::AdminMisc::GetUsers( $Config{machine}, $Prefix, \@Users );
        push( @AccountList, @Users );
    }
    else
    {
        push( @AccountList, $Account );
    }
}

$~ = Attributes;
foreach my $Account ( sort( @AccountList ) )
{
    my %Info;
    print "Displaying $Config{machine}\\$Account:\n";
    if( Win32::AdminMisc::UserGetMiscAttributes( $Config{machine},
                                                 $Account, 
                                                 \%Info ) )
    {
        foreach $Key ( sort( keys( %Info ) ) )
        {
            $Value = $Info{$Key};
            ( $DisplayKey ) = ( ( lc $Key ) =~ /^.*?_(.*)/i );
            $DisplayKey =~ s/_/ /g;
            if( $Key =~ /_age/i )
            {
                # Break apart the C timestamp into years, days, 
                # hours and minutes
                # There are 31536000 seconds in a year...
                # There are 86400 seconds in a day...
                # There are 3600 seconds in an hour...
                # And of course 60 seconds in a minute.
                my $Time = $Info{$Key};
                my $Years   = int( $Time / 31536000 );
                my $Days    = int( ( $Time % 31536000 ) / 86400 );
                my $Hours   = int( ( ( $Time % 31536000 ) % 86400 ) / 3600 );
                my $Minutes = int( ( ( ( $Time % 31536000 ) % 86400 ) % 3600 ) / 60 );
                $Value = sprintf( "%01d Years %02d Days %02d Hours %02d Minutes", 
                                  $Years, $Days, $Hours, $Minutes );
            }
            elsif( $Key =~ /LAST/i || $Key =~ /ACCT_EXPIRES/i )
            {
                if( 1 > $Value )
                {
                    $Value = "No date specified";
                }
                else
                {    
                    $Value = scalar localtime( $Info{$Key} );
                }    
            }
            elsif( $Key =~ /AUTH_FLAGS/i )
            {
                # Break apart the flags attribute into individual flag strings
                $Value = DecodeBits( $Value, \%AUTH_FLAGS );      
            }
            elsif( $Key =~ /FLAGS/i )
            {
                # Break apart the flags attribute into individual flag strings
                $Value = DecodeBits( $Value, \%FLAGS );
            }
            elsif( $Key =~ /PASSWORD$/i )
            {
                $Value = "<Unknown>";
            }
            elsif( $Key =~ /PRIV$/i )
            {
                $Value = $PRIVILEGES{$Value};
            }
            elsif( $Key =~ /MAX_STORAGE$/i )
            {
                if( -1 == $Value )
                {
                    $Value = "Unlimited storage";
                }
                else
                {
                    # Format the number to include commas as in 123,456,789
                    while( $Value =~ s/^(-?\d+)(\d{3})/$1,$2/ ){};
                    $Value .= " bytes";
                }
            }
            elsif( $Key =~ /LOGON_HOURS/i )
            {
                # Determine which hours on what days of the week this user has
                # permission to logon
                my $DayIndex = 0;
                my $BinaryHours = $Value;
                my $HourString = unpack( "b*", $BinaryHours );
                $Value = "";                
                
                # We need to offset the logon hours string by the number
                # of hours we are either before or after GMT.
                if( 0 > $TZOffset )
                {
                   $TZOffset *= -1;
                   $HourString =~ s/^(.*)(.{$TZOffset})$/$2$1/;
                }
                else
                {
                   $HourString =~ s/^(.{$TZOffset})(.*)$/$2$1/;
                }

                # Now walk through each day and determine what
                # hours the user is allowed to log on.
                foreach my $Day ( $HourString =~ /.{24}/g )
                {
                    my @Hours;
                    my $Display = "";
                    my $Index = 0;
                    
                    $Day .= "0";
                    foreach my $HourValue ( split( "", $Day ) )
                    {
                        if( $HourValue )
                        {
                            if( "" eq $Display )
                            {
                                $Display = sprintf( "%02d", $Index );
                            }
                        }
                        else
                        {
                            if( "" ne $Display )
                            {
                                if( $Display < $Index - 1 )
                                {
                                    $Display .= sprintf( "-%02d", $Index - 1 );
                                }    
                                push( @Hours, $Display );
                                $Display = "";
                            }
                        }
                        $Index++;
                    }
                    $Value .= sprintf( "%- 50s", "$WEEKDAYS[$DayIndex++]: " 
                              . join( ",", @Hours ) ), "\n";
                }
            }
            write;           
        }
    }
    else
    {
        print "failure: ";
        print Win32::FormatMessage( Win32::AdminMisc::GetError() );
    }
    print "\n";
}

sub DecodeBits
{
    my( $Bits, $FlagList ) = @_;
    my $Result = "";
    
    foreach my $Flag ( keys( %{$FlagList} ) )
    {
        # Add spaces so that the format breaks lines (formats will not
        # break lines on carriage returns)
        $Result .= $FlagList->{$Flag} . " " x 40 . "\n" if( $Bits & $Flag );
    }
    return( $Result );
}
    
sub Configure
{
    my( $Config ) = @_;

    Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
    $Result = GetOptions( $Config, 
                            qw(
                                machine|m=s
                                domain|d=s
                                help|?
                            )
                        );

    $Config->{help} = 1 if( ! $Result );
    if( "" ne $Config->{machine} )
    {
      $Config->{machine} = "\\\\$Config->{machine}";
      $Config->{machine} =~ s/^(\\\\)+/\\\\/;
    }
    push( @{$Config->{accounts}}, @ARGV );
    $Config->{help} = 1 if( ! scalar @{$Config->{accounts}} );
}

sub Syntax
{
    my( $Script ) = ( Win32::GetLongPathName( $0 ) =~ /([^\\\/]*?)$/ );
    my( $Line ) = "-" x length( $Script );

    print <<EOT;

$Script
$Line
Displays an account's configuration.

Syntax:
    perl $Script [-d Domain | -m Machine] Account [Account2 ...]
        -m Machine..Specify a machine where the user account lives.
        -d Domain...Specify a domain where the user account lives.
        Account.....The name of the account (the userid).
                    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 Attributes = 
   @<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
   "$DisplayKey:",   $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
~                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                     $Value
.
