package IO::EPP::RIPN;
=encoding utf8
=head1 NAME
IO::EPP::RIPN
=head1 SYNOPSIS
use IO::EPP::RIPN;
# Parameters for LWP
my %sock_params = (
PeerHost => 'uap.tcinet.ru',
PeerPort => 8028, # 8027 for .SU, 8028 for .RU, 8029 for .РФ
SSL_key_file => 'key_file.pem',
SSL_cert_file => 'cert_file.pem',
LocalAddr => '1.2.3.4',
Timeout => 30,
);
# Create object, get greeting and call login()
my $conn = IO::EPP::RIPN->new( {
user => 'XXX-RU',
pass => 'XXXXXXXX',
sock_params => \%sock_params,
test_mode => 0, # real connect
} );
# Check domain
my ( $answ, $code, $msg ) = $conn->check_domains( { domains => [ 'my.ru', 'out.ru' ] } );
# Call logout() and destroy object
undef $conn;
=head1 DESCRIPTION
RIPN is the first organization the registry in the .ru tld.
Then it transferred functions of the registry into L<TCI|https://tcinet.ru>,
but all special headings in epp remained
Examlpe:
C<xsi:schemaLocation="http://www.ripn.net/epp/ripn-epp-1.0 ripn-epp-1.0.xsd">
instead of
C<xsi:schemaLocation="urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd">
Module overwrites IO::EPP::Base where there are differences from RFC
and work with tcinet epp using http api.
For details see:
L<https://tcinet.ru/documents/RU-RF/TechRules.pdf>,
L<https://tcinet.ru/documents/RU-RF/P2_RIPN-EPP.pdf>,
L<https://tcinet.ru/documents/SU/SUTechRules.pdf>,
L<https://tcinet.ru/documents/SU/SU_P2_RipnEPP.pdf>.
All documents -- L<https://tcinet.ru/documents/>.
IO::EPP::RIPN only works with .RU, .SU & .РФ cctlds.
For work with the new gtlds .ДЕТИ, .TATAR need use L<IO::EPP::TCI>.
Features:
Working over https;
Completely other contacts;
Non-standard domain transfer in the .su zone;
The domain:check function has an error: when checking the availability of a blocked domain, it responds that it is available.
The list of blocked domains should be downloaded from the Registrar panel.
=cut
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Cookies;
use Time::HiRes qw( time );
use IO::EPP::Base;
use parent qw( IO::EPP::Base );
use strict;
use warnings;
# Old TCI uses special headings
our $epp_head = '<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="http://www.ripn.net/epp/ripn-epp-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ripn.net/epp/ripn-epp-1.0 ripn-epp-1.0.xsd">';
our $epp_cont_urn =
'xmlns:contact="http://www.ripn.net/epp/ripn-contact-1.0" xsi:schemaLocation="http://www.ripn.net/epp/ripn-contact-1.0 ripn-contact-1.0.xsd"';
our $epp_host_urn =
'xmlns:host="http://www.ripn.net/epp/ripn-host-1.0" xsi:schemaLocation="http://www.ripn.net/epp/ripn-host-1.0 ripn-host-1.0.xsd"';
our $epp_dom_urn =
'xmlns:domain="http://www.ripn.net/epp/ripn-domain-1.0" xsi:schemaLocation="http://www.ripn.net/epp/ripn-domain-1.0 ripn-domain-1.0.xsd"';
our $epp_dom_urn_ru =
'xmlns:domain="http://www.ripn.net/epp/ripn-domain-1.1" xsi:schemaLocation="http://www.ripn.net/epp/ripn-domain-1.1 ripn-domain-1.1.xsd"';
our $epp_reg_urn =
'xmlns:registrar="http://www.ripn.net/epp/ripn-registrar-1.0" xsi:schemaLocation="http://www.ripn.net/epp/ripn-registrar-1.0 ripn-registrar-1.0.xsd"';
sub make_request {
my ( $action, $params ) = @_;
#$params = IO::EPP::Base::recursive_utf8_unflaged( $params );
my ( $code, $msg, $answ, $self );
unless ( $params->{conn} ) {
# Default:
$params->{sock_params}{PeerHost} ||= 'uap.tcinet.ru';
$params->{sock_params}{PeerPort} ||= 8028; # .RU
( $self, $code, $msg ) = IO::EPP::RIPN->new( $params );
unless ( $code and $code == 1000 ) {
goto END_MR;
}
}
else {
$self = $params->{conn};
}
$self->{critical_error} = '';
if ( $self->can( $action ) ) {
( $answ, $code, $msg ) = $self->$action( $params );
}
else {
$msg = "undefined command <$action>, request cancelled";
$code = 0;
}
END_MR:
$msg .= ', ' . $self->{critical_error} if $self->{critical_error};
my $full_answ = "code: $code\nmsg: $msg";
$answ = {} unless $answ && ref $answ;
$answ->{code} = $code;
$answ->{msg} = $msg;
return wantarray ? ( $answ, $full_answ, $self ) : $answ;
}
sub gen_pw {
my @chars = ( 'A'..'Z', 'a'..'z', '0'..'9', '!', '@', '$', '%', '*', '_', '.', ':', '-', '=', '+', '?', '#', ',' );
return join '', map( { $chars[ int rand( scalar @chars ) ] } 1..16 );
}
=head1 METHODS
=head2 new
If the C<alien_conn> parameter is received, it loads cookies from the file specified by C<load_cook_from>
=cut
sub new {
my ( $package, $params ) = @_;
my ( $self, $code, $msg );
my $sock_params = delete $params->{sock_params};
my $host = $sock_params->{PeerHost};
my $port = $sock_params->{PeerPort};
my $url = "https://$host:$port";
my $local_address = $sock_params->{LocalAddr};
my $timeout = $sock_params->{Timeout} || 5;
my %ua_params = ( ssl_opts => $sock_params );
$ua_params{local_address} = $local_address if $local_address;
if ( $timeout ) {
# LWP feature: first param for LWP, second - for IO::Socket
$ua_params{timeout} = $timeout;
$ua_params{Timeout} = $timeout;
}
my $cookie;
if ( $params->{alien_conn} ) {
$cookie = HTTP::Cookies->new( autosave => 0 );
unless ( $cookie->load( $params->{load_cook_from} ) ) {
$msg = "load cooker is fail";
$code = 0;
goto ERR;
}
}
else {
$cookie = HTTP::Cookies->new;
}
my $ua = LWP::UserAgent->new(
agent => 'EppBot/7.02 (Perl; Linux i686; ru, en_US)',
parse_head => 0,
keep_alive => 30,
cookie_jar => $cookie,
%ua_params,
);
unless ( $ua ) {
$msg = "can not connect";
$code = 0;
goto ERR;
}
$self = bless {
sock => $ua,
user => $params->{user},
url => $url,
cookies => $cookie,
no_log => delete $params->{no_log},
alien => $params->{alien_conn} ? 1 : 0,
};
$self->set_urn();
$self->set_log_vars( $params );
$self->epp_log( "Connect to $url\n" );
if ( $self->{alien} ) {
return wantarray ? ( $self, 1000, 'ok' ) : $self;
}
# Get HEADER only
$self->epp_log( "HEAD connect to $url from $local_address" );
my $request = HTTP::Request->new( HEAD => $url ); # не POST
my $response = $ua->request( $request );
my $rcode = $response->code;
$self->epp_log( "header answ code: $rcode" );
unless ( $rcode == 200 ) {
$code = 0;
$msg = "Can't open socket";
goto ERR;
}
my $headers = $response->headers;
my $length = $headers->content_length;
$self->epp_log( "header content-length == $length" );
if ( $length == 0 ) {
$code = 0;
$msg = "Can't open socket";
goto ERR;
}
my ( undef, $c0, $m0 ) = $self->hello();
unless ( $c0 && $c0 == 1000 ) {
$code = 0;
$msg = "Can't get greeting";
$msg .= '; ' . $self->{critical_error} if $self->{critical_error};
goto ERR;
}
my ( undef, $c1, $m1 ) = $self->login( delete $params->{pass} ); # no password in object
if ( $c1 && $c1 == 1000 ) {
return wantarray ? ( $self, $c1, $m1 ) : $self;
}
$msg = ( $m1 || '' ) . $self->{critical_error};
$code = $c1 || 0;
ERR:
return wantarray ? ( 0, $code, $msg ) : 0;
}
sub set_urn {
$_[0]->{urn} = {
head => $IO::EPP::RIPN::epp_head,
cont => $IO::EPP::RIPN::epp_cont_urn,
host => $IO::EPP::RIPN::epp_host_urn,
dom => $IO::EPP::RIPN::epp_dom_urn,
reg => $IO::EPP::RIPN::epp_reg_urn,
};
}
sub req {
my ( $self, $out_data, $info ) = @_;
return 0 unless $out_data && $self->{sock};
$info ||= '';
if ( $out_data ) {
my $d = $out_data;
# remove password, authinfo from log
$d =~ s/<pw>[^<>]+<\/pw>/<pw>xxxxx<\/pw>/;
$self->epp_log( "$info request:\n$d" );
}
my $request = HTTP::Request->new( POST => $self->{url} );
$request->content_type('text/xml');
$request->content_type_charset('UTF-8');
$request->content( $out_data );
my $start_time = time;
my $response = $self->{sock}->request( $request );
my $req_time = sprintf( '%0.4f', time - $start_time );
# print Dumper $response;
my $rcode = $response->code;
unless ( $rcode == 200 ) {
$self->{critical_error} = "Get answer code = $rcode";
return 0;
}
# feature of connection on epp over https
if ( $info eq 'login' ) {
$self->{cook} = $self->{sock}->cookie_jar->as_string;
$self->epp_log( "cookies: $$self{cook}" );
$self->{sessionid} = $response->header('set-cookie') || '';
$self->epp_log( "sessionid: $$self{sessionid}" );
}
my $in_data = $response->content;
$self->epp_log( "req_time: $req_time\n$info answer:\n$in_data\n" );
return $in_data;
}
=head2 login
Ext params for login,
INPUT: new password for change
=cut
sub login {
my ( $self, $pw, undef, undef, $new_pw ) = @_;
return 0 unless $pw;
my $npw = $new_pw ? "\n <newPW>$new_pw</newPW>" : '';
my ( $svcs, $ext ) = ( '', '' );
if ( $self->{user} =~ /-(RU|RF)$/ ) {
$svcs = "\n <objURI>http://www.ripn.net/epp/ripn-domain-1.1</objURI>";
# Does not work $ext = "\n <extURI>http://www.tcinet.ru/epp/tci-billing-1.0</extURI>";
}
my $cltrid = $self->get_cltrid();
my $body = <<LOGIN;
$$self{urn}{head}
<command>
<login>
<clID>$$self{user}</clID>
<pw>$pw</pw>$npw
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>http://www.ripn.net/epp/ripn-contact-1.0</objURI>
<objURI>http://www.ripn.net/epp/ripn-domain-1.0</objURI>$svcs
<objURI>http://www.ripn.net/epp/ripn-epp-1.0</objURI>
<objURI>http://www.ripn.net/epp/ripn-eppcom-1.0</objURI>
<objURI>http://www.ripn.net/epp/ripn-host-1.0</objURI>
<objURI>http://www.ripn.net/epp/ripn-registrar-1.0</objURI>
<svcExtension>
<extURI>urn:ietf:params:xml:ns:secDNS-1.1</extURI>$ext
</svcExtension>
</svcs>
</login>
<clTRID>$cltrid</clTRID>
</command>
</epp>
LOGIN
return $self->simple_request( $body, 'login' );
}
=head2 save_cookies
Save http connection cookies,
they can be used to create another connection on this IP address without opening a new session, that is, without a login
=cut
sub save_cookies {
my ( $self, $params ) = @_;
unless ( ref $params and $params->{save_cook_to} ) {
return wantarray ? ( 0, 0, 'no params' ) : 0;
}
my $cook = $self->{sock}->cookie_jar->as_string;
open( COOKFILE, '>', $params->{save_cook_to} ) or return ( 0, 0, "Can't open $$params{save_cook_to} file: $!" );
print COOKFILE "#LWP-Cookies-1.0\n";
print COOKFILE "$cook\n";
close COOKFILE;
my %info = ( cook => $cook );
$self->{cook} = $cook;
return wantarray ? ( \%info, 1000, 'ok' ) : \%info;
}
=head2 hello
For details, see L<IO::EPP::Base/hello>
=cut
sub hello {
my ( $self ) = @_;
my $body = <<HELLO;
$$self{urn}{head}
<hello/>
</epp>
HELLO
my $content = $self->req( $body, 'hello' );
return 0 unless $content && $content =~ /greeting/;
my $info = { code => 1000, msg => $content };
return wantarray ? ( $info, 1000, $content ) : $info;
}
=head2 cont_to_xml
Overrides the base class converter, since the contacts are very different here.
=cut
sub cont_to_xml {
my ( undef, $cont ) = @_;
my $is_person = $cont->{passport} ? 1 : 0;
my $txtcont .= $is_person ? "<contact:person>\n" : "<contact:organization>\n";
foreach my $type ( 'int', 'loc' ) {
$txtcont .= " <contact:".$type."PostalInfo>\n";
if ( $is_person ) {
$txtcont .= " <contact:name>".$$cont{$type}{name}."</contact:name>\n";
}
else {
$txtcont .= " <contact:org>".$$cont{$type}{org}."</contact:org>\n";
}
$$cont{$type}{addr} = [ $$cont{$type}{addr} ] unless ref $$cont{$type}{addr};
$txtcont .= " <contact:address>$_</contact:address>\n" foreach @{$$cont{$type}{addr}};
$txtcont .= " </contact:".$type."PostalInfo>\n";
}
unless ( $is_person ) {
$txtcont .= " <contact:legalInfo>\n";
$$cont{legal}{addr} = [ $$cont{legal}{addr} ] unless ref $$cont{legal}{addr};
$txtcont .= " <contact:address>$_</contact:address>\n" foreach @{$$cont{legal}{addr}};
$txtcont .= " </contact:legalInfo>\n";
}
if ( $$cont{taxpayerNumbers} ) {
$txtcont .= " <contact:taxpayerNumbers>$$cont{TIN}</contact:taxpayerNumbers>\n";
}
else {
$txtcont .= " <contact:taxpayerNumbers/>\n";
}
if ( $is_person ) {
$txtcont .= " <contact:birthday>$$cont{birthday}</contact:birthday>\n";
$$cont{passport} = [ $$cont{passport} ] unless ref $$cont{passport};
$txtcont .= " <contact:passport>$_</contact:passport>\n" foreach @{$$cont{passport}};
}
$$cont{phone} = [ $$cont{phone} ] unless ref $$cont{phone};
$txtcont .= " <contact:voice>$_</contact:voice>\n" foreach @{$$cont{phone}};
if ( $$cont{fax} ) {
$$cont{fax} = [ $$cont{fax} ] unless ref $$cont{fax};
$txtcont .= " <contact:fax>$_</contact:fax>\n" foreach @{$$cont{fax}};
}
else {
$txtcont .= " <contact:fax/>\n";
}
$$cont{email} = [ $$cont{email} ] unless ref $$cont{email};
$txtcont .= " <contact:email>$_</contact:email>\n" foreach @{$$cont{email}};
if ( $is_person ) {
$txtcont .= " </contact:person>\n";
}
else {
$txtcont .= " </contact:organization>\n";
}
if ( $$cont{verified} ) {
$txtcont .= " <contact:verified/>";
}
else {
$txtcont .= " <contact:unverified/>";
}
return $txtcont;
}
=head2 create_contact
Parameter names are maximally unified with other providers.
INPUT:
for individual:
C<name> — full name, need for C<int> and C<loc> types;
C<birthday> — date of birth;
C<passport> — identification card number, place and date of issue;
for legal entity:
C<org> — organization name
C<addr> — string or array with full legal address of the organization, need for C<legal> type data
common fields:
C<addr> — string or array with full address;
C<TIN> - taxpayer numbers;
C<phone> – string or array with phone numbers in international format,
you can specify a list of multiple phones,
the suffixes C<(sms)> and C<(transfer)> are used to mark phones for confirming transfers;
C<fax> – string or array with faxes, usually only required for legal entities;
C<email>;
C<verified> – the full name or name of the organization was confirmed by documents.
Examples:
Create person contact
my %pers = (
cont_id => 'MY-123456',
'int' => {
name => 'Igor I Igover',
addr => 'UA, 12345, Igorevsk, Igoreva str, 13',
},
loc => {.
name => 'Игорь Игоревич Игорев',.
addr => [ 'UA', '85012', 'Игоревск', 'ул. Игорева, д.12, Игореву И.И.' ],
},
TIN => '',
birthday => '2001-01-01',
passport => [ 'II662244', 'выдан Игоревским МВД УДМС', '1.1.2017' ],
phone => '+380.501234567',
fax => '',
email => 'mail@igor.name',
);
my ( $answ, $code, $msg ) = $conn->create_contact( \%pers );
# answer
{
'cont_id' => 'my-123456',
'cre_date' => '2020-01-11 10:10:10',
'cltrid' => '1710de82a0e9249277ffd713f51c8888',
'svtrid' => '4997598888'
};
Create legal entity contact
my %org = (
# cont_id - auto
'int' => {.
org => 'Igor Limited Liability Company',
addr => [ 'RU', '123456', 'Moscow', 'Igoreva str, 3', 'Igor LLC' ]
},
loc => {
org => 'ООО «Игорь»',
addr => [ 'RU, 123456, г. Москва, ул. Игорева, дом 3, ООО «Игорь»', 'охраннику' ],
},
legal => {.
addr => [ '125476, г.Москва, ул. Игорева, д.3' ],
},
TIN => '7777777777',
phone => [ '+7.4951111111', '+7.4951111111(transfer)' ],
fax => '+7.4951111111',
email => [ 'mail@igor.ru' ],
);
my ( $answ, $code, $msg ) = $conn->create_contact( \%org );
# answer
{
'cont_id' => 'e88c1fngsz1e',
'cre_date' => '2020-01-01 10:10:10',
'cltrid' => '6194b816dd3f5d3f417fd2cfe0c88888',
'svtrid' => '4997633333'
};
=cut
sub create_contact {
my ( $self, $params ) = @_;
$params->{cont_id} ||= IO::EPP::Base::gen_id( 16 );
return $self->SUPER::create_contact( $params );
}
=head2 cont_from_xml
Overrides the base class contact parser.
As a result, the get_contact_info function displays the request response in the registry as follows:
Individual
my ( $a, $m, $o ) = make_request( 'get_contact_info', { cont_id => 'my-123456' } );
# answer
{
'msg' => 'Command completed successfully',
'owner' => 'XXX-RU',
'int' => {
'name' => 'Igor I Igover',
'addr' => [
'UA, 12345, Igorevsk, Igoreva str, 13'
]
},
'cre_date' => '2020-01-10 10:10:10',
'phone' => [
'+380.501234567'
],
'email' => [
'mail@igor.name'
],
'loc' => {
'name' => 'Игорь Игоревич Игорев',
'addr' => [
'UA',
'85012',
'Игоревск',
'ул. Игорева, д.12, Игореву И.И.'
]
},
'fax' => [],
'creater' => 'XXX-RU',
'verified' => 0,
'statuses' => {
'ok' => '+'
},
'birthday' => '2001-01-01',
'passport' => [
'II662244',
'выдан Игоревским МВД УДМС',
'1.1.2017'
],
'code' => '1000'
};
Legal entity
my ( $a, $m, $o ) = make_request( 'get_contact_info', { cont_id => 'e88c1fngsz1e' } );
# answer
{
'msg' => 'Command completed successfully',
'owner' => 'XXX-RU',
'int' => {
'org' => 'Igor Limited Liability Company',
'addr' => [
'RU',
'123456',
'Moscow',
'Igoreva str, 3',
'Igor LLC'
]
},
'cre_date' => '2020-01-10 10:10:10',
'phone' => [
'+7.4951111111',
'+7.4951111111(transfer)'
],
'email' => [
'mail@igor.ru'
],
'loc' => {
'org' => 'ООО «Игорь»',
'addr' => [
'RU, 123456, г. Москва, ул. Игорева, дом 3, ООО «Игорь»',
'охраннику'
]
},
'fax' => [
'+7.4951111111'
],
'legal' => {
'addr' => [
'125476, г.Москва, ул. Игорева, д.3'
]
},
'creater' => 'XXX-RU',
'verified' => 0,
'statuses' => {
'ok' => '+'
},
'code' => '1000'
};
=cut
sub cont_from_xml {
my ( undef, $txtcont ) = @_;
my %cont;
my $is_person = ($txtcont =~ /contact:person/) ? 1 : 0;
my @ss = $txtcont =~ /<contact:status s="([^"]+)"\/>/g;
$cont{statuses}{$_} = '+' for @ss;
my %types = ( intPostalInfo => 'int', locPostalInfo => 'loc', legalInfo => 'legal' );
foreach my $type ( keys %types ) {
if ( $txtcont =~ /<contact:$type>(.+)<\/contact:$type>/s ) {
my $pi = $1;
if ( $pi =~ /<contact:name>([^<>]+)<\/contact:name>/ ) {
$cont{$types{$type}}{name} = $1;
}
if ( $pi =~ /<contact:org>([^<>]+)<\/contact:org>/ ) {
$cont{$types{$type}}{org} = $1;
}
$cont{$types{$type}}{addr} = [ $pi =~ /<contact:address>([^<>]+)<\/contact:address>/g ];
}
}
if ( $txtcont =~ /<contact:taxpayerNumbers>([^<>]+)<\/contact:taxpayerNumbers>/ ) {
$cont{TIN} = $1;
}
if ( $is_person ) {
if ( $txtcont =~ /<contact:birthday>([^<>]+)<\/contact:birthday>/ ) {
$cont{birthday} = $1;
}
$cont{passport} = [ $txtcont =~ /<contact:passport>([^<>]+)<\/contact:passport>/g ];
}
$cont{phone} = [ $txtcont =~ /<contact:voice>([^<>]+)<\/contact:voice>/g ];
$cont{fax} = [ $txtcont =~ /<contact:fax>([^<>]+)<\/contact:fax>/g ];
$cont{email} = [ $txtcont =~ /<contact:email>([^<>]+)<\/contact:email>/g ];
if ( $txtcont =~ /<contact:verified\/>/ ) {
$cont{verified} = 1;
}
elsif ( $txtcont =~ /<contact:unverified\/>/ ) {
$cont{verified} = 0;
}
my %id = %IO::EPP::Base::id;
foreach my $k ( keys %id ) {
if ( $txtcont =~ /<contact:$k>([^<>]+)<\/contact:$k>/ ) {
$cont{$id{$k}} = $1;
}
}
my %dt = %IO::EPP::Base::dt;
foreach my $k ( keys %dt ) {
if ( $txtcont =~ /<contact:$k>([^<>]+)<\/contact:$k>/ ) {
$cont{$dt{$k}} = $1;
$cont{$dt{$k}} =~ s/T/ /;
$cont{$dt{$k}} =~ s/\.\d+Z$//;
}
}
return \%cont;
}
=head2 transfer
Addition parameter for .SU, .NET.RU, .ORG.RU, .PP.RU:
C<sent_to> - registrar name which will receive the domain (here all on the contrary)
=cut
sub transfer {
my ( $self, $params ) = @_;
if ( $params->{to} ) {
$params->{addition} = "\n <domain:acID>$$params{sent_to}</domain:acID>";
}
if ( $params->{user} =~ /-(RU|RF)$/ ) {
$self->{urn}{dom} = $epp_dom_urn_ru;
}
my @res = $self->SUPER::transfer( $params );
$self->{urn}{dom} = $IO::EPP::RIPN::epp_dom_urn;
return @res;
}
=head2 get_registrar_info
Get Registrar data: white IP, email, whois data
=cut
sub get_registrar_info {
my ( $self ) = @_;
my $cltrid = $self->get_cltrid();
my $body = <<REGINFO;
$$self{urn}{head}
<command>
<info>
<registrar:info $$self{urn}{reg}>
<registrar:id>$$self{user}</registrar:id>
</registrar:info>
</info>
<clTRID>$cltrid</clTRID>
</command>
</epp>
REGINFO
my $answ = $self->req( $body, 'registrar_info' );
if ( $answ && $answ =~ /<result code="(\d+)">/ ) {
my $rcode = $1 + 0;
my $msg = '';
if ( $answ =~ /<result.+<msg[^<>]*>(.+)<\/msg>.+\/result>/s ) {
$msg = $1;
}
if ( $rcode != 1000 ) {
if ( $answ =~ /<reason>(.+)<\/reason>/s ) {
$msg .= '; ' . $1;
}
return wantarray ? ( 0, $rcode, $msg ) : 0;
}
my $info = {};
if ( $answ =~ /<resData>(.+)<\/resData>/s ) {
my $rdata = $1;
my %types = ( intPostalInfo => 'int', locPostalInfo => 'loc', legalInfo => 'legal' );
foreach my $type ( keys %types ) {
if ( $rdata =~ /<registrar:$type>(.+)<\/registrar:$type>/s ) {
my $pi = $1;
if ( $pi =~ /<registrar:org>([^<>]+)<\/registrar:org>/ ) {
$info->{$types{$type}}{org} = $1;
}
$info->{$types{$type}}{addr} = join(', ', $pi =~ /<registrar:address>([^<>]+)<\/registrar:address>/g );
}
}
if ( $rdata =~ /<registrar:taxpayerNumbers>([^<>]+)<\/registrar:taxpayerNumbers>/ ) {
$info->{TIN} = $1;
}
$info->{phone} = [ $rdata =~ /<registrar:voice>([^<>]+)<\/registrar:voice>/g ];
$info->{fax} = [ $rdata =~ /<registrar:fax>([^<>]+)<\/registrar:fax>/g ];
my @emails = $rdata =~ /(<registrar:email type="[^"]+">[^<>]+<\/registrar:email>)/g;
foreach my $e ( @emails ) {
if ( $e =~ /registrar:email type="([^"]+)">([^<>]+)<\/registrar:email/ ) {
$info->{emails}{$1} = $2;
}
}
if ( $rdata =~ /<registrar:www>([^<>]+)<\/registrar:www>/ ) {
$info->{www} = $1;
}
if ( $rdata =~ /<registrar:whois>([^<>]+)<\/registrar:whois>/ ) {
$info->{whois} = $1;
}
$info->{ips} = [ $rdata =~ /<registrar:addr ip="v\d">([0-9A-Fa-f.:]+)<\/registrar:addr>/g ];
my %dt = %IO::EPP::Base::dt;
foreach my $k ( keys %dt ) {
if ( $rdata =~ /<registrar:$k>([^<>]+)<\/registrar:$k>/ ) {
$info->{$dt{$k}} = $1;
$info->{$dt{$k}} =~ s/T/ /;
$info->{$dt{$k}} =~ s/\.\d+Z$//;
}
}
}
return wantarray ? ( $info, $rcode, $msg ) : $info;
}
return wantarray ? ( 0, 0, 'empty answer' ) : 0;
}
=head2 update_registrar
Changing Registrar data: white IP, email, whois data
INPUT:
key of params:
C<add> or C<rem>:
C<ips> -- arrayref of ipv4 or ipv6 address,
C<emails> - hashref where keys - email type, values - email
C<chg>:
C<www> - new web url
C<whois> - new whois url
=cut
sub update_registrar {
my ( $self, $params ) = @_;
return ( 0, 0, 'no params' ) unless ref $params;
my $cltrid = $self->get_cltrid();
my $add = '';
if ( $params->{add} ) {
if ( defined $params->{add}{ips} and ref $params->{add}{ips} ) {
foreach my $ip ( @{$params->{add}{ips}} ) {
if ( $ip =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
$add .= ' <registrar:addr ip="v4">' . $ip . "</registrar:addr>\n";
}
else {
$add .= ' <registrar:addr ip="v6">' . $ip . "</registrar:addr>\n";
}
}
}
if ( defined $params->{add}{emails} and ref $params->{add}{emails} ) {
foreach my $type ( @{$params->{add}{emails}} ) {
$add .= qq| <registrar:emailtype="$type">| . $$params{add}{emails}{$type} . "</registrar:email>\n";
}
}
}
if ( $add ) {
$add = "<registrar:add>\n$add </registrar:add>";
}
else {
$add = '<registrar:add/>'
}
my $rem = '';
if ( $params->{rem} ) {
if ( defined $params->{rem}{ips} && ref $params->{rem}{ips} ) {
foreach my $ip ( @{$params->{rem}{ips}} ) {
if ( $ip =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
$rem .= ' <registrar:addr ip="v4">' . $ip . "</registrar:addr>\n";
}
else {
$rem .= ' <registrar:addr ip="v6">' . $ip . "</registrar:addr>\n";
}
}
}
if ( defined $params->{rem}{emails} and ref $params->{rem}{emails} ) {
foreach my $type ( @{$params->{rem}{emails}} ) {
$rem .= qq| <registrar:emailtype="$type">| . $$params{rem}{emails}{$type} . "</registrar:email>\n";
}
}
}
if ( $rem ) {
$rem = "<registrar:rem>\n$rem </registrar:rem>";
}
else {
$rem = '<registrar:rem/>'
}
my $chg = '';
if ( $params->{chg} ) {
if ( $params->{chg}{www} ) {
$chg .= ' <registrar:www>' . $$params{chg}{www} . "</registrar:www>\n";
}
if ( $params->{chg}{whois} ) {
$chg .= ' <registrar:whois>' . $$params{chg}{www} . "</registrar:whois>\n";
}
}
if ( $chg ) {
$chg = "<registrar:chg>\n$chg </registrar:chg>";
}
else {
$chg = "<registrar:chg/>";
}
my $body = <<UPDREG;
$$self{urn}{head}
<command>
<update>
<registrar:update $$self{urn}{reg}>
<registrar:id>$$self{user}</registrar:id>
$add
$rem
$chg
</registrar:update>
</update>
<clTRID>$cltrid</clTRID>
</command>
</epp>
UPDREG
return $self->simple_request( $body, 'update_registrar' );
}
=head2 get_billing_info
INPUT:
keys of params:
C<date>,
C<period>: in days,
C<currency>: RUB.
=cut
sub get_billing_info {
my ( $self, $params ) = @_;
return ( 0, 0, 'no params' ) unless ref $params;
my $cltrid = $self->get_cltrid();
my $body = <<BILINFO;
$$self{urn}{head}
<command>
<info>
<billing:info xmlns:billing="http://www.tcinet.ru/epp/tci-billing-1.0">
<billing:type>balance</billing:type>
<billing:param>
<billing:date>$$params{date}</billing:date>
<billing:period unit="d">$$params{period}</billing:period>
<billing:currency>$$params{currency}</billing:currency>
</billing:param>
</billing:info>
</info>
<clTRID>$cltrid</clTRID>
</command>
</epp>
BILINFO
my $answ = $self->req( $body, 'billing_info' );
if ( $answ && $answ =~ /<result code=['"](\d+)['"]>/ ) {
my $rcode = $1 + 0;
my $msg = '';
if ( $answ =~ /<result.+<msg[^<>]*>(.+)<\/msg>.+\/result>/s ) {
$msg = $1;
}
if ( $rcode != 1000 ) {
if ( $answ =~ /<reason>(.+)<\/reason>/s ) {
$msg .= '; ' . $1;
}
return wantarray ? ( 0, $rcode, $msg ) : 0;
}
my $info = {};
if ( $answ =~ /<resData>(.+)<\/resData>/s ) {
my $rdata = $1;
my @billing = $rdata =~ /(<billing:[^<>]+>[^<>]+<\/billing:[^<>]+>)/g;
foreach my $row ( @billing ) {
if ( $row =~ /<billing:([A-Za-z]+)\b[^<>]*>([^<>]+)<\/billing:[^<>]+>/ ) {
$info->{$1} = $2;
}
}
$info->{calc_date} = delete $info->{calcDate};
$info->{calc_date} =~ s/T/ /;
$info->{calc_date} =~ s/\.\d+Z$//;
}
return wantarray ? ( $info, $rcode, $msg ) : $info;
}
return wantarray ? ( 0, 0, 'empty answer' ) : 0;
}
=head2 get_limits_info
How many requests are left in this hour
=cut
sub get_limits_info {
my ( $self ) = @_;
my $cltrid = $self->get_cltrid();
my $body = <<LIMINFO;
$$self{urn}{head}
<command>
<info>
<limits:info xmlns:limits="http://www.tcinet.ru/epp/tci-limits-1.0" xsi:schemaLocation="http://www.tcinet.ru/epp/tci-limits-1.0 tci-limits-1.0.xsd"/>
</info>
<clTRID>$cltrid</clTRID>
</command>
</epp>
LIMINFO
my $answ = $self->req( $body, 'limits_info' );
if ( $answ && $answ =~ /<result code=['"](\d+)['"]>/ ) {
my $rcode = $1 + 0;
my $msg = '';
if ( $answ =~ /<result.+<msg[^<>]*>(.+)<\/msg>.+\/result>/s ) {
$msg = $1;
}
if ( $rcode != 1000 ) {
if ( $answ =~ /<reason>(.+)<\/reason>/s ) {
$msg .= '; ' . $1;
}
return wantarray ? ( 0, $rcode, $msg ) : 0;
}
my $info = {};
if ( $answ =~ /<resData>(.+)<\/resData>/s ) {
my $rdata = $1;
my @limits = $rdata =~ /(<limits:[^<>]+>[^<>]+<\/limits:[^<>]+>)/g;
foreach my $row ( @limits ) {
if ( $row =~ /<limits:([^<>]+)>([^<>]+)<\/limits:[^<>]+>/ ) {
$info->{$1} = $2;
}
}
}
return wantarray ? ( $info, $rcode, $msg ) : $info;
}
return wantarray ? ( 0, 0, 'empty answer' ) : 0;
}
=head2 get_stat_info
Show domain statistics by metric
key of params:
C<metric> -- varians: C<domain>, C<domain_pending_transfer>, C<domain_pending_delete>, C<contact>, C<host>, C<all>
Now not work:
code="2400", msg="Command failed", reason="Internal server error"
=cut
sub get_stat_info {
my ( $self, $params ) = @_;
return ( 0, 0, 'no params' ) unless ref $params;
my $cltrid = $self->get_cltrid();
my $body = <<STATINFO;
$$self{urn}{head}
<command>
<info>
<stat:info xmlns:stat="http://www.tcinet.ru/epp/tci-stat-1.0">
<stat:metric name="$$params{metric}"/>
</stat:info>
</info>
<clTRID>$cltrid</clTRID>
</command>
</epp>
STATINFO
return $self->simple_request( $body, 'info' );
}
=head2 logout
Close session, disconnect
=cut
sub logout {
my ( $self ) = @_;
return 0 unless $self && $self->{sock};
return 0 if $self->{alien};
my $cltrid = $self->get_cltrid();
my $body = <<LOGOUT;
$$self{urn}{head}
<command>
<logout/>
<clTRID>$cltrid</clTRID>
</command>
</epp>
LOGOUT
# The answer doesn't matter
$self->req( $body, 'logout' );
delete $$self{sock};
delete $$self{cook};
delete $$self{cookies};
delete $$self{sessionid};
delete $$self{user};
delete $$self{url};
}
1;
__END__
=pod
=head1 AUTHORS
Vadim Likhota <vadiml@cpan.org>
=head1 COPYRIGHT
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
=cut