root/honeyclient/branches/rel/0.9/lib/HoneyClient/Util/SOAP.pm

Revision 130, 22.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-42 and svn+ssh://kindlund@www.honeyclient.org/home/svn/honeyclient/honeyclient/tags/bug/POST-42

  • Property svn:keywords set to Id "$file"
Line 
1 #######################################################################
2 # Created on:  Apr 20, 2006
3 # Package:     HoneyClient::Util::SOAP
4 # File:        SOAP.pm
5 # Description: Generic interface to server and client SOAP operations.
6 #
7 # CVS: $Id$
8 #
9 # @author ttruong, 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 HoneyClient::Util::SOAP - Perl extension to provide a generic interface
35 to all client and server SOAP operations, for any HoneyClient module.
36
37 =head1 VERSION
38
39 This documentation refers to HoneyClient::Util::SOAP version 1.0.
40
41 =head1 SYNOPSIS
42
43 =head2 CREATING A SOAP SERVER
44
45   use HoneyClient::Util::SOAP qw(getServerHandle);
46
47   # Create a new SOAP server, using default values.
48   my $daemon = getServerHandle();
49
50   # In the previous example, if this code were listed in package
51   # "A::B", where the package's global configuration variables
52   # for "address" and "port" was "localhost" and "8080" respectively
53   # (as listed in etc/honeyclient.conf), then the corresponding
54   # SOAP server URL would be:
55   #
56   # http://localhost:8080/A/B
57
58   # Create a new SOAP server, using specific address/ports.
59   my $daemon = getServerHandle(address => "localhost",
60                                port    => 9090);
61
62   # Create a new SOAP server, using the specific "A::B::C" namespace.
63   my $daemon = getServerHandle(address   => "localhost",
64                                port      => 9090,
65                                namespace => "A::B::C");
66
67   # When you're ready to start listening for connections, call
68   # the handle() function, like:
69   $daemon->handle();
70
71   # Note: Remember, this handle() call *will* block.  If you have
72   # any other code you want to execute after calling handle(), then
73   # it is suggested that you call handle() from within a child
74   # process or thread.
75
76 =head2 CREATING A SOAP CLIENT
77
78   use HoneyClient::Util::SOAP qw(getClientHandle);
79
80   # Create a new SOAP client, to talk to the HoneyClient::Manager::VM
81   # module.
82   my $stub = getClientHandle(namespace => "HoneyClient::Manager::VM");
83
84   # Create a new SOAP client, to talk to the HoneyClient::Agent::Driver
85   # module
86   my $stub = getClientHandle(namespace => "HoneyClient::Agent::Driver");
87
88   # Create a new SOAP client, to talk to the HoneyClient::Manager::VM
89   # module on localhost:9090.
90   my $stub = getClientHandle(namespace => "HoneyClient::Agent::Driver",
91                              address   => "localhost",
92                              port      => 9090);
93  
94   # Create a new SOAP client, to talk to the HoneyClient::Manager::VM
95   # module on localhost:9090, using a custom fault handler.
96   $faultHandler = sub { die "Something bad happened!"; };
97   my $stub = getClientHandle(namespace     => "HoneyClient::Agent::Driver",
98                              address       => "localhost",
99                              port          => 9090,
100                              fault_handler => $faultHandler);
101
102   # Create a new SOAP client, as a callback to this package.
103   my $stub = getClientHandle();
104
105 =head1 DESCRIPTION
106
107 This library allows any HoneyClient module to quickly create new
108 SOAP servers or interact with existing ones, by using ports and
109 protocols that are globally defined within a configuration file,
110 rather than using hard coded values within each module.
111
112 This library makes extensive use of the SOAP::Lite module.
113
114 =cut
115
116 package HoneyClient::Util::SOAP;
117
118 use strict;
119 use warnings;
120 use Carp ();
121
122 #######################################################################
123 # Module Initialization                                               #
124 #######################################################################
125
126 BEGIN {
127     # Defines which functions can be called externally.
128     require Exporter;
129     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
130
131     # Set our package version.
132     $VERSION = 0.9;
133
134     @ISA = qw(Exporter);
135
136     # Symbols to export on request
137     @EXPORT = qw(getServerHandle getClientHandle);
138
139     # Items to export into callers namespace by default. Note: do not export
140     # names by default without a very good reason. Use EXPORT_OK instead.
141     # Do not simply export all your public functions/methods/constants.
142
143     # This allows declaration use HoneyClient::Util::SOAP ':all';
144     # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
145     # will save memory.
146
147     %EXPORT_TAGS = (
148         'all' => [ qw(getServerHandle getClientHandle) ],
149     );
150
151     # Symbols to autoexport (:DEFAULT tag)
152     @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
153
154     $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
155 }
156 our (@EXPORT_OK, $VERSION);
157
158 =pod
159
160 =begin testing
161
162 # Make sure Log::Log4perl loads
163 BEGIN { use_ok('Log::Log4perl', qw(:nowarn))
164         or diag("Can't load Log::Log4perl package. Check to make sure the package library is correctly listed within the path.");
165        
166         # Suppress all logging messages, since we need clean output for unit testing.
167         Log::Log4perl->init({
168             "log4perl.rootLogger"                               => "DEBUG, Buffer",
169             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
170             "log4perl.appender.Buffer.min_level"                => "fatal",
171             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
172             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
173         });
174 }
175 require_ok('Log::Log4perl');
176 use Log::Log4perl qw(:easy);
177
178 # Make sure the module loads properly, with the exportable
179 # functions shared.
180 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."); }
181 require_ok('HoneyClient::Util::SOAP');
182 can_ok('HoneyClient::Util::SOAP', 'getServerHandle');
183 can_ok('HoneyClient::Util::SOAP', 'getClientHandle');
184 use HoneyClient::Util::SOAP qw(getServerHandle getClientHandle);
185
186 # Make sure HoneyClient::Util::Config loads.
187 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."); }
188 require_ok('HoneyClient::Util::Config');
189 can_ok('HoneyClient::Util::Config', 'getVar');
190 use HoneyClient::Util::Config qw(getVar);
191
192 # Suppress all logging messages, since we need clean output for unit testing.
193 Log::Log4perl->init({
194     "log4perl.rootLogger"                               => "DEBUG, Buffer",
195     "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
196     "log4perl.appender.Buffer.min_level"                => "fatal",
197     "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
198     "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
199 });
200
201 # Make sure SOAP::Lite loads.
202 BEGIN { use_ok('SOAP::Lite') or diag("Can't load SOAP::Lite package.  Check to make sure the package library is correctly listed within the path."); }
203 require_ok('SOAP::Lite');
204 use SOAP::Lite;
205
206 # Make sure SOAP::Transport::HTTP loads.
207 BEGIN { use_ok('SOAP::Transport::HTTP') or diag("Can't load SOAP::Transport::HTTP package.  Check to make sure the package library is correctly listed within the path."); }
208 require_ok('SOAP::Transport::HTTP');
209 use SOAP::Transport::HTTP;
210
211 # Make sure Data::Dumper loads.
212 BEGIN { use_ok('Data::Dumper') or diag("Can't load Data::Dumper package.  Check to make sure the package library is correctly listed within the path."); }
213 require_ok('Data::Dumper');
214 use Data::Dumper;
215
216 =end testing
217
218 =cut
219
220 #######################################################################
221
222 # Include utility access to global configuration.
223 use HoneyClient::Util::Config qw(getVar);
224
225 # Include the SOAP APIs
226 use SOAP::Lite 0.67;
227
228 # If you want debugging on, use this line instead.
229 #use SOAP::Lite +trace => 'all';
230 use SOAP::Transport::HTTP;
231
232 # Include Data Dumper API
233 use Data::Dumper;
234
235 # Include Logging Library
236 use Log::Log4perl qw(:easy);
237
238 # The global logging object.
239 our $LOG = get_logger();
240
241 # Make Dumper format more terse.
242 $Data::Dumper::Terse = 1;
243 $Data::Dumper::Indent = 0;
244
245 #######################################################################
246 # Private Methods Implemented                                         #
247 #######################################################################
248
249 # Default handler for any faults that are received by any client.
250 # Inputs: Class, SOAP::SOM
251 # Outputs: None
252 sub _handleFault {
253
254     # Extract arguments.
255     my ($class, $res) = @_;
256
257     # Construct error message.
258     # Figure out if the error occurred in transport or
259     # over on the other side.
260     my $errMsg = $class->transport->status; # Assume transport error.
261     if (ref $res) {
262         # Extract base error message.
263         $errMsg = $res->faultcode . ": " . $res->faultstring . "\n";
264
265         # Extract error details.
266         my $errNo  = undef;
267         my $errStr = undef;
268         my $errDetail = "";
269         if (defined($res->faultdetail)) {
270
271             # Since we don't know what the fault detail may look like,
272             # we output its contents in a generic fashion.
273             # Make Dumper format more terse.
274             $Data::Dumper::Terse = 1;
275             $Data::Dumper::Indent = 0;
276             $errDetail = $res->faultcode . ": " . Dumper($res->faultdetail);
277
278         }
279
280         $errMsg = $errMsg . $errDetail . "\n";
281     }
282
283     $LOG->error("Error occurred during processing. " . $errMsg);
284     die __PACKAGE__ . "->handleFault(): Error occurred during processing.\n" . $errMsg;
285 }
286
287 #######################################################################
288 # Public Methods Implemented                                          #
289 #######################################################################
290
291 =pod
292
293 =head1 EXPORTS
294
295 =head2 getServerHandle(namespace => $caller, address => $localAddr, port => $localPort)
296
297 =over 4
298
299 Returns a new SOAP::Server object, using the caller's package
300 namespace as the dispatch location, if not specified.  If neither
301 the $localAddr nor $localPort is specified, then the function will attempt
302 to retrieve the "address" and "port" global configuration variables, set
303 within the caller's namespace.
304
305 I<Inputs>:
306  B<$caller> is an optional argument, used to explicitly specify the package
307 namespace to be used as the dispatch point.
308  B<$localAddr> is an optional argument, specifying the IP address for the
309 SOAP server to listen on.
310  B<$localPort> is an optional argument, specifying the TCP port for the
311 SOAP server to listen on.
312  
313 I<Output>: The corresponding SOAP::Server object if successful, croaks
314 otherwise.
315
316 =back
317
318 =begin testing
319
320 # Check to make sure we can get a valid handle.
321 my $daemon = getServerHandle(namespace => "HoneyClient::Manager::VM");
322 isa_ok($daemon, 'SOAP::Server', "getServerHandle(namespace => 'HoneyClient::Manager::VM')") or diag("The getServerHandle() call failed.");
323
324 =end testing
325
326 =cut
327
328 sub getServerHandle {
329
330     # Extract arguments.
331     my (%args) = @_;
332     my $argsExist = scalar(%args);
333
334     # Find out who is calling this function.
335     if (!$argsExist ||
336         !exists($args{'namespace'}) ||
337         !defined($args{'namespace'})) {
338         $args{'namespace'} = caller();
339     }
340
341     if (!$argsExist ||
342         !exists($args{'address'}) ||
343         !defined($args{'address'})) {
344         $args{'address'} = getVar(name      => "address",
345                                   namespace => $args{'namespace'});
346     }
347
348     if (!$argsExist ||
349         !exists($args{'port'}) ||
350         !defined($args{'port'})) {
351         $args{'port'} = getVar(name      => "port",
352                                namespace => $args{'namespace'});
353     }
354
355     # Log resolved arguments.
356     # Make Dumper format more terse.
357     $Data::Dumper::Terse = 1;
358     $Data::Dumper::Indent = 0;
359     $LOG->debug(Dumper(\%args));
360
361     my $daemon = SOAP::Transport::HTTP::Daemon
362                     ->new( LocalAddr => $args{'address'},
363                            LocalPort => $args{'port'},
364                            Reuse => 1 )
365                     ->dispatch_to($args{'namespace'})
366                     ->options({ compress_threshold => 10000 });
367
368     # Sanity check.
369     if (!defined($daemon)) {
370         $LOG->fatal("Unable to create SOAP server using namespace " .
371                     "'" . $args{'namespace'} . "', listening on " .
372                     $args{'address'} . ":" . $args{'port'} . ".");
373         Carp::croak "Error: Unable to create SOAP server using namespace " .
374                     "'" . $args{'namespace'} . "', listening on " .
375                     $args{'address'} . ":" . $args{'port'} . ".\n";
376     }
377
378     return $daemon;
379 }
380
381 =pod
382
383 =head2 getClientHandle(namespace => $caller, address => $address, port => $port, fault_handler => $faultHandler)
384
385 =over 4
386
387 Returns a new SOAP::Lite client object, using the caller's package
388 namespace as the URI, if not specified.  If neither
389 the $address nor $port is specified, then the function will attempt
390 to retrieve the "address" and "port" global configuration variables, set
391 within the caller's namespace.
392
393 I<Inputs>:
394  B<$caller> is an optional argument, used to explicitly specify the package
395 namespace URI.
396  B<$address> is an optional argument, specifying the IP address for the
397 SOAP server to listen on.
398  B<$port> is an optional argument, specifying the TCP port for the
399 SOAP server to listen on.
400  B<$faultHandler> is an optional argument, specifying the code reference to
401 call if a fault occurs during any subsequent SOAP call using this object.
402  
403 I<Output>: The corresponding SOAP::Lite object if successful, croaks
404 otherwise.
405
406 =back
407
408 =begin testing
409
410 # Check to make sure we can get a valid handle.
411 my $stub = getClientHandle(namespace => "HoneyClient::Manager::VM");
412 isa_ok($stub, 'SOAP::Lite', "getClientHandle(namespace => 'HoneyClient::Manager::VM')") or diag("The getClientHandle() call failed.");
413
414 =end testing
415
416 =cut
417
418 sub getClientHandle {
419    
420     # Extract arguments.
421     my (%args) = @_;
422     my $argsExist = scalar(%args);
423     #my ($caller, $address, $port, $faultHandler) = @_;
424
425     # Find out who is calling this function.
426     if (!$argsExist ||
427         !exists($args{'namespace'}) ||
428         !defined($args{'namespace'})) {
429         $args{'namespace'} = caller();
430     }
431
432     if (!$argsExist ||
433         !exists($args{'address'}) ||
434         !defined($args{'address'})) {
435         $args{'address'} = getVar(name      => "address",
436                                   namespace => $args{'namespace'});
437     }
438
439     if (!$argsExist ||
440         !exists($args{'port'}) ||
441         !defined($args{'port'})) {
442         $args{'port'} = getVar(name      => "port",
443                                namespace => $args{'namespace'});
444     }
445    
446     # If no fault handler was supplied, use the default.
447     if (!$argsExist ||
448         !exists($args{'fault_handler'}) ||
449         !defined($args{'fault_handler'})) {
450         $args{'fault_handler'} = \&_handleFault;
451     }
452
453     # Log resolved arguments.
454     # Make Dumper format more terse.
455     $Data::Dumper::Terse = 1;
456     $Data::Dumper::Indent = 0;
457     $LOG->debug(Dumper(\%args));
458
459     my $timeout = getVar(name      => "timeout",
460                          namespace => $args{'namespace'});
461     my $URL_BASE = "http://" . $args{'address'} . ":" . $args{'port'};
462     my $URL = $URL_BASE . "/" . join('/', split(/::/, $args{'namespace'}));
463
464     my $stub = SOAP::Lite
465                 ->default_ns($URL)
466                 ->proxy($URL_BASE, timeout => $timeout);
467
468     # If we were supplied with a fault handler, register it.
469     if (defined($args{'fault_handler'}) and
470         (ref($args{'fault_handler'}) eq "CODE")) {
471         $stub->on_fault($args{'fault_handler'});
472     }
473    
474     # Sanity check.
475     if (!defined($stub)) {
476         $LOG->fatal("Unable to connect to SOAP server at: " .
477                     "$URL");
478         Carp::croak "Error: Unable to connect to SOAP server at: " .
479                     "$URL\n";
480     }
481
482     return $stub;
483 }
484
485 1;
486
487 #######################################################################
488 # Additional Module Documentation                                     #
489 #######################################################################
490
491 __END__
492
493 =head1 HANDLING FAULTS
494
495 When talking to any SOAP server, it is B<highly recommended> that you
496 create a SOAP B<fault handler>, when creating a new client to talk to
497 the server.  This will ensure that B<all> errors are properly relayed
498 back to the client, including errors that occur during a failure within
499 SOAP communications or from errors that occur as a result of a remote call
500 failure.
501
502 =head2 EXAMPLE SERVER-SIDE FAULT CODE
503
504   Q)  So, how do I properly generate SOAP faults in my server code?
505
506   A1) For basic errors that include just an error string, here's what you'd
507       include:
508
509   # Assume you want to generate fault message "Unspecified argument." within
510   # function "foo()".
511
512   sub foo {
513
514       # ... do other stuff ...
515    
516       # Check for some error status condition.
517       if ($condition) {
518           die SOAP::Fault->faultcode(__PACKAGE__ . "->foo()")
519                          ->faultstring("Unspecified argument.");
520       }
521   }
522
523   A2) For complex errors, where you want to include an error number
524       along with an error message of an upstream function call, then
525       here's what you'd include:
526
527   # Assume you want to generate fault message "Unspecified argument." within
528   # function "foo()", but include an upstream error number and string as well.
529
530   sub foo {
531
532       # ... do other stuff ...
533    
534       # Check for some error status condition.
535       if ($condition) {
536
537           my $errorNumber = ...get the upstream error number...;
538           my $errorString = ...get the upstream error string...;
539
540           die SOAP::Fault->faultcode(__PACKAGE__ . "->foo()")
541                          ->faultstring("Unspecified argument.")
542                          ->faultdetail(bless { errNo  => $errorNumber,
543                                                errStr => $errorString },
544                                        'err');
545       }
546   }
547
548 =head2 EXAMPLE CLIENT-SIDE FAULT CODE
549
550   Q) So, now that I'm generating faults in my SOAP server, how do I handle
551      them within my SOAP client?
552
553   A) A default fault handler is provided by this library; however, this
554      default handler will NOT know how to properly parse any data within
555      the faultdetail() segment.  If you don't plan on using the
556      faultdetail() field, then the default code will usually suffice.
557      Otherwise, continue reading on.
558  
559      Assume you have faults that look like (A1) and (A2) from the previous
560      EXAMPLE SERVER-SIDE FAULT CODE.  Here's example code on how to emit
561      proper notification back to the user of the SOAP client:
562
563
564   # Handle any faults, as they occur.
565   # Inputs: Class, SOAP::SOM
566   # Outputs: None
567   sub handleFault {
568
569       # Extract arguments.
570       my ($class, $res) = @_;
571
572       # Construct error message.
573       # Figure out if the error occurred in transport or
574       # over on the other side.
575       my $errMsg = $class->transport->status; # Assume transport error.
576       if (ref $res) {
577           # Extract base error message.
578           $errMsg = $res->faultcode . ": " . $res->faultstring . "\n";
579
580           # Extract error details.
581           my $errNo  = undef;
582           my $errStr = undef;
583           my $errDetail = "";
584           if (defined($res->faultdetail)) {
585               $errNo  = $res->faultdetail->{"err"}->{"errNo"};
586               $errStr = $res->faultdetail->{"err"}->{"errStr"};
587               $errDetail = $res->faultcode . ": (" . $errNo . ") " . $errStr;
588           }
589
590           $errMsg = $errMsg . $errDetail . "\n";
591       }
592
593       die __PACKAGE__ . "->handleFault(): Error occurred during processing.\n" . $errMsg;
594   }
595
596   Q) Okay, so I've created this "handleFault()" function within my SOAP client
597      code.  What's the proper way to use this function within the getClientHandle()
598      call?
599
600   A) Here's the proper way to use your fault handler:
601
602   # Assume you want to interact with "HoneyClient::Manager::VM" as a SOAP
603   # client, using the default address and port.
604
605   my $stub = getClientHandle(namespace     => "HoneyClient::Manager::VM",
606                              fault_handler => \&handleFault);
607
608 =head1 BUGS & ASSUMPTIONS
609
610 Most likely, you will always want to specify getClientHandle(namespace => "Path::To::Module"),
611 when creating a new SOAP client to talk to an external module.
612
613 If you just use getClientHandle(), without any module specified, then the function
614 will assume you want a SOAP client to simply talk to yourself (the calling
615 package).  While this is useful for external callbacks, it's highly
616 unlikely most people will use this for normal communication.
617
618 =head1 SEE ALSO
619
620 L<http://www.honeyclient.org/trac>
621
622 SOAP::Lite, SOAP::Transport::HTTP
623
624 L<http://www.soaplite.com>
625
626 =head1 REPORTING BUGS
627
628 L<http://www.honeyclient.org/trac/newticket>
629
630 =head1 ACKNOWLEDGEMENTS
631
632 Paul Kulchenko for developing the SOAP::Lite module.
633
634 =head1 AUTHORS
635
636 Thanh Truong, E<lt>ttruong@mitre.orgE<gt>
637
638 Darien Kindlund, E<lt>kindlund@mitre.orgE<gt>
639
640 =head1 COPYRIGHT & LICENSE
641
642 Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
643
644 This program is free software; you can redistribute it and/or
645 modify it under the terms of the GNU General Public License
646 as published by the Free Software Foundation, using version 2
647 of the License.
648  
649 This program is distributed in the hope that it will be useful,
650 but WITHOUT ANY WARRANTY; without even the implied warranty of
651 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
652 GNU General Public License for more details.
653  
654 You should have received a copy of the GNU General Public License
655 along with this program; if not, write to the Free Software
656 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
657 02110-1301, USA.
658
659
660 =cut
Note: See TracBrowser for help on using the browser.