File Coverage

blib/lib/Net/MirapointAdmin.pm
Criterion Covered Total %
statement 71 328 21.6
branch 15 154 9.7
condition 11 53 20.7
subroutine 15 27 55.5
pod 11 13 84.6
total 123 575 21.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             ##############################################################################
3             #
4             # Net::MirapointAdmin Module
5             # Copyright (C) 1999-2008, Mirapoint Inc. All rights reserved.
6             #
7             # History:
8             # 2008-01-14 nick@mirapoint.com (3.06)
9             # Fix several Unicode related bugs
10             # 2007-09-18 gpalmer@mirapoint.com (3.05)
11             # Update get_response to cope with the output from
12             # some MOS commands which do not add an extra blank
13             # line after the literal section
14             # 2007-03-20 adrianhall@mirapoint.com (3.04)
15             # Corrected 00_use.t test - no changes to the
16             # library except for the version string.
17             # 2007-03-19 adrianhall@mirapoint.com (3.03)
18             # Corrected Makefile.PL so that automated testers
19             # would work - no changes to the library except for
20             # the version string.
21             # 2007-03-12 gpalmer@mirapoint.com (3.02)
22             # Fixed issue resulting from an API change in
23             # recent versions of IO::Socket::SSL that prevented
24             # SSL connections from working
25             # 2005-03-10 ahall@mirapoint.com (3.01)
26             # Fixed issues with the return values of the low
27             # level protocol to match what the docs say and
28             # what the module tests perform
29             # 2005-03-09 ahall@mirapoint.com (3.01)
30             # Fixed the problem on later versions of Perl (> 5.8.3)
31             # that $! == errno, not a string.
32             # 2005-03-07 gpalmer@mirapoint.com (3.0)
33             # Fixed two more !defined bugs in send_command and
34             # get_response
35             # 2005-03-04 ahall@mirapoint.com (3.0)
36             # Name change to Net::MirapointAdmin
37             # Change per-character I/O to per-line I/O
38             # Make exception handling in new() work properly
39             # Fix the !defined problem in login()
40             # 2004-12-06 gpalmer@mirapoint.com (2.10)
41             # Handle reply from "autoreply get" better.
42             # 2004-08-13 ahall@mirapoint.com
43             # Doc fix - the link to the protocol guide is broken
44             # 2004-08-02 ahall@mirapoint.com (2.9)
45             # Fixed $VERSION to reflect new version number
46             # 2004-07-22 jxh@mirapoint.com
47             # Fix EOF/error handling in _getline().
48             # Permit debugfunc on new() for connection debugging.
49             # 2004-03-04 gpalmer@mirapoint.com (2.8)
50             # Fix a few bugs relating to checking the handshake
51             # from the remote server, and also hex encode
52             # the version into mos_ver.
53             # 2002-06-14 ahall@mirapoint.com (2.7)
54             # Added custom debug logging. Removed tagprint (it
55             # wasn't doing the right thing anyway!).
56             # Fixed documentation for new custom debug logging
57             # Corrected problems in the low-level problems which
58             # are only seen if you don't undef the connection prior
59             # to quitting on the newer version of Perl.
60             # 2001-12-18 ahall@mirapoint.com
61             # Updated script so that it is suitable for uploading
62             # to PAUSE/CPAN
63             # 2001-08-28 ahall@mirapoint.com (2.6)
64             # Fixed the version number to match the Version: line
65             # above.
66             # 2001-05-11 jxh@mirapoint.com
67             # Fix some runtime warnings about undefined vars.
68             # 2001-04-02 ahall@mirapoint.com (2.5)
69             # SSL Integration using std. IO::Socket::SSL instead
70             # of the Net::SSLeay module
71             # 2001-03-12 ahall@mirapoint.com (2.4)
72             # PR #7564. zero length literal strings were not
73             # handled appropriately. They are now.
74             # 2001-01-08 ahall@mirapoint.com (2.3)
75             # Updated quote/de-quote to handle parens.
76             # 2000-03-21 ahall@mirapoint.com (2.2)
77             # Integrated pod documentation from Tech Pubs. Also
78             # added a version so that make dist will work properly
79             # 2000-03-17 ahall@mirapoint.com (3895)
80             # loggedin() gets maintained across command(), so
81             # calling command('LOGOUT') will set it to 0.
82             # 2000-03-15 ahall@mirapoint.com
83             # OpenSSL is illegal in US, so disabled SSL.
84             # 2000-03-10 ahall@mirapoint.com
85             # Updated SSL integration - Released as 2.1.
86             # 2000-03-09 ahall@mirapoint.com
87             # First pass at SSL integration
88             # 2000-02-22 jxh@mirapoint.com
89             # Deal with server literals that do not end in \r\n.
90             # 2000-02-09 jxh@mirapoint.com
91             # Fixed quoting, dequoting. Released as 2.0.
92             # 2000-01-19 jxh@mirapoint.com
93             # Overhauled to include older, low-level interface.
94             # 1999-10-18 sandeep@mirapoint.com
95             # Added functionality for extracting sessionID.
96             # 1999-10-06 jxh@mirapoint.com
97             # Don't dereference unblessed object if error during
98             # new().
99             # 1999-10-05 ahall@mirapoint.com
100             # Corrected bug in get_response due to the fact that
101             # some commands only return an OK and no message
102             # afterwards (e.g. UPDATE LIST)
103             # 1999-09-27 ahall@mirapoint.com
104             # Tidyed up documentation and added DESTROY destructor
105             # function.
106             # 1999-09-26 jxh@mirapoint.com
107             # Revised to handle literal strings.
108             # Also revised the API.
109             # 1999-09-01 ahall@mirapoint.com
110             # Initial Edit
111             #
112             ##############################################################################
113             #
114             # This module provides Perl access routines for dealing
115             # with the Mirapoint administration protocol, as implemented in Mirapoint
116             # Internet Messaging appliances. Refer to http://www.mirapoint.com/.
117             #
118             # Examples of use:
119             #
120             # High-level interface: handles tag generation/stripping,
121             # quoted and literal arguments and binding to Perl data types (in an
122             # array context), optional response checking, and auto-logout before
123             # disconnection. Exception handler can be set.
124             #
125             # Both "raw" and "cooked" commands and responses are supported:
126             # send_command() with a single scalar argument simply generates and
127             # prepends a tag; get_response() checks and strips the tag from the
128             # response. send_command(LIST) quotes arguments containing embedded
129             # spaces and makes IMAP-style literals of those containing newlines,
130             # and sends the list as a single, tagged command.
131             #
132             # get_response() in stores the OK or NO response in the object, and, in a
133             # scalar context, returns any other output (minus tags) as a single string,
134             # with embedded newlines. In an array context, it returns a two-dimensional
135             # array, by line and field. Responses are dequoted, and counted literals
136             # are stored as scalars.
137             #
138             # command(), command_ok(), and command_no() behave the same way, but they
139             # combine send_command() and get_response(), and optionally check the OK/NO
140             # response.
141             #
142             # EXAMPLES OF USE:
143             #
144             # Login:
145             #
146             # $mp = Net::MirapointAdmin->new(host => $host,
147             # port => $port,
148             # debug => $debug);
149             # $mp->login($user, $password);
150             #
151             # Raw command and raw response:
152             #
153             # $status = $mp->command_ok("BACKUP STATUS");
154             # results in:
155             # C: tag BACKUP STATUS\r\n
156             # S: * tag Backup-10000 Error\r\n
157             # S: * tag Backup-10000 Done\r\n
158             # S: tag OK\r\n
159             # $status = "Backup-10000 Error\nBackup-10000 Done\n"
160             #
161             # Cooked command:
162             #
163             # $user = "bob"; $password = "pwd"; $fullname = "Bob Smith";
164             # $mp->command_ok(qw/USER ADD/, $user, $password, $fullname);
165             # results in:
166             # C: tag USER ADD bob pwd "Bob Smith"\r\n
167             # S: tag OK\r\n
168             #
169             # Cooked command and response:
170             #
171             # $pattern = ""; $start = "", $count = "";
172             # @users = $mp->command_ok(qw/USER LIST/, $pattern, $start, $count);
173             # @usernames = map { $_ = $$_[0] } @users;
174             # results in:
175             # C: tag USER LIST "" "" ""\r\n
176             # S: * tag "bob" "Bob Smith"\r\n
177             # S: * tag "joe" "Joe Brown"\r\n
178             # S: tag OK
179             # @users = ( [ "bob", "Bob Smith" ], [ "joe", "Joe Brown" ] )
180             # @usernames = ("bob", "joe");
181             #
182             # With error checking (OK, or NO followed by pattern):
183             #
184             # $mp->command_no(/Already exists/, qw/DL ADD/, $dl);
185             #
186             # Manual error checking:
187             #
188             # @response = $mp->command(qw/DLENTRY LIST/, $dl, "", "", "");
189             # if ($mp->okno =~ /^NO/) {
190             # ...
191             # }
192             #
193             # Logout:
194             #
195             # undef $mp; -- Performs logout and disconnect
196             #
197             #
198             ##############################################################################
199             package Net::MirapointAdmin;
200              
201 2     2   19752 use strict;
  2         6  
  2         281  
