root/honeyclient/tags/exp/UP2-kindlund-dynamic_updates/lib/HoneyClient/Agent.pm

Revision 796, 40.2 kB (checked in by kindlund, 1 year ago)

Version bump.

  • Property svn:keywords set to Id "$file"
Line 
1 #######################################################################
2 # Created on:  May 11, 2006
3 # Package:     HoneyClient::Agent
4 # File:        Agent.pm
5 # Description: Central library used for agent-based operations.
6 #
7 # CVS: $Id$
8 #
9 # @author knwang, ttruong, kindlund
10 #
11 # Copyright (C) 2007 The MITRE Corporation.  All rights reserved.
12 #
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation, using version 2
16 # of the License.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301, USA.
27 #
28 #######################################################################
29
30 =pod
31
32 =head1 NAME
33
34 HoneyClient::Agent - Perl extension to instantiate a SOAP server
35 that provides a central interface for all agent-based HoneyClient
36 operations.
37
38 =head1 VERSION
39
40 0.99
41
42 =head1 SYNOPSIS
43
44 =head2 CREATING THE SOAP SERVER
45
46 # XXX: Fill this in.
47
48 =head2 INTERACTING WITH THE SOAP SERVER
49
50 # XXX: Fill this in.
51
52 =head1 DESCRIPTION
53
54 This library creates a SOAP server within the HoneyClient VM, allowing
55 the HoneyClient::Manager to perform agent-based operations within the
56 VM.
57
58 =cut
59
60 package HoneyClient::Agent;
61
62 # XXX: Disabled version check, Honeywall does not have Perl v5.8 installed.
63 #use 5.008006;
64 use strict;
65 use warnings FATAL => 'all';
66 use Config;
67 use Carp ();
68 # TODO: This can go away.
69 use POSIX qw(SIGALRM);
70
71 #######################################################################
72 # Module Initialization                                               #
73 #######################################################################
74
75 BEGIN {
76     # Defines which functions can be called externally.
77     require Exporter;
78     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
79
80     # Set our package version.
81     $VERSION = 0.99;
82
83     @ISA = qw(Exporter);
84
85     # Symbols to export automatically
86     @EXPORT = qw();
87
88     # Items to export into callers namespace by default. Note: do not export
89     # names by default without a very good reason. Use EXPORT_OK instead.
90     # Do not simply export all your public functions/methods/constants.
91
92     # This allows declaration use HoneyClient::Agent ':all';
93     # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
94     # will save memory.
95
96     %EXPORT_TAGS = (
97         'all' => [ qw() ],
98     );
99
100     # Symbols to autoexport (when qw(:all) tag is used)
101     @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
102
103     # Check to make sure our OS is Windows-based.
104     # XXX: Fix this!
105     #if ($Config{osname} !~ /^MSWin32$/) {
106     #    Carp::croak "Error: " . __PACKAGE__ . " will only run on Win32 platforms!\n";
107     #}
108
109     # Check to see if ithreads are compiled into this version of Perl.
110     $Config{useithreads} or Carp::croak "Error: Recompile Perl with ithread support, in order to use this module.\n";
111
112     $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
113 }
114 our (@EXPORT_OK, $VERSION);
115
116 =pod
117
118 =begin testing
119
120 # Make sure the module loads properly, with the exportable
121 # functions shared.
122 BEGIN { use_ok('HoneyClient::Agent') or diag("Can't load HoneyClient::Agent package.  Check to make sure the package library is correctly listed within the path."); }
123 require_ok('HoneyClient::Agent');
124 can_ok('HoneyClient::Agent', 'init');
125 can_ok('HoneyClient::Agent', 'destroy');
126 use HoneyClient::Agent;
127
128 # Make sure HoneyClient::Util::SOAP loads.
129 BEGIN { use_ok('HoneyClient::Util::SOAP', qw(getServerHandle getClientHandle)) or diag("Can't load HoneyClient::Util::SOAP package.  Check to make sure the package library is correctly listed within the path."); }
130 require_ok('HoneyClient::Util::SOAP');
131 can_ok('HoneyClient::Util::SOAP', 'getServerHandle');
132 can_ok('HoneyClient::Util::SOAP', 'getClientHandle');
133 use HoneyClient::Util::SOAP qw(getServerHandle getClientHandle);
134
135 # Make sure HoneyClient::Util::Config loads.
136 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."); }
137 require_ok('HoneyClient::Util::Config');
138 can_ok('HoneyClient::Util::Config', 'getVar');
139 use HoneyClient::Util::Config qw(getVar);
140
141 # TODO: Include FF
142 # Make sure HoneyClient::Agent::Driver::Browser::IE loads.
143 BEGIN { use_ok('HoneyClient::Agent::Driver::Browser::IE') or diag("Can't load HoneyClient::Agent::Driver::Browser::IE package.  Check to make sure the package library is correctly listed within the path."); }
144 require_ok('HoneyClient::Agent::Driver::Browser::IE');
145 # TODO: Update this list of function names.
146 can_ok('HoneyClient::Agent::Driver::Browser::IE', 'new');
147 can_ok('HoneyClient::Agent::Driver::Browser::IE', 'drive');
148 can_ok('HoneyClient::Agent::Driver::Browser::IE', 'getNextLink');
149 can_ok('HoneyClient::Agent::Driver::Browser::IE', 'next');
150 can_ok('HoneyClient::Agent::Driver::Browser::IE', 'isFinished');
151 can_ok('HoneyClient::Agent::Driver::Browser::IE', 'status');
152 use HoneyClient::Agent::Driver::Browser::IE;
153
154 # Make sure Storable loads.
155 BEGIN { use_ok('Storable', qw(freeze nfreeze thaw dclone)) or diag("Can't load Storable package.  Check to make sure the package library is correctly listed within the path."); }
156 require_ok('Storable');
157 can_ok('Storable', 'freeze');
158 can_ok('Storable', 'nfreeze');
159 can_ok('Storable', 'thaw');
160 can_ok('Storable', 'dclone');
161 use Storable qw(freeze nfreeze thaw dclone);
162
163 # Make sure MIME::Base64 loads.
164 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."); }
165 require_ok('MIME::Base64');
166 can_ok('MIME::Base64', 'encode_base64');
167 can_ok('MIME::Base64', 'decode_base64');
168 use MIME::Base64 qw(encode_base64 decode_base64);
169
170 #XXX: Check to see if the port number should be externalized.
171 # Global test variables.
172 our $PORT = getVar(name      => "port",
173                    namespace => "HoneyClient::Agent");
174 our ($stub, $som);
175
176 =end testing
177
178 =cut
179
180 #######################################################################
181
182 # Include the SOAP Utility Library
183 use HoneyClient::Util::SOAP qw(getClientHandle getServerHandle);
184
185 # Include Integrity Library
186 # TODO: Include corresponding unit tests.
187 use HoneyClient::Agent::Integrity;
188
189 # Include Thread Libraries
190 use threads;
191 use threads::shared;
192 use Thread::Semaphore;
193 use Thread::Queue;
194
195 # Include utility access to global configuration.
196 use HoneyClient::Util::Config qw(getVar);
197
198 # XXX: Remove this, eventually.
199 use Data::Dumper;
200
201 # Include Hash Serialization Utility Libraries
202 use Storable qw(freeze nfreeze thaw dclone);
203 $Storable::Deparse = 1;
204 $Storable::Eval = 1;
205
206 # Include Base64 Libraries
207 use MIME::Base64 qw(encode_base64 decode_base64);
208
209 # Include Data Differential Analysis Libraries
210 # TODO: Include corresponding unit tests.
211 # XXX: Do we need this?
212 use Data::Diff;
213 # TODO: Include corresponding unit tests.
214 # XXX: Do we need this?
215 use Data::Structure::Util qw(unbless);
216 # TODO: Include corresponding unit tests.
217 # XXX: Do we need this?
218 use Data::Compare;
219
220 # Include Logging Library
221 use Log::Log4perl qw(:easy);
222
223 # The global logging object.
224 our $LOG = get_logger();
225
226 # Complete URL of SOAP server, when initialized.
227 our $URL_BASE       : shared = undef;
228 our $URL            : shared = undef;
229
230 # The process ID of the SOAP server daemon, once created.
231 our $DAEMON_PID     : shared = undef;
232
233 # Global array, to indicate which implemented Drivers the
234 # Agent is allowed to run.
235 our $ALLOWED_DRIVERS = getVar(name => 'allowed_drivers')->{name};
236
237 # Global value, to indicate if the Agent should perform
238 # any integrity checks.
239 our $PERFORM_INTEGRITY_CHECKS : shared =
240     getVar(name => "perform_integrity_checks");
241
242 # A globally shared object, containing the initialized integrity
243 # state of the VM -- ready to be checked against, at any time after
244 # initialization.
245 our $integrityData;
246
247 # A globally shared, serialized hashtable, containing data per
248 # registered driver.  Specifically, for each @DRIVER <entry>,
249 # the following data is created:
250 #   '<entry_name>' => {
251 #       'state'     => undef; # Driver-specific state information.
252 #       'thread_id' => undef; # The thread registered to handle
253 #                             # the driver.
254 #       'status'    => undef; # Driver-specific status information.
255 #       'next'      => undef; # Driver-specific connection information.
256 #   }
257 our $driverData     : shared = undef;
258
259 # A global shared semaphore, designed to limit read/write
260 # access to $driverData, by only allowing one thread
261 # at a time to freeze/thaw the data.  While $driverData is
262 # a scalar, the freeze/thaw operation is not atomic; thus,
263 # this semaphore ensures all operations remain atomic.
264 our $driverDataSemaphore     = Thread::Semaphore->new(1);
265
266 # A globally shared hashtable, containing one "update queue"
267 # per driver.  This allows different "driver threads" to
268 # receive asynchronous updates to their state information
269 # in a thread-safe manor.
270 our %driverUpdateQueues : shared = ( );
271
272 #######################################################################
273 # Daemon Initialization / Destruction                                 #
274 #######################################################################
275
276 =pod
277
278 =head1 LOCAL FUNCTIONS
279
280 The following init() and destroy() functions are the only direct
281 calls required to startup and shutdown the SOAP server.
282
283 All other interactions with this daemon should be performed as
284 C<SOAP::Lite> function calls, in order to ensure consistency across
285 client sessions.  See the L<"EXTERNAL SOAP FUNCTIONS"> section, for
286 more details.
287
288 =head2 HoneyClient::Agent->init(address => $localAddr, port => $localPort, ...)
289
290 =over 4
291
292 Starts a new SOAP server, within a child process.
293
294 I<Inputs>:
295  B<$localAddr> is an optional argument, specifying the IP address for the SOAP server to listen on.
296  B<$localPort> is an optional argument, specifying the TCP port for the SOAP server to listen on.
297
298 Additionally optional, driver-specific arguments can be specified
299 as sub-hashtables, where the top-level key corresponds to the name of
300 the implemented driver and the value contains all the expected hash data
301 that can be fed to HoneyClient::Agent::Driver->new() instances.
302
303  Here is an example set of arguments:
304
305    HoneyClient::Agent->init(
306        address => '127.0.0.1',
307        port    => 9000,
308        IE      => {
309            timeout => 30,
310            links_to_visit => {
311                'http://www.mitre.org/' => 1,
312            },
313        },
314    );
315
316  
317 I<Output>: The full URL of the web service provided by the SOAP server.
318
319 =back
320
321 =begin testing
322
323 # XXX: Test init() method.
324 our $URL = HoneyClient::Agent->init();
325 our $PORT = getVar(name      => "port",
326                    namespace => "HoneyClient::Agent");
327 our $HOST = getVar(name      => "address",
328                    namespace => "HoneyClient::Agent");
329 is($URL, "http://$HOST:$PORT/HoneyClient/Agent", "init()") or diag("Failed to start up the VM SOAP server.  Check to see if any other daemon is listening on TCP port $PORT.");
330
331 =end testing
332
333 =cut
334
335 # TODO: Update documentation to reflect hash-based args.
336 sub init {
337     # Extract arguments.
338     # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle
339     # hash references directly.  Thus, flat hashtables are used throughout the code
340     # for consistency.
341     my ($class, %args) = @_;
342
343     # Sanity check.  Make sure the daemon isn't already running.
344     if (defined($DAEMON_PID)) {
345         $LOG->fatal("Error: " . __PACKAGE__ . " daemon is already running (PID = " . $DAEMON_PID .")!");
346         Carp::croak "Error: " . __PACKAGE__ . " daemon is already running (PID = $DAEMON_PID)!\n";
347     }
348
349     # Figure out what our list of allowed Drivers are.
350     $ALLOWED_DRIVERS = getVar(name => 'allowed_drivers')->{name};
351
352     # Acquire data lock.
353     _lock();
354
355     # Initialize the $driverData shared hashtable.
356     my $data = { };
357     for my $driverName (@{$ALLOWED_DRIVERS}) {
358
359         eval "use $driverName";
360         if ($@) {
361             $LOG->fatal($@);
362             Carp::croak $@;
363         }
364  
365         $data->{$driverName} = {
366             'state'     => undef,
367             'thread_id' => undef,
368             'status'    => undef,
369             'next'      => undef,
370         };
371
372         # Initialize the corresponding %driverUpdateQueues
373         $driverUpdateQueues{$driverName} = new Thread::Queue;
374     }
375
376     # Perform initial integrity baseline check.
377     if ($PERFORM_INTEGRITY_CHECKS) {
378         $integrityData = HoneyClient::Agent::Integrity->new();
379         $integrityData->closeFiles();
380     }
381
382     # Release data lock.
383     _unlock($data);
384
385     my $argsExist = scalar(%args);
386
387     if (!($argsExist &&
388           exists($args{'address'}) &&
389           defined($args{'address'}))) {
390         $args{'address'} = getVar(name => "address");
391     }
392
393     if (!($argsExist &&
394           exists($args{'port'}) &&
395           defined($args{'port'}))) {
396         $args{'port'} = getVar(name => "port");
397     }
398
399     $URL_BASE = "http://" . $args{'address'} . ":" . $args{'port'};
400     $URL = $URL_BASE . "/" . join('/', split(/::/, __PACKAGE__));
401
402     my $pid = undef;
403     if ($pid = fork) {
404         # We use a local variable to get the pid, and then we set the global
405         # DAEMON_PID variable after the fork().  This is intentional, because
406         # it seems the Win32 version of fork() doesn't seem to be an atomic
407         # operation.
408         $DAEMON_PID = $pid;
409         return $URL;
410    
411     } else {
412         # Make sure the fork was successful.
413         if (!defined($pid)) {
414             $LOG->fatal("Error: Unable to fork child process.\n$!");
415             Carp::croak "Error: Unable to fork child process.\n$!";
416         }
417
418         # Do not attempt to rejoin parent process tree,
419         # if any type of termination signal is received.
420         local $SIG{HUP} = sub { exit; };
421         local $SIG{INT} = sub { exit; };
422         local $SIG{QUIT} = sub { exit; };
423         local $SIG{ABRT} = sub { exit; };
424         local $SIG{PIPE} = sub { exit; };
425         local $SIG{TERM} = sub { exit; };
426
427         my $daemon = getServerHandle(address => $args{'address'},
428                                      port    => $args{'port'});
429
430         # Populate our driver's object state with the remaining
431         # arguments.
432         delete($args{'address'});
433         delete($args{'port'});
434
435         # If this call fails, an exception is thrown or the process
436         # remains locked.  If the process locks, then external
437         # detection is used to catch for these types of failures.
438         updateState($class, encode_base64(nfreeze(\%args)));
439    
440         for (;;) {
441             $daemon->handle();
442         }
443     }
444 }
445
446 =pod
447
448 =head2 HoneyClient::Agent->destroy()
449
450 =over 4
451
452 Terminates the SOAP server within the child process.
453
454 I<Output>: True if successful, false otherwise.
455
456 =back
457
458 =begin testing
459
460 # XXX: Test destroy() method.
461 is(HoneyClient::Agent->destroy(), 1, "destroy()") or diag("Unable to terminate Agent SOAP server.  Be sure to check for any stale or lingering processes.");
462
463 # TODO: delete this.
464 #exit;
465
466 =end testing
467
468 =cut
469
470 sub destroy {
471     my $ret = undef;
472     # Make sure the PID is defined and not
473     # the parent process...
474     if (defined($DAEMON_PID) && ($DAEMON_PID != 0)) {
475         $LOG->error("Killing PID = " . $DAEMON_PID);
476         print STDERR "Killing PID = " . $DAEMON_PID . "\n";
477         # The Win32 version of kill() seems to only respond to SIGKILL(9).
478         # XXX: This doesn't work.
479         #$ret = kill(9, $DAEMON_PID);
480         
481         # TODO: Need unit tests.
482         require Win32::Process;
483         Win32::Process::KillProcess($DAEMON_PID, 0);
484         $ret = 1;
485     }
486     if ($ret) {
487         # Acquire data lock.
488         _lock();
489
490         # Destroy all globally shared state data.
491         $URL                  = undef;
492         $URL_BASE             = undef;
493         $DAEMON_PID           = undef;
494         $driverData           = undef;
495         $driverDataSemaphore  = Thread::Semaphore->new(1);
496         %driverUpdateQueues   = ( );
497
498         # Destroy all integrity data, if defined.
499         if (defined($integrityData)) {
500             $integrityData->destroy();
501         }
502         $integrityData        = undef;
503        
504         # Release data lock.
505         _unlock();
506     }
507     return $ret;
508 }
509
510 #######################################################################
511 # Private Methods Implemented                                         #
512 #######################################################################
513
514 # Helper function designed to acquire exclusive access to the
515 # shared $driverData, for use within any thread.
516 #
517 # In perl, it is difficult to share hashtables between threads.
518 # However, it is easy to share scalars between threads.
519 # As such, we share a hashtable between threads by *serializing*
520 # the data using nfreeze().  The result can be stored in a scalar.
521 #
522 # When we are in a thread where we subsequently want to read/use
523 # this hashtable, we thaw() the serialized data (it performs the
524 # deserialization process) and use the hashtable accordingly.
525 #
526 # This function guarantees that no other thread will access
527 # $driverData and returns the thaw()'d contents of $driverData.
528 #
529 # Input: None
530 # Output: driverData (deserialized)
531 sub _lock {
532     # Acquire lock on stored driver state.
533     $driverDataSemaphore->down();
534        
535     # Thaw the data.
536     return thaw($driverData);
537 }
538
539 # Helper function designed to release exclusive access to the
540 # shared $driverData, for use within any thread.
541 #
542 # By calling this function, we assume that the thread has already
543 # called _lock() and would like to (optionally) update $driverData
544 # with a new, modified hashtable, prior to releasing the lock
545 # on $driverData.
546 #
547 # This function can optionally take in a normal hashtable reference,
548 # overwriting the $driverData with the contents of the supplied
549 # hashtable.  Once the $driverData's updated contents has been
550 # set and serialized, this function releases the corresponding
551 # lock.
552 #
553 # Input: driverData (deserialized, optional)
554 # Output: None
555 sub _unlock {
556     my $data = shift;
557
558     if (defined($data)) {
559         # Refreze changed data.
560         $driverData = nfreeze($data);
561     }
562    
563     # Release lock on stored driver state.
564     $driverDataSemaphore->up();
565 }
566
567 # Helper function designed to retrieve queued, external
568 # updates to driver state information from %driverUpdateQueues.
569 #
570 # When called from run(), this function takes in the corresponding
571 # Driver object; checks to see if there's a new entry within the
572 # driver's corresponding update queue; and dequeues the *all*
573 # entries in the queue, overwriting the Driver's state data
574 # accordingly.
575 #
576 # The external updateState() call adds new driver state into the queue,
577 # one entry per call.  The internal _update() function merges this
578 # driver state with the currently running driver, merging everything
579 # queued per call.  In order words, a single call to _update()
580 # *WILL* empty the corresponding Driver update queue completely
581 # -- all entries within the queue will be dequeued per _update()
582 # call made.
583 #
584 # Input: driver
585 # Output: driver (updated)
586 sub _update {
587     # Extract arguments.
588     my $driver = shift;
589
590     # Figure out the corresponding driver name.
591     my $driverName = ref($driver);
592
593     # Extract the corresponding queue.
594     my $queue = $driverUpdateQueues{$driverName};
595
596     # XXX: One possible DoS condition here; what if
597     # the manager keeps feeding updates to the Agent
598     # before the Agent has a chance to do any work?
599     
600     # If we have data in our driver specific queue...
601     while ($queue->pending) {
602
603         # Update our driver state with the first entry
604         # found...
605         my $queuedData = thaw($queue->dequeue_nb);
606
607         # Sanity check: Only copy defined data.
608         if (defined($queuedData)) {
609
610             # Copy (and overwrite) overloaded object data
611             # into shared memory.  This looks creepy, I know, but
612             # it actually works.  We're essentially identifying
613             # driver-specific parameters that the user supplied
614             # via $queuedData and overwriting our current driver state
615             # with any matching, user supplied values.
616             @{$driver}{keys %{$queuedData}} = values %{$queuedData};
617         }
618     }
619
620     # Return the modified driver state.
621     return $driver;
622 }
623
624 #######################################################################
625 # Public Methods Implemented                                          #
626 #######################################################################
627
628 =pod
629
630 =head1 EXTERNAL SOAP FUNCTIONS
631
632 =head2 run(driver_name => $driverName)
633
634 =over 4
635
636 Runs the Agent for one cycle.  In this cycle, the following happens:
637
638 =over 4
639
640 =item 1)
641
642 The specified Driver is driven for multiple work units, where each
643 consecutive drive operation contacts the same network resources
644 (aka. "targets").  The Driver ceases its operation, as soon as
645 it has exhausted all targets or until it is ready to contact a
646 different set of targets.
647
648 =item 2)
649
650 Once the specified driver has stopped, the Agent performs a corresponding
651 Integrity check.
652
653 =back
654
655 # XXX: Fill this in.
656
657 I<Inputs>:
658  B<$driverName> is the name of the Driver to use, when running this
659 cycle.
660  
661 I<Output>: Returns true if the Agent successfully started a new cycle;
662 returns false, if the Agent is still running an existing cycle and
663 has not finished yet.
664
665 I<Notes>:
666 During a single run() cycle, it is expected that the driven application
667 will only contact the same targets.  This allows the Manager to update
668 firewall rules between cycles.
669
670 =back
671
672 #=begin testing
673 #
674 # XXX: Fill this in.
675 #
676 #=end testing
677
678 =cut
679
680 sub run {
681     # Extract arguments.
682     my ($class, %args) = @_;
683
684     # Log resolved arguments.
685     $LOG->debug(sub {
686         # Make Dumper format more terse.
687         $Data::Dumper::Terse = 1;
688         $Data::Dumper::Indent = 0;
689         Dumper(\%args);
690     });
691
692     # Sanity check.  Make sure we get a valid argument.
693     my $argsExist = scalar(%args);
694     if (!$argsExist ||
695         !exists($args{'driver_name'}) ||
696         !defined($args{'driver_name'})) {
697
698         # Die if no valid argument is supplied.
699         $LOG->warn("No Driver name specified.");
700         die SOAP::Fault->faultcode(__PACKAGE__ . "->run()")
701                        ->faultstring("No Driver name specified.");
702     }
703
704     # Sanity check.  Make sure the driver name specified is
705     # on our allowed list.
706     my @drivers_found = grep(/^$args{'driver_name'}$/, @{$ALLOWED_DRIVERS});
707     my $driverName = pop(@drivers_found);
708     unless (defined($driverName)) {
709         $LOG->warn("Not allowed to run Driver (" . $args{'driver_name'} . ").");
710         die SOAP::Fault->faultcode(__PACKAGE__ . "->run()")
711                        ->faultstring("Not allowed to run Driver (" . $args{'driver_name'} . ").");
712     }
713
714     # Temporary variable, used to hold thawed driver data.
715     my $data = undef;
716
717     # Temporary variable, used to hold thread IDs.
718     my $tid = undef;
719
720     # Temporary variable, used to hold thread objects.
721     my $thread = undef;
722
723     if (defined($driverName)) {
724
725         # Acquire data lock.
726         $data = _lock();
727
728         # Read the TID.
729         $tid = $data->{$driverName}->{'thread_id'};
730
731 # XXX: Delete this, eventually.
732 print $driverName . " - Checking TID = " . Dumper($tid) . "\n";
733 if (defined(threads->object($tid))) {
734     print $driverName . " - Thread defined.\n";
735     if (threads->object($tid)->is_running()) {
736         print $driverName . " - Thread is running.\n";
737     } else {
738         print $driverName . " - Thread is NOT running.\n";
739     }
740 } else {
741     print $driverName . " - Thread NOT defined.\n";
742 }
743        
744         # Sanity check: Return false, if we already have a
745         # driver thread running.
746         if (defined($tid) &&
747             defined($thread = threads->object($tid)) &&
748             $thread->is_running()) {
749
750             # Release data lock.
751             _unlock();
752
753             return 0;
754         } else {
755             # XXX: Remove this, eventually.
756             print $driverName . " - Creating a new run() child thread...\n";
757         }
758
759         # Quickly define a temporary thread ID.
760         # This value is simply a placeholder that will
761         # get redefined later on in this function to
762         # the thread's valid ID, once the thread has been
763         # initialized.
764         #
765         # By defining a placeholder valid here, we avoid
766         # a potential race condition, where multiple calls
767         # to run() are made consecutively.
768         #
769         # Temporarily set the driver thread to be the
770         # main thread.
771         $data->{$driverName}->{'thread_id'} = 0;
772        
773         # Release data lock.
774         _unlock($data);
775
776         $thread = threads->create(\&worker,
777                                   {
778                                     'driver_name' => $driverName,
779                                     'integrity'   => $integrityData,
780                                   }
781                                  );
782            
783         # Acquire data lock.
784         $data = _lock();
785            
786         # Set the valid thread ID.
787         $data->{$driverName}->{'thread_id'} = $thread->tid();
788         if ($thread->is_running()) {
789             # XXX: Debugging, remove eventually.
790             print $driverName . " - Thread ID = " . $thread->tid() . "\n";
791         } else {
792             # XXX: Debugging, remove eventually.
793             print $driverName . " - Thread ID = " . $thread->tid() . " (NOT RUNNING)\n";
794         }
795
796         # Release data lock.
797         _unlock($data);
798     }
799
800     # XXX: Debugging, remove eventually.
801     print "Run thread(s) initialized.\n";
802
803     # At this point, the driver thread is initialized and running,
804     # return true.
805     return 1;
806 }
807
808 # TODO: Clean up this comment block.
809 # This function should do the following:
810 # - Initialize all drivers with starting state.
811 # - "Drive" each driver, one-by-one.
812 # - Collect any integrity violations found, with offending
813 #   state information.
814 #
815 # Notes:
816 # This function will eventually sit in a sub-thread, allowing the parent
817 # thread to return without any delay.  It is expected that the Manager
818 # would then subsequently call a getStatus() operation, in order to
819 # then poll for any new violations found.
820 #
821 # TODO: We need to create a fault reporting mechanism, in order
822 # to properly deal with exceptions/faults that occur within this
823 # thread.
824 sub worker {
825
826     # Extract arguments.
827     my $args = shift;
828     my $driverName = $args->{'driver_name'};
829     my $integrity  = $args->{'integrity'};
830
831     # Temporary variable, used to hold thawed driver data.
832     my $data = undef;
833
834     # Yield processing to parent thread.
835     threads->yield();
836
837     # Trap all faults that may occur from these asynchronous operations.
838     eval {
839
840         ###################################
841         ### Driver Initialization Phase ###
842         ###################################
843
844         # Initially set all driver objects to undef.
845         my $driver = undef;
846
847         # Last resource used by driver.
848         my $lastResource = undef;
849    
850         # Acquire lock on stored driver state.
851         $data = _lock();
852
853         # Now, initialize each driver object.
854         # Figure out which $driver object to use...
855         my $driverClass = $driverName;
856
857         if (!defined($data->{$driverName}->{'state'})) {
858    
859             # If the driver state is undefined, then
860             # create a new state object.
861             $driver = $driverClass->new();
862
863         } else {
864             # Then the driver state object is already defined,
865