# -*- mode:CPerl -*- # N.B. revision control headers below reflect only recent work # $Header # $Id: Whois.pm,v 1.6 1999/12/01 14:00:51 dhudes Exp dhudes $ # $Log: Whois.pm,v $ # # Revision 1.6 1999/08/31 11:57:12 dhudes # Don't require 5.005, only 5.004 and don't do it in a BEGIN # per CPAN tester pudge@pobox.comm # # Revision 1.5 1999/08/29 15:09:26 dhudes # Fixes for new Network Solutions response when domain unregistered: # 1. break out of the loop that scans through the leading boilerplate # when the string "No match" is found as well as when "REGISTRANT" is found. # 2. If no match, set the MATCH to 0 (if there is a match, MATCH is set to 1) # and (very important) test this value before looking for the record fields # If match is 0, skip to the end and bless the structure. Caller tests by invoking the method 'ok' 1 match 0 no match # # Revision 1.401 1999/08/13 22:11:39 dhudes # Revised POD to reflect dual-maintainers # # Revision 1.4 1999/08/09 00:05:44 dhudes # local /$ rather than undef /$ # Thanks to Chris Nandor for pointing this out # # Revision 1.3 1999/08/08 22:53:54 dhudes # change \r and \n in network connection to \x0d\x0a for portability # # Revision 1.2 1999/07/25 03:01:04 dhudes # 1. Fix to address changes by Network Solutions in response to WHOIS requests # (strip out leading disclaimer) # 2. fix record created and record created internal tags # 3. Reformat POD portion # # Revision 1.1 1999/07/20 03:54:11 dhudes # Initial revision #
package Net::Whois; require 5.004; use strict; use Carp;
=head1 NAME
Net::Whois - Get and parse "whois" domain data from InterNIC
=head1 SYNOPSIS
Note that all fields except "name" and "tag" may be undef because "whois" information is erratically filled in.
use Net::Whois; use Carp;
my $w = new Net::Whois::Domain $dom or die "Can't connect to Whois server\n;
Net::Whois::Domain new() attempts to retrieve and parse the given domain's "whois" information from the InterNIC (whois.internic.net). If the server could not be contacted, is too busy, or otherwise does not process the query then the constructor does not return a reference and your object is undefined. If the constructor returns a reference, that reference can be used to access the various attributes of the domains' whois entry assuming that there was a match. The member function ok returns 1 if a match 0 if no match.
Note that the Locale::Country module (part of the Locale-Codes distribution) is used to recognize spelled-out country names; if that module is not present, only two-letter country abbreviations will be recognized.
The server consulted is "whois.internic.net". You can only get .org, .edu, .net, .com domains from Internic. Other whois servers for other Top-Level-Domains (TLD) return information in a different syntax and are not supported at this time. Also, only queries for domains are valid. Querying for a network will fail utterly since those are not kept in the whois.internic.net server (a future enhancement will add a network lookup function). Querying for NIC handles won't work since they have a different return syntax than a domain. Domains other than those listed won't work they're not in the server. A future enhancment planned will send the query to the appropriate server based on its TLD.
=head1 AUTHOR
Originally written by Chip Salzenberg (chip@pobox.com) in April of 1997 for Idle Communications, Inc. In September of 1998 Dana Hudes (dhudes@hudes.org) found this but it was broken and he needed it so he fixed it. In August, 1999 Dana and Chip agreed to become co-maintainers of the module. Dana released a new version of Net::Whois to CPAN and resumed active development.
=head1 COPYRIGHT
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. If you make modifications, the author would like to know so that they can be incorporated into future releases.
=cut
use IO::Socket; use IO::File; use Carp; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
BEGIN { if (eval { require Locale::Country }) { Locale::Country->import(qw(code2country country2code)); }else { *code2country = sub { ($_[0] =~ /^[^\W\d_]{2}$/i) && $_[0] }; *country2code = sub { undef }; } }
sub new { my $class = @_ ? shift : 'Net::Whois'; @_ == 1 or croak "usage: new $class DOMAIN"; my ($domain) = @_; my $text; my $retval; # my $FH = new IO::File ">> whois.log" or croak "Could not open log"; my ($sock, $target_server); my @fieldlist; # each element is one part of FQDN e.g. www.smartcard.com would be 3 entries 0-3 [0]=www [1]=smartcard [3]=com my $tld; my %info;
@fieldlist = split /\./, $domain; eval { # convert to one-entry/one-exit . replace individual out-of-path returns with die. eval will catch them. $tld = $fieldlist[$#fieldlist]; $tld =~ tr /a-z/A-Z/; #uppercase key $target_server = $TLDs {$tld}; $server_name = $target_server if defined $target_server; $sock = Net::Whois::_connect(); # print $sock "dom $domain\x0d\x0a"; print $sock "$domain\x0d\x0a"; { local $/; $text = <$sock>; } undef $sock; $text || die "No data returned from server"; if ($text =~ /single out one record/) { return unless $text =~ /\((.+?)\)[ \t]+\Q$domain\E\x0d?\x0a/i; my $newdomain = $1; $sock = Net::Whois::_connect(); print $sock "dom $newdomain\x0d\x0a"; { local $/; $text = <$sock>; } undef $sock; $text || die "No data from server"; } # 7/21/99 Network Solutions now put a bunch of garbage text before the beginning of the actual record # so we have to spin past it. ARIN records start with registrant name on the 2nd line. Both identify the whois server # on the first line. $text =~ s/^ +//gm; # if (defined $FH) { # print $FH $text; # }
my @text = split / *\x0d?\x0a/, $text; for (@text) {s/^ +//} my (@t, $t, $c); my $flag = 1; $t= shift @text; until (!defined $t || $t =~ /Registrant/ || $t =~ /No match/) { $t = shift @text; } $t =~ s/^\s//; #trim whitespace if ($t eq '') { $t = shift @text; } #if domain exists next line up is "Registrant" which we don't want, we want the name and tag of registrant $_ = $t; if (/Registrant/) { $t = shift @text; $info{'MATCH'} = 1; } elsif (/No match/) { $info{'MATCH'} = 0; } if ($info{'MATCH'} ) { @info{'NAME', 'TAG'} = ( $t =~ /^(.*)\s+\((\S+)\)$/) or die "Registrant Name not found in returned information"; @t = (); push @t, shift @text while $text[0]; $t = $t[$#t]; if (! defined $t) { # do nothing } elsif ( $t =~ /^(?:usa|u\.\s*s\.\s*a\.)$/i) { pop @t; $t = 'US'; } elsif (code2country( $t)) { pop @t; $t = uc $t; } elsif ( $c = country2code($t)) { pop @t; $t = uc $c; } elsif ( $t =~ /,\s*([^,]+?)(?:\s+\d{5}(?:-\d{4})?)?$/) { $t = $US_State{uc $1} ? 'US' : undef; } else { undef $t; } $info{ADDRESS} = [@t]; $info{COUNTRY} = $t; while (@text) { $t = shift @text; next if $t=~ /^$/; #discard blank line if ( $t =~ s/^domain name:\s+(\S+)$//i) { $info{DOMAIN} = $1; $info{DOMAIN} =~ tr/A-Z/a-z/ ; } elsif ( $t =~ /contact.*:$/i) { my @ctypes = ( $t =~ /\b(\S+) contact/ig); my @c; while ( $text[0] ) { last if $text[0] =~ /contact.*:$/i; push @c, shift @text; } @{ $info{CONTACTS} } {map {uc} @ctypes} = (\@c) x @ctypes; } elsif ( $t =~ /^Record created on (\S+)\.$/) { $info{RECORD_CREATED} = $1; } elsif ( $t =~ /^Record last updated on (\S+)\.$/) { $info{RECORD_UPDATED} = $1; } elsif ( $t =~ /^Domain servers/i) { my @s; shift @text unless $text[0]; while ( $t = shift @text) { #translate to lower case to match useage in DNS $t =~ tr/A-Z/a-z/; push @s, [split /\s+/, $t]; } $info{SERVERS} = \@s; } } } }; if ($@) { carp $@; undef $retval; } else { $retval = bless [\%info], $class; } return $retval; }
sub domain { my $self = shift; $self->[0]->{DOMAIN}; }
sub name { my $self = shift; $self->[0]->{NAME}; }
sub tag { my $self = shift; $self->[0]->{TAG}; }
sub address { my $self = shift; my $addr = $self->[0]->{ADDRESS}; wantarray ? @ $addr : join "\n", @$addr; }
sub country { my $self = shift; $self->[0]->{COUNTRY}; }
sub contacts { my $self = shift; $self->[0]->{CONTACTS}; }
sub servers { my $self = shift; $self->[0]->{SERVERS}; }
sub record_created { my $self = shift; $self->[0]->{RECORD_CREATED}; }
sub record_updated { my $self = shift; $self->[0]->{RECORD_UPDATED}; }
sub ok { my $self = shift; $self->[0]->{MATCH}; } ;