File Coverage

blib/lib/IO/EPP/Base.pm
Criterion Covered Total %
statement 763 1071 71.2
branch 327 674 48.5
condition 96 186 51.6
subroutine 55 62 88.7
pod 49 54 90.7
total 1290 2047 63.0


line stmt bran cond sub pod time code
1             package IO::EPP::Base;
2              
3             =encoding utf8
4              
5             =head1 NAME
6              
7             IO::EPP::Base
8              
9             =head1 SYNOPSIS
10              
11             use Data::Dumper;
12             use IO::EPP::Base;
13              
14             sub make_request {
15             my ( $action, $params ) = @_;
16              
17             unless ( $params->{conn} ) {
18             # need to connect
19              
20             my %sock_params = (
21             PeerHost => 'epp.example.com',
22             PeerPort => 700,
23             SSL_key_file => 'key.pem',
24             SSL_cert_file => 'cert.pem',
25             Timeout => 30,
26             );
27              
28             $params->{user} = 'login';
29             $params->{pass} = 'xxxxx';
30              
31             $params->{sock_params} = \%sock_params;
32              
33             $params->{test_mode} = 1; # use emulator
34              
35             # $params->{no_log} = 1; # 1 if no logging
36              
37             # enter a name if you need to specify a file for the log
38             # $params->{log_name} = '/var/log/comm_epp_example.log';
39              
40             # use our function for logging
41             $params->{log_fn} = sub { print "epp.example.com logger:\n$_[0]\n" };
42             }
43              
44             return IO::EPP::Base::make_request( $action, $params );
45             }
46              
47             my ( $answ, $msg, $conn_obj ) = make_request( 'check_domains', { domains => [ 'xyz.com', 'com.xyz', 'reged.xyz' ] } );
48              
49             print Dumper $answ;
50              
51             Result:
52              
53             $VAR1 = {
54             'msg' => 'Command completed successfully.',
55             'xyz.com' => {
56             'avail' => '1'
57             },
58             'reged.xyz' => {
59             'reason' => 'in use',
60             'avail' => '0'
61             },
62             'code' => '1000',
63             'com.xyz' => {
64             'avail' => '1'
65             }
66             };
67             }
68              
69             =head1 DESCRIPTION
70              
71             Module for common EPP-functions, without extension (dnssec only).
72              
73             The module can be used to work with any provider,
74             if the requests do not use extensions and the provider does not have its own features
75              
76             It has two options: using a separate function call or working as an object
77              
78             =cut
79              
80 14     14   1132 use Digest::MD5 qw(md5_hex);
  14         30  
  14         794  
81 14     14   5183 use Time::HiRes qw(time);
  14         14801  
  14         64  
82 14     14   8157 use IO::Socket;
  14         157898  
  14         64  
83 14     14   15908 use IO::Socket::SSL;
  14         770983  
  14         125  
84              
85 14     14   2464 use strict;
  14         46  
  14         338  
86 14     14   71 use warnings;
  14         109  
  14         236111  
