File Coverage

blib/lib/Net/EPP/Simple.pm
Criterion Covered Total %
statement 39 796 4.9
branch 0 334 0.0
condition 0 165 0.0
subroutine 13 85 15.2
pod 5 40 12.5
total 57 1420 4.0


line stmt bran cond sub pod time code
1             package Net::EPP::Simple;
2 1     1   10 use Carp;
  1         2  
  1         475  
3 1     1   6 use Config;
  1         2  
  1         60  
4 1     1   535 use Digest::SHA qw(sha1_hex);
  1         2935  
  1         93  
5 1     1   8 use List::Util qw(any);
  1         1  
  1         117  
6 1     1   7 use Net::EPP;
  1         11  
  1         21  
7 1     1   4 use Net::EPP::Frame;
  1         4  
  1         28  
8 1     1   5 use Net::EPP::ResponseCodes;
  1         2  
  1         129  
9 1     1   606 use Time::HiRes qw(time);
  1         1440  
  1         5  
10 1     1   198 use base qw(Net::EPP::Client);
  1         2  
  1         108  
11             use constant {
12 1         84 EPP_XMLNS => 'urn:ietf:params:xml:ns:epp-1.0',
13             LOGINSEC_XMLNS => 'urn:ietf:params:xml:ns:epp:loginSec-1.0',
14 1     1   6 };
  1         2  
15 1     1   7 use vars qw($Error $Code $Message @Log);
  1         1  
  1         62  
16 1     1   7 use strict;
  1         2  
  1         19  
17 1     1   4 use warnings;
  1         2  
  1         10039  
