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 =~ /(.+?<\/TABLE>)/is; $resp = $1; $resp =~ s/

.+?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 =~ /(

Domain Name:<\/b>
.+?)


/s; $resp = $1; $ishtml = 1; } elsif ($tld eq 'ru' || $tld eq 'su') { $resp = _koi2win($resp); (undef, $resp) = split('',$resp); ($resp) = split('', $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|

(.+?)

|s) { return 0; } $resp = $1; $resp =~ s|||g; $resp =~ s|||g; $resp =~ s|
||g; $resp =~ s| | |g; } elsif ($tld eq 'in') { if ($resp =~ /Domain ID:\w{3,10}-\w{4}\n(.+?)\n\n/s) { $resp = $1; $resp =~ s/
//g; } else { return 0; } } elsif ($tld eq 'cn') { if ($resp =~ m|\n\n(.+?)\n
|s) { $resp = $1; $resp =~ s|||isg; $resp =~ s|||isg; $resp =~ s|||isg; $resp =~ s|||isg; $resp =~ s|.+?||isg; $resp =~ s|||isg; $resp =~ s|\n\s+|\n|sg; $resp =~ s|\n\n|\n|sg; } else { return 0; } } elsif ($tld eq 'ws') { if ($resp =~ /Whois information for .+?:(.+?)/s) { $resp = $1; $resp =~ s|||isg; $resp =~ s|||isg; $ishtml = 1; } else { return 0; } } elsif ($tld eq 'kz') { if ($resp =~ /Domain Name\.{10}/s && $resp =~ /
(.+?)<\/pre>/s) {
	    $resp = $1;
	} else {
	    return 0;
	}

    } else {
        return 'ERROR';
    }

    return wantarray ? ($resp, $ishtml) : $resp;
}

sub _domain_level {
    my ($str) = @_;
    my $dotcount = $str =~ tr/././;
    return $dotcount + 1;
}

sub _dlen {
    my ($str) = @_;
    return length($str) * _domain_level($str);
}


sub check_existance {
    $_ = $_[0];

    return undef if
        /is unavailable/is ||
        /No entries found for the selected source/is ||
        /Not found:/s ||
        /No match\./s ||
        /is available for/is ||
        /Not found/is &&
            !/ your query returns "NOT FOUND"/ &&
            !/Domain not found locally/ ||
        /No match for/is ||
        /No Objects Found/s ||
        /No domain records were found/s ||
        /No such domain/s ||
        /No entries found in the /s ||
        /Could not find a match for/s ||
        /Unable to find any information for your query/s ||
        /is not registered/s ||
        /no matching record/s ||
	/No match found\n/ ||
        /NOMATCH/s;

    return 1;
}

sub strip_whois {
    $_ = $_[0];

    s/The Data.+(policy|connection)\.\n//is;
    s/% NOTE:.+prohibited\.//is;
    s/Disclaimer:.+\*\*\*\n?//is;
    s/NeuLevel,.+A DOMAIN NAME\.//is;
    s/For information about.+page=spec//is;
    s/NOTICE: Access to.+this policy.//is;
    s/The previous information.+completeness\.//s;
    s/NOTICE AND TERMS OF USE:.*modify these terms at any time\.//s;
    s/TERMS OF USE:.*?modify these terms at any time\.//s;
    s/NOTICE:.*for this registration\.//s;

    s/By submitting a WHOIS query.+?DOMAIN AVAILABILITY.\n?//s;
    s/Registration and WHOIS.+?its accuracy.\n?//s;
    s/Disclaimer:.+?\*\*\*\n?//s;
    s/The .COOP Registration .+ Information\.//s;
    s/Whois Server Version \d+\.\d+.//is;
    s/NeuStar,.+www.whois.us\.//is;
    s/\n?Domain names in the \.com, .+ detailed information.\n?//s;
    s/\n?The Registry database .+?Registrars\.\n//s;
    s/\n?>>> Last update of .+? <<<\n?//;
    s/% .+?\n//gs;
    s/Domain names can now be registered.+?for detailed information.//s;

    s/^\n+//s;
    s/(?:\s*\n)+$/\n/s;

    $_;
}

# charset / conversion functions

sub _koi2win($) {
    my $val = $_[0];
    $val =~ tr/áâ÷çäåöúéêëìíîïðòóôõæèãþûýøùÿüàñÁÂ×ÇÄÅÖÚÉÊËÌÍÎÏÐÒÓÔÕÆÈÃÞÛÝßÙØÜÀѳ£/À-ÿ¨¸/;
    return $val;
}

sub _require_once ($) {
    my ($module) = @_;

    my $module_file = $module.'.pm';
    $module_file =~ s/::/\//g;

    unless ($INC{$module_file}) {
	eval "require $module";
	import $module;
    }
}

sub import {
    my $mypkg = shift;
    my $callpkg = caller;

    no strict 'refs';

    # export subs
    *{"$callpkg\::$_"} = \&{"$mypkg\::$_"} foreach ((@EXPORT, @_));
}

1;
__END__

=head1 NAME

Net::Whois::Raw - Get Whois information for domains

