|
Title | Windows NT Win32 Perl Programming: The Administrator's Handbook | Authors | Dave Roth | Publisher | New Riders | Published | Oct 2000 | ISBN | 1578702151 | Price | US 35.00 | Pages | 416 |
|
Chapter 3 - Tools
Every good administrator assembles a collection of tools that she
simply cannot do without. These tools perform specific tasks that enable her to
quickly perform her job. A perfect example of such a utility is the popular XCACLS.EXE
program that comes with the Windows
NT Resource Kit.
This utility (and its little brother CACLS.EXE) dumps any
permissions that are applied to a file or directory. Without such a tool, you
are doomed to open an Explorer window, select the object in question,
right-click, select the Properties context menu, and then go to the Security
tab on the resulting property page. If this procedure doesn't sound like very
much work, you have not administered a network of thousands of machines with
various security permissions applied to various directories and files.
This chapter covers an assortment of scripts that were created for
my own toolkit. To be sure, I wrote each of these scripts to simplify and
lessen my workload as an administrator. I included these scripts only for the
sake of demonstrating how you can leverage Win32 Perl to solve problems that
administrators face every day. When you scrutinize the code, it becomes obvious
that there is almost no Win32 task that cannot be managed with a bit of Perl
coding.
These scripts were not written
to be simple or to be tutorials but instead to get into the nitty-gritty of
task solving. I recommend that you use a good Win32 API reference while looking
at the code that makes up these scripts; in particular, check out Microsoft's
MSDN library (http://msdn.microsoft.com/) and, of course, my other book, Win32 Perl Programming: The Standard
Extensions (New Riders Publishing).
The File System
The Win32 operating system makes copious use of its file system. As
an administrator, you have the burden of managing this important resource. Your
job covers the entire range of administrative tasks, ranging from securing
files and directories to creating directories and updating outdated files. Many
different tools and utilities available from various companies can help you
administer your machine's file system. However, you will not be able to easily
find some tools.
Many of the existing tools are confusing because they reflect
aspects of the file system that are not commonly known, such as alternate data
streams. Some tools are not used simply because administrators have no idea
that they exist, such as tools used to discover hidden directory shares on
remote machines. The following sections focus on file system-related tools that
can save you a great deal of work.
Discovering Shared Directories
Ever since Windows for Workgroups was released, sharing directories
for others to connect to has been quite simple. From a Windows NT/2000 command
line, you can easily share a directory by using the net share
command. The following line shares a temporary directory
with the name TempDir
:
net share TempDir=c:\temp
Alternatively, you can share directories from the File Manager, the
Explorer, and the Service Manager GUI applications.
The Win32 world permits you to hide
shared directories-that is, share a directory that nobody can see when browsing
the network. The only way to access the directory is to know it has been shared
and explicitly access the hidden share. You hide directories by appending a
dollar sign ($) to the end of the share name. For example, the following
command line creates a hidden shared directory called MyHiddenShare$
:
net share MyHiddenShare$=c:\temp
Now, only users who know about this hidden share can access it.
You can see which directories your machine is sharing by using this
command line:
net share
This line displays all the shared directories on the local machine.
However, there is no easy way to discover shared directories on a remote
machine. You can always open an Explorer window for a particular machine, but
this solution is bulky and displays only nonhidden directories.
Let's say that you need to see what shares are available on some
remote machine. Explorer won't help you in this case. You could walk over to
the machine, log on, and issue the net share command, but this method is not
very convenient, especially if the machine is across town or across the
country. And assuming that you don't have some type of Telnet daemon installed,
this problem is quite serious.
At this point, you can turn to the DumpShares.pl script (shown in
Example 3.1). The actual core of the script revolves around line 11, where a
call is made into the Win32::Lanman extension to get a list of shared
directory names.
As long as you can physically connect to the remote machine, you
can see all its shared directories, regardless of whether or not you are
granted administrative privileges on the remote machine. If you lack admin
privileges on the remote machine, the shared directory's paths are not visible.
The script takes in any number of remote machine names and tries to
resolve each passed-in machine name. If nothing is passed in, the local
machine's shared directories are displayed.
Example
3.1 Displaying All Shared Directories on a Remote
Machine
01. use Win32;
02. use Win32::Lanman;
03.
04. push( @ARGV, Win32::NodeName() ) if( ! scalar @ARGV );
05. foreach my $Machine ( @ARGV )
06. {
07. my @List;
08. $Machine =~ s#^[\\/]*#\\\\#;
09.
10. print "\nShare list for ‘$Machine'\n";
11. if( Win32::Lanman::NetShareEnum( $Machine, \@List ) )
12. {
13. foreach my $Share ( @List )
14. {
15. my( $Remark, $Path, $NetName );
16.
17. $NetName = $Share->{netname};
18. if( "" ne $Share->{remark} )
19. {
20. $Remark = "($Share->{remark})";
21. }
22. if( "" ne $Share->{path} )
23. {
24. $Path = $Share->{path};
25. }
26. else
27. {
28. $Path = "No permission to display";
29. }
30. push( @ShareList, { name => $NetName,
31. remark => $Remark,
32. path => $Path } );
33. }
34. $~ = Share_Header;
35. write;
36.
37. $~ = Share_Info;
38. $iCount = 0;
39.
40. foreach $Share ( sort( {
41. lc $a->{name} cmp lc $b->{name}
42. } @ShareList ) )
43. {
44. $iCount++;
45. write;
46. }
47. }
48. else
49. {
50. print "...not available : ";
51. print Win32::FormatMessage( Win32::Lanman::GetLastError() );
52. }
53.
54. print "\n";
55. }
56.
57. format Share_Info =
58. @>>> @<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<
59. $iCount, $Share->{name}, $Share->{remark}, $Share->{path}
60. ~ ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<
61. $Share->{remark}, $Share->{path}
62. ~ ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<
63. $Share->{remark}, $Share->{path}
64. .
65.
66. format Share_Header =
67. @||| @<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<
68. "Num", "Share Name", "Comment", "Path"
69. ---- ------------------ ----------------------- ------------------------
70. .
Volume Information
For an administrator, discovering details of hard drives on remote
machines can be essential. Determining how large the volume is, as well as how
much space is available, is an important part of drive management. Example 3.2
retrieves such necessary information and other relevant data, such as the
volume name, file system type (NTFS, FAT, FAT32, and so on), serial number, and
the volume's flags. The flags indicate what features are available for the
drive. The flags and their descriptions are listed in Table 3.1.
Table 3.1 Volume Flags
Flag
Description
FS_CASE_IS_PRESERVED | The file system
preserves the case of filenames when it places a name on disk. |
FS_CASE_SENSITIVE | The file
system supports case-sensitive file names. |
FS_UNICODE_STORED_ON_DISK | The file system supports
Unicode in filenames as they appear on disk. |
FS_PERSISTENT_ACLS | The file system
preserves and enforces Access Control Lists (ACLs-also known as permissions).
For example, NTFS preserves and enforces ACLs, but FAT does not. |
FS_FILE_COMPRESSION | The file system supports
file-based compression. |
FS_VOL_IS_COMPRESSED | The specified volume
is a compressed volume-for example, a DoubleSpace volume. |
FILE_NAMED_STREAMS | The file system
supports named streams. |
FILE_SUPPORTS_ENCRYPTION | The file system supports the
Encrypted File System (EFS). |
FILE_SUPPORTS_OBJECT_IDS | The file system supports object
identifiers. |
FILE_SUPPORTS_REPARSE_POINTS | The file system
supports reparse points. This capability allows for features such as using NTFS
links and moving infrequently accessed data to long-term storage such as tape
backup. |
FILE_SUPPORTS_SPARSE_FILES | The file system supports
sparse files. Sparse files have disk storage allocated only for the parts of
the file that contain data. Therefore, large blocks of 0s are not stored to
disk to conserve drive storage space. |
FILE_VOLUME_QUOTAS | The file system
supports space allocation limits on a per user basis. |
The script accepts a list of drive letters or UNCs like this:
perl volume.pl c:\ \\server\share
In this case, the script displays volume information for both the
local machine's C:\
drive as well as for the volume that is
shared by the UNC \\server\share
. Figure 3.1 illustrates the
output from this code. If no parameter is passed in, information regarding the
volume for the current directory is displayed.
The code uses a Win32 API function that
was introduced with Windows NT 4.0 and Windows 95 OEM Service Release 2 (OSR2).
It will most likely fail on platforms earlier than this.
C:\>perl volume.pl c:\ \\server\share\
c:\:
FS Serial Volume Name Flags
----- --------- ------------------------------ ----------------------------
NTFS DCF6-AF23 This is my test volume FS_CASE_SENSITIVE
Total Size: 4,301,788,672 FS_CASE_IS_PRESERVED
Avail Size: 726,140,928 FS_UNICODE_STORED_ON_DISK
FILE_VOLUME_QUOTAS
FS_FILE_COMPRESSION
FS_PERSISTENT_ACLS
FILE_SUPPORTS_OBJECT_IDS
FILE_SUPPORTS_SPARSE_FILES
FILE_SUPPORTS_REPARSE_POINTS
FILE_SUPPORTS_ENCRYPTION
\\server\share\:
FS Serial Volume Name Flags
----- --------- ------------------------------ ----------------------------
NTFS 8063-FA56 New Volume FS_CASE_SENSITIVE
Total Size: 18,202,226,688 FS_CASE_IS_PRESERVED
Avail Size: 12,755,464,192 FS_UNICODE_STORED_ON_DISK
FILE_VOLUME_QUOTAS
FS_FILE_COMPRESSION
FS_PERSISTENT_ACLS
FILE_SUPPORTS_OBJECT_IDS
FILE_SUPPORTS_SPARSE_FILES
FILE_SUPPORTS_REPARSE_POINTS
FILE_SUPPORTS_ENCRYPTION
Figure 3.1 Displaying
volume information.
Example
3.2 Retrieving Volume Information
01. use Win32::API::Prototype;
02.
03. ApiLink( ‘kernel32.dll', ‘BOOL GetVolumeInformation(
04. LPCTSTR lpRootPathName,
05. LPTSTR lpVolumeNameBuffer,
06. DWORD nVolumeNameSize,
07. LPDWORD lpVolumeSerialNumber,
08. LPDWORD lpMaximumComponentLength,
09. LPDWORD lpFileSystemFlags,
10. LPTSTR lpFileSystemNameBuffer,
11. DWORD nFileSystemNameSize )' )
12. || die "Can not link to GetVolumeInformation()";
13. ApiLink( ‘kernel32.dll', ‘BOOL GetDiskFreeSpaceEx(
14. LPCTSTR lpDirectoryName,
15. PVOID lpFreeBytesAvailable,
16. PVOID lpTotalNumberOfBytes,
17. PVOID lpTotalNumberOfFreeBytes )' )
18. || die "Wrong version of NT";
19.
20. %FLAGS = (
21. 0x00000001 => ‘FS_CASE_SENSITIVE',
22. 0x00000002 => ‘FS_CASE_IS_PRESERVED',
23. 0x00000004 => ‘FS_UNICODE_STORED_ON_DISK',
24. 0x00000008 => ‘FS_PERSISTENT_ACLS',
25. 0x00000010 => ‘FS_FILE_COMPRESSION',
26. 0x00000020 => ‘FILE_VOLUME_QUOTAS',
27. 0x00000040 => ‘FILE_SUPPORTS_SPARSE_FILES',
28. 0x00000080 => ‘FILE_SUPPORTS_REPARSE_POINTS',
29. 0x00008000 => ‘FS_VOL_IS_COMPRESSED',
30. 0x00000000 => ‘FILE_NAMED_STREAMS',
31. 0x00020000 => ‘FILE_SUPPORTS_ENCRYPTION',
32. 0x00010000 => ‘FILE_SUPPORTS_OBJECT_IDS',
33. );
34.
35. push( @ARGV, "\\" ) if( 0 == scalar @ARGV );
36.
37. foreach my $Path ( @ARGV )
38. {
39. my $dwVolSize = 256;
40. my $szVolName = NewString( $dwVolSize );
41. my $pdwSerialNum = pack( "L", 0 );
42. my $pdwMaxLength = pack( "L", 0 );
43. my $pdwFlags = pack( "L", 0 );
44. my $dwFSNameSize = 256;
45. my $szFSName = NewString( $dwFSNameSize );
46.
47. $Path .= "\\" unless( $Path =~ /\\$/ );
48. next unless( -d $Path );
49. $Path = NewString( $Path );
50.
51. print "\n$Path:\n";
52. $~ = DumpVol;
53. if( GetVolumeInformation( $Path, $szVolName,
54. $dwVolSize, $pdwSerialNum,
55. $pdwMaxLength, $pdwFlags,
56. $szFSName, $dwFSNameSize ) )
57. {
58. local %Vol = (
59. path => $Path,
60. volume_name => CleanString( $szVolName ),
61. serial_num => sprintf( "\U%04x-%04x",
62. reverse( unpack( "S2",
63. $pdwSerialNum ) ) ),
64. max_length => unpack( "L", $pdwMaxLength ),
65. flags => DecodeFlags( unpack( "L", $pdwFlags ) ),
66. fs_name => CleanString( $szFSName )
67. );
68. my $pFree = pack( "L2", 0, 0 );
69. my $pTotal = pack( "L2", 0, 0 );
70. my $pTotalFree = pack( "L2", 0, 0 );
71. if( GetDiskFreeSpaceEx( $Path, $pFree, $pTotal, $pTotalFree ) )
72. {
73. $Vol{free} = FormatNumber( MakeLargeInt( unpack( "L2",
74. $pTotalFree ) ) );
75. $Vol{total} = FormatNumber( MakeLargeInt( unpack( "L2",
76. $pTotal ) ) );
77. }
78. write;
79. }
80. }
81.
82. sub MakeLargeInt
83. {
84. my( $Low, $High ) = @_;
85. return( $High * ( 1 + 0xFFFFFFFF ) + $Low );
86. }
87.
88. sub DecodeFlags
89. {
90. my( $Flags ) = @_;
91. my $String = "";
92. foreach my $Key ( keys( %FLAGS ) )
93. {
94. if( $Flags & $Key )
95. {
96. $String .= sprintf( "%- 30s\n", $FLAGS{$Key} );
97. }
98. }
99. return( $String );
100. }
101.
102. sub FormatNumber
103. {
104. my( $Num ) = @_;
105. {} while( $Num =~ s/^(-?\d+)(\d{3})/$1,$2/ );
106. return( $Num );
107. }
108.
109. format DumpVol =
110. FS Serial Volume Name Flags
111. ----- --------- --------------------------- ----------------------------
112. @<<<< @<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
113. $Vol{fs_name}, $Vol{serial_num}, $Vol{volume_name}, $Vol{flags}
114. Total Size: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
115. $Vol{total} $Vol{flags
116. Avail Size: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
117. $Vol{free} $Vol{flags}
118. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
119. $Vol{flags}
120. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
121. $Vol{flags}
122. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
123. $Vol{flags}
124. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
125. $Vol{flags}
126. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
127. $Vol{flags}
128. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
129. $Vol{flags}
130. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
131. $Vol{flags}
132. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
133. $Vol{flags}
134. ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
135. $Vol{flags}
136. .
Creating Hard File Links
UNIX users could always create hard links between files, and
starting with Windows 2000, so can users of the Win32 platform. Previous
versions of Windows NT were able to create links by utilizing a POSIX
subsystem; however, with Windows 2000 the link functionality is built directly
into the Win32 API. Unfortunately, these links are not as rich as their UNIX
brethren. You can create links only for files but not directories. And like
UNIX's hard links, they must be located on the same volume. This means that a
link must reside on the same hard drive as the file it is linked to. Win32,
however, does not have the capability to symbolically link across volumes. The
closest equivalent is a shortcut, but that is interpreted by an application,
not the operating system (therefore, you cannot rely on all programs to
understand how to use them).
This hard-linking capability is supported only on NTFS volumes.
Example 3.3 creates such hard links. The script takes in two parameters like
this:
perl ln.pl c:\temp\MyExistingFile.txt c:\MyNewLink.txt
The first parameter is the existing file, and the second parameter
is the path to where the new linked file will be created. This new link file
will be (for all practical purposes) the same file as specified in the first
parameter. Editing either of these files results in both files being modified.
This makes sense because you really have only one file, just two separate file
system entries for it.
After a hard link has been created on an NTFS volume, it is
literally linked to the original file. Even if you move the original or linked
file, they both still point to the same data. Additionally, you can modify the
content of either file, and it updates the same data. This can cause
concurrency problems if you are accessing multiple files all linked together.
Because the file is only a link
to the original file, it can be deleted without affecting the original file.
Example
3.3 Creating Hard Links
01. use Win32::API::Prototype;
02.
03. if( 2 != scalar @ARGV )
04. {
05. print "Syntax:\n $0 <File to link to> <New Link File>\n";
06. exit();
07. }
08.
09. my( $NewLink, $File ) = @ARGV;
10. ApiLink( ‘kernel32.dll',
11. ‘BOOL CreateHardLink( LPCTSTR lpFileName,
12. LPCTSTR lpExistingFileName,
13. LPSECURITY_ATTRIBUTES lpSa )' )
14. || die "This version of Windows does not support CreateHardLink()";
15. if( CreateHardLink( $File, $NewLink, 0 ) )
16. {
17. print "Link successfully created.\n";
18. }
19. else
20. {
21. print "Failed to create link.\nError: ";
22. print Win32::FormatMessage( Win32::GetLastError() ), "\n";
23. }
Creating Directories
Creating
directories is not a new concept. All disk-based operating systems such as
UNIX, Win32, DOS, and Macintosh have the concept of some file-based container
(for example, a directory or folder). You must create a container so that you
can move objects such as files and other directories into it. You usually do so
by using a command such as md
or mkdir
. Win32 exposes
this functionality through its Win32 API CreateDirectory()
and CreateDirectoryEx()
functions. It is also available from a DOS box when you use the mkdir
or md
command. These techniques, however, fail to create intermediary directories.
For example, if you run
md Dir1\Dir2
and Dir1
does not already exist, then Dir2
will not be created.
Tip
On the Windows NT and 2000 platforms, if command extensions are
enabled, the DOS box md
and mkdir
commands
create any intermediary directories. For more information, run cmd.exe /h
.
Even Win32
Perl fails to create any intermediary directories-most likely because it simply
calls into the C: library's mkdir()
function,
which, in turn, calls the Win32 API's CreateDirectory()
function.
Example 3.4
creates as many directories as it needs in order to create the passed-in
directory. There is nothing Win32-specific about this particular script;
therefore, it could run on any platform that runs Perl. This script accepts
only one parameter, which must be either a full path (for example, C
<code>:
\temp\Dir1\Dir2),
a relative path (for example, ..\Dir1\Dir2
), or a UNC (for example, \\server\share\Dir1\Dir2
).
To use this
script, just pass in the name of the path to create-for example,
perl makedir.pl \\server\share\dir1\dir2
Example 3.4 Creating Intermediary Directories
01. $Dir = shift @ARGV || die "Syntax: $0 <Dir>";
02. $Result = 1;
03. ( $Root, $Path ) = ( $Dir =~ /^(\w:\\?|\\\\.+?\\.+?\\|\\)?(.*)$/ );
04. print "Creating directory ‘$Dir'...\n";
05.
06. if( -d $Dir )
07. {
08. print "Directory already exists.\n";
09. exit;
10. }
11.
12. @DirList = split( /\\/, $Path );
13. $Path = $Root;
14.
15. while( $Result && scalar @DirList )
16. {
17. $Path .= ( shift @DirList ) . "\\";
18.
19. next if( -d $Path );
20. $Result = mkdir( $Path, 0777 );
21. }
22.
23. print ( ( $Result )? "Success" : "Failure (Error: $!)" ), "\n";
NTFS Streams
One of the greatest joys of using the NTFS file system is the
ability to create multiple streams in a file. However, most administrators, let
alone users, are not even aware of this capability.
Just like any file system, an NTFS file contains data. However,
unlike other file systems, in this one the data is stored in what is called an
NTFS stream. This stream is
simple data that is associated with the file by means of a name. This is very
similar to the way a Perl hash works. A stream name is much like a hash key.
Data is associated with a hash key, just as data can be associated with a file
stream name. By default, all NTFS files have only one stream, the default
stream, which is the place data is stored. However, NTFS allows additional
streams to be created in a file. Because each stream has a name (with the
exception of the default stream), you access the stream by name.
Thinking of multiple data
segments for a single file may be mind-bending-but only because it is
not common. Think of multiple streams as you would a tar file, which is basically an archive of multiple
data files. You could create one file with multiple streams, each stream
consisting of data from different files. This would enable you to create a log
file for, say, a Web server that consists of only one file. But each day the
Web server creates a new log, it creates a new stream in the log file. The end
result is that you could have an entire year's worth of Web server log entries
consolidated into one convenient file. Then you can easily copy all the logged
data from one place to another because you are copying only one file.
A Windows NT/2000 machine can store Macintosh files on its NTFS drives
this way. By using alternate data streams, the operating system can store both
the Macintosh data and resource forks in one file.
To access an NTFS stream you must refer to the NTFS file as you normally
would, but you use a colon followed by the stream name. The format is
path\file_name:stream_name
Here's an example:
C:\temp\MyTestFile.txt:MyFirstStream
Perl can read and write to such NTFS streams using the normal file
functions. You can test this capability by running Example 3.5 and Example 3.6
on an NTFS drive. Example 3.5 creates a text file with two streams: the main
data stream and an alternative stream.
Example 3.6 reads back the data from both streams in the text file.
If you examine the resulting text file, you will notice that the file size
reflects only the data in the default data stream. If you were unaware that the
file contained multiple streams, there would normally be no way to discover
this fact. As you will see, Win32 Perl can be used to discover such alternate
data streams.
Example
3.5 Creating a Test File with Multiple Streams
01. $File = "StreamTest.txt";
02. $Stream = "AltDataStream";
03. if( open( STREAM1, "> $File" ) )
04. {
05. print "Writing to the main data stream.\n";
06. print STREAM1 "\tWelcome to the main data stream.\n";
07. close( STREAM1 );
08.
09. if( open( STREAM2, "> $File:$Stream" ) )
10. {
11. print "Writing to alternative stream ‘$Stream'.\n";
12. print STREAM2 "\tWelcome to the alternative data stream ‘$Stream'\n";
13. close( STREAM2 );
14. }
15. else
16. {
17. print "Error: $!\n";
18. }
19. }
20. else
21. {
22. print "Error: $!\n";
23. }
Example
3.6 Reading Multiple Streams from a Test File
01. $File = "StreamTest.txt";
02. $Stream = "AltDataStream";
03. if( open( STREAM1, "< $File" ) )
04. {
05. print "This is the main data stream for $File.\n";
06. while( my $Line = <STREAM1> )
07. {
08. print $Line;
09. }
10. close( STREAM1 );
11.
12. if( open( STREAM2, "< $File:$Stream" ) )
13. {
14. print "\n\nThis is the data stream $File:$Stream:\n";
15. while( my $Line = <STREAM2> )
16. {
17. print $Line;
18. }
19. close( STREAM2 );
20. }
21. else
22. {
23. print "Error: $!\n";
24. }
25. }
26. else
27. {
28. print "Error: $!\n";
29. }
On Windows NT/2000, the more command can be coaxed into
displaying alternate data streams. You do so by redirecting the input into the more
command from a data stream like this:
more < test.txt:MyStream
Tip
For some reason, most programs do not permit you to enter valid
NTFS pathnames that include alternate streams. For example, an attempt to load test.txt:MyStream
into Notepad, Microsoft Word, or various other programs will fail.
Example 3.7 is a replacement for the DOS type command.
This script functions the same way, except that it can display NTFS data
streams-something that type cannot do.
The script accepts any number of paths to display. This includes
paths with wildcards. For example,
perl streamdump.pl myfile.txt:altStream1 c:\temp\*.txt
Example
3.7 Displaying NTFS Alternate Data Stream Content
01. foreach my $Mask ( @ARGV )
02. {
03. my @List;
04.
05. # Must be careful since glob() does not support
06. # paths with embedded spaces.
07. if( @List = glob( $Mask ) )
08. {
09. push( @FileList, @List );
10. }
11. else
12. {
13. push( @FileList, $Mask );
14. }
15. }
16. foreach my $Path ( @FileList )
17. {
18. print $Path, "\n";
19. print "-" x length( $Path ), "\n";
20. if( open( FILE, "< $Path" ) )
21. {
22. my $Buffer;
23.
24. binmode( FILE );
25. while( read( FILE, $Buffer, 1024 ) )
26. {
27. print $Buffer;
28. }
29. close( FILE );
30. }
31. else
32. {
33. print "\tError: $!\n";
34. }
35. print "\n";
36. }
NTFS, Alternative Data Streams, and File Sizes
Interestingly enough, the reported size of a file does not reflect
the contents of alternative data streams. For example, if you create a 32-byte
file and add an alternative data stream that is 500,000 bytes in size, the file
still reports only 32 bytes. This makes it even more impractical for an
administrator to determine exactly how much space a file truly takes up.
Just to help complicate NTFS size reporting issues, due to the
nature of NTFS, a directory listing may report unexpected available disk space
values. For example, if you create this text file
C:\>echo Big Old Test > test.txt
a directory listing shows how large the
file is and how much space is available on the disk:
C:\>dir test.txt
Volume in drive C is unnamed
Volume Serial Number is DCF6-AF23
Directory of C:\
06/01/2000 09:14a 15 test.txt
1 File(s) 15 bytes
0 Dir(s) 592,346,112 bytes free
If you rewrite the file to a different file size,
C:\>echo This is really a test > test.txt
a directory listing yields
C:\>dir test.txt
Volume in drive C is unnamed
Volume Serial Number is DCF6-AF23
Directory of C:\
06/01/2000 09:14a 24 test.txt
1 File(s) 17 bytes
0 Dir(s) 592,346,112 bytes free
Notice that even though the file size changes, the amount of
available drive space does not because NTFS calculates two different size
values for each file: how large the file is and how much disk space the file
takes up. In this case, the file size is either 15 or 24 bytes. However, the
file takes up (at least) one sector of disk space. And each sector (on this
particular volume) is made up of 1 kilobyte (1024 bytes) of space. Therefore,
it takes 1024 bytes of disk space to store only 15 bytes of data. When NTFS
computes the available disk space, that amount is based on the amount of
unallocated disk space. Because disk space is taken up in 1KB blocks, the available
free space is always rounded down to a multiple of 1024.
Enumerating NTFS Streams
The one problem with NTFS streams is that there is no way to
enumerate the streams in a file. This problem is rather tragic, considering how
powerful NTFS streams can be. Basically, this means that if you forget which
streams you have added to a file, there is no way to discover them. This
situation is akin to removing the dir
command from the DOS box: How would
you possibly discover which files are in a directory? Yes, bizarre as it
sounds, this is the reality of Win32's lack of proper support for NTFS streams.
The good news is that you can use a hack to enumerate streams. This
solution involves reading the file as backup software does, using the Win32
API's BackupRead()
function.
The Streams.pl script (see Example 3.8) displays the names and
sizes of all streams in an NTFS file. The script accepts any number of
parameters from the command line. Each parameter is a path to a file that is to
be examined for alternate streams. The file path can contain wildcards.
Example
3.8 Enumerating Streams in an NTFS File
01. use Win32::API::Prototype;
02.
03. $OPEN_EXISTING = 3;
04. $GENERIC_READ = 0x80000000;
05. $BACKUP_DATA = 0x00000001;
06. $BACKUP_ALTERNATE_DATA = 0x00000004;
07.
08. ApiLink( ‘kernel32.dll',
09. ‘HANDLE CreateFile( LPCTSTR pszPath,
10. DWORD dwAccess,
11. DWORD dwShareMode,
12. PVOID SecurityAttributes,
13. DWORD dwCreationDist,
14. DWORD dwFlags,
15. HANDLE hTemplate )' )
16. || die "Can not locate CreateFile()";
17. ApiLink( ‘kernel32.dll',
18. ‘BOOL CloseHandle( HANDLE hFile )' )
19. || die "Can not locate CloseHandle()";
20. ApiLink( ‘kernel32.dll',
21. ‘BOOL BackupRead( HANDLE hFile,
22. LPBYTE pBuffer,
23. DWORD dwBytesToRead,
24. LPDWORD pdwBytesRead,
25. BOOL bAbort,
26. BOOL bProcessSecurity,
27. LPVOID *ppContext)' )
28. || die "Can not locate BackupRead()";
29. ApiLink( ‘kernel32.dll',
30. ‘BOOL BackupSeek( HANDLE hFile,
31. DWORD dwLowBytesToSeek,
32. DWORD dwHighBytesToSeek,
33. LPDWORD pdwLowByteSeeked,
34. LPDWORD pdwHighByteSeeked,
35. LPVOID *pContext )' )
36. || die "Can not create BackupSeek()";
37.
38. # Generate a list of all files to process
39. # therefore we have to expand any wildcards
40. foreach my $Mask ( @ARGV )
41. {
42. foreach my $Path ( glob( $Mask ) )
43. {
44. push( @Files, $Path ) if( -f $Path );
45. }
46. }
47.
48. foreach my $File ( @Files )
49. {
50. print "$File\n";
51.
52. $hFile = CreateFile( $File,
53. $GENERIC_READ,
54. 0,
55. undef,
56. $OPEN_EXISTING,
57. 0,
58. 0 ) || die "Can not open the file ‘$File'\n";
59.
60. # If CreateFile() failed $hFile is a negative value
61. if( 0 < $hFile )
62. {
63. my $iStreamCount = 0;
64. my $iTotalSize = 0;
65. my $pBytesRead = pack( "L", 0 );
66. my $pContext = pack( "L", 0 );
67. my $pStreamIDStruct = pack( "L5", 0,0,0,0,0,0 );
68.
69. while( BackupRead( $hFile,
70. $pStreamIDStruct,
71. length( $pStreamIDStruct ),
72. $pBytesRead,
73. 0,
74. 0,
75. $pContext ) )
76. {
77. my $BytesRead = unpack( "L", $pBytesRead );
78. my $Context = unpack( "L", $pContext );
79. my %Stream;
80. my( $pSeekBytesLow, $pSeekBytesHigh ) = ( pack( "L", 0 ),
81. pack( "L", 0 ) );
82. my $StreamName = "";
83. my $StreamSize;
84.
85. # No more data to read
86. last if( 0 == $BytesRead );
87.
88. @Stream{ id, attributes,
89. size_low, size_high,
90. name_size } = unpack( "L5", $pStreamIDStruct );
91.
92. if( $BACKUP_ALTERNATE_DATA == $Stream{id} )
93. {
94. $StreamName = NewString( $Stream{name_size} );
95. if( BackupRead( $hFile,
96. $StreamName,
97. $Stream{name_size},
98. $pBytesRead,
99. 0,
100. 0,
101. $pContext ) )
102. {
103. my $String = CleanString( $StreamName, 1 );
104. $String =~ s/^:(.*?):.*$/$1/;
105. $StreamName = $String;
106. }
107. }
108. elsif( $BACKUP_DATA == $Stream{id} )
109. {
110. $StreamName = "<Main Data Stream>";
111. }
112. $StreamSize = MakeLargeInt( $Stream{size_low}, $Stream{size_high} );
113. $iTotalSize += $StreamSize;
114.
115. printf( " % 3d) %s (%s bytes)\n",
116. ++$iStreamCount,
117. $StreamName,
118. FormatNumber( $StreamSize ) ) if( "" ne $StreamName );
119.
120. # Move to next stream...
121. if( ! BackupSeek( $hFile,
122. $Stream{size_low},
123. $Stream{size_high},
124. $pSeekBytesLow,
125. $pSeekBytesHigh,
126. $pContext ) )
127. {
128. last;
129. }
130. $pBytesRead = pack( "L2", 0 );
131. $pStreamIDStruct = pack( "L5", 0,0,0,0,0 );
132. }
133. printf( " Total file size: %s bytes\n",
134. FormatNumber( $iTotalSize ) );
135. # Abort the backup reading. Win32 API claims we MUST do this.
136. BackupRead( $hFile, undef, 0, 0, 1, 0, $pContext );
137. CloseHandle( $hFile );
138. }
139. print "\n";
140. }
141.
142. sub FormatNumber
143. {
144. my( $Num ) = @_;
145. {} while( $Num =~ s/^(-?\d+)(\d{3})/$1,$2/ );
146. return( $Num );
147. }
148.
149. sub MakeLargeInt
150. {
151. my( $Low, $High ) = @_;
152. return( $High * ( 1 + 0xFFFFFFFF ) + $Low );
153. }
Discovering File Versions
Many binary files, such as programs and DLLs, can have version
information branded into them. Such branding is not required but is incredibly
helpful. When you install software that has updated binary files, it usually
checks the version information on files to make sure it is not overwriting a
file with an older version.
Figure 3.2 shows all the version
information on a given binary file. This particular display is useful in
determining not only what version the file is, but also what platform the file
was created for (NT, Win32, Win16, DOS).
C:\>perl filever.pl c:\winnt\system32\AUTPRX32.DLL
1) c:\winnt\system32\AUTPRX32.DLL
Comments............January 15, 1997
CompanyName.........Microsoft Corporation
FileDescription.....Microsoft Remote Automation Proxy for Windows
NT(TM) Operating System
FileVersion.........5.00.3715
InternalName........AUTPRX32.DLL
LangID..............0x0409
Language............English (United States)
LegalCopyright......Copyright ¬ Microsoft Corp. 1995.
LegalTrademarks.....Microsoft« is a registered trademark of
Microsoft Corporation. Windows(TM) is a
trademark of Microsoft Corporation
ProductName.........Microsoft Remote Automation
ProductVersion......5.00.3715
Internal Info:
DateStamp.......0
Debug...........0
FileType........dll
FileVersion.....5.0.37.15
OS..............nt
Patched.........0
PreRelease......0
ProductVersion..5.0.37.15
SpecialBuild....0
Figure 3.2 Displaying
version information on a DLL file.
Example 3.9 accepts any number of parameters, each representing a
path to a binary file. The paths can include wildcards and can be UNCs to
access version information residing on remote machines.
Example
3.9 Displaying File Version Information
01. use Win32::AdminMisc;
02.
03. my $iCount = 0;
04. if( 0 == scalar @ARGV )
05. {
06. Syntax();
07. exit;
08. }
09.
10. foreach my $File ( @ARGV )
11. {
12. my %Info;
13.
14. $iCount++;
15. print "$iCount) $File ";
16. if( -f $File )
17. {
18. if( Win32::AdminMisc::GetFileInfo( $File, \%Info ) )
19. {
20. print "\n";
21. foreach my $Attr ( sort( keys( %Info ) ) )
22. {
23. next if( ref $Info{$Attr} );
24. Display( $Attr, $Info{$Attr} );
25. }
26.
27. if( $Info{FileInfo} )
28. {
29. Display( "Internal Info:" );
30. foreach my $Attr ( sort( keys( %{$Info{FileInfo}} ) ) )
31. {
32. next if( ref $Info{FileInfo}->{$Attr} );
33. Display( " $Attr", $Info{FileInfo}->{$Attr} );
34. }
35. }
36. }
37. else
38. {
39. print "unable to get version.\n";
40. }
41. }
42. else
43. {
44. print "unable to locate file.\n";
45. }
46.
47. }
48.
49. sub Display
50. {
51. my( $Attr, $Data ) = @_;
52.
53. print "\t$Attr";
54. if( "" ne $Data )
55. {
56. print "." x ( 20 - length( $Attr ) ), "$Data";
57. }
58. print "\n";
59. }
60.
61. sub Syntax
62. {
63. my( $Line ) = "-" x length( $0 );
64.
65. print <<EOT;
66.
67. $0
68. $Line
69. Display version information on a particular file.
70.
71. Syntax:
72. perl $0 File[, File2[, ... ]]]
73.
74. EOT
75. }
Permissions
Win32 security is, in part, based on permissions. Permissions,
which are placed on securable objects (such as files, directories, Registry
keys, and network shares), designate which users have access to perform
actions. The topic of permissions is quite involved and can easily take up an
entire book by itself, so I won't go into detail on how they work. For this
type of information, you can consult any number of different resources,
including http://www.roth.net/perl/perms/security/.
Displaying Permissions
UNIX machines have always been capable of listing who has
permissions on files and directories. You get this listing by using the ls
command. Win32's equivalent to ls
is dir
, but it does not
display any permission information.
Example 3.10 displays permission information on any path passed in
on the command line. This script accepts a number of parameters representing
paths to securable objects, as in this example:
perl perm.pl c:\temp\*.txt \\server\HKLM\Software\ActiveState \\server2\Temp$
This example displays the permissions for all text files in the c:\temp
directory as well as the HKEY_LOCAL_MACHINE\Software\ActiveState
Registry
key on the machine \\server
and the Temp$
share on the
machine \\server2
.
Example 3.10 displays the permissions in the form of an array of
letters. Each letter indicates the type of permission that the specified user
has been granted. Table 3.2 describes these letters.
Table 3.2 Permissions by Letter
Letter
Permission
R | User
is allowed to open the object for reading. |
r | User is not allowed to open the object for
reading. |
W | User is
allowed to open the file for writing. |
w | User is not allowed to open the file for
writing. |
D | User is
allowed to delete the object. |
d | User is not allowed to delete the object. |
X | User
is allowed to execute the object. When this permission is applied to a
directory or Registry key, the user is allowed to access its contents. |
x | User
is not allowed to execute the
object. When this permission is applied to a directory or Registry key, the
user is not allowed to access
its contents. |
P | User is
allowed to change the object's permissions. |
p | User is not allowed to change the object's
permissions. |
O | User is
allowed to take ownership of the object. |
o | User is not allowed to take ownership of
the object. |
A | User
has been granted all
permissions. This permission may be set in addition to other flags. |
a | User
has been denied all
permissions. This permission may be set in addition to other flags. |
Figure 3.3 shows the permissions on a specific directory (C:\temp
).
The script first prints the name of the owner and group of the object (in this
case, a directory). Next, it prints each user and group account that has been
granted or denied access to the object. The type of object that the permission
relates to follows the account name. In this example, the Administrator account
is granted directory permissions, and the Perltest7 account has
been granted both directory and file permissions.
The permissions are then displayed. This seven-letter string
indicates which permissions are granted, denied, or not addressed. A capital
letter indicates that the related permission has been explicitly granted, and a
lowercase letter indicates that the permission has been explicitly denied. You
can see that Perltest7 has been granted read and execute but denied write
permissions on files in the C:\temp
directory. Additionally, the
account has been denied permission to take ownership of the directory. On the
other hand, the Administrator account has been granted all permissions. The
letter A means that the account has been
granted all permissions. This
means that there was no need to explicitly grant the read, write, execute, and
delete permissions. Notice that the Joel account has been granted all access
(just like the Administrator); however, the delete permission has been
explicitly denied. Because explicitly denied access always overrides allowed
access, the user has full control over the directory, except that he cannot
delete it.
C:\>perl perm.pl c:\temp
Permissions for ‘c:\temp':
Owner: BUILTIN\Administrators
Group: BUILTIN\Domain Admins
Administrator (Directory) RWXD--A
Joel (Directory) RWXd--A
Perltest7 (Directory) -----o-
Perltest7 (File) RwX----
Figure 3.3 Displaying
permissions on a directory.
Notes on Files and Directories
For files and directory paths, wildcards are acceptable; however,
for Registry keys, they are not.
You can specify files and directories on remote machines by using a
UNC. However, if you're specifying a UNC's root directory, make sure that you
terminate the path with a backslash like this:
\\server\share\
If you do not append the backslash, the permissions of the network
share itself are referenced.
Notes on Shared Directories and Printers
Shared directory and printer pathnames must not end with a backslash. If you specify a shared
directory path with an ending backslash, the permissions for the shared
directory's root directory are displayed, not the permissions for the network
share itself. An attempt to reference a printer with an ending backslash simply
fails.
Also, note that shared directories have neither an owner nor a
group.
Notes on Registry Keys
You specify Registry keys with a full Registry key path like this:
HKEY_LOCAL_MACHINE\Software
You can abbreviate the Registry root by shortening it like this:
HKLM\Software
You also can specify Registry keys on remote machines by prepending
the machine name like this:
\\server\HKEY_LOCAL_MACHINE\Software
or
\\server\HKLM\Software
Note
When accessing remote registries, only the HKEY_USERS
and HKEY_LOCAL_MACHINE
hives are accessible.
Example
3.10 Displaying Permission Information
01. use Win32::Perms;
02.
03. # The %PERM hash maps a permission type to its position in the
04. # permission string array. Notice that there is no 0 position
05. # since that is reserved for unhandled permission types (and
06. # we won't print it).
07. %PERM = (
08. R => 1,
09. W => 2,
10. X => 3,
11. D => 4,
12. P => 5,
13. O => 6,
14. A => 7,
15. );
16.
17. %MAP = (
18. ‘FILE_READ_DATA' => ‘R',
19. ‘GENERIC_READ' => ‘R',
20. ‘KEY_READ' => ‘R',
21. ‘DIR_READ' => ‘R',
22.
23. ‘FILE_WRITE_DATA' => ‘W',
24. ‘KEY_WRITE' => ‘W',
25. ‘GENERIC_WRITE' => ‘W',
26. ‘FILE_APPEND_DATA' => ‘W',
27. ‘DIR_ADD_SUBDIR' => ‘W',
28. ‘DIR_ADD_FILE' => ‘W',
29.
30. ‘DELETE' => ‘D',
31. ‘FILE_DELETE_CHILD' => ‘D',
32.
33. ‘FILE_EXECUTE' => ‘X',
34. ‘FILE_TRAVERSE' => ‘X',
35. ‘GENERIC_EXECUTE' => ‘X',
36. ‘DIR_TRAVERSE' => ‘X',
37. ‘DIR_EXECUTE' => ‘X',
38.
39. ‘CHANGE_PERMISSION' => ‘P',
40.
41. ‘TAKE_OWNERSHIP' => ‘O',
42.
43. ‘FILE_ALL_ACCESS' => ‘A',
44. ‘GENERIC_ALL' => ‘A',
45. ‘DIR_ALL_ACCESS' => ‘A',
46. ‘STANDARD_RIGHTS_ALL' => ‘A',
47. );
48.
49. push( @ARGV, "*.*" ) unless( scalar @ARGV );
50. foreach my $Mask ( @ARGV )
51. {
52. my( @List ) = glob( $Mask );
53. if( ! scalar @List )
54. {
55. push( @List, $Mask );
56. }
57. foreach my $Path ( @List )
58. {
59. print "\nPermissions for ‘$Path':\n";
60. ReportPerms( $Path );
61. print "\n\n";
62. }
63. }
64.
65. sub ReportPerms
66. {
67. my( $Path ) = @_;
68. my( $Acct, @List );
69. my( $Perm ) = new Win32::Perms( $Path );
70. my( %PermList ) = ();
71. my( $MaxAcctLength ) = 1;
72.
73. if( ! $Perm )
74. {
75. print "Can not obtain permissions for ‘$Path'\n";
76. return;
77. };
78.
79. printf( " Owner: %s\n Group: %s\n",
80. $Perm->Owner(),
81. $Perm->Group() );
82. $Perm->Dump( \@List );
83. foreach $Acct ( @List )
84. {
85. my( $PermMask );
86. my( $Mask, @M, @F );
87. my( $DaclType );
88. my $bAllowAccess = ( "Deny" ne $Acct->{Access} );
89. my $String;
90. my $Account;
91.
92. next if( $Acct->{Entry} ne "DACL" );
93.
94. if( "" eq $Acct->{Account} )
95. {
96. $Account = $Acct->{SID};
97. }
98. else
99. {
100. $Account = "$Acct->{Domain}\\" if( "" ne $Acct->{Domain} );
101. $Account .= $Acct->{Account};
102. }
103. if( length( $Account ) > $MaxAcctLength )
104. {
105. $MaxAcctLength = length( $Account )
106. }
107. $iTotal++;
108. DecodeMask( $Acct, \@M, \@F );
109. foreach $Mask ( @M )
110. {
111. $PermMask |= 2**$PERM{ $MAP{$Mask} };
112. }
113. $DaclType = $Acct->{ObjectName};
114. if( 2 == $Acct->{ObjectType} )
115. {
116. # We have either a file or directory. Therefore we need to
117. # figure out if this DACL represents an object (file) or
118. # a container (dir)...
119. if( $Acct->{Flag} & DIR )
120. {
121. $DaclType = "Directory";
122. }
123. else
124. {
125. $DaclType = "File";
126. }
127. }
128. if( ! defined $PermList{$Account}->{$DaclType} )
129. {
130. # Create the permission string array. The first element in the
131. # array must be blank since all unhandled permissions will default
132. # to that position (and we won't print it).
133. my $TempHash = [
134. " ",
135. split(
136. ];
137. $PermList{$Account}->{$DaclType} = $TempHash;
138.
139. }
140. foreach $Mask ( keys( %PERM ) )
141. {
142. if( $PermMask & 2**$PERM{$Mask} )
143. {
144. $String = $PermList{$Account}->{$DaclType};
145. # If we already have a denied permission then skip this step
146. # since denied access overrides any explicitly allowed access
147. if( $String->[$PERM{$Mask}] !~ /[a-z]/ )
148. {
149. my $TempMask = $Mask;
150. $TempMask = lc $Mask if( 0 == $bAllowAccess );
151. $String->[$PERM{$Mask}] = $TempMask ;
152. }
153. }
154. }
155. }
156.
157. if( ! $iTotal )
158. {
159. # There are no DACL entries therefore...
160. print "\t Everyone has full permissions.\n";
161. }
162. else
163. {
164. foreach my $Permission ( sort( keys( %PermList ) ) )
165. {
166. foreach my $DaclType ( sort( keys( %{$PermList{$Permission}} ) ) )
167. {
168. my $String = $PermList{$Permission}->{$DaclType};
169. printf( " % " . $MaxAcctLength . "s % -11s %s\n",
170. $Permission,
171. "($DaclType)",
172. join( ‘', @$String ) );
173. }
174. }
175. }
176. }
Displaying Verbose Permissions
Example 3.10 in the "Displaying Permissions" section displays the
permissions for various securable objects, such as files and directories. That
particular script distills the complex world of permissions into a few very
basic and simple permission sets. Using that script, you can easily get an idea
of which users have which permissions. However, to display the permissions in a
simple way, many assumptions and interpretations must be made, which can leave
out details that may be critical for administrators to successfully maintain
true security.
Example 3.11 displays the full set of permissions that an object
has. No interpretations are made, and all permissions, flags, and types are
displayed in their full, confusing glory. If you're not familiar with the
different permissions that can be set on an object, the display may be more
than just confusing.
Figure 3.4 shows the output this script
produces. To be sure, it is not for the faint at heart. This particular example
is a permission dump for the same c:\temp
directory shown in Figure 3.3.
Unlike Example 3.10, this script displays all
the permission information. Some of these permissions may not make much sense,
but they are documented in the Win32 API SDK (http://msdn.microsoft.com/).
C:\>perl vperm.pl c:\temp
Permissions for ‘c:\temp':
Owner: BUILTIN\Administrators
Group: BUILTIN\Domain Admins
Account Permissions Flags
------------------------------ ---------------------- -----------------------
CREATOR OWNER DELETE INHERIT_ONLY_ACE
Deny Access OBJECT_INHERIT_ACE
(Directory) CONTAINER_INHERIT_ACE
ROTH.NET\Perltest7 FILE_WRITE_DATA INHERIT_ONLY_ACE
Deny Access FILE_APPEND_DATA OBJECT_INHERIT_ACE
(File) FILE_WRITE_EA
FILE_WRITE_ATTRIBUTES
ROTH.NET\Perltest7 DIR_ADD_SUBDIR OBJECT_INHERIT_ACE
Deny Access TAKE_OWNERSHIP CONTAINER_INHERIT_ACE
(Directory) CHANGE_PERMISSION
CREATOR OWNER DIR_READ INHERIT_ONLY_ACE
Allow Access DIR_ADD_FILE OBJECT_INHERIT_ACE
(Directory) DIR_ADD_SUBDIR CONTAINER_INHERIT_ACE
DIR_READ_EATTRIB
DIR_WRITE_EATTRIB
DIR_EXECUTE
DIR_DEL_SUBDIR
DIR_READ_ATTRIB
DIR_WRITE_ATTRIB
READ_CONTROL
TAKE_OWNERSHIP
ROTH.NET\Administrator DIR_READ OBJECT_INHERIT_ACE
Allow Access DIR_ADD_FILE CONTAINER_INHERIT_ACE
(Directory) DIR_ADD_SUBDIR
DIR_READ_EATTRIB
DIR_WRITE_EATTRIB
DIR_EXECUTE
DIR_DEL_SUBDIR
DIR_READ_ATTRIB
DIR_WRITE_ATTRIB
STANDARD_RIGHTS_ALL
ROTH.NET\Perltest7 FILE_READ_DATA INHERIT_ONLY_ACE
Allow Access FILE_READ_EA OBJECT_INHERIT_ACE
(File) FILE_EXECUTE
FILE_READ_ATTRIBUTES
READ_CONTROL
SYNCHRONIZE
Figure 3.4 The
true permissions placed on a file.
Example 3.11 accepts any number of paths
on the command line. Each path can contain wildcards. Additionally, an optional
/F
switch is accepted; it reports the so-called friendly
permissions. Most administrators are familiar with these permissions, such as
FULL_CONTROL
,
CHANGE
,
and
READ.
Such friendly permissions are, however, far from complete and do not
necessarily explain the true nature of the permissions. For example, no
friendly permission describes that a file has both
READ
and
DELETE
access.
Refer to the "Displaying Permissions" section to learn how to
address different paths.
Example
3.11 Displaying the Full Details of an Object's
Permissions
01. use Getopt::Long;
02. use Win32::Perms;
03.
04. # Turn off aggressive domain controller lookups
05. Win32::Perms::LookupDC( 0 );
06. Configure( \%Config );
07. if( $Config{help} )
08. {
09. Syntax();
10. exit();
11. }
12.
13. foreach my $Mask ( @{$Config{masks}} )
14. {
15. my( @List ) = glob( $Mask );
16. if( ! scalar @List )
17. {
18. push( @List, $Mask );
19. }
20. foreach $Path ( @List )
21. {
22. print "\nPermissions for ‘$Path':\n";
23. ReportPerms( $Path );
24. print "\n\n";
25. }
26. }
27.
28. sub ReportPerms
29. {
30. my( $Path ) = @_;
31. my( $Acct, @List );
32. my( $Perm ) = new Win32::Perms( $Path );
33.
34. if( ! $Perm )
35. {
36. print "Can not obtain permissions for ‘$Path'\n";
37. return;
38. };
39.
40. printf( " Owner: %s\n Group: %s\n", $Perm->Owner(), $Perm->Group() );
41. $Perm->Dump( \@List );
42. $~ = PermissionHeader;
43. write;
44. foreach $Acct ( @List )
45. {
46. my( $PermMask );
47. my( @String ) = split(
48. my( $Mask, @Permissions, @Friendly );
49. local $DaclType;
50. local $Access = $Acct->{Access};
51. local $Account;
52.
53. $~ = Permission;
54.
55. next if( $Acct->{Entry} ne "DACL" );
56. $iTotal++;
57.
58. DecodeMask( $Acct, \@Permissions, \@Friendly );
59. DecodeFlag( $Acct, \@Flag );
60. # Pad each flag and permission with spaces so that they correctly
61. # break into one permission/flag per line when printed. This is a
62. # trick when working with write/formats
63. $Flags = join( " " x 10 . "\n", @Flag );
64. $Perms = join( " " x 10 . "\n",
65. ( $Config{friendly} )? @Friendly : @Permissions );
66. if( "" eq $Acct->{Account} )
67. {
68. $Account = $Acct->{SID};
69. }
70. else
71. {
72. $Account = "$Acct->{Domain}\\" if( "" ne $Acct->{Domain} );
73. $Account .= $Acct->{Account};
74. }
75. $DaclType = $Acct->{ObjectName};
76. if( 2 == $Acct->{ObjectType} )
77. {
78. # We have either a file or directory. Therefore we need to
79. # figure out if this DACL represents an object (file) or
80. # a container (dir)...
81. if( $Acct->{Flag} & DIR )
82. {
83. $DaclType = "Directory";
84. }
85. else
86. {
87. $DaclType = "File";
88. }
89. }
90. write;
91. print "\n";
92. }
93.
94. if( ! $iTotal )
95. {
96. print "\t Everyone has full permissions.\n";
97. }
98. }
99.
100. sub Configure
101. {
102. my( $Config ) = @_;
103. my $Result;
104.
105. Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
106. $Result = GetOptions( $Config,
107. qw(
108. friendly|f
109. help|?|h
110. )
111. );
112. push( @{$Config->{masks}}, @ARGV );
113. $Config->{help} = 1 unless( $Result && scalar @{$Config->{masks}} );
114. }
115.
116. sub Syntax
117. {
118. my( $Script ) = ( $0 =~ /([^\\\/]*?)$/ );
119. my( $Line ) = "-" x length( $Script );
120.
121. print <<EOT;
122.
123. $Script
124. $Line
125. Displays verbose permissions set on securable objects.
126.
127. Syntax:
128. perl $Script [-f] Path [ Path2 ... ]
129. -f..........Show "friendly" permissions.
130. Path........The path to a securable object.
131. This path can consist of ? and * wildcards.
132.
133. EOT
134. }
135.
136. format PermissionHeader =
137. @||||||||||||||||||||||||| @||||||||||||||||||||| @||||||||||||||||||||||
138. "Account", "Permissions", "Flags"
139. -------------------------- ---------------------- -----------------------
140. .
141.
142. format Permission =
143. ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
144. $Account, $Perms, $Flags
145. ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
146. $Account, $Perms, $Flags
147. @<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
148. "$Access Access", $Perms, $Flags
149. @<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
150. "($DaclType)", $Perms, $Flags
151. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
152. $Perms, $Flags
153. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
154. $Perms, $Flags
155. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
156. $Perms, $Flags
157. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
158. $Perms, $Flags
159. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
160. $Perms, $Flags
161. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
162. $Perms, $Flags
163. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
164. $Perms, $Flags
165. ~ ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
166. $Perms, $Flags
167. .
Ownership
Almost all securable objects have an owner.
The owner is an account (either a user or group account) that has ultimate
access over the file. The owner of an object can never be denied access to it.
He can read, write, delete, and modify permissions, as well as do anything else
that is possible.
To discover who is the owner of a particular object (file,
directory, or Registry key), you need to locate the security information for
the object. For files and directories, you typically use the Explorer program
to select the object's properties and then select the Security tab on the
displayed property page. For a Registry key, you display the key's permission
information from within the REGEDT32.EXE
program.
Ownership of an object was designed such that it could only be
taken, not given. For example, an administrator could become the owner of a
file (take ownership) so that she could usurp permissions placed on the file.
However, because she could not assign ownership to another user (give
ownership), it would be obvious as to who had bypassed security because the
administrator would remain the owner until someone else took ownership. Because
an administrator must grant a user account with the privilege to take ownership
(the SE_TAKE_OWNERSHIP_NAME
privilege to be precise), this paradigm was deemed safe.
It did not take long, however, for programmers to discover that
backup software somehow had to be able to restore a file with its original
owner intact. That meant that there was a way to give
ownership! The technique for doing so lies in the ability for a process to act
like backup software. This, of course, requires that the user has a special
privilege (SE_RESTORE_NAME).
All this leads to Example 3.12, which enables you not only to
discover who is the owner for a given object but also to change the object's
owner (provided that you have permission to do so). Any user can use this
script to view who is the owner, but a user requires the SE_RESTORE_NAME
privilege to change the ownership of a secured object.
This script accepts a number of parameters representing paths to
securable objects. To set the owner on the specified objects, you pass in an -s
switch followed by the account name of the new owner. Consider these two
examples:
perl owner.pl c:\temp\*.txt \\server\HKLM\Software\ActiveState
perl owner.pl -s JOEL c:\temp\*.txt \\server\HKLM\Software\ActiveState
The first example displays the owner for all text files in the c:\temp
directory as well as the HKEY_LOCAL_MACHINE\Software\ActiveState
Registry
key on the machine \\server
.
The second example is essentially the same as the first example,
except that it first sets all the objects' owners to Joel and then displays who
is the owner. If the user running the script has the appropriate privileges,
Joel is displayed as the new owner for each object.
Tip
When referring to an account name, you can pass in the name of an
account and have Win32 decide if the account is a domain or local machine
account. Alternatively, you can explicitly specify what domain or machine the
account belongs to.
To specify a domain account, refer to it by specifying the domain
followed by a backslash and account name as in
MyDomain\joel
"Some Domain\Domain Admins"
To specify a particular machine account, pass in the server name
beginning with double backslashes. Then append a backslash and account name as
in
\\Server1\joel
"\\Server1\Domain Admins"
Refer to the notes in the "Displaying Permissions" section for
information regarding how to address different paths.
Example
3.12 Retrieving and Setting the Owner of a Securable
Object
01. use Getopt::Long;
02. use Win32::Perms;
03.
04. # Turn off intense domain lookups
05. Win32::Perms::LookupDC( 0 );
06.
07. Configure( \%Config );
08. if( $Config{help} )
09. {
10. Syntax();
11. exit();
12. }
13.
14. $~ = OwnerHeader;
15. write;
16. $~ = Owner;
17. foreach $Mask ( @{$Config{masks}} )
18. {
19. my( @List ) = glob( $Mask );
20. if( ! scalar @List )
21. {
22. push( @List, $Mask );
23. }
24. foreach $Path ( @List )
25. {
26. my $Perm;
27.
28. if( ! ( $Perm = new Win32::Perms( $Path ) ) )
29. {
30. $Owner = "Can't get permissions.\n";
31. }
32. else
33. {
34. if( "" ne $Config{owner} )
35. {
36. $Perm->Owner( $Config{owner} );
37. $Perm->Set();
38. }
39. $Owner = $Perm->Owner();
40. }
41. write;
42. }
43. }
44.
45. sub Configure
46. {
47. my( $Config ) = @_;
48. my $Result;
49.
50. Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
51. $Result = GetOptions( $Config,
52. qw(
53. owner|o|s=s
54. help|?|h
55. )
56. );
57. push( @{$Config->{masks}}, @ARGV );
58. $Config->{help} = 1 unless( $Result && scalar @{$Config->{masks}} );
59. }
60.
61. sub Syntax
62. {
63. my( $Script ) = ( $0 =~ /([^\\\/]*?)$/ );
64. my( $Line ) = "-" x length( $Script );
65.
66. print <<EOT;
67.
68. $Script
69. $Line
70. Displays (and sets) the owner of securable objects.
71.
72. Syntax:
73. perl $Script [-s Account] Path [ Path2 ... ]
74. -s Account..Set the owner to the specified account.
75. Path........The path to a securable object.
76. This path can consist of ? and * wildcards.
77.
78. EOT
79. }
80.
81. format OwnerHeader =
82. @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<
83. "Path", "Owner"
84. -------------------------------------- ----------------------------
85. .
86.
87. format Owner =
88. @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<
89. $Path, $Owner
90. .
DCOM Configurations
Most users never know that their programs communicate with each
other using COM. They paste spreadsheets inside their word processing documents
without ever knowing that this capability is made possible by object linking and embedding (OLE)
technology, which is based on COM. However, you need to be vigilant regarding
security because recent versions of Windows have enabled Distributed COM
(DCOM). This technology enables one computer to communicate with a program
running on another machine. This capability is ideal for administration
purposes but potentially dangerous if renegade hackers break into a machine.
Windows provides the DCOMCNFG.EXE utility, which is used to
configure DCOM security. This tool enables you to select who has access to a
computer via DCOM. It has two problems, though: it is GUI-based, and it runs
only on a local machine.
Because the program is GUI-based, getting a quick overview of how a
machine's DCOM is configured is difficult. Instead, you must move from tab to
tab clicking on buttons. Doing so is not only annoying but a time burden as
well.
Because the utility reports only the DCOM configuration for the
machine it is running on, its use is quite limited. This limitation requires
that you walk to each server, log on, and then run the utility if you want to
check your server farm.
Example 3.13 displays a machine's DCOM configuration quickly and
without any GUI. You can also check a remote machine's DCOM configuration by
passing in the machine's name when running the script.
Note
Every machine that is a member of a domain has a machine account
(similar to user account) in the domain. When the machine is booted up and
comes online, it logs into the domain using the machine account and a password.
When examining a machine's DCOM configuration, you might find references to
user, group, and machine accounts.
Figure 3.5 shows the output of Example
3.13. Notice that the Guest account has been explicitly denied in all the
security-related configurations. The script shows whether access was granted or
denied.
C:\>perl dcom.pl \\server1
DCOM settings for \\Server1:
DCOM is enabled.
COM Internet Services are enabled.
The following accounts are allowed to launch COM applications:
Allow NT AUTHORITY\SYSTEM
Allow NT AUTHORITY\INTERACTIVE
Deny SERVER1\Guest
Allow BUILTIN\Administrators
Allow SERVER1\IUSR_SERVER1
Allow SERVER1\IWAM_SERVER1
Accounts with access to this machine via DCOM:
Allow NT AUTHORITY\SYSTEM
Allow NT AUTHORITY\INTERACTIVE
Deny SERVER1\Guest
Allow BUILTIN\Administrators
Allow SERVER1\IUSR_SERVER1
Allow SERVER1\IWAM_SERVER1
Allow BUILTIN\Administrators
Accounts with access to COM/OLE configuration:
Allow BUILTIN\Administrators
Allow CREATOR OWNER
Deny SERVER1\Guest
Allow Everyone
Allow BUILTIN\Power Users
Allow NT AUTHORITY\SYSTEM
Allow BUILTIN\Users
The following protocols are permitted for use with DCOM:
ncacn_ip_tcp
ncacn_spx
ncacn_nb_nb
ncacn_nb_ipx
Figure 3.5 Examining
a DCOM configuration.
Example
3.13 Displaying DCOM Configurations
01. use Win32::Registry;
02. use Win32::Perms;
03.
04. # Prevent aggressive domain controller lookups
05. Win32::Perms::LookupDC( 0 );
06. %PATH = (
07. ole => ‘SOFTWARE\Microsoft\Ole',
08. rpc => ‘SOFTWARE\Microsoft\RPC',
09. );
10.
11. $Perm = new Win32::Perms() || die "Cannot create Permissions object";
12. if( scalar @ARGV )
13. {
14. $Machine = "\\\\" . shift @ARGV;
15. }
16. else
17. {
18. $Machine = "\\\\" . Win32::NodeName();
19. }
20. $Machine =~ s/^\\+/\\\\/;
21. $HKEY_LOCAL_MACHINE->Connect( $Machine, $Root )
22. || die "Can not connect";
23. print "\nDCOM settings for $Machine:\n";
24. if( $Root->Open( $PATH{ole}, $Key ) )
25. {
26. my( $Flag, $Type, $Value, $Sd );
27.
28. undef $Value;
29. $Key->QueryValueEx( "EnableDCOM", $Type, $Value );
30. print " DCOM is ", ( "y" eq lc $Value )? "":"not ", "enabled.\n";
31.
32. $Key->QueryValueEx( "EnableDCOMHTTP", $Type, $Value );
33. print " COM Internet Services are ",
34. ( "y" eq lc $Value )? "":"not ", "enabled.\n";
35.
36. print "\n The following accounts are allowed to "
37. . "launch COM applications:\n";
38. if( $Key->QueryValueEx( "DefaultLaunchPermission", $Type, $Sd ) )
39. {
40. DisplayPerms( $Sd ) if( REG_BINARY == $Type );
41. }
42.
43. print "\n Accounts with access to this machine via DCOM:\n";
44. if( $Key->QueryValueEx( "DefaultAccessPermission", $Type, $Sd ) )
45. {
46. DisplayPerms( $Sd ) if( REG_BINARY == $Type );
47. }
48.
49. print "\n Accounts with access to COM/OLE configuration:\n";
50. DisplayPerms( "$Machine\\HKEY_CLASSES_ROOT" );
51.
52. print "\n The following protocols are permitted "
53. . "for use with DCOM:\n";
54. if( $Root->Open( $PATH{rpc}, $Key2 ) )
55. {
56. my $MString;
57. if( $Key2->QueryValueEx( "DCOM Protocols", $Type, $MString ) )
58. {
59. foreach my $Protocol ( split( "\x00", $MString ) )
60. {
61. print " $Protocol\n";
62. }
63. }
64. $Key2->Close();
65. }
66. $Key->Close();
67. }
68. $Root->Close();
69.
70. sub DisplayPerms
71. {
72. my( $Sd ) = @_;
73. my @List;
74.
75. $Perm->Import( $Sd );
76. $Perm->Dump( \@List );
77.
78. foreach my $Ace ( @List )
79. {
80. my $Account;
81. if( "" ne $Ace->{Account} )
82. {
83. $Account = "$Ace->{Domain}\\" if( "" ne $Ace->{Domain} );
84. $Account .= $Ace->{Account};
85. }
86. else
87. {
88. $Account = $Ace->{SID};
89. }
90.
91. # Object Type of 1 represents a DACL
92. next if( 1 != $Ace->{ObjectType} );
93.
94. printf( " %-6s %-30s\n", $Ace->{Access}, $Account );
95. }
96. return;
97. }
Registry
Win32 platforms make use of the Registry to store configuration
information. Programs and system services alike populate Registry keys with
values that represent various settings of some sort. Information on services
and system drivers are stored along with details on how they are loaded and
where their files are located. The Registry is where you can find COM component
configurations, TCP settings, and auto logon settings, just to name a few.
Regfind
The Registry is a vast wasteland of configuration information. It's
so vast, in fact, that locating anything held therein can be quite daunting.
Because the Registry is a database of configuration data, you would hope that
you could search it and efficiently locate necessary tidbits of information.
However, you would be disappointed to learn that this is not possible. The
Win32 API does not have any mechanism that enables such searching. Considering
that this database, even on a notebook, can grow several tens of megabytes in
size, locating data is comparable to finding a needle in a haystack.
The lack of such search functionality is probably due, in part, to
the lack of any real need. After all, the repository of information was
designed for processes to hold configuration data-what used to be held in .INI
files. The presumption here is that software would know where it stored data in
the Registry, so it would never need to look for it. This presumption, however,
is based on the notion that software behaves itself.
All too often when you uninstall software, it leaves, intact,
traces of itself, such as COM registration information. Under these
circumstances, searching the Registry for a given GUID or string can be quite
valuable. To give another, but altogether pertinent, example, often, for some
odd reason, some software moves a window to a location just off of the screen.
Such repositioning of a window may occur if you are using dual monitors but
have to temporarily resort to using only one. This could also occur if, from
time to time, you use a higher resolution display. Either way, every time the
application is run, the window the user interacts with is located where it
cannot be seen. Because most users do not know where, say, Notepad may locate
its configuration store in the Registry, a search becomes quite important.
Example 3.14 performs such searches. When you pass in Registry
paths and search strings, the script scans the Registry and displays its
findings as it proceeds. You can pass in multiple paths and multiple search
strings. All you need to do is specify each path with a -p
switch-as many of them as necessary. Then pass in as many strings as required.
You can locate each path on a remote machine by prepending the
machine name to the path. You can also abbreviate the Registry root keys as in
Table 3.3.
Table 3.3 Registry Roots and Their Abbreviations
Registry Root
Abbreviation
HKEY_LOCAL_MACHINE | HKLM |
HKEY_CURRENT_USER | HKCR |
HKEY_CLASSES_ROOT | HKCU |
HKEY_USERS | HKU |
HKEY_CURRENT_CONFIG | HKCC |
Consider this example:
perl regfind.pl -p HKEY_LOCAL_MACHINE\Software
-p \\server1\HKLM\Software winamp "windows media"
This particular instance searches the
HKEY_LOCAL_MACHINE\Software
Registry
paths on the current machine as well as on
\\Server1
. The
Registry is searched for the string
winamp
or "
windows media
". The
search is not case sensitive, so
WinAmp
also triggers a match. The search
traverses the entire specified Registry paths. Therefore, it could take some
time to complete.
Output is printed to STDOUT
, but information about how the
search is proceeding is printed to STDERR
. This way, you can redirect output
to a file while still observing the findings of the search.
Example
3.14 Locating Strings in the Registry
01. use Getopt::Long;
02. use Win32::Registry;
03.
04. %HIVE_LIST = (
05. HKEY_LOCAL_MACHINE => $HKEY_LOCAL_MACHINE,
06. HKEY_CURRENT_USER => $HKEY_CURRENT_USER,
07. HKEY_CLASSES_ROOT => $HKEY_CLASSES_ROOT,
08. HKEY_USERS => $HKEY_USERS,
09. HKEY_CURRENT_CONFIG => $HKEY_CURRENT_CONFIG,
10. );
11. %HIVE_LIST_ABBR = (
12. HKLM => $HKEY_LOCAL_MACHINE,
13. HKCU => $HKEY_CURRENT_USER,
14. HKCR => $HKEY_CLASSES_ROOT,
15. HKU => $HKEY_USERS,
16. HKCC => $HKEY_CURRENT_CONFIG,
17. );
18. foreach my $Key ( keys( %HIVE_LIST ) )
19. {
20. $HIVE_LIST_REVERSE{$HIVE_LIST{$Key}} = $Key;
21. }
22. $iTotalKeys = $iTotalMatch = 0;
23. Configure( \%Config );
24. if( $Config{help} )
25. {
26. Syntax();
27. exit();
28. }
29.
30. while( my $Path = shift @{$Config{paths}} )
31. {
32. my( $Temp, $Machine, $Hive ) = ( $Path =~ /^((\\\\.*?)\\)?(.*)/ );
33. my( $Hive, $Temp2, $Path ) = ( $Hive =~ /^([^\\]*)(\\(.*))?/ );
34. my $Root = OpenHive( $Hive, $Machine );
35. if( 0 != $Root )
36. {
37. my @Keys;
38. my @List;
39.
40. if( "" ne $Machine )
41. {
42. $Hive = "$Machine\\$Hive";
43. }
44.
45. if( $Root->Open( $Path, $Key ) )
46. {
47. $Path = "\\$Path" if( "" ne $Path );
48. print "Scanning $Hive$Path\n";
49. ProcessKey( $Key, "$Hive$Path", $KeyName );
50. }
51. else
52. {
53. print "Unable to open $Hive\\$Path\n";
54. }
55. $Root->Close();
56. }
57. else
58. {
59. # Can not connect.
60. }
61. }
62.
63. print STDERR "\n-------------------\n";
64. print STDERR "Total values checked: $iTotalKeys\n";
65. print STDERR "Total values matching criteria: $iTotalMatch\n";
66.
67. sub OpenHive
68. {
69. my( $Hive, $Machine ) = @_;
70. my $Root;
71. my $HiveKey = $HIVE_LIST_ABBR{uc $Hive};
72.
73. if( "Win32::Registry" ne ref $HiveKey )
74. {
75. $HiveKey = $HIVE_LIST{uc $Hive}
76. }
77.
78. if( "" ne $Machine )
79. {
80. if( ! $HiveKey->Connect( $Machine, $Root ) )
81. {
82. print STDERR "Could not connect to ‘$Machine'\n";
83. $Root = 0;
84. }
85. }
86. else
87. {
88. $Root = $HiveKey;
89. }
90. return( $Root );
91. }
92.
93. sub ProcessKey
94. {
95. my( $Key, $Path, $KeyName ) = @_;
96. my $TempKey;
97.
98. if( $Key->Open( $KeyName, $TempKey ) )
99. {
100. my( $SubKey, $Value, @Keys, %Values, $iCount );
101. my $ThisPath = $Path;
102. $ThisPath .= "\\$KeyName" if( "" ne $KeyName );
103. if( $TempKey->GetValues( \%Values ) )
104. {
105. foreach my $Value ( sort( keys( %Values ) ) )
106. {
107. my( $Name, $Type, $Data ) = @{$Values{$Value}};
108. $iTotalKeys++;
109. printf( STDERR "% 10s keys checked; %s matched.\r",
110. FormatNumber( $iTotalKeys ),
111. FormatNumber( $iTotalMatch ) );
112. foreach my $Target ( @{$Config{target}} )
113. {
114. if( "$Name\x00$Data" =~ /$Target->{find}/i )
115. {
116. if( REG_BINARY == $Type )
117. {
118. $Data = "<Binary Data>";
119. }
120. print STDERR " " x 60, "\r";
121. printf( STDOUT " % d) %s\\%s: (%s) ‘%s'\n",
122. ++$iTotalMatch,
123. $ThisPath,
124. $Value,
125. ValueType( $Type ),
126. $Data );
127. }
128. }
129. }
130. }
131. $TempKey->GetKeys( \@Keys );
132.
133. foreach $SubKey ( sort( @Keys ) )
134. {
135. ProcessKey( $TempKey, $ThisPath, $SubKey );
136. }
137. $TempKey->Close();
138. }
139. }
140.
141. sub ValueType
142. {
143. my( $Type ) = @_;
144. my( $ValueType );
145.
146. if( REG_SZ == $Type )
147. {
148. $ValueType = "REG_SZ";
149. }
150. elsif( REG_EXPAND_SZ == $Type )
151. {
152. $ValueType = "REG_EXPAND_SZ";
153. }
154. elsif( REG_MULTI_SZ == $Type )
155. {
156. $ValueType = "REG_MULTI_SZ";
157. }
158. elsif( REG_DWORD == $Type )
159. {
160. $ValueType = "REG_DWORD";
161. }
162. elsif( REG_BINARY == $Type)
163. {
164. $ValueType = "REG_BINARY";
165. }
166. else
167. {
168. $ValueType = "Unknown Type";
169. }
170. return( $ValueType );
171. }
172.
173. sub FormatNumber
174. {
175. my( $Number ) = @_;
176. while( $Number =~ s/^(-?\d+)(\d{3})/$1,$2/ ){};
177. return( $Number );
178. }
179.
180. sub Configure
181. {
182. my( $Config ) = @_;
183. my $Result;
184.
185. Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
186. $Result = GetOptions( $Config,
187. qw(
188. paths|p=s@
189. help|?|h
190. )
191. );
192. foreach my $Target ( @ARGV )
193. {
194. push( @{$Config->{target}}, { find => $Target } );
195. }
196. if( ! scalar @{$Config->{paths}} )
197. {
198. push( @{$Config->{paths}}, ‘HKLM' );
199. }
200. $Config->{help} = 1 unless( scalar @{$Config->{target}} );
201. $Config->{help} = 1 unless( $Result );
202. return;
203. }
204.
205. sub Syntax
206. {
207. my( $Script ) = ( $0 =~ m#([^\\/]+)$# );
208. my $Line = "-" x length( $Script );
209. print << "EOT";
210.
211. $Script
212. $Line
213. Locates specified strings in the registry.
214. Syntax: $Script [-p <path>]<find> [<find2> ...]
215. Path..........Registry path to look into such as
216. HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER
217. Abbreviations are allowed (eg. HKLM).
218. Default: HKEY_LOCAL_MACHINE
219. Find..........String to search for.
220.
221. Remote registries are allowed by prepending a machine name
222. such as: \\\\server1\\hklm\\software
223.
224. EOT
225. }
Setting Autologon
There is a little-known ability to configure a Windows NT/2000
machine to autologon. This means that when a machine boots up, it automatically
logs on as a specific user. You can set this autologon by manipulating some
settings in the Registry. Using such a configuration is a terrific way to
autoconfigure a machine.
Say that you need to configure 100 brand new machines. You could
create an administrator-like account with a special logon script (covered in
Chapter 6, "Logon Scripts"). The logon script would perform any functions that
it needs to properly configure the machine-creating proper directories, copying
files from a master file server, installing ODBC drivers and other software
packages, and so on. Often, however, many programs need to reboot the computer
before they can finish installing. Autologon handles this task. Before the
logon script finishes, it sets the necessary information in the Registry and
reboots the computer. The computer reboots and logs on automatically using the
specified user ID. That user ID causes a logon script to run; the script
recognizes the state of the machine and finishes whatever it needs to do.
Finally, the logon script removes the autologon settings and either logs off or
reboots again.
Using this particular example, all you need to do is walk over to
each machine and log on with the special account. You do so 100 times, and
you're finished. Consider this to be a poor man's version of Microsoft's
Systems Management Server (SMS).
The dark side of autologon is that a username and password are
added into the Registry, and they can be discovered with relative ease. The
information is not encoded, so anyone with permission to view the Registry key
can see both the username and password. You can remedy this by applying
permissions so that only administrators and the System accounts have read
access on the key and can both change permissions and take ownership.
Example 3.15 turns on or off the autologon feature, depending on
what you pass into the script. The syntax is
perl setlogon.pl [-m Machine] [-clear] [<Domain\User | User> <Password>]
The switches are described in Table 3.4. If a username is
specified, a password must be specified as well. The user account's domain
should be specified but does not need to be. However, a logon problem may occur
if another user account in the local machine's user database has the same name
as one in the default domain.
Table 3.4 Autologon Switches
Switch
Description
-m Machine | This switch specifies for which
machine to configure autologon. If this switch is not specified, the default is
the local machine. |
clear | This
switch clears the autologon configuration. The username, domain, and password
are cleared, and autologon is turned off. |
User | The
username of the account to log on automatically. |
Domain | The
domain of the account to log on automatically. This switch is optional. |
Password | The
password of the account to log on automatically. |
Example
3.15 Setting the Autologon User Account
01. use Getopt::Long;
02. use Win32::Registry;
03.
04. $PATH = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon";
05. Configure( \%Config );
06. if( $Config{help} )
07. {
08. Syntax();
09. exit();
10. }
11.
12. my $Root = $HKEY_LOCAL_MACHINE;
13. my $Key;
14.
15. if( "" ne $Config{machine} )
16. {
17. print "Connecting to $Config{machine}...\n";
18. if( ! $Root->Connect( $Config{machine}, $Root ) )
19. {
20. print "Can not connect to $Machine\n";
21. exit();
22. }
23. }
24.
25. if( $Root->Create( $PATH, $Key ) )
26. {
27. foreach my $Param ( keys( %{$Config{params}} ) )
28. {
29. my $Result = $Key->SetValueEx( $Param,
30. 0,
31. REG_SZ,
32. $Config{params}->{$Param} );
33. print "Setting $Param to ‘$Config{params}->{$Param}': ",
34. ( $Result )? "success":"failure", "\n";
35. }
36. $Key->Close();
37. }
38. else
39. {
40. print "Unable to open the $PATH key.\n";
41. }
42.
43. if( "" ne $Config{machine} )
44. {
45. $Root->Close();
46. }
47.
48. sub Configure
49. {
50. my( $Config ) = @_;
51. my $Result;
52. my $Params = {
53. DefaultDomainName => "",
54. DefaultUserName => "",
55. DefaultPassword => "",
56. AutoAdminLogon => 0,
57. };
58.
59. Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
60. $Result = GetOptions( $Config,
61. qw(
62. machine|m=s
63. clear|c
64. help|?|h
65. )
66. );
67. $Config->{help} = 1 unless( $Result );
68.
69. if( ! $Config->{clear} )
70. {
71. $Params->{DefaultDomainName} = shift @ARGV;
72. if( "" ne $Params->{DefaultDomainName} )
73. {
74. ( $Params->{DefaultDomainName},
75. $Params->{DefaultUserName} ) = ( $Params->{DefaultDomainName}
76. =~ /((.*)\\)?(.*)$/ )[1..3];
77. }
78. $Params->{DefaultPassword} = shift @ARGV;
79. $Params->{AutoAdminLogon} = 1;
80. }
81. else
82. {
83. $Params->{AutoAdminLogon} = 0;
84. }
85. if( ( "" eq $Params->{DefaultUserName} )
86. && ( ! $Config->{clear} ) )
87. {
88. $Config->{help} = 1;
89. }
90. $Config->{params} = $Params;
91. return;
92. }
93.
94. sub Syntax
95. {
96. my( $Script ) = ( $0 =~ m#([^\\/]+)$# );
97. my $Line = "-" x length( $Script );
98. my $WhiteSpace = " " x length( $Script );
99. print << "EOT";
100.
101. $Script
102. $Line
103. This utility will set the default logon information such as
104. Userid, Domain, Password and whether or not the computer
105. automatically logs on the default user.
106.
107. Syntax: $Script [-m Machine] <Domain\\User|User> <Password>
108. $WhiteSpace -clear [-m Machine]
109.
110. User............The userid to autologon as
111. Domain..........The domain of the autologon userid
112. Password........The autologon userid's password
113. -m Machine......Specifies what machine to set/unset.
114. -clear..........Remove the domain, username and password
115.
116. EOT
117. }
TCP/IP Configuration
You can configure multiple network cards on Windows machines. You also
can bind multiple TCP/IP addresses to a given network card, which can make it
difficult to track which computer has what IP address. All this information is
held in the Registry on each computer. Being able to track this information,
however, can be quite tedious.
Example 3.16 displays a machine's TCP/IP configuration, including
addresses procured by means of DHCP. All addresses for all network cards are
displayed. The information from other, remote machines can be displayed in
addition to the local machine.
The script takes a single parameter that represents which machine
to query. If no machine name is passed in, the local machine is assumed. For
example,
perl tcpinfo.pl \\server1
In this case, the TCP configuration information is obtained for the
machine \\server1
.
Example
3.16 Displaying a Machine's TCP/IP Configuration
01. use Win32::Registry;
02.
03. %KeyName = (
04. serviceroot => ‘System\CurrentControlSet\Services',
05. tcplink => ‘Tcpip\Linkage',
06. tcplink_disabled => ‘Tcpip\Linkage\Disabled',
07. win2k_tcplink => ‘Tcpip\Parameters\Interfaces',
08. deviceparam_tcp => ‘Parameters\Tcpip',
09. );
10.
11. $Root = $HKEY_LOCAL_MACHINE;
12.
13. if( $Machine = $ARGV[0] )
14. {
15. $HKEY_LOCAL_MACHINE->Connect( $Machine, $Root )
16. || die "Could not connect to the registry on ‘$Machine'\n";
17. }
18.
19. if( $Root->Open( $KeyName{serviceroot}, $ServiceRoot ) )
20. {
21. # First check if this is Win2k...
22. if( $ServiceRoot->Open( $KeyName{win2k_tcplink}, $Links ) )
23. {
24. my @Interfaces;
25. if( $Links->GetKeys( \@Interfaces ) )
26. {
27. foreach my $DeviceName ( @Interfaces )
28. {
29. push( @Devices, [
30. "$KeyName{win2k_tcplink}\\$DeviceName",
31. $DeviceName
32. ] );
33. }
34. }
35. $Links->Close();
36. }
37. # Get the device names of the cards tcp is bound to...
38. else
39. {
40. if( $ServiceRoot->Open( $KeyName{tcplink}, $Links ) )
41. {
42. my( $Data );
43. if( $Links->QueryValueEx( "Bind", $DataType, $Data ) )
44. {
45. $Data =~ s/\n/ /gs;
46. $Data =~ s/\\Device\\//gis;
47. $Data =~ s/^\s+(.*)\s+$/$1/gs;
48. foreach my $DeviceName ( split( /\c@+/, $Data ) )
49. {
50. push( @Devices,
51. [
52. "$DeviceName\\$KeyName{deviceparam_tcp}",
53. $DeviceName
54. ] );
55. }
56. }
57. $Links->Close();
58. }
59. # Get the device names of cards that tcp is bound to but disabled...
60. if( $ServiceRoot->Open( $KeyName{tcplink_disabled}, $Links ) )
61. {
62. my( $Data );
63.
64. if( $Links->QueryValueEx( "Bind", $DataType, $Data ) )
65. {
66. $Data =~ s/\s+//gs;
67. $Data =~ s/\\Device\\//gis;
68. foreach my $DeviceName ( split( /\c@+/, $Data ) )
69. {
70. push( @Devices,
71. [
72. "$DeviceName\\$KeyName{deviceparam_tcp}",
73. $DeviceName
74. ] );
75. }
76. }
77. $Links->Close();
78. }
79. }
80.
81. foreach my $Device ( @Devices )
82. {
83. my( $DeviceTCPKey );
84. my( $Path ) = $Device->[0];
85. my( $DeviceName ) = $Device->[1];
86.
87. if( $ServiceRoot->Open( $Path, $DeviceTCPKey ) )
88. {
89. my %Hash;
90. my( @IP, @Subnet, @Gateway, @DHCP, @DNS );
91. my( $Data, $DataType, $Domain );
92.
93. # Get the domain...
94. $DeviceTCPKey->QueryValueEx( "Domain", $DataType, $Domain );
95.
96. # Get the IP addresses...
97. if( $DeviceTCPKey->QueryValueEx( "IPAddress",
98. $DataType,
99. $Data ) )
100. {
101. $Data =~ s/\s+//g;
102. $Data =~ s/0\.0\.0\.0//g;
103. $Data =~ s/\c@+/\c@/g;
104. push( @IP, split( /\c@/, $Data ) );
105. push( @DHCP, ( "no" ) x scalar split( /\c@/, $Data ) );
106. }
107.
108. # Get the Subnet masks...
109. if( $DeviceTCPKey->QueryValueEx( "SubnetMask",
110. $DataType,
111. $Data ) )
112. {
113. $Data =~ s/\s+//g;
114. $Data =~ s/0\.0\.0\.0//g;
115. $Data =~ s/\c@+/\c@/g;
116. push( @Subnet, split( /\c@/, $Data ) );
117. }
118. # Get the default gateways...
119. if( $DeviceTCPKey->QueryValueEx( "DefaultGateway",
120. $DataType,
121. $Data ) )
122. {
123. $Data =~ s/\s+//g;
124. $Data =~ s/0\.0\.0\.0//g;
125. $Data =~ s/\c@+/\c@/g;
126. push( @Gateway, split( /\c@/, $Data ) );
127. }
128. # Get the name servers...
129. if( $DeviceTCPKey->QueryValueEx( "NameServer",
130. $DataType,
131. $Data ) )
132. {
133. $Data =~ s/0\.0\.0\.0//g;
134. push( @DNS, split( /\s+/, $Data ) );
135. }
136.
137. # Query DHCP stats
138. if( $DeviceTCPKey->QueryValueEx( "EnableDHCP",
139. $DataType,
140. $Data ) )
141. {
142. $Hash{dhcp} = (( $Data )? "yes":"no" );
143.
144. # Get the DHCP domain...
145. $DeviceTCPKey->QueryValueEx( "DhcpDomain",
146. $DataType,
147. $Domain );
148.
149. # Get the DHCP IP Addresses...
150. if( $DeviceTCPKey->QueryValueEx( "DHCPIPAddress",
151. $DataType,
152. $Data ) )
153. {
154. $Data =~ s/\s+//g;
155. $Data =~ s/0\.0\.0\.0//g;
156. $Data =~ s/\c@+/\c@/g;
157. push( @IP, split( /\c@/, $Data ) );
158. push( @DHCP, ( "yes" ) x scalar split( /\c@/, $Data ) );
159. }
160.
161. # Get the DHCP subnet masks...
162. if( $DeviceTCPKey->QueryValueEx( "DHCPSubnetMask",
163. $DataType,
164. $Data ) )
165. {
166. $Data =~ s/\s+//g;
167. $Data =~ s/0\.0\.0\.0//g;
168. $Data =~ s/\c@+/\c@/g;
169. push( @Subnet, split( /\c@/, $Data ) );
170. }
171.
172. # Get the DHCP gateways...
173. if( $DeviceTCPKey->QueryValueEx( "DHCPDefaultGateway",
174. $DataType,
175. $Data ) )
176. {
177. $Data =~ s/\s+//g;
178. $Data =~ s/0\.0\.0\.0//g;
179. $Data =~ s/\c@+/\c@/g;
180. push( @Gateway, split( /\c@/, $Data ) );
181. }
182.
183. # Get the DHCP name servers...
184. if( $DeviceTCPKey->QueryValueEx( "DHCPNameServer",
185. $DataType,
186. $Data ) )
187. {
188. $Data =~ s/0\.0\.0\.0//g;
189. push( @DNS, split( /\s+/, $Data ) );
190. }
191. }
192. next if( 0 == scalar @IP );
193. $Hash{name} = $DeviceName;
194. $Hash{ip} = join( " " x 8, @IP );
195. $Hash{subnet} = join( " " x 8, @Subnet );
196. $Hash{gateway} = join( " " x 8, @Gateway );
197. $Hash{dhcp} = join( " " x 8, @DHCP );
198. $Hash{dns} = join( " " x 8, @DNS );
199. $Hash{domain} = $Domain;
200.
201. # Push our newfound data onto the stack...
202. push( @TCPConfig, \%Hash );
203.
204. $DeviceTCPKey->Close();
205. }
206. }
207. print "The machine $Machine has the following IP addresses:\n";
208. foreach $IP ( @TCPConfig )
209. {
210. print "\nInterface: $IP->{name}\n";
211. print "Domain: $IP->{domain}\n";
212.
213. $~ = TCPHeader;
214. write;
215. $~ = TCPDump;
216. write;
217. }
218. $ServiceRoot->Close();
219. }
220.
221. format TCPHeader =
222. @<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<<
223. "DHCP", "IP Address", "Subnet Mask", "Gateway", "DNS"
224. ---- --------------- --------------- --------------- ---------------
225. .
226.
227. format TCPDump =
228. ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
229. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
230. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
231. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
232. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
233. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
234. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
235. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
236. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
237. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
238. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
239. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
240. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
241. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
242. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
243. $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
244. .
Conclusion
There are dozens of different tools that an administrator can use
to make her job easier. Tools ranging from open source code to commercial
shrink-wrapped packages provide the necessary utilities to accomplish an
administrative goal.
This chapter demonstrates Perl's remarkable capability to exercise
the Win32 API to accomplish tasks. We have uncovered ways to use Perl to manage
the Win32 file system, permissions, and the Registry. The tools discussed are
useful and help define techniques that can be modified to target other tasks.
Copyright © 2000 New Riders Publishing
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.