File Coverage

blib/lib/NOLookup/Whois/WhoisLookup.pm
Criterion Covered Total %
statement 70 94 74.4
branch 30 50 60.0
condition 1 3 33.3
subroutine 11 12 91.6
pod 4 4 100.0
total 116 163 71.1


line stmt bran cond sub pod time code
1             package NOLookup::Whois::WhoisLookup;
2              
3 1     1   126535 use strict;
  1         10  
  1         31  
4 1     1   5 use warnings;
  1         2  
  1         27  
5 1     1   507 use IO::Socket;
  1         21695  
  1         6  
6              
7 1     1   464 use vars qw(@ISA @EXPORT_OK);
  1         3  
  1         155  
8             @ISA = qw( Exporter );
9             @EXPORT_OK = qw / $WHOIS_LOOKUP_ERR_NO_CONN
10              
11             $WHOIS_LOOKUP_ERR_QUOTA_EXCEEDED
12             $WHOIS_LOOKUP_ERR_NO_ACCESS
13             $WHOIS_LOOKUP_ERR_REFERRAL_DENIED
14              
15             $WHOIS_LOOKUP_ERR_OTHER
16              
17             $WHOIS_LOOKUP_ERR_NO_MATCH
18              
19             /;
20              
21             # Error codes returned from the WhoisLookup module
22             # Ref. the Norid Whois API definition.
23              
24             # Connection problems
25             our $WHOIS_LOOKUP_ERR_NO_CONN = 100;
26              
27             # Controlled refuses
28             our $WHOIS_LOOKUP_ERR_QUOTA_EXCEEDED = 101;
29             our $WHOIS_LOOKUP_ERR_NO_ACCESS = 102;
30             our $WHOIS_LOOKUP_ERR_REFERRAL_DENIED = 103;
31              
32             # DB and other problems, all the 'ERROR - xxxx'
33             # See raw_text for details on the problem.
34             our $WHOIS_LOOKUP_ERR_OTHER = 104;
35              
36             our $WHOIS_LOOKUP_ERR_NO_MATCH = 105;
37              
38 1     1   11 use Data::Dumper;
  1         2  
  1         68  
39             $Data::Dumper::Indent=1;
40              
41 1     1   7 use vars qw/$AUTOLOAD/;
  1         2  
  1         1430  