=head1 SYNOPSIS

  use Net::Whois::Raw;
  
  $dominfo = whois('perl.com');
  $dominfo = whois('funet.fi');
  $reginfo = whois('REGRU-REG-RIPN', 'whois.ripn.net');

  $arrayref = get_whois('yahoo.co.uk', undef, 'QRY_ALL');
  $text = get_whois('yahoo.co.uk', undef, 'QRY_LAST');
  ($text, $srv) = get_whois('yahoo.co.uk', undef, 'QRY_FIRST');

  ### if you do "use Net::Whois::Raw qw(
  #     $OMIT_MSG $CHECK_FAIL $CHECK_EXCEED
  #     $CACHE_DIR $CACHE_TIME $USE_CNAMES $TIMEOUT @SRC_IPS );
  ### you can use these:

  $OMIT_MSG = 1; # This will attempt to strip several known copyright
                messages and disclaimers sorted by servers.
                Default is to give the whole response.

  $OMIT_MSG = 2; # This will try some additional stripping rules
                if none are known for the spcific server.

  $CHECK_FAIL = 1; # This will return undef if the response matches
                one of the known patterns for a failed search,
                sorted by servers.
                Default is to give the textual response.

  $CHECK_FAIL = 2; # This will match against several more rules
                if none are known for the specific server.

  $CHECK_EXCEED = 1; # When this option is set, "die" will be called
                if connection rate to specific whois server have been
                exceeded

  $CACHE_DIR = "/var/spool/pwhois/"; # Whois information will be
                cached in this directory. Default is no cache.

  $CACHE_TIME = 60; # Cache files will be cleared after not accessed
                for a specific number of minutes. Documents will not be
                cleared if they keep get requested for, independent
                of disk space. Default is not to clear the cache.

  $USE_CNAMES = 1; # Use whois-servers.net to get the whois server
                name when possible. Default is to use the 
                hardcoded defaults.


  $TIMEOUT = 10; # Cancel the request if connection is not made within
                a specific number of seconds.

  @SRC_IPS = (11.22.33.44); # List of local IP addresses to
		use for WHOIS queries. Addresses will be used used
		successively in the successive queries

=head1 DESCRIPTION

Net::Whois::Raw queries WHOIS servers about domains.
The module supports recursive WHOIS queries.
Also queries via HTTP is supported for some TLDs.

Setting the variables $OMIT_MSG and $CHECK_FAIL will match the results
against a set of known patterns. The first flag will try to omit the
copyright message/disclaimer, the second will attempt to determine if
the search failed and return undef in such a case.

B: these checks merely use pattern matching; they will work
on several servers but certainly not on all of them.

=head1 FUNCTIONS

=over 3

=item whois( DOMAIN [, SRV [, WHICH_WHOIS]] )

Returns Whois information for C.
Without C argument default Whois server for specified domain name
zone will be used. Use 'www_whois' as server name to force
WHOIS querying via HTTP (only few TLDs are supported in HTTP queries).
Caching is supported: if $CACHE_DIR variable is set and there is cached
entry for that domain - information from the cache will be used.
C argument - look get_whois docs below.

=item get_whois( DOMAIN [, SRV [, WHICH_WHOIS]] )

Lower-level function to query Whois information for C.
Caching IS NOT supported (caching is implemented only in higher-level
C function).
Without C argument default Whois server for specified domain name
zone will be used.
C argument is used to access a results if recursive queries;
possible values:

C<'QRY_FIRST'> -
    returns results of the first query. Non't make recursive queries.
    In scalar context returns just whois text.
    In list context returns two values: whois text and whois server
    which was used to make query).

C<'QRY_LAST'> -
    returns results of the last query.
    In scalar context returns just whois text.
    In list context returns two values: whois text and whois server
    which was used to make query).
    This is the default option.

C<'QRY_ALL'> -
    returns results of the all queries of the recursive chain.
    Reference to array of references to hashes is returned.
    Hash keys: C - result of whois query, C -
    whois server which was used to make query.

=back

=head1 AUTHOR

Original author Ariel Brosh B, 
Inspired by jwhois.pl available on the net.

Since Ariel has passed away in September 2002:

Past maintainers Gabor Szabo B,
Corris Randall B

Current Maintainer: Walery Studennikov B

=head1 CREDITS

See file "Changes" in the distribution for the complete list of contributors.

=head1 CHANGES

See file "Changes" in the distribution

=head1 NOTE

Some users complained that the B statements in the module make their
CGI scripts crash. Please consult the entries on B and
B on L about exception handling in Perl.

=head1 COPYRIGHT

Copyright 2000-2002 Ariel Brosh.
Copyright 2003-2003 Gabor Szabo.
Copyright 2003-2003 Corris Randall.
Copyright 2003-2006 Walery Studennikov.

This package is free software. You may redistribute it or modify it under
the same terms as Perl itself.

I apologize for any misunderstandings caused by the lack of a clear
licence in previous versions.

=head1 COMMERCIAL SUPPORT

Not available anymore.

=head1 LEGAL

Notice that registrars forbid querying their whois servers as a part of
a search engine, or querying for a lot of domains by script. 
Also, omitting the copyright information (that was requested by users of this 
module) is forbidden by the registrars.

=head1 SEE ALSO

L, L.

=cut