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

Revision 96, 24.0 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::Agent::Driver
4 # File:        Driver.pm
5 # Description: Generic driver model for all drivers running inside a
6 #              HoneyClient VM.
7 #
8 # CVS: $Id$
9 #
10 # @author knwang, ttruong, kindlund
11 #
12 # Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
13 #
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation, using version 2
17 # of the License.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301, USA.
28 #
29 #######################################################################
30
31 =pod
32
33 =head1 NAME
34
35 HoneyClient::Agent::Driver - Perl extension to provide a generic driver
36 interface for all drivers resident within any HoneyClient VM.
37
38 =head1 VERSION
39
40 This documentation refers to HoneyClient::Agent::Driver version 1.0.
41
42 =head1 SYNOPSIS
43
44   # NOTE: This package is an INTERFACE specification only!  It is
45   # NOT intended to be used directly.  Rather, it is expected that
46   # other Driver specific sub-packages will INHERIT and IMPLEMENT
47   # these methods.
48
49   # Eventually, change each reference of 'HoneyClient::Agent::Driver'
50   # to an implementation-specific 'HoneyClient::Agent::Driver::*'
51   # package name.
52   use HoneyClient::Agent::Driver;
53
54   # Library used exclusively for debugging complex objects.
55   use Data::Dumper;
56
57   # Eventually, call the new() function on an implementation-specific
58   # Driver package name.
59   my $driver = HoneyClient::Agent::Driver->new();
60
61   # If you want to see what type of "state information" is physically
62   # inside $driver, try this command at any time.
63   print Dumper($driver);
64
65   # Continue to "drive" the driver, until it is finished.
66   while (!$driver->isFinished()) {
67
68       # Before we drive the application to a new set of resources,
69       # find out where we will be going within the application, first.
70       print "About to contact the following resources:\n";
71       print Dumper($driver->next());
72
73       # Now, drive the application.
74       $driver->drive();
75
76       # Get status of current iteration of work.
77       print "Status:\n";
78       print Dumper($driver->status());
79      
80   }
81
82 =head1 DESCRIPTION
83
84 This library allows the Agent module to access any drivers running on the
85 HoneyClient VM in a consistent fashion.  This module is object-oriented in
86 design, allowing specific types of drivers to inherit these abstractly
87 defined interface methods.
88
89 Fundamentally, a "Driver" is a programmatic construct, designed to
90 automate a back-end application that is intended to be exploited by
91 different types of malware.  As such, the "Agent" interacts with each
92 B<application-specific> Driver running inside the HoneyClient VM, in
93 order to programmatically automate the corresponding applications.
94
95 When a "Driver" is "driven", this implies that the back-end application
96 is accessing a B<new> Internet resource, in order to intentionally be exposed
97 to new malware and thus become exploited.
98
99 Example implementation Drivers involve automating certain types and
100 B<versions> of web browsers (e.g., Microsoft Internet Explorer, Mozilla
101 Firefox) or even email applications (e.g., Microsoft Outlook, Mozilla
102 Thunderbird).
103
104 =cut
105
106 package HoneyClient::Agent::Driver;
107
108 use strict;
109 use warnings;
110 use Carp ();
111
112 #######################################################################
113 # Module Initialization                                               #
114 #######################################################################
115
116 BEGIN {
117     # Defines which functions can be called externally.
118     require Exporter;
119     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
120
121     # Set our package version.
122     $VERSION = 0.9;
123
124     @ISA = qw(Exporter);
125
126     # Symbols to export on request
127     # Note: Since this module is object-oriented, we do *NOT* export
128     # any functions other than "new" to call statically.  Each function
129     # for this module *must* be called as a method from a unique
130     # object instance.
131     @EXPORT = qw();
132
133     # Items to export into callers namespace by default. Note: do not export
134     # names by default without a very good reason. Use EXPORT_OK instead.
135     # Do not simply export all your public functions/methods/constants.
136
137     # This allows declaration use HoneyClient::Agent::Driver ':all';
138     # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
139     # will save memory.
140
141     # Note: Since this module is object-oriented, we do *NOT* export
142     # any functions other than "new" to call statically.  Each function
143     # for this module *must* be called as a method from a unique
144     # object instance.
145     %EXPORT_TAGS = (
146         'all' => [ qw() ],
147     );
148
149     # Symbols to autoexport (:DEFAULT tag)
150     @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
151
152     $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
153 }
154 our (@EXPORT_OK, $VERSION);
155
156 =pod
157
158 =begin testing
159
160 # Make sure Log::Log4perl loads
161 BEGIN { use_ok('Log::Log4perl', qw(:nowarn))
162         or diag("Can't load Log::Log4perl package. Check to make sure the package library is correctly listed within the path.");
163        
164         # Suppress all logging messages, since we need clean output for unit testing.
165         Log::Log4perl->init({
166             "log4perl.rootLogger"                               => "DEBUG, Buffer",
167             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
168             "log4perl.appender.Buffer.min_level"                => "fatal",
169             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
170             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
171         });
172 }
173 require_ok('Log::Log4perl');
174 use Log::Log4perl qw(:easy);
175
176 # Make sure HoneyClient::Util::Config loads.
177 BEGIN { use_ok('HoneyClient::Util::Config', qw(getVar))
178         or diag("Can't load HoneyClient::Util::Config package.  Check to make sure the package library is correctly listed within the path.");
179
180         # Suppress all logging messages, since we need clean output for unit testing.
181         Log::Log4perl->init({
182             "log4perl.rootLogger"                               => "DEBUG, Buffer",
183             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
184             "log4perl.appender.Buffer.min_level"                => "fatal",
185             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
186             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
187         });
188        
189 }
190 require_ok('HoneyClient::Util::Config');
191 can_ok('HoneyClient::Util::Config', 'getVar');
192 use HoneyClient::Util::Config qw(getVar);
193
194 # Make sure the module loads properly, with the exportable
195 # functions shared.
196 BEGIN { use_ok('HoneyClient::Agent::Driver') or diag("Can't load HoneyClient::Agent::Driver package.  Check to make sure the package library is correctly listed within the path."); }
197 require_ok('HoneyClient::Agent::Driver');
198 can_ok('HoneyClient::Agent::Driver', 'new');
199 can_ok('HoneyClient::Agent::Driver', 'drive');
200 can_ok('HoneyClient::Agent::Driver', 'isFinished');
201 can_ok('HoneyClient::Agent::Driver', 'next');
202 can_ok('HoneyClient::Agent::Driver', 'status');
203 use HoneyClient::Agent::Driver;
204
205 # Suppress all logging messages, since we need clean output for unit testing.
206 Log::Log4perl->init({
207     "log4perl.rootLogger"                               => "DEBUG, Buffer",
208     "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
209     "log4perl.appender.Buffer.min_level"                => "fatal",
210     "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
211     "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
212 });
213
214 # Make sure we use the exception testing library.
215 require_ok('Test::Exception');
216 can_ok('Test::Exception', 'dies_ok');
217 use Test::Exception;
218
219 # Make sure Storable loads.
220 BEGIN { use_ok('Storable', qw(dclone)) or diag("Can't load Storable package.  Check to make sure the package library is correctly listed within the path."); }
221 require_ok('Storable');
222 can_ok('Storable', 'dclone');
223 use Storable qw(dclone);
224
225 =end testing
226
227 =cut
228
229 #######################################################################
230
231 # Include Global Configuration Processing Library
232 use HoneyClient::Util::Config qw(getVar);
233
234 # Use Storable Library
235 use Storable qw(dclone);
236
237 # Package Global Variable
238 our $AUTOLOAD;
239
240 # Include Logging Library
241 use Log::Log4perl qw(:easy);
242
243 # The global logging object.
244 our $LOG = get_logger();
245
246 =pod
247
248 =head1 DEFAULT PARAMETER LIST
249
250 When a Driver B<$object> is instantiated using the B<new()> function,
251 the following parameters are supplied default values.  Each value
252 can be overridden by specifying the new (key => value) pair into the
253 B<new()> function, as arguments.
254
255 Furthermore, as each parameter is initialized, each can be individually
256 retrieved and set at any time, using the following syntax:
257
258   my $value = $object->{key}; # Gets key's value.
259   $object->{key} = $value;    # Sets key's value.
260
261 =head2 timeout
262
263 =over 4
264
265 This parameter indicates how long (in seconds) the Driver should wait
266 for an application response, once driven for one iteration.
267 The default value is any valid "timeout" setting located within the
268 global configuration file that matches any portion of this package's
269 namespace.  See L<HoneyClient::Util::Config> for more information.
270
271 =back
272
273 =cut
274
275 my %PARAMS = (
276     timeout     => getVar(name => "timeout"), # Timeout (in seconds).
277 );
278
279 #######################################################################
280 # Private Methods Implemented                                         #
281 #######################################################################
282
283 # Helper function designed to programmatically get or set parameters
284 # within this object, through indirect use of the AUTOLOAD function.
285 #
286 # It's best to explain by example:
287 # Assume we have defined a driver object, like the following.
288 #
289 # use HoneyClient::Agent::Driver;
290 # my $driver = Driver->new(someVar => 'someValue');
291 #
292 # What this function allows us to do, is programmatically, get or set
293 # the 'someVar' parameter, like:
294 #
295 # my $value = $driver->someVar();    # Gets the value of 'someVar'.
296 # my $value = $driver->someVar('2'); # Sets the value of 'someVar' to '2'
297 #                                    # and returns '2'.
298 #
299 # Rather than creating getter/setter functions for every possible parameter,
300 # the AUTOLOAD function allows us to create these operations in a generic,
301 # reusable fashion.
302 #
303 # See "Autoloaded Data Methods" in perltoot for more details.
304 #
305 # Inputs: set a new value (optional)
306 # Outputs: the currently set value
307 sub AUTOLOAD {
308     # Get the object.
309     my $self = shift;
310
311     # Sanity check: Make sure the supplied value is an object.
312     my $type = ref($self) or Carp::croak "Error: $self is not an object!\n";
313
314     # Now, get the name of the function.
315     my $name = $AUTOLOAD;
316
317     # Strip the fully-qualified portion of the function name.
318     $name =~ s/.*://;
319
320     # Make sure the parameter exists in the object, before we try
321     # to get or set it.
322     unless (exists $self->{$name}) {
323         $LOG->error("Can't access '$name' parameter in class $type!");
324         Carp::croak "Error: Can't access '$name' parameter in class $type!\n";
325     }
326
327     if (@_) {
328         # If we were given an argument, then set the parameter's value.
329         return $self->{$name} = shift;
330     } else {
331         # Else, just return the existing value.
332         return $self->{$name};
333     }
334 }
335
336 # Base destructor function.
337 # Since none of our state data ever contains circular references,
338 # we can simply leave the garbage collection up to Perl's internal
339 # mechanism.
340 sub DESTROY {
341 }
342
343 #######################################################################
344 # Public Methods Implemented                                          #
345 #######################################################################
346
347 =pod
348
349 =head1 METHOD INTERFACES
350
351 The following functions B<must> be implemented be each driver
352 implementation, upon inheriting this package interface.
353
354 =head2 HoneyClient::Agent::Driver->new($param => $value, ...)
355
356 =over 4
357
358 Creates a new Driver object, which contains a hashtable
359 containing any of the supplied "param => value" arguments.
360
361 I<Inputs>:
362  B<$param> is an optional parameter variable.
363  B<$value> is $param's corresponding value.
364  
365 Note: If any $param(s) are supplied, then an equal number of
366 corresponding $value(s) B<must> also be specified.
367
368 I<Output>: The instantiated Driver B<$object>, fully initialized.
369
370 =back
371
372 =begin testing
373
374 # Create a generic driver, with test state data.
375 my $driver = HoneyClient::Agent::Driver->new(test => 1);
376 is($driver->{test}, 1, "new(test => 1)") or diag("The new() call failed.");
377
378 =end testing
379
380 =cut
381
382 sub new {
383     # - This function takes in an optional hashtable,
384     #   that contains various key => 'value' configuration
385     #   parameters.
386     #
387     # - For each parameter given, it overwrites any corresponding
388     #   parameters specified within the default hashtable, %PARAMS,
389     #   with custom entries that were given as parameters.
390     #
391     # - Finally, it returns a blessed instance of the
392     #   merged hashtable, as an 'object'.
393
394     # Get the class name.
395     my $self = shift;
396
397     # Get the rest of the arguments, as a hashtable.
398     # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle
399     # hash references directly.  Thus, flat hashtables are used throughout the code
400     # for consistency.
401     my %args = @_;
402
403     # Check to see if the class name is inherited or defined.
404     my $class = ref($self) || $self;
405
406     # Initialize default parameters.
407     $self = { };
408     my %params = %{dclone(\%PARAMS)};
409     @{$self}{keys %params} = values %params;
410
411     # Now, overwrite any default parameters that were redefined
412     # in the supplied arguments.
413     @{$self}{keys %args} = values %args;
414
415     # Now, assign our object the appropriate namespace.
416     bless $self, $class;
417
418     # Finally, return the blessed object.
419     return $self;
420 }
421
422 =pod
423
424 =head2 $object->drive()
425
426 =over 4
427
428 Drives the back-end application for one iteration, updating the
429 corresponding internal object state with information obtained
430 from driving this application for one iteration.
431
432 I<Output>: The updated Driver B<$object>, containing state information
433 from driving the application for one iteration.  Will croak if
434 operation fails.
435
436 =back
437
438 =begin testing
439
440 # Create a generic driver, with test state data.
441 my $driver = HoneyClient::Agent::Driver->new(test => 1);
442 dies_ok {$driver->drive()} 'drive()' or diag("The drive() call failed.  Expected drive() to throw an exception.");
443
444 =end testing
445
446 =cut
447
448 sub drive {
449     # Get the class name.
450     my $self = shift;
451    
452     # Check to see if the class name is inherited or defined.
453     my $class = ref($self) || $self;
454
455     # Emit generic "not implemented" error message.
456     $LOG->error($class . "->drive() is not implemented!");
457     Carp::croak "Error: " . $class . "->drive() is not implemented!\n";
458 }
459
460 =pod
461
462 =head2 $object->isFinished()
463
464 =over 4
465
466 Indicates if the Driver B<$object> has driven the back-end application
467 through the Driver's entire state and is unable to drive the application
468 further without additional input.
469
470 I<Output>: True if the Driver B<$object> is finished, false otherwise.
471
472 =back
473
474 =begin testing
475
476 # Create a generic driver, with test state data.
477 my $driver = HoneyClient::Agent::Driver->new(test => 1);
478 dies_ok {$driver->isFinished()} 'isFinished()' or diag("The isFinished() call failed.  Expected isFinished() to throw an exception.");
479
480 =end testing
481
482 =cut
483
484 sub isFinished {
485     # Get the class name.
486     my $self = shift;
487    
488     # Check to see if the class name is inherited or defined.
489     my $class = ref($self) || $self;
490
491     # Emit generic "not implemented" error message.
492     $LOG->error($class . "->isFinished() is not implemented!");
493     Carp::croak "Error: " . $class . "->isFinished() is not implemented!\n";
494 }
495
496 =pod
497
498 =head2 $object->next()
499
500 =over 4
501
502 Returns the next set of server hostnames and/or IP addresses that the
503 back-end application will contact, upon the next subsequent call to
504 the B<$object>'s drive() method.
505
506 Specifically, the returned data is a reference to a hashtable, containing
507 detailed information about which resources, hostnames, IPs, protocols, and
508 ports that the application will contact upon the next iteration.
509
510 Here is an example of such returned data:
511
512   $hashref = {
513  
514       # The set of servers that the driver will contact upon
515       # the next drive() operation.
516       targets => {
517           # The application will contact 'site.com' using
518           # TCP ports 80 and 81.
519           'site.com' => {
520               'tcp' => [ 80, 81 ],
521           },
522
523           # The application will contact '192.168.1.1' using
524           # UDP ports 53 and 123.
525           '192.168.1.1' => {
526               'udp' => [ 53, 123 ],
527           },
528  
529           # Or, more generically:
530           'hostname_or_IP' => {
531               'protocol_type' => [ portnumbers_as_list ],
532           },
533       },
534
535       # The set of resources that the driver will operate upon
536       # the next drive() operation.
537       resources => {
538           'http://www.mitre.org/' => 1,
539       },
540   };
541
542 B<Note>: For each hostname or IP address specified, if B<no>
543 corresponding protocol/port sub-hastables are given, then it
544 must be B<assumed> that the back-end application may contact
545 the hostname or IP address using B<ANY> protocol/port.
546
547 I<Output>: The aforementioned B<$hashref> containing the next set of
548 resources that the back-end application will attempt to contact upon
549 the next drive() iteration.
550
551 # XXX: Resolve this.
552
553 B<Note>: Eventually this B<$hashref> will become a structured object,
554 created via a HoneyClient::Util::* package.  However, the underlying
555 structure of this hashtable is not expected to change.
556
557 =back
558
559 =begin testing
560
561 # Create a generic driver, with test state data.
562 my $driver = HoneyClient::Agent::Driver->new(test => 1);
563 dies_ok {$driver->next()} 'next()' or diag("The next() call failed.  Expected next() to throw an exception.");
564
565 =end testing
566
567 =cut
568
569 sub next {
570     # Get the class name.
571     my $self = shift;
572    
573     # Check to see if the class name is inherited or defined.
574     my $class = ref($self) || $self;
575
576     # Emit generic "not implemented" error message.
577     $LOG->error($class . "->next() is not implemented!");
578     Carp::croak "Error: " . $class . "->next() is not implemented!\n";
579 }
580
581 =pod
582
583 =head2 $object->status()
584
585 =over 4
586
587 Returns the current status of the Driver B<$object>, as it's state
588 exists, between subsequent calls to $object->driver().
589
590 Specifically, the data returned is a reference to a hashtable,
591 containing specific statistical information about the status
592 of the Driver's progress during back-end application automation.
593
594 As such, the exact structure of this returned hashtable is not strictly
595 defined.  Instead, it is left up to each specific Driver implementation
596 to return useful, statistical information back to the Agent that
597 makes sense for the driven application.
598
599 For example, if an Internet Explorer specific Driver were implemented,
600 then the corresponding status hashtable reference returned may look
601 something like:
602
603   $hashref = {
604       'links_remaining'  =>       56, # Number of URLs left to process.
605       'links_processed'  =>       44, # Number of URLs processed.
606       'links_total'      =>      100, # Total number of URLs given.
607       'percent_complete' => '44.00%', # Percent complete.
608   };
609
610 For another example, if an Outlook specific Driver were implemented,
611 then the corresponding status hashtable reference returned may look
612 something like:
613
614   $hashref = {
615       'mail_remaining'   =>       56, # Number of messages left to process.
616       'mail_processed'   =>       44, # Number of messages processed.
617       'mail_total'       =>      100, # Total number of messages given.
618       'percent_complete' => '44.00%', # Percent complete.
619   };
620
621 I<Output>: A corresponding B<$hashref>, containing statistical information
622 about the Driver's progress, as previously mentioned.
623
624 # XXX: Resolve this.
625
626 B<Note>: The exact structure of this status hashtable may become more
627 concrete, as we define a generic concept of a "unit of work" per every
628 iteration of the $object->drive() method.  For example, it may be
629 likely that each Driver will attempt to contact a series of resources
630 per every "unit of work" iteration.  As such, we may generically
631 record how many "work units" are remaining, processed, and total --
632 rather than specifically state "links" or "mail" within the hashtable
633 key names, accordingly.
634
635 At the least, it can be assumed that even if a generic structure were
636 defined, we would leave room available in the status hashtable to
637 capture additional, implementation-specific statistics that are not
638 generic among every Driver implementation.
639
640 =back
641
642 =begin testing
643
644 # Create a generic driver, with test state data.
645 my $driver = HoneyClient::Agent::Driver->new(test => 1);
646 dies_ok {$driver->status()} 'status()' or diag("The status() call failed.  Expected status() to throw an exception.");
647
648 =end testing
649
650 =cut
651
652 sub status {
653     # Get the class name.
654     my $self = shift;
655    
656     # Check to see if the class name is inherited or defined.
657     my $class = ref($self) || $self;
658
659     # Emit generic "not implemented" error message.
660     $LOG->error($class . "->next() is not implemented!");
661     Carp::croak "Error: " . $class . "->next() is not implemented!\n";
662 }
663
664
665 #######################################################################
666
667 1;
668
669 #######################################################################
670 # Additional Module Documentation                                     #
671 #######################################################################
672
673 __END__
674
675 =head1 BUGS & ASSUMPTIONS
676
677 This package has been designed in an object-oriented fashion, as a
678 simple B<INTERFACE> for other, more robust fully-implemented
679 HoneyClient::Agent::Driver::* sub-packages to inherit.
680
681 Specifically, B<ONLY> the new() function is implemented in this package.
682 While this allows any user to create Driver B<$object>s by explicitly
683 calling the HoneyClient::Agent::Driver->new() function, any subsequent
684 calls to any other method (i.e., $object->drive()) will B<FAIL>, as it is
685 expected that fully defined Driver sub-packages would implement these
686 capabilities.
687
688 In a nutshell, this object is nothing more than a blessed anonymous
689 reference to a hashtable, where (key => value) pairs are defined in
690 the L<DEFAULT PARAMETER LIST>, as well as fed via the new() function
691 during object initialization.  As such, this package does B<not>
692 perform any rigorous B<data validation> prior to accepting any new
693 or overriding (key => value) pairs.
694
695 =head1 SEE ALSO
696
697 L<perltoot/"Autoloaded Data Methods">
698
699 L<http://www.honeyclient.org/trac>
700
701 =head1 REPORTING BUGS
702
703 L<http://www.honeyclient.org/trac/newticket>
704
705 =head1 AUTHORS
706
707 Kathy Wang, E<lt>knwang@mitre.orgE<gt>
708
709 Thanh Truong, E<lt>ttruong@mitre.orgE<gt>
710
711 Darien Kindlund, E<lt>kindlund@mitre.orgE<gt>
712
713 =head1 COPYRIGHT & LICENSE
714
715 Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
716
717 This program is free software; you can redistribute it and/or
718 modify it under the terms of the GNU General Public License
719 as published by the Free Software Foundation, using version 2
720 of the License.
721  
722 This program is distributed in the hope that it will be useful,
723 but WITHOUT ANY WARRANTY; without even the implied warranty of
724 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
725 GNU General Public License for more details.
726  
727 You should have received a copy of the GNU General Public License
728 along with this program; if not, write to the Free Software
729 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
730 02110-1301, USA.
731
732
733 =cut
Note: See TracBrowser for help on using the browser.