File Coverage

blib/lib/Net/EPP/Simple.pm
Criterion Covered Total %
statement 39 818 4.7
branch 0 354 0.0
condition 0 180 0.0
subroutine 13 85 15.2
pod 5 41 12.2
total 57 1478 3.8


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