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