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