root/honeyclient/tags/rel/0.9.7/lib/HoneyClient/Manager.pm

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

sc: merging branch using tags svn+ssh://kindlund@www.honeyclient.org/home/svn/honeyclient/honeyclient/tags/bug/PRE-51 and svn+ssh://kindlund@www.honeyclient.org/home/svn/honeyclient/honeyclient/tags/bug/POST-51

  • 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         $ret = $som->result();
479         if (defined($ret)) {
480             print "Result: " . $ret . "\n";
481         }
482     }
483
484     print "Calling getStateVM()...\n";
485     $som = $stubVM->getStateVM(config => $vmCloneConfig);
486     $vmState = $som->result();
487
488     if ($som->result() == VM_EXECUTION_STATE_ON) {
489         print "ON\n";
490     } elsif ($som->result() == VM_EXECUTION_STATE_OFF) {
491         print "OFF\n";
492     } elsif ($som->result() == VM_EXECUTION_STATE_SUSPENDED) {
493         print "SUSPENDED\n";
494     } elsif ($som->result() == VM_EXECUTION_STATE_STUCK) {
495         print "STUCK\n";
496     } else {
497         print "UNKNOWN\n";
498     }
499
500     while ($vmState != VM_EXECUTION_STATE_ON) {
501         sleep (3);
502
503         print "Calling getStateVM()...\n";
504         $som = $stubVM->getStateVM(config => $vmCloneConfig);
505         $vmState = $som->result();
506
507         if ($som->result() == VM_EXECUTION_STATE_ON) {
508             print "ON\n";
509         } elsif ($som->result() == VM_EXECUTION_STATE_OFF) {
510             print "OFF\n";
511         } elsif ($som->result() == VM_EXECUTION_STATE_SUSPENDED) {
512             print "SUSPENDED\n";
513         } elsif ($som->result() == VM_EXECUTION_STATE_STUCK) {
514             print "STUCK\n";
515         } else {
516             print "UNKNOWN\n";
517         }
518     }
519
520     print "Calling getMACaddrVM()...\n";
521     $som = $stubVM->getMACaddrVM(config => $vmCloneConfig);
522     print "Result: " . $som->result() . "\n";
523     $vmMAC = $som->result();
524
525     # Figure out when the Agent on the VM is alive and well.
526     $ret = undef;
527     while (!$ret) {
528         sleep (3);
529         print "Calling getIPaddrVM()...\n";
530         $som = $stubVM->getIPaddrVM(config => $vmCloneConfig);
531         if (defined($som->result())) {
532             print "Result: " . $som->result() . "\n";
533         }
534         $vmIP = $som->result();
535
536         if (defined($vmIP)) {
537
538             # Try contacting the Agent; ignore any faults.
539             $stubAgent = getClientHandle(namespace     => "HoneyClient::Agent",
540                                          address       => $vmIP,
541                                          fault_handler => \&_handleFault);
542
543             eval {
544                 print "Calling getStatus()...\n";
545                 $som = $stubAgent->getStatus();
546                 $ret = thaw(decode_base64($som->result()));
547                 print "Result:\n";
548                 # Make Dumper format more verbose.
549                 $Data::Dumper::Terse = 0;
550                 $Data::Dumper::Indent = 2;
551                 print Dumper($ret);
552
553                 print "Calling getNameVM()...\n";
554                 $som = $stubVM->getNameVM(config => $vmCloneConfig);
555                 print "Result: " . $som->result() . "\n";
556                 $vmName = $som->result();
557             };
558             # Clear returned state, if any fault occurs.
559             if ($@) {
560                 $ret = undef;
561             }
562         }
563     }
564
565     # Build our VM's connection table.
566     # Note: We assume our VM has a single MAC address
567     # and a single IP address.
568     $vmStateTable->{$vmName}->{sources}->{$vmMAC}->{$vmIP} = {
569         # XXX: We assume we can't pinpoint what source TCP ports the
570         # corresponding driver will need.  (We may want to get this
571         # information eventually from the Agent, as part of Driver::next().)
572         'tcp' => undef,
573     };
574
575     print "VM State Table:\n";
576     # Make Dumper format more verbose.
577     $Data::Dumper::Terse = 0;
578     $Data::Dumper::Indent = 2;
579     print Dumper($vmStateTable) . "\n";
580  
581     # Initialize the firewall.
582     $stubFW->fwInit();
583
584     # Add new chain, per cloned VM.
585     $stubFW->addChain($vmStateTable);
586    
587     sleep (2);
588
589     # Recreate the client stub; ignore faults.
590     $stubAgent = getClientHandle(namespace     => "HoneyClient::Agent",
591                                  address       => $vmIP,
592                                  fault_handler => \&_handleFault);
593
594     # Recreate the firewall stub; ignore faults.
595     $stubFW = getClientHandle(namespace     => "HoneyClient::Manager::FW",
596                               fault_handler => \&_handleFault);
597
598     for (my $counter = 1;; $counter++) {
599
600         # From this point on, catch all errors generated and
601         # assume that the Agent's watchdog process will recover.
602         eval {
603             print "Calling getStatus()...\n";
604             $som = $stubAgent->getStatus();
605             print "Result:\n";
606             my $ret = thaw(decode_base64($som->result()));
607             # Make Dumper format more verbose.
608             $Data::Dumper::Terse = 0;
609             $Data::Dumper::Indent = 2;
610             print Dumper($ret->{$args{'driver'}}->{status});
611             #print Dumper($ret);
612
613             # Check to see if Agent::run() thread has stopped
614             # and that a compromise was detected.
615             if (!defined($ret->{$args{'driver'}}->{thread_id})) {
616                 if ($ret->{$args{'driver'}}->{status}->{is_compromised}) {
617                     print "Calling getState()...\n";
618                     $som = $stubAgent->getState();
619                     $args{'agent_state'} = $som->result();
620
621                     # Check to see if the VM has been compromised.
622                     print "WARNING: VM HAS BEEN COMPROMISED!\n";
623                     print "Suspending: (" . $vmCloneConfig . ")...\n";
624                     print "Calling suspendVM()...\n";
625                     $som = $stubVM->suspendVM(config => $vmCloneConfig);
626                     HoneyClient::Manager::VM->destroy();
627                     $vmCompromised = 1;
628                     return; # Return out of eval block.
629                 } else {
630                     print "VM Integrity Check: OK!\n";
631                 }
632             }
633            
634             # Only call updateState() on the first iteration.
635             # TODO: Need to support asynchronous updates (url adding)
636             # from user input.
637             if ($counter == 1) {
638                 print "Calling updateState()...\n";
639                 $som = $stubAgent->updateState($args{'agent_state'});
640             }
641            
642             print "Calling getState()...\n";
643             $som = $stubAgent->getState();
644             $args{'agent_state'} = $som->result();
645
646             print "Calling getStatus()...\n";
647             $som = $stubAgent->getStatus();
648             print "Result:\n";
649             $ret = thaw(decode_base64($som->result()));
650             # Make Dumper format more verbose.
651             $Data::Dumper::Terse = 0;
652             $Data::Dumper::Indent = 2;
653             #print Dumper($ret->{$args{'driver'}}->{status});
654             print Dumper($ret);
655
656             # The Agent::run() thread has stopped; we assume
657             # it's because the Agent is waiting for the firewall
658             # to allow access to the new targets.
659             # TODO: Need to distinguish between run() stopping because
660             # of firewall mods, or if the Agent is completely finished
661             # and needs more input to continue.
662             if (!defined($ret->{$args{'driver'}}->{thread_id})) {
663
664
665                 # Delete the old firewall rules, based upon existing
666                 # targets.
667                 $stubFW->deleteRules($vmStateTable);
668
669                 # Get the new targets from the Agent.
670                 $vmStateTable->{$vmName}->{targets} = $ret->{$args{'driver'}}->{next}->{targets};
671
672                 print "VM State Table:\n";
673                 # Make Dumper format more verbose.
674                 $Data::Dumper::Terse = 0;
675                 $Data::Dumper::Indent = 2;
676                 print Dumper($vmStateTable) . "\n";
677
678                 # Add the new targets from the Agent.
679                 $stubFW->addRules($vmStateTable);
680
681                 print "Calling run()...\n";
682                 $som = $stubAgent->run();
683             }
684         };
685         if ($@) {
686             my $resetSuccessful = 0;
687             while (!$resetSuccessful) {
688                 print "Resetting firewall...\n";
689                 eval {
690                     # We assume the error was caused by some sort of communications
691                     # problem with the Agent.  Assume the Agent's watchdog will restart
692                     # the daemon, in which case, we indefinately try to reset the
693                     # firewall accordingly.
694                     $stubFW->fwInit();
695                     $stubFW->addChain($vmStateTable);
696                     $stubFW->addRules($vmStateTable);
697                 };
698                 if (!$@) {
699                     $resetSuccessful = 1;
700                 } else {
701                     sleep (3);
702                 }
703             }   
704         }
705         if ($vmCompromised) {
706             return $args{'agent_state'};
707         }
708         sleep (10);
709     }
710 }
711
712 #######################################################################
713
714 1;
715
716 #######################################################################
717 # Additional Module Documentation                                     #
718 #######################################################################
719
720 __END__
721
722 =head1 BUGS & ASSUMPTIONS
723
724 # XXX: Fill this in.
725
726 =head1 SEE ALSO
727
728 L<http://www.honeyclient.org/trac>
729
730 =head1 REPORTING BUGS
731
732 L<http://www.honeyclient.org/trac/newticket>
733
734 =head1 ACKNOWLEDGEMENTS
735
736 Paul Kulchenko for developing the SOAP::Lite module.
737
738 =head1 AUTHORS
739
740 Kathy Wang, E<lt>knwang@mitre.orgE<gt>
741
742 Thanh Truong, E<lt>ttruong@mitre.orgE<gt>
743
744 Darien Kindlund, E<lt>kindlund@mitre.orgE<gt>
745
746 =head1 COPYRIGHT & LICENSE
747
748 Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
749
750 This program is free software; you can redistribute it and/or
751 modify it under the terms of the GNU General Public License
752 as published by the Free Software Foundation, using version 2
753 of the License.
754  
755 This program is distributed in the hope that it will be useful,
756 but WITHOUT ANY WARRANTY; without even the implied warranty of
757 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
758 GNU General Public License for more details.
759  
760 You should have received a copy of the GNU General Public License
761 along with this program; if not, write to the Free Software
762 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
763 02110-1301, USA.
764
765
766 =cut
Note: See TracBrowser for help on using the browser.