package Net::Whois::Raw;
require Net::Whois::Raw::Data;
use strict;
use Carp;
use IO::Socket;
our @EXPORT = qw( whois get_whois );
our $VERSION = '1.33';
our ($OMIT_MSG, $CHECK_FAIL, $CHECK_EXCEED, $CACHE_DIR, $USE_CNAMES, $TIMEOUT, $DEBUG) = (0) x 7;
our $CACHE_TIME = 60;
our (%notfound, %strip, @SRC_IPS);
my $last_cache_clear_time;
sub whois_config {
my ($par) = @_;
my @parnames = qw(OMIT_MSG CHECK_FAIL CACHE_DIR CACHE_TIME USE_CNAMES TIMEOUT @SRC_IPS);
foreach my $parname (@parnames) {
if (exists($par->{$parname})) {
no strict 'refs';
${$parname} = $par->{$parname};
}
}
}
# get cached whois
sub whois {
my ($dom) = @_;
my $got_from_cache;
my $res = get_from_cache( $dom );
if ($res) {
$got_from_cache = 1;
} else {
$res = get_whois(@_);
}
unless ($got_from_cache) {
write_to_cache( $dom, $res );
}
return $res;
}
# obtain whois
sub get_whois {
my ($dom, $srv, $which_whois) = @_;
$which_whois ||= 'QRY_LAST';
my $whois = get_all_whois($dom, $srv, $which_whois eq 'QRY_FIRST')
or return undef;
if ($which_whois eq 'QRY_LAST') {
my $thewhois = $whois->[-1];
return wantarray ? ($thewhois->{text}, $thewhois->{srv}) : $thewhois->{text};
} elsif ($which_whois eq 'QRY_FIRST') {
my $thewhois = $whois->[0];
return wantarray ? ($thewhois->{text}, $thewhois->{srv}) : $thewhois->{text};
} else {
return $whois;
}
}
sub get_from_cache {
my ($dom) = @_;
return undef unless $CACHE_DIR;
mkdir $CACHE_DIR, 0755 unless -d $CACHE_DIR;
my $now = time;
if ($CACHE_TIME && (!$last_cache_clear_time || $last_cache_clear_time < $now - 60)) {
# clear the cache
foreach (glob("$CACHE_DIR/*.*")) {
my $mtime = (stat($_))[8];
my $elapsed = $now - $mtime;
unlink $_ if ($elapsed / 60 > $CACHE_TIME);
}
$last_cache_clear_time = time;
}
if (-f "$CACHE_DIR/$dom") {
if (open(I, "$CACHE_DIR/$dom")) {
my $res = join("", );
close(I);
return $res;
}
}
}
sub write_to_cache {
my ($dom, $whois) = @_;
return unless $CACHE_DIR && $dom && $whois;
if (open(O, ">$CACHE_DIR/$dom")) {
print O $whois;
close(O);
}
}
sub get_all_whois {
my ($dom, $srv, $norecurse) = @_;
$srv ||= get_srv( $dom );
if ($srv eq 'www_whois') {
my ($responce, $ishtml) = www_whois_query( $dom );
return $responce ? [ { text => $responce, srv => $srv } ] : $responce;
}
$dom =~ s/.NS$//i;
my @whois = recursive_whois($dom, $srv, [], $norecurse);
return process_whois_answers( \@whois, $dom );
}
sub get_srv {
my ($dom) = @_;
my $tld = uc get_dom_tld( $dom );
$tld =~ s/^XN--(\w)/XN---$1/;
if (grep { $_ eq $tld } @Net::Whois::Raw::Data::www_whois) {
return 'www_whois';
}
my $cname = "$tld.whois-servers.net";
my $srv = $Net::Whois::Raw::Data::servers{$tld} || $cname;
$srv = $cname if $USE_CNAMES && gethostbyname($cname);
return $srv;
}
sub get_dom_tld {
my ($dom) = @_;
my $tld;
if (_is_ipaddr($dom)) {
$tld = "IP";
} elsif (_domain_level($dom) == 1) {
$tld = "NOTLD";
} else {
my @alltlds = keys %Net::Whois::Raw::Data::servers;
@alltlds = sort { _dlen($b) <=> _dlen($a) } @alltlds;
foreach my $awailtld (@alltlds) {
$awailtld = lc $awailtld;
if ($dom =~ /(.+?)\.($awailtld)$/) {
$tld = $2;
last;
}
}
unless ($tld) {
my @tokens = split(/\./, $dom);
$tld = $tokens[-1];
}
}
return $tld;
}
sub split_domname {
my ($dom) = @_;
my $tld = get_dom_tld( $dom );
my $name;
if (uc $tld eq 'IP' || $tld eq 'NOTLD') {
$name = $dom;
} else {
$dom =~ /(.+?)\.$tld$/ or die "Can't match $tld in $dom";
$name = $1;
}
return ($name, $tld);
}
sub _is_ipaddr {
$_[0] =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
}
sub process_whois_answers {
my ($raw_whois, $dom) = @_;
my @processed_whois;
my $level = 0;
foreach my $whois_rec (@{$raw_whois}) {
$whois_rec->{level} = $level;
my $text = process_whois( $whois_rec, $dom );
if ($text) {
$whois_rec->{text} = $text;
push @processed_whois, $whois_rec;
}
$level++;
}
return \@processed_whois;
}
sub process_whois {
my ($whois_rec, $dom) = @_;
my $text = $whois_rec->{text};
my $srv = lc $whois_rec->{srv};
my $level = $whois_rec->{level} || 0;
my ($name, $tld) = split_domname( $dom );
if ($tld eq 'mu') {
if ($text =~ /.MU Domain Information\n(.+?\n)\n/s) {
$text = $1;
}
}
return $text unless $CHECK_FAIL || $OMIT_MSG || $CHECK_EXCEED;
my $exceed = $Net::Whois::Raw::Data::exceed{$srv};
if ($CHECK_EXCEED && $exceed && $text =~ /$exceed/s) {
if ($level == 0) {
die "Connection rate exceeded";
} else {
return undef;
}
}
*notfound = \%Net::Whois::Raw::Data::notfound;
*strip = \%Net::Whois::Raw::Data::strip;
my $notfound = $notfound{$srv};
my @strip = $strip{$srv} ? @{$strip{$srv}} : ();
my @lines;
MAIN: foreach (split(/\n/, $text)) {
if ($CHECK_FAIL && $notfound && /$notfound/) {
return undef;
};
if ($OMIT_MSG) {
foreach my $re (@strip) {
next MAIN if (/$re/);
}
}
push(@lines, $_);
}
local ($_) = join("\n", @lines, "");
if ($CHECK_FAIL > 1) {
return undef unless check_existance($_);
}
if ($OMIT_MSG > 1) {
$_ = strip_whois( $_ );
}
$_;
}
sub recursive_whois {
my ($dom, $srv, $was_srv, $norecurse) = @_;
my $lines = whois_query( $dom, $srv );
my $whois = join("", @{$lines});
my ($newsrv, $registrar);
foreach (@{$lines}) {
$registrar ||= /Registrar/ || /Registered through/;
if ( $registrar && !$norecurse && /Whois Server:\s*([A-Za-z0-9\-_\.]+)/ ) {
$newsrv = lc $1;
} elsif ($whois =~ /To single out one record, look it up with \"xxx\",/s) {
return recursive_whois( "=$dom", $srv, $was_srv );
} elsif (/ReferralServer: whois:\/\/([-.\w]+)/) {
warn "SEX!!!!\n";
$newsrv = $1;
last;
} elsif (/Contact information can be found in the (\S+)\s+database/) {
$newsrv = $Net::Whois::Raw::Data::ip_whois_servers{ $1 };
} elsif ((/OrgID:\s+(\w+)/ || /descr:\s+(\w+)/) && _is_ipaddr($dom)) {
my $val = $1;
if($val =~ /^(?:RIPE|APNIC|KRNIC|LACNIC)$/) {
$newsrv = $Net::Whois::Raw::Data::ip_whois_servers{ $val };
last;
}
} elsif (/^\s+Maintainer:\s+RIPE\b/ && _is_ipaddr($dom)) {
$newsrv = $Net::Whois::Raw::Data::servers{RIPE};
}
}
my @whois_recs = ( { text => $whois, srv => $srv } );
if ($newsrv && $newsrv ne $srv) {
warn "recurse to $newsrv\n" if $DEBUG;
return () if grep {$_ eq $newsrv} @$was_srv;
my @new_whois_recs = eval { recursive_whois( $dom, $newsrv, [@$was_srv, $srv]) };
my $new_whois = scalar(@new_whois_recs) ? $new_whois_recs[0]->{text} : '';
if ($new_whois && !$@ && check_existance($new_whois)) {
push @whois_recs, @new_whois_recs;
} else {
warn "recursive query failed\n" if $DEBUG;
}
}
return @whois_recs;
}
sub whois_query {
my ($dom, $srv) = @_;
# Prepare query
my $whoisquery = $dom;
if ($srv eq 'whois.crsnic.net') {
$whoisquery = "domain $whoisquery";
}
if ($srv eq 'whois.denic.de') {
$whoisquery = "-T dn,ace -C ISO-8859-1 $whoisquery";
}
if ($srv eq 'whois.nic.name') {
$whoisquery = "domain=$whoisquery";
}
# Prepare for query
my @sockparams;
if (scalar(@SRC_IPS)) {
my $src_ip = $SRC_IPS[0];
push @SRC_IPS, shift @SRC_IPS; # rotate ips
@sockparams = (PeerAddr => "$srv:43", LocalAddr => $src_ip);
} else {
@sockparams = "$srv:43";
}
print "QUERY: $whoisquery; SRV: $srv, ".
"OMIT_MSG: $OMIT_MSG, CHECK_FAIL: $CHECK_FAIL, CACHE_DIR: $CACHE_DIR, ".
"CACHE_TIME: $CACHE_TIME, USE_CNAMES: $USE_CNAMES, TIMEOUT: $TIMEOUT\n" if $DEBUG >= 2;
my $prev_alarm = 0;
my @lines;
# Make query
eval {
local $SIG{'ALRM'} = sub { die "Connection timeout to $srv" };
$prev_alarm = alarm $TIMEOUT if $TIMEOUT;
my $sock = new IO::Socket::INET(@sockparams) || Carp::confess "$srv: $!: ".join(', ', @sockparams);
if ($DEBUG >= 2) {
_require_once('Data::Dumper');
print "Socket: ".Dumper($sock);
}
print $sock "$whoisquery\r\n";
@lines = <$sock>;
close $sock;
};
alarm $prev_alarm;
die $@ if $@;
foreach (@lines) { s/\r//g; }
print "Received ".scalar(@lines)." lines\n" if $DEBUG >= 2;
return \@lines;
}
sub www_whois_query {
my ($dom) = (lc shift);
my ($name, $tld) = split_domname( $dom );
my ($url, $curl, %form);
if ($tld eq 'tv') {
$url = "http://www.tv/cgi-bin/whois.cgi?domain=$name&tld=tv";
} elsif ($tld eq 'mu') {
$url = 'http://www.mu/cgi-bin/mu_whois.cgi';
$form{whois} = $name;
} elsif ($tld eq 'spb.ru' || $tld eq 'msk.ru') {
$url = "http://www.relcom.ru/Services/Whois/?fullName=$name.$tld";
} elsif ($tld eq 'ru' || $tld eq 'su') {
$url = "http://www.nic.ru/whois/?domain=$name.$tld";
} elsif ($tld eq 'ip') {
$url = "http://www.nic.ru/whois/?ip=$name";
} elsif ($tld eq 'in') {
$url = "http://www.registry.in/cgi-bin/whois.cgi?whois_query_field=$name";
} elsif ($tld eq 'cn') {
$url = "http://ewhois.cnnic.net.cn/whois?value=$name.$tld&entity=domain";
} elsif ($tld eq 'ws') {
$url = "http://worldsite.ws/utilities/lookup.dhtml?domain=$name&tld=$tld";
} elsif ($tld eq 'kz') {
$url = "http://www.nic.kz/cgi-bin/whois?query=$name.$tld&x=0&y=0";
} else {
return 0;
}
# load-on-demand
unless ($INC{'LWP/UserAgent.pm'}) {
require LWP::UserAgent;
require HTTP::Request;
require URI::URL;
import LWP::UserAgent;
import HTTP::Request;
import URI::URL;
}
my $method = scalar(keys %form) ? 'POST' : 'GET';
my $ua = new LWP::UserAgent( parse_head => 0 );
my $req = new HTTP::Request $method, $url;
if ($method eq 'POST') {
$curl = url("http:");
$req->content_type('application/x-www-form-urlencoded');
$curl->query_form( %form );
$req->content( $curl->equery );
}
my $resp = eval {
local $SIG{ALRM} = sub { die "www_whois connection timeout" };
alarm 10;
$ua->request($req)->content;
};
alarm 0;
return undef if !$resp || $@ || $resp =~ /www_whois connection timeout/;
chomp $resp;
$resp =~ s/\r//g;
my $ishtml;
if ($tld eq 'tv') {
return 0 unless
$resp =~ /( Domain Name:<\/b>.+?<\/TABLE>)/is;
$resp = $1;
$resp =~ s/
', $resp);
$resp =~ s/ / /gi;
$resp =~ s/<([^>]|\n)*>//gi;
return 0 if ($resp=~ m/Äîìåííîå èìÿ .*? íå çàðåãèñòðèðîâàíî/i);
$resp = 'ERROR' if $resp =~ m/Error:/i || $resp !~ m/Èíôîðìàöèÿ î äîìåíå .+? \(ïî äàííûì WHOIS.RIPN.NET\):/;;
} elsif ($tld eq 'ip') {
unless ($resp =~ m|
.+?The data in The.+?any time.+?
//is;
return 0 if $resp =~ /Whois information is not available for domain/s;
$ishtml = 1;
} elsif ($tld eq 'spb.ru' || $tld eq 'msk.ru') {
$resp = _koi2win( $resp );
return undef unless $resp =~ m|
|s;
$resp = $1;
return 0 if $resp =~ m/ÑÂÎÁÎÄÍÎ/;
if ($resp =~ m|(.+?) (.+?)
|s) {
$resp = $1;
} elsif ($resp =~ m|DNS \(name-ñåðâåðàõ\):(.+?)
Äîïîëíèòåëüíóþ èíôîðìàöèþ ìîæíî ïîëó÷èòü ïî àäðåñó:
(.+?)
|) {
my $nameservers = $1;
my $emails = $2;
my (@nameservers, @emails);
while ($nameservers =~ m|(.+?)|g) {
push @nameservers, $1;
}
while ($emails =~ m|(.+?)|g) {
push @emails, $1;
}
if (scalar @nameservers && scalar @emails) {
$resp = '';
foreach my $ns (@nameservers) {
$resp .= "nserver: $ns\n";
}
foreach my $email (@emails) {
$resp .= "e-mail: $email\n";
}
}
}
} elsif ($tld eq 'mu') {
return 0 unless
$resp =~ /(
.+?)
/s;
$resp = $1;
$ishtml = 1;
} elsif ($tld eq 'ru' || $tld eq 'su') {
$resp = _koi2win($resp);
(undef, $resp) = split('',$resp);
($resp) = split('
(.+?)
|s) { return 0; } $resp = $1; $resp =~ s|