42              
43             sub AUTOLOAD {
44 64     64   13978 my $self=shift;
45 64         338 $AUTOLOAD =~ s/.*:://;
46 64         216 return $self->get($AUTOLOAD);
47             }
48              
49             sub new {
50 28     28 1 1091 my ($proto, $query, $whois_server, $whois_port, $client_ip)=@_;
51 28   33     168 my $class=ref $proto||$proto;
52 28         85 my $self=bless {},$class;
53              
54             # $query is required for something to happen
55 28 100       100 return $self unless $query;
56              
57             # defaults
58 20 50       51 $whois_server = 'whois.norid.no' unless ($whois_server);
59 20 50       76 $whois_port = 43 unless ($whois_port);
60              
61 20         74 return $self->lookup($query, $whois_server, $whois_port, $client_ip);
62             }
63              
64             sub get {
65 64     64 1 148 my ($self, $key) = @_;
66 64         123 $key=lc($key);
67 64 50       207 if (exists $self->{"${key}_handle"} ) {
68 0         0 my @objs=(map { $self->new($_) }
69 0         0 split (m/\n/,$self->{"${key}_handle"}));
70 0 0       0 return ( wantarray ? @objs : $objs[0] );
71             }
72 64         304 return $self->{$key};
73             }
74              
75             sub lookup {
76 20     20 1 80 my ($self, $query, $whois_server, $whois_port, $client_ip) = @_;
77              
78 20         56 my ($line, $text);
79              
80             #$client_ip = undef;
81            
82 20         236 my $sock = IO::Socket::INET->new (
83             PeerAddr => $whois_server,
84             PeerPort => $whois_port,
85             Proto => 'tcp',
86             Timeout => 10,
87             );
88              
89 20 50       1711850 unless($sock) {
90 0         0 $self->{errno} = $WHOIS_LOOKUP_ERR_NO_CONN;
91             #print STDERR "SOCK ERR: $!\n";
92 0         0 return $self;
93             }
94              
95 20         393 $query = Encode::encode('UTF-8', $query);
96            
97 20 50       1866 if ($client_ip) {
98             # Use the special -V option to identify the client IP
99             # for proper rate limiting purposes.
100             # Note that the ip address of the proxy itself
101             # must be registered by Norid for this to work properly,
102             # if not, a referral error is returned.
103 0         0 print $sock "-V v0,$client_ip -c utf-8 $query\n";
104             } else {
105 20         2580 print $sock "-c utf-8 $query\n";
106             }
107            
108             # Read all answer lines into one long LF separated $text
109 20         972999 while ($line = <$sock>) {
110 636         2231 $text .= $line;
111             }
112 20         1895 close $sock;
113 20         564 $text = Encode::decode('UTF-8', $text);
114              
115             #print STDERR "text: $text\n";
116            
117             # Parse whois and map values into the self object.
118 20         2226 $self->_parse($text);
119              
120 20 100       113 if ($text =~ m/\nDomain Information\n/) {
121            
122             # If a domain name, or a domain handle, is looked up, the
123             # whois server may also return the holder info as a second
124             # block. The below code parses the domain and holder info and
125             # returns the data in separate objects.
126             #
127            
128             # Domain info is first block. Holder contact info is second
129             # block, but only if the full (but limited) registrarwhois
130             # service is used. Split the text and make two objects.
131            
132 4         88 my ($dmy, $dtxt, $htxt) = split ('NORID Handle', $text);
133              
134 4         11 my $holder_whois;
135 4         46 my $domain_whois = NOLookup::Whois::WhoisLookup->new;
136              
137             #print STDERR "\n------\nparse domain text: '$dtxt'\n";
138 4         21 $domain_whois->_parse("\nNORID Handle" . $dtxt);
139              
140 4 50       10 if ($htxt) {
141 4         12 $holder_whois = NOLookup::Whois::WhoisLookup->new;
142             #print STDERR "\n------\nparse holder text: '$htxt'\n";
143 4         17 $holder_whois->_parse("\nNORID Handle" . $htxt);
144             }
145             #print STDERR "self : ", Dumper $self;
146             #print STDERR "domain whois: ", Dumper $domain_whois;
147             #print STDERR "holder whois: ", Dumper $holder_whois if $holder_whois;
148              
149 4         85 return $self, $domain_whois, $holder_whois;
150              
151             }
152              
153 16 50       88 if ($text =~ m/\nHosts matching the search parameter\n/) {
154             # Set a method telling that a name_server_list is found,
155             # which is only the case when a host name is looked up.
156 0         0 $self->{name_server_list} = 1;
157             }
158              
159             #print STDERR "\n\n====\nself after $query: ", Dumper $self;
160 16         313 return $self;
161             }
162              
163             sub _parse {
164 28     28   103 my ($self, $text)=@_;
165              
166 28         737 foreach my $line (split("\n",$text)) {
167             # Map all elements into the object key method and set the value
168 754         1376 my ($key, $ix, $value);
169              
170             # Parse DNSSEC stuff, if present
171 754 50       5357 if (($key,$value) = $line =~ m/^(DNSSEC)\.+:\s*(.+)$/) {
    50          
    100          
    100          
    100          
172 0         0 $self->{dnssec}->{$key} = $value;
173              
174             } elsif (($key, $ix, $value) = $line =~ m/^(DS Key Tag|Algorithm|Digest Type|Digest|Key Flags|Key Protocol|Key Algorithm|Key Public)\s+(\d+)\.+:\s*(.+)$/) {
175             # Translate all DNSSEC stuff to methods
176             # replace spaces and - with _ for accessors.
177              
178 0         0 $key =~ y/ -/_/;
179             # multiple '_' are collapsed to one '_'
180 0         0 $key =~ s/_+/_/g;
181 0         0 $key = lc($key);
182             $self->{dnssec}->{$ix}->{$key} =
183 0 0       0 ($self->{dnssec}->{$ix}->{$key} ? $self->{dnssec}->{$ix}->{$key}."\n$value" : $value);
184              
185             #print STDERR "DNSSEC parse self: $key , $ix, $value\n--\n";
186              
187             } elsif (($key,$value) = $line =~ m/^(\w+[^.]+)\.{2,}\:\s*(.+)$/) {
188             # replace spaces and - with _ for accessors.
189 326         680 $key =~ y/ -/_/;
190 326         779 $key = lc($key);
191             $self->{$key} =
192 326 100       1329 ($self->{$key} ? $self->{$key}."\n$value" : $value);
193              
194             } elsif (($key,$value) = $line =~ m/^(Created|Last updated):\s*(.+)$/) {
195 48         122 $key =~ y/ -/_/;
196 48         97 $key = lc($key);
197             $self->{$key} =
198 48 100       198 ($self->{$key} ? $self->{$key}."\n$value" : $value);
199              
200             } elsif (($key,$value) = $line =~ m/^(% )(.+)$/) {
201              
202 220 50       916 if ($value =~ m/(No match)$/) {
    50          
    50          
    50          
    50          
203 0         0 $self->{errno} = $WHOIS_LOOKUP_ERR_NO_MATCH;
204              
205             } elsif ($value =~ m/(Quota exceeded)$/) {
206 0         0 $self->{errno} = $WHOIS_LOOKUP_ERR_QUOTA_EXCEEDED;
207              
208             } elsif ($value =~ m/(Access denied)$/) {
209 0         0 $self->{errno} = $WHOIS_LOOKUP_ERR_NO_ACCESS;
210              
211             } elsif ($value =~ m/(Referral denied)$/) {
212 0         0 $self->{errno} = $WHOIS_LOOKUP_ERR_REFERRAL_DENIED;
213              
214             } elsif ($value =~ m/(ERROR - )$/) {
215             # Details can be found in the raw_text
216 0         0 $self->{errno} = $WHOIS_LOOKUP_ERR_OTHER;
217            
218             } else {
219 220         352 $key = 'copyright';
220             $self->{$key} =
221 220 100       905 ($self->{$key} ? $self->{$key}."\n$value" : $value);
222             }
223             }
224             }
225 28         143 $self->{raw_text} = $text;
226              
227             #print STDERR "_parse self: ", Dumper $self, "\n";
228             #if (exists($self->{dnssec})) {
229             # print STDERR "_parse self DNSSEC: ", Dumper $self->{dnssec}, "\n";
230             #}
231              
232 28         78 return $self;
233             }
234              
235            
236             sub TO_JSON {
237 0     0 1   my ($whois) = @_;
238              
239 0           my $rh;
240              
241 0 0         if ($whois) {
242 0           foreach my $k (sort keys(%$whois)) {
243 0           my $a = $whois->$k;
244 0           $rh->{$k} = $whois->get($k);
245             }
246             }
247              
248             #use Data::Dumper;
249             #$Data::Dumper::Indent=1;
250             #print STDERR "rh: ", Dumper $rh;
251              
252 0           $rh;
253             }
254              
255             =pod
256              
257             =encoding ISO-8859-1
258              
259             =head1 NAME
260              
261             NOLookup::Whois::WhoisLookup - Lookup WHOIS data from Norid.
262              
263             =head1 SYNOPSIS
264              
265             use Encode;
266             use NOLookup::Whois::WhoisLookup;
267            
268             # The $SERVER and $PORT can be set to what you need.
269             # The defaults are the below, so in this case they don't
270             # change anything.
271             my $SERVER = 'whois.norid.no';
272             my $PORT = 43;
273              
274             # Example 1: Domain name lookup
275             # Decode the query when needed, like for IDNs
276             # or names with national characters.
277             my $q = decode('UTF-8', 'norid.no');
278              
279             my ($wh, $do, $ho) = NOLookup::Whois::WhoisLookup->new($q, $SERVER, $PORT);
280              
281             # $wh is always populated.
282             # For a domain lookup, the $do and $ho objects should be
283             # used to access the domain and holder information.
284             # In all other cases, $wh contains the information.
285             if ($wh->errno) {
286             print STDERR "Whois error: ", $wh->errno, "\n";
287             if ($wh->raw_text) {
288             print STDERR "Raw text : ", $wh->raw_text, "\n";
289             }
290             exit;
291             }
292             print $wh->post_address;
293             print $wh->domain_name;
294             print $wh->name;
295              
296             if ($do && $ho) {
297             # when a domain name or domain handle is looked up,
298             # $do contains the domain information,
299             # and $ho contains the holder information
300             print "Domain name : ", encode('UTF-8', $do->domain_name), "\n";
301             print "Holder name : ", encode('UTF-8', $ho->name), "\n";
302             print "Holder address: ", encode('UTF-8', $ho->post_address), "\n";
303             }
304              
305             # Example 2: Registrar lookup
306             $q = 'reg2-norid';
307             $wh = NOLookup::Whois::WhoisLookup->new($q);
308             unless ($wh->errno) {
309             print "Registrar name : ", encode('UTF-8', $wh->registrar_name), "\n";
310             print "Registrar email: ", $wh->email_address, "\n";
311             }
312              
313              
314              
315             =head1 DESCRIPTION
316              
317             This module provides an object oriented API for use with the
318             Norid whois service. It uses the command line based whois interface
319             internally to fetch information from Norid.
320              
321             The values in the objects are decoded to internal perl data.
322              
323             This code is stolen from Cpan package Net::Whois::Norid
324             and adapted to suit our needs.
325             Adaption was needed because create date etc. were not collected.
326             We could've considered using the module as it was, but it also
327             dragged in some more modules which seems a bit much for such a simple task.
328              
329             Also nice to produce some more error codes.
330              
331             =head2 METHODS
332              
333             =over 4
334              
335             =item new
336              
337             The constructor. Takes an optional lookup argument. Returns a new object.
338              
339             =item lookup
340              
341             Do a whois lookup in the Norid database and populate the object
342             from the result.
343              
344             =item get
345              
346             Use this to access any data parsed. Note that spaces and '-'s will be
347             converted to underscores (_). For the special "Handle" entries,
348             omitting the _Handle part will return a new NOLookup::Whois::WhoisLookup object.
349              
350             The method is case insensitive.
351              
352             =item TO_JSON
353              
354             Note: The name of this method is important,
355             must be upper case and name must not be changed!
356              
357             Provide a TO_JSON method for JSON usage, ref. TO_JSON discussion in
358             https://metacpan.org/pod/JSON
359              
360             JSON does not handles objects, as the internals are not known,
361             then we need a method to present the object as a hash structure for
362             JSON to use. This method does the conversion from object to a hash
363             ready for JSON encoding.
364              
365             =item AUTOLOAD
366              
367             This module uses the autoload mechanism to provide accessors for any
368             available data through the get mechanism above.
369              
370             =back
371              
372             =head1 SUPPORT
373              
374             For now, support questions should be sent to:
375              
376             E<lt>(nospam)info(at)norid.noE<gt>
377              
378             =head1 SEE ALSO
379              
380             L<http://www.norid.no/en>
381             L<https://www.norid.no/en/registrar/system/tjenester/whois-das-service>
382              
383             =head1 CAVEATS
384              
385             Some rows in the whois data, like address lines, might appear more than once.
386             In that case they are separated with line space.
387             For objects, an array is returned.
388              
389             =head1 AUTHOR
390              
391             Trond Haugen, E<lt>(nospam)info(at)norid.noE<gt>
392              
393             =head1 COPYRIGHT
394              
395             Copyright (c) 2017 Trond Haugen <(nospam)info(at)norid.no>.
396             All rights reserved.
397              
398             This program is free software; you can redistribute it and/or modify
399             it under the terms of the GNU General Public License as published by
400             the Free Software Foundation; either version 2 of the License, or
401             (at your option) any later version.
402              
403             =head1 LICENSE
404              
405             This library is free software. You can redistribute it and/or modify
406             it under the same terms as Perl itself.
407              
408             =cut
409              
410             1;