root/honeyclient/branches/bug/42/lib/HoneyClient/Agent/Integrity.pm

Revision 127, 31.7 kB (checked in by kindlund, 2 years ago)

Updated filesystem exclude list.

  • Property svn:keywords set to Id "$file"
Line 
1 ################################################################################
2 # Created on:  June 01, 2006
3 # Package:     HoneyClient::Agent
4 # File:        Integrity.pm
5 # Description: Module for checking the system integrity for possible
6 #              modifications.
7 #
8 # @author knwang, xkovah, ttruong
9 #
10 # Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
11 #
12 # This program is free software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation, using version 2
15 # of the License.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 # 02110-1301, USA.
26 #
27 ################################################################################
28
29 =pod
30
31 =head1 NAME
32
33 HoneyClient::Agent::Integrity - Responsible for performing static integrity
34 checks on the filesystem and registry. (Additionally it calls an external module
35 which is responsible for performing real-time checking for new processes which
36 are created.
37
38 =head1 VERSION
39
40 0.08
41
42 =head1 SYNOPSIS
43
44 =head2 INITIALIZATION
45
46 In order to properly check the system, a snapshot must be taken of a known-good
47 state.
48
49 For the filesystem this means a listing is created which contains
50 cryptographic hashes of files in their start state. The only files what are
51 checked are those which are explicitly specified in the checklist file (or are
52 found in a specified directory) and are not in the exclusion list will be checked.
53 Initialization of the filesystem is done with the initFileSystem() function,
54 described later.
55
56 For the registry a similar logic applies in that the only the specified keys are
57 checked and only if they are not in the exclusion list. The desired registry keys
58 are exported to a text file via the command line functionality of regedit. This
59 is done via initRegistry().
60
61
62 =head2 CHECKING
63
64 Checking the filesystem entails running mostly the same code as the initialization
65 piece in order to obtain a snapshot of the current state of the filesystem. At that
66 point additional checks are performed to look for additions, deletions, and
67 modifications to the filesystem. These checks are done with checkFileSystem().
68
69 A speed-optimized check of the registry is performed by first dumping the current
70 state, again with the command line version of regedit. Then the unix "diff"
71 utility is used to compare the clean registry dump to the current one. The output
72 from a diff is in a format which shows the minimum possible changes which can be
73 done to the first file in order to yield the same content as the second file.
74 Therefore this format must be parsed in order to determine what specific additions,
75 deletions, and modifications were made to the clean registry. Further, because
76 the output of diff need not exactly reflect changes (for instance when the same
77 content would be the first line of the previous value and the last line of the new
78 value) this requires some cases to re-consult the original and current state in order
79 to disambiguate the changes which were made. These tests are done in checkRegistry().
80
81 NOTE: Because these are simple, static, user-space checks, they can fail in the
82 presense of even user-space rootkits. Therefore these checks should not be taken as
83 definitive proof of the absense of malicious software until they are integrated more
84 tightly with the system.
85
86 =cut
87
88 package HoneyClient::Agent::Integrity;
89
90 use strict;
91 use warnings;
92 use Carp ();
93
94 =pod
95
96 =begin testing
97
98 # Make sure HoneyClient::Agent::Integrity loads.
99 BEGIN { use_ok('HoneyClient::Agent::Integrity', qw(initAll checkAll initRegistry checkRegistry initFileSystem checkFileSystem)) or diag("Can't load HoneyClient::Util::Config package.  Check to make sure the package library is correctly listed within the path."); }
100 require_ok('HoneyClient::Agent::Integrity');
101 #can_ok('HoneyClient::Agent::Integrity', 'new');
102 can_ok('HoneyClient::Agent::Integrity', 'initAll');
103 can_ok('HoneyClient::Agent::Integrity', 'checkAll');
104 can_ok('HoneyClient::Agent::Integrity', 'initFileSystem');
105 can_ok('HoneyClient::Agent::Integrity', 'checkFileSystem');
106 use HoneyClient::Agent::Integrity qw(initAll checkAll initFileSystem checkFileSystem);
107
108 # Make sure HoneyClient::Util::Config loads.
109 BEGIN { use_ok('HoneyClient::Util::Config', qw(getVar)) or diag("Can't load HoneyClient::Util::Config package.  Check to make sure the package library is correctly listed within the path."); }
110 require_ok('HoneyClient::Util::Config');
111 can_ok('HoneyClient::Util::Config', 'getVar');
112 use HoneyClient::Util::Config qw(getVar);
113
114 # Make sure File::Find loads.
115 BEGIN { use_ok('File::Find', qw(find)) or diag("Can't load File::Find package.  Check to make sure the package library is correctly listed within the path."); }
116 require_ok('File::Find');
117 can_ok('File::Find', 'find');
118 use File::Find;
119
120 # Make sure Digest::MD5 loads.
121 #BEGIN { use_ok('Digest::MD5', qw(new)) or diag("Can't load Digest::MD5 package.  Check to make sure the package library is correctly listed within the path."); }
122 #require_ok('Digest::MD5');
123 #use Digest::MD5;
124
125 # Make sure MIME::Base64 loads.
126 BEGIN { use_ok('MIME::Base64', qw(encode_base64 decode_base64)) or diag("Can't load MIME::Base64 package.  Check to make sure the package library is correctly listed within the path."); }
127 require_ok('MIME::Base64');
128 can_ok('MIME::Base64', 'encode_base64');
129 can_ok('MIME::Base64', 'decode_base64');
130 use MIME::Base64 qw(encode_base64 decode_base64);
131
132 # Make sure Storable loads.
133 BEGIN { use_ok('Storable', qw(dclone nfreeze thaw)) or diag("Can't load Storable package.  Check to make sure the package library is correctly listed within the path."); }
134 require_ok('Storable');
135 can_ok('Storable', 'dclone');
136 can_ok('Storable', 'nfreeze');
137 can_ok('Storable', 'thaw');
138 use Storable qw(dclone nfreeze thaw);
139
140 ###Testing Globals###
141 # Directory where the known-good test files are stored
142 $test_dir = getVar(name => "test_dir");
143
144 # List of files and directories to check during filesystem checking
145 $file_checklist = getVar(name => "file_checklist");
146
147 # List of files or directories to exclude if found in subdirs during
148 # filesystem check.
149 $file_exclude = getVar(name => "file_exclude");
150
151 # File where found changes are written to
152 $change_file = getVar(name => "change_file");
153
154 =end testing
155
156 =cut
157
158 # Include Global Configuration Processing Library
159 use HoneyClient::Util::Config qw(getVar);
160 use HoneyClient::Agent::Integrity::Registry;
161 use File::Find qw(find);
162 #use Win32::TieRegistry;
163 use Digest::MD5;
164 use MIME::Base64;
165 use Storable qw(nfreeze thaw dclone);
166 $Storable::Deparse = 1;
167 $Storable::Eval = 1;
168 use Data::Dumper;
169 use File::Basename qw(dirname);
170
171 BEGIN {
172     # Defines which functions can be called externally.
173     require Exporter;
174     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
175
176     # Set our package version.
177     $VERSION = 0.9;
178
179     @ISA = qw(Exporter);
180
181     # Symbols to export on request
182     @EXPORT = qw(new initAll checkAll);
183
184     # Items to export into callers namespace by default. Note: do not export
185     # names by default without a very good reason. Use EXPORT_OK instead.
186     # Do not simply export all your public functions/methods/constants.
187
188     # Symbols to autoexport (:DEFAULT tag)
189     @EXPORT_OK = qw(initAll checkAll);
190
191 }
192 our (@EXPORT_OK, $VERSION);
193
194
195
196 #####################
197 # GLOBALS
198 #####################
199
200 # Package Global Variable
201 our $AUTOLOAD;
202
203 # These two hack variables are necessary currently in order to get values back
204 # out of the functions used with the find() function from File::Find. I can
205 # not pass in/out the current object, so these get around that by making a
206 # global copy.
207 my $g_hack;
208 my $g_ex_hash;
209
210 #Used *for now* to signal whether any changes occured (if they == 1)
211 my $g_fs_changes = 0;
212
213 # XXX: All dirs must NEVER end in a trailing slash.
214 my @default_file_exclude_array = (
215     '/cygdrive/c/cygwin/tmp/changes.txt',
216     '/cygdrive/c/cygwin/tmp/cleanfile.txt',
217     '/cygdrive/c/cygwin/home/Administrator',
218     '/cygdrive/c/Documents and Settings/Administrator/Desktop/honeyclient',
219     '/cygdrive/c/WINDOWS/Prefetch',
220     '/cygdrive/c/WINDOWS/WindowsUpdate.log',
221     '/cygdrive/c/WINDOWS/Debug/UserMode/userenv.log',
222     '/cygdrive/c/WINDOWS/SoftwareDistribution/DataStore',
223     '/cygdrive/c/WINDOWS/SchedLgU.Txt',
224     '/cygdrive/c/WINDOWS/SoftwareDistribution/ReportingEvents.log',
225     '/cygdrive/c/WINDOWS/system32/config/SysEvent.Evt',
226     '/cygdrive/c/WINDOWS/system32/wbem',
227     '/cygdrive/c/WINDOWS/PCHEALTH/HELPCTR/DataColl',
228     '/cygdrive/c/Documents and Settings/All Users/Application Data/Microsoft/Network/Downloader',
229     '/cygdrive/c/Documents and Settings/Administrator/Application Data/Mozilla/Firefox/Profiles',
230     '/cygdrive/c/Documents and Settings/Administrator/Local Settings/Application Data/Mozilla/Firefox/Profiles',
231     '/cygdrive/c/Documents and Settings/Administrator/Application Data/Talkback/MozillaOrg/Firefox15/Win32/2006050817/permdata.box',
232     '/cygdrive/c/Documents and Settings/Administrator/Cookies/index.dat',
233     '/cygdrive/c/Documents and Settings/Administrator/Local Settings/History/History.IE5',
234     '/cygdrive/c/Documents and Settings/Administrator/Local Settings/Temporary Internet Files/Content.IE5',
235     '/cygdrive/c/Documents and Settings/Administrator/Recent',
236     '/cygdrive/c/Program Files/Mozilla Firefox/updates',
237     '/cygdrive/c/Program Files/Mozilla Firefox/active-update.xml',
238     '/cygdrive/c/Program Files/Mozilla Firefox/updates.xml',
239     '/cygdrive/c/WINDOWS/SoftwareDistribution/WuRedir',
240     '/cygdrive/c/WINDOWS/SYSTEM32/config/SecEvent.Evt',
241     '/cygdrive/c/WINDOWS/SYSTEM32/config/SysEvent.Evt',
242     '/cygdrive/c/WINDOWS/SYSTEM32/wbem/Repository/FS/INDEX.BTR',
243     '/cygdrive/c/WINDOWS/SYSTEM32/wbem/Repository/FS/INDEX.MAP',
244     '/cygdrive/c/WINDOWS/SYSTEM32/wbem/Repository/FS/MAPPING.VER',
245     '/cygdrive/c/WINDOWS/SYSTEM32/wbem/Repository/FS/MAPPING1.MAP',
246     '/cygdrive/c/WINDOWS/SYSTEM32/wbem/Repository/FS/MAPPING2.MAP',
247     '/cygdrive/c/WINDOWS/SYSTEM32/wbem/Repository/FS/OBJECTS.DATA',
248     '/cygdrive/c/WINDOWS/SYSTEM32/wbem/Repository/FS/OBJECTS.MAP',
249 );
250
251
252 my %PARAMS = (
253
254     # Contains the Registry object, once initialized.
255     _registry => undef,
256
257     # XXX: Clean the rest of these variables up.
258     ### Files which are read in only ###
259     # List of files and directories to check during filesystem checking
260     file_checklist => getVar(name => "file_checklist", namespace => "HoneyClient::Agent::Integrity"),
261    
262     # List of files or directories to exclude if found in subdirs during
263     # filesystem check.
264     file_exclude => getVar(name => "file_exclude", namespace => "HoneyClient::Agent::Integrity"),
265    
266     ### Files to write and read ###
267     # File to store hashes for files selected during the baseline
268     clean_file => getVar(name => "clean_file", namespace => "HoneyClient::Agent::Integrity"),
269    
270     # File to write any found changes to
271     change_file => getVar(name => "change_file", namespace => "HoneyClient::Agent::Integrity"),
272    
273     #vars
274     file_exclude_hash => undef, #hash, holds files to exclude
275     file_list => undef, #list, files to check when checking filesystem
276     
277     #works exactly like the reg_exclude_array, and is initialized in a similar way
278     file_exclude_array => undef,
279
280     changes => undef,   #multi-dimensional array used for holding individual instances of a diff output
281 );
282
283
284
285
286 ################################################################################
287
288 =pod
289
290 =head1 EXPORTED FUNCTIONS
291
292 new() : Creates a new Integrity object.
293
294 =cut
295
296 sub new {
297     # - This function takes in an optional hashtable,
298     #   that contains various key => 'value' configuration
299     #   parameters.
300     #
301     # - For each parameter given, it overwrites any corresponding
302     #   parameters specified within the default hashtable, %PARAMS,
303     #   with custom entries that were given as parameters.
304     #
305     # - Finally, it returns a blessed instance of the
306     #   merged hashtable, as an 'object'.
307
308     # Get the class name.
309     my $self = shift;
310
311     # Get the rest of the arguments, as a hashtable.
312     # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle
313     # hash references directly.  Thus, flat hashtables are used throughout the code
314     # for consistency.
315     my %args = @_;
316
317     # Check to see if the class name is inherited or defined.
318     my $class = ref($self) || $self;
319
320     # Initialize default parameters.
321     $self = { };
322     my %params = %{dclone(\%PARAMS)};
323     @{$self}{keys %params} = values %params;
324
325     # Now, overwrite any default parameters that were redefined
326     # in the supplied arguments.
327     @{$self}{keys %args} = values %args;
328
329     # Now, assign our object the appropriate namespace.
330     bless $self, $class;
331
332     # Finally, return the blessed object.
333     return $self;
334 }
335
336 ################################################################################
337
338 =pod
339
340 =head1
341
342 initAll() : Takes no input, runs all current init functions.
343
344 =cut
345
346 sub initAll {
347     my $self = shift;
348     # XXX: initRegistry() MUST be called before initFileSystem, since initRegistry
349     # creates new files that must exist to be added to the exclusion list for
350     # initFileSystem.
351     $self->{'_registry'} = HoneyClient::Agent::Integrity::Registry->new();
352     $self->initFileSystem();
353 }
354
355 ################################################################################
356
357 =pod
358
359 =head1
360
361 checkAll() : Takes no input, runs all current check functions.
362
363 =cut
364
365 sub checkAll {
366     my $self = shift;
367     my $retval;
368
369     # If at all possible we want the (faster) registry checks to short circut
370     # the overall checks so we don't have to do the very slow filesystem checks.
371     my $changes = $self->{'_registry'}->check();
372     if (scalar(@{$changes})) {
373         print "Registry has changed:\n";
374         foreach my $change (@{$changes}) {
375             print $change->{'key'} . " (" . $change->{'status'} . ")\n";
376         }
377         open CHANGES, ">>$self->{change_file}" or die "Cannot open $self->{change_file}: $!\n";     
378         $Data::Dumper::Terse = 1;
379         $Data::Dumper::Indent = 1;
380         print CHANGES Dumper($changes);
381         close CHANGES;
382         return $changes;
383     }
384     print "No registry changes have occurred.\n";
385     $retval = $self->checkFileSystem();
386
387     return $retval;
388 }
389
390 # TODO: Comment this.
391 sub serialize {
392     my $self = shift;
393
394     if (defined($self->{'_registry'})) {
395         $self->{'_registry'}->closeFiles();
396     }
397
398     return nfreeze($self);
399 }
400
401 ################################################################################
402
403 =pod
404
405 =head1
406
407 initFileSystem() : Takes no input, however it expects certain files to exist.
408 In particular it will not run unless there is a file checklist created,
409 containing a one-per-line list of files and directories to check. It
410 will automatically find all files in the specified directories as well as all
411 sub-directories. This file should be named as defined by $self->{file_checklist}.
412
413 Optionally there can be specified another file which lists files or subdirectories
414 which should be excluded from checks (for instance log files which are known to
415 change frequently). This file should be named as defined by $self->{file_exclude}.
416
417 =begin testing
418
419 #Testing initFileSystem();
420
421 my $ob = HoneyClient::Agent::Integrity->new();
422
423 system("mkdir /tmp/hc_test_dir");
424 system("echo hi > /tmp/hc_test_dir/hi.txt");
425 system("echo /tmp/hc_test_dir/hi.txt > $file_checklist");
426 system("echo /tmp/hc_test_dir/hi.txt > $file_exclude");
427 $ob->initFileSystem();
428 open (FILE, "cleanfile.txt") or die "Can't check the cleanfile.txt\n";
429 @result = <FILE>;
430 close FILE;
431 #Bad test because it will be empty in the case of an error anyway?
432 is(scalar(@result), 0, 'initFileSystem: Explicit Filesystem Omission');
433
434 system("rm $file_exclude");
435 system("echo hi > /tmp/hc_test_dir/hi.txt");
436 system("echo /tmp/hc_test_dir/hi.txt > $file_checklist");
437 system("echo /tmp/hc_test_dir/ > $file_exclude");
438 $ob->initFileSystem();
439 open (FILE, "cleanfile.txt") or die "Can't check the cleanfile.txt\n";
440 @result = <FILE>;
441 close FILE;
442 #Bad test because it will be empty in the case of an error anyway?
443 is(scalar(@result), 0, 'initFileSystem: Directory Filesystem Omission');
444
445 system("rm $file_exclude");
446 system("echo hi > /tmp/hc_test_dir/hi.txt");
447 system("echo /tmp/hc_test_dir/hi.txt > $file_checklist");
448 $ob->initFileSystem();
449 open (DIFF, "diff $test_dir/fs1.txt cleanfile.txt |") or die "Can't check the cleanfile.txt\n";
450 @result = <DIFF>;
451 close DIFF;
452 #Bad test because it will be empty in the case of an error anyway?
453 is(scalar(@result), 0, 'initFileSystem: Known-good file hash');
454
455 system("rm -rf /tmp/hc_test_dir/");
456 system("rm $file_checklist");
457
458 =end testing
459
460 =cut
461
462 sub initFileSystem {
463     # Get the object state.
464     my $self = shift;
465     $g_hack = $self->{file_list};
466     $g_ex_hash = ();
467     my $file;
468    
469     my @checkdirs = $self->_get_directories_to_check();
470     my @exclude_dirs = ();
471
472    
473     if(-e $self->{file_exclude}){
474         open EXCLUDE, "$self->{file_exclude}" or die "Can't open $self->{file_exclude} in initFileSystem()\n";
475         @{$self->{file_exclude_array}} = <EXCLUDE>;
476         close EXCLUDE;
477     }
478     else{
479         #use the defaults
480         $self->{file_exclude_array} = \@default_file_exclude_array;
481     }
482
483     $/ = "\n";
484     foreach $file (@{$self->{file_exclude_array}}){
485         chomp $file;
486         if(-f $file){
487             print "excluding $file\n";
488             $g_ex_hash->{$file} = 1;    #used in lieu of the $self->file_exclude_hash
489                                 # because you can't get to that in _found()
490         }
491         elsif(-d $file){
492             print "excluding $file\n";
493             $g_ex_hash->{$file} = 1;    #used in lieu of the $self->file_exclude_hash
494             #find (\&_recursive_exclude, $file);
495         }
496         else{
497             #XXX: Does this case matter(exist?) for pipes for instance?
498             print "A file that isn't a file or directory (or just general problem, or the file just isn't there) was found with file: $file\n";
499         }
500     }
501
502     print "Finding Files in initFileSystem...Be Patient.\n";
503     foreach my $checkdir (@checkdirs) {
504         find (\&_found, "$checkdir");   #this will populate @{$self->{file_list}}
505     }
506
507     $self->{file_list} = $g_hack;
508     $self->{file_exclude_hash} = $g_ex_hash;
509 ##  print "file_exclude_hash in init\n" . Dumper($self->{file_exclude_hash}) . "\n";
510
511     print "Hashing Files in initFileSystem...Be Patient\n";
512     open CLEANFILE, ">$self->{clean_file}" or die "Cannot open $self->{clean_file}: $!\n";
513     foreach $file (@{$self->{file_list}}) {
514 #       print "hashing $file\n";
515         if(open HASHFILE, "$file") {
516             my $md5ctx = Digest::MD5->new();
517             # If this call fails, an exception will be generated.
518             $md5ctx->addfile(*HASHFILE);
519             my $md5string = $md5ctx->hexdigest();
520             #print "writing $file:$md5string\n";
521             print CLEANFILE "$file:$md5string\n";
522             close HASHFILE;
523         }
524         else{
525             print "died trying to open $file\n";
526         }
527     }
528     close CLEANFILE;
529
530 }
531
532 ################################################################################
533
534 =pod
535
536 =head1
537
538 checkFileSystem() : Takes no input, but requires the file name by $self->{file_checklist}
539 to exist, as well as and optional $self->{file_exclude} if it existed at the time
540 initFileSystem() was called. Will detect any additions, deletions, or modifications
541 which occured in the filesystem.
542
543 =begin testing
544
545 #Testing that checkFileSystem()
546
547 my $ob = HoneyClient::Agent::Integrity->new();
548 my @result;
549
550 #add
551 system("rm $change_file");
552 system("mkdir /tmp/hc_test_dir/");
553 system("echo hi > /tmp/hc_test_dir/hi.txt");
554 system("echo /tmp/hc_test_dir/ > $file_checklist");
555 $ob->initFileSystem();
556 system("echo hi > /tmp/hc_test_dir/hi2.txt");
557 $ob->checkFileSystem();
558 open(CHECK, "diff $test_dir/fs2.txt $change_file |") or die "There was a problem doing the fs2.txt diff";
559 #XXX Won't the die statement just be masked by the redirection of stdout/stderr?
560 @result = <CHECK>;
561 close(CHECK);
562 is(scalar(@result), 0, "checkFileSystem: Files added");
563
564
565 #delete
566 system("rm $change_file");
567 system("rm /tmp/hc_test_dir/hi.txt");
568 $ob->checkFileSystem();
569 open(CHECK, "diff $test_dir/fs3.txt $change_file |") or die "There was a problem doing the fs2.txt diff";
570 #XXX Won't the die statement just be masked by the redirection of stdout/stderr?
571 @result = <CHECK>;
572 close(CHECK);
573 is(scalar(@result), 0, "checkFileSystem: Files deleted");
574
575 #change
576 system("rm $change_file");
577 system("echo again >> /tmp/hc_test_dir/hi.txt");
578 $ob->checkFileSystem();
579 open(CHECK, "diff $test_dir/fs4.txt $change_file |") or die "There was a problem doing the fs2.txt diff";
580 #XXX Won't the die statement just be masked by the redirection of stdout/stderr?
581 @result = <CHECK>;
582 close(CHECK);
583 is(scalar(@result), 0, "checkFileSystem: Files changed");
584
585 system("rm -rf /tmp/hc_test_dir/");
586 system("rm $file_checklist");
587
588 =end testing
589
590 =cut
591
592 sub checkFileSystem {
593
594     my $self = shift;   #Object
595     %{$self->{clean_file_hash}} = ();
596     %{$self->{changed_file_hash}} = ();
597     my %current_file_hash = ();
598     my %new_file_hash = ();
599     my %del_file_hash = ();
600     my @checkdirs;
601     my $standalone_test = 0;
602     my $file;
603     my $key;
604
605 ### print "file_exclude_hash in check\n" . Dumper($self->{file_exclude_hash}) . "\n";
606
607
608     if($standalone_test){
609         $g_hack = $self->{file_list};
610     }
611     #open file to create hash of values for clean files
612     open CLEANFILE, "$self->{clean_file}" or die "Cannot open $self->{clean_file}: $!\n";
613     $/ = "\n";
614     while(<CLEANFILE>) {
615         my $line = $_;
616         chomp($line);
617         if($line =~ /(.+):(\w+)/) {
618             $self->{clean_file_hash}->{$1} = $2;
619         } else {
620             print "Malformed line in $self->{clean_file}: $line\n";
621         }
622     }
623     close CLEANFILE;
624
625     @checkdirs = $self->_get_directories_to_check();
626
627     # NOTE: Unfortunately I now believe that because of the nature
628     # of some of the directories which we are checking, we need to
629     # re-create this list of excluded files each time. This is because
630     # otherwise we can not prevent it from thinking that files added
631     # to those directories were added, because they did not exist
632     # at the time the exclusion list was created.
633
634     # NOTE: This will hopefully be fixed when we add regular expression
635     # support for both checking files and excluding files
636
637
638     #************ FOR STANDALONE METHOD TESTING ONLY
639     if($standalone_test){
640         if(-e $self->{file_exclude}){
641             open EXCLUDE, "$self->{file_exclude}" or die "Can't open $self->{file_exclude} in initFileSystem()\n";
642             @{$self->{file_exclude_array}} = <EXCLUDE>;
643             close EXCLUDE;
644         }
645         else{
646             #use the defaults
647             $self->{file_exclude_array} = \@default_file_exclude_array;
648         }
649    
650         $/ = "\n";
651         foreach $file (@{$self->{file_exclude_array}}){
652             chomp $file;
653             if(-f $file){
654                 print "excluding $file\n";
655                 $g_ex_hash->{$file} = 1;    #used in lieu of the $self->file_exclude_hash
656                                     # because you can't get to that in _found()
657             }
658             else { if(-d  $file){
659                     find (\&_recursive_exclude, $file);
660                 }
661                 else{
662                     #XXX: Does this case matter(exist?) for pipes for instance?
663                     print "A file that isn't a file or directory (or just general problem, or the file just isn't there) was found with file: $file\n";
664                 }
665             }
666         }
667     }
668     #**************
669
670     print "Finding Files in checkFileSystem...Be Patient\n";
671     @{$g_hack} = ();
672     foreach my $checkdir(@checkdirs) {
673         find (\&_found, "$checkdir");
674     }
675    
676     $self->{file_list} = $g_hack;
677
678     #iterate through hash checking current files against previous files
679     #also detects new files
680     print "Hashing Files in checkFileSystem...Be Patient\n";
681     foreach $file (@{$self->{file_list}}) {
682         if(open HASHFILE, "$file") {
683             my $md5ctx = Digest::MD5->new();
684             # If this call fails, an exception will be generated.
685             $md5ctx->addfile(*HASHFILE);
686             close HASHFILE;
687             $current_file_hash{$file} = $md5ctx->hexdigest();
688             if($self->{clean_file_hash}->{$file}) {
689                 if($self->{clean_file_hash}->{$file} ne $current_file_hash{$file}) {
690                     $self->{changed_file_hash}->{$file} = $current_file_hash{$file};
691                 }
692             } else {
693                 $new_file_hash{$file} = $current_file_hash{$file};
694             }
695         }
696     }
697
698     #check for deleted files
699     foreach $key (keys %{$self->{clean_file_hash}}) {
700         if(!($current_file_hash{$key})) {
701             $del_file_hash{$key} = $self->{clean_file_hash}->{$key};
702         }
703     }
704
705     if((scalar(keys %del_file_hash) > 0) ||
706         (scalar(keys %new_file_hash) > 0) ||
707         (scalar(keys %{$self->{changed_file_hash}}) > 0)) {
708         open CHANGES, ">>$self->{change_file}" or die "Cannot open $self->{change_file}: $!\n";     
709
710         print CHANGES "Files deleted:\n";
711         foreach $key (sort keys %del_file_hash) {
712             print CHANGES "$key\n";
713         }
714         print CHANGES "\n\n";
715         print CHANGES "Files added:\n";
716         foreach $key (sort keys %new_file_hash) {
717             print CHANGES "$key\n";
718         }
719         print CHANGES "\n\n";
720         print CHANGES "Files modified:\n";
721         foreach $key (sort keys %{$self->{changed_file_hash}}) {
722             print CHANGES "$key\n";
723         }
724         print CHANGES "\n\n";
725         $g_fs_changes = 1;
726         close CHANGES;
727
728         print "Changes detected in the filesystem. Written to $self->{change_file}\n";
729     }
730     else{
731         print "No changes detected in the filesystem\n";
732     }
733     return $g_fs_changes;
734
735 }
736
737 ################################################################################
738
739 sub _get_directories_to_check(){
740 my $self = shift;
741 my @checkdirs;
742
743     #Sets the VERY hardcoded default for now (late addition for ease of use, not clean code)
744     push @checkdirs, "/cygdrive/c/";
745
746     #Can override the default by creating this file (for now, eventually put directly into XML)
747     if($self->{file_checklist} ne "none"){
748         open CHECK, "$self->{file_checklist}" or
749         die "You need a file named $self->{file_checklist} with fully-qualified " .
750          "file and directory names to check in order to run this program. " .
751          "Please see the POD documentation for more information.\n";
752         @checkdirs = ();
753         $/ = "\n";
754         while(<CHECK>) {
755             chomp;
756             print "reading in $_ from $self->{file_checklist}\n";
757             push @checkdirs, $_;
758         }
759         close CHECK;
760     }
761     return @checkdirs;
762 }
763
764 ################################################################################
765
766 # _found() takes input from $file_checklist, which is a file created by the
767 # user specifying which directory to baseline and check. As long as the
768 # directory is not in $file_exclude, then proceed with check.
769 # Uses the global hash reference hack because
770 # it's used by find() so can't take in/pass back stuff
771
772 sub _found {
773        
774     my $foundfile = $File::Find::name;
775    
776     if (-f $foundfile) {
777         if (exists($g_ex_hash->{$foundfile})) {
778             return;
779         }
780
781         my $dir = dirname($foundfile);
782         while ($dir ne "/") {
783             # XXX: Need to add some sort of logic to account
784             # for off-by-one key names (i.e., /dir versus /dir/).
785             if (exists($g_ex_hash->{$dir})) {
786                 return;
787             }
788             $dir = dirname($dir);
789         }
790
791 #       if (!exists($g_ex_hash->{$foundfile})) {
792             push @{$g_hack}, $foundfile;
793 #           print "found $foundfile\n";
794 #           print "@{$g_hack}\n";
795 #       }
796     }
797 }
798
799 ################################################################################
800
801 # This function is used to delve down into directories and make sure all the
802 # contained files are excluded. Uses the global hash reference hack because
803 # it's used by find() so can't take in/pass back stuff
804
805 sub _recursive_exclude{
806    
807    my $foundfile = $File::Find::name;
808     if (-f $foundfile) {
809         $g_ex_hash->{$foundfile} = 1;
810 ##      print "\t _recursive_exclude()d $foundfile\n";
811     }
812 }
813
814 ################################################################################
815
816 # Helper function designed to programmatically get or set parameters
817 # within this object, through indirect use of the AUTOLOAD function.
818 #
819 # It's best to explain by example:
820 # Assume we have defined a driver object, like the following.
821 #
822 # use HoneyClient::Agent::Driver;
823 # my $driver = Driver->new(someVar => 'someValue');
824 #
825 # What this function allows us to do, is programmatically, get or set
826 # the 'someVar' parameter, like:
827 #
828 # my $value = $driver->someVar();    # Gets the value of 'someVar'.
829 # my $value = $driver->someVar('2'); # Sets the value of 'someVar' to '2'
830 #                                    # and returns '2'.
831 #
832 # Rather than creating getter/setter functions for every possible parameter,
833 # the AUTOLOAD function allows us to create these operations in a generic,
834 # reusable fashion.
835 #
836 # See "Autoloaded Data Methods" in perltoot for more details.
837 #
838 # Inputs: set a new value (optional)
839 # Outputs: the currently set value
840 sub AUTOLOAD {
841     # Get the object.
842     my $self = shift;
843
844     # Sanity check: Make sure the supplied value is an object.
845     my $type = ref($self) or Carp::croak "Error: $self is not an object!\n";
846
847     # Now, get the name of the function.
848     my $name = $AUTOLOAD;
849
850     # Strip the fully-qualified portion of the function name.
851     $name =~ s/.*://;
852
853     # Make sure the parameter exists in the object, before we try
854     # to get or set it.
855     unless (exists $self->{$name}) {
856         Carp::croak "Error: Can't access '$name' parameter in class $type!\n";
857     }
858
859     if (@_) {
860         # If we were given an argument, then set the parameter's value.
861         return $self->{$name} = shift;
862     } else {
863         # Else, just return the existing value.
864         return $self->{$name};
865     }
866 }
867
868 ################################################################################
869
870 # Base destructor function.
871 # Since none of our state data ever contains circular references,
872 # we can simply leave the garbage collection up to Perl's internal
873 # mechanism.
874 #sub DESTROY {
875 #}
876 ################################################################################
877
878
879 1;
880
881 =pod
882
883 =head1 BUGS & ASSUMPTIONS
884
885 BUG: Can not handle the "hidden" keys which contain null string and can only be
886 read/written with special calls to the windows "Native API"
887 More about this problem referenced here:
888
889 BUG: Can't deal with http://www.sysinternals.com/Information/TipsAndTrivia.html#HiddenKeys
890