root/honeyclient/branches/bug/42/lib/HoneyClient/Manager.pm

Revision 96, 23.2 kB (checked in by kindlund, 2 years ago)

Completed registry parser documentation and unit tests; corrected minor mispellings; updated POD documentation to reflect public website.

  • Property svn:keywords set to Id "$file"
Line 
1 #######################################################################
2 # Created on:  May 11, 2006
3 # Package:     HoneyClient::Manager
4 # File:        Manager.pm
5 # Description: Central library used for manager-based operations.
6 #
7 # CVS: $Id$
8 #
9 # @author knwang, ttruong, jdurick, kindlund
10 #
11 # Copyright (C) 2006 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 # XXX: Fill this in.
35
36 =head1 VERSION
37
38 This documentation refers to HoneyClient::Manager version 1.0.
39
40 =head1 SYNOPSIS
41
42 =head2 CREATING THE SOAP SERVER
43
44 # XXX: Fill this in.
45
46 =head2 INTERACTING WITH THE SOAP SERVER
47
48 # XXX: Fill this in.
49
50 =head1 DESCRIPTION
51
52 # XXX: Fill this in.
53
54 =cut
55
56 package HoneyClient::Manager;
57
58 # XXX: Disabled version check, Honeywall does not have Perl v5.8 installed.
59 #use 5.008006;
60 use strict;
61 use warnings FATAL => 'all';
62 use Config;
63 use Carp ();
64
65 #######################################################################
66 # Module Initialization                                               #
67 #######################################################################
68
69 BEGIN {
70     # Defines which functions can be called externally.
71     require Exporter;
72     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION, @DRIVERS);
73
74     # Set our package version.
75     $VERSION = 0.9;
76
77     @ISA = qw(Exporter);
78
79     # Symbols to export on request
80     @EXPORT = qw(init destroy);
81
82     # Items to export into callers namespace by default. Note: do not export
83     # names by default without a very good reason. Use EXPORT_OK instead.
84     # Do not simply export all your public functions/methods/constants.
85
86     # This allows declaration use HoneyClient::Manager ':all';
87     # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
88     # will save memory.
89
90     %EXPORT_TAGS = (
91         'all' => [ qw(init destroy) ],
92     );
93
94     # Symbols to autoexport (:DEFAULT tag)
95     @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
96
97     # Check to see if ithreads are compiled into this version of Perl.
98     $Config{useithreads} or Carp::croak "Error: Recompile Perl with ithread support, in order to use this module.\n";
99
100     $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
101 }
102 our (@EXPORT_OK, $VERSION);
103
104 =pod
105
106 =begin testing
107
108 # Make sure the module loads properly, with the exportable
109 # functions shared.
110 BEGIN { use_ok('HoneyClient::Manager', qw(init destroy)) or diag("Can't load HoneyClient::Manager package.  Check to make sure the package library is correctly listed within the path."); }
111 require_ok('HoneyClient::Manager');
112 can_ok('HoneyClient::Manager', 'init');
113 can_ok('HoneyClient::Manager', 'destroy');
114 use HoneyClient::Manager qw(init destroy);
115
116 # Make sure HoneyClient::Util::SOAP loads.
117 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."); }
118 require_ok('HoneyClient::Util::SOAP');
119 can_ok('HoneyClient::Util::SOAP', 'getServerHandle');
120 can_ok('HoneyClient::Util::SOAP', 'getClientHandle');
121 use HoneyClient::Util::SOAP qw(getServerHandle getClientHandle);
122
123 # Make sure HoneyClient::Util::Config loads.
124 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."); }
125 require_ok('HoneyClient::Util::Config');
126 can_ok('HoneyClient::Util::Config', 'getVar');
127 use HoneyClient::Util::Config qw(getVar);
128
129 # Make sure Storable loads.
130 BEGIN { use_ok('Storable', qw(nfreeze thaw)) or diag("Can't load Storable package.  Check to make sure the package library is correctly listed within the path."); }
131 require_ok('Storable');
132 can_ok('Storable', 'nfreeze');
133 can_ok('Storable', 'thaw');
134 use Storable qw(nfreeze thaw);
135
136 # Make sure MIME::Base64 loads.
137 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."); }
138 require_ok('MIME::Base64');
139 can_ok('MIME::Base64', 'encode_base64');
140 can_ok('MIME::Base64', 'decode_base64');
141 use MIME::Base64 qw(encode_base64 decode_base64);
142
143 =end testing
144
145 =cut
146
147 #######################################################################
148
149 # Include the SOAP Utility Library
150 use HoneyClient::Util::SOAP qw(getClientHandle getServerHandle);
151
152 # Include Thread Libraries
153 use threads;
154 use threads::shared;
155 use Thread::Semaphore;
156 use Thread::Queue;
157
158 # Include utility access to global configuration.
159 use HoneyClient::Util::Config qw(getVar);
160
161 # Include the VM Utility Library
162 # TODO: Include unit tests.
163 use HoneyClient::Manager::VM qw();
164
165 # XXX: Remove this, eventually.
166 use Data::Dumper;
167
168 # Make Dumper format more verbose.
169 $Data::Dumper::Terse = 0;
170 $Data::Dumper::Indent = 2;
171
172 # Include Hash Serialization Utility Libraries
173 use Storable qw(nfreeze thaw);
174
175 # Include Base64 Libraries
176 use MIME::Base64 qw(encode_base64 decode_base64);
177
178 # Include FW Utility Library
179 # TODO: Include unit tests.
180 use HoneyClient::Manager::FW;
181
182 # Include Hash Serialization Utility Libraries
183 # TODO: Include unit tests.
184 use Storable qw(nfreeze thaw);
185
186 # Include VmPerl Constants.
187 # TODO: Include unit tests.
188 use VMware::VmPerl qw(VM_EXECUTION_STATE_ON
189                       VM_EXECUTION_STATE_OFF
190                       VM_EXECUTION_STATE_STUCK
191                       VM_EXECUTION_STATE_SUSPENDED);
192
193 # Complete URL of SOAP server, when initialized.
194 our $URL_BASE       : shared = undef;
195 our $URL            : shared = undef;
196
197 # The process ID of the SOAP server daemon, once created.
198 our $DAEMON_PID     : shared = undef;
199
200 # XXX: These will be migrated somewhere else, eventually.
201 our $vmStateTable = { };
202 our $vmCloneConfig      = undef;
203 our $stubVM             = undef;
204 our $stubAgent          = undef;
205 our $stubFW             = undef;
206
207 #######################################################################
208 # Daemon Initialization / Destruction                                 #
209 #######################################################################
210
211 =pod
212
213 =head1 EXPORTED FUNCTIONS
214
215 The following init() and destroy() functions are the only direct
216 calls required to startup and shutdown the SOAP server.
217
218 All other interactions with this daemon should be performed as
219 C<SOAP::Lite> function calls, in order to ensure consistency across
220 client sessions.  See the L<"EXTERNAL SOAP FUNCTIONS"> section, for
221 more details.
222
223 =head2 HoneyClient::Manager->init()
224
225 =over 4
226
227 Starts a new SOAP server, within a child process.
228
229 I<Inputs>:
230
231 # XXX: Finish this.
232
233 I<Output>:
234
235 # XXX: Finish this.
236
237 =back
238
239 =begin testing
240
241 # XXX: Test init() method.
242
243 =end testing
244
245 =cut
246
247 sub init {
248     # Extract arguments.
249     # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle
250     # hash references directly.  Thus, flat hashtables are used throughout the code
251     # for consistency.
252     my ($class, %args) = @_;
253    
254     # XXX: Finish this.
255 }
256
257 =pod
258
259 =head2 HoneyClient::Manager->destroy()
260
261 =over 4
262
263 Terminates the SOAP server within the child process.
264
265 I<Output>: True if successful, false otherwise.
266
267 =back
268
269 =begin testing
270
271 # XXX: Test destroy() method.
272
273 # TODO: delete this.
274 #exit;
275
276 =end testing
277
278 =cut
279
280 sub destroy {
281     my $ret = undef;
282    
283     # XXX: Finish this.
284     
285     return $ret;
286 }
287
288 #######################################################################
289 # Private Methods Implemented                                         #
290 #######################################################################
291
292 sub _handleFault {
293
294     # Extract arguments.
295     my ($class, $res) = @_;
296
297     # Construct error message.
298     # Figure out if the error occurred in transport or over
299     # on the other side.
300     my $errMsg = $class->transport->status; # Assume transport error.
301
302     if (ref $res) {
303         $errMsg = $res->faultcode . ": ".  $res->faultstring . "\n";
304     }
305
306     Carp::carp __PACKAGE__ . "->_handleFault(): Error occurred during processing.\n" . $errMsg;
307 }
308
309 sub _handleFaultAndCleanup {
310
311     # Extract arguments.
312     my ($class, $res) = @_;
313
314     # Print fault.
315     _handleFault($class, $res);
316    
317     # Cleanup before dying.
318     _cleanup();
319 }
320
321 sub _cleanup {
322
323     print "Cleaning up...\n";
324
325     # Mask all possible signals, so that we don't call this function multiple times.
326     $SIG{HUP}     = sub { };
327     $SIG{INT}     = sub { };
328     $SIG{QUIT}    = sub { };
329     $SIG{ABRT}    = sub { };
330     $SIG{PIPE}    = sub { };
331     $SIG{TERM}    = sub { };
332
333     HoneyClient::Manager::VM->destroy();
334
335     # XXX: Need to clean this up.
336     my $stubFW = getClientHandle(namespace     => "HoneyClient::Manager::FW");
337
338     # XXX: Change this to fwInit(), eventually.
339     # Reset the firewall, to allow everything open.
340     $stubFW->testConnect();
341
342     # Check to see if a clone was created...
343     if (defined($vmCloneConfig)) {
344         # We sleep for a bit, to make sure that the previous VM daemon was
345         # properly destroyed and released the previous port that was in use.
346         sleep (10);
347
348         # We reinstantiate a new VM daemon, because if the user had hit CTRL-C
349         # or called any other signal, then that signal would propagate to all
350         # processes, causing the VM daemon's signal handler to self terminate.
351         #
352         # Hence, rather than fight the VM daemon's natural self termination,
353         # we let the daemon die, but the create a new one, for the sole purpose
354         # of cleanup up the clones.
355         HoneyClient::Manager::VM->init();
356         print "Calling suspendVM(config => $vmCloneConfig)...\n";
357         my $stubVM = getClientHandle(namespace     => "HoneyClient::Manager::VM");
358         $stubVM->suspendVM(config => $vmCloneConfig);
359         print "Done!\n";
360         HoneyClient::Manager::VM->destroy();
361     }
362
363     exit;
364 }
365
366 # XXX: Install the cleanup handler, in case the parent process dies
367 # unexpectedly.
368 $SIG{HUP} = sub { _cleanup(); };
369 $SIG{INT}   = sub { _cleanup(); };
370 $SIG{QUIT}  = sub { _cleanup(); };
371 $SIG{ABRT}  = sub { _cleanup(); };
372 $SIG{PIPE}  = sub { _cleanup(); };
373 $SIG{TERM}  = sub { _cleanup(); };
374
375 #######################################################################
376 # Public Methods Implemented                                          #
377 #######################################################################
378
379 =pod
380
381 =head1 EXPORTS
382
383 =head2 run()
384
385 =over 4
386
387 # XXX: Fill this in.
388
389 I<Inputs>:
390  B<$arg> is an optional argument.
391
392 driver
393 master_vm_config
394 start_state
395  
396 I<Output>: XXX: Fill this in.
397
398 =back
399
400 =begin testing
401
402 # XXX: Fill this in.
403
404 =end testing
405
406 =cut
407
408 sub run {
409     # Extract arguments.
410     # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle
411     # hash references directly.  Thus, flat hashtables are used throughout the code
412     # for consistency.
413     my ($class, %args) = @_;
414     my $agentState = undef;
415
416     for (;;) {
417         print "Starting new session...\n";
418         $agentState = $class->runSession(%args);
419         $args{'agent_state'} = $agentState;
420         #$Data::Dumper::Terse = 0;
421         #$Data::Dumper::Indent = 2;
422         #print Dumper(thaw(decode_base64($agentState)));
423     }
424 }
425
426 sub runSession {
427
428     # Extract arguments.
429     # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle
430     # hash references directly.  Thus, flat hashtables are used throughout the code
431     # for consistency.
432     my ($class, %args) = @_;
433
434     my $som       = undef;
435     my $ret       = undef;
436     my $vmIP      = undef;
437     my $vmMAC     = undef;
438     my $vmName    = undef;
439     my $URL       = undef;
440     my $vmState   = undef;
441     my $vmCompromised = 0;
442
443     # Get a stub connection to the firewall.
444     $stubFW = getClientHandle(namespace     => "HoneyClient::Manager::FW",
445                               fault_handler => \&_handleFaultAndCleanup);
446
447     # Open up the firewall initially, to allow the Agent to do an SVN update.
448     $stubFW->testConnect();
449
450     $URL = HoneyClient::Manager::VM->init();
451     print "VM Daemon Listening On: " . $URL . "\n";
452    
453     $stubVM = getClientHandle(namespace     => "HoneyClient::Manager::VM",
454                               fault_handler => \&_handleFaultAndCleanup);
455    
456     print "Calling setMasterVM()...\n";
457     $som = $stubVM->setMasterVM(config => $args{'master_vm_config'});
458     print "Result: " . $som->result() . "\n";
459
460     print "Calling quickCloneVM()...\n";
461     $som = $stubVM->quickCloneVM();
462     print "Result: " . $som->result() . "\n";
463     $vmCloneConfig = $som->result();
464
465     # Make sure the VM is fully cloned, before trying to make any subsequent calls.
466     print "Calling isRegisteredVM()...\n";
467     $som = $stubVM->isRegisteredVM(config => $vmCloneConfig);
468     $ret = $som->result();
469
470     if (defined($ret)) {
471         print "Result: " . $ret . "\n";
472     }
473
474     while (!defined($ret)) {
475         sleep (3);
476         print "Calling isRegisteredVM()...\n";
477         $som = $stubVM->isRegisteredVM(config => $vmCloneConfig);
478         print "Result: " . $som->result() . "\n";
479         $ret = $som->result();
480     }
481
482     print "Calling getStateVM()...\n";
483     $som = $stubVM->getStateVM(config => $vmCloneConfig);
484     $vmState = $som->result();
485
486     if ($som->result() == VM_EXECUTION_STATE_ON) {
487         print "ON\n";
488     } elsif ($som->result() == VM_EXECUTION_STATE_OFF) {
489         print "OFF\n";
490     } elsif ($som->result() == VM_EXECUTION_STATE_SUSPENDED) {
491         print "SUSPENDED\n";
492     } elsif ($som->result() == VM_EXECUTION_STATE_STUCK) {
493         print "STUCK\n";
494     } else {
495         print "UNKNOWN\n";
496     }
497
498     while ($vmState != VM_EXECUTION_STATE_ON) {
499         sleep (3);
500
501         print "Calling getStateVM()...\n";
502         $som = $stubVM->getStateVM(config => $vmCloneConfig);
503         $vmState = $som->result();
504
505         if ($som->result() == VM_EXECUTION_STATE_ON) {
506             print "ON\n";
507         } elsif ($som->result() == VM_EXECUTION_STATE_OFF) {
508             print "OFF\n";
509         } elsif ($som->result() == VM_EXECUTION_STATE_SUSPENDED) {
510             print "SUSPENDED\n";
511         } elsif ($som->result() == VM_EXECUTION_STATE_STUCK) {
512             print "STUCK\n";
513         } else {
514             print "UNKNOWN\n";
515         }
516     }
517
518     print "Calling getMACaddrVM()...\n";
519     $som = $stubVM->getMACaddrVM(config => $vmCloneConfig);
520     print "Result: " . $som->result() . "\n";
521     $vmMAC = $som->result();
522
523     # Figure out when the Agent on the VM is alive and well.
524     $ret = undef;
525     while (!$ret) {
526         sleep (3);
527         print "Calling getIPaddrVM()...\n";
528         $som = $stubVM->getIPaddrVM(config => $vmCloneConfig);
529         if (defined($som->result())) {
530             print "Result: " . $som->result() . "\n";
531         }
532         $vmIP = $som->result();
533
534         if (defined($vmIP)) {
535
536             # Try contacting the Agent; ignore any faults.
537             $stubAgent = getClientHandle(namespace     => "HoneyClient::Agent",
538                                          address       => $vmIP,
539                                          fault_handler => \&_handleFault);
540
541             eval {
542                 print "Calling getStatus()...\n";
543                 $som = $stubAgent->getStatus();
544                 $ret = thaw(decode_base64($som->result()));
545                 print "Result:\n";
546                 # Make Dumper format more verbose.
547                 $Data::Dumper::Terse = 0;
548                 $Data::Dumper::Indent = 2;
549                 print Dumper($ret);
550
551                 print "Calling getNameVM()...\n";
552                 $som = $stubVM->getNameVM(config => $vmCloneConfig);
553                 print "Result: " . $som->result() . "\n";
554                 $vmName = $som->result();
555             };
556             # Clear returned state, if any fault occurs.
557             if ($@) {
558                 $ret = undef;
559             }
560         }
561     }
562
563     # Build our VM's connection table.
564     # Note: We assume our VM has a single MAC address
565     # and a single IP address.
566     $vmStateTable->{$vmName}->{sources}->{$vmMAC}->{$vmIP} = {
567         # XXX: We assume we can't pinpoint what source TCP ports the
568         # corresponding driver will need.  (We may want to get this
569         # information eventually from the Agent, as part of Driver::next().)
570         'tcp' => undef,
571     };
572
573     print "VM State Table:\n";
574     # Make Dumper format more verbose.
575     $Data::Dumper::Terse = 0;
576     $Data::Dumper::Indent = 2;
577     print Dumper($vmStateTable) . "\n";
578  
579     # Initialize the firewall.
580     $stubFW->fwInit();
581
582     # Add new chain, per cloned VM.
583     $stubFW->addChain($vmStateTable);
584    
585     sleep (2);
586
587     # Recreate the client stub; ignore faults.
588     $stubAgent = getClientHandle(namespace     => "HoneyClient::Agent",
589                                  address       => $vmIP,
590                                  fault_handler => \&_handleFault);
591
592     # Recreate the firewall stub; ignore faults.
593     $stubFW = getClientHandle(namespace     => "HoneyClient::Manager::FW",
594                               fault_handler => \&_handleFault);
595
596     for (my $counter = 1;; $counter++) {
597
598         # From this point on, catch all errors generated and
599         # assume that the Agent's watchdog process will recover.
600         eval {
601             print "Calling getStatus()...\n";
602             $som = $stubAgent->getStatus();
603             print "Result:\n";
604             my $ret = thaw(decode_base64($som->result()));
605             # Make Dumper format more verbose.
606             $Data::Dumper::Terse = 0;
607             $Data::Dumper::Indent = 2;
608             print Dumper($ret->{$args{'driver'}}->{status});
609             #print Dumper($ret);
610
611             # Check to see if Agent::run() thread has stopped
612             # and that a compromise was detected.
613             if (!defined($ret->{$args{'driver'}}->{thread_id})) {
614                 if ($ret->{$args{'driver'}}->{status}->{is_compromised}) {
615                     print "Calling getState()...\n";
616                     $som = $stubAgent->getState();
617                     $args{'agent_state'} = $som->result();
618
619                     # Check to see if the VM has been compromised.
620                     print "WARNING: VM HAS BEEN COMPROMISED!\n";
621                     print "Suspending: (" . $vmCloneConfig . ")...\n";
622                     print "Calling suspendVM()...\n";
623                     $som = $stubVM->suspendVM(config => $vmCloneConfig);
624                     HoneyClient::Manager::VM->destroy();
625                     $vmCompromised = 1;
626                     return; # Return out of eval block.
627                 } else {
628                     print "VM Integrity Check: OK!\n";
629                 }
630             }
631            
632             # Only call updateState() on the first iteration.
633             # TODO: Need to support asynchronous updates (url adding)
634             # from user input.
635             if ($counter == 1) {
636                 print "Calling updateState()...\n";
637                 $som = $stubAgent->updateState($args{'agent_state'});
638             }
639            
640             print "Calling getState()...\n";
641             $som = $stubAgent->getState();
642             $args{'agent_state'} = $som->result();
643
644             print "Calling getStatus()...\n";
645             $som = $stubAgent->getStatus();
646             print "Result:\n";
647             $ret = thaw(decode_base64($som->result()));
648             # Make Dumper format more verbose.
649             $Data::Dumper::Terse = 0;
650             $Data::Dumper::Indent = 2;
651             #print Dumper($ret->{$args{'driver'}}->{status});
652             print Dumper($ret);
653
654             # The Agent::run() thread has stopped; we assume
655             # it's because the Agent is waiting for the firewall
656             # to allow access to the new targets.
657             # TODO: Need to distinguish between run() stopping because
658             # of firewall mods, or if the Agent is completely finished
659             # and needs more input to continue.
660             if (!defined($ret->{$args{'driver'}}->{thread_id})) {
661
662
663                 # Delete the old firewall rules, based upon existing
664                 # targets.
665                 $stubFW->deleteRules($vmStateTable);
666
667                 # Get the new targets from the Agent.
668                 $vmStateTable->{$vmName}->{targets} = $ret->{$args{'driver'}}->{next}->{targets};
669
670                 print "VM State Table:\n";
671                 # Make Dumper format more verbose.
672                 $Data::Dumper::Terse = 0;
673                 $Data::Dumper::Indent = 2;
674                 print Dumper($vmStateTable) . "\n";
675
676                 # Add the new targets from the Agent.
677                 $stubFW->addRules($vmStateTable);
678
679                 print "Calling run()...\n";
680                 $som = $stubAgent->run();
681             }
682         };
683         if ($@) {
684             my $resetSuccessful = 0;
685             while (!$resetSuccessful) {
686                 print "Resetting firewall...\n";
687                 eval {
688                     # We assume the error was caused by some sort of communications
689                     # problem with the Agent.  Assume the Agent's watchdog will restart
690                     # the daemon, in which case, we indefinately try to reset the
691                     # firewall accordingly.
692                     $stubFW->fwInit();
693                     $stubFW->addChain($vmStateTable);
694                     $stubFW->addRules($vmStateTable);
695                 };
696                 if (!$@) {
697                     $resetSuccessful = 1;
698                 } else {
699                     sleep (3);
700                 }
701             }   
702         }
703         if ($vmCompromised) {
704             return $args{'agent_state'};
705         }
706         sleep (10);
707     }
708 }
709
710 #######################################################################
711
712 1;
713
714 #######################################################################
715 # Additional Module Documentation                                     #
716 #######################################################################
717
718 __END__
719
720 =head1 BUGS & ASSUMPTIONS
721
722 # XXX: Fill this in.
723
724 =head1 SEE ALSO
725
726 L<http://www.honeyclient.org/trac>
727
728 =head1 REPORTING BUGS
729
730 L<http://www.honeyclient.org/trac/newticket>
731
732 =head1 ACKNOWLEDGEMENTS
733
734 Paul Kulchenko for developing the SOAP::Lite module.
735
736 =head1 AUTHORS
737
738 Kathy Wang, E<lt>knwang@mitre.orgE<gt>
739
740 Thanh Truong, E<lt>ttruong@mitre.orgE<gt>
741
742 Darien Kindlund, E<lt>kindlund@mitre.orgE<gt>
743
744 =head1 COPYRIGHT & LICENSE
745
746 Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
747
748 This program is free software; you can redistribute it and/or
749 modify it under the terms of the GNU General Public License
750 as published by the Free Software Foundation, using version 2
751 of the License.
752  
753 This program is distributed in the hope that it will be useful,
754 but WITHOUT ANY WARRANTY; without even the implied warranty of
755 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
756 GNU General Public License for more details.
757  
758 You should have received a copy of the GNU General Public License
759 along with this program; if not, write to the Free Software
760 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
761 02110-1301, USA.
762
763
764 =cut
Note: See TracBrowser for help on using the browser.