Chapter 4. User Activity
4.4. Tracking File and Network Operations
4.4.1. Tracking Operations on Windows NT/2000
If we want to track other users' open files, the closest we can come involves using a third−party
command−line program called nthandle by Mark Russinovich, found at http://www.sysinternals.com. It can show us all of the open handles on a particular system. Here's some sample output:
System pid: 2
10: File C:\WINNT\SYSTEM32\CONFIG\SECURITY 84: File C:\WINNT\SYSTEM32\CONFIG\SAM.LOG cc: File C:\WINNT\SYSTEM32\CONFIG\SYSTEM d0: File C:\WINNT\SYSTEM32\CONFIG\SECURITY.LOG d4: File C:\WINNT\SYSTEM32\CONFIG\DEFAULT e8: File C:\WINNT\SYSTEM32\CONFIG\SYSTEM.ALT fc: File C:\WINNT\SYSTEM32\CONFIG\SOFTWARE.LOG 118: File C:\WINNT\SYSTEM32\CONFIG\SAM
128: File C:\pagefile.sys
134: File C:\WINNT\SYSTEM32\CONFIG\DEFAULT.LOG 154: File C:\WINNT\SYSTEM32\CONFIG\SOFTWARE 1b0: File \Device\NamedPipe\
294: File C:\WINNT\PROFILES\Administrator\ntuser.dat.LOG 2a4: File C:\WINNT\PROFILES\Administrator\NTUSER.DAT
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
SMSS.EXE pid: 27 (NT AUTHORITY:SYSTEM)
4: Section C:\WINNT\SYSTEM32\SMSS.EXE c: File C:\WINNT
28: File C:\WINNT\SYSTEM32
Information on specific files or directories can also be requested:
> nthandle c:\temp Handle V1.11
Copyright (C) 1997 Mark Russinovich http://www.sysinternals.com
WINWORD.EXE pid: 652 C:\TEMP\~DFF2B3.tmp WINWORD.EXE pid: 652 C:\TEMP\~DFA773.tmp WINWORD.EXE pid: 652 C:\TEMP\~DF913E.tmp
nthandle can provide this information for a specific process using the −p switch.
4.4. Tracking File and Network Operations 146
Using this executable from Perl is straightforward, so we won't provide any sample code. Instead, let's look at a related and more interesting operation: auditing.
NT/2000 allows us to efficiently watch a file, directory, or hierarchy of directories for changes. You could imagine repeatedly performing stat( )s on the desired object or objects, but that would be highly CPU intensive. Under NT/2000, we can ask the operating system to keep watch for us.
There are two specialized Perl modules that make this job relatively painless for us:
Win32::ChangeNotify by Christopher J. Madsen and Win32::AdvNotify by Amine Moulay Ramdane. The latter is a bit more flexible, so we'll use it for our example in this section.
Using Win32::AdvNotify is a multiple−step process. First, you load the module and create a new AdvNotify object:
# also import two constants we'll use in a moment use Win32::AdvNotify qw(All %ActionName);
use Data::Dumper;
$aobj = new Win32::AdvNotify( ) or die "Can't make a new object\n";
The next step is to create a monitoring thread for the directory in question. Win32::AdvNotify allows you to watch multiple directories at once simply by creating multiple threads. We'll stick to monitoring a single directory:
$thread = $aobj−>StartThread(Directory => 'C:\temp', Filter => All,
WatchSubtree => 0) or die "Unable to start thread\n";
The first parameter of this method is self−explanatory; let's look at the others.
We can look for many types of changes by setting Filter to one or a combination (SETTING1 | SETTING2 | SETTING3...) of the constants listed in Table 4−1.
Table 4.1. Win32::AdvNotify Filter Parameters
Parameter Notices
FILE_NAME Creating, deleting, renaming of a file or files DIR_NAME Creating or deleting a directory or directories ATTRIBUTES Change in any directory attribute
SIZE Change in any file size
LAST_WRITE Change in the modification date of a file or files CREATION Change in the creation date of a file or files
SECURITY Change in the security info (ACL, etc.) of a file or files
The All setting you see in our code above is just a constant that includes a combination of the choices.
Leaving the Filter parameter out of the method call will also select All. The WatchSubtree parameter determines if the thread will watch just the directory specified, or it and all of its subdirectories.
StartThread( ) creates a monitoring thread, but that thread doesn't actually begin monitoring until we ask it to:
$thread−>EnableWatch( ) or die "Can't start watching\n";
The Five−Minute RCS Tutorial (Perl for System Administration)
4.4.1. Tracking Operations on Windows NT/2000 147
There is also a DisableWatch( ) call, should you choose to turn off monitoring at any point in your program.
Now that we're monitoring our desired object, how do we know when something changes? We need some way for our thread to report back to us when the change we're looking for takes place. The process is similar to one we'll see in Chapter 9, "Log Files", when we discuss network sockets. Basically, we call a function that blocks, or hangs, until a change occurs:
while($thread−>Wait(INFINITE)){
print "Something changed!\n";
last if ($changes++ == 5);
}
This while( ) loop will call the Wait( ) method for our thread. This call will block until the thread has something to report. Wait( ) normally takes a parameter that dictates the number of milliseconds it should wait until giving up, though here we've given it a special value that says "wait forever." Once Wait( ) returns, we print a message and go back to waiting unless we've already noticed five other changes. We now clean up:
$thread−>Terminate( );
undef $aobj;
Our program isn't all that useful as written. All we know is something changed, but we don't know what changed or how it changed. To improve the situation, let's replace the contents of the while( ) loop and add a Perl format specification:
while($thread−>Wait(INFINITE)){
while ($thread−>Read(\@status)){
foreach $event (@status){
The key change here is the addition of the Read( ) method. Read( ) gets information about the change event and populates the @status list above with a set of hash references. Each reference points to an anonymous hash that looks something like this:
{'FileName' => '~GLF2425.TMP',
'DateTime' => '11/08/1999 06:23:25p', 'Directory' => 'C:\temp',
'Action' => 3 }
Multiple sets of event changes can queue up for every change, hence our need to call Read( ) in the while( ) loop until it runs out of steam. When we de−reference the contents of those hash references appropriately and pass them through a Perl format, we get handy output that like this:
File Name Date Action
4.4.1. Tracking Operations on Windows NT/2000 148
−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−−−−
~DF40DE.tmp 11/08/1999 07:29:56p FILE_ACTION_REMOVED
~DF6E5C.tmp 11/08/1999 07:29:56p FILE_ACTION_ADDED
~DF6E66.tmp 11/08/1999 07:29:56p FILE_ACTION_ADDED
~DF6E5C.tmp 11/08/1999 07:29:56p FILE_ACTION_REMOVED
Unfortunately, the tracking of network operations under NT/2000 is not nearly as impressive. Ideally, as an administrator you'd like to know which process (and therefore which user) has opened a network port.
Unfortunately, I know of no Perl module or free third−party command−line tool that can provide this information. There does exist a single commercial command−line tool called TCPVstat that can show us the network−connection−to−process mapping. TCPVstat is found in the TCPView Professional Edition package available at http://www.winternals.com.
If we are only able to use free tools, then we'll have to make do with a simple listing of the currently open network ports on our system. For that, we'll use another module by Ramdane called Win32::IpHelp.
Here's code to print this information:
use Win32::IpHelp;
# note: the case of "IpHelp" is signficant in this call my $iobj = new Win32::IpHelp;
# populates list of hash of hashes
$iobj−>GetTcpTable(\@table,1);
foreach $entry (@table){
print $entry−>{LocalIP}−>{Value} . ":" . $entry−>{LocalPort}−>{Value}. " −> ";
print $entry−>{RemoteIP}−>{Value} . ":" . $entry−>{RemotePort}−>{Value}."\n";
}
Let's see how we'd perform the same tasks from within the Unix world.