202 2     2   11 use vars qw($ERRSTR $VERSION $AUTOLOAD);
  2         3  
  2         289  
203              
204             $VERSION = "3.06";
205             $ERRSTR = "";
206              
207 2     2   3059 use bytes ();
  2         24  
  2         43  
208              
209 2     2   9 use Carp;
  2         4  
  2         134  
210 2     2   2842 use Socket;
  2         12687  
  2         1862  
211 2     2   2789 use IO::Socket;
  2         52872  
  2         10  
212             #
213             # We have this version to determine the right API support.
214             #
215             #
216             # Since we do not necessarily have SSL, we need to be careful when loading
217             # the module to ensure we are handling it appropriately.
218             #
219             my $SSL;
220             BEGIN {
221 2     2   2552 eval 'use IO::Socket::SSL';
  2     2   3111  
  2         172290  
  2         23  
222 2 50 33     403 if (!defined $@ || $@ ne "")
223             {
224 0         0 $SSL = 0;
225             } else {
226 2         6906 $SSL = 1;
227             }
228             }
229             #
230             ##############################################################################
231             #
232             # Function to return the SSL support.
233             #
234             sub supports_ssl
235             {
236 0     0 1 0 return $SSL;
237             }
238             #
239             ##############################################################################
240             #
241             # The entities in %MP_Fields are also the entities that can be called for
242             # setting and retrieving of information.
243             #
244             my %MP_Fields = (
245             "hostname" => undef, # Who are we connected to?
246             "portnumber" => undef, # and where?
247             "reported_hostname" => undef, # Who does it think it is?
248             "error" => undef, # What was the last error?
249             "debug" => 0, # Is debugging turned on?
250             "version" => undef, # What version are we connecting to?
251             "mos_ver" => undef, # Hex encoding of "version"
252             "connected" => 0, # Are we connected?
253             "loggedin" => 0, # and logged in?
254             "socket" => undef, # the socket descriptor?
255             "sessionid" => undef, # the sessionid?
256             "lasttag" => 0, # and the last tag we handled?
257             "exception" => undef, # the exception handler
258             "okno" => undef, # last OK/NO response
259             "ssl" => 0, # Are we on an SSL link?
260             "ignore_hand" => 0, # Ignore the Handshake?
261             "debugfunc" => undef, # Non-default debug function
262             );
263             #
264             ###############################################################################
265             #
266             # Function: AUTOLOAD
267             #
268             # This is actually the "catch-all" function. We use this to set or
269             # get the settings within %MP_Fields. In our case, this means you can do
270             # things like:
271             #
272             # $conn->debug(1);
273             # $debug = $conn->debug();
274             #
275             sub AUTOLOAD
276             {
277 6     6   72 my $self = shift;
278 6   33     19 my $type = ref($self) || croak "$self is not an object.";
279 6         10 my $name = $AUTOLOAD;
280            
281 6         34 $name =~ s/.*://; # Strip fully-qualified version.
282 6 50       18 croak "Cannot access $name in object of class $type.\n"
283             unless (exists $self->{$name});
284 6 50       15 if (@_)
285             {
286 0 0 0     0 if ($name eq "error" && $self->{"debug"})
287             {
288 0         0 print "ERROR: $_[0]\n";
289             }
290 0         0 $self->{$name} = shift;
291             }
292 6         65 return $self->{$name};
293             }
294              
295             ###############################################################################
296             #
297             # Function: _default_exception(LIST)
298             #
299             # Usage: $obj->default_exception("Doing thing 1");
300             # Arguments: none.
301             # Returns: undef.
302             # Error: dies.
303             #
304             # This is the default exception handler, used if no user-specified
305             # handler is supplied on new() or in a later call to
306             # $obj->exception(). Prints each argument and the value of
307             # $self->error, then dies.
308              
309             sub _default_exception
310             {
311 0     0   0 my $self = shift;
312 0         0 my $report = join("", @_);
313 0         0 my $err = $self->{"error"};
314 0 0       0 if (defined $err)
315             {
316 0         0 die "Error $err\n$report";
317             } else {
318 0         0 die $report;
319             }
320             }
321              
322             sub raise_exception
323             {
324 0     0 0 0 my $self = shift;
325              
326 0         0 &{$self->{exception}}($self, @_);
  0         0  
327             }
328              
329             ###############################################################################
330             # Function: debuglog(message)
331             # Usage:
332             # Arguments: Message to be logged
333             # Returns: nothing
334             # Error: returns
335             #
336             # This is the default log routine for the system
337             #
338             sub debuglog
339             {
340 0     0 0 0 my $self = shift;
341 0         0 my $msg = join('', @_);
342              
343 0 0       0 if (defined $self->{debugfunc}) {
344 0         0 $self->{debugfunc}->($msg);
345             } else {
346 0         0 print STDERR $msg, "\n";
347             }
348             }
349              
350             ###############################################################################
351             #
352             # Function: new(List)
353             #
354             # Usage: $obj = Net::MirapointAdmin->new(host=>$host)
355             # Arguments: host (string) DNS hostname, required
356             # port (numeric) TCP port number
357             # exception (subroutine) exception handler
358             # debug (boolean) Default to 0
359             # ssl (boolean) Default to 0
360             # ignore_handshake (boolean) Default to 1
361             # debugfunc (coderef) Default to undef
362             # Returns: object reference
363             # Error: raises exception
364             #
365             # Creates a blessed instance of this object, and uses it to connect.
366             # If exception is not specified, a default exception handler is used.
367             # On error, raises exception, then returns whatever the exception
368             # handler itself returns. The exception handler should usually not
369             # return at all.
370             #
371             #
372             sub new
373             {
374 1     1 1 1089 my $that = shift;
375 1   33     10 my $class = ref($that) || $that;
376 1         22 my $self = { %MP_Fields };
377 1         5 bless $self, $class;
378              
379 1         2 my %args;
380 1         2 my $connect_now = 0;
381              
382 1 50 33     11 if ($#_ < 1 || $_[0] ne "host" ) # Hostname only or host, port
383             {
384 1         4 $args{"host"} = shift;
385 1 50       6 if (@_)
386             { # host,port
387 0         0 $args{"port"} = shift;
388 0         0 $connect_now = 1;
389             }
390             } else { # named arguments
391 0         0 %args = @_;
392 0         0 $connect_now = 1;
393             }
394              
395 1         9 $self->{"hostname"} = $args{"host"};
396 1   50     11 $self->{"exception"} = $args{"exception"} || \&_default_exception;
397 1   50     8 $self->{"debug"} = $args{"debug"} || 0;
398 1   50     8 $self->{"ignore_hand"} = $args{"ignore_handshake"} || 0;
399 1   50     8 $self->{"debugfunc"} = $args{"debugfunc"} || undef;
400              
401              
402 1 50 33     6 if (defined $args{"ssl"} && $args{"ssl"} > 0)
403             {
404 0 0       0 if ($SSL != 1)
405             {
406 0         0 $self->error("SSL requested but not available");
407 0         0 $self->raise_exception();
408             } else {
409 0         0 $self->{"ssl"} = 1;
410 0   0     0 $self->{"portnumber"} = $args{"port"} || 10243;
411             }
412             } else {
413 1         3 $self->{"ssl"} = 0;
414 1   50     7 $self->{"portnumber"} = $args{"port"} || 10143;
415             }
416 1 50       6 if (!defined $args{"host"})
417             {
418 0         0 $self->error("No hostname specified in new()");
419 0         0 return $self->raise_exception();
420             }
421 1 50       4 return $self->connect($connect_now) if $connect_now;
422 1         6 return $self;
423             }
424             #
425             ###############################################################################
426             #
427             # Function: login(Username, Password)
428             #
429             # Usage: $obj->login($user, $password);
430             # Returns: defined
431             # Error: raises exception
432             #
433             # Uses the open connection to issue a LOGIN command, and sets an
434             # internal variable in the object to allow further command()s.
435             # Returns undef on an error and a defined value on success.
436              
437             sub login
438             {
439 0     0 1 0 my $self = shift;
440 0         0 my $user = shift;
441 0         0 my $pass = shift;
442 0         0 my($res);
443              
444 0 0       0 if ($self->connected != 1)
445             {
446 0         0 $self->error("Not Connected");
447 0         0 return $self->raise_exception();
448             }
449              
450 0         0 $res = $self->command_ok("LOGIN", $user, $pass);
451 0 0       0 if ($self->{"okno"} =~ /^OK/) {
452 0         0 $self->loggedin(1);
453 0         0 $self->sessionid($res);
454 0         0 return 1;
455             }
456 0         0 return undef;
457             }
458              
459              
460             ###############################################################################
461             #
462             # Function: send_command(List);
463             #
464             # Usage: $lasttag = $obj->send_command(qw/LICENSE STATUS/);
465             # Returns: string
466             # Error: raises exception
467             #
468             # This function sets up a tag (based on $self->{lasttag}) and sends a
469             # command to the admin API consisting of arguments in LIST. We return the
470             # tag to be used in a later get_response() call.
471             # Each element of the list is quoted if it contains spaces, or is sent as
472             # an IMAP-style counted literal if it contains newlines.
473             # If LIST contains only a single scalar, it is neither quoted nor sent as a
474             # literal.
475              
476             sub send_command
477             {
478 0     0 1 0 my $self = shift;
479 0         0 my @cmd = @_;
480 0         0 my $tag = $self->{lasttag}++;
481 0         0 my ($cmd);
482            
483 0 0       0 if ($self->connected == 0)
484             {
485 0         0 $self->error("Not Connected.");
486 0         0 return $self->raise_exception();
487             }
488 0 0       0 if (@cmd < 2) { # scalar command
489 0         0 $cmd = $tag . " " . $cmd[0];
490             } else {
491 0         0 $cmd = $tag . " " . $self->_pack_args(@cmd);
492             }
493              
494 0 0       0 if (! $self->xmit($cmd)) {
495 0         0 return undef;
496             }
497              
498 0         0 return $tag;
499             }
500              
501              
502              
503             ###############################################################################
504             #
505             # Function: get_response(Tag)
506             #
507             # Usage: $obj->get_response($lasttag);
508             # Returns: defined
509             # Error: raises exception
510             #
511             # This function reads a multi-line admin response and returns it, in an
512             # array context, as an array of arrays, by line and field. Responses are
513             # dequoted, and counted literals are stored as scalars.
514             # Called in a scalar context, the multi-line response is only stripped of
515             # tags and returned in a single string, with embedded newlines.
516             # On error, we return undef and $obj->error is set appropriately.
517             # The OK/NO response line is stored in the object, to permit multi-
518             # threaded operation.
519              
520             sub get_response
521             {
522 0     0 1 0 my $self = shift;
523 0         0 my $tag = shift;
524 0         0 my($response, @response, @line, $line, $lineref);
525 0         0 my($done, $lit, $count, $buf);
526 0         0 my $cooked = wantarray;
527              
528 0 0       0 if ($self->connected == 0)
529             {
530 0         0 $self->error("Not Connected.");
531 0         0 return $self->raise_exception();
532             }
533 0         0 @response = (); # cooked response
534 0         0 $response = ""; # raw response
535 0         0 $self->okno(undef);
536 0         0 my $blankOK = 0;
537 0         0 while (1)
538             {
539 0         0 $line = $self->_getline;
540 0 0       0 if (!defined $line)
541             {
542 0         0 $self->error("Connection was dropped.");
543 0         0 return $self->raise_exception();
544             }
545 0 0       0 if ( $line eq '' )
546             {
547 0         0 $self->error("EOF on connection.");
548 0         0 return $self->raise_exception();
549             }
550 0 0       0 if ($blankOK == 1)
551             {
552 0         0 $blankOK = 0;
553 0 0       0 next if ($line =~ /^[\r\n]+$/);
554             }
555 0         0 $lineref = [];
556             #
557             # Now that we have a line, check to see if it is a
558             # continuation line or a command termination (or
559             # something else) and act appropriately.
560             #
561 0 0 0     0 if ($line =~ /^\* $tag /)
    0          
562             {
563 0         0 $line =~ s/^\* $tag //g;
564 0         0 $line =~ s/\r\n$/\n/;
565 0 0       0 if ($cooked) {
566 0         0 chop($line); # discard newline
567             } else {
568 0         0 $response .= $line; # keep newline
569             }
570             # continue processing arguments
571             } elsif ($line =~ /^$tag OK/ || $line =~ /^$tag NO/) {
572 0         0 $line =~ s/^$tag //g;
573 0         0 $line =~ s/\r\n$//;
574 0         0 $self->okno($line);
575 0 0       0 if ($cooked) {
576 0         0 return @response;
577             } else {
578 0         0 return $response;
579             }
580             } else {
581 0         0 $line =~ s/\r\n$/\n/;
582             # This is possible during clean-up
583 0 0       0 if ($line eq "") {
584 0         0 return @response;
585             }
586 0         0 $self->error("Unknown tag: $line");
587 0         0 return $self->raise_exception();
588             }
589 0 0       0 $self->_dequote_args($lineref, \$line) if $cooked;
590 0 0 0     0 if (defined $line && $line =~ /\{[0-9]+\}\n*$/) {
591             # Handle Literal String
592 0         0 $lit = "";
593 0         0 $count = $line;
594 0         0 $count =~ s/^.*\{(\d+)\}$/$1/;
595 0         0 while ($count > 0) {
596 0         0 $line = $self->_getline;
597 0 0       0 unless (defined $line)
598             {
599 0         0 $self->error("Connection was dropped.");
600 0         0 return $self->raise_exception();
601             }
602 0 0       0 if ($line eq '')
603             {
604 0         0 $self->error("EOF on connection.");
605 0         0 return $self->raise_exception();
606             }
607             # _getline will eat the \r\n after the literal
608             # if the literal does not itself end with \n.
609             # In this case, $count would become negative.
610 0 0       0 if ($count < bytes::length($line)) {
611 0         0 substr($line, $count) = "";
612             }
613 0         0 $count -= bytes::length($line);
614 0         0 $lit .= $line;
615 0 0       0 last if bytes::length($line) <= 0;
616             }
617 0 0       0 if ($cooked) {
618 0         0 push(@$lineref, $lit);
619             } else {
620 0         0 $response .= $lit; $response .= "\n";
  0         0  
621             }
622             # Eat closing CRLF if necessary
623 0 0 0     0 $blankOK = 1
624             if ($lit eq "" || $lit =~ /\n$/);
625             }
626 0 0       0 push(@response, $lineref) if $cooked;
627             }
628             # We should never get here, but if we do, its an error.
629 0         0 $self->error("Net::MirapointAdmin Generic Error 1");
630 0         0 return $self->raise_exception();
631             }
632              
633              
634             ###############################################################################
635             #
636             # Function: command(List);
637             #
638             # Usage: $obj->command(qw/USER LIST/);
639             # Returns: array or scalar
640             # Error: raises exception
641             #
642             # This function issues an admin command and returns its response.
643             # If LIST has only one value, it is a raw command; see send_command().
644             # In an array context, returns the response as an array of arrays,
645             # by line and field. In a scalar context, returns raw response;
646             # see get_response().
647             #
648             # On error, raises an exception. (NB: a "NO" response is not
649             # considered an error here.)
650             # The OK/NO response line is stored in the object, to permit multi-
651             # threaded operation.
652             #
653              
654             sub command
655             {
656 0     0 1 0 my ($self) = shift;
657 0         0 my ($tag, $response, @response);
658              
659 0         0 $tag = $self->send_command(@_);
660 0 0       0 return $self->raise_exception() unless $tag;
661 0 0       0 if (wantarray) {
662 0         0 @response = $self->get_response($tag);
663             } else {
664 0         0 $response = $self->get_response($tag);
665             }
666 0 0       0 $self->{"loggedin"} = 0 if ($_[0] =~ /logout/i);
667 0 0       0 return (wantarray ? @response : $response);
668             }
669              
670              
671             ###############################################################################
672             #
673             # Function: command_ok(List);
674             #
675             # Usage: $obj->command_ok(qw/MAILBOX LIST/, "%");
676             # Returns: array or scalar
677             # Error: raises exception
678             #
679             # This function calls command(), but then checks the OK/NO response
680             # and raises an exception if the response is not "OK".
681             # It's useful for issuing routine commands that are expected to
682             # produce an "OK" response.
683             #
684             # If LIST has only one value, it is a raw command; see send_command().
685             # In an array context, returns the response as an array of arrays,
686             # by line and field. In a scalar context, returns raw response;
687             # see get_response().
688              
689             sub command_ok
690             {
691 0     0 1 0 my ($self) = shift;
692 0         0 my ($response, @response);
693 0 0       0 if (wantarray) {
694 0         0 @response = $self->command(@_);
695             } else {
696 0         0 $response = $self->command(@_);
697             }
698 0 0       0 if ($self->okno !~ /^OK/)
699             {
700 0         0 return $self->raise_exception("ERROR: ", $self->okno, " in COMMAND ", join(" ", @_), "\n");
701             }
702 0 0       0 if (wantarray) {
703 0         0 return @response;
704             } else {
705 0         0 return $response;
706             }
707             }
708              
709             ###############################################################################
710             #
711             # Function: command_no(Regexp, List);
712             #
713             # Usage: $obj->command_no(/Already exists/, qw/DL ADD/, $dl);
714             # Returns: array or scalar
715             # Error: raises exception, then returns undef
716             #
717             # This function calls command(), but then checks the OK/NO response.
718             # A "NO" response that matches the supplied regular expression is
719             # considered the same as an "OK" response.
720             # This is useful for issuing commands that can return an expected
721             # "NO" response in some cases.
722             #
723             # Many scripts will likely use command_ok() and command_no() for most
724             # of their work.
725             # If LIST has only one value, it is a raw command; see send_command().
726             #
727             # In an array context, returns the response as an array of arrays,
728             # by line and field. In a scalar context, returns raw response;
729             # see get_response().
730              
731             sub command_no
732             {
733 0     0 1 0 my ($self) = shift;
734 0         0 my ($regexp) = shift;
735 0         0 my ($response, @response);
736 0 0       0 if (wantarray) {
737 0         0 @response = $self->command(@_);
738             } else {
739 0         0 $response = $self->command(@_);
740             }
741 0 0 0     0 return $self->raise_exception("ERROR: ", $self->okno, " in COMMAND ", join(" ", @_), "\n") unless ($self->okno =~ /^OK/ || ($self->okno =~ /^NO/) && ($self->okno =~ $regexp));
      0        
742 0 0       0 if (wantarray) {
743 0         0 return @response;
744             } else {
745 0         0 return $response;
746             }
747             }
748              
749             ###############################################################################
750             #
751             # Function: DESTROY
752             #
753             # This function is called when the Perl script destructs the object. We
754             # use this to log out of the system and disconnect from the admin API.
755             #
756             sub DESTROY
757             {
758 1     1   2 my $self = shift;
759            
760 1 50       93 return if ($self->{"connected"} == 0);
761 0 0       0 return if ($self->{"loggedin"} == 0);
762              
763 0         0 $self->command("logout");
764 0 0       0 if (defined $self->{"socket"}) {
765 0         0 $self->{"socket"}->close();
766             }
767 0         0 return;
768             }
769              
770             ###############################################################################
771             #
772             # Function: _getline
773             #
774             # A low-level function that implements getline() at the socket level.
775             #
776             # Unfortunately, as of IO::Socket::SSL v0.77, getline() was not
777             # implemented. Thus, we have to do this the old fashioned way. The
778             # only real way to do this is by reading byte by byte (since in SSL
779             # we cannot go back)
780             #
781             # As of IO::Socket::SSL v0.96, this has been repaired, so this should
782             # now work.
783             #
784             sub _getline
785             {
786 1     1   4 my $self = shift;
787              
788             # If we aren't connected, return undef
789 1 50       7 return undef if ($self->connected() != 1);
790              
791             # If our socket has disappeared, return undef
792 0         0 my $fd = $self->{'socket'};
793 0 0       0 return undef if (!defined $fd);
794              
795             # This should now work.
796 0         0 my $ret = <$fd>;
797 0 0       0 if ($ret) {
798 0 0       0 $self->debuglog("S: $ret") if ($self->{"debug"});
799             } else {
800 0 0       0 $self->debuglog("S: return is undef") if ($self->{"debug"});
801             }
802              
803 0         0 return $ret;
804             }
805              
806             ###############################################################################
807             #
808             # Function: _pack_args
809             #
810             # Turns a list of scalars into a string of IMAP-style arguments,
811             # quoting blank arguments and those with embedded spaces, and
812             # encoding in literals those containing newlines.
813             # Backslashes ('\') and double-quotes ('"') are escaped with
814             # a leading backslash. Arguments containing these escapes are
815             # quoted just as for embedded spaces.
816             #
817             sub _pack_args
818             {
819 0     0   0 my ($self) = shift;
820 0         0 my ($cmd, @cmd, $lit, $quoteme);
821 0         0 $cmd = "";
822 0         0 @cmd = ();
823 0         0 for (@_) {
824 0         0 $quoteme = 0;
825 0 0       0 if (/\n/) { # literal
826 0         0 $lit = $_; # work on a copy of it
827             # $lit =~ s/[^\r]\n/\r\n/g; # force network EOLs
828 0         0 $cmd = "{" . bytes::length($lit) . "+}\r\n" . $lit;
829             } else {
830 0         0 $cmd = $_;
831 0 0       0 if (/\\/) { # escape literal backslashes
832 0         0 $cmd =~ s/\\/\\\\/g;
833 0         0 $quoteme = 1;
834             }
835 0 0       0 if (/\"/) { # escape literal quotes
836 0         0 $cmd =~ s/\"/\\\"/g;
837 0         0 $quoteme = 1;
838             }
839 0 0 0     0 if (/^$/ || /[\(\)\s]/ || $quoteme) {
      0        
840             # quote if embedded space or \ or "
841             # 2001-01-08 - also quote if embedded parens
842 0         0 $cmd = '"' . $cmd . '"';
843             }
844             # else bareword
845             }
846 0         0 push(@cmd, $cmd); # accumulate
847             }
848 0         0 return join(" ", @cmd);
849             }
850              
851             ###############################################################################
852             #
853             # Function: _dequote_args
854             #
855             # Turns a string of possibly-quoted arguments into a list.
856             # Stops processing if it encounters a literal introducer (e.g. "{42}").
857             # Double quotes ('"') and backslashes ('\') escaped with leading
858             # backslashes have the escape stripped.
859             #
860              
861             sub _dequote_args
862             {
863 0     0   0 my ($self) = shift;
864 0         0 my ($listref, $sref) = @_;
865 0         0 my ($line, @line);
866              
867             # Remove literal introducer, if any
868              
869 0 0       0 if ($$sref =~ /\{[0-9]+\}.*/) { # there's a literal
870 0         0 $line = $`;
871 0         0 $$sref = $&; # leave the introducer and the literal there
872             } else {
873 0         0 $line = $$sref;
874 0         0 $$sref = ""; # eat entire line
875             }
876              
877             # Play regexp games to be able to split out quoted strings, being careful not
878             # to be fooled by escaped quotes or escaped backslashes. Take advantage of
879             # the fact that there are neither CR nor LF characters outside a literal.
880             # If this actually works, credit goes to .
881              
882 0         0 $line =~ s/\r//g; # remove all CRs, just in case
883 0         0 $line =~ s/\\/\r/g; # hide all backslashes
884 0         0 $line =~ s/\r\r/\\/g; # reveal escaped backslashes
885 0         0 $line =~ s/\r\"/\n/g; # hide escaped quotes
886 0         0 $line =~ s/\r/\\/g; # reveal remaining backslashes
887 0         0 $line =~ s/\n/\r/g; # hide escaped quotes in a different way
888 0         0 $line =~ s/\s*(\".*?\")\s*/\n$1\n/g; # mark bounds of quoted words
889 0         0 $line =~ s/\n\n/\n/g; # fix abutting quoted words
890 0         0 $line =~ s/^\n//; # deal with quoted word first on the line
891 0         0 $line =~ s/\n$//; # deal with quoted word last on the line
892              
893             # We can now split on \n and get a list of "words", where some of them
894             # are enclosed in quotes, and the rest can be further split on whitespace
895             # to make more words.
896              
897 0         0 @line = split(/\n/, $line);
898 0         0 for (@line) {
899 0 0       0 if ($_ =~ /^\"(.*)\"$/s) { # quoted string
900 0         0 my $word = $+;
901 0         0 $word =~ s/\r/\"/g; # reveal escaped quotes
902 0         0 push(@$listref, $word);
903             } else { # bareword(s)
904 0         0 my @word = split(/\s+/, $_);
905 0         0 for (@word) {
906 0         0 s/\r/\"/g; # reveal escaped quotes
907 0         0 push(@$listref, $_);
908             }
909             }
910             }
911             }
912              
913              
914             ###############################################################################
915             ###############################################################################
916             #
917             # Compatibility (low-level) interface
918             #
919             # This low-level interface is provided for compatibility with an older
920             # instance of this module, for use with existing scripts. Most new scripts
921             # should use the high-level interface.
922             #
923             ###############################################################################
924             ###############################################################################
925              
926              
927             ###############################################################################
928             #
929             # Function: new(Hostname);
930             #
931             # Usage: $obj = Net::MirapointAdmin->new($hostname);
932             # Returns: object reference
933             # Error: calls die()
934             #
935             # Creates an object for use by the other methods in the module.
936             # Object type is the same as for the new() method in the high-level
937             # interface, but mixing them is not supported.
938              
939             # same as the new() function in the new interface, near the top of this file.
940              
941             ###############################################################################
942             #
943             # Function: connect;
944             #
945             # Usage: $obj->connect;
946             # Returns: $obj
947             # Error: raises exception, then returns undef
948             #
949             # Creates a TCP connection to the hostname given in new()
950             #
951             sub connect
952             {
953 1     1 1 29 my $self = shift;
954             # $the == 1 if in new()
955 1   50     6 my $the = shift || 0;
956              
957 1         3 $ERRSTR = ""; # Clean out the error
958 1 50       5 return $self if $self->connected(); # Just in case...
959              
960 1 50       5 if ($self->{"ssl"} == 1) {
961 0         0 $self->{"socket"} = IO::Socket::SSL->new(
962             PeerAddr => $self->hostname,
963             PeerPort => $self->portnumber,
964             Proto => 'tcp',
965             SSL_use_cert => 0,
966             SSL_verify_mode => 0x00);
967 0 0       0 if (!defined $self->{"socket"})
968             {
969 0         0 $ERRSTR = "Cannot create SSL connection: $^E";
970 0 0       0 if ($the) {
971 0         0 return undef;
972             } else {
973 0         0 $self->error($ERRSTR);
974 0         0 return $self->raise_exception();
975             }
976             }
977             } else {
978 1         11 $self->{"socket"} = IO::Socket::INET->new(
979             PeerAddr => $self->hostname,
980             PeerPort => $self->portnumber,
981             Proto => 'tcp',
982             Timeout => 20);
983 1 50       7008 if (!defined $self->{"socket"})
984             {
985 1         9 $ERRSTR = "Cannot create TCP connection: $^E";
986 1 50       5 if ($the) {
987 1         5 return undef;
988             } else {
989 0         0 $self->error($ERRSTR);
990 0         0 return $self->raise_exception();
991             }
992             }
993             }
994              
995 0         0 $self->socket->autoflush(1);
996 0         0 $self->connected(1);
997 0         0 $self->loggedin(0);
998 0         0 $self->lasttag(1);
999              
1000             #
1001             # We need to fill in the information about the version of the
1002             # Admind we are connecting to and the FQDN of the host.
1003             #
1004 0         0 my $l = $self->_getline;
1005 0 0       0 if (!defined $l)
1006             {
1007 0         0 $ERRSTR = "Cannot read handshake.";
1008 0 0       0 if ($the) {
1009 0         0 return undef;
1010             } else {
1011 0         0 $self->error($ERRSTR);
1012 0         0 return $self->raise_exception();
1013             }
1014             }
1015 0 0       0 if ($self->{"ignore_hand"} == 0) {
1016 0 0       0 if ($l !~ /\* OK ([^ ]+) admind ([0-9\.]+).*/)
1017             {
1018 0         0 $ERRSTR = "Bad handshake: $l";
1019 0 0       0 if ($the) {
1020 0         0 return undef;
1021             } else {
1022 0         0 $self->error($ERRSTR);
1023 0         0 return $self->raise_exception();
1024             }
1025             } else {
1026              
1027 0         0 $self->{'reported_hostname'} = $1;
1028 0         0 $self->{'version'} = $2;
1029 0 0       0 if ($self->version() =~ /(\d+)\.(\d+)\.(\d+)/) {
    0          
1030 0         0 my $mos_ver = $3;
1031 0         0 $mos_ver += $2 << 8;
1032 0         0 $mos_ver += $1 << 16;
1033 0         0 $self->mos_ver($mos_ver);
1034             } elsif ($self->version() =~ /(\d+)\.(\d+)/) {
1035 0         0 my $mos_ver += $2 << 8;
1036 0         0 $mos_ver += $1 << 16;
1037 0         0 $self->mos_ver($mos_ver);
1038             }
1039             }
1040             }
1041              
1042 0         0 return $self;
1043             }
1044              
1045              
1046             ###############################################################################
1047             #
1048             # Function: xmit(String);
1049             #
1050             # Usage: $obj->xmit("tag LOGIN user pass");
1051             # Returns: undef
1052             # Error: calls die()
1053             #
1054             # Writes the supplied string to the TCP connection, followed by CRLF.
1055              
1056             sub xmit
1057             {
1058 1     1 1 518 my $self = shift;
1059 1         5 my $cmd = shift;
1060 1         2 my $res;
1061              
1062             # Quickie error check --- socket must be defined, or this has
1063             # no meaning.
1064 1 50       176 return undef if ($self->connected() != 1);
1065 0 0       0 return undef unless ($self->{"socket"});
1066              
1067 0 0       0 $self->debuglog("C: $cmd") if ($self->{"debug"});
1068 0         0 $res = $self->{"socket"}->print("$cmd\r\n");
1069 0 0       0 if ($res < 1)
1070             {
1071 0         0 $self->error("Cannot write to channel: $^E");
1072 0         0 return $self->raise_exception();
1073             }
1074 0         0 return bytes::length("$cmd\r\n");
1075             }
1076              
1077              
1078             ###############################################################################
1079             #
1080             # Function: getbuf;
1081             #
1082             # Usage: $buf = $obj->getbuf;
1083             # Returns: string
1084             # Error: calls die()
1085             #
1086             # Gets a line of text from the TCP connection.
1087              
1088             sub getbuf
1089             {
1090 1     1 1 3 my $self = shift;
1091              
1092 1         5 return $self->_getline;
1093             }
1094              
1095              
1096             ##############################################################################
1097             #
1098             # Examples of use:
1099             #
1100             # Lower-level interface: (with implicit exception handler)
1101             #
1102             # $mp = Net::MirapointAdmin->new($host);
1103             # $mp->connect;
1104             # $mp->xmit("a00001 LOGIN user password");
1105             # $okno = $mp->getbuf;
1106             # if ($okno !~ /^a00001 OK/) { ... }
1107             # $mp->xmit("a00002 VERSION");
1108             # $version = $mp->getbuf;
1109             # if ($version !~ /^a00002 OK/) { ... }
1110             # $version =~ s/^.*OK //;
1111             # $mp->xmit("a00003 LOGOUT");
1112             # $okno = $mp->getbuf;
1113             # undef $mp; -- Performs disconnect
1114             #
1115              
1116             ###############################################################################
1117             #
1118             # The End. (And Lets Keep Perl Happy)
1119             #
1120             1;
1121              
1122             __END__