File Coverage

blib/lib/Net/OpenSRS/Email_APP.pm
Criterion Covered Total %
statement 24 712 3.3
branch 0 298 0.0
condition 0 102 0.0
subroutine 8 84 9.5
pod 66 66 100.0
total 98 1262 7.7


line stmt bran cond sub pod time code
1             package Net::OpenSRS::Email_APP;
2              
3 1     1   30175 use strict;
  1         3  
  1         46  
4 1     1   7 use warnings;
  1         3  
  1         40  
5 1     1   6 use vars qw($VERSION @ISA $APP_PROTOCOL_VERSION $Debug $Emit_Debug $Last_Error);
  1         7  
  1         151  
6 1     1   7 use Carp;
  1         2  
  1         101  
7 1     1   1779 use IO::Socket::SSL;
  1         147804  
  1         11  
8 1     1   2830 use IO::Select;
  1         1827  
  1         68  
9 1     1   8 use Errno;
  1         1  
  1         40  
10 1     1   2031 use Time::HiRes qw(gettimeofday tv_interval);
  1         1994  
  1         5  
11              
12             =head1 NAME
13              
14             Net::OpenSRS::Email_APP -- Communicate using the OpenSRS Email Service Account Provisioning Protocol
15              
16             =head1 VERSION
17              
18             Version 0.61
19              
20             =cut
21              
22             our $VERSION = '0.61';
23             $APP_PROTOCOL_VERSION='3.4';
24             $Debug=0;
25             $Emit_Debug = sub { print STDERR join("\n", @_) . "\n"; };
26              
27             # All possible OpenSRS Email Service APP environments
28             my %environments = (
29             test => 'admin.test.hostedemail.com:4449',
30             production => 'admin.hostedemail.com:4449',
31             );
32              
33             # Default timeout
34             my $Timeout = 10;
35             my $Buf_len = 32768;
36              
37             =head1 SYNOPSIS
38              
39             use strict;
40             use Net::OpenSRS::Email_APP;
41              
42             my $app = new Net::OpenSRS::Email_APP(Environment=>'test',
43             User=>'admin',
44             Domain=>'example.com',
45             Password=>'secret')
46             || die "I encountered a problem: " . \
47             Net::OpenSRS::Email_APP::errstr();
48              
49             $app->login();
50              
51             my $rows = $app->get_company_domains();
52             foreach my $r (@$rows) {
53             print "$r->{DOMAIN}\n";
54             }
55              
56             $app->quit();
57              
58             =head1 DESCRIPTION
59              
60             "Net::OpenSRS::Email_APP" provides an object interface for
61             communicating OpenSRS Email Service Account Provisioning Protocol
62             (APP). For this module to be useful to you, you will need an
63             OpenSRS reseller account, and MAC credentials. This module uses
64             IO::Socket::SSL, thus depends upon its presence to function.
65              
66             =cut
67              
68             =head1 CONSTRUCTOR
69              
70             =head2 new ( [ARGS] )
71              
72             Creates a "Net::OpenSRS::Email_APP" object. "new"
73             requires the User, Domain and Password arguments in
74             key-value pairs.
75            
76             The following key-value pairs are accepted:
77            
78             Environment Either 'test' or 'production' - defaults to 'test'
79             User User for login() to use
80             Domain Domain for login() to use
81             Password Password for login() to use
82              
83             =cut
84             sub new {
85 0     0 1   my ($class, %arg) = @_;
86              
87 0           my $self = {};
88 0           bless $self, $class;
89 0           $self->_initialise(%arg);
90 0           return $self;
91             }
92              
93             sub _initialise {
94 0     0     my ($self, %arg) = @_;
95              
96 0           my $env = delete $arg{Environment};
97 0 0 0       if (defined $env && !exists $environments{$env}) {
98 0           croak "Net::OpenSRS::Email_APP: Unsupported environment: $env";
99             }
100              
101             # If unspecified, default to test
102 0 0         if (defined $env) {
103 0           $arg{PeerAddr} = $environments{$env};
104             }
105             else {
106 0           $arg{PeerAddr} = $environments{test};
107 0           $env = 'test';
108             }
109              
110 0           $self->{environment} = $env;
111 0           $self->{username} = delete $arg{User};
112 0           $self->{domain} = delete $arg{Domain};
113 0           $self->{password} = delete $arg{Password};
114 0 0 0       if (!defined $self->{username} || $self->{username} eq '') {
115 0           croak 'Net::OpenSRS::Email_APP: User must be specified';
116             }
117 0 0 0       if (!defined $self->{domain} || $self->{domain} eq '') {
118 0           croak 'Net::OpenSRS::Email_APP: Domain must be specified';
119             }
120 0 0 0       if (!defined $self->{password} || $self->{password} eq '') {
121 0           croak 'Net::OpenSRS::Email_APP: Password must be specified';
122             }
123              
124             # Hard-wire this, udp will never work
125 0           $arg{Proto} = 'tcp';
126            
127 0 0         if (!exists $arg{Timeout}) {
128 0           $arg{Timeout} = $Timeout;
129             }
130              
131 0 0         if ($Debug) {
132 0           $Emit_Debug->("Net::OpenSRS::Email_APP using:\nEnvironment: $self->{environment}\nHost/Port: $arg{PeerAddr}\nUser: $self->{username}\nDomain: $self->{domain}\nPassword: $self->{password}\nTimeout: $arg{Timeout}\n\n");
133             }
134              
135 0           my $socket = new IO::Socket::SSL(%arg);
136 0           $self->{socket} = $socket;
137 0           return $self;
138             }
139              
140             =head1 GENERAL METHODS
141              
142             =head2 login ()
143              
144             Attempt to login to OpenSRS APP
145              
146             =cut
147             sub login {
148 0     0 1   my ($self) = @_;
149              
150 0           my $resp = $self->_read();
151 0           $self->_send("VER VER=\"$APP_PROTOCOL_VERSION\"");
152 0           my ($r_code, $r) = $self->_read();
153 0 0         if ($r_code != 0) {
154 0           confess "Unable to VER: $r";
155             }
156              
157 0           my %args;
158 0           $args{User} = $self->{username};
159 0           $args{Domain} = $self->{domain};
160 0           $args{Password} = $self->{password};
161 0           my ($rows,$error) = $self->_call_opensrs(Required=>[qw/User Domain Password/], Args=>\%args);
162 0 0         if (defined $error) {
163 0           carp $error;
164 0           $Last_Error = $error;
165 0           return 0;
166             }
167              
168 0           return 1;
169             }
170              
171             =head2 quit ()
172              
173             Close your APP connection
174              
175             =cut
176             sub quit {
177 0     0 1   my ($self) = @_;
178              
179 0           $self->_send('QUIT');
180 0           my ($r_code, $r) = $self->_read();
181 0 0         if ($r_code != 0) {
182 0           carp "quit: Unsuccessful return from OpenSRS: ($r_code) $r";
183             }
184 0           my $socket = $self->{socket};
185 0           $socket->close(SSL_fast_shutdown=>1);
186             }
187              
188             =head2 debug ( $level, $debug_cb )
189              
190             Set the debug level, debug output will optionally be returned
191             using supplied callback
192              
193             If $debug_cb is not supplied, output will be emitted via STDERR
194              
195             =cut
196             sub debug {
197 0     0 1   my ($self, $level, $debug_cb) = @_;
198              
199 0 0 0       if (defined $level && $level =~ /^\d+$/) {
200 0           $Debug = $level;
201             }
202              
203 0 0 0       if (defined $debug_cb && ref($debug_cb) eq 'CODE') {
204 0           $Emit_Debug = $debug_cb;
205             }
206             }
207              
208             =head2 last_status ( )
209              
210             Returns an array containing the status code and status text from
211             the last OpenSRS call
212              
213             Note: The status text may be undefined, you should test for this.
214              
215             =cut
216             sub last_status {
217 0     0 1   my ($self) = @_;
218              
219 0           my $status_code = $self->{status_code};
220 0           my $status_text = $self->{status_text};
221              
222 0           return ($status_code, $status_text);
223             }
224              
225             =head1 GET METHODS
226              
227             =head2 get_admin ( [ARGS] )
228              
229             The privilege level of this mailbox
230              
231             Required: Domain Mailbox
232              
233             =cut
234             sub get_admin {
235 0     0 1   my ($self, %args) = @_;
236              
237 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
238 0 0         if (defined $error) {
239 0           carp $error;
240 0           $Last_Error = $error;
241             }
242            
243 0           return $rows;
244             }
245              
246             =head2 get_alternate_mailbox_names ( [ARGS] )
247              
248             Given a comma-seperated list of email addresses, provide a
249             comma-seperated list of available alternatives
250              
251             Required: Mailbox_List
252              
253             =cut
254             sub get_alternate_mailbox_names {
255 0     0 1   my ($self, %args) = @_;
256              
257 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Mailbox_List/], Args=>\%args);
258 0 0         if (defined $error) {
259 0           carp $error;
260 0           $Last_Error = $error;
261             }
262            
263 0           return $rows;
264             }
265              
266             =head2 get_company_domains ()
267              
268             A list of all domains
269              
270             =cut
271             sub get_company_domains {
272 0     0 1   my ($self) = @_;
273              
274 0           my ($rows, $error) = $self->_call_opensrs();
275 0 0         if (defined $error) {
276 0           carp $error;
277 0           $Last_Error = $error;
278             }
279            
280 0           return $rows;
281             }
282              
283             =head2 get_domain ( [ARGS] )
284              
285             Information about this domain
286              
287             Required: Domain
288              
289             =cut
290             sub get_domain {
291 0     0 1   my ($self, %args) = @_;
292              
293 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
294 0 0         if (defined $error) {
295 0           carp $error;
296 0           $Last_Error = $error;
297             }
298            
299 0           return $rows;
300             }
301              
302             =head2 get_domain_allow_list ( [ARGS] )
303              
304             The allowed senders list for this domain
305              
306             Required: Domain
307              
308             =cut
309             sub get_domain_allow_list {
310 0     0 1   my ($self, %args) = @_;
311              
312 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
313 0 0         if (defined $error) {
314 0           carp $error;
315 0           $Last_Error = $error;
316             }
317            
318 0           return $rows;
319             }
320              
321             =head2 get_domain_block_list ( [ARGS] )
322              
323             The blocked senders list for this domain
324              
325             Required: Domain
326              
327             =cut
328             sub get_domain_block_list {
329 0     0 1   my ($self, %args) = @_;
330              
331 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
332 0 0         if (defined $error) {
333 0           carp $error;
334 0           $Last_Error = $error;
335             }
336            
337 0           return $rows;
338             }
339              
340             =head2 get_domain_brand ( [ARGS] )
341              
342             The name of the brand associated to this domain
343              
344             Required: Domain
345              
346             =cut
347             sub get_domain_brand {
348 0     0 1   my ($self, %args) = @_;
349              
350 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
351 0 0         if (defined $error) {
352 0           carp $error;
353 0           $Last_Error = $error;
354             }
355            
356 0           return $rows;
357             }
358              
359             =head2 get_domain_mailboxes ( [ARGS] )
360              
361             The list of mailboxes for this domain
362            
363             Required: Domain
364              
365             =cut
366             sub get_domain_mailboxes {
367 0     0 1   my ($self, %args) = @_;
368              
369 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
370 0 0         if (defined $error) {
371 0           carp $error;
372 0           $Last_Error = $error;
373             }
374            
375 0           return $rows;
376             }
377              
378             =head2 get_domain_mailbox_limits ( [ARGS] )
379              
380             Counts of each mailbox type permitted to be configured for
381             this domain
382              
383             Required: Domain
384              
385             =cut
386             sub get_domain_mailbox_limits {
387 0     0 1   my ($self, %args) = @_;
388              
389 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
390 0 0         if (defined $error) {
391 0           carp $error;
392 0           $Last_Error = $error;
393             }
394            
395 0           return $rows;
396             }
397              
398             =head2 get_domain_workgroups ( [ARGS] )
399              
400             The list of workgroups for this domain
401              
402             Required: Domain
403              
404             =cut
405              
406             sub get_domain_workgroups {
407 0     0 1   my ($self, %args) = @_;
408              
409 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
410 0 0         if (defined $error) {
411 0           carp $error;
412 0           $Last_Error = $error;
413             }
414            
415 0           return $rows;
416             }
417              
418             =head2 get_group_alias_mailbox ( [ARGS] )
419              
420             List the attributes and members of this mailing-list
421              
422             Required: Domain Group_Alias_Mailbox
423              
424             =cut
425             sub get_group_alias_mailbox {
426 0     0 1   my ($self, %args) = @_;
427              
428 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Group_Alias_Mailbox/], Args=>\%args);
429 0 0         if (defined $error) {
430 0           carp $error;
431 0           $Last_Error = $error;
432             }
433            
434 0           return $rows;
435             }
436              
437             =head2 get_mailbox ( [ARGS] )
438              
439             Information about this mailbox (ONLY regular and filter-only
440             mailboxes)
441              
442             Required: Domain Mailbox
443              
444             =cut
445             sub get_mailbox {
446 0     0 1   my ($self, %args) = @_;
447              
448 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
449 0 0         if (defined $error) {
450 0           carp $error;
451 0           $Last_Error = $error;
452             }
453            
454 0           return $rows;
455             }
456              
457             =head2 get_mailbox_allow_list ( [ARGS] )
458              
459             The allowed senders list for this mailbox
460              
461             Required: Domain Mailbox
462              
463             =cut
464             sub get_mailbox_allow_list {
465 0     0 1   my ($self, %args) = @_;
466              
467 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
468 0 0         if (defined $error) {
469 0           carp $error;
470 0           $Last_Error = $error;
471             }
472            
473 0           return $rows;
474             }
475              
476             =head2 get_mailbox_any ( [ARGS] )
477              
478             Information about this mailbox (INCLUDING forward-only and
479             mailing-lists)
480              
481             Required: Domain Mailbox
482              
483             =cut
484             sub get_mailbox_any {
485 0     0 1   my ($self, %args) = @_;
486              
487 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
488 0 0         if (defined $error) {
489 0           carp $error;
490 0           $Last_Error = $error;
491             }
492            
493 0           return $rows;
494             }
495              
496             =head2 get_mailbox_autorespond ( [ARGS] )
497              
498             The autoresponse state, text and attributes for this mailbox
499              
500             Required: Domain Mailbox
501              
502             =cut
503             sub get_mailbox_autorespond {
504 0     0 1   my ($self, %args) = @_;
505              
506 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
507 0 0         if (defined $error) {
508 0           carp $error;
509 0           $Last_Error = $error;
510             }
511            
512 0           return $rows;
513             }
514              
515             =head2 get_mailbox_availability ( [ARGS] )
516              
517             Supplying a comma-seperated list of users, indicate whether
518             they already exist or not
519              
520             Required: Domain Mailbox_List
521              
522             =cut
523             sub get_mailbox_availability {
524 0     0 1   my ($self, %args) = @_;
525              
526 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox_List/], Args=>\%args);
527 0 0         if (defined $error) {
528 0           carp $error;
529 0           $Last_Error = $error;
530             }
531            
532 0           return $rows;
533             }
534              
535             =head2 get_mailbox_block_list ( [ARGS] )
536              
537             The blocked senders list for this mailbox
538              
539             Required: Domain Mailbox
540              
541             =cut
542             sub get_mailbox_block_list {
543 0     0 1   my ($self, %args) = @_;
544              
545 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
546 0 0         if (defined $error) {
547 0           carp $error;
548 0           $Last_Error = $error;
549             }
550            
551 0           return $rows;
552             }
553              
554             =head2 get_mailbox_forward ( [ARGS] )
555              
556             Configured forwarding details for this regular mailbox
557              
558             Required: Domain Mailbox
559              
560             =cut
561             sub get_mailbox_forward {
562 0     0 1   my ($self, %args) = @_;
563              
564 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
565 0 0         if (defined $error) {
566 0           carp $error;
567 0           $Last_Error = $error;
568             }
569            
570 0           return $rows;
571             }
572              
573             =head2 get_mailbox_forward_only ( [ARGS] )
574              
575             Details for this forward-only mailbox
576              
577             Required: Domain Mailbox
578              
579             =cut
580             sub get_mailbox_forward_only {
581 0     0 1   my ($self, %args) = @_;
582              
583 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
584 0 0         if (defined $error) {
585 0           carp $error;
586 0           $Last_Error = $error;
587             }
588            
589 0           return $rows;
590             }
591              
592             =head2 get_mailbox_suspension ( [ARGS] )
593              
594             List the suspension status of each service for this mailbox
595              
596             Required: Domain Mailbox
597              
598             =cut
599             sub get_mailbox_suspension {
600 0     0 1   my ($self, %args) = @_;
601              
602 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
603 0 0         if (defined $error) {
604 0           carp $error;
605 0           $Last_Error = $error;
606             }
607            
608 0           return $rows;
609             }
610              
611             =head2 get_num_domain_mailboxes ( [ARGS] )
612              
613             Counts of each mailbox type and whether a domain
614             catch-all is configured
615              
616             Required: Domain
617              
618             =cut
619             sub get_num_domain_mailboxes {
620 0     0 1   my ($self, %args) = @_;
621              
622 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
623 0 0         if (defined $error) {
624 0           carp $error;
625 0           $Last_Error = $error;
626             }
627            
628 0           return $rows;
629             }
630              
631             =head1 CREATE METHODS
632              
633             =head2 create_alias_mailbox ( [ARGS] )
634              
635             Add an alias pointing to another mailbox on this domain
636            
637             Required: Domain Alias_Mailbox Mailbox
638              
639             =cut
640             sub create_alias_mailbox {
641 0     0 1   my ($self, %args) = @_;
642              
643 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Alias_Mailbox Mailbox/], Args=>\%args);
644 0 0         if (defined $error) {
645 0           carp $error;
646 0           $Last_Error = $error;
647             }
648            
649 0           return $rows;
650             }
651              
652             =head2 create_domain ( [ARGS] )
653              
654             Add a new domain
655            
656             Required: Domain
657             Optional: Timezone Language FilterMX Spam_Tag Spam_Folder Spam_Level
658              
659             =cut
660             sub create_domain {
661 0     0 1   my ($self, %args) = @_;
662              
663 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Optional=>[qw/Timezone Language FilterMX Spam_Tag Spam_Folder Spam_Level/], Args=>\%args);
664 0 0         if (defined $error) {
665 0           carp $error;
666 0           $Last_Error = $error;
667             }
668            
669 0           return $rows;
670             }
671              
672             =head2 create_domain_alias ( [ARGS] )
673              
674             Creates a domain aliased to this one
675            
676             Required: Domain Alias
677             =cut
678             sub create_domain_alias {
679 0     0 1   my ($self, %args) = @_;
680              
681 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Alias/], Args=>\%args);
682 0 0         if (defined $error) {
683 0           carp $error;
684 0           $Last_Error = $error;
685             }
686            
687 0           return $rows;
688             }
689              
690             =head2 create_domain_welcome_email ( [ARGS] )
691              
692             The welcome message to send to each new user for this domain
693            
694             Required: Domain Welcome_Text Welcome_Subject From_Name From_Address Charset Mime_Type
695              
696             =cut
697             sub create_domain_welcome_email {
698 0     0 1   my ($self, %args) = @_;
699              
700 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Welcome_Text Welcome_Subject From_Name From_Address Charset Mime_Type/], Args=>\%args);
701 0 0         if (defined $error) {
702 0           carp $error;
703 0           $Last_Error = $error;
704             }
705            
706 0           return $rows;
707             }
708              
709             =head2 create_group_alias_mailbox ( [ARGS] )
710              
711             Creates a mailing-list to the specified list of addresses
712            
713             Required: Domain Group_Alias_Mailbox Workgroup Alias_To_Email_CDL
714             Optional: Spam_Level
715              
716             =cut
717             sub create_group_alias_mailbox {
718 0     0 1   my ($self, %args) = @_;
719              
720 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Group_Alias_Mailbox Workgroup Alias_To_Email_CDL/], Optional=>[qw/Spam_Level/], Args=>\%args);
721 0 0         if (defined $error) {
722 0           carp $error;
723 0           $Last_Error = $error;
724             }
725            
726 0           return $rows;
727             }
728              
729             =head2 create_mailbox ( [ARGS] )
730              
731             Create a regular or filter-only mailbox
732            
733             Required: Domain Mailbox Workgroup Password
734             Optional: FilterOnly First_Name Last_Name Phone Fax Title Timezone Lang Spam_Tag Spam_Folder Spam_Level
735              
736             =cut
737             sub create_mailbox {
738 0     0 1   my ($self, %args) = @_;
739              
740 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox Workgroup Password/], Optional=>[qw/FilterOnly First_Name Last_Name Phone Fax Title Timezone Lang Spam_Tag Spam_Folder Spam_Level/], Args=>\%args);
741 0 0         if (defined $error) {
742 0           carp $error;
743 0           $Last_Error = $error;
744             }
745            
746 0           return $rows;
747             }
748              
749             =head2 create_mailbox_forward_only ( [ARGS] )
750              
751             Creates an alias which forwards to any single address
752            
753             Required: Domain Mailbox Workgroup Forward_Email
754             Optional: Spam_Level
755              
756             =cut
757             sub create_mailbox_forward_only {
758 0     0 1   my ($self, %args) = @_;
759              
760 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox Workgroup Forward_Email/], Optional=>[qw/Spam_Level/], Args=>\%args);
761 0 0         if (defined $error) {
762 0           carp $error;
763 0           $Last_Error = $error;
764             }
765            
766 0           return $rows;
767             }
768              
769             =head2 create_workgroup ( [ARGS] )
770              
771             Create a workgroup within this domain
772            
773             Required: Domain Workgroup
774              
775             =cut
776             sub create_workgroup {
777 0     0 1   my ($self, %args) = @_;
778              
779 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Workgroup/], Args=>\%args);
780 0 0         if (defined $error) {
781 0           carp $error;
782 0           $Last_Error = $error;
783             }
784            
785 0           return $rows;
786             }
787              
788             =head1 DELETE METHODS
789              
790             =head2 delete_domain ( [ARGS] )
791              
792             Delete this domain
793              
794             Required: Domain
795             Optional: Cascade
796              
797             =cut
798             sub delete_domain {
799 0     0 1   my ($self, %args) = @_;
800              
801 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Optional=>[qw/Cascade/], Args=>\%args);
802 0 0         if (defined $error) {
803 0           carp $error;
804 0           $Last_Error = $error;
805             }
806            
807 0           return $rows;
808             }
809              
810             =head2 delete_group_alias_mailbox ( [ARGS] )
811              
812             Deletes this mailing-list
813              
814             Required: Domain Group_Alias_Mailbox
815            
816             =cut
817             sub delete_group_alias_mailbox {
818 0     0 1   my ($self, %args) = @_;
819              
820 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Group_Alias_Mailbox/], Args=>\%args);
821 0 0         if (defined $error) {
822 0           carp $error;
823 0           $Last_Error = $error;
824             }
825            
826 0           return $rows;
827             }
828              
829             =head2 delete_domain_alias ( [ARGS] )
830              
831             Delete this domain alias
832              
833             Required: Alias
834              
835             =cut
836             sub delete_domain_alias {
837 0     0 1   my ($self, %args) = @_;
838              
839 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Alias/], Args=>\%args);
840 0 0         if (defined $error) {
841 0           carp $error;
842 0           $Last_Error = $error;
843             }
844            
845 0           return $rows;
846             }
847              
848             =head2 delete_domain_welcome_email ( [ARGS] )
849              
850             Delete the welcome email for this domain
851            
852             Required: Domain
853              
854             =cut
855             sub delete_domain_welcome_email {
856 0     0 1   my ($self, %args) = @_;
857              
858 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Args=>\%args);
859 0 0         if (defined $error) {
860 0           carp $error;
861 0           $Last_Error = $error;
862             }
863            
864 0           return $rows;
865             }
866              
867             =head2 delete_mailbox ( [ARGS] )
868              
869             Deletes this regular or filter-only mailbox
870              
871             Required: Domain Mailbox
872            
873             =cut
874             sub delete_mailbox {
875 0     0 1   my ($self, %args) = @_;
876              
877 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
878 0 0         if (defined $error) {
879 0           carp $error;
880 0           $Last_Error = $error;
881             }
882            
883 0           return $rows;
884             }
885              
886             =head2 delete_mailbox_any ( $domain, $mailbox )
887              
888             Deletes this mailbox (irrespective of type)
889            
890             Required: Domain Mailbox
891              
892             =cut
893             sub delete_mailbox_any {
894 0     0 1   my ($self, %args) = @_;
895              
896 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
897 0 0         if (defined $error) {
898 0           carp $error;
899 0           $Last_Error = $error;
900             }
901            
902 0           return $rows;
903             }
904              
905             =head2 delete_mailbox_forward_only ( [ARGS] )
906              
907             Deletes this forward-only mailbox
908              
909             Required: Domain Mailbox
910            
911             =cut
912             sub delete_mailbox_forward_only {
913 0     0 1   my ($self, %args) = @_;
914              
915 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
916 0 0         if (defined $error) {
917 0           carp $error;
918 0           $Last_Error = $error;
919             }
920            
921 0           return $rows;
922             }
923              
924             =head2 delete_workgroup ( [ARGS] )
925              
926             Delete a workgroup within this domain
927            
928             Required: Domain Workgroup
929             Optional: Cascade
930              
931             =cut
932             sub delete_workgroup {
933 0     0 1   my ($self, %args) = @_;
934              
935 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/Cascade/], Args=>\%args);
936 0 0         if (defined $error) {
937 0           carp $error;
938 0           $Last_Error = $error;
939             }
940            
941 0           return $rows;
942             }
943              
944             =head1 CHANGE METHODS
945              
946             =head2 change_domain ( [ARGS] )
947              
948             Change this domain's details
949              
950             Required: Domain (and at least one of the optionals)
951             Optional: Timezone Language FilterMX Spam_Tag Spam_Folder Spam_Level
952              
953             =cut
954             sub change_domain {
955 0     0 1   my ($self, %args) = @_;
956              
957 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Optional=>[qw/Timezone Language FilterMX Spam_Tag Spam_Folder Spam_Level/], Required_Optional=>1, Args=>\%args);
958 0 0         if (defined $error) {
959 0           carp $error;
960 0           $Last_Error = $error;
961             }
962            
963 0           return $rows;
964             }
965              
966             =head2 change_group_alias_mailbox ( [ARGS] )
967              
968             Alter this mailing-list
969            
970             Required: Domain Group_Alias_Mailbox (and one optional)
971             Optional: Alias_To_Email_CDL Spam_Level
972              
973             =cut
974             sub change_group_alias_mailbox {
975 0     0 1   my ($self, %args) = @_;
976              
977 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Group_Alias_Mailbox/], Optional=>[qw/Alias_To_Email_CDL Spam_Level/], Required_Optional=>1, Args=>\%args);
978 0 0         if (defined $error) {
979 0           carp $error;
980 0           $Last_Error = $error;
981             }
982            
983 0           return $rows;
984             }
985              
986             =head2 change_mailbox ( [ARGS] )
987              
988             Alters this regular or filter-only mailbox
989            
990             Required: Domain Mailbox
991             Optional: Workgroup Password FilterOnly First_Name Last_Name Phone Fax Title Timezone Language Spam_Tag Spam_Folder Spam_Level
992              
993             Note: When specifying FilterOnly, it may only be 'F' - you may change a filter-only mailbox to regular, but not the reverse.
994              
995             =cut
996             sub change_mailbox {
997 0     0 1   my ($self, %args) = @_;
998              
999 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/Workgroup Password FilterOnly First_Name Last_Name Phone Fax Title Timezone Language Spam_Tag Spam_Folder Spam_Level/], Required_Optional=>1, Args=>\%args);
1000 0 0         if (defined $error) {
1001 0           carp $error;
1002 0           $Last_Error = $error;
1003             }
1004            
1005 0           return $rows;
1006             }
1007              
1008             =head2 change_mailbox_forward_only ( [ARGS] )
1009              
1010             Alters this forward-only mailbox
1011            
1012             Required: Domain Mailbox Forward_Email
1013             Optional: New_Mailbox_Name Spam_Level
1014              
1015             =cut
1016             sub change_mailbox_forward_only {
1017 0     0 1   my ($self, %args) = @_;
1018              
1019 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox Forward_Email/], Optional=>[qw/New_Mailbox_Name Spam_Level/], Args=>\%args);
1020 0 0         if (defined $error) {
1021 0           carp $error;
1022 0           $Last_Error = $error;
1023             }
1024            
1025 0           return $rows;
1026             }
1027              
1028             =head1 SET METHODS
1029              
1030             =head2 set_domain_admin ( [ARGS] )
1031              
1032             Specify the domain administrator for this domain
1033              
1034             Required: Domain Mailbox
1035             Optional: State
1036              
1037             =cut
1038             sub set_domain_admin {
1039 0     0 1   my ($self, %args) = @_;
1040              
1041 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/State/], Args=>\%args);
1042 0 0         if (defined $error) {
1043 0           carp $error;
1044 0           $Last_Error = $error;
1045             }
1046            
1047 0           return $rows;
1048             }
1049              
1050             =head2 set_domain_allow_list ( [ARGS] )
1051              
1052             Set the permitted sender list for this domain
1053              
1054             Required: Domain List
1055              
1056             =cut
1057             sub set_domain_allow_list {
1058 0     0 1   my ($self, %args) = @_;
1059              
1060 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain List/], Args=>\%args);
1061 0 0         if (defined $error) {
1062 0           carp $error;
1063 0           $Last_Error = $error;
1064             }
1065            
1066 0           return $rows;
1067             }
1068              
1069             =head2 set_domain_block_list ( [ARGS] )
1070              
1071             Set the blocked sender list for this domain
1072              
1073             Required: Domain List
1074              
1075             =cut
1076             sub set_domain_block_list {
1077 0     0 1   my ($self, %args) = @_;
1078              
1079 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain List/], Args=>\%args);
1080 0 0         if (defined $error) {
1081 0           carp $error;
1082 0           $Last_Error = $error;
1083             }
1084            
1085 0           return $rows;
1086             }
1087              
1088             =head2 set_domain_brand ( [ARGS] )
1089              
1090             Assign a brand for this domain
1091              
1092             Required: Domain Brand_Code
1093              
1094             =cut
1095             sub set_domain_brand {
1096 0     0 1   my ($self, %args) = @_;
1097              
1098 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Brand_Code/], Args=>\%args);
1099 0 0         if (defined $error) {
1100 0           carp $error;
1101 0           $Last_Error = $error;
1102             }
1103            
1104 0           return $rows;
1105             }
1106              
1107             =head2 set_domain_catch_all_mailbox ( [ARGS] )
1108              
1109             Set the mailbox to receive mail for any non-existent recipients
1110              
1111             Required: Domain (and one of the optionals)
1112             Optional: Mailbox State
1113              
1114             Note: OpenSRS will return Internal system error if you attempt to
1115             set State='T' on a domain which currently does not have a
1116             catch-all mailbox. OpenSRS have deprecated catch-all addresses.
1117              
1118             =cut
1119             sub set_domain_catch_all_mailbox {
1120 0     0 1   my ($self, %args) = @_;
1121              
1122 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Optional=>[qw/Mailbox State/], Required_Optional=>1, Args=>\%args);
1123 0 0         if (defined $error) {
1124 0           carp $error;
1125 0           $Last_Error = $error;
1126             }
1127            
1128 0           return $rows;
1129             }
1130              
1131             =head2 set_domain_disabled_status ( [ARGS] )
1132              
1133             Enable or disable this domain
1134              
1135             Required: Domain Disabled
1136              
1137             =cut
1138             sub set_domain_disabled_status {
1139 0     0 1   my ($self, %args) = @_;
1140              
1141 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Disabled/], Args=>\%args);
1142 0 0         if (defined $error) {
1143 0           carp $error;
1144 0           $Last_Error = $error;
1145             }
1146            
1147 0           return $rows;
1148             }
1149              
1150             =head2 set_domain_mailbox_limits ( [ARGS] )
1151              
1152             Set the limit of each mailbox type which may be created on this domain
1153              
1154             Required: Domain
1155             Optional: Mailbox Filter_Only Alias Forward_Only Mailing_List
1156              
1157             =cut
1158             sub set_domain_mailbox_limits {
1159 0     0 1   my ($self, %args) = @_;
1160              
1161 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain/], Optional=>[qw/Mailbox Filter_Only Alias Forward_Only Mailing_List/], Args=>\%args);
1162 0 0         if (defined $error) {
1163 0           carp $error;
1164 0           $Last_Error = $error;
1165             }
1166            
1167 0           return $rows;
1168             }
1169              
1170              
1171             =head2 set_mail_admin ( [ARGS] )
1172              
1173             Grant or revoke administrative privileges for this mailbox
1174              
1175             Required: Domain Mailbox
1176             Optional: State
1177              
1178             =cut
1179             sub set_mail_admin {
1180 0     0 1   my ($self, %args) = @_;
1181              
1182 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/State/], Args=>\%args);
1183 0 0         if (defined $error) {
1184 0           carp $error;
1185 0           $Last_Error = $error;
1186             }
1187            
1188 0           return $rows;
1189             }
1190              
1191             =head2 set_mailbox_allow_list ( [ARGS] )
1192              
1193             Set the permitted sender list for this mailbox
1194              
1195             Required: Domain Mailbox List
1196              
1197             =cut
1198             sub set_mailbox_allow_list {
1199 0     0 1   my ($self, %args) = @_;
1200              
1201 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox List/], Args=>\%args);
1202 0 0         if (defined $error) {
1203 0           carp $error;
1204 0           $Last_Error = $error;
1205             }
1206            
1207 0           return $rows;
1208             }
1209              
1210             =head2 set_mailbox_block_list ( [ARGS] )
1211              
1212             Set the blocked sender list for this mailbox
1213              
1214             Required: Domain Mailbox List
1215              
1216             =cut
1217             sub set_mailbox_block_list {
1218 0     0 1   my ($self, %args) = @_;
1219              
1220 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox List/], Args=>\%args);
1221 0 0         if (defined $error) {
1222 0           carp $error;
1223 0           $Last_Error = $error;
1224             }
1225            
1226 0           return $rows;
1227             }
1228              
1229             =head2 set_mailbox_autorespond ( [ARGS] )
1230              
1231             Configure autoresponse for this mailbox
1232              
1233             Required: Domain Mailbox (and at least one optional)
1234             Optional: State Text
1235              
1236             =cut
1237             sub set_mailbox_autorespond {
1238 0     0 1   my ($self, %args) = @_;
1239              
1240 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/State Text/], Required_Optional=>1, Args=>\%args);
1241 0 0         if (defined $error) {
1242 0           carp $error;
1243 0           $Last_Error = $error;
1244             }
1245            
1246 0           return $rows;
1247             }
1248              
1249             =head2 set_mailbox_forward ( [ARGS] )
1250              
1251             Configure forwarding for this mailbox
1252              
1253             Required: Domain Mailbox (and at least one optional)
1254             Optional: Forward Keep_Copy State
1255              
1256             =cut
1257             sub set_mailbox_forward {
1258 0     0 1   my ($self, %args) = @_;
1259              
1260 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/Forward Keep_Copy State/], Required_Optional=>1, Args=>\%args);
1261 0 0         if (defined $error) {
1262 0           carp $error;
1263 0           $Last_Error = $error;
1264             }
1265            
1266 0           return $rows;
1267             }
1268              
1269             =head2 set_mailbox_suspension ( [ARGS] )
1270              
1271             Enable or disable services for this mailbox
1272              
1273             Required: Domain Mailbox
1274             Optional: SMTPIn SMTPRelay IMAP POP Webmail
1275              
1276             =cut
1277             sub set_mailbox_suspension {
1278 0     0 1   my ($self, %args) = @_;
1279              
1280 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/SMTPIn SMTPRelay IMAP POP Webmail/], Args=>\%args);
1281 0 0         if (defined $error) {
1282 0           carp $error;
1283 0           $Last_Error = $error;
1284             }
1285            
1286 0           return $rows;
1287             }
1288              
1289             =head2 set_workgroup_admin ( [ARGS] )
1290              
1291             Add or remove a workgroup administrator
1292              
1293             Required: Domain Mailbox
1294             Optional: State
1295              
1296             =cut
1297             sub set_workgroup_admin {
1298 0     0 1   my ($self, %args) = @_;
1299              
1300 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Optional=>[qw/State/], Args=>\%args);
1301 0 0         if (defined $error) {
1302 0           carp $error;
1303 0           $Last_Error = $error;
1304             }
1305            
1306 0           return $rows;
1307             }
1308              
1309             =head1 RENAME METHODS
1310              
1311             =head2 rename_mailbox ( [ARGS] )
1312              
1313             Rename this regular mailbox and update all references to it
1314              
1315             Required: Domain Old_Mailbox New_Mailbox
1316              
1317             =cut
1318             sub rename_mailbox {
1319 0     0 1   my ($self, %args) = @_;
1320              
1321 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Old_Mailbox New_Mailbox/], Args=>\%args);
1322 0 0         if (defined $error) {
1323 0           carp $error;
1324 0           $Last_Error = $error;
1325             }
1326            
1327 0           return $rows;
1328             }
1329              
1330             =head1 VERIFY METHODS
1331              
1332             =head2 verify_password ( [ARGS] )
1333              
1334             Verify this mailbox's password
1335              
1336             Required: Domain Mailbox Password
1337              
1338             =cut
1339             sub verify_password {
1340 0     0 1   my ($self, %args) = @_;
1341              
1342 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox Password/], Args=>\%args);
1343 0 0         if (defined $error) {
1344 0           carp $error;
1345 0           $Last_Error = $error;
1346             }
1347            
1348 0           return $rows;
1349             }
1350              
1351             =head1 SHOW METHODS
1352              
1353             =head2 show_available_offerings ( [ARGS] )
1354              
1355             Available offers for this mailbox
1356              
1357             Required: Domain Mailbox
1358              
1359             =cut
1360             sub show_available_offerings {
1361 0     0 1   my ($self, %args) = @_;
1362              
1363 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
1364 0 0         if (defined $error) {
1365 0           carp $error;
1366 0           $Last_Error = $error;
1367             }
1368            
1369 0           return $rows;
1370             }
1371              
1372             =head2 show_enabled_offerings ( [ARGS] )
1373              
1374             The active offers for this mailbox
1375              
1376             Required: Domain Mailbox
1377              
1378             =cut
1379             sub show_enabled_offerings {
1380 0     0 1   my ($self, %args) = @_;
1381              
1382 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox/], Args=>\%args);
1383 0 0         if (defined $error) {
1384 0           carp $error;
1385 0           $Last_Error = $error;
1386             }
1387            
1388 0           return $rows;
1389             }
1390              
1391             =head1 DISABLE METHODS
1392              
1393             =head2 disable_offering ( [ARGS] )
1394              
1395             Disables an active mailbox offer
1396              
1397             Required: Mailbox_Offering_ID
1398              
1399             =cut
1400             sub disable_offering {
1401 0     0 1   my ($self, %args) = @_;
1402              
1403 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Mailbox_Offering_ID/], Args=>\%args);
1404 0 0         if (defined $error) {
1405 0           carp $error;
1406 0           $Last_Error = $error;
1407             }
1408            
1409 0           return $rows;
1410             }
1411              
1412             =head1 ENABLE METHODS
1413              
1414             =head2 enable_offering ( [ARGS] )
1415              
1416             Activate the specified offer for this mailbox
1417              
1418             Required: Domain Mailbox Offering_ID
1419             Optional: Auto_Renew
1420              
1421             =cut
1422             sub enable_offering {
1423 0     0 1   my ($self, %args) = @_;
1424              
1425 0           my ($rows, $error) = $self->_call_opensrs(Required=>[qw/Domain Mailbox Offering_ID/], Optional=>[qw/Auto_Renew/], Args=>\%args);
1426 0 0         if (defined $error) {
1427 0           carp $error;
1428 0           $Last_Error = $error;
1429             }
1430            
1431 0           return $rows;
1432             }
1433              
1434             #
1435             # Only internal routines from here on..
1436             #
1437             sub _reconnect {
1438 0     0     my ($self) = @_;
1439              
1440 0 0         if ($Debug) {
1441 0           $Emit_Debug->("_reconnect: Closing original connection\n");
1442             }
1443            
1444 0           my $environment = $self->{environment};
1445 0           my $username = $self->{username};
1446 0           my $domain = $self->{domain};
1447 0           my $password = $self->{password};
1448              
1449 0           my $socket = $self->{socket};
1450 0           $socket->close(SSL_fast_shutdown=>1);
1451            
1452 0 0         $self->_initialise( Environment => $environment,
1453             User => $username,
1454             Domain => $domain,
1455             Password => $password ) || die "I encountered a problem: $Net::OpenSRS::Email_APP::Last_Error";
1456              
1457 0 0         if (!$self->login()) {
1458 0           die "unable to login to OpenSRS APP: $Net::OpenSRS::Email_APP::Last_Error";
1459             }
1460            
1461 0           return $self;
1462             }
1463              
1464             sub _call_opensrs {
1465 0     0     my ($self, %params) = @_;
1466              
1467 0           my ($sub, $cmd) = _generate_opensrs_cmd();
1468 0           my $args = _normalise_keys($params{Args});
1469 0           my @keys;
1470             my $error;
1471              
1472 0 0         if (exists $params{Required}) {
1473 0           foreach my $required (@{$params{Required}}) {
  0            
1474 0           my $r = uc($required);
1475 0 0 0       if (!exists $args->{$r} || !defined $args->{$r}) {
1476 0           $error = "$sub: Please supply $required";
1477 0           return (undef, $error);
1478             }
1479              
1480 0           push @keys, $r;
1481             }
1482             }
1483            
1484 0 0         if (exists $params{Optional}) {
1485 0           foreach my $optional (@{$params{Optional}}) {
  0            
1486 0           push @keys, uc($optional);
1487             }
1488             }
1489            
1490 0 0 0       if (exists $params{Required_Optional} && $params{Required_Optional} > 0) {
1491 0           my $expected_count = int(@{$params{Required}});
  0            
1492 0           $expected_count += $params{Required_Optional};
1493 0           my $actual_count = int(keys(%$args));
1494 0 0         if ($actual_count < $expected_count) {
1495 0           $error = "$sub: Please supply at least $params{Required_Optional} optional arguments";
1496 0           return (undef, $error);
1497             }
1498             }
1499              
1500 0           my $statement = "$cmd";
1501 0           foreach my $key (@keys) {
1502 0 0         if (exists $args->{$key}) {
1503 0           $statement .= " $key=\"$args->{$key}\"";
1504             }
1505             }
1506            
1507 0           $self->_send("$statement");
1508 0           my ($r_code, $r) = $self->_read();
1509              
1510             # Attempt a single retransmit if our read errorred
1511 0 0         if ($r_code != 0) {
1512 0 0         if ($Debug) {
1513 0           $Emit_Debug->("Got $r_code - $r, attempting reconnect and retransmit\n");
1514             }
1515              
1516 0           $self->_reconnect();
1517 0           $self->_send("$statement");
1518 0           ($r_code, $r) = $self->_read();
1519             }
1520              
1521             # Log the fact it *still* didn't work
1522 0 0         if ($r_code != 0) {
1523 0           $error = "$sub unsuccessful return from OpenSRS: ($r_code) $r";
1524 0 0         if ($Debug) {
1525 0           $Emit_Debug->("$sub unsuccessful return from OpenSRS: ($r_code) $r\n");
1526             }
1527 0           return (undef, $error);
1528             }
1529              
1530 0           return $r;
1531             }
1532              
1533             sub _normalise_keys(\%) {
1534 0     0     my ($args) = @_;
1535 0           my $new = {};
1536 0           foreach my $key (sort keys %$args) {
1537 0           $new->{uc($key)} = $args->{$key};
1538             }
1539 0           return $new;
1540             }
1541              
1542             sub _generate_opensrs_cmd {
1543 0     0     my ($sub) = (caller(2))[3] =~ /^.+::([^:]+)$/;
1544 0           return ($sub, uc($sub));
1545             }
1546              
1547             sub _send {
1548 0     0     my ($self, $msg) = @_;
1549              
1550 0           my $socket = $self->{socket};
1551 0           my $sel = new IO::Select $socket;
1552 0 0         unless ($sel->can_write($Timeout)) {
1553 0 0         if ($Debug) {
1554 0           $Emit_Debug->("_send: select can_write returns false\n");
1555             }
1556 0           $@ = '_send: timeout';
1557 0 0         $! = (exists &Errno::ETIMEDOUT ? &Errno::ETIMEDOUT : 1);
1558 0           return $!;
1559             }
1560              
1561 0 0         if ($Debug) {
1562 0           $Emit_Debug->("sending: $msg\n");
1563             }
1564              
1565 0           $SIG{PIPE} = 'IGNORE';
1566 0           my $bytes = syswrite($socket, sprintf("%s\r\n.\r\n", $msg));
1567 0 0         if (defined $bytes) {
1568 0           return 0;
1569             }
1570              
1571             # We likely got a SIGPIPE above, reconnect and try one more time
1572 0           $self->_reconnect();
1573 0           $socket = $self->{socket};
1574 0           $bytes = syswrite($socket, sprintf("%s\r\n.\r\n", $msg));
1575 0 0         if (defined $bytes) {
1576 0           return 0;
1577             }
1578             else {
1579 0           $@ = '_send: broken pipe';
1580 0 0         $! = (exists &Errno::EPIPE ? &Errno::EPIPE : 1);
1581 0           return $!;
1582             }
1583             }
1584              
1585             sub _read {
1586 0     0     my ($self) = @_;
1587              
1588             #
1589             # First lets read out the buffer a reasonable number of times
1590             # until we receive a complete response (signified by \r\n.\r\n)
1591             #
1592 0           my $buf;
1593 0           my $t0 = [gettimeofday()];
1594 0           my $elapsed = tv_interval($t0);
1595 0           my $complete_response = 0;
1596 0   0       while (!$complete_response && ($elapsed < $Timeout)) {
1597 0 0         if ($Debug > 1) {
1598 0           $Emit_Debug->("==enter buf read ==\ncomplete_response: $complete_response\nelapsed: $elapsed\nTimeout: $Timeout\n\n");
1599             }
1600 0           my $b = _read_buf($self);
1601 0 0         if (!defined $b) {
1602 0           return $!, $@;
1603             }
1604            
1605 0           $buf .= $b;
1606              
1607 0 0         if ($Debug) {
1608 0           $Emit_Debug->("read: [$b]\nbuf: [$buf]\n\n");
1609             }
1610              
1611 0 0         if ($buf =~ /\r\n\.\r\n/ms) {
1612 0           $complete_response = 1;
1613 0           last;
1614             }
1615              
1616 0           $elapsed = tv_interval($t0);
1617 0 0         if ($Debug > 1) {
1618 0           $Emit_Debug->("== buf read ==\ncomplete_response: $complete_response\nelapsed: $elapsed\n\n");
1619             }
1620             }
1621              
1622 0 0         if (!$complete_response) {
1623 0           return 1, "unable to receive complete response within $Timeout seconds\n";
1624             }
1625              
1626            
1627 0           my @response = split(/\r\n/, $buf);
1628 0           pop @response;
1629              
1630             #
1631             # Second, parse out the status-line, return if we encountered an error
1632             #
1633 0           my $status_line = shift @response;
1634 0           my ($status, $status_code, $status_text) = split(/\s+/, $status_line, 3);
1635 0           $self->{status_code} = $status_code;
1636 0           $self->{status_text} = $status_text;
1637              
1638 0 0         if ($status eq 'ER') {
1639 0 0         if (@response > 0) {
1640 0 0         if (!defined $status_text) {
1641 0           $status_text = '';
1642             }
1643 0           $status_text = join("\n", $status_text, @response);
1644             }
1645              
1646 0           $self->{status_text} = $status_text;
1647              
1648 0           my $error = "OpenSRS Email APP error: $status_code";
1649 0 0         if (defined $status_text) {
1650 0           $error .= ", $status_text";
1651             }
1652              
1653 0 0         if ($status_code > 0) {
1654 0           return $status_code, $error;
1655             }
1656             else {
1657 0           return 1, $error;
1658             }
1659             }
1660              
1661             #
1662             # Third, if there is any response lines, parse them into an array of hashes
1663             # OpenSRS's response differs depending upon whether this is a single or multi-row response
1664             #
1665             # Single-row response:
1666             # MAILBOX="sifl" WORKGROUP="staff"
1667 0 0         if (@response == 1) {
    0          
1668 0           my $row = _parse_single_row(shift @response);
1669            
1670 0 0         if (int(keys %$row) > 0) {
1671 0           return $status_code, $row;
1672             }
1673             }
1674             # Multi-row response:
1675             # MAILBOX DOMAIN WORKGROUP
1676             # ,
1677             # "sifl" "example.net" "staff"
1678             # ,
1679             # "ollie" "example.net" "staff"
1680             elsif(@response > 1) {
1681 0           my $rows = _parse_multiple_rows(\@response);
1682 0           return $status_code, $rows;
1683             }
1684             }
1685              
1686             #
1687             # Okay this is insane, but due to the fact that key-val
1688             # delimiter is space which is also present in values, this is
1689             # the only way to parse values 100% safely.
1690             #
1691             sub _parse_single_row {
1692 0     0     my ($line) = @_;
1693 0           my $row = {};
1694            
1695 0           my $within_key = 1;
1696 0           my $within_value = 0;
1697 0           my $seen_quote = 0;
1698 0           my $key;
1699             my $value;
1700            
1701 0 0         if ($Debug > 1) {
1702 0           $Emit_Debug->("Response: $line\n");
1703             }
1704            
1705 0           foreach my $char (split(//, $line)) {
1706 0 0         if ($Debug > 2) {
1707 0           $Emit_Debug->("char: $char ");
1708             }
1709            
1710 0 0 0       if ($within_key && $char ne '=') {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
1711 0 0         if ($Debug > 2) {
1712 0           $Emit_Debug->("within_key and char ne =\n");
1713             }
1714            
1715 0 0         if ($char !~ /\s/) {
1716 0           $key .= $char;
1717             }
1718             }
1719             elsif ($within_key && $char eq '=') {
1720 0 0         if ($Debug > 2) {
1721 0           $Emit_Debug->("within_key and char eq =\n");
1722             }
1723 0           $within_key = 0;
1724             }
1725             elsif (!$within_key && !$within_value && $char eq '"') {
1726 0 0         if ($Debug > 2) {
1727 0           $Emit_Debug->("within_value and char eq \"\n");
1728             }
1729 0           $within_value = 1;
1730 0           $seen_quote = 0;
1731 0           $value = $char;
1732             }
1733             elsif ($within_value && !$seen_quote && $char eq '"') {
1734 0 0         if ($Debug > 2) {
1735 0           $Emit_Debug->("within_value and !seen_quote and char eq \"\n");
1736             }
1737 0           $seen_quote = 1;
1738 0           $value .= $char;
1739             }
1740             elsif ($within_value && $seen_quote && $char eq '"') {
1741 0 0         if ($Debug > 2) {
1742 0           $Emit_Debug->("within_value and seen_quote and char eq \"\n");
1743             }
1744 0           $seen_quote = 0;
1745 0           $value .= $char;
1746             }
1747             elsif ($within_value && $seen_quote && $char =~ /\s/) {
1748 0 0         if ($Debug > 2) {
1749 0           $Emit_Debug->("within_value and seen_quote and char matches space\n");
1750             }
1751 0           $seen_quote = 0;
1752 0           $within_value = 0;
1753 0           $within_key = 1;
1754            
1755 0           $value =~ s/^\"//;
1756 0           $value =~ s/\"$//;
1757 0           $value =~ s/\"\"/\"/g;
1758            
1759 0           $row->{$key} = $value;
1760            
1761 0           $key = undef;
1762 0           $value = undef;
1763             }
1764             elsif ($within_value && !$seen_quote) {
1765 0 0         if ($Debug > 2) {
1766 0           $Emit_Debug->("within_value and !seen_quote\n");
1767             }
1768 0           $value .= $char;
1769             }
1770             }
1771              
1772 0 0 0       if (defined $value && $value ne '' && $within_value && $seen_quote) {
      0        
      0        
1773 0           $value =~ s/^\"//;
1774 0           $value =~ s/\"$//;
1775 0           $value =~ s/\"\"/\"/g;
1776            
1777 0           $row->{$key} = $value;
1778             }
1779            
1780 0           return $row;
1781             }
1782              
1783             sub _parse_multiple_rows {
1784 0     0     my ($response) = @_;
1785              
1786 0 0         if ($Debug > 1) {
1787 0           $Emit_Debug->("Response: " . join("\n", @$response) . "\n");
1788             }
1789              
1790 0           my $rows = [];
1791 0           my $line_no = 0;
1792 0           my @keys;
1793 0           foreach my $line (@$response) {
1794 0           my $row = {};
1795 0           $line_no++;
1796 0 0         if ($line_no == 1) {
    0          
1797 0           foreach my $key (split(/\s+/, $line)) {
1798 0 0         if ($Debug > 2) {
1799 0           $Emit_Debug->("found key $key\n");
1800             }
1801 0           push @keys, $key;
1802             }
1803             }
1804             elsif ($line eq ',') {
1805 0           next;
1806             }
1807             else {
1808 0           my $within_value = 0;
1809 0           my $seen_quote = 0;
1810 0           my $column = 0;
1811 0           my $value;
1812 0           foreach my $char (split(//, $line)) {
1813 0 0         if ($Debug > 2) {
1814 0           $Emit_Debug->("char: $char ");
1815             }
1816            
1817 0 0 0       if (!$within_value && $char eq '"') {
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
1818 0 0         if ($Debug > 2) {
1819 0           $Emit_Debug->("within_value and char eq \"\n");
1820             }
1821 0           $within_value = 1;
1822 0           $seen_quote = 0;
1823 0           $value = $char;
1824             }
1825             elsif ($within_value && !$seen_quote && $char eq '"') {
1826 0 0         if ($Debug > 2) {
1827 0           $Emit_Debug->("within_value and !seen_quote and char eq \"\n");
1828             }
1829 0           $seen_quote = 1;
1830 0           $value .= $char;
1831             }
1832             elsif ($within_value && $seen_quote && $char eq '"') {
1833 0 0         if ($Debug > 2) {
1834 0           $Emit_Debug->("within_value and seen_quote and char eq \"\n");
1835             }
1836 0           $seen_quote = 0;
1837 0           $value .= $char;
1838             }
1839             elsif ($within_value && $seen_quote && $char =~ /\s/) {
1840 0 0         if ($Debug > 2) {
1841 0           $Emit_Debug->("within_value and seen_quote and char matches space\n");
1842             }
1843 0           $seen_quote = 0;
1844 0           $within_value = 0;
1845            
1846 0           $value =~ s/^\"//;
1847 0           $value =~ s/\"$//;
1848 0           $value =~ s/\"\"/\"/g;
1849              
1850 0 0         if ($Debug > 2) {
1851 0           $Emit_Debug->("adding $keys[$column]: $value\n");
1852             }
1853            
1854 0 0         if (exists $keys[$column]) {
1855 0           $row->{$keys[$column]} = $value;
1856             }
1857            
1858 0           $value = undef;
1859 0           $column++;
1860             }
1861             elsif ($within_value && !$seen_quote) {
1862 0 0         if ($Debug > 2) {
1863 0           $Emit_Debug->("within_value and !seen_quote\n");
1864             }
1865 0           $value .= $char;
1866             }
1867             }
1868              
1869 0 0 0       if (defined $value && $value ne '' && $within_value && $seen_quote) {
      0        
      0        
1870 0           $value =~ s/^\"//;
1871 0           $value =~ s/\"$//;
1872 0           $value =~ s/\"\"/\"/g;
1873            
1874 0 0         if (exists $keys[$column]) {
1875 0           $row->{$keys[$column]} = $value;
1876             }
1877             }
1878            
1879 0           push @$rows, $row;
1880             }
1881             }
1882              
1883 0           return $rows;
1884             }
1885              
1886             sub _read_buf {
1887 0     0     my ($self) = @_;
1888 0           my $buf;
1889 0           my $socket = $self->{socket};
1890 0           my $sel = new IO::Select $socket;
1891 0 0         if (!$sel->can_read($Timeout)) {
1892 0 0         if ($Debug) {
1893 0           $Emit_Debug->("_read_buf: select can_read returns false\n");
1894             }
1895            
1896 0           $@ = 'read: timeout';
1897 0 0         $! = (exists &Errno::ETIMEDOUT ? &Errno::ETIMEDOUT : 1);
1898 0           return;
1899             }
1900            
1901 0           my $bytes = sysread($socket, $buf, $Buf_len);
1902              
1903 0 0         if ($bytes == 0) {
1904 0           $@ = 'read: connection closed';
1905 0 0         $! = (exists &Errno::EINTR ? &Errno::EINTR : 1);
1906 0           return;
1907             }
1908              
1909 0           return $buf;
1910             }
1911              
1912             1;
1913              
1914             =head1 NOTES
1915              
1916             The functions get_mailbox_status and set_mailbox_status are not
1917             implemented, OpenSRS have tagged these functions as being deprecated.
1918             Use get_mailbox_suspension and set_mailbox_suspension functions
1919             instead.
1920              
1921             =head1 AUTHOR
1922              
1923             Mark Goldfinch, C<< mark.goldfinch at modicagroup.com >>
1924              
1925             =head1 BUGS
1926              
1927             The internal functions _parse_single_row and _parse_multiple_rows
1928             currently make use some handwritten logic to correctly parse the rows
1929             as returned by APP. The OpenSRS supplied documentation includes an
1930             ABNF definition for the entire protocol. The handwritten logic could
1931             likely be replaced by Parser::RecDescent (or similar) logic. A hurdle
1932             to this is the left-resolving the supplied ABNF uses,
1933             Parser::RecDescent's design inhibits the use of left-resolving
1934             parsing. Patches are welcome to address this. My testing suggests
1935             the current handwritten logic is robust and functional however.
1936              
1937             Other than presence of required arguments, no validation of supplied
1938             arguments is currently performed.
1939              
1940             Otherwise please report any bugs or feature requests to
1941             C, or through the web
1942             interface at L.
1943              
1944             =head1 SEE ALSO
1945              
1946             This implementation is based upon documentation from
1947             L dated December
1948             14, 2010. Please read the pdf for greater detail about the protocol,
1949             required and returned values of each function.
1950              
1951             =head1 SUPPORT
1952              
1953             You can find documentation for this module with the perldoc command.
1954              
1955             perldoc Net::OpenSRS::Email_APP
1956              
1957              
1958             You can also look for information at:
1959              
1960             =over 4
1961              
1962             =item * RT: CPAN's request tracker
1963              
1964             L
1965              
1966             =item * AnnoCPAN: Annotated CPAN documentation
1967              
1968             L
1969              
1970             =item * CPAN Ratings
1971              
1972             L
1973              
1974             =item * Search CPAN
1975              
1976             L
1977              
1978             =item * Github repository
1979              
1980             L
1981              
1982             =back
1983              
1984             =head1 ACKNOWLEDGEMENTS
1985              
1986             Thank you to Modica Group L for funding
1987             the development of this module.
1988              
1989             =head1 LICENSE AND COPYRIGHT
1990              
1991             Copyright 2011 Mark Goldfinch.
1992              
1993             This program is free software; you can redistribute it and/or modify it
1994             under the terms of either: the GNU General Public License as published
1995             by the Free Software Foundation; or the Artistic License.
1996              
1997             See http://dev.perl.org/licenses/ for more information.
1998              
1999             =cut