18              
19             our $Error = '';
20             our $Code = OK;
21             our $Message = '';
22             our @Log = ();
23              
24             =pod
25              
26             =head1 Name
27              
28             Net::EPP::Simple - a simple EPP client interface for the most common jobs.
29              
30             =head1 Synopsis
31              
32             #!/usr/bin/perl
33             use Net::EPP::Simple;
34             use strict;
35              
36             my $epp = Net::EPP::Simple->new(
37             host => 'epp.nic.tld',
38             user => 'my-id',
39             pass => 'my-password',
40             );
41              
42             my $domain = 'example.tld';
43              
44             if ($epp->check_domain($domain) == 1) {
45             print "Domain is available\n" ;
46              
47             } else {
48             my $info = $epp->domain_info($domain);
49             printf("Domain was registered on %s by %s\n", $info->{crDate}, $info->{crID});
50              
51             }
52              
53             =head1 Description
54              
55             EPP is the Extensible Provisioning Protocol. EPP (defined in RFC 4930) is an
56             application layer client-server protocol for the provisioning and management of
57             objects stored in a shared central repository. Specified in XML, the protocol
58             defines generic object management operations and an extensible framework that
59             maps protocol operations to objects. As of writing, its only well-developed
60             application is the provisioning of Internet domain names, hosts, and related
61             contact details.
62              
63             This module provides a high level interface to the EPP protocol. It hides all
64             the boilerplate of connecting, logging in, building request frames and parsing
65             response frames behind a simple, Perlish interface.
66              
67             It is based on the C module and uses C
68             to build request frames.
69              
70             =head1 Constructor
71              
72             The constructor for C has the same general form as the
73             one for C, but with the following exceptions:
74              
75             =over
76              
77             =item * Unless otherwise set, C defaults to 700
78              
79             =item * Unless the C parameter is set, SSL is always on
80              
81             =item * You can use the C and C parameters to supply authentication
82             information.
83              
84             =item * You can use the C parameter to specify a new password.
85              
86             =item * The C parameter can be used to force the use of the
87             Login Security Extension (see RFC8807). C will automatically
88             use this extension if the server supports it, but clients may wish to force
89             this behaviour to prevent downgrade attacks.
90              
91             =item * The C parameter can be used to specify the value of the
92             Capp> element in the Login Security extension (if used). Unless
93             specified, the name and current version of C will be used.
94              
95             =item * The C parameter controls how long the client waits for a
96             response from the server before returning an error.
97              
98             =item * if C is set, C will output verbose debugging
99             information on C, including all frames sent to and received from the
100             server.
101              
102             =item * C can be used to disable automatic reconnection (it is
103             enabled by default). Before sending a frame to the server, C
104             will send a ChelloE> to check that the connection is up, if not, it
105             will try to reconnect, aborting after the Ith time, where I is the value
106             of C (the default is 3).
107              
108             =item * C can be used to disable automatic logins. If you set it
109             to C<0>, you can manually log in using the C<$epp-E_login()> method.
110              
111             =item * C is a reference to an array of the EPP object schema
112             URIs that the client requires.
113              
114             =item * C is a flag saying the client only requires the
115             standard EPP C, C, and C schemas.
116              
117             =item * If neither C nor C is specified then the
118             client will echo the server's object schema list.
119              
120             =item * C is a reference to an array of the EPP extension
121             schema URIs that the client requires.
122              
123             =item * C is a flag saying the client only requires the
124             standard EPP C DNSSEC extension schema.
125              
126             =item * If neither C nor C is specified then the
127             client will echo the server's extension schema list.
128              
129             =item * The C parameter can be used to specify the language. The
130             default is "C".
131              
132             =back
133              
134             The constructor will establish a connection to the server and retrieve the
135             greeting (which is available via C<$epp-E{greeting}>) and then send a
136             CloginE> request.
137              
138             If the login fails, the constructor will return C and set
139             C<$Net::EPP::Simple::Error> and C<$Net::EPP::Simple::Code>.
140              
141             =head2 Client and Server SSL options
142              
143             RFC 5730 requires that all EPP instances must be protected using "mutual,
144             strong client-server authentication". In practice, this means that both
145             client and server must present an SSL certificate, and that they must
146             both verify the certificate of their peer.
147              
148             =head3 Server Certificate Verification
149              
150             C will verify the certificate presented by a server if
151             the C, and either C or C are passed to the
152             constructor:
153              
154             my $epp = Net::EPP::Simple->new(
155             host => 'epp.nic.tld',
156             user => 'my-id',
157             pass => 'my-password',
158             verify => 1,
159             ca_file => '/etc/pki/tls/certs/ca-bundle.crt',
160             ca_path => '/etc/pki/tls/certs',
161             );
162              
163             C will fail to connect to the server if the
164             certificate is not valid.
165              
166             You can disable SSL certificate verification by omitting the C
167             argument or setting it to C. This is strongly discouraged,
168             particularly in production environments.
169              
170             =head3 SSL Cipher Selection
171              
172             You can restrict the ciphers that you will use to connect to the server
173             by passing a C parameter to the constructor. This is a colon-
174             separated list of cipher names and aliases. See L
175             for further details. As an example, the following cipher list is
176             suggested for clients who wish to ensure high-security connections to
177             servers:
178              
179             HIGH:!ADH:!MEDIUM:!LOW:!SSLv2:!EXP
180              
181             =head3 Client Certificates
182              
183             If you are connecting to an EPP server which requires a client
184             certificate, you can configure C to use one as
185             follows:
186              
187             my $epp = Net::EPP::Simple->new(
188             host => 'epp.nic.tld',
189             user => 'my-id',
190             pass => 'my-password',
191             key => '/path/to/my.key',
192             cert => '/path/to/my.crt',
193             passphrase => 'foobar123',
194             );
195              
196             C is the filename of the private key, C is the filename of
197             the certificate. If the private key is encrypted, the C
198             parameter will be used to decrypt it.
199              
200             =head2 Configuration File
201              
202             C supports the use of a simple configuration file. To
203             use this feature, you need to install the L module.
204              
205             When starting up, C will look for
206             C<$HOME/.net-epp-simple-rc>. This file is an ini-style configuration
207             file.
208              
209             =head3 Default Options
210              
211             You can specify default options for all EPP servers using the C<[default]>
212             section:
213              
214             [default]
215             default=epp.nic.tld
216             debug=1
217              
218             =head3 Server Specific Options
219              
220             You can specify options for for specific EPP servers by giving each EPP server
221             its own section:
222              
223             [epp.nic.tld]
224             user=abc123
225             pass=foo2bar
226             port=777
227             ssl=0
228              
229             This means that when you write a script that uses C, you can
230             do the following:
231              
232             # config file has a default server:
233             my $epp = Net::EPP::Simple->new;
234              
235             # config file has connection options for this EPP server:
236             my $epp = Net::EPP:Simple->new('host' => 'epp.nic.tld');
237              
238             Any parameters provided to the constructor will override those in the config
239             file.
240              
241             =cut
242              
243             sub new {
244 0     0 1   my ($package, %params) = @_;
245 0           $params{dom} = 1;
246              
247 0 0         my $load_config = (defined($params{load_config}) ? $params{load_config} : 1);
248 0 0         $package->_load_config(\%params) if ($load_config);
249              
250 0 0 0       $params{port} = (defined($params{port}) && int($params{port}) > 0 ? $params{port} : 700);
251 0 0         $params{ssl} = ($params{no_ssl} ? undef : 1);
252              
253 0           my $self = $package->SUPER::new(%params);
254              
255 0           $self->{user} = $params{user};
256 0           $self->{pass} = $params{pass};
257 0           $self->{newPW} = $params{newPW};
258 0 0         $self->{debug} = (defined($params{debug}) ? int($params{debug}) : undef);
259 0 0 0       $self->{timeout} = (defined($params{timeout}) && int($params{timeout}) > 0 ? $params{timeout} : 5);
260 0 0         $self->{reconnect} = (defined($params{reconnect}) ? int($params{reconnect}) : 3);
261 0           $self->{'connected'} = undef;
262 0           $self->{'authenticated'} = undef;
263 0 0         $self->{connect} = (exists($params{connect}) ? $params{connect} : 1);
264 0 0         $self->{login} = (exists($params{login}) ? $params{login} : 1);
265 0           $self->{key} = $params{key};
266 0           $self->{cert} = $params{cert};
267 0           $self->{passphrase} = $params{passphrase};
268 0           $self->{verify} = $params{verify};
269 0           $self->{ca_file} = $params{ca_file};
270 0           $self->{ca_path} = $params{ca_path};
271 0           $self->{ciphers} = $params{ciphers};
272 0           $self->{objects} = $params{objects};
273 0           $self->{stdobj} = $params{stdobj};
274 0           $self->{extensions} = $params{extensions};
275 0           $self->{stdext} = $params{stdext};
276 0   0       $self->{lang} = $params{lang} || 'en';
277 0           $self->{login_security} = $params{login_security};
278 0           $self->{appname} = $params{appname};
279              
280 0           bless($self, $package);
281              
282 0 0         if ($self->{connect}) {
283 0 0         return ($self->_connect($self->{login}) ? $self : undef);
284              
285             } else {
286 0           return $self;
287              
288             }
289             }
290              
291             sub _load_config {
292 0     0     my ($package, $params_ref) = @_;
293              
294 0           eval 'use Config::Simple';
295 0 0         if (!$@) {
296             # we have Config::Simple, so let's try to parse the RC file:
297 0           my $rcfile = $ENV{'HOME'}.'/.net-epp-simple-rc';
298 0 0         if (-e $rcfile) {
299 0           my $config = Config::Simple->new($rcfile);
300              
301             # if no host was defined in the constructor, use the default (if specified):
302 0 0 0       if (!defined($params_ref->{'host'}) && $config->param('default.default')) {
303 0           $params_ref->{'host'} = $config->param('default.default');
304             }
305              
306             # if no debug level was defined in the constructor, use the default (if specified):
307 0 0 0       if (!defined($params_ref->{'debug'}) && $config->param('default.debug')) {
308 0           $params_ref->{'debug'} = $config->param('default.debug');
309             }
310              
311             # grep through the file's values for settings for the selected host:
312 0           my %vars = $config->vars;
313 0           foreach my $key (grep { /^$params_ref->{'host'}\./ } keys(%vars)) {
  0            
314 0           my $value = $vars{$key};
315 0           $key =~ s/^$params_ref->{'host'}\.//;
316 0 0         $params_ref->{$key} = $value unless (defined($params_ref->{$key}));
317             }
318             }
319             }
320             }
321              
322             sub _connect {
323 0     0     my ($self, $login) = @_;
324              
325 0           my %params;
326              
327 0 0 0       $params{SSL_cipher_list} = $self->{ciphers} if (defined($self->{ssl}) && defined($self->{ciphers}));
328              
329 0 0 0       if (defined($self->{key}) && defined($self->{cert}) && defined($self->{ssl})) {
      0        
330 0           $self->debug('configuring client certificate parameters');
331 0           $params{SSL_key_file} = $self->{key};
332 0           $params{SSL_cert_file} = $self->{cert};
333 0     0     $params{SSL_passwd_cb} = sub { $self->{passphrase} };
  0            
334             }
335              
336 0 0 0       if (defined($self->{ssl}) && defined($self->{verify})) {
    0          
337 0           $self->debug('configuring server verification');
338 0           $params{SSL_verify_mode} = 1;
339 0           $params{SSL_ca_file} = $self->{ca_file};
340 0           $params{SSL_ca_path} = $self->{ca_path};
341              
342             } elsif (defined($self->{ssl})) {
343 0           $params{SSL_verify_mode} = 0;
344              
345             }
346              
347 0           $self->debug(sprintf('Attempting to connect to %s:%d', $self->{host}, $self->{port}));
348 0           eval {
349 0           $params{no_greeting} = 1;
350 0           $self->connect(%params);
351             };
352 0 0         if ($@ ne '') {
353 0           chomp($@);
354 0           $@ =~ s/ at .+ line .+$//;
355 0           $self->debug($@);
356 0           $Code = COMMAND_FAILED;
357 0           $Error = $Message = "Error connecting: ".$@;
358 0           return undef;
359              
360             } else {
361 0           $self->{'connected'} = 1;
362              
363 0           $self->debug('Connected OK, retrieving greeting frame');
364 0           $self->{greeting} = $self->get_frame;
365 0 0         if (ref($self->{greeting}) ne 'Net::EPP::Frame::Response') {
366 0           $Code = COMMAND_FAILED;
367 0           $Error = $Message = "Error retrieving greeting: ".$@;
368 0           return undef;
369              
370             } else {
371 0           $self->debug('greeting frame retrieved OK');
372              
373             }
374             }
375              
376 0           map { $self->debug('S: '.$_) } split(/\n/, $self->{greeting}->toString(1));
  0            
377              
378 0 0         if ($login) {
379 0           $self->debug('attempting login');
380 0           return $self->_login;
381              
382             } else {
383 0           return 1;
384              
385             }
386             }
387              
388             sub _login {
389 0     0     my $self = shift;
390              
391 0           $self->debug(sprintf("Attempting to login as client ID '%s'", $self->{user}));
392 0           my $response = $self->request($self->_prepare_login_frame());
393              
394 0 0         if (!$response) {
395 0           $Error = $Message = "Error getting response to login request: ".$Error;
396 0           return undef;
397              
398             } else {
399 0           $Code = $self->_get_response_code($response);
400 0           $Message = $self->_get_message($response);
401              
402 0           $self->debug(sprintf('%04d: %s', $Code, $Message));
403              
404 0 0         if ($Code > 1999) {
405 0           $Error = "Error logging in (response code $Code, message $Message)";
406 0           return undef;
407              
408             } else {
409 0           $self->{'authenticated'} = 1;
410 0           return 1;
411              
412             }
413             }
414             }
415              
416             sub _get_option_uri_list {
417 0     0     my $self = shift;
418 0           my $tag = shift;
419 0           my $list = [];
420 0           my $elems = $self->{greeting}->getElementsByTagNameNS(EPP_XMLNS, $tag);
421 0           while (my $elem = $elems->shift) {
422 0           push @$list, $elem->firstChild->data;
423             }
424 0           return $list;
425             }
426              
427             sub _prepare_login_frame {
428 0     0     my $self = shift;
429              
430 0           $self->debug('preparing login frame');
431 0           my $login = Net::EPP::Frame::Command::Login->new;
432              
433 0           my @extensions;
434 0 0         if ($self->{'stdext'}) {
    0          
435 0           push(@extensions, (Net::EPP::Frame::ObjectSpec->spec('secDNS'))[1]);
436              
437             } elsif ($self->{'extensions'}) {
438 0           @extensions = @{$self->{'extensions'}};
  0            
439              
440             } else {
441 0           @extensions = @{$self->_get_option_uri_list('extURI')};
  0            
442              
443             }
444              
445 0           $login->clID->appendText($self->{'user'});
446              
447 0 0 0 0     if ($self->{'login_security'} || any { LOGINSEC_XMLNS eq $_ } @extensions) {
  0            
448 0 0   0     push(@extensions, LOGINSEC_XMLNS) unless (any { LOGINSEC_XMLNS eq $_ } @extensions);
  0            
449              
450 0           $login->pw->appendText('[LOGIN-SECURITY]');
451              
452 0           my $loginSec = $login->createElementNS(LOGINSEC_XMLNS, 'loginSec');
453              
454 0           my $userAgent = $login->createElement('userAgent');
455 0           $loginSec->appendChild($userAgent);
456              
457 0           my $app = $login->createElement('app');
458 0   0       $app->appendText($self->{'appname'} || sprintf('%s %s', __PACKAGE__, $Net::EPP::VERSION));
459 0           $userAgent->appendChild($app);
460              
461 0           my $tech = $login->createElement('tech');
462 0           $tech->appendText(sprintf('Perl %s', $Config{'version'}));
463 0           $userAgent->appendChild($tech);
464              
465 0           my $os = $login->createElement('os');
466 0           $os->appendText(sprintf('%s %s', ucfirst($Config{'osname'}), $Config{'osvers'}));
467 0           $userAgent->appendChild($os);
468              
469 0           my $pw = $login->createElement('pw');
470 0           $pw->appendText($self->{'pass'});
471 0           $loginSec->appendChild($pw);
472              
473 0 0         if ($self->{'newPW'}) {
474 0           my $newPW = $login->createElement('newPW');
475 0           $newPW->appendText('[LOGIN-SECURITY]');
476 0           $login->getNode('login')->insertAfter($newPW, $login->pw);
477              
478 0           $newPW = $login->createElement('newPW');
479 0           $newPW->appendText($self->{'newPW'});
480 0           $loginSec->appendChild($newPW);
481             }
482              
483 0           my $extension = $login->createElement('extension');
484 0           $extension->appendChild($loginSec);
485              
486 0           $login->getCommandNode()->parentNode()->insertAfter($extension, $login->getCommandNode());
487              
488             } else {
489 0           $login->pw->appendText($self->{pass});
490              
491 0 0         if ($self->{newPW}) {
492 0           my $newPW = $login->createElement('newPW');
493 0           $newPW->appendText($self->{newPW});
494 0           $login->getNode('login')->insertAfter($newPW, $login->pw);
495             }
496             }
497              
498 0           $login->version->appendText($self->{greeting}->getElementsByTagNameNS(EPP_XMLNS, 'version')->shift->firstChild->data);
499 0           $login->lang->appendText($self->{lang});
500              
501 0           my $objects = $self->{objects};
502 0           $objects = [map { (Net::EPP::Frame::ObjectSpec->spec($_))[1] }
503 0 0         qw(contact domain host)] if $self->{stdobj};
504 0 0         $objects = _get_option_uri_list($self,'objURI') if not $objects;
505 0           $login->svcs->appendTextChild('objURI', $_) for @$objects;
506              
507 0 0         if (scalar(@extensions) > 0) {
508 0           my $svcext = $login->createElement('svcExtension');
509 0           $login->svcs->appendChild($svcext);
510 0           $svcext->appendTextChild('extURI', $_) for @extensions;
511              
512             }
513              
514 0           return $login;
515             }
516              
517             =pod
518              
519             =head1 Availability Checks
520              
521             You can do a simple CcheckE> request for an object like so:
522              
523             my $result = $epp->check_domain($domain);
524              
525             my $result = $epp->check_host($host);
526              
527             my $result = $epp->check_contact($contact);
528              
529             Each of these methods has the same profile. They will return one of the
530             following:
531              
532             =over
533              
534             =item * C in the case of an error (check C<$Net::EPP::Simple::Error> and C<$Net::EPP::Simple::Code>).
535              
536             =item * C<0> if the object is already provisioned.
537              
538             =item * C<1> if the object is available.
539              
540             =back
541              
542             =cut
543              
544             sub check_domain {
545 0     0 0   my ($self, $domain) = @_;
546 0           return $self->_check('domain', $domain);
547             }
548              
549             sub check_host {
550 0     0 0   my ($self, $host) = @_;
551 0           return $self->_check('host', $host);
552             }
553              
554             sub check_contact {
555 0     0 0   my ($self, $contact) = @_;
556 0           return $self->_check('contact', $contact);
557             }
558              
559             sub _check {
560 0     0     my ($self, $type, $identifier) = @_;
561 0           my $frame;
562 0 0         if ($type eq 'domain') {
    0          
    0          
563 0           $frame = Net::EPP::Frame::Command::Check::Domain->new;
564 0           $frame->addDomain($identifier);
565              
566             } elsif ($type eq 'contact') {
567 0           $frame = Net::EPP::Frame::Command::Check::Contact->new;
568 0           $frame->addContact($identifier);
569              
570             } elsif ($type eq 'host') {
571 0           $frame = Net::EPP::Frame::Command::Check::Host->new;
572 0           $frame->addHost($identifier);
573              
574             } else {
575 0           $Error = "Unknown object type '$type'";
576 0           return undef;
577             }
578              
579 0           my $response = $self->_request($frame);
580              
581 0 0         if (!$response) {
582 0           return undef;
583              
584             } else {
585 0           $Code = $self->_get_response_code($response);
586 0           $Message = $self->_get_message($response);
587              
588 0 0         if ($Code > 1999) {
589 0           $Error = $self->_get_error_message($response);
590 0           return undef;
591              
592             } else {
593 0           my $xmlns = (Net::EPP::Frame::ObjectSpec->spec($type))[1];
594 0           my $key;
595 0 0 0       if ($type eq 'domain' || $type eq 'host') {
    0          
596 0           $key = 'name';
597              
598             } elsif ($type eq 'contact') {
599 0           $key = 'id';
600              
601             }
602 0           return $response->getNode($xmlns, $key)->getAttribute('avail');
603              
604             }
605             }
606             }
607              
608             =pod
609              
610             =head1 Retrieving Object Information
611              
612             =head2 Domain Objects
613              
614             my $info = $epp->domain_info($domain, $authInfo, $follow);
615              
616             This method constructs an CinfoE> frame and sends
617             it to the server, then parses the response into a simple hash ref. If
618             there is an error, this method will return C, and you can then
619             check C<$Net::EPP::Simple::Error> and C<$Net::EPP::Simple::Code>.
620              
621             If C<$authInfo> is defined, it will be sent to the server as per RFC
622             5731, Section 3.1.2.
623              
624             If the C<$follow> parameter is true, then C will also
625             retrieve the relevant host and contact details for a domain: instead of
626             returning an object name or ID for the domain's registrant, contact
627             associations, DNS servers or subordinate hosts, the values will be
628             replaced with the return value from the appropriate C or
629             C command (unless there was an error, in which case the
630             original object ID will be used instead).
631              
632             =cut
633              
634             sub domain_info {
635 0     0 0   my ($self, $domain, $authInfo, $follow, $hosts) = @_;
636 0   0       $hosts = $hosts || 'all';
637              
638 0           my $result = $self->_info('domain', $domain, $authInfo, $hosts);
639 0 0 0       return $result if (ref($result) ne 'HASH' || !$follow);
640              
641 0 0 0       if (defined($result->{'ns'}) && ref($result->{'ns'}) eq 'ARRAY') {
642 0           for (my $i = 0 ; $i < scalar(@{$result->{'ns'}}) ; $i++) {
  0            
643 0           my $info = $self->host_info($result->{'ns'}->[$i]);
644 0 0         $result->{'ns'}->[$i] = $info if (ref($info) eq 'HASH');
645             }
646             }
647              
648 0 0 0       if (defined($result->{'hosts'}) && ref($result->{'hosts'}) eq 'ARRAY') {
649 0           for (my $i = 0 ; $i < scalar(@{$result->{'hosts'}}) ; $i++) {
  0            
650 0           my $info = $self->host_info($result->{'hosts'}->[$i]);
651 0 0         $result->{'hosts'}->[$i] = $info if (ref($info) eq 'HASH');
652             }
653             }
654              
655 0           my $info = $self->contact_info($result->{'registrant'});
656 0 0         $result->{'registrant'} = $info if (ref($info) eq 'HASH');
657              
658 0           foreach my $type (keys(%{$result->{'contacts'}})) {
  0            
659 0           my $info = $self->contact_info($result->{'contacts'}->{$type});
660 0 0         $result->{'contacts'}->{$type} = $info if (ref($info) eq 'HASH');
661             }
662              
663 0           return $result;
664             }
665              
666             =pod
667              
668             =head2 Host Objects
669              
670             my $info = $epp->host_info($host);
671              
672             This method constructs an CinfoE> frame and sends
673             it to the server, then parses the response into a simple hash ref. If
674             there is an error, this method will return C, and you can then
675             check C<$Net::EPP::Simple::Error> and C<$Net::EPP::Simple::Code>.
676              
677             =cut
678              
679             sub host_info {
680 0     0 0   my ($self, $host) = @_;
681 0           return $self->_info('host', $host);
682             }
683              
684             =pod
685              
686             =head2 Contact Objects
687              
688             my $info = $epp->contact_info($contact, $authInfo, $roid);
689              
690             This method constructs an CinfoE> frame and sends
691             it to the server, then parses the response into a simple hash ref. If
692             there is an error, this method will return C, and you can then
693             check C<$Net::EPP::Simple::Error> and C<$Net::EPP::Simple::Code>.
694              
695             If C<$authInfo> is defined, it will be sent to the server as per RFC
696             RFC 5733, Section 3.1.2.
697              
698             If the C<$roid> parameter to C is set, then the C
699             attribute will be set on the CauthInfoE> element.
700              
701             =cut
702              
703             sub contact_info {
704 0     0 0   my ($self, $contact, $authInfo, $roid) = @_;
705 0           return $self->_info('contact', $contact, $authInfo, $roid);
706             }
707              
708             sub _info {
709             # $opt is the "hosts" attribute value for domains or the "roid"
710             # attribute for contacts
711 0     0     my ($self, $type, $identifier, $authInfo, $opt) = @_;
712 0           my $frame;
713 0 0         if ($type eq 'domain') {
    0          
    0          
714 0           $frame = Net::EPP::Frame::Command::Info::Domain->new;
715 0   0       $frame->setDomain($identifier, $opt || 'all');
716              
717             } elsif ($type eq 'contact') {
718 0           $frame = Net::EPP::Frame::Command::Info::Contact->new;
719 0           $frame->setContact($identifier);
720              
721             } elsif ($type eq 'host') {
722 0           $frame = Net::EPP::Frame::Command::Info::Host->new;
723 0           $frame->setHost($identifier);
724              
725             } else {
726 0           $Error = "Unknown object type '$type'";
727 0           return undef;
728              
729             }
730              
731 0 0 0       if (defined($authInfo) && $authInfo ne '') {
732 0           $self->debug('adding authInfo element to request frame');
733 0           my $el = $frame->createElement((Net::EPP::Frame::ObjectSpec->spec($type))[0].':authInfo');
734 0           my $pw = $frame->createElement((Net::EPP::Frame::ObjectSpec->spec($type))[0].':pw');
735 0           $pw->appendChild($frame->createTextNode($authInfo));
736 0 0 0       $pw->setAttribute('roid', $opt) if ($type eq 'contact' && $opt);
737 0           $el->appendChild($pw);
738 0           $frame->getNode((Net::EPP::Frame::ObjectSpec->spec($type))[1], 'info')->appendChild($el);
739             }
740              
741 0           my $response = $self->_request($frame);
742              
743 0 0         if (!$response) {
744 0           return undef;
745              
746             } else {
747 0           $Code = $self->_get_response_code($response);
748 0           $Message = $self->_get_message($response);
749              
750 0 0         if ($Code > 1999) {
751 0           $Error = $self->_get_error_message($response);
752 0           return undef;
753              
754             } else {
755 0           return $self->parse_object_info($type, $response);
756             }
757             }
758             }
759              
760             # An easy-to-subclass method for parsing object info
761             sub parse_object_info {
762 0     0 0   my ($self, $type, $response) = @_;
763              
764 0           my $infData = $response->getNode((Net::EPP::Frame::ObjectSpec->spec($type))[1], 'infData');
765              
766 0 0         if ($type eq 'domain') {
    0          
    0          
767             # secDNS extension only applies to domain objects
768 0           my $secinfo = $response->getNode((Net::EPP::Frame::ObjectSpec->spec('secDNS'))[1], 'infData');
769 0           return $self->_domain_infData_to_hash($infData, $secinfo);
770              
771             } elsif ($type eq 'contact') {
772 0           return $self->_contact_infData_to_hash($infData);
773              
774             } elsif ($type eq 'host') {
775 0           return $self->_host_infData_to_hash($infData);
776              
777             } else {
778 0           $Error = "Unknown object type '$type'";
779 0           return undef;
780              
781             }
782             }
783              
784             sub _get_common_properties_from_infData {
785 0     0     my ($self, $infData, @extra) = @_;
786 0           my $hash = {};
787              
788 0           my @default = qw(roid clID crID crDate upID upDate trDate);
789              
790 0           foreach my $name (@default, @extra) {
791 0           my $els = $infData->getElementsByLocalName($name);
792 0 0         $hash->{$name} = $els->shift->textContent if ($els->size > 0);
793             }
794              
795 0           my $codes = $infData->getElementsByLocalName('status');
796 0           while (my $code = $codes->shift) {
797 0           push(@{$hash->{status}}, $code->getAttribute('s'));
  0            
798             }
799              
800 0           return $hash;
801             }
802              
803             =pod
804              
805             =head2 Domain Information
806              
807             The hash ref returned by C will usually look something
808             like this:
809              
810             $info = {
811             'contacts' => {
812             'admin' => 'contact-id'
813             'tech' => 'contact-id'
814             'billing' => 'contact-id'
815             },
816             'registrant' => 'contact-id',
817             'clID' => 'registrar-id',
818             'roid' => 'tld-12345',
819             'status' => [
820             'ok'
821             ],
822             'authInfo' => 'abc-12345',
823             'name' => 'example.tld',
824             'trDate' => '2011-01-18T11:08:03.0Z',
825             'ns' => [
826             'ns0.example.com',
827             'ns1.example.com',
828             ],
829             'crDate' => '2011-02-16T12:06:31.0Z',
830             'exDate' => '2011-02-16T12:06:31.0Z',
831             'crID' => 'registrar-id',
832             'upDate' => '2011-08-29T04:02:12.0Z',
833             hosts => [
834             'ns0.example.tld',
835             'ns1.example.tld',
836             ],
837             };
838              
839             Members of the C hash ref may be strings or, if there are
840             multiple associations of the same type, an anonymous array of strings.
841             If the server uses the Host Attribute model instead of the Host Object
842             model, then the C member will look like this:
843              
844             $info->{ns} = [
845             {
846             name => 'ns0.example.com',
847             addrs => [
848             version => 'v4',
849             addr => '10.0.0.1',
850             ],
851             },
852             {
853             name => 'ns1.example.com',
854             addrs => [
855             version => 'v4',
856             addr => '10.0.0.2',
857             ],
858             },
859             ];
860              
861             Note that there may be multiple members in the C section and that
862             the C attribute is optional.
863              
864             =cut
865              
866             sub _domain_infData_to_hash {
867 0     0     my ($self, $infData, $secinfo) = @_;
868              
869 0           my $hash = $self->_get_common_properties_from_infData($infData, 'registrant', 'name', 'exDate');
870              
871 0           my $contacts = $infData->getElementsByLocalName('contact');
872 0           while (my $contact = $contacts->shift) {
873 0           my $type = $contact->getAttribute('type');
874 0           my $id = $contact->textContent;
875              
876 0 0         if (ref($hash->{contacts}->{$type}) eq 'STRING') {
    0          
877 0           $hash->{contacts}->{$type} = [ $hash->{contacts}->{$type}, $id ];
878              
879             } elsif (ref($hash->{contacts}->{$type}) eq 'ARRAY') {
880 0           push(@{$hash->{contacts}->{$type}}, $id);
  0            
881              
882             } else {
883 0           $hash->{contacts}->{$type} = $id;
884              
885             }
886              
887             }
888              
889 0           my $ns = $infData->getElementsByLocalName('ns');
890 0 0         if ($ns->size == 1) {
891 0           my $el = $ns->shift;
892 0           my $hostObjs = $el->getElementsByLocalName('hostObj');
893 0           while (my $hostObj = $hostObjs->shift) {
894 0           push(@{$hash->{ns}}, $hostObj->textContent);
  0            
895             }
896              
897 0           my $hostAttrs = $el->getElementsByLocalName('hostAttr');
898 0           while (my $hostAttr = $hostAttrs->shift) {
899 0           my $host = {};
900 0           $host->{name} = $hostAttr->getElementsByLocalName('hostName')->shift->textContent;
901 0           my $addrs = $hostAttr->getElementsByLocalName('hostAddr');
902 0           while (my $addr = $addrs->shift) {
903 0           push(@{$host->{addrs}}, { version => $addr->getAttribute('ip'), addr => $addr->textContent });
  0            
904             }
905 0           push(@{$hash->{ns}}, $host);
  0            
906             }
907             }
908              
909 0           my $hosts = $infData->getElementsByLocalName('host');
910 0           while (my $host = $hosts->shift) {
911 0           push(@{$hash->{hosts}}, $host->textContent);
  0            
912             }
913              
914 0           my $auths = $infData->getElementsByLocalName('authInfo');
915 0 0         if ($auths->size == 1) {
916 0           my $authInfo = $auths->shift;
917 0           my $pw = $authInfo->getElementsByLocalName('pw');
918 0 0         $hash->{authInfo} = $pw->shift->textContent if ($pw->size == 1);
919             }
920              
921 0 0         if (defined $secinfo) {
922 0 0         if (my $maxSigLife = $secinfo->getElementsByLocalName('maxSigLife')) {
923 0           $hash->{maxSigLife} = $maxSigLife->shift->textContent;
924             }
925 0           my $dslist = $secinfo->getElementsByTagName('secDNS:dsData');
926 0           while (my $ds = $dslist->shift) {
927 0           my @ds = map { $ds->getElementsByLocalName($_)->string_value() }
  0            
928             qw(keyTag alg digestType digest);
929 0           push @{ $hash->{DS} }, "@ds";
  0            
930             }
931 0           my $keylist = $secinfo->getElementsByLocalName('keyData');
932 0           while (my $key = $keylist->shift) {
933 0           my @key = map { $key->getElementsByLocalName($_)->string_value() }
  0            
934             qw(flags protocol alg pubKey);
935 0           push @{ $hash->{DNSKEY} }, "@key";
  0            
936             }
937             }
938              
939 0           return $hash;
940             }
941              
942              
943             =pod
944              
945             =head2 Host Information
946              
947             The hash ref returned by C will usually look something like
948             this:
949              
950             $info = {
951             'crDate' => '2011-09-17T15:38:56.0Z',
952             'clID' => 'registrar-id',
953             'crID' => 'registrar-id',
954             'roid' => 'tld-12345',
955             'status' => [
956             'linked',
957             'serverDeleteProhibited',
958             ],
959             'name' => 'ns0.example.tld',
960             'addrs' => [
961             {
962             'version' => 'v4',
963             'addr' => '10.0.0.1'
964             }
965             ]
966             };
967              
968             Note that hosts may have multiple addresses, and that C is
969             optional.
970              
971             =cut
972              
973             sub _host_infData_to_hash {
974 0     0     my ($self, $infData) = @_;
975              
976 0           my $hash = $self->_get_common_properties_from_infData($infData, 'name');
977              
978 0           my $addrs = $infData->getElementsByLocalName('addr');
979 0           while (my $addr = $addrs->shift) {
980 0           push(@{$hash->{addrs}}, { version => $addr->getAttribute('ip'), addr => $addr->textContent });
  0            
981             }
982              
983 0           return $hash;
984             }
985              
986             =pod
987              
988             =head2 Contact Information
989              
990             The hash ref returned by C will usually look something
991             like this:
992              
993             $VAR1 = {
994             'id' => 'contact-id',
995             'postalInfo' => {
996             'int' => {
997             'name' => 'John Doe',
998             'org' => 'Example Inc.',
999             'addr' => {
1000             'street' => [
1001             '123 Example Dr.'
1002             'Suite 100'
1003             ],
1004             'city' => 'Dulles',
1005             'sp' => 'VA',
1006             'pc' => '20116-6503'
1007             'cc' => 'US',
1008             }
1009             }
1010             },
1011             'clID' => 'registrar-id',
1012             'roid' => 'CNIC-HA321983',
1013             'status' => [
1014             'linked',
1015             'serverDeleteProhibited'
1016             ],
1017             'voice' => '+1.7035555555x1234',
1018             'fax' => '+1.7035555556',
1019             'email' => 'jdoe@example.com',
1020             'crDate' => '2011-09-23T03:51:29.0Z',
1021             'upDate' => '1999-11-30T00:00:00.0Z'
1022             };
1023              
1024             There may be up to two members of the C hash, corresponding
1025             to the C and C internationalised and localised types.
1026              
1027             =cut
1028              
1029             sub _contact_infData_to_hash {
1030 0     0     my ($self, $infData) = @_;
1031              
1032 0           my $hash = $self->_get_common_properties_from_infData($infData, 'email', 'id');
1033              
1034             # remove this as it gets in the way:
1035 0           my $els = $infData->getElementsByLocalName('disclose');
1036 0 0         if ($els->size > 0) {
1037 0           while (my $el = $els->shift) {
1038 0           $el->parentNode->removeChild($el);
1039             }
1040             }
1041              
1042 0           foreach my $name ('voice', 'fax') {
1043 0           my $els = $infData->getElementsByLocalName($name);
1044 0 0 0       if (defined($els) && $els->size == 1) {
1045 0           my $el = $els->shift;
1046 0 0         if (defined($el)) {
1047 0           $hash->{$name} = $el->textContent;
1048 0 0 0       $hash->{$name} .= 'x'.$el->getAttribute('x') if (defined($el->getAttribute('x')) && $el->getAttribute('x') ne '');
1049             }
1050             }
1051             }
1052              
1053 0           my $postalInfo = $infData->getElementsByLocalName('postalInfo');
1054 0           while (my $info = $postalInfo->shift) {
1055 0           my $ref = {};
1056              
1057 0           foreach my $name (qw(name org)) {
1058 0           my $els = $info->getElementsByLocalName($name);
1059 0 0         $ref->{$name} = $els->shift->textContent if ($els->size == 1);
1060             }
1061              
1062 0           my $addrs = $info->getElementsByLocalName('addr');
1063 0 0         if ($addrs->size == 1) {
1064 0           my $addr = $addrs->shift;
1065 0           foreach my $child ($addr->childNodes) {
1066 0 0         next if (XML::LibXML::XML_ELEMENT_NODE != $child->nodeType);
1067 0 0         if ($child->localName eq 'street') {
1068 0           push(@{$ref->{addr}->{$child->localName}}, $child->textContent);
  0            
1069              
1070             } else {
1071 0           $ref->{addr}->{$child->localName} = $child->textContent;
1072              
1073             }
1074             }
1075             }
1076              
1077 0           $hash->{postalInfo}->{$info->getAttribute('type')} = $ref;
1078             }
1079              
1080 0           my $auths = $infData->getElementsByLocalName('authInfo');
1081 0 0         if ($auths->size == 1) {
1082 0           my $authInfo = $auths->shift;
1083 0           my $pw = $authInfo->getElementsByLocalName('pw');
1084 0 0         $hash->{authInfo} = $pw->shift->textContent if ($pw->size == 1);
1085             }
1086              
1087 0           return $hash;
1088             }
1089              
1090             =pod
1091              
1092             =head1 Object Transfers
1093              
1094             The EPP CtransferE> command suppots five different operations:
1095             query, request, cancel, approve, and reject. C makes
1096             these available using the following methods:
1097              
1098             # For domain objects:
1099              
1100             $epp->domain_transfer_query($domain);
1101             $epp->domain_transfer_cancel($domain);
1102             $epp->domain_transfer_request($domain, $authInfo, $period);
1103             $epp->domain_transfer_approve($domain);
1104             $epp->domain_transfer_reject($domain);
1105              
1106             # For contact objects:
1107              
1108             $epp->contact_transfer_query($contact);
1109             $epp->contact_transfer_cancel($contact);
1110             $epp->contact_transfer_request($contact, $authInfo);
1111             $epp->contact_transfer_approve($contact);
1112             $epp->contact_transfer_reject($contact);
1113              
1114             Most of these methods will just set the value of C<$Net::EPP::Simple::Code>
1115             and return either true or false. However, the C,
1116             C, C and C
1117             methods will return a hash ref that looks like this:
1118              
1119             my $trnData = {
1120             'name' => 'example.tld',
1121             'reID' => 'losing-registrar',
1122             'acDate' => '2011-12-04T12:24:53.0Z',
1123             'acID' => 'gaining-registrar',
1124             'reDate' => '2011-11-29T12:24:53.0Z',
1125             'trStatus' => 'pending'
1126             };
1127              
1128             =cut
1129              
1130             sub _transfer_request {
1131 0     0     my ($self, $op, $type, $identifier, $authInfo, $period) = @_;
1132              
1133 0           my $class = sprintf('Net::EPP::Frame::Command::Transfer::%s', ucfirst(lc($type)));
1134              
1135 0           my $frame;
1136 0           eval("\$frame = $class->new");
1137 0 0 0       if ($@ || ref($frame) ne $class) {
1138 0           $Error = "Error building request frame: $@";
1139 0           $Code = COMMAND_FAILED;
1140 0           return undef;
1141              
1142             } else {
1143 0           $frame->setOp($op);
1144 0 0         if ($type eq 'domain') {
    0          
1145 0           $frame->setDomain($identifier);
1146 0 0         $frame->setPeriod(int($period)) if ($op eq 'request');
1147              
1148             } elsif ($type eq 'contact') {
1149 0           $frame->setContact($identifier);
1150              
1151             }
1152              
1153 0 0 0       if ($op eq 'request' || $op eq 'query') {
1154 0 0         $frame->setAuthInfo($authInfo) if ($authInfo ne '');
1155             }
1156              
1157             }
1158              
1159 0           my $response = $self->_request($frame);
1160              
1161              
1162 0 0         if (!$response) {
1163 0           return undef;
1164              
1165             } else {
1166 0           $Code = $self->_get_response_code($response);
1167 0           $Message = $self->_get_message($response);
1168              
1169 0 0 0       if ($Code > 1999) {
    0          
1170 0           $Error = $response->msg;
1171 0           return undef;
1172              
1173             } elsif ($op eq 'query' || $op eq 'request') {
1174 0           my $trnData = $response->getElementsByLocalName('trnData')->shift;
1175 0           my $hash = {};
1176 0           foreach my $child ($trnData->childNodes) {
1177 0           $hash->{$child->localName} = $child->textContent;
1178             }
1179              
1180 0           return $hash;
1181              
1182             } else {
1183 0           return 1;
1184              
1185             }
1186             }
1187             }
1188              
1189             sub domain_transfer_query {
1190 0     0 0   return $_[0]->_transfer_request('query', 'domain', $_[1]);
1191             }
1192              
1193             sub domain_transfer_cancel {
1194 0     0 0   return $_[0]->_transfer_request('cancel', 'domain', $_[1]);
1195             }
1196              
1197             sub domain_transfer_request {
1198 0     0 0   return $_[0]->_transfer_request('request', 'domain', $_[1], $_[2], $_[3]);
1199             }
1200              
1201             sub domain_transfer_approve {
1202 0     0 0   return $_[0]->_transfer_request('approve', 'domain', $_[1]);
1203             }
1204              
1205             sub domain_transfer_reject {
1206 0     0 0   return $_[0]->_transfer_request('reject', 'domain', $_[1]);
1207             }
1208              
1209             sub contact_transfer_query {
1210 0     0 0   return $_[0]->_transfer_request('query', 'contact', $_[1]);
1211             }
1212              
1213             sub contact_transfer_cancel {
1214 0     0 0   return $_[0]->_transfer_request('cancel', 'contact', $_[1]);
1215             }
1216              
1217             sub contact_transfer_request {
1218 0     0 0   return $_[0]->_transfer_request('request', 'contact', $_[1], $_[2]);
1219             }
1220              
1221             sub contact_transfer_approve {
1222 0     0 0   return $_[0]->_transfer_request('approve', 'contact', $_[1]);
1223             }
1224              
1225             sub contact_transfer_reject {
1226 0     0 0   return $_[0]->_transfer_request('reject', 'contact', $_[1]);
1227             }
1228              
1229             =pod
1230              
1231             =head1 Creating Objects
1232              
1233             The following methods can be used to create a new object at the server:
1234              
1235             $epp->create_domain($domain);
1236             $epp->create_host($host);
1237             $epp->create_contact($contact);
1238              
1239             The argument for these methods is a hash ref of the same format as that
1240             returned by the info methods above. As a result, cloning an existing
1241             object is as simple as the following:
1242              
1243             my $info = $epp->contact_info($contact);
1244              
1245             # set a new contact ID to avoid clashing with the existing object
1246             $info->{id} = $new_contact;
1247              
1248             # randomize authInfo:
1249             $info->{authInfo} = $random_string;
1250              
1251             $epp->create_contact($info);
1252              
1253             C will ignore object properties that it does not recognise,
1254             and those properties (such as server-managed status codes) that clients are
1255             not permitted to set.
1256              
1257             =head2 Creating New Domains
1258              
1259             When creating a new domain object, you may also specify a C key, like so:
1260              
1261             my $domain = {
1262             'name' => 'example.tld',
1263             'period' => 2,
1264             'registrant' => 'contact-id',
1265             'contacts' => {
1266             'tech' => 'contact-id',
1267             'admin' => 'contact-id',
1268             'billing' => 'contact-id',
1269             },
1270             'status' => [
1271             'clientTransferProhibited',
1272             ],
1273             'ns' => {
1274             'ns0.example.com',
1275             'ns1.example.com',
1276             },
1277             };
1278              
1279             $epp->create_domain($domain);
1280              
1281             The C key is assumed to be in years rather than months. C
1282             assumes the registry uses the host object model rather than the host attribute model.
1283              
1284             =cut
1285              
1286             sub create_domain {
1287 0     0 0   my ($self, $domain) = @_;
1288              
1289 0           return $self->_get_response_result(
1290             $self->_request(
1291             $self->_prepare_create_domain_frame($domain)
1292             )
1293             );
1294             }
1295              
1296             sub _prepare_create_domain_frame {
1297 0     0     my ($self, $domain) = @_;
1298              
1299 0           my $frame = Net::EPP::Frame::Command::Create::Domain->new;
1300 0           $frame->setDomain($domain->{'name'});
1301 0 0 0       $frame->setPeriod($domain->{'period'}) if (defined($domain->{period}) && $domain->{period} > 0);
1302 0 0 0       $frame->setNS(@{$domain->{'ns'}}) if $domain->{'ns'} and @{$domain->{'ns'}};
  0            
  0            
1303 0 0 0       $frame->setRegistrant($domain->{'registrant'}) if (defined($domain->{registrant}) && $domain->{registrant} ne '');
1304 0           $frame->setContacts($domain->{'contacts'});
1305 0 0 0       $frame->setAuthInfo($domain->{authInfo}) if (defined($domain->{authInfo}) && $domain->{authInfo} ne '');
1306 0           return $frame;
1307             }
1308              
1309             =head2 Creating Hosts
1310              
1311             my $host = {
1312             name => 'ns1.example.tld',
1313             addrs => [
1314             { ip => '123.45.67.89', version => 'v4' },
1315             { ip => '98.76.54.32', version => 'v4' },
1316             ],
1317             };
1318             $epp->create_host($host);
1319              
1320             =cut
1321              
1322             sub create_host {
1323 0     0 0   my ($self, $host) = @_;
1324              
1325 0           return $self->_get_response_result(
1326             $self->_request(
1327             $self->_prepare_create_host_frame($host)
1328             )
1329             );
1330             }
1331              
1332             sub _prepare_create_host_frame {
1333 0     0     my ($self, $host) = @_;
1334              
1335 0           my $frame = Net::EPP::Frame::Command::Create::Host->new;
1336 0           $frame->setHost($host->{name});
1337 0           $frame->setAddr(@{$host->{addrs}});
  0            
1338 0           return $frame;
1339             }
1340              
1341             sub create_contact {
1342 0     0 0   my ($self, $contact) = @_;
1343              
1344 0           return $self->_get_response_result(
1345             $self->_request(
1346             $self->_prepare_create_contact_frame($contact)
1347             )
1348             );
1349             }
1350              
1351              
1352             sub _prepare_create_contact_frame {
1353 0     0     my ($self, $contact) = @_;
1354              
1355 0           my $frame = Net::EPP::Frame::Command::Create::Contact->new;
1356              
1357 0           $frame->setContact($contact->{id});
1358              
1359 0 0         if (ref($contact->{postalInfo}) eq 'HASH') {
1360 0           foreach my $type (keys(%{$contact->{postalInfo}})) {
  0            
1361             $frame->addPostalInfo(
1362             $type,
1363             $contact->{postalInfo}->{$type}->{name},
1364             $contact->{postalInfo}->{$type}->{org},
1365             $contact->{postalInfo}->{$type}->{addr}
1366 0           );
1367             }
1368             }
1369              
1370 0 0 0       $frame->setVoice($contact->{voice}) if (defined($contact->{voice}) && $contact->{voice} ne '');
1371 0 0 0       $frame->setFax($contact->{fax}) if (defined($contact->{fax}) && $contact->{fax} ne '');
1372 0           $frame->setEmail($contact->{email});
1373 0 0 0       $frame->setAuthInfo($contact->{authInfo}) if (defined($contact->{authInfo}) && $contact->{authInfo} ne '');
1374              
1375 0 0         if (ref($contact->{status}) eq 'ARRAY') {
1376 0           foreach my $status (grep { /^client/ } @{$contact->{status}}) {
  0            
  0            
1377 0           $frame->appendStatus($status);
1378             }
1379             }
1380 0           return $frame;
1381             }
1382              
1383              
1384             # Process response code and return result
1385             sub _get_response_result {
1386 0     0     my ($self, $response) = @_;
1387              
1388 0 0         return undef if !$response;
1389              
1390             # If there was a response...
1391 0           $Code = $self->_get_response_code($response);
1392 0           $Message = $self->_get_message($response);
1393 0 0         if ($Code > 1999) {
1394 0           $Error = $response->msg;
1395 0           return undef;
1396             }
1397 0           return 1;
1398             }
1399              
1400              
1401             =head1 Updating Objects
1402              
1403             The following methods can be used to update an object at the server:
1404              
1405             $epp->update_domain($domain);
1406             $epp->update_host($host);
1407             $epp->update_contact($contact);
1408              
1409             Each of these methods has the same profile. They will return one of the following:
1410              
1411             =over
1412              
1413             =item * undef in the case of an error (check C<$Net::EPP::Simple::Error> and C<$Net::EPP::Simple::Code>).
1414              
1415             =item * 1 if the update request was accepted.
1416              
1417             =back
1418              
1419             You may wish to check the value of $Net::EPP::Simple::Code to determine whether the response code was 1000 (OK) or 1001 (action pending).
1420              
1421             =cut
1422              
1423              
1424             =head2 Updating Domains
1425              
1426             Use update_domain() method to update domains' data.
1427              
1428             The update info parameter may look like:
1429             $update_info = {
1430             name => $domain,
1431             chg => {
1432             registrant => $new_registrant_id,
1433             authInfo => $new_domain_password,
1434             },
1435             add => {
1436             # DNS info with "hostObj" or "hostAttr" model, see create_domain()
1437             ns => [ ns1.example.com ns2.example.com ],
1438             contacts => {
1439             tech => 'contact-id',
1440             billing => 'contact-id',
1441             admin => 'contact-id',
1442             },
1443              
1444             # Status info, simple form:
1445             status => [ qw/ clientUpdateProhibited clientHold / ],
1446              
1447             # Status info may be in more detailed form:
1448             # status => {
1449             # clientUpdateProbhibited => 'Avoid accidental change',
1450             # clientHold => 'This domain is not delegated',
1451             # },
1452             },
1453             rem => {
1454             ns => [ ... ],
1455             contacts => {
1456             tech => 'old_tech_id',
1457             billing => 'old_billing_id',
1458             admin => 'old_admin_id',
1459             },
1460             status => [ qw/ clientTransferProhibited ... / ],
1461             },
1462             }
1463              
1464             All fields except 'name' in $update_info hash are optional.
1465              
1466             =cut
1467              
1468             sub update_domain {
1469 0     0 0   my ($self, $domain) = @_;
1470 0           return $self->_update('domain', $domain);
1471             }
1472              
1473             =head2 Updating Contacts
1474              
1475             Use update_contact() method to update contact's data.
1476              
1477             The $update_info for contacts may look like this:
1478              
1479             $update_info = {
1480             id => $contact_id,
1481             add => {
1482             status => [ qw/ clientDeleteProhibited / ],
1483             # OR
1484             # status => {
1485             # clientDeleteProhibited => 'Avoid accidental removal',
1486             # },
1487             },
1488             rem => {
1489             status => [ qw/ clientUpdateProhibited / ],
1490             },
1491             chg => {
1492             postalInfo => {
1493             int => {
1494             name => 'John Doe',
1495             org => 'Example Inc.',
1496             addr => {
1497             street => [
1498             '123 Example Dr.'
1499             'Suite 100'
1500             ],
1501             city => 'Dulles',
1502             sp => 'VA',
1503             pc => '20116-6503'
1504             cc => 'US',
1505             },
1506             },
1507             },
1508             voice => '+1.7035555555x1234',
1509             fax => '+1.7035555556',
1510             email => 'jdoe@example.com',
1511             authInfo => 'new-contact-password',
1512             },
1513             }
1514              
1515             All fields except 'id' in $update_info hash are optional.
1516              
1517             =cut
1518              
1519             sub update_contact {
1520 0     0 0   my ($self, $contact) = @_;
1521 0           return $self->_update('contact', $contact);
1522             }
1523              
1524             =head2 Updating Hosts
1525              
1526             Use update_host() method to update EPP hosts.
1527              
1528             The $update_info for hosts may look like this:
1529              
1530             $update_info = {
1531             name => 'ns1.example.com',
1532             add => {
1533             status => [ qw/ clientDeleteProhibited / ],
1534             # OR
1535             # status => {
1536             # clientDeleteProhibited => 'Avoid accidental removal',
1537             # },
1538              
1539             addrs => [
1540             { ip => '123.45.67.89', version => 'v4' },
1541             { ip => '98.76.54.32', version => 'v4' },
1542             ],
1543             },
1544             rem => {
1545             status => [ qw/ clientUpdateProhibited / ],
1546             addrs => [
1547             { ip => '1.2.3.4', version => 'v4' },
1548             { ip => '5.6.7.8', version => 'v4' },
1549             ],
1550             },
1551             chg => {
1552             name => 'ns2.example.com',
1553             },
1554             }
1555              
1556             All fields except first 'name' in $update_info hash are optional.
1557              
1558             =cut
1559              
1560             sub update_host {
1561 0     0 0   my ($self, $host) = @_;
1562 0           return $self->_update('host', $host);
1563             }
1564              
1565              
1566             # Update domain/contact/host information
1567             sub _update {
1568 0     0     my ($self, $type, $info) = @_;
1569              
1570 0           my %frame_generator = (
1571             'domain' => \&_generate_update_domain_frame,
1572             'contact' => \&_generate_update_contact_frame,
1573             'host' => \&_generate_update_host_frame,
1574             );
1575              
1576 0 0         if ( !exists $frame_generator{$type} ) {
1577 0           $Error = "Unknown object type: '$type'";
1578 0           return undef;
1579             }
1580              
1581 0           my $generator = $frame_generator{$type};
1582 0           my $frame = $self->$generator($info);
1583 0           return $self->_get_response_result( $self->request($frame) );
1584             }
1585              
1586              
1587             sub _generate_update_domain_frame {
1588 0     0     my ($self, $info) = @_;
1589              
1590 0           my $frame = Net::EPP::Frame::Command::Update::Domain->new;
1591 0           $frame->setDomain( $info->{name} );
1592              
1593             # 'add' element
1594 0 0 0       if ( exists $info->{add} && ref $info->{add} eq 'HASH' ) {
1595              
1596 0           my $add = $info->{add};
1597              
1598             # Add DNS
1599 0 0 0       if ( exists $add->{ns} && ref $add->{ns} eq 'ARRAY' ) {
1600 0           $frame->addNS( @{ $add->{ns} } );
  0            
1601             }
1602              
1603             # Add contacts
1604 0 0 0       if ( exists $add->{contacts} && ref $add->{contacts} eq 'HASH' ) {
1605              
1606 0           my $contacts = $add->{contacts};
1607 0           foreach my $type ( keys %{ $contacts } ) {
  0            
1608 0           $frame->addContact( $type, $contacts->{$type} );
1609             }
1610             }
1611              
1612             # Add status info
1613 0 0 0       if ( exists $add->{status} && ref $add->{status} ) {
1614 0 0         if ( ref $add->{status} eq 'HASH' ) {
    0          
1615 0           while ( my ($type, $info) = each %{ $add->{status} } ) {
  0            
1616 0           $frame->addStatus($type, $info);
1617             }
1618             }
1619             elsif ( ref $add->{status} eq 'ARRAY' ) {
1620 0           $frame->addStatus($_) for @{ $add->{status} };
  0            
1621             }
1622             }
1623             }
1624              
1625             # 'rem' element
1626 0 0 0       if ( exists $info->{rem} && ref $info->{rem} eq 'HASH' ) {
1627              
1628 0           my $rem = $info->{rem};
1629              
1630             # DNS
1631 0 0 0       if ( exists $rem->{ns} && ref $rem->{ns} eq 'ARRAY' ) {
1632 0           $frame->remNS( @{ $rem->{ns} } );
  0            
1633             }
1634              
1635             # Contacts
1636 0 0 0       if ( exists $rem->{contacts} && ref $rem->{contacts} eq 'HASH' ) {
1637 0           my $contacts = $rem->{contacts};
1638              
1639 0           foreach my $type ( keys %{ $contacts } ) {
  0            
1640 0           $frame->remContact( $type, $contacts->{$type} );
1641             }
1642             }
1643              
1644             # Status info
1645 0 0 0       if ( exists $rem->{status} && ref $rem->{status} eq 'ARRAY' ) {
1646 0           $frame->remStatus($_) for @{ $rem->{status} };
  0            
1647             }
1648             }
1649              
1650             # 'chg' element
1651 0 0 0       if ( exists $info->{chg} && ref $info->{chg} eq 'HASH' ) {
1652              
1653 0           my $chg = $info->{chg};
1654              
1655 0 0         if ( defined $chg->{registrant} ) {
1656 0           $frame->chgRegistrant( $chg->{registrant} );
1657             }
1658              
1659 0 0         if ( defined $chg->{authInfo} ) {
1660 0           $frame->chgAuthInfo( $chg->{authInfo} );
1661             }
1662             }
1663              
1664 0           return $frame;
1665             }
1666              
1667              
1668             sub _generate_update_contact_frame {
1669 0     0     my ($self, $info) = @_;
1670              
1671 0           my $frame = Net::EPP::Frame::Command::Update::Contact->new;
1672 0           $frame->setContact( $info->{id} );
1673              
1674             # Add
1675 0 0 0       if ( exists $info->{add} && ref $info->{add} eq 'HASH' ) {
1676 0           my $add = $info->{add};
1677              
1678 0 0 0       if ( exists $add->{status} && ref $add->{status} ) {
1679 0 0         if ( ref $add->{status} eq 'HASH' ) {
    0          
1680 0           while ( my ($type, $info) = each %{ $add->{status} } ) {
  0            
1681 0           $frame->addStatus($type, $info);
1682             }
1683             }
1684             elsif ( ref $add->{status} eq 'ARRAY' ) {
1685 0           $frame->addStatus($_) for @{ $add->{status} };
  0            
1686             }
1687             }
1688             }
1689              
1690             # Remove
1691 0 0 0       if ( exists $info->{rem} && ref $info->{rem} eq 'HASH' ) {
1692              
1693 0           my $rem = $info->{rem};
1694              
1695 0 0 0       if ( exists $rem->{status} && ref $rem->{status} eq 'ARRAY' ) {
1696 0           $frame->remStatus($_) for @{ $rem->{status} };
  0            
1697             }
1698             }
1699              
1700             # Change
1701 0 0 0       if ( exists $info->{chg} && ref $info->{chg} eq 'HASH' ) {
1702              
1703 0           my $chg = $info->{chg};
1704              
1705             # Change postal info
1706 0 0         if ( ref $chg->{postalInfo} eq 'HASH' ) {
1707 0           foreach my $type ( keys %{ $chg->{postalInfo} } ) {
  0            
1708             $frame->chgPostalInfo(
1709             $type,
1710             $chg->{postalInfo}->{$type}->{name},
1711             $chg->{postalInfo}->{$type}->{org},
1712             $chg->{postalInfo}->{$type}->{addr}
1713 0           );
1714             }
1715             }
1716              
1717             # Change voice / fax / email
1718 0           for my $contact_type ( qw/ voice fax email / ) {
1719 0 0         if ( defined $chg->{$contact_type} ) {
1720 0           my $el = $frame->createElement("contact:$contact_type");
1721 0           $el->appendText( $chg->{$contact_type} );
1722 0           $frame->chg->appendChild($el);
1723             }
1724             }
1725              
1726             # Change auth info
1727 0 0         if ( $chg->{authInfo} ) {
1728 0           $frame->chgAuthInfo( $chg->{authInfo} );
1729             }
1730              
1731             # 'disclose' option is still unimplemented
1732             }
1733              
1734 0           return $frame;
1735             }
1736              
1737             sub _generate_update_host_frame {
1738 0     0     my ($self, $info) = @_;
1739              
1740 0           my $frame = Net::EPP::Frame::Command::Update::Host->new;
1741 0           $frame->setHost($info->{name});
1742              
1743 0 0 0       if ( exists $info->{add} && ref $info->{add} eq 'HASH' ) {
1744 0           my $add = $info->{add};
1745             # Process addresses
1746 0 0 0       if ( exists $add->{addrs} && ref $add->{addrs} eq 'ARRAY' ) {
1747 0           $frame->addAddr( @{ $add->{addrs} } );
  0            
1748             }
1749             # Process statuses
1750 0 0 0       if ( exists $add->{status} && ref $add->{status} ) {
1751 0 0         if ( ref $add->{status} eq 'HASH' ) {
    0          
1752 0           while ( my ($type, $info) = each %{ $add->{status} } ) {
  0            
1753 0           $frame->addStatus($type, $info);
1754             }
1755             }
1756             elsif ( ref $add->{status} eq 'ARRAY' ) {
1757 0           $frame->addStatus($_) for @{ $add->{status} };
  0            
1758             }
1759             }
1760             }
1761              
1762 0 0 0       if ( exists $info->{rem} && ref $info->{rem} eq 'HASH' ) {
1763 0           my $rem = $info->{rem};
1764             # Process addresses
1765 0 0 0       if ( exists $rem->{addrs} && ref $rem->{addrs} eq 'ARRAY' ) {
1766 0           $frame->remAddr( @{ $rem->{addrs} } );
  0            
1767             }
1768             # Process statuses
1769 0 0 0       if ( exists $rem->{status} && ref $rem->{status} ) {
1770 0 0         if ( ref $rem->{status} eq 'HASH' ) {
    0          
1771 0           while ( my ($type, $info) = each %{ $rem->{status} } ) {
  0            
1772 0           $frame->remStatus($type, $info);
1773             }
1774             }
1775             elsif ( ref $rem->{status} eq 'ARRAY' ) {
1776 0           $frame->remStatus($_) for @{ $rem->{status} };
  0            
1777             }
1778             }
1779             }
1780              
1781 0 0 0       if ( exists $info->{chg} && ref $info->{chg} eq 'HASH' ) {
1782 0 0         if ( $info->{chg}->{name} ) {
1783 0           $frame->chgName( $info->{chg}->{name} );
1784             }
1785             }
1786              
1787 0           return $frame;
1788             }
1789              
1790              
1791             =pod
1792              
1793             =head1 Deleting Objects
1794              
1795             The following methods can be used to delete an object at the server:
1796              
1797             $epp->delete_domain($domain);
1798             $epp->delete_host($host);
1799             $epp->delete_contact($contact);
1800              
1801             Each of these methods has the same profile. They will return one of the following:
1802              
1803             =over
1804              
1805             =item * undef in the case of an error (check C<$Net::EPP::Simple::Error> and C<$Net::EPP::Simple::Code>).
1806              
1807             =item * 1 if the deletion request was accepted.
1808              
1809             =back
1810              
1811             You may wish to check the value of $Net::EPP::Simple::Code to determine whether the response code was 1000 (OK) or 1001 (action pending).
1812              
1813             =cut
1814              
1815             sub delete_domain {
1816 0     0 0   my ($self, $domain) = @_;
1817 0           return $self->_delete('domain', $domain);
1818             }
1819              
1820             sub delete_host {
1821 0     0 0   my ($self, $host) = @_;
1822 0           return $self->_delete('host', $host);
1823             }
1824              
1825             sub delete_contact {
1826 0     0 0   my ($self, $contact) = @_;
1827 0           return $self->_delete('contact', $contact);
1828             }
1829              
1830             sub _delete {
1831 0     0     my ($self, $type, $identifier) = @_;
1832 0           my $frame;
1833 0 0         if ($type eq 'domain') {
    0          
    0          
1834 0           $frame = Net::EPP::Frame::Command::Delete::Domain->new;
1835 0           $frame->setDomain($identifier);
1836              
1837             } elsif ($type eq 'contact') {
1838 0           $frame = Net::EPP::Frame::Command::Delete::Contact->new;
1839 0           $frame->setContact($identifier);
1840              
1841             } elsif ($type eq 'host') {
1842 0           $frame = Net::EPP::Frame::Command::Delete::Host->new;
1843 0           $frame->setHost($identifier);
1844              
1845             } else {
1846 0           $Error = "Unknown object type '$type'";
1847 0           return undef;
1848              
1849             }
1850              
1851 0           my $response = $self->_request($frame);
1852              
1853              
1854 0 0         if (!$response) {
1855 0           return undef;
1856              
1857             } else {
1858 0           $Code = $self->_get_response_code($response);
1859 0           $Message = $self->_get_message($response);
1860              
1861 0 0         if ($Code > 1999) {
1862 0           $Error = $self->_get_error_message($response);
1863 0           return undef;
1864              
1865             } else {
1866 0           return 1;
1867              
1868             }
1869             }
1870             }
1871              
1872             =head1 Domain Renewal
1873              
1874             You can extend the validity period of the domain object by issuing a
1875             renew_domain() command.
1876              
1877             my $result = $epp->renew_domain({
1878             name => 'example.com',
1879             cur_exp_date => '2011-02-05', # current expiration date
1880             period => 2, # prolongation period in years
1881             });
1882              
1883             Return value is C<1> on success and C on error.
1884             In the case of error C<$Net::EPP::Simple::Error> contains the appropriate
1885             error message.
1886              
1887             =cut
1888              
1889             sub renew_domain {
1890 0     0 0   my ($self, $info) = @_;
1891              
1892 0           return $self->_get_response_result(
1893             $self->request(
1894             $self->_generate_renew_domain_frame($info)
1895             )
1896             );
1897             }
1898              
1899             sub _generate_renew_domain_frame {
1900 0     0     my ($self, $info) = @_;
1901              
1902 0           my $frame = Net::EPP::Frame::Command::Renew::Domain->new;
1903 0           $frame->setDomain( $info->{name} );
1904 0           $frame->setCurExpDate( $info->{cur_exp_date} );
1905 0 0         $frame->setPeriod( $info->{period} ) if $info->{period};
1906              
1907 0           return $frame;
1908             }
1909              
1910             =pod
1911              
1912             =head1 Miscellaneous Methods
1913              
1914             =cut
1915              
1916 0     0 1   sub error { $Error }
1917              
1918 0     0 0   sub code { $Code }
1919              
1920 0     0 0   sub message { $Message }
1921              
1922             =pod
1923              
1924             my $greeting = $epp->greeting;
1925              
1926             Returns the a C object representing the greeting returned by the server.
1927              
1928             =cut
1929              
1930 0     0 0   sub greeting { $_[0]->{greeting} }
1931              
1932             =pod
1933              
1934             $epp->ping;
1935              
1936             Checks that the connection is up by sending a ChelloE> to the server. Returns false if no
1937             response is received.
1938              
1939             =cut
1940              
1941             sub ping {
1942 0     0 0   my $self = shift;
1943 0           my $hello = Net::EPP::Frame::Hello->new;
1944 0           my $response = $self->request($hello);
1945              
1946 0 0         if (UNIVERSAL::isa($response, 'XML::LibXML::Document')) {
1947 0           $Code = 1000;
1948 0           $Message = 'Command completed successfully.';
1949 0           return 1;
1950              
1951             } else {
1952 0           $Code = 2400;
1953 0           $Message = 'Error getting greeting from server.';
1954 0           return undef;
1955             }
1956             }
1957              
1958             sub _request {
1959 0     0     my ($self, $frame) = @_;
1960              
1961 0 0         if ($self->{reconnect} > 0) {
1962 0           $self->debug("reconnect is $self->{reconnect}, pinging");
1963 0 0         if (!$self->ping) {
1964 0           $self->debug('connection seems dead, trying to reconnect');
1965 0           for (1..$self->{reconnect}) {
1966 0           $self->debug("attempt #$_");
1967 0 0         if ($self->_connect) {
1968 0           $self->debug("attempt #$_ succeeded");
1969 0           return $self->request($frame);
1970              
1971             } else {
1972 0           $self->debug("attempt #$_ failed, sleeping");
1973 0           sleep($self->{timeout});
1974              
1975             }
1976             }
1977 0           $self->debug('unable to reconnect!');
1978 0           return undef;
1979              
1980             } else {
1981 0           $self->debug("Connection is up, sending frame");
1982 0           return $self->request($frame);
1983              
1984             }
1985              
1986             } else {
1987 0           return $self->request($frame);
1988              
1989             }
1990             }
1991              
1992             =pod
1993              
1994             =head1 Overridden Methods From C
1995              
1996             C overrides some methods inherited from
1997             C. These are described below:
1998              
1999             =head2 The C Method
2000              
2001             C overrides this method so it can automatically populate
2002             the CclTRIDE> element with a unique string. It then passes the
2003             frame back up to C.
2004              
2005             =cut
2006              
2007             sub request {
2008 0     0 1   my ($self, $frame) = @_;
2009             # Make sure we start with blank variables
2010 0           $Code = undef;
2011 0           $Error = '';
2012 0           $Message = '';
2013              
2014 0 0         if (!$self->connected) {
    0          
2015 0           $Code = COMMAND_FAILED;
2016 0           $Error = $Message = 'Not connected';
2017 0           $self->debug('cannot send frame if not connected');
2018 0           return undef;
2019              
2020             } elsif (!$frame) {
2021 0           $Code = COMMAND_FAILED;
2022 0           $Error = $Message = 'Invalid frame';
2023 0           $self->debug($Message);
2024 0           return undef;
2025              
2026             } else {
2027 0 0         $frame->clTRID->appendText(sha1_hex(ref($self).time().$$)) if (UNIVERSAL::isa($frame, 'Net::EPP::Frame::Command'));
2028              
2029 0           my $type = ref($frame);
2030 0 0         if ($frame =~ /^\//) {
2031 0           $type = 'file';
2032              
2033             } else {
2034 0           $type = 'string';
2035              
2036             }
2037 0           $self->debug(sprintf('sending a %s to the server', $type));
2038 0 0         if (UNIVERSAL::isa($frame, 'XML::LibXML::Document')) {
2039 0           map { $self->debug('C: '.$_) } split(/\n/, $frame->toString(2));
  0            
2040              
2041             } else {
2042 0           map { $self->debug('C: '.$_) } split(/\n/, $frame);
  0            
2043              
2044             }
2045              
2046 0           my $response = $self->SUPER::request($frame);
2047              
2048 0 0         map { $self->debug('S: '.$_) } split(/\n/, $response->toString(2)) if (UNIVERSAL::isa($response, 'XML::LibXML::Document'));
  0            
2049              
2050 0           return $response;
2051             }
2052             }
2053              
2054             =pod
2055              
2056             =head2 The C Method
2057              
2058             C overrides this method so it can catch timeouts and
2059             network errors. If such an error occurs it will return C.
2060              
2061             =cut
2062              
2063             sub get_frame {
2064 0     0 1   my $self = shift;
2065 0 0         if (!$self->connected) {
2066 0           $self->debug('cannot send frame if not connected');
2067 0           $Code = COMMAND_FAILED;
2068 0           $Error = $Message = 'Not connected';
2069 0           return undef;
2070              
2071             } else {
2072 0           my $frame;
2073 0           $self->debug(sprintf('reading frame, waiting %d seconds before timeout', $self->{timeout}));
2074 0           eval {
2075 0     0     local $SIG{ALRM} = sub { die 'timeout' };
  0            
2076 0           $self->debug('setting timeout alarm for receiving frame');
2077 0           alarm($self->{timeout});
2078 0           $frame = $self->SUPER::get_frame();
2079 0           $self->debug('unsetting timeout alarm after successful receive');
2080 0           alarm(0);
2081             };
2082 0 0         if ($@ ne '') {
2083 0           chomp($@);
2084 0           $@ =~ s/ at .+ line .+$//;
2085 0           $self->debug("unsetting timeout alarm after alarm was triggered ($@)");
2086 0           alarm(0);
2087 0           $Code = COMMAND_FAILED;
2088 0 0         if ($@ =~ /^timeout/) {
2089 0           $Error = $Message = "get_frame() timed out after $self->{timeout} seconds";
2090              
2091             } else {
2092 0           $Error = $Message = "get_frame() received an error: $@";
2093              
2094             }
2095 0           return undef;
2096              
2097             } else {
2098 0           return bless($frame, 'Net::EPP::Frame::Response');
2099              
2100             }
2101             }
2102             }
2103              
2104             sub send_frame {
2105 0     0 0   my ($self, $frame, $wfcheck) = @_;
2106 0 0         if (!$self->connected) {
2107 0           $self->debug('cannot get frame if not connected');
2108 0           $Code = 2400;
2109 0           $Message = 'Not connected';
2110 0           return undef;
2111              
2112             } else {
2113 0           return $self->SUPER::send_frame($frame, $wfcheck);
2114              
2115             }
2116             }
2117              
2118             # Get details error description including code, message and reason
2119             sub _get_error_message {
2120 0     0     my ($self, $doc) = @_;
2121              
2122 0           my $code = $self->_get_response_code($doc);
2123 0           my $error = "Error $code";
2124              
2125 0           my $message = $self->_get_message($doc);
2126 0 0         if ( $message ) {
2127 0           $error .= ": $message";
2128             }
2129              
2130 0           my $reason = $self->_get_reason($doc);
2131 0 0         if ( $reason ) {
2132 0           $error .= " ($reason)";
2133             }
2134              
2135 0           return $error;
2136             }
2137              
2138             sub _get_response_code {
2139 0     0     my ($self, $doc) = @_;
2140 0 0 0       if ($doc->isa('XML::DOM::Document') || $doc->isa('Net::EPP::Frame::Response')) {
2141 0           my $els = $doc->getElementsByTagNameNS(EPP_XMLNS, 'result');
2142 0 0         if (defined($els)) {
2143 0           my $el = $els->shift;
2144 0 0         return $el->getAttribute('code') if (defined($el));
2145             }
2146             }
2147 0           return 2400;
2148             }
2149              
2150             sub _get_message {
2151 0     0     my ($self, $doc) = @_;
2152 0 0 0       if ($doc->isa('XML::DOM::Document') || $doc->isa('Net::EPP::Frame::Response')) {
2153 0           my $msgs = $doc->getElementsByTagNameNS(EPP_XMLNS, 'msg');
2154 0 0         if (defined($msgs)) {
2155 0           my $msg = $msgs->shift;
2156 0 0         return $msg->textContent if (defined($msg));
2157             }
2158             }
2159 0           return '';
2160             }
2161              
2162             sub _get_reason {
2163 0     0     my ($self, $doc) = @_;
2164 0 0 0       if ($doc->isa('XML::DOM::Document') || $doc->isa('Net::EPP::Frame::Response')) {
2165 0           my $reasons = $doc->getElementsByTagNameNS(EPP_XMLNS, 'reason');
2166 0 0         if (defined($reasons)) {
2167 0           my $reason = $reasons->shift;
2168 0 0         if (defined($reason)) {
2169 0           return $reason->textContent;
2170             }
2171             }
2172             }
2173 0           return '';
2174             }
2175              
2176             sub logout {
2177 0     0 0   my $self = shift;
2178 0 0         if ($self->authenticated) {
2179 0           $self->debug('logging out');
2180 0           my $response = $self->request(Net::EPP::Frame::Command::Logout->new);
2181 0           undef($self->{'authenticated'});
2182 0 0         if (!$response) {
2183 0           $Code = COMMAND_FAILED;
2184 0           $Message = $Error = 'unknown error';
2185             return undef
2186              
2187 0           } else {
2188 0           $Code = $self->_get_response_code($response);
2189 0           $Message = $self->_get_message($response);
2190              
2191             }
2192             }
2193 0           $self->debug('disconnecting from server');
2194 0           $self->disconnect;
2195 0           undef($self->{'connected'});
2196 0           return 1;
2197             }
2198              
2199             sub DESTROY {
2200 0     0     my $self = shift;
2201 0           $self->debug('DESTROY() method called');
2202 0 0         $self->logout if ($self->connected);
2203             }
2204              
2205             sub debug {
2206 0     0 1   my ($self, $msg) = @_;
2207 0           my $log = sprintf("%s (%d): %s", scalar(localtime()), $$, $msg);
2208 0           push(@Log, $log);
2209 0 0 0       print STDERR $log."\n" if (defined($self->{debug}) && $self->{debug} == 1);
2210             }
2211              
2212             =pod
2213              
2214             $connected = $epp->connected;
2215              
2216             Returns a boolean if C has a connection to the server. Note that this
2217             connection might have dropped, use C to test it.
2218              
2219             =cut
2220              
2221             sub connected {
2222 0     0 0   my $self = shift;
2223 0           return defined($self->{'connected'});
2224             }
2225              
2226             =pod
2227              
2228             $authenticated = $epp->authenticated;
2229              
2230             Returns a boolean if C has successfully authenticated with the server.
2231              
2232             =cut
2233              
2234             sub authenticated {
2235 0     0 0   my $self = shift;
2236 0           return defined($self->{'authenticated'});
2237             }
2238              
2239             =pod
2240              
2241             =head1 Package Variables
2242              
2243             =head2 $Net::EPP::Simple::Error
2244              
2245             This variable contains an english text message explaining the last error
2246             to occur. This is may be due to invalid parameters being passed to a
2247             method, a network error, or an error response being returned by the
2248             server.
2249              
2250             =head2 $Net::EPP::Simple::Message
2251              
2252             This variable contains the contains the text content of the
2253             CmsgE> element in the response frame for the last transaction.
2254              
2255             =head2 $Net::EPP::Simple::Code
2256              
2257             This variable contains the integer result code returned by the server
2258             for the last transaction. A successful transaction will always return an
2259             error code of 1999 or lower, for an unsuccessful transaction it will be
2260             2011 or more. If there is an internal client error (due to invalid
2261             parameters being passed to a method, or a network error) then this will
2262             be set to 2400 (C). See L for
2263             more information about thes codes.
2264              
2265             =cut
2266              
2267             1;