87              
88             # common chunks for all standard queries
89             our $epp_head = '
90             ';
91             our $epp_cont_urn =
92             'xmlns:contact="urn:ietf:params:xml:ns:contact-1.0" xsi:schemaLocation="urn:ietf:params:xml:ns:contact-1.0 contact-1.0.xsd"';
93             our $epp_host_urn =
94             'xmlns:host="urn:ietf:params:xml:ns:host-1.0" xsi:schemaLocation="urn:ietf:params:xml:ns:host-1.0 host-1.0.xsd"';
95             our $epp_dom_urn =
96             'xmlns:domain="urn:ietf:params:xml:ns:domain-1.0" xsi:schemaLocation="urn:ietf:params:xml:ns:domain-1.0 domain-1.0.xsd"';
97             our $epp_dnssec =
98             'xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:ietf:params:xml:ns:secDNS-1.1 secDNS-1.1.xsd"';
99              
100              
101             our %id = ( crID => 'creater', clID => 'owner', upID => 'updater', reID => 'requestors_id', acID => 'senders_id' );
102             our %dt = ( crDate => 'cre_date', upDate => 'upd_date', trDate => 'trans_date', exDate => 'exp_date', reDate => 'request_date', acDate => 'send_date' );
103              
104              
105             =head1 FUNCTIONS
106              
107             =head2 make_request
108              
109             See L for description
110              
111             An example of working with functions is presented in the synopsis
112              
113             Work checked on CentralNic server
114              
115             INPUT:
116              
117             action name;
118              
119             parameters of query
120              
121             OUTPUT:
122              
123             io::epp object
124              
125             or, in list context:
126              
127             ( full answer with code and message, string with code and message, io::epp object )
128              
129             An Example:
130              
131             my ( $answer, $message, $conn_object ) = make_request( 'hello', \%login_params );
132              
133             A more complete example is found in L
134              
135             =cut
136              
137             sub make_request {
138 92     92 1 83407 my ( $action, $params ) = @_;
139              
140 92         166 my ( $self, $code, $msg, $answ );
141              
142 92 100 66     455 if ( !$params->{tld} && $params->{dname} ) {
143 52         366 ( $params->{tld} ) = $params->{dname} =~ /^[0-9a-z\-]+\.(.+)$/;
144             }
145              
146 92 100       237 unless ( $params->{conn} ) {
147             # Need greate obj and login
148 46         131 ( $self, $code, $msg ) = IO::EPP::Base->new( $params );
149              
150 46 50 33     175 unless ( $code and $code == 1000 ) {
151 0         0 goto END_MR;
152             }
153             }
154             else {
155 46         88 $self = $params->{conn};
156             }
157              
158 92         174 $self->{critical_error} = '';
159              
160 92 50       381 if ( $self->can( $action ) ) {
161 92         258 ( $answ, $code, $msg ) = $self->$action( $params );
162             }
163             else {
164 0         0 $msg = "undefined command <$action>, request cancelled";
165 0         0 $code = 0;
166             }
167              
168             END_MR:
169              
170 92 50       243 $msg .= '; ' . $self->{critical_error} if $self->{critical_error};
171              
172 92         254 my $full_msg = "code: $code\nmsg: $msg";
173              
174 92 100 66     417 $answ = {} unless $answ && ref $answ;
175              
176 92         210 $answ->{code} = $code;
177 92         155 $answ->{msg} = $msg;
178              
179 92 50       449 return wantarray ? ( $answ, $full_msg, $self ) : $answ;
180             }
181              
182              
183             =head2 gen_id
184              
185             Gereration ID for contacts
186              
187             INPUT:
188              
189             no params
190              
191             OUTPUT:
192              
193             new id
194              
195             =cut
196              
197             sub gen_id {
198 0     0 1 0 my @chars = ( 'a'..'z', '0'..'9' );
199              
200 0         0 return join '', map( { $chars[ int rand( scalar @chars ) ] } 1..12 );
  0         0  
201             }
202              
203              
204             =head2 gen_pw
205              
206             Authinfo Generation
207              
208             INPUT:
209              
210             length of authInfo, default 16 symbols
211              
212             OUTPUT:
213              
214             new authInfo
215              
216             =cut
217              
218             sub gen_pw {
219 8     8 1 12 my ( $pw_length ) = @_;
220              
221 8 50       16 $pw_length = 16 unless $pw_length;
222              
223 8         12 my $pw;
224              
225 8         103 my @chars = ( '0'..'9', 'A'..'Z', 'a'..'z', '!', '@', '$', '%', '*', '_', '.', ':', '-', '=', '+', '?', '#', ',', '"', "'" );
226              
227 8         16 for ( 0..32 ) {
228 8         21 $pw = join '', map( { $chars[ int rand( scalar @chars ) ] } 1..$pw_length );
  128         202  
229              
230             # буквы, цифры и символы должны быть обязательно
231 8 50 33     88 last if ( $pw =~ /\d/ and $pw =~ /[A-Z]/ and $pw =~ /[a-z]/ and $pw =~ /[!\@\$\%\*_\.:-=\+\?#,"']/ );
      33        
      33        
232             }
233              
234 8         45 return $pw;
235             }
236              
237              
238             # Generation transaction id
239              
240             sub get_cltrid {
241 326     326 0 4209 return md5_hex( time() . $$ . rand(1000000) );
242             }
243              
244              
245             # recursive removal of utf8 flag
246              
247             sub recursive_utf8_unflaged {
248 0     0 0 0 my $root = shift;
249              
250 0 0 0     0 if ( ref $root eq 'HASH' ) {
    0          
    0          
251 0         0 foreach my $k ( keys %$root ) {
252 0         0 my $key = $k;
253 0         0 utf8::decode( $key );
254 0         0 utf8::decode( $key );
255 0         0 utf8::encode( $key );
256             # work if $root->{with_utf8_flag} ne $root->{without_utf8_flag}
257 0         0 $root->{$key} = recursive_utf8_unflaged( delete $root->{$k} ) ;
258             }
259             }
260             elsif ( ref $root eq 'ARRAY' ) {
261 0         0 $_ = recursive_utf8_unflaged($_) foreach @$root;
262             }
263             elsif ( $root && ref $root eq '' ) {
264 0         0 utf8::decode( $root );
265 0         0 utf8::decode( $root );
266 0         0 utf8::encode( $root );
267             }
268              
269 0         0 return $root;
270             }
271              
272             # clear date-time
273              
274             sub cldate {
275 51     51 0 164 my ( $dt ) = @_;
276              
277 51         206 $dt =~ s/T/ /;
278 51         170 $dt =~ s/\.\d+Z$//;
279 51         88 $dt =~ s/Z$//;
280              
281 51         188 return $dt;
282             }
283              
284              
285             =head1 METHODS
286              
287             =head2 new
288              
289             Create new IO::EPP object, аutomatically connects to the provider and logins.
290              
291             Example of a call
292              
293             # Parameters for IO::Socket::SSL
294             my %sock_params = (
295             PeerHost => 'epp.example.com',
296             PeerPort => 700,
297             SSL_key_file => $path_to_ssl_key_file,
298             SSL_cert_file => $path_to_ssl_cert_file,
299             Timeout => 30,
300             );
301              
302             # initialization of an object, during which login is called
303             my $o = IO::EPP::Base->new( {
304             sock_params => \%sock_params,
305             user => $login_name,
306             pass => $login_password,
307             log_name => '/var/log/comm_epp_registry_name',
308             } );
309              
310             # call check of domains
311             my ( $answ, $code, $msg ) = $o->check_domains( { domains => [ 'kalinka.realty' ] } );
312              
313             undef $o; # call logout() и DESTROY() of object
314              
315             INPUT:
316              
317             package name, parameters.
318              
319             Connection parameters:
320              
321             C – login;
322              
323             C – password;
324              
325             C – zone for providers that have a binding in it, for example, verisign;
326              
327             C – server name if the registry has different servers with different extensions, for example, pir/afilias for afilias;
328              
329             C – hashref with L parameters;
330              
331             C – use a real connection or registry emulator.
332              
333             Parameters for logging:
334              
335             C – do not write anything to the log;
336              
337             C – write log in this file, not in STDOUT;
338              
339             C – ref on functions to write to the log.
340              
341             OUTPUT:
342              
343             io::epp object or array ( object, login code, login message )
344              
345             If the connection or authorization failed, the response will contain zero instead of an object
346              
347             =cut
348              
349             sub new {
350 77     77 1 2755 my ( $package, $params ) = @_;
351              
352 77         170 my ( $self, $code, $msg );
353              
354 77         0 my $sock;
355              
356 77         135 my $sock_params = delete $params->{sock_params};
357              
358 77         133 my $test = delete $params->{test_mode};
359              
360 77 50       149 if ( $test ) {
361 77         221 $sock = $sock_params->{PeerHost} . ':' . $sock_params->{PeerPort};
362             }
363             else {
364             $sock = IO::Socket::SSL->new(
365             PeerPort => 700,
366             Timeout => 30,
367 0         0 %{$sock_params},
  0         0  
368             );
369             }
370              
371 77 50       171 unless ( $sock ) {
372 0         0 $msg = "can not connect";
373 0         0 $code = 0;
374              
375 0         0 goto ERR;
376             }
377              
378             $self = bless {
379             sock => $sock,
380             user => delete $params->{user},
381             tld => $params->{tld} || '',
382             server => delete $params->{server} || '',
383             #launch => $params->{launch} || '',
384             log_name => delete $params->{log_name},
385             log_fn => delete $params->{log_fn},
386 77   100     803 no_log => delete $params->{no_log} || 0,
      100        
      50        
387             test => $test,
388             critical_error => undef,
389             }, $package;
390              
391 77         233 $self->set_urn();
392              
393 77         217 $self->set_log_vars( $params );
394              
395 77         282 $self->epp_log( "Connect to $$sock_params{PeerHost}:$$sock_params{PeerPort}\n" );
396              
397 77         169 my $hello = $self->req();
398              
399 77 50 33     356 if ( !$hello || $self->{critical_error} ) {
400 0         0 $msg = "Can't get greeting";
401 0 0       0 $msg .= '; ' . $self->{critical_error} if $self->{critical_error};
402 0         0 $code = 0;
403              
404 0         0 goto ERR;
405             }
406              
407 77         166 my ( $svcs, $extension ) = ( '', '' );
408              
409 77 100       354 if ( ref( $self ) =~ /IO::EPP::Base/ ) {
410 47 50       224 if ( $hello =~ /urn:ietf:params:xml:ns:contact-1.0/ ) {
411 47         93 $svcs .= '
412             urn:ietf:params:xml:ns:contact-1.0';
413             }
414 47 50       163 if ( $hello =~ /urn:ietf:params:xml:ns:domain-1.0/ ) {
415 47         80 $svcs .= '
416             urn:ietf:params:xml:ns:domain-1.0';
417             }
418 47 50       139 if ( $hello =~ /urn:ietf:params:xml:ns:host-1.0/ ) {
419             # drs.ua not want host
420 47         75 $svcs .= '
421             urn:ietf:params:xml:ns:host-1.0';
422             }
423              
424 47 50       147 if ( $hello =~ /urn:ietf:params:xml:ns:secDNS-1.1/ ) {
425 47         75 $extension .= '
426             urn:ietf:params:xml:ns:secDNS-1.1';
427             }
428             }
429              
430             # have a connection, can log in
431 77         273 my ( undef, $c, $m ) = $self->login( delete $params->{pass}, $svcs, $extension ); # no save passwd in object
432              
433 77 50 33     391 if ( $c and $c == 1000 ) {
434 77 100       341 return wantarray ? ( $self, $c, $m ) : $self;
435             }
436              
437 0   0     0 $msg = ( $m || '' ) . $self->{critical_error};
438 0   0     0 $code = $c || 0;
439              
440 0 0       0 ERR:
441             return wantarray ? ( 0, $code, $msg ) : 0;
442             }
443              
444              
445             sub set_urn {
446             $_[0]->{urn} = {
447 77     77 0 320 head => $IO::EPP::Base::epp_head,
448             cont => $IO::EPP::Base::epp_cont_urn,
449             host => $IO::EPP::Base::epp_host_urn,
450             dom => $IO::EPP::Base::epp_dom_urn,
451             };
452             }
453              
454              
455             # Set name for log
456              
457             sub set_log_vars {
458 77     77 0 136 my ( $self, $params ) = @_;
459              
460 77 50       188 $self->{log_name} = delete $params->{log_name} if $params->{log_name};
461 77 50       215 $self->{log_fn} = delete $params->{log_fn} if $params->{log_fn};
462             }
463              
464              
465             =head2 epp_log
466              
467             Writes data to the log or calls the function specified when creating the object
468              
469             By default, the log is written: date and time, pid of the process, name and body of the request:
470              
471             Thu Jan 1 01:00:00 1111
472             pid: 12345
473             check_domains request:
474            
475            
476            
477            
478            
479             xyz.comcom.xyzreged.xyz
480            
481            
482             50df482a1e928a00fa0e7fce3fe68f0f
483            
484            
485              
486             Thu Feb 2 02:02:22 2222
487             pid: 12345
488             check_domains answer:
489            
490            
491            
492            
493             Command completed successfully.
494            
495            
496             xyz.com
497             com.xyz
498             reged.xyzin use
499            
500             50df482a1e928a00fa0e7fce3fe68f0fTEST-2979E52890117206AAA1639725F4E862
501            
502            
503            
504              
505             =cut
506              
507             sub epp_log {
508 814     814 1 1226 my ( $self, $string ) = @_;
509              
510 814 50       1809 return if $self->{no_log};
511              
512 0         0 $string = "pid: $$\n" . $string;
513              
514 0 0       0 if ( $self->{log_fn} ) {
    0          
515 0         0 &{$self->{log_fn}}( $string );
  0         0  
516             }
517             elsif ( $self->{log_name} ) {
518 0         0 my $fh;
519              
520 0 0       0 if ( $self->{log_fh} ) {
521 0         0 $fh = $self->{log_fh};
522             }
523             else{
524 0 0       0 open( $fh, '>>', $self->{log_name} ) or die "Can't open $self->{log_name}: $!\n";
525              
526 0         0 $self->{log_fh} = $fh;
527             }
528              
529 0         0 print $fh scalar(localtime) . "\n$string\n\n"; # if `print( $self->{log_fh} $string );` that get error `(Missing operator before $string?)`
530             }
531             else {
532 0         0 print scalar(localtime) . "\n$string\n\n";
533             }
534             }
535              
536              
537             =head2 req_test
538              
539             Used instead of L in test mode
540              
541             =cut
542              
543             sub req_test {
544 233     233 1 387 my ( $self, $out_data, $info ) = @_;
545              
546 233         2178 require IO::EPP::Test::Base;
547              
548 233 100       938 $self->epp_log( "$info request:\n$out_data" ) if $out_data;
549              
550 233         428 my $answ;
551             eval{
552 233         614 $answ = IO::EPP::Test::Base::req( @_ );
553 233         579 1;
554             }
555 233 50       378 or do {
556 0         0 $self->{critical_error} = "$info req error: $@";
557 0         0 return;
558             };
559              
560 233         909 $self->epp_log( "$info answer:\n$answ" );
561              
562 233         566 return $answ;
563             }
564              
565              
566             =head2 req
567              
568             Request to registry
569              
570             INPUT:
571              
572             C – body of request;
573              
574             C – name of request for log.
575              
576             OUTPUT:
577              
578             answer from registry.
579              
580             =cut
581              
582             sub req {
583 407     407 1 690 my ( $self, $out_data, $info ) = @_;
584              
585 407         620 $self->{critical_error} = '';
586              
587 407   100     801 $info ||= '';
588              
589 407 50       1206 return $self->req_test( $out_data, $info ) if $self->{test};
590              
591 0         0 my $THRESHOLD = 100000000;
592              
593 0 0       0 if ( $out_data ) {
594 0         0 my $d = $out_data;
595             # Remove password, authinfo from log
596 0         0 $d =~ s/[^<>]+<\/pw>/xxxxx<\/pw>/;
597              
598 0         0 $self->epp_log( "$info request:\n$d" );
599             }
600              
601 0         0 my $in_data = '';
602 0         0 my $start_time = time;
603              
604             eval{
605 0     0   0 local $SIG{ALRM} = sub { die "connection timeout\n" };
  0         0  
606              
607 0         0 alarm 120;
608              
609 0 0       0 if ( $out_data ) {
610             # https://rt.cpan.org/Ticket/Display.html?id=98368
611             # https://rt.cpan.org/Ticket/Display.html?id=98372
612 0         0 utf8::downgrade( $out_data );
613              
614 0         0 my $len = length( $out_data ) + 4;
615 0         0 my $pk_data_size = pack( 'N', $len );
616              
617 0         0 my $a_out = $self->{sock}->print( $pk_data_size . $out_data );
618 0         0 $self->{sock}->flush();
619              
620 0 0       0 die "data write failed" unless $a_out;
621             };
622              
623             # header - 4 bytes Nxxx with size
624 0         0 my $hdr;
625 0 0       0 unless ( defined( $self->{sock}->read( $hdr, 4 ) ) ) {
626 0         0 die "closed connection\n";
627             }
628              
629 0   0     0 my $data_size = ( unpack( 'N', $hdr ) || 0 ) - 4;
630              
631 0 0       0 die "closed connection\n" if $data_size < 0;
632              
633 0 0       0 die "data length is zero\n" unless $data_size;
634              
635 0 0       0 die "data length is $data_size which exceeds $THRESHOLD\n" if $data_size > $THRESHOLD;
636              
637             # Read data block
638 0         0 my $buf;
639 0         0 my $wait_cnt = 0;
640              
641 0         0 while ( length( $in_data ) < $data_size ) {
642 0         0 $buf = '';
643 0         0 $self->{sock}->read( $buf, ( $data_size - length( $in_data ) ));
644              
645 0 0       0 if ( length( $buf ) == 0 ) {
646 0 0       0 if ( $wait_cnt < 3 ) {
647             # part of the data may come with a long delay when saving the connection
648             # this problem is observed in corenic and drs
649 0         0 $wait_cnt++;
650 0         0 sleep 1;
651 0         0 redo;
652             }
653             else {
654             # it is likely that the socket has closed
655 0         0 last;
656             }
657             }
658              
659 0         0 $in_data .= $buf;
660             }
661              
662             # recheck, because something could not reach or stop at \0
663 0         0 my $l = length( $in_data );
664 0 0       0 die "data read failed: readed $l, need $data_size\ndata: $in_data" if $l != $data_size;
665              
666 0         0 alarm 0;
667              
668 0         0 1;
669 0 0       0 } or do {
670 0         0 my $err = $@;
671              
672 0         0 alarm 0;
673              
674 0         0 my $req_time = sprintf( '%0.4f', time - $start_time );
675 0         0 $self->epp_log( "req_time: $req_time\n$info req error: $err" );
676              
677 0         0 $self->{critical_error} = "req error: $err";
678              
679 0         0 return;
680             };
681              
682 0         0 my $req_time = sprintf( '%0.4f', time - $start_time );
683 0         0 $self->epp_log( "req_time: $req_time\n$info answer:\n$in_data" );
684              
685 0         0 return $in_data;
686             }
687              
688              
689             =head2 simple_request
690              
691             Universal handler for simple answer
692              
693             INPUT:
694              
695             request body;
696              
697             request name;
698              
699             check or not epp poll, default is 0
700              
701             OUTPUT:
702              
703             answer, may contain the object's name, id, creation and/or expiration date, client-side transaction id, and registry id;
704              
705             answer code;
706              
707             answer message, if there is an error in the response, an additional reason for the error may be passed along with the message.
708              
709             An Example:
710              
711             # answer for create_contact:
712              
713             {
714             'msg' => 'Command completed successfully.',
715             'cont_id' => 'sxsup8ehs000',
716             'cre_date' => '2020-01-01 01:01:01',
717             'cltrid' => 'd0a528a4816ea4e16c3f56e25bf11111',
718             'code' => 1000,
719             'svtrid' => 'CNIC-22E5B2CBBD6C04169AEC9228FB0677FA173D76487AF8BA8734AF3C11111'
720             };
721              
722             # answer with error, "1.2.3.4 addr not found" is reason:
723              
724             {
725             'msg' => 'Parameter value policy error; 1.2.3.4 addr not found',
726             'cltrid' => 'd0e2a9c2af427264847b0a6e59b60000',
727             'code' => 2306,
728             'svtrid' => '4586654601-1579115463111'
729             };
730              
731             =cut
732              
733             sub simple_request {
734 211     211 1 420 my ( $self, $body, $info, $check_queue_msgs ) = @_;
735              
736 211 50       432 unless ( $body ) {
737 0 0       0 return wantarray ? ( 0, 0, 'no query' ) : 0 ;
738             }
739              
740 211         420 my $content = $self->req( $body, $info );
741              
742 211 50 33     1257 if ( $content && $content =~ // ) {
743 211         681 my $code = $1 + 0;
744              
745 211         305 my $msg = '';
746 211 50       1675 if ( $content =~ /]*>(.+)<\/msg>.+\/result>/s ) {
747 211         455 $msg = $1;
748             }
749              
750 211 100 66     732 if ( $code == 1001 or $code >= 2000 ) {
751             # 1001 -- pendingAction
752             # 2000+ - May be an addition to the error, It is the inventions of different providers
753 90         330 my $reason = join( ';', $content =~ /]*>([^<>]+)<\/reason>/g );
754              
755 90 100       200 $msg .= "; " . $reason if $reason;
756              
757 90         189 my ( $xcp ) = $content =~ /([^<>]+)<\/oxrs:xcp>/;
758              
759 90 50       172 $msg .= "; " . $xcp if $xcp;
760              
761 90         406 my ( $text ) = $content =~ /([^<>]+)<\/text>.+\/result>/s;
762              
763 90 50       200 $msg .= "; " . $text if $text;
764             }
765              
766             # And check epp poll
767 211         263 my $queue_msgs = '';
768              
769 211 50 33     759 if ( $check_queue_msgs and $content =~ // ) {
770 0         0 $queue_msgs = { count => $1 , id => $2 };
771             }
772              
773 211         328 my $info = {};
774              
775             # dates
776 211         720 foreach my $k ( keys %dt ) {
777 1266 100       19712 if ( $content =~ m|<[a-z]+:$k>([^<>]+)| ) {
778 21         63 $info->{$dt{$k}} = cldate( $1 );
779             }
780             }
781              
782 211 100       694 if ( $content =~ m{([^<>]+)} ) {
783 7         22 $info->{cont_id} = $1;
784             }
785              
786 211 100       519 if ( $content =~ m{<(host|domain):name>([^<>]+)} ) {
787 11         45 my %r = ( host => 'ns', domain => 'dname' );
788              
789 11         46 $info->{$r{$1}} = $2;
790             }
791              
792             # This is needed to monitor deferred actions at some providers
793 211         1122 ( $info->{cltrid} ) = $content =~ /([0-9A-Za-z\-]+)<\/clTRID>/;
794 211         858 ( $info->{svtrid} ) = $content =~ /([0-9A-Za-z\-]+)<\/svTRID>/;
795              
796 211 50       1205 return wantarray ? ( $info, $code, $msg, $queue_msgs ) : $info;
797             }
798              
799 0 0       0 return wantarray ? ( 0, 0, 'empty answer' ) : 0 ;
800             }
801              
802             =head2 login
803              
804             Authorization on the server.
805             The function is automatically called from new.
806             A separate call is only needed to change the password.
807              
808             INPUT:
809              
810             password;
811              
812             addition standard parameters (xxxxx-1.0);
813              
814             extensions (yyyyyy-1.0);
815              
816             new password if need.
817              
818             OUTPUT: see L.
819              
820             =cut
821              
822             sub login {
823 77     77 1 190 my ( $self, $pw, $svcs, $ext, $new_pw ) = @_;
824              
825 77 50       185 return ( 0, 0, 'no user' ) unless $self->{user};
826 77 50       146 return ( 0, 0, 'no passwd' ) unless $pw;
827              
828 77   50     144 $svcs ||= ''; # addition standard parameters
829 77   50     143 $ext ||= ''; # extension
830              
831 77 50       154 if ( $ext ) {
832 77         173 $ext = "\n $ext\n ";
833             }
834              
835 77         114 my $npw = '';
836 77 50       155 if ( $new_pw ) {
837 0         0 $npw = "\n $new_pw";
838             }
839              
840 77         145 my $cltrid = get_cltrid();
841              
842 77         425 my $body = <
843             $$self{urn}{head}
844            
845            
846             $$self{user}
847             $pw$npw
848            
849             1.0
850             en
851            
852             $svcs$ext
853            
854            
855             $cltrid
856            
857            
858             LOGIN
859              
860 77         180 return $self->simple_request( $body, 'login' );
861             }
862              
863              
864             =head2 hello
865              
866             Get greeting, ping analog.
867              
868             No input parameters.
869              
870             Sample response:
871              
872             {
873             'msg' => '
874            
875             CentralNic EPP server EPP.CENTRALNIC.COM
876             2020-20-20T20:20:20.0Z
877            
878             1.0en
879             urn:ietf:params:xml:ns:domain-1.0
880             urn:ietf:params:xml:ns:contact-1.0
881             urn:ietf:params:xml:ns:host-1.0
882            
883             urn:ietf:params:xml:ns:rgp-1.0
884             urn:ietf:params:xml:ns:secDNS-1.1
885             urn:ietf:params:xml:ns:idn-1.0
886             urn:ietf:params:xml:ns:fee-0.4
887             urn:ietf:params:xml:ns:fee-0.5
888             urn:ietf:params:xml:ns:launch-1.0
889             urn:ietf:params:xml:ns:regtype-0.1
890             urn:ietf:params:xml:ns:auxcontact-0.1
891             urn:ietf:params:xml:ns:artRecord-0.2
892             http://www.nic.coop/contactCoopExt-1.0
893            
894            
895            
896            
897            
898            
899            
900            
901            
902             ',
903             'code' => 1000
904             };
905              
906             =cut
907              
908             sub hello {
909 4     4 1 3814 my ( $self ) = @_;
910              
911 4         17 my $body = <
912             $$self{urn}{head}
913            
914            
915             HELLO
916              
917 4         27 my $content = $self->req( $body, 'hello' );
918              
919 4 50 33     36 unless ( $content && $content =~ /greeting/ ) {
920 0 0       0 return wantarray ? ( 0, 0, 'no greeting' ) : 0;
921             }
922              
923 4         17 my $info = {
924             code => 1000,
925             msg => $content,
926             };
927              
928 4 50       26 return wantarray ? ( $info, 1000, $content ) : $info;
929             }
930              
931              
932             =head2 check_contacts
933              
934             Check whether there are contacts with such IDs
935              
936             INPUT:
937             params with key:
938             C -- arrayref on contact id list.
939              
940             Request:
941              
942             my ( $answ, $msg ) = make_request( 'check_contacts', { contacts => [ 'H1234567', 'nfjkrek-fre8fm' ] } );
943              
944             print Dumper $answ;
945              
946             Answer:
947              
948             $VAR1 = {
949             'msg' => 'Command completed successfully.',
950             'nfjkrek-fre8fm' => {
951             'avail' => '1'
952             },
953             'H1234567' => {
954             'avail' => '0'
955             },
956             'code' => '1000'
957             };
958              
959             =cut
960              
961             sub check_contacts {
962 1     1 1 4 my ( $self, $params ) = @_;
963              
964 1 50 50     9 return ( 0, 0, 'no contacts' ) unless $params->{contacts} && scalar( @{$params->{contacts}} );
  1         5  
965              
966 1         3 my $contacts = $params->{contacts};
967              
968 1         3 my $conts = '';
969              
970 1         3 foreach my $cont ( @$contacts ) {
971 2         6 $conts .= "$cont";
972             }
973              
974 1   50     6 my $ext = $$params{extension} || '';
975              
976 1 50       3 $ext = "\n \n$ext " if $ext;
977              
978 1         2 my $cltrid = get_cltrid();
979              
980 1         7 my $body = <
981             $$self{urn}{head}
982            
983            
984            
985             $conts
986            
987             $ext
988             $cltrid
989            
990            
991             CHECKCONT
992              
993 1         4 my $content = $self->req( $body, 'check_contacts' );
994              
995 1 50       7 if ( $content =~ // ) {
996 1         4 my $code = $1 + 0;
997              
998 1         3 my $msg = '';
999 1 50       15 if ( $content =~ /]*>(.+)<\/msg>.+\/result>/s ) {
1000 1         3 $msg = $1;
1001             }
1002              
1003 1         9 my @aa = $content =~ /([^<>]+<\/contact:id>)/g;
1004              
1005 1         11 my %answ;
1006 1         3 foreach my $a ( @aa ) {
1007 2 50       13 if ( $a =~ /([^<>]+)<\/contact:id>/ ) {
1008 2         20 $answ{$2} = { avail => $1 };
1009             }
1010             }
1011              
1012 1 50       9 return wantarray ? ( \%answ, $code, $msg ) : \%answ;
1013             }
1014              
1015 0 0       0 return wantarray ? ( 0, 0, 'empty answer' ) : 0 ;
1016             }
1017              
1018             =head2 cont_to_xml
1019              
1020             Covertor contact user date to epp xml
1021              
1022             for create/update_contact functions
1023              
1024             =cut
1025              
1026             sub cont_to_xml {
1027 13     13 1 22 my ( $self, $params ) = @_;
1028              
1029 13 100 100     57 unless ( $$params{'int'} && $$params{'loc'} ) {
1030             # Set default is 'int'
1031 8         21 foreach my $f ( 'name', 'first_name', 'last_name', 'patronymic', 'family_name', 'company', 'addr', 'city', 'state', 'postcode', 'country_code' ) {
1032 88 100       161 $$params{'int'}{$f} = delete $$params{$f} if defined $$params{$f};
1033             }
1034             }
1035              
1036 13         26 my $postalinfo = '';
1037 13         23 foreach my $type ( 'int', 'loc' ) { # legal - in children modules
1038 26 100       55 next unless $$params{$type};
1039              
1040             # need_name=1 for creation - always, for update:
1041             # According to the standard at change of id the name does not change ( https://tools.ietf.org/html/rfc5733#page-23 ),
1042             # but some providers of it do not know,
1043             # at the same time, they have no documentation, but send all to read RFC, example, drs.ua
1044 18         24 my $name = '';
1045 18 100       37 if ( $$params{need_name} ) {
1046 16 50       79 if ( $$params{$type}{name} ) {
1047 0         0 $name = $$params{$type}{name};
1048             }
1049             else {
1050 16 50       55 $name = $$params{$type}{first_name} if $$params{$type}{first_name};
1051              
1052 16 50 33     61 if ( $$params{$type}{last_name} && $$params{$type}{family_name} ) {
1053 0 0       0 if ( $$params{$type}{last_name} ) {
1054 0 0       0 $name .= ' ' if $name;
1055 0         0 $name .= $$params{$type}{last_name};
1056             }
1057              
1058 0 0       0 if ( $$params{$type}{patronymic} ) {
1059 0 0       0 $name .= ' ' if $name;
1060 0         0 $name .= $$params{$type}{patronymic};
1061             }
1062              
1063 0 0       0 if ( $$params{$type}{family_name} ) {
1064 0 0       0 $name .= ' ' if $name;
1065 0         0 $name .= $$params{$type}{family_name};
1066             }
1067             }
1068             else {
1069             # family_name eq last_name
1070 16 50       31 if ( $$params{$type}{patronymic} ) {
1071 0 0       0 $name .= ' ' if $name;
1072 0         0 $name .= $$params{$type}{patronymic};
1073             }
1074              
1075 16 50 33     44 if ( $$params{$type}{last_name} || $$params{$type}{family_name} ) {
1076 16 50       33 $name .= ' ' if $name;
1077 16   33     43 $name .= $$params{$type}{last_name} || $$params{$type}{family_name};
1078             }
1079             }
1080              
1081             }
1082              
1083 16 50       42 $name = "\n $name" if $name;
1084             }
1085              
1086 18         26 my $org;
1087 18 100       34 if ( $$params{$type}{org} ) {
1088 10         20 $$params{$type}{org} =~ s/&/&/g;
1089              
1090 10         20 $org = "$$params{$type}{org}";
1091             }
1092             else {
1093 8         14 $org = '';
1094             }
1095              
1096              
1097 18         30 my $street = '';
1098              
1099 18 100       49 $$params{$type}{addr} = [ $$params{$type}{addr} ] unless ref $$params{$type}{addr};
1100              
1101 18         25 foreach my $s ( @{$$params{$type}{addr}} ) {
  18         50  
1102 23 100       40 $street .= "\n " if $street;
1103 23         52 $street .= "$s";
1104             }
1105              
1106 18 50       54 my $sp = $$params{$type}{'state'} ? "$$params{$type}{state}" : '' ;
1107 18 50       51 my $pc = $$params{$type}{postcode} ? "$$params{$type}{postcode}" : '' ;
1108              
1109 18         86 $postalinfo .= qq|
1110             $name
1111             $org
1112            
1113             $street
1114             $$params{$type}{city}
1115             $sp
1116             $pc
1117             $$params{$type}{country_code}
1118            
1119             |;
1120             }
1121              
1122             # voice / fax Extension is disabled
1123 13         24 my $voice = '';
1124 13 100       38 $$params{phone} = [ $$params{phone} ] unless ref $$params{phone};
1125 13         22 foreach my $s ( @{$$params{phone}} ) {
  13         24  
1126 17 100       31 $voice .= "\n " if $voice;
1127 17         37 $voice .= "$s";
1128             }
1129              
1130 13 50       33 my $fax = $$params{fax} ? "$$params{fax}" : '';
1131              
1132 13         19 my $email = '';
1133 13 100       39 $$params{email} = [ $$params{email} ] unless ref $$params{email};
1134 13         19 foreach my $s ( @{$$params{email}} ) {
  13         26  
1135 13 50       25 $email .= "\n " if $email;
1136 13         26 $email .= "$s";
1137             }
1138              
1139 13 50       46 my $pw = $$params{authinfo} ? "$$params{authinfo}" : '' ;
1140              
1141 13   50     58 $$params{pp_ext} ||= '';
1142              
1143 13         62 my $textcont = qq|$postalinfo
1144             $voice
1145             $fax
1146             $email
1147            
1148             $pw
1149             $$params{pp_ext}|;
1150              
1151 13         31 return $textcont;
1152             }
1153              
1154             =head2 create_contact_ext
1155              
1156             Create contact extensions,
1157             for overwriting in child classes
1158              
1159             =cut
1160              
1161             sub create_contact_ext {
1162 11     11 1 25 return '';
1163             }
1164              
1165             =head2 create_contact
1166              
1167             Register a contact
1168              
1169             INPUT:
1170              
1171             Hash with parameters:
1172              
1173             C – some providers create contact ID automatically;
1174              
1175             C or C, C, C, C – full name in one field or first, last, patronymic, family names separately;
1176              
1177             C – organization if necessary, some registries require a zero-length string, while others require C;
1178              
1179             C – street, house, building, apartment;
1180              
1181             C – city, town;
1182              
1183             C – state, region, province, republic, optional field;
1184              
1185             C – zip code;
1186              
1187             C – two-character country code;
1188              
1189             C – the phone number in international format;
1190              
1191             C – usually only required for legal entities;
1192              
1193             C;
1194              
1195             C – the key is to transfer your contacts, but usually the contacts are transferred automatically together with a domain.
1196              
1197             If only the C type of contacts is passed, it can be omitted.
1198              
1199             OUTPUT: see L.
1200              
1201             An Example, one type (by default this is C):
1202              
1203             ( $answ, $code, $msg ) = $conn->create_contact(
1204             {
1205             cont_id => '123qwerty',
1206             first_name => 'Test',
1207             last_name => 'Testov',
1208             org => 'Private Person',
1209             addr => 'Vagnera 11-22',
1210             city => 'Donetsk',
1211             state => 'Donetskaya',
1212             postcode => '83061',
1213             country_code => 'DN',
1214             phone => '+380.501234567',
1215             fax => '',
1216             email => 'reg1010@yandex.com',
1217             authinfo => 'Q2+qqqqqqqqqqqqqqqqqqqqqqqqqq',
1218             },
1219             );
1220              
1221             Contact with two types
1222              
1223             ( $answ, $code, $msg ) = $conn->create_contact(
1224             {
1225             cont_id => '123qwerty',
1226             int => {
1227             first_name => 'Test',
1228             last_name => 'Testov',
1229             org => 'Private Person',
1230             addr => 'Vagnera 11-22',
1231             city => 'Donetsk',
1232             state => 'Donetskaya',
1233             postcode => '83061',
1234             country_code => 'DN',
1235             },
1236             loc => {
1237             first_name => 'Тест',
1238             last_name => 'Тестов',
1239             org => 'Частное лицо',
1240             addr => 'Вагнера 11/22',
1241             city => 'Донецк',
1242             state => 'Донецкая обл.',
1243             postcode => '83061',
1244             country_code => 'DN',
1245             },
1246             phone => '+380.501234567',
1247             fax => '',
1248             email => 'reg1010@yandex.com',
1249             authinfo => 'Q2+qqqqqqqqqqqqqqqqqqqqqqqqqq',
1250             }
1251             );
1252              
1253             =cut
1254              
1255             sub create_contact {
1256 11     11 1 26 my ( $self, $params ) = @_;
1257              
1258 11 50       30 return ( 0, 0, 'no params' ) unless ref $params;
1259              
1260 11 50       29 return ( 0, 0, 'no cont_id' ) unless $params->{cont_id};
1261              
1262 11         24 $params->{need_name} = 1;
1263              
1264 11         28 my $textcont = $self->cont_to_xml( $params );
1265              
1266 11   50     39 my $ext = $params->{extension} || '';
1267              
1268 11         61 $ext .= $self->create_contact_ext( $params );
1269              
1270 11 50       23 if ( $ext ) {
1271 0         0 $ext = "\n \n$ext ";
1272             }
1273              
1274 11         19 my $cltrid = get_cltrid();
1275              
1276 11         90 my $body = <
1277             $$self{urn}{head}
1278            
1279            
1280            
1281             $$params{cont_id}$textcont
1282            
1283             $ext
1284             $cltrid
1285            
1286            
1287             CRECONT
1288              
1289 11         27 return $self->simple_request( $body, 'create_contact' );
1290             }
1291              
1292              
1293             =head2 cont_from_xml
1294              
1295             Covertor contact epp xml data to hash
1296              
1297             for get_contact_info, overwritten in some child modules
1298              
1299             =cut
1300              
1301             sub cont_from_xml {
1302 3     3 1 12 my ( undef, $rdata ) = @_;
1303              
1304 3         6 my %cont;
1305              
1306 3         17 ( $cont{cont_id} ) = $rdata =~ /([^<>]+)<\/contact:id>/;
1307              
1308 3         31 ( $cont{roid} ) = $rdata =~ /([^<>]+)<\/contact:roid>/;
1309              
1310 3         11 my @atypes = ( 'int', 'loc' );
1311 3         12 foreach my $atype ( @atypes ) {
1312 6         152 my ( $postal ) = $rdata =~ /(.+?)<\/contact:postalInfo>/s;
1313              
1314 6 100       19 next unless $postal;
1315              
1316 5         26 ( $cont{$atype}{name} ) = $postal =~ /([^<>]+)<\/contact:name>/;
1317              
1318 5 100       23 if ( $rdata =~ /([^<>]*)<\/contact:org>/ ) {
1319 4         13 $cont{$atype}{org} = $1;
1320 4         10 $cont{$atype}{org} =~ s/&/&/g;
1321             }
1322              
1323              
1324 5         33 $cont{$atype}{addr} = join(', ', $postal =~ /([^<>]*)<\/contact:street>/ );
1325              
1326 5         22 ( $cont{$atype}{city} ) = $postal =~ /([^<>]*)<\/contact:city>/;
1327              
1328 5         24 ( $cont{$atype}{'state'} ) = $postal =~ /([^<>]*)<\/contact:sp>/;
1329              
1330 5         19 ( $cont{$atype}{postcode} ) = $postal =~ /([^<>]*)<\/contact:pc>/;
1331              
1332 5         21 ( $cont{$atype}{country_code} ) = $postal =~ /([A-Za-z]+)<\/contact:cc>/;
1333 5         16 $cont{$atype}{country_code} = uc $cont{$atype}{country_code};
1334             }
1335              
1336 3         27 $cont{phone} = [ $rdata =~ /]*>([0-9+.]*)<\/contact:voice>/g ];
1337              
1338 3         13 $cont{fax} = [ $rdata =~ /]*>([0-9+.]*)<\/contact:fax>/g ];
1339              
1340 3         19 $cont{email} = [ $rdata =~ /([^<>]+)<\/contact:email>/g ];
1341              
1342             #
1343 3         17 my @ss = $rdata =~ //g;
1344             # No changes pending
1345 3         10 my @aa = $rdata =~ /]+>[^<>]+<\/contact:status>/g;
1346 3 50       8 if ( scalar @aa ) {
1347 0         0 foreach my $row ( @aa ) {
1348 0 0       0 if ( $row =~ /([^<>]+)<\/contact:status>/ ) {
1349 0         0 $cont{statuses}{$1} = $2;
1350             }
1351             }
1352             }
1353             else {
1354 3         12 $cont{statuses}{$_} = '+' for @ss;
1355             }
1356              
1357 3 50       13 if ( $rdata =~ /\s*(.+?)<\/contact:pw>/s ) {
1358 0         0 $cont{authinfo} = $1;
1359             }
1360              
1361 3 50       14 if ( $rdata =~ // ) {
1362 3 50       12 $cont{pp_flag} = $1 ? 0 : 1;
1363             }
1364              
1365             # owner, ...
1366 3         13 foreach my $k ( keys %id ) {
1367 15 100       309 if ( $rdata =~ /([^<>]+)<\/contact:$k>/ ) {
1368 9         40 $cont{$id{$k}} = $1;
1369             }
1370             }
1371              
1372             # dates
1373 3         12 foreach my $k ( keys %dt ) {
1374 18 100       273 if ( $rdata =~ /([^<>]+)<\/contact:$k>/ ) {
1375 6         28 $cont{$dt{$k}} = $1;
1376              
1377 6         26 $cont{$dt{$k}} =~ s/T/ /;
1378 6         25 $cont{$dt{$k}} =~ s/\.\d+Z$//;
1379 6         17 $cont{$dt{$k}} =~ s/Z$//;
1380             }
1381             }
1382              
1383 3         14 return \%cont;
1384             }
1385              
1386              
1387             =head2 get_contact_ext
1388              
1389             Providers extension, replaced in provider modules
1390              
1391             Returns an empty hashref here
1392              
1393             =cut
1394              
1395             sub get_contact_ext {
1396 0     0 1 0 return {};
1397             }
1398              
1399              
1400             =head2 get_contact_info
1401              
1402             Get information on the specified contact
1403              
1404             INPUT:
1405              
1406             C – contact id
1407              
1408             C – epp extensions in xml
1409              
1410             An Example:
1411              
1412             my ( $answer, $code, $msg ) = $conn->get_contact_info( { cont_id => 'H12345' } );
1413              
1414             # $answer:
1415              
1416             {
1417             'owner' => 'H2220222',
1418             'int' => {
1419             'city' => 'Moscow',
1420             'org' => 'My Ltd',
1421             'country_code' => 'RU',
1422             'name' => 'Igor Igorev',
1423             'postcode' => '123456',
1424             'addr' => 'Igoreva str, 3',
1425             'state' => 'Igorevskya obl.'
1426             },
1427             'roid' => 'C2222888-CNIC',
1428             'cre_date' => '2012-12-12 12:12:12',
1429             'phone' => [
1430             '+7.4952334455'
1431             ],
1432             'pp_flag' => 1,
1433             'email' => [
1434             'mail@igor.ru'
1435             ],
1436             'upd_date' => '2012-12-12 12:12:12',
1437             'cont_id' => 'H12345',
1438             'fax' => [
1439             '+7.4952334455'
1440             ],
1441             'creater' => 'H2220222',
1442             'statuses' => {
1443             'serverDeleteProhibited' => '+',
1444             'serverTransferProhibited' => '+',
1445             'linked' => '+'
1446             }
1447             };
1448              
1449             =cut
1450              
1451             sub get_contact_info {
1452 4     4 1 9 my ( $self, $params ) = @_;
1453              
1454 4 50       10 return ( 0, 0, 'no cont_id' ) unless $$params{cont_id};
1455              
1456 4   50     19 my $ext = $$params{extension} || '';
1457              
1458 4 50       8 if ( $ext ) {
1459 0         0 $ext = "\n \n$ext ";
1460             }
1461              
1462 4         10 my $cltrid = get_cltrid();
1463              
1464 4         26 my $body = <
1465             $$self{urn}{head}
1466            
1467            
1468            
1469             $$params{cont_id}
1470            
1471             $ext
1472             $cltrid
1473            
1474            
1475             CONTINFO
1476              
1477 4         10 my $content = $self->req( $body, 'get_contact_info' );
1478              
1479 4 50       24 if ( $content =~ /result code=['"](\d+)['"]/ ) {
1480 4         15 my $rcode = $1 + 0;
1481              
1482 4         7 my $msg = '';
1483 4 50       119 if ( $content =~ /]*>(.+)<\/msg>.+\/result>/s ) {
1484 4         12 $msg = $1;
1485             }
1486              
1487 4         8 my $cont;
1488              
1489             # take the main part and disassemble
1490 4 100       24 if ( $content =~ /(.+)<\/resData>/s ) {
1491 3         19 $cont = $self->cont_from_xml( $1 );
1492             }
1493             else {
1494 1 50       7 return wantarray ? ( 0, $rcode, $msg ) : 0 ;
1495             }
1496              
1497 3 50       16 if ( $content =~ /(.+)<\/extension>/s ) {
1498 0         0 my $ext = $1;
1499              
1500 0         0 my $spec_ext = $self->get_contact_ext( $cont, $ext );
1501             }
1502              
1503 3 50       18 return wantarray ? ( $cont, $rcode, $msg ) : $cont;
1504             }
1505              
1506 0 0       0 return wantarray ? ( 0, 0, 'empty answer' ) : 0 ;
1507             }
1508              
1509             =head2 update_statuses_add
1510              
1511             Part of update_* functions
1512              
1513             =cut
1514              
1515             sub update_statuses_add {
1516 14     14 1 28 my ( undef, $type, $statuses ) = @_;
1517              
1518 14         23 my $add = '';
1519 14         17 my %sts;
1520              
1521 14 100       48 if ( ref $statuses eq 'HASH' ) {
    50          
1522 2         3 %sts = %{$statuses};
  2         6  
1523             }
1524             elsif ( ref $statuses eq 'ARRAY' ) {
1525 12         18 $sts{$_} = '+' for @{$statuses};
  12         37  
1526             }
1527              
1528 14         34 foreach my $st ( keys %sts ) {
1529 14 100 66     79 if ( !$sts{$st} or $sts{$st} eq '+' ) {
1530 12         42 $add .= qq| <$type:status s="$st"/>\n|;
1531             }
1532             else {
1533 2         7 $add .= qq| <$type:status s="$st">$sts{$st}\n|;
1534             }
1535             }
1536              
1537 14         41 return $add;
1538             }
1539              
1540              
1541             =head2 update_statuses_rem
1542              
1543             Part of update_* functions
1544              
1545             =cut
1546              
1547             sub update_statuses_rem {
1548 4     4 1 10 my ( undef, $type, $statuses ) = @_;
1549              
1550 4         8 my $rem = '';
1551 4         7 my @sts;
1552              
1553 4 50       19 if ( ref $statuses eq 'HASH' ) {
    50          
1554 0         0 @sts = keys %{$statuses};
  0         0  
1555             }
1556             elsif ( ref $statuses eq 'ARRAY' ) {
1557 4         6 @sts = @{$statuses};
  4         9  
1558             }
1559              
1560 4         20 $rem .= qq| <$type:status s="$_"/>\n| foreach @sts;
1561              
1562 4         9 return $rem;
1563             }
1564              
1565             =head2 update_contact
1566              
1567             To update contact information
1568              
1569             INPUT:
1570              
1571             params with keys:
1572              
1573             C – contact id
1574              
1575             C, C – only contact statuses can be added or deleted, , such as clientUpdateProhibited
1576              
1577             C – modify data, see fields in L
1578              
1579             OUTPUT: see L.
1580              
1581             An Example, change data, one type (by default this is C):
1582              
1583             ( $answ, $code, $msg ) = $conn->update_contact(
1584             {
1585             cont_id => '123qwerty',
1586             chg => {
1587             first_name => 'Test',
1588             last_name => 'Testov',
1589             org => 'Private Person',
1590             addr => 'Vagnera 11-22',
1591             city => 'Donetsk',
1592             state => 'Donetskaya',
1593             postcode => '83061',
1594             country_code => 'DN',
1595             phone => '+380.501234567',
1596             fax => '',
1597             email => 'reg1010@yandex.com',
1598             authinfo => 'Q2+qqqqqqqqqqqqqqqqqqqqqqqqqq',
1599             }
1600             },
1601             );
1602              
1603             =cut
1604              
1605             sub update_contact {
1606 2     2 1 5 my ( $self, $params ) = @_;
1607              
1608 2 50       8 return ( 0, 0, 'no params' ) unless ref $params;
1609              
1610 2 50       6 return ( 0, 0, 'no cont_id' ) unless $params->{cont_id};
1611              
1612 2         7 my ( $add, $rem, $chg ) = ( '', '', '' );
1613              
1614 2 50       5 if ( $$params{add} ) {
1615 0 0       0 if ( $$params{add}{statuses} ) {
1616 0         0 $add .= $self->update_statuses_add( 'contact', $$params{add}{statuses} );
1617             }
1618             }
1619              
1620 2 50       6 $add = "\n \n$add " if $add;
1621              
1622 2 50       5 if ( $$params{rem} ) {
1623 0 0       0 if ( $$params{rem}{statuses} ) {
1624 0         0 $rem .= $self->update_statuses_rem( 'contact', $$params{rem}{statuses} );
1625             }
1626             }
1627              
1628 2 50       6 $rem = "\n \n$rem " if $rem;
1629              
1630 2 50       7 if ( $$params{chg} ) {
1631 2         7 $chg .= $self->cont_to_xml( $$params{chg} );
1632              
1633 2         23 $chg =~ s/\n/\n /g;
1634             }
1635              
1636 2 50       10 $chg = "\n $chg " if $chg;
1637              
1638 2   50     11 my $ext = $$params{extension} || '';
1639              
1640 2 50       6 $ext = "\n \n$ext " if $ext;
1641              
1642 2         5 my $cltrid = get_cltrid();
1643              
1644 2         19 my $body = <
1645             $$self{urn}{head}
1646            
1647            
1648            
1649             $$params{cont_id}$add$rem$chg
1650            
1651             $ext
1652             $cltrid
1653            
1654            
1655             UPDCONT
1656              
1657 2         6 return $self->simple_request( $body, 'update_contact' );
1658             }
1659              
1660              
1661             =head2 delete_contact
1662              
1663             Delete the specified contact.
1664             Usually this function is not needed because the registry itself deletes unused contacts.
1665              
1666             INPUT:
1667              
1668             params with keys:
1669              
1670             C – contact id.
1671              
1672             C – extensions for some providers, empty by default
1673              
1674             OUTPUT: see L.
1675              
1676             An Example:
1677              
1678             my ( $answ, $msg ) = make_request( 'delete_contact', { cont_id => 'H12345', %conn_params } );
1679              
1680             =cut
1681              
1682             sub delete_contact {
1683 2     2 1 6 my ( $self, $params ) = @_;
1684              
1685 2 50       7 return ( 0, 0, 'no params' ) unless ref $params;
1686              
1687 2 50       8 return ( 0, 0, 'no cont_id' ) unless $$params{cont_id};
1688              
1689 2   50     9 my $ext = $$params{extension} || '';
1690              
1691 2 50       6 $ext = "\n \n$ext " if $ext;
1692              
1693 2         4 my $cltrid = get_cltrid();
1694              
1695 2         14 my $body = <
1696             $$self{urn}{head}
1697            
1698            
1699            
1700             $$params{cont_id}
1701            
1702             $ext
1703             $cltrid
1704            
1705            
1706             DELCONT
1707              
1708 2         6 return $self->simple_request( $body, 'delete_contact' );
1709             }
1710              
1711              
1712             =head2 check_nss
1713              
1714             Check that the nameserver is registered
1715              
1716             INPUT:
1717              
1718             params with keys:
1719              
1720             C – list with nameservers
1721              
1722             C – extensions for some providers, empty by default
1723              
1724             OUTPUT: see L.
1725              
1726             An Example:
1727              
1728             my ( $a, $m, $o ) = make_request( 'check_nss', { nss => [ 'ns99.cnic.com', 'ns1.godaddy.com' ] } );
1729              
1730             # answer:
1731              
1732             {
1733             'msg' => 'Command completed successfully.',
1734             'ns1.godaddy.com' => {
1735             'reason' => 'in use',
1736             'avail' => '0'
1737             },
1738             'ns99.cnic.com' => {
1739             'avail' => '1'
1740             },
1741             'code' => '1000'
1742             };
1743              
1744             =cut
1745              
1746             sub check_nss {
1747 2     2 1 6 my ( $self, $params ) = @_;
1748              
1749 2 50 50     12 return ( 0, 0, 'no nss' ) unless $params->{nss} && scalar( @{$params->{nss}} );
  2         10  
1750              
1751 2         5 my $hosts = '';
1752              
1753 2         5 foreach my $h ( @{$params->{nss}} ) {
  2         6  
1754 8         16 $hosts .= "$h";
1755             }
1756              
1757 2   100     9 my $ext = $$params{extension} || '';
1758              
1759 2 100       17 $ext = "\n \n$ext " if $ext;
1760              
1761 2         8 my $cltrid = get_cltrid();
1762              
1763 2         15 my $body = <
1764             $$self{urn}{head}
1765            
1766            
1767            
1768             $hosts
1769            
1770             $ext
1771             $cltrid
1772            
1773            
1774             CHECKNSS
1775              
1776 2         6 my $content = $self->req( $body, 'check_nss' );
1777              
1778 2 50       15 if ( $content =~ // ) {
1779 2         8 my $code = $1 + 0;
1780              
1781 2         4 my $msg = '';
1782 2 50       46 if ( $content =~ /]*>(.+)<\/msg>.+\/result>/s ) {
1783 2         8 $msg = $1;
1784             }
1785              
1786 2         22 my @aa = $content =~ /(.+?)<\/host:cd>/sg;
1787              
1788 2         4 my %answ;
1789 2         6 foreach my $a ( @aa ) {
1790 8 50       30 if ( $a =~ /([^<>]+)<\/host:name>/ ) {
1791 8         15 my $ns = $2;
1792 8         23 $answ{$ns} = { avail => $1 };
1793              
1794 8 100       30 if ( $a =~ /([^<>]+)<\/host:reason>/ ) {
1795 2         7 $answ{$ns}{reason} = $1;
1796             }
1797             }
1798             }
1799              
1800 2 50       15 return wantarray ? ( \%answ, $code, $msg ) : \%answ;
1801             }
1802              
1803 0 0       0 return wantarray ? ( 0, 0, 'no answer' ) : 0;
1804             }
1805              
1806              
1807             =head2 create_ns
1808              
1809             Registering a nameserver
1810              
1811             INPUT:
1812              
1813             params with keys:
1814              
1815             C – name server
1816              
1817             C – array with IP, IPv4 and IPv6,
1818             IP must be specified only we register nameserver based on the domain of the same registry
1819              
1820             C – extensions for some providers, empty by default
1821              
1822             OUTPUT:
1823             see L
1824              
1825             An Example:
1826              
1827             my ( $h, $m, $o ) = make_request( 'create_ns', { ns => 'ns111.sssllll.info', %conn_params } );
1828              
1829             # check answer
1830              
1831             ( $a, $m ) = make_request( 'create_ns', { ns => 'ns222.ssslll.com', ips => ['1.2.3.4', 'fe80::aa00:bb11' ], conn => $o } );
1832              
1833             # check answer
1834              
1835             =cut
1836              
1837             sub create_ns {
1838 17     17 1 33 my ( $self, $params ) = @_;
1839              
1840 17 50       38 return ( 0, 0, 'no ns' ) unless $params->{ns};
1841              
1842 17         26 my $addrs = '';
1843 17 100 66     66 if ( $params->{ips} and ref( $params->{ips} ) eq 'ARRAY' ) {
1844 8         13 foreach my $ip ( @{$params->{ips}} ) {
  8         20  
1845 10 100       48 if ( $ip =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
1846 6         22 $addrs .= '' . $ip . '';
1847             }
1848             else {
1849 4         12 $addrs .= '' . $ip . '';
1850             }
1851             }
1852             }
1853              
1854 17   100     51 my $ext = $$params{extension} || '';
1855              
1856 17 100       52 $ext = "\n \n$ext " if $ext;
1857              
1858 17         29 my $cltrid = get_cltrid();
1859              
1860 17         93 my $body = <
1861             $$self{urn}{head}
1862            
1863            
1864            
1865             $$params{ns}$addrs
1866            
1867             $ext
1868             $cltrid
1869            
1870            
1871             CREATENS
1872              
1873 17         43 return $self->simple_request( $body, 'create_ns' );
1874             }
1875              
1876              
1877             =head2 get_ns_info_rdata
1878              
1879             Covertor NS xml resData data to hash.
1880              
1881             Can be overwritten in a child module.
1882              
1883             =cut
1884              
1885             sub get_ns_info_rdata {
1886 2     2 1 6 my ( undef, $rdata ) = @_;
1887              
1888 2         4 my %ns;
1889              
1890 2         21 ( $ns{name} ) = $rdata =~ /([^<>]+)<\/host:name>/;
1891 2         9 $ns{name} = lc $ns{name};
1892              
1893 2         14 ( $ns{roid} ) = $rdata =~ /([^<>]+)<\/host:roid>/;
1894              
1895             #
1896 2         12 my @ss = $rdata =~ //g;
1897             # No changes pending
1898 2         8 my @aa = $rdata =~ /]+>[^<>]+<\/host:status>/g;
1899 2 50       9 if ( scalar @aa ) {
1900 0         0 foreach my $row ( @aa ) {
1901 0 0       0 if ( $row =~ /([^<>]+)<\/host:status>/ ) {
1902 0         0 $ns{statuses}{$1} = $2;
1903             }
1904             }
1905             }
1906             else {
1907 2         9 $ns{statuses}{$_} = '+' for @ss;
1908             }
1909              
1910 2         17 $ns{ips} = [ $rdata =~ /([0-9A-Fa-f.:]+)<\/host:addr>/g ];
1911              
1912             # owner, ...
1913 2         10 foreach my $k ( keys %id ) {
1914 10 100       157 if ( $rdata =~ /([^<>]+)<\/host:$k>/ ) {
1915 5         36 $ns{$id{$k}} = $1;
1916             }
1917             }
1918              
1919             # dates
1920 2         10 foreach my $k ( keys %dt ) {
1921 12 100       170 if ( $rdata =~ /([^<>]+)<\/host:$k>/ ) {
1922 3         14 $ns{$dt{$k}} = $1;
1923              
1924 3         12 $ns{$dt{$k}} =~ s/T/ /;
1925 3         14 $ns{$dt{$k}} =~ s/\.\d+Z$//;
1926 3         9 $ns{$dt{$k}} =~ s/Z$//;
1927             }
1928             }
1929              
1930 2         11 return \%ns;
1931             }
1932              
1933              
1934             =head2 get_ns_info
1935              
1936             Get information about the specified nameserver
1937              
1938             INPUT:
1939              
1940             params with keys:
1941              
1942             C – name server;
1943              
1944             C – extensions for some providers, empty by default.
1945              
1946             OUTPUT:
1947              
1948             hash with ns data: statuses, dates, ips and other
1949              
1950             C – the account where the name server is located;
1951              
1952             C – the account where the name server was registered;
1953              
1954             C – name server registration date;
1955              
1956             C – name server id in the registry;
1957              
1958             C – list of IP addresses, IPv4 and IPv6;
1959              
1960             C – this status indicates that the name server is being used by some domain.
1961              
1962             An Example:
1963              
1964             my ( $answer, $msg, $conn ) = make_request( 'get_ns_info', { ns => 'ns1.sss.ru.com', %conn_params } );
1965              
1966             # answer:
1967              
1968             {
1969             'msg' => 'Command completed successfully.',
1970             'owner' => 'H2220222',
1971             'roid' => 'H370000-CNIC',
1972             'cre_date' => '2013-09-05 18:42:49',
1973             'name' => 'ns1.sss.ru.com',
1974             'ips' => [
1975             '2A00:3B00:0:0:0:0:0:25'
1976             ],
1977             'creater' => 'H2220222',
1978             'statuses' => {
1979             'ok' => '+',
1980             'linked' => '+'
1981             },
1982             'code' => '1000'
1983             };
1984              
1985             =cut
1986              
1987             sub get_ns_info {
1988 6     6 1 12 my ( $self, $params ) = @_;
1989              
1990 6 50       17 return ( 0, 0, 'no ns' ) unless $params->{ns};
1991              
1992 6   100     20 my $ext = $$params{extension} || '';
1993              
1994 6 100       19 $ext = "\n \n$ext " if $ext;
1995              
1996 6         11 my $cltrid = get_cltrid();
1997              
1998 6         33 my $body = <
1999             $$self{urn}{head}
2000            
2001            
2002            
2003             $$params{ns}
2004            
2005             $ext
2006             $cltrid
2007            
2008            
2009             NSINFO
2010              
2011 6         14 my $content = $self->req( $body, 'get_ns_info' );
2012              
2013 6 50       33 if ( $content =~ /result code=['"](\d+)['"]/ ) {
2014 6         19 my $rcode = $1 + 0;
2015              
2016 6         10 my $msg = '';
2017 6 50       61 if ( $content =~ /]*>(.+)<\/msg>.+\/result>/s ) {
2018 6         14 $msg = $1;
2019             }
2020              
2021 6         10 my $ns = {};
2022              
2023             # вытягиваем смысловую часть и парсим
2024 6 100       24 if ( $content =~ /(.+)<\/resData>/s ) {
2025 2         8 my $rdata = $1;
2026              
2027 2         15 $ns = $self->get_ns_info_rdata( $rdata );
2028             }
2029              
2030 6 50       32 return wantarray ? ( $ns, $rcode, $msg ) : $ns;
2031             }
2032              
2033 0         0 return 0;
2034             }
2035              
2036             =head2 update_ns
2037              
2038             Change the data of the specified name server
2039              
2040             INPUT
2041              
2042             params with keys:
2043              
2044             C – name server
2045              
2046             C, C – adding or removing the name server parameters listed below:
2047              
2048             C – IPv4 and IPv6 addresses;
2049              
2050             C – clientUpdateProhibited and other client*;
2051              
2052             C – change the server name, this is used to move the name server to a different domain.
2053              
2054             C – some registries prohibit passing an empty chg parameter – C<< >>
2055              
2056             C – extensions for some providers, empty by default
2057              
2058             OUTPUT:
2059             see L.
2060              
2061             An Example
2062              
2063             my ( $answ, $msg, $conn ) = make_request( 'update_ns', {
2064             ns => 'ns1.sss.ru.com',
2065             rem => { ips => [ '2A00:3B00:0:0:0:0:0:25' ] },
2066             add => { ips => [ '176.99.13.11' ] },
2067             %conn_params,
2068             } );
2069              
2070             ( $answ, $msg ) = make_request( 'update_ns', {
2071             ns => 'ns1.sss.ru.com',
2072             chg => { new_name => 'ns1.sss.xyz' },
2073             conn => $conn,
2074             } );
2075              
2076             =cut
2077              
2078             sub update_ns {
2079 16     16 1 33 my ( $self, $params ) = @_;
2080              
2081 16 50       32 return ( 0, 0, 'no ns' ) unless $$params{ns};
2082              
2083 16         24 my $add = '';
2084              
2085 16 100       34 if ( $params->{add} ) {
2086 11 100 66     39 if ( $params->{add}{ips} and ref $params->{add}{ips} ) {
2087 7         11 foreach my $ip ( @{$params->{add}{ips}} ) {
  7         19  
2088 7 100       33 if ( $ip =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
2089 5         16 $add .= ' '.$ip."\n";
2090             }
2091             else {
2092 2         8 $add .= ' '.$ip."\n";
2093             }
2094             }
2095             }
2096              
2097 11 100       25 if ( $params->{add}{statuses} ) {
2098 4         18 $add .= $self->update_statuses_add( 'host', $params->{add}{statuses} );
2099             }
2100             }
2101              
2102 16 100       28 if ( $add ) {
2103 11         24 $add = "\n$add ";
2104             }
2105             else {
2106 5         7 $add = '';
2107             }
2108              
2109 16         26 my $rem = '';
2110              
2111 16 100       26 if ( $params->{rem} ) {
2112 5 100 66     23 if ( defined $params->{rem}{ips} and ref $params->{rem}{ips} ) {
2113 3         4 foreach my $ip ( @{$params->{rem}{ips}} ) {
  3         7  
2114 4 100       16 if ( $ip =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
2115 3         9 $rem .= ' '.$ip."\n";
2116             }
2117             else {
2118 1         2 $rem .= ' '.$ip."\n";
2119             }
2120             }
2121             }
2122              
2123 5 100       10 if ( $params->{rem}{statuses} ) {
2124 2         12 $rem .= $self->update_statuses_rem( 'host', $params->{rem}{statuses} );
2125             }
2126             }
2127              
2128 16 100       29 if ( $rem ) {
2129 5         11 $rem = "\n$rem ";
2130             }
2131             else {
2132 11         15 $rem = "";
2133             }
2134              
2135 16         18 my $chg = '';
2136              
2137 16 100       28 if ( $params->{chg} ) {
2138 1 50       4 if ( $params->{chg}{new_name} ) {
2139 0         0 $chg .= " " . $$params{chg}{new_name} . "\n";
2140             }
2141             }
2142              
2143 16 50       38 if ( $chg ) {
    100          
2144 0         0 $chg = "\n$chg \n";
2145             }
2146             elsif ( !$params->{no_empty_chg} ) {
2147 3         5 $chg = "";
2148             }
2149              
2150 16   100     35 my $ext = $$params{extension} || '';
2151              
2152 16 100       35 $ext = "\n \n$ext " if $ext;
2153              
2154 16         27 my $cltrid = get_cltrid();
2155              
2156 16         102 my $body = <
2157             $$self{urn}{head}
2158            
2159            
2160            
2161             $$params{ns}
2162             $add
2163             $rem
2164             $chg
2165            
2166             $ext
2167             $cltrid
2168            
2169            
2170             UPDATENS
2171              
2172 16         35 return $self->simple_request( $body, 'update_ns' );
2173             }
2174              
2175              
2176             =head2 delete_ns
2177              
2178             Remove nameserver from the registry.
2179              
2180             It is usually forbidden to delete a name server that has the C status.
2181              
2182             INPUT:
2183              
2184             params with keys:
2185              
2186             C – name server;
2187              
2188             C – extensions for some providers, empty by default.
2189              
2190             OUTPUT:
2191             see L.
2192              
2193             An Example:
2194              
2195             my ( $answ, $msg ) = make_request( 'delete_ns', { ns => 'ns1.sss.ru.com', %conn_params } );
2196              
2197             =cut
2198              
2199             sub delete_ns {
2200 5     5 1 13 my ( $self, $params ) = @_;
2201              
2202 5 50       14 return ( 0, 0, 'no ns' ) unless $$params{ns};
2203              
2204 5   100     17 my $ext = $$params{extension} || '';
2205              
2206 5 100       16 $ext = "\n \n$ext " if $ext;
2207              
2208 5         10 my $cltrid = get_cltrid();
2209              
2210 5         26 my $body = <
2211             $$self{urn}{head}
2212            
2213            
2214            
2215             $$params{ns}
2216            
2217             $ext
2218             $cltrid
2219            
2220            
2221             DELNS
2222              
2223 5         13 return $self->simple_request( $body, 'delete_ns' );
2224             }
2225              
2226              
2227             =head2 check_domains_rdata
2228              
2229             Parses resData in the registry response
2230              
2231             Can be overwritten in a child module.
2232              
2233             =cut
2234              
2235             sub check_domains_rdata {
2236 3     3 1 11 my ( undef, $rdata ) = @_;
2237              
2238 3         7 my %domlist;
2239              
2240 3         58 my @aa = $rdata =~ /\s*([^<>]+<\/domain:name>(?:\s*[^<>]+<\/domain:reason>)?)\s*<\/domain:cd>/sg;
2241              
2242 3         10 foreach my $a ( @aa ) {
2243 26 50       93 if ( $a =~ /([^<>]+)<\/domain:name>/ ) {
2244 26         60 my $dm = lc($2);
2245              
2246 26         82 $domlist{$dm} = { avail => $1 }; # no utf8, puny only
2247              
2248 26 100       77 if ( $a =~ /([^<>]+)<\/domain:reason>/ ) {
2249 9         29 $domlist{$dm}{reason} = $1;
2250             }
2251             }
2252             }
2253              
2254 3 50       29 if ( $rdata =~ /claims<\/launch:phase>/ ) {
2255             # this is a call with an extension to get the key, if there is one
2256 0 0       0 if ( $rdata =~ /([0-9a-z.\-]+)<\/launch:name>\n?\s*([^<>]+)<\/launch:claimKey>/ ) {
2257 0         0 $domlist{ lc($2) }{claim} = { avail => $1, claimkey => $3 };
2258             }
2259             }
2260              
2261 3 50       22 if ( $rdata =~ /]+>(.+)<\/fee:chkData>/ ) {
2262             # this is a call with the extension draft-brown-epp-fees-02
2263 0         0 my $fee = $1;
2264              
2265 0         0 my @ff = $fee =~ /(.+)<\/fee:cd>/g;
2266              
2267 0         0 foreach my $f ( @ff ) {
2268 0         0 $f =~ /([0-9a-z\-\.])<\/fee:name>.*([0-9\.])<\/fee:fee>/;
2269 0         0 $domlist{ lc($1) }{fee} = { new => $2 }
2270             }
2271             }
2272              
2273 3         14 return \%domlist;
2274             }
2275              
2276             =head2 check_domains
2277              
2278             Check that the domain is available for registration
2279              
2280             INPUT:
2281              
2282             params with keys:
2283              
2284             C – list of domains to check;
2285              
2286             C – extensions for some providers, empty by default.
2287              
2288             OUTPUT:
2289              
2290             hash with domains, each domain has an C parameter, if avail == 0, the C parameter is usually added
2291              
2292             An Example
2293              
2294             my ( $answer, $code, $msg ) = $conn->check_domains( {
2295             tld => 'com',
2296             domains => [ 'qwerty.com', 'bjdwferbkre3jd0hf.net', 'xn--xx.com', 'hiebw.info' ],
2297             } );
2298              
2299             # answer:
2300              
2301             {
2302             'qwerty.com' => {
2303             'reason' => 'Domain exists',
2304             'avail' => '0'
2305             },
2306             'bjdwferbkre3jd0hf.net' => {
2307             'avail' => '1'
2308             },
2309             'hiebw.info' => {
2310             'reason' => 'Not an authoritative TLD',
2311             'avail' => '0'
2312             },
2313             'xn--xx.com' => {
2314             'reason' => 'Invalid punycode encoding',
2315             'avail' => '0'
2316             }
2317             };
2318              
2319             =cut
2320              
2321             sub check_domains {
2322 3     3 1 10 my ( $self, $params ) = @_;
2323              
2324 3 50 50     16 return ( 0, 0, 'no domains' ) unless $params->{domains} && scalar( @{$params->{domains}} );
  3         15  
2325              
2326 3         9 my $dms = '';
2327              
2328 3         6 foreach my $dm ( @{$params->{domains}} ) {
  3         10  
2329 26         50 $dms .= "$dm";
2330             }
2331              
2332 3   100     17 my $ext = $$params{extension} || '';
2333              
2334 3 100       11 $ext = "\n \n$ext " if $ext;
2335              
2336 3         9 my $cltrid = get_cltrid();
2337              
2338 3         27 my $body = <
2339             $$self{urn}{head}
2340            
2341            
2342            
2343             $dms
2344            
2345             $ext
2346             $cltrid
2347            
2348            
2349             CHECKDOMS
2350              
2351 3         10 my $answ = $self->req( $body, 'check_domains' );
2352              
2353 3         10 my ( $rcode, $msg ) = ( 0, '' );
2354              
2355 3 50 33     31 if ( $answ and $answ =~ /
2356 3         12 $rcode = $1 + 0;
2357              
2358 3 50       26 if ( $answ =~ /]*>([^<>]+)<\/msg>/ ) {
2359 3         9 $msg = $1;
2360             }
2361              
2362 3 50       26 if ( $answ =~ /(.+)<\/resData>/s ) {
2363 3         22 my $domlist = $self->check_domains_rdata( $1 );
2364              
2365 3 50       21 return wantarray ? ( $domlist, $rcode, $msg ) : $domlist;
2366             }
2367             }
2368              
2369 0 0       0 return wantarray ? ( 0, $rcode, $msg ) : 0;
2370             }
2371              
2372              
2373             =head2 create_domain_nss
2374              
2375             Generating a list of ns-s for domain registration.
2376              
2377             Can be overwritten in a child module.
2378              
2379             =cut
2380              
2381             sub create_domain_nss {
2382 14     14 1 22 my ( $self, $params ) = @_;
2383              
2384 14         21 my $nss = '';
2385              
2386 14         20 foreach my $ns ( @{$params->{nss}} ) {
  14         30  
2387 27         70 $nss .= " $ns\n";
2388             }
2389              
2390 14 50       45 $nss = "\n \n$nss " if $nss;
2391              
2392 14         32 return $nss;
2393             }
2394              
2395              
2396             =head2 create_domain_authinfo
2397              
2398             authinfo block for domain registration.
2399              
2400             Can be overwritten in a child module.
2401              
2402             =cut
2403              
2404             sub create_domain_authinfo {
2405 21     21 1 33 my ( $self, $params ) = @_;
2406              
2407             # Some providers require an empty authinfo, but no
2408 21 50       52 if ( exists $params->{authinfo} ) {
2409 21         59 return "\n \n $$params{authinfo}\n ";
2410             }
2411              
2412 0         0 return "\n \n \n ";
2413             }
2414              
2415              
2416             =head2 create_domain_ext
2417              
2418             Block with the DNSSEC extension for domain registration
2419              
2420             Can be overwritten in a child module.
2421              
2422             =cut
2423              
2424             sub create_domain_ext {
2425 21     21 1 30 my ( $self, $params ) = @_;
2426              
2427 21         30 my $ext = '';
2428              
2429 21 50       38 if ( $params->{dnssec} ) {
2430 0         0 my $dsdata = '';
2431 0         0 foreach my $raw ( @{$params->{dnssec}} ) {
  0         0  
2432 0         0 my $ds = '';
2433 0 0       0 $ds .= " $$raw{keytag}\n" if $raw->{keytag};
2434 0 0       0 $ds .= " $$raw{alg}\n" if $raw->{alg};
2435 0 0       0 $ds .= " $$raw{digtype}\n" if $raw->{digtype};
2436 0 0       0 $ds .= " $$raw{digest}\n" if $raw->{digest};
2437              
2438 0 0       0 $dsdata .= " \n$ds \n" if $ds;
2439             }
2440              
2441 0 0       0 $ext = " \n$dsdata \n"
2442             if $dsdata;
2443             }
2444              
2445 21         36 return $ext;
2446             }
2447              
2448              
2449             =head2 create_domain
2450              
2451             Domain registration.
2452              
2453             INPUT:
2454              
2455             params with keys:
2456              
2457             C – domain name;
2458              
2459             C – domain registration period, usually the default value is 1 year,
2460             registration for several months is not implemented – this is a very rare case;
2461              
2462             C, C, C, C – id of registrant, administrator, technical and billing contacts,
2463             at least one contact is required, usually the registrant;
2464              
2465             C -- hash for DNSSEC params: C, C, C, C,
2466             for details see L;
2467              
2468             C – array with nameservers;
2469              
2470             C – extensions for some providers, empty by default.
2471              
2472             OUTPUT:
2473             see L.
2474              
2475             An Example:
2476              
2477             my ( $answ, $msg ) = make_request(
2478             'create_domain',
2479             {
2480             dname => "sss.ru.com",
2481             reg_id => 'jp1g8fcv30fq',
2482             admin_id => 'jp1g8fcv31fq',
2483             tech_id => 'jp1g8fcv32fq',
2484             billing_id => 'jp1g8fcv33fq',
2485             authinfo => 'jp1g8fcv30fq+jp1g8fcv31fq',
2486             nss => [ 'dns1.yandex.net','dns2.yandex.net' ],
2487             period => 1,
2488             },
2489             );
2490              
2491             =cut
2492              
2493             sub create_domain {
2494 21     21 1 66 my ( $self, $params ) = @_;
2495              
2496 21 50       45 return ( 0, 0, 'no dname' ) unless $params->{dname};
2497              
2498 21         35 my $nss = '';
2499 21 100 100     49 if ( $params->{nss} && scalar @{$params->{nss}} ) {
  15         46  
2500 14         38 $nss = $self->create_domain_nss( $params );
2501             }
2502              
2503 21         35 my $cont = '';
2504             # 1. There is a zone without an owner, but with admin :)
2505             # 2. Verisign Core server -- without all contacts
2506 21 100       47 $cont .= qq|\n $$params{reg_id}| if $$params{reg_id};
2507              
2508 21         38 foreach my $t ( 'tech', 'admin', 'billing' ) {
2509 63 100       126 if ( $$params{$t.'_id'} ) {
2510 36 50       114 $$params{$t.'_id'} = [ $$params{$t.'_id'} ] unless ref $$params{$t.'_id'};
2511              
2512 36         45 foreach my $c ( @{$$params{$t.'_id'}} ) {
  36         66  
2513 36         80 $cont .= qq|\n $c|;
2514             }
2515             }
2516             }
2517              
2518 21         26 my $descr = ''; # tcinet registry
2519 21 50       56 if ( $params->{descr} ) {
2520 0 0       0 $params->{descr} = [ $params->{descr} ] unless ref $params->{descr};
2521              
2522 0         0 $descr .= "\n $_" for @{$params->{descr}};
  0         0  
2523             }
2524              
2525 21         46 my $authinfo = $self->create_domain_authinfo( $params );
2526              
2527 21   100     62 my $ext = $params->{extension} || '';
2528              
2529 21         41 $ext .= $self->create_domain_ext( $params );
2530              
2531 21 100       46 $ext = "\n \n$ext " if $ext;
2532              
2533 21         33 my $cltrid = get_cltrid();
2534              
2535 21         217 my $body = <
2536             $$self{urn}{head}
2537            
2538            
2539            
2540             $$params{dname}
2541             $$params{period}$nss$cont$authinfo$descr
2542            
2543             $ext
2544             $cltrid
2545            
2546            
2547             CREATEDOM
2548              
2549 21         49 return $self->simple_request( $body, 'create_domain' );
2550             }
2551              
2552              
2553             =head2 get_domain_info_rdata
2554              
2555             Covertor domains xml resData data to hash.
2556              
2557             Can be overwritten in a child module.
2558              
2559             =cut
2560              
2561             sub get_domain_info_rdata {
2562 10     10 1 20 my ( $self, $rdata ) = @_;
2563              
2564 10         19 my $info = {};
2565              
2566 10         48 ( $info->{dname} ) = $rdata =~ /([^<>]+)<\/domain:name>/;
2567 10         29 $info->{dname} = lc $info->{dname};
2568              
2569             #
2570 10         57 my @ss = $rdata =~ //g;
2571             # No reason supplied
2572 10         34 my @aa = $rdata =~ /]+>[^<>]+<\/domain:status>/g;
2573 10 50       52 if ( scalar @aa ) {
2574 0         0 foreach my $row ( @aa ) {
2575 0 0       0 if ( $row =~ /([^<>]+)<\/domain:status>/ ) {
2576 0         0 $info->{statuses}{$1} = $2;
2577             }
2578             }
2579             }
2580             else {
2581 10         47 $info->{statuses}{$_} = '+' for @ss;
2582             }
2583              
2584 10 100       43 if ( $rdata =~ /([^<>]+)<\/domain:registrant>/ ) {
2585             # One of the .ua zones uses admin instead of owner
2586 3         10 $info->{reg_id} = $1;
2587             }
2588              
2589 10         42 my @cc = $rdata =~ /[^<>]+<\/domain:contact>/g;
2590 10         20 foreach my $row ( @cc ) {
2591 9 50       38 if ( $row =~ /([^<>]+)<\/domain:contact>/ ) {
2592 9         48 $info->{ lc($1) . '_id' } = $2;
2593             }
2594             }
2595              
2596 10 50       37 if ( $rdata =~ // ) {
2597 0         0 $info->{descr} = [ $rdata =~ /([^<>]+)<\/domain:description>/g ];
2598             }
2599              
2600 10 50       38 if ( $rdata =~ // ) {
2601 10         76 $info->{nss} = [ $rdata =~ /([^<>]+)<\/domain:hostObj>/g ];
2602             }
2603              
2604 10 50 33     49 unless ( $info->{nss} or $rdata !~ // ) {
2605             # some providers use the old variant for some zones, example: irrp for ph
2606             # this is a rare option, so it is made separately and not built into the previous regexp
2607 0         0 $info->{nss} = [ $rdata =~ /([^<>]+)<\/domain:hostName>/g ];
2608             }
2609              
2610 10 0 33     32 unless ( $info->{nss} or $rdata !~ // or scalar @{$info->{nss}} ) {
  0   50     0  
2611             # 1 more ancient artifact, probably the oldest
2612 0         0 $info->{nss} = [ $rdata =~ /([^<>]+)<\/domain:ns>/g ];
2613             }
2614              
2615 10 50       24 if ( $info->{nss} ) {
2616 10         22 $info->{nss} = [ map{ lc $_ } @{$info->{nss}} ];
  20         59  
  10         25  
2617             }
2618              
2619             # Domain-based nss
2620 10 100       55 if ( $rdata =~ // ) {
2621 4         74 $info->{hosts} = [ $rdata =~ /([^<>]+)<\/domain:host>/g ];
2622 4         10 $info->{hosts} = [ map{ lc $_ } @{$info->{hosts}} ];
  5         13  
  4         10  
2623             }
2624              
2625             # owner, ...
2626 10         44 foreach my $k ( keys %id ) {
2627 50 100       777 if ( $rdata =~ /([^<>]+)<\/domain:$k>/ ) {
2628 30         114 $info->{$id{$k}} = $1;
2629             }
2630             }
2631              
2632             # dates
2633 10         35 foreach my $k ( keys %dt ) {
2634 60 100       813 if ( $rdata =~ /([^<>]+)<\/domain:$k>/ ) {
2635 30         64 $info->{$dt{$k}} = cldate( $1 );
2636             }
2637             }
2638              
2639 10 100       62 if ( $rdata =~ /authInfo.+([^<>]+)<\/domain:pw>.+authInfo/s ) {
2640 7         21 ( $info->{authinfo} ) = $1;
2641              
2642 7         15 $info->{authinfo} =~ s/>/>/g;
2643 7         11 $info->{authinfo} =~ s/</
2644 7         12 $info->{authinfo} =~ s/&/&/g;
2645             }
2646              
2647 10         48 ( $info->{roid} ) = $rdata =~ /([^<>]+)<\/domain:roid>/;
2648              
2649 10         33 my $spec = $self->get_domain_spec_rdata( $rdata );
2650 10         28 $info->{$_} = $spec->{$_} for keys %$spec;
2651              
2652 10         37 return $info;
2653             }
2654              
2655              
2656             =head2 get_domain_spec_rdata
2657              
2658             Parse special data in resData from provider.
2659              
2660             For overwritten in a child module.
2661              
2662             In this module, the function does nothing
2663              
2664             =cut
2665              
2666             sub get_domain_spec_rdata {
2667 10     10 1 25 return {};
2668             }
2669              
2670              
2671             =head2 get_domain_spec_ext
2672              
2673             Parse special data in extension from provider.
2674              
2675             For overwritten in a child module.
2676              
2677             In this module, the function does nothing
2678              
2679             =cut
2680              
2681             sub get_domain_spec_ext {
2682 7     7 1 14 return {};
2683             }
2684              
2685              
2686             =head2 get_domain_info
2687              
2688             The main information on the domain
2689              
2690             INPUT:
2691              
2692             params with keys:
2693              
2694             C – domain name;
2695              
2696             C – extensions for some providers, empty by default.
2697              
2698             OUTPUT:
2699              
2700             C;
2701              
2702             C – domain id if registry;
2703              
2704             C – the account where the domain is located now;
2705              
2706             C – the account where the domain was registered;
2707              
2708             C – domain registration date;
2709              
2710             C – domain last transfer date;
2711              
2712             C – domain last update date;
2713              
2714             C – domain expiration date;
2715              
2716             C, C, C, C – domain contact IDs;
2717              
2718             C – list of domain name servers;
2719              
2720             C – hash where keys is status flags, values is status expiration date, if any, or other information;
2721              
2722             C – list with name servers based on this domain.
2723              
2724             There can also be extension parameters.
2725              
2726             An Example:
2727              
2728             my ( $answer, $msg, $conn ) = make_request( 'get_domain_info', { dname => 'sss.ru.com', %conn_params } );
2729              
2730             # answer:
2731              
2732             {
2733             'hosts' => [
2734             'ns1.sss.ru.com',
2735             'ns2.sss.ru.com'
2736             ],
2737             'roid' => 'D888888-CNIC',
2738             'cre_date' => '1212-12-12 12:12:12',
2739             'upd_date' => '2020-02-02 20:02:20',
2740             'trans_date' => '2012-12-12 12:12:12',
2741             'creater' => 'H12345',
2742             'tech_id' => '1iuhajppwsjp',
2743             'reg_id' => 'H12346',
2744             'owner' => 'H2220222',
2745             'exp_date' => '2022-12-12 23:59:59',
2746             'billing_id' => 'H12347',
2747             'nss' => [
2748             'ns1.sss.ru.com'
2749             'ns1.mmm.ru.com'
2750             ],
2751             'dname' => 'sss.ru.com',
2752             'admin_id' => 'H12348',
2753             'statuses' => {
2754             'renewPeriod' => '+',
2755             'clientTransferProhibited' => '+'
2756             }
2757             };
2758              
2759             =cut
2760              
2761             sub get_domain_info {
2762 14     14 1 31 my ( $self, $params ) = @_;
2763              
2764 14 50       41 unless ( $$params{dname} ) {
2765 0 0       0 return wantarray ? ( 0, 0, 'no dname') : 0;
2766             }
2767              
2768 14 50       37 my $pw = $$params{authinfo} ? "\n \n $$params{authinfo}\n " : '';
2769              
2770 14 50       36 my $hosts_type = $$params{hosts} ? ' hosts="'.$$params{hosts}.'"' : '';
2771              
2772 14   100     37 my $ext = $$params{extension} || '';
2773              
2774 14 100       40 $ext = "\n \n$ext " if $ext;
2775              
2776 14         30 my $cltrid = get_cltrid();
2777              
2778 14         84 my $body = <
2779             $$self{urn}{head}
2780            
2781            
2782            
2783             $$params{dname}$pw
2784            
2785             $ext
2786             $cltrid
2787            
2788            
2789             DOMINFO
2790              
2791 14         31 my $answ = $self->req( $body, 'domain_info' );
2792              
2793 14 50 33     100 if ( $answ && $answ =~ // ) {
2794 14         47 my $rcode = $1 + 0;
2795              
2796 14         20 my $msg = '';
2797 14 50       251 if ( $answ =~ /]*>(.+)<\/msg>.+\/result>/s ) {
2798 14         36 $msg = $1;
2799             }
2800              
2801 14 100       44 if ( $rcode != 1000 ) {
2802 4 100       18 if ( $answ =~ /(.+)<\/reason>/s ) {
2803             # for details
2804 2         5 $msg .= '; ' . $1;
2805             }
2806              
2807 4 50       12 if ( $answ =~ /(.+)<\/oxrs:xcp>/s ) {
2808             # for oxrs details
2809 0         0 $msg .= '; ' . $1;
2810             }
2811              
2812 4 50       26 return wantarray ? ( 0, $rcode, $msg ) : 0;
2813             }
2814              
2815 10         23 my $info = {};
2816              
2817             # pull out the main part and parse
2818 10 50       74 if ( $answ =~ /(.+)<\/resData>/s ) {
2819 10         27 my $rdata = $1;
2820              
2821 10         34 $info = $self->get_domain_info_rdata( $rdata );
2822             }
2823              
2824 10 100       62 if ( $answ =~ /(.+)<\/extension>/s ) {
2825 7         18 my $rdata = $1;
2826              
2827 7         21 my @st = $rdata =~ //g;
2828 7         14 $info->{statuses}{$_} = '+' for @st;
2829              
2830 7         39 my @est = $rdata =~ /([^<>]+<\/rgp:rgpStatus>)/g;
2831              
2832 7         24 foreach my $e ( @est ) {
2833 14         56 my ( $st, $descr ) = $e =~ /([^<>]+)<\/rgp:rgpStatus>/;
2834              
2835 14 50       32 if ( $descr =~ /^endDate=/ ) {
2836 14         32 $descr =~ s/T/ /;
2837 14         40 $descr =~ s/\.\d+Z$//;
2838 14         23 $descr =~ s/Z$//;
2839             }
2840 14         39 $info->{statuses}{$st} = $descr;
2841             }
2842              
2843 7 50       21 if ( $rdata =~ /secDNS:infData/ ) {
2844 0         0 $info->{dnssec} = [];
2845              
2846 0         0 my @dsdata = $rdata =~ /(.+?)<\/secDNS:dsData>/g;
2847 0         0 foreach my $sdata ( @dsdata ) {
2848 0         0 my %one_raw;
2849 0         0 ( $one_raw{keytag} ) = $sdata =~ /(\d+)<\/secDNS:keyTag>/;
2850 0         0 ( $one_raw{alg} ) = $sdata =~ /(\d+)<\/secDNS:alg>/;
2851 0         0 ( $one_raw{digtype} ) = $sdata =~ /(\d+)<\/secDNS:digestType>/;
2852 0         0 ( $one_raw{digest} ) = $sdata =~ /([A-Za-z0-9]+)<\/secDNS:digest>/;
2853              
2854 0 0       0 if ( $sdata =~ /(.+)<\/secDNS:keyData>/s ) {
2855 0         0 my $kdata = $1;
2856              
2857 0         0 $one_raw{keydata} = {};
2858 0         0 ( $one_raw{keydata}{flags} ) = $kdata =~ /(\d+)<\/secDNS:flags>/;
2859 0         0 ( $one_raw{keydata}{protocol} ) = $kdata =~ /(\d+)<\/secDNS:protocol>/;
2860 0         0 ( $one_raw{keydata}{alg} ) = $kdata =~ /(\d+)<\/secDNS:alg>/;
2861 0         0 ( $one_raw{keydata}{pubkey} ) = $kdata =~ /([^<>]+)<\/secDNS:pubKey>/;
2862             }
2863              
2864 0         0 push @{$$info{dnssec}}, \%one_raw;
  0         0  
2865             }
2866             }
2867              
2868 7         19 my $spec = $self->get_domain_spec_ext( $rdata );
2869 7         25 $info->{$_} = $spec->{$_} for keys %$spec;
2870             }
2871              
2872 10 50       59 return wantarray ? ( $info, $rcode, $msg ) : $info;
2873             }
2874              
2875 0 0       0 return wantarray ? ( 0, 0, 'empty answer' ) : 0;
2876             }
2877              
2878              
2879             =head2 renew_domain
2880              
2881             Domain registration renewal for N years.
2882              
2883             INPUT:
2884              
2885             params with keys:
2886              
2887             C – domain name;
2888              
2889             C – the domain renewal period in years, by default, will be prologed for 1 year;
2890              
2891             C – current expiration date, without specifying the time;
2892              
2893             C – extensions for some providers, empty by default.
2894              
2895             OUTPUT:
2896             see L.
2897              
2898             An Example:
2899              
2900             my ( $a, $m ) = make_request( 'renew_domain', { dname => 'datada.net', period => 1, exp_date => '2022-22-22' } );
2901              
2902             =cut
2903              
2904             sub renew_domain {
2905 11     11 1 21 my ( $self, $params ) = @_;
2906              
2907 11 50 33     51 return ( 0, 0, 'no params' ) unless $$params{dname} && $$params{exp_date};
2908              
2909 11   50     23 $params->{period} ||= 1;
2910              
2911 11   100     32 my $ext = $params->{extension} || '';
2912              
2913 11 100       31 $ext = "\n \n$ext " if $ext;
2914              
2915 11         21 my $cltrid = get_cltrid();
2916              
2917 11         68 my $body = <
2918             $$self{urn}{head}
2919            
2920            
2921            
2922             $$params{dname}
2923             $$params{exp_date}
2924             $$params{period}
2925            
2926             $ext
2927             $cltrid
2928            
2929            
2930             RENEWDOM
2931              
2932 11         26 return $self->simple_request( $body, 'renew_domain' );
2933             }
2934              
2935              
2936             =head2 update_domain_add_nss
2937              
2938             Part of the update_domain function.
2939              
2940             Can be overwritten in a child module, example, in L
2941              
2942             =cut
2943              
2944             sub update_domain_add_nss {
2945 9     9 1 22 my ( undef, $params ) = @_;
2946              
2947 9         17 my $add = " \n";
2948              
2949 9         12 foreach my $ns ( @{$$params{add}{nss}} ) {
  9         26  
2950 9         29 $add .= " $ns\n";
2951             }
2952              
2953 9         18 $add .= " \n";
2954              
2955 9         21 return $add;
2956             }
2957              
2958              
2959             =head2 update_domain_rem_nss
2960              
2961             Part of the update_domain function.
2962              
2963             Can be overwritten in a child module.
2964              
2965             =cut
2966              
2967             sub update_domain_rem_nss {
2968 6     6 1 11 my ( undef, $params ) = @_;
2969              
2970 6         12 my $rem = " \n";
2971              
2972 6         11 foreach my $ns ( @{$$params{rem}{nss}} ) {
  6         16  
2973 6         20 $rem .= " $ns\n";
2974             }
2975              
2976 6         13 $rem .= " \n";
2977              
2978 6         13 return $rem;
2979             }
2980              
2981              
2982             =head2 update_domain_ext
2983              
2984             Part of the update_domain function.
2985              
2986             Can be overwritten in a child module.
2987              
2988             In this function this module contains the DNSSEC extension
2989              
2990             =cut
2991              
2992             sub update_domain_ext {
2993 39     39 1 64 my ( undef, $params ) = @_;
2994              
2995 39         59 my $ext = '';
2996              
2997 39         47 my $rem_ds = '';
2998 39 50 66     94 if ( $params->{rem} && $params->{rem}{dnssec} ) {
2999 0         0 foreach my $raw ( @{$params->{rem}{dnssec}} ) {
  0         0  
3000 0         0 my $ds = '';
3001 0 0       0 $ds .= " $$raw{keytag}\n" if $raw->{keytag};
3002 0 0       0 $ds .= " $$raw{alg}\n" if $raw->{alg};
3003 0 0       0 $ds .= " $$raw{digtype}\n" if $raw->{digtype};
3004 0 0       0 $ds .= " $$raw{digest}\n" if $raw->{digest};
3005              
3006 0 0       0 $rem_ds .= " \n$ds \n" if $ds;
3007             }
3008              
3009 0 0       0 $rem_ds = " \n$rem_ds \n" if $rem_ds;
3010             }
3011              
3012 39         57 my $add_ds = '';
3013 39 50 66     109 if ( $params->{add} && $params->{add}{dnssec} ) {
3014 0         0 foreach my $raw ( @{$params->{add}{dnssec}} ) {
  0         0  
3015 0         0 my $ds = '';
3016 0 0       0 $ds .= " $$raw{keytag}\n" if $raw->{keytag};
3017 0 0       0 $ds .= " $$raw{alg}\n" if $raw->{alg};
3018 0 0       0 $ds .= " $$raw{digtype}\n" if $raw->{digtype};
3019 0 0       0 $ds .= " $$raw{digest}\n" if $raw->{digest};
3020              
3021 0 0       0 $add_ds .= " \n$ds \n" if $ds;
3022             }
3023              
3024 0 0       0 $add_ds = " \n$add_ds \n" if $add_ds;
3025             }
3026              
3027 39 50 33     119 if ( $rem_ds || $add_ds ) {
3028 0         0 $ext .= "\n \n";
3029 0         0 $ext .= $rem_ds;
3030 0         0 $ext .= $add_ds;
3031 0         0 $ext .= " \n";
3032             }
3033              
3034 39         79 return $ext;
3035             }
3036              
3037             =head2 update_domain
3038              
3039             To update domain data: contact ids, authinfo, nss, statuses.
3040              
3041             INPUT:
3042              
3043             params with keys:
3044              
3045             C – domain name
3046              
3047             C, C – hashes for adding and deleting data:
3048              
3049             C, C, C – contact IDs;
3050              
3051             C – list with name servers;
3052              
3053             C – various client* statuses;
3054              
3055             C – DNSSEC extension parameters.
3056              
3057             C – hash for changeable data:
3058              
3059             C – registrant contact id;
3060              
3061             C – new key for domain;
3062              
3063             OUTPUT:
3064             see L.
3065              
3066             Examples:
3067              
3068             my ( $a, $m, $c ) = make_request( 'update_domain', {
3069             dname => 'example.com',
3070             chg => { authinfo => 'fnjkfrekrejkfrenkfrenjkfren' },
3071             rem => { nss => [ 'ns1.qqfklnqq.com', 'ns2.qqfklnqq.com' ] },
3072             add => { nss => [ 'ns1.web.name', 'ns2.web.name' ] },
3073             %conn_params,
3074             } );
3075              
3076             ( $a, $m ) = make_request( 'update_domain', {
3077             dname => 'example.com',
3078             rem => { statuses => [ 'clientUpdateProhibited','clientDeleteProhibited' ] },
3079             add => { statuses => [ 'clientHold' ] },
3080             conn => $c,
3081             } );
3082              
3083             =cut
3084              
3085             sub update_domain {
3086 39     39 1 69 my ( $self, $params ) = @_;
3087              
3088 39 50       111 return ( 0, 0, 'no params' ) unless ref $params;
3089              
3090 39 50       78 return ( 0, 0, 'no dname' ) unless $params->{dname};
3091              
3092 39         63 my $nm = 'update_domain';
3093              
3094 39         48 my $add = '';
3095 39 100       85 if ( ref $$params{add} ) {
3096 21 50 66     83 if ( $$params{add}{nss} && ref $$params{add}{nss} && scalar( @{$$params{add}{nss}} ) ) {
  9   100     26  
3097 9         30 $add .= $self->update_domain_add_nss( $params );
3098              
3099 9         15 $nm .= '_add_ns';
3100             }
3101              
3102 21         40 foreach my $t ( 'admin', 'billing', 'tech' ) {
3103 63 100       141 if ( $$params{add}{$t.'_id'} ) {
3104 2 50       9 $$params{add}{$t.'_id'} = [ $$params{add}{$t.'_id'} ] unless ref $$params{add}{$t.'_id'};
3105              
3106 2         5 foreach my $c ( @{$$params{add}{$t.'_id'}} ) {
  2         6  
3107 2         9 $add .= qq| $c\n|;
3108             }
3109             }
3110             }
3111              
3112 21 100       47 if ( $params->{add}{statuses} ) {
3113 10         30 $add .= $self->update_statuses_add( 'domain', $params->{add}{statuses} );
3114              
3115 10         19 $nm .= '_add_status';
3116             }
3117             }
3118              
3119 39 100       74 if ( $add ) {
3120 21         44 $add = "\n$add ";
3121             }
3122             else {
3123 18         30 $add = '';
3124             }
3125              
3126 39         53 my $chg = '';
3127 39 100       79 if ( ref $$params{chg} ) {
3128 9 100       23 if ( $$params{chg}{reg_id} ) {
3129 3         12 $chg .= ' ' . $$params{chg}{reg_id} . "\n";
3130              
3131 3         6 $nm .= '_chg_cont';
3132             }
3133              
3134 9 100       21 if ( $$params{chg}{authinfo} ) {
3135 6         19 $chg .= " \n ".$$params{chg}{authinfo}."\n \n";
3136              
3137 6         11 $nm .= '_chg_key';
3138             }
3139              
3140 9 50       31 if ( $params->{chg}{descr} ) {
3141 0 0       0 $params->{chg}{descr} = [ $params->{chg}{descr} ] unless ref $params->{chg}{descr};
3142              
3143 0         0 $chg .= " $_\n" foreach @{$params->{chg}{descr}};
  0         0  
3144              
3145 0         0 $nm .= '_chg_descr';
3146             }
3147             }
3148              
3149 39 100       67 if ( $chg ) {
3150 9         22 $chg = "\n$chg ";
3151             }
3152             else {
3153 30         52 $chg = '';
3154             }
3155              
3156 39         53 my $rem = '';
3157 39 100       75 if ( $$params{rem} ) {
3158 10 50 66     57 if ( $$params{rem}{nss} && ref $$params{rem}{nss} && scalar( @{$$params{rem}{nss}} ) ) {
  6   100     20  
3159 6         26 $rem .= $self->update_domain_rem_nss( $params );
3160              
3161 6         9 $nm .= '_del_ns';
3162             }
3163              
3164 10         23 foreach my $t ( 'admin', 'billing', 'tech' ) {
3165 30 100       73 if ( $$params{rem}{$t.'_id'} ) {
3166 2 50       10 $$params{rem}{$t.'_id'} = [ $$params{rem}{$t.'_id'} ] unless ref $$params{rem}{$t.'_id'};
3167              
3168 2         3 foreach my $c ( @{$$params{rem}{$t.'_id'}} ) {
  2         7  
3169 2         8 $rem .= qq| $c\n|;
3170             }
3171             }
3172             }
3173              
3174 10 100       27 if ( $$params{rem}{statuses} ) {
3175 2         9 $rem .= $self->update_statuses_rem( 'domain', $$params{rem}{statuses} );
3176              
3177 2         5 $nm .= '_del_status';
3178             }
3179             }
3180              
3181 39 100       63 if ( $rem ) {
3182 10         24 $rem = "\n$rem ";
3183             }
3184             else {
3185 29         39 $rem = '';
3186             }
3187              
3188 39   100     97 my $ext = $$params{extension} || '';
3189              
3190 39         89 $ext .= $self->update_domain_ext( $params );
3191              
3192 39 100       99 $ext = "\n \n$ext " if $ext;
3193              
3194 39         72 my $cltrid = get_cltrid();
3195              
3196 39         261 my $body = <
3197             $$self{urn}{head}
3198            
3199            
3200            
3201             $$params{dname}
3202             $add
3203             $rem
3204             $chg
3205            
3206             $ext
3207             $cltrid
3208            
3209            
3210             UPDDOM
3211              
3212 39         95 return $self->simple_request( $body, $nm );
3213             }
3214              
3215              
3216             =head2 transfer
3217              
3218             Domain transfers: to us, from us, reject transfers.
3219              
3220             INPUT:
3221              
3222             params with keys:
3223              
3224             C – operation, possible variants: C, C, C, C, C;
3225              
3226             C – key for alien domain;
3227              
3228             C – if the transfer with renew, you can specify the extension period for some registries, undef and zero have different values;
3229              
3230             C – extensions for some registries in xml format;
3231              
3232             C – special parameters for very original providers.
3233              
3234             OUTPUT:
3235              
3236             It depends very much on the operation and on the registry
3237              
3238             Examples:
3239              
3240             my ( $answ, $code, $msg ) = $conn->transfer( { op => 'request', dname => 'reclick.realty', authinfo => '123qweRTY{*}', period => 1 } );
3241              
3242             ( $answ, $code, $msg ) = $conn->transfer( { op => 'query', dname => 'reclick.realty', authinfo => '123qweRTY{*}' } );
3243              
3244             # answer from the CentralNic
3245              
3246             {
3247             'exp_date' => '2021-01-18 23:59:59',
3248             'cltrid' => '9d7e6ec767ec7d9d9d40fc518a5',
3249             'trstatus' => 'pending', # transfer status
3250             'requestors_id' => 'H2220222', # this we
3251             'dname' => 'reclick.realty',
3252             'senders_id' => 'H3105376', # godaddy
3253             'send_date' => '2020-01-15 21:14:26',
3254             'svtrid' => 'CNIC-82A2E9B355020697D1B3EF6FDE9D822D4CCE1D1616412EF53',
3255             'request_date' => '2020-01-10 21:14:26'
3256             };
3257              
3258             ( $answ, $code, $msg ) = $conn->transfer( { op => 'approve', dname => 'reclick.realty' } );
3259              
3260             ( $answ, $code, $msg ) = $conn->transfer( { op => 'reject', dname => 'reclick.realty', authinfo => '123qweRTY{*}' } );
3261              
3262             ( $answ, $code, $msg ) = $conn->transfer( { op => 'cancel', dname => 'reclick.realty', authinfo => '123qweRTY{*}' } );
3263              
3264             =cut
3265              
3266             sub transfer {
3267 7     7 1 16 my ( $self, $params ) = @_;
3268              
3269 7 50       20 return ( 0, 0, 'no dname' ) unless $params->{dname};
3270              
3271 7 50 33     52 return ( 0, 0, 'no op[eration]' ) unless $params->{op} && $params->{op} =~ /query|request|cancel|approve|reject|usertransfer/;
3272              
3273 7         13 my $pw = '';
3274 7 100       18 if ( defined $params->{authinfo} ) {
3275             # 0 & undef are differents
3276 2         9 $pw = "\n \n $$params{authinfo}\n ";
3277             }
3278              
3279 7         13 my $per = '';
3280 7 100       16 if ( defined $params->{period} ) {
3281             # 0 & undef is different
3282 4         12 $per = qq|\n $$params{period}|;
3283             }
3284              
3285             # special parameters for very original registries
3286 7   50     21 my $spec = $$params{addition} || '';
3287              
3288 7   50     22 my $ext = $$params{extension} || '';
3289              
3290 7 50       14 $ext = "\n \n$ext " if $ext;
3291              
3292 7         14 my $cltrid = get_cltrid();
3293              
3294 7         42 my $body = <
3295             $$self{urn}{head}
3296            
3297            
3298            
3299             $$params{dname}$per$pw$spec
3300            
3301             $ext
3302             $cltrid
3303            
3304            
3305             TRANS
3306              
3307 7         23 my $answ = $self->req( $body, $$params{op}.'_transfer' );
3308              
3309 7 50       37 if ( $answ =~ // ) {
3310 7         24 my $rcode = $1 + 0;
3311              
3312 7         11 my $msg = '';
3313 7 50       74 if ( $answ =~ /]*>(.+)<\/msg>.+\/result>/s ) {
3314 7         17 $msg = $1;
3315              
3316 7 50       52 if ( $answ =~ /]*>(.+)<\/text>.+\/result>/s ) {
3317 0         0 $msg .= '; ' . $1;
3318             }
3319              
3320 7 50       22 if ( $answ =~ /([^<>]+)<\/reason>/ ) {
3321 0         0 $msg .= '; ' . $1;
3322             }
3323             }
3324              
3325 7         12 my $info = {}; # for data
3326              
3327             # pull out the main part and parse
3328 7 100       28 if ( $answ =~ /(.+)<\/resData>/s ) {
3329 2         5 my $rdata = $1;
3330              
3331 2         10 ( $info->{dname} ) = $rdata =~ /([^<>]+)<\/domain:name>/;
3332              
3333 2         10 ( $info->{trstatus} ) = $rdata =~ /([^<>]+)<\/domain:trStatus>/;
3334              
3335             # owner, ...
3336 2         11 foreach my $k ( keys %id ) {
3337 10 100       168 if ( $rdata =~ /([^<>]+)<\/domain:$k>/ ) {
3338 4         19 $info->{$id{$k}} = $1;
3339             }
3340             }
3341              
3342             # dates
3343 2         10 foreach my $k ( keys %dt ) {
3344 12 100       183 if ( $rdata =~ /([^<>]+)<\/domain:$k>/ ) {
3345 6         47 $info->{$dt{$k}} = $1;
3346              
3347 6         24 $info->{$dt{$k}} =~ s/T/ /;
3348 6         26 $info->{$dt{$k}} =~ s/\.\d+Z$//;
3349 6         17 $info->{$dt{$k}} =~ s/Z$//;
3350             }
3351             }
3352             }
3353              
3354 7         40 ( $info->{cltrid} ) = $answ =~ /([0-9A-Za-z\-]+)<\/clTRID>/;
3355 7         32 ( $info->{svtrid} ) = $answ =~ /([0-9A-Za-z\-]+)<\/svTRID>/;
3356              
3357 7 50       38 return wantarray ? ( $info, $rcode, $msg ) : $info;
3358             }
3359              
3360 0 0       0 return wantarray ? ( 0, 0, 'empty answer' ) : 0;
3361             }
3362              
3363              
3364             =head2 delete_domain
3365              
3366             Deleting a domain.
3367              
3368             params with keys:
3369              
3370             C – domain name
3371              
3372             C – extensions for some registries in xml format
3373              
3374             OUTPUT:
3375             see L.
3376              
3377             An Example:
3378              
3379             my ( $a, $m ) = make_request( 'delete_domain', { dname => 'ssslll.ru.com', %conn_params } );
3380              
3381             =cut
3382              
3383             sub delete_domain {
3384 10     10 1 20 my ( $self, $params ) = @_;
3385              
3386 10 50       24 return ( 0, 0, 'no dname' ) unless $params->{dname};
3387              
3388 10   100     30 my $ext = $$params{extension} || '';
3389              
3390 10 100       25 $ext = "\n \n$ext " if $ext;
3391              
3392 10         23 my $cltrid = get_cltrid();
3393              
3394 10         55 my $body = <
3395             $$self{urn}{head}
3396            
3397            
3398            
3399             $$params{dname}
3400            
3401             $ext
3402             $cltrid
3403            
3404            
3405             DELDOM
3406              
3407 10         25 return $self->simple_request( $body, 'delete_domain' );
3408             }
3409              
3410              
3411             =head2 req_poll_rdata
3412              
3413             Parse resData from req poll
3414              
3415             Can be overwritten in a child module
3416              
3417             =cut
3418              
3419             sub req_poll_rdata {
3420 0     0 1 0 my ( $self, $rdata ) = @_;
3421              
3422 0         0 my %info;
3423              
3424 0 0       0 if ( $rdata =~ /^\s*
    0          
    0          
    0          
    0          
    0          
3425 0         0 $info{upd_del} = {};
3426 0         0 ( $info{upd_del}{result}, $info{upd_del}{contact} ) =
3427             $rdata =~ /([^<>]+)<\/contact:id>/;
3428             }
3429              
3430             elsif ( $rdata =~ /\s*
3431 0         0 ( $info{ns} ) = $rdata =~ m|([^<>]+)|s;
3432 0         0 $info{ns} = lc $info{ns};
3433              
3434 0         0 ( $info{roid} ) = $rdata =~ m|([^<>]+)|s;
3435              
3436 0         0 my @sts = $rdata =~ m|()|gs;
3437 0         0 for my $row ( @sts ) {
3438 0 0       0 if ( $row =~ /host:status s="([^"]+)"/ ) {
3439 0         0 $info{statuses}{$1} = '+';
3440             }
3441             }
3442              
3443 0         0 my @ips = $rdata =~ m|([^<>]+)|gs;
3444 0         0 $info{ips} = [];
3445 0         0 for my $row ( @ips ) {
3446 0 0       0 if ( $row =~ m|host:addr ip="v\d">([^<>]+)
3447 0         0 push @{$info{ips}}, $1;
  0         0  
3448             }
3449             }
3450             # owner, ...
3451 0         0 foreach my $k ( keys %id ) {
3452 0 0       0 if ( $rdata =~ /([^<>]+)<\/host:$k>/ ) {
3453 0         0 $info{$id{$k}} = $1;
3454             }
3455             }
3456             # dates
3457 0         0 foreach my $k ( keys %dt ) {
3458 0 0       0 if ( $rdata =~ m|([^<>]+)| ) {
3459 0         0 $info{$dt{$k}} = cldate( $1 );
3460             }
3461             }
3462             }
3463              
3464             elsif ( $rdata =~ /^\s*
3465 0         0 $info{create} = {};
3466 0         0 ( $info{create}{dname} ) = $rdata =~ /([^<>]+)<\/domain:name>/;
3467              
3468 0 0       0 if ( $rdata =~ /([^<>]+)<\/domain:crDate>/ ) {
3469 0         0 $info{create}{date} = IO::EPP::Base::cldate( $1 );
3470             }
3471             }
3472              
3473             elsif ( $rdata =~ /^\s*
3474 0         0 $info{renew} = {};
3475 0         0 ( $info{renew}{dname} ) = $rdata =~ /([^<>]+)<\/domain:name>/;
3476             }
3477              
3478             elsif ( $rdata =~ /^\s*
3479 0         0 $info{transfer} = {};
3480 0         0 ( $info{transfer}{dname} ) = $rdata =~ /([^<>]+)<\/domain:name>/;
3481 0         0 ( $info{transfer}{status} ) = $rdata =~ /([^<>]+)<\/domain:trStatus>/;
3482              
3483             # sender, requestor
3484 0         0 foreach my $k ( keys %id ) {
3485 0 0       0 if ( $rdata =~ /([^<>]+)<\/domain:$k>/ ) {
3486 0         0 $info{transfer}{$id{$k}} = $1;
3487             }
3488             }
3489             # dates
3490 0         0 foreach my $k ( keys %dt ) {
3491 0 0       0 if ( $rdata =~ /([^<>]+)<\/domain:$k>/ ) {
3492 0         0 $info{transfer}{$dt{$k}} = IO::EPP::Base::cldate( $1 );
3493             }
3494             }
3495             }
3496              
3497             elsif ( $rdata =~ /^\s*
3498 0         0 $info{upd_del} = {};
3499 0         0 ( $info{upd_del}{result}, $info{upd_del}{dname} ) =
3500             $rdata =~ /([^<>]+)<\/domain:name>/;
3501              
3502 0 0       0 if ( $rdata =~ /(.+?)<\/domain:paTRID>/s ) {
3503 0         0 my $trids = $1;
3504 0 0       0 if ( $trids =~ /([0-9A-Za-z]+)<\/clTRID>/ ) {
3505 0         0 $info{upd_del}{cltrid} = $1;
3506             }
3507 0 0       0 if ( $trids =~ /([0-9A-Za-z\-]+)<\/svTRID>/ ) {
3508 0         0 $info{upd_del}{svtrid} = $1;
3509             }
3510             }
3511              
3512 0 0       0 if ( $rdata =~ /([^<>]+)<\/domain:paDate>/ ) {
3513 0         0 $info{upd_del}{date} = IO::EPP::Base::cldate( $1 );
3514             }
3515             }
3516              
3517             else {
3518 0         0 return ( 0, 'New poll message type!' );
3519             }
3520              
3521 0         0 return ( \%info, '' );
3522              
3523             }
3524              
3525              
3526             =head2 req_poll_ext
3527              
3528             Parse req poll extension
3529              
3530             Empty, for overwriting in children modules
3531              
3532             =cut
3533              
3534             sub req_poll_ext {
3535 0     0 1 0 return {};
3536             }
3537              
3538              
3539             =head2 req_poll
3540              
3541             Get and parse top message from poll
3542              
3543             No input params.
3544              
3545             OUTPUT:
3546              
3547             Еach provider has a lot of different types of messages.
3548             Only domain transfer messages are similar.
3549             They have something like this format:
3550              
3551             {
3552             'code' => '1301',
3553             'msg' => 'Command completed successfully; ack to dequeue',
3554             'count' => '1',
3555             'id' => '456789',
3556             'date' => '2020-02-02 20:02:02',
3557             'qmsg' => 'Transfer Requested.',
3558             'transfer' => {
3559             'dname' => 'example.com',
3560             'status' => 'pending',
3561             'senders_id' => '1111'.
3562             'requestors_id' => '999',
3563             'request_date' => '2001-01-01 01:01:01',
3564             'send_date' => '2001-01-06 01:01:01'
3565             },
3566             'svtrid' => '4569552848-1578703988000',
3567             'cltrid' => '1f80c34195a936dfb0d2bd0c414141414'
3568             };
3569              
3570             C – the registrar who made the transfer request;
3571              
3572             C – the registrar from which the domain is transferred;
3573              
3574             C – the start date of the transfer
3575              
3576             C – date when the transfer is completed, unless it is canceled or the domain is released;
3577              
3578             C – the status of the transfer, the most common meaning: C, C, C, C.
3579              
3580             =cut
3581              
3582             sub req_poll {
3583 1     1 1 4 my ( $self, undef ) = @_;
3584              
3585 1         3 my $cltrid = get_cltrid();
3586              
3587 1         7 my $body = <
3588             $$self{urn}{head}
3589            
3590            
3591             $cltrid
3592            
3593            
3594             RPOLL
3595              
3596 1         5 my $answ = $self->req( $body, 'req_poll' );
3597              
3598 1 50 33     9 if ( $answ and $answ =~ // ) {
3599 1         5 my $rcode = $1 + 0;
3600              
3601 1         2 my $msg = '';
3602 1 50       9 if ( $answ =~ /]*>(.+?)<\/msg>.+\/result>/s ) {
3603 1         4 $msg = $1;
3604             }
3605              
3606 1         3 my %info;
3607              
3608 1 50       4 if ( $rcode == 1301 ) {
3609 0 0       0 if ( $answ =~ /(.*)<\/msgQ>/s ) {
3610 0         0 $info{$1} = $2;
3611 0         0 $info{$3} = $4;
3612 0         0 my $q = $5;
3613              
3614 0 0 0     0 if ( $q and $q =~ /(.+)<\/qDate>.*(.+?)<\/msg>/s ) {
    0 0        
    0          
3615 0         0 $info{date} = IO::EPP::Base::cldate( $1 );
3616 0         0 $info{qmsg} = $3;
3617 0         0 $info{qmsg} =~ s/"/"/g;
3618 0 0       0 if ( $info{qmsg} =~ /\[CDATA\[/ ) {
3619 0         0 $info{qmsg} =~ s/
3620 0         0 $info{qmsg} =~ s/\]\]>//;
3621             }
3622             }
3623             # wihout special message
3624             elsif ( $q and $q =~ /(.+)<\/qDate>/s ) {
3625 0         0 $info{date} = IO::EPP::Base::cldate( $1 );
3626 0         0 $info{qmsg} = $q; #
3627             }
3628             elsif ( $q ) {
3629             # not standard
3630 0         0 $info{qmsg} = $q;
3631             }
3632             }
3633              
3634 0 0       0 if ( $answ =~ /(.+?)<\/resData>/s ) {
3635 0         0 my ( $rdata, $err ) = $self->req_poll_rdata( $1 );
3636              
3637 0 0 0     0 if ( !$rdata and $err ) {
3638 0 0       0 return wantarray ? ( 0, 0, $err ) : 0 ;
3639             }
3640              
3641 0         0 $info{$_} = $rdata->{$_} for keys %$rdata;
3642             }
3643              
3644 0 0       0 if ( $answ =~ /(.+?<\/extension>)/s ) {
3645 0         0 $info{ext} = $self->req_poll_ext( $1 );
3646             }
3647              
3648 0         0 ( $info{cltrid} ) = $answ =~ /([0-9A-Za-z\-]+)<\/clTRID>/;
3649 0         0 ( $info{svtrid} ) = $answ =~ /([0-9A-Za-z\-]+)<\/svTRID>/;
3650             }
3651              
3652 1 50       7 return wantarray ? ( \%info, $rcode, $msg ) : \%info;
3653             }
3654              
3655 0 0       0 return wantarray ? ( 0, 0, 'empty answer' ) : 0 ;
3656             }
3657              
3658             =head2 ask_poll
3659              
3660             Delete message from poll by id
3661              
3662             INPUT:
3663              
3664             C – id of the message to be removed from the queue.
3665              
3666             =cut
3667              
3668             sub ask_poll {
3669 0     0 1 0 my ( $self, $params ) = @_;
3670              
3671 0 0       0 return ( 0, 0, 'no msg_id' ) unless $params->{msg_id};
3672              
3673 0         0 my $cltrid = get_cltrid();
3674              
3675 0         0 my $body = <
3676             $$self{urn}{head}
3677            
3678            
3679             $cltrid
3680            
3681            
3682             APOLL
3683              
3684 0         0 my $answ = $self->req( $body, 'ask_poll' );
3685              
3686 0 0 0     0 if ( $answ && $answ =~ // ) {
3687 0         0 my $rcode = $1 + 0;
3688              
3689 0         0 my ( $msg ) = $answ =~ /]*>(.+)<\/msg>.+\/result>/s;
3690              
3691 0         0 my %info;
3692              
3693 0 0       0 if ( $answ =~ // ) {
3694 0         0 $info{msg_cnt} = $1;
3695 0         0 $info{msg_id} = $2;
3696             }
3697              
3698 0 0       0 return wantarray ? ( \%info, $rcode, $msg ) : \%info;
3699             }
3700              
3701             # Неконнект или ошибка запроса
3702             # По хорошему надо отделять неконнект от ошибки
3703 0 0       0 return wantarray ? ( 0, 0, 'empty answer' ) : 0 ;
3704             }
3705              
3706              
3707             =head2 logout
3708              
3709             Close session, disconnect
3710              
3711             No input parameters.
3712              
3713             =cut
3714              
3715             sub logout {
3716 77     77 1 3827 my ( $self ) = @_;
3717              
3718 77 50       168 return unless $self->{sock};
3719              
3720 77 50 33     203 unless ( $self->{test} || $self->{sock}->opened() ) {
3721 0         0 delete $self->{sock};
3722              
3723 0         0 return;
3724             }
3725              
3726 77         160 my $cltrid = get_cltrid();
3727              
3728 77         394 my $logout = <
3729             $$self{urn}{head}
3730            
3731            
3732             $cltrid
3733            
3734            
3735             LOGOUT
3736              
3737 77         217 $self->req( $logout, 'logout' );
3738              
3739 77 50       164 unless ( $self->{test} ) {
3740 0         0 close( $self->{sock} );
3741             }
3742              
3743 77         140 delete $self->{sock};
3744              
3745 77         163 return ( undef, '1500', 'ok' );
3746             }
3747              
3748              
3749             sub DESTROY {
3750 77     77   26843 my ( $self ) = @_;
3751              
3752 77         627 local ($!, $@, $^E, $?); # Protection against action-at-distance
3753              
3754 77 100       207 if ( $self->{sock} ) {
3755 73         191 $self->logout();
3756             }
3757              
3758 77 50       896 if ( $self->{log_fh} ) {
3759 0           close $self->{log_fh};
3760              
3761 0           delete $self->{log_fh};
3762             }
3763             }
3764              
3765              
3766             1;
3767              
3768             __END__