File Coverage

blib/lib/NOLookup/Whois/WhoisLookup.pm
Criterion Covered Total %
statement 38 94 40.4
branch 5 50 10.0
condition 1 3 33.3
subroutine 10 12 83.3
pod 4 4 100.0
total 58 163 35.5


line stmt bran cond sub pod time code
1             package NOLookup::Whois::WhoisLookup;
2              
3 1     1   131382 use strict;
  1         9  
  1         31  
4 1     1   5 use warnings;
  1         2  
  1         24  
5 1     1   585 use IO::Socket::INET6;
  1         28724  
  1         6  
6              
7 1     1   461 use vars qw(@ISA @EXPORT_OK);
  1         3  
  1         140  
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         9  
  1         70  
39             $Data::Dumper::Indent=1;
40              
41 1     1   7 use vars qw/$AUTOLOAD/;
  1         3  
  1         1505  
42              
43             sub AUTOLOAD {
44 8     8   98 my $self=shift;
45 8         59 $AUTOLOAD =~ s/.*:://;
46 8         51 return $self->get($AUTOLOAD);
47             }
48              
49             sub new {
50 2     2 1 843 my ($proto, $query, $whois_server, $whois_port, $client_ip)=@_;
51 2   33     17 my $class=ref $proto||$proto;
52 2         7 my $self=bless {},$class;
53              
54             # $query is required for something to happen
55 2 50       7 return $self unless $query;
56              
57             # defaults
58 2 50       6 $whois_server = 'whois.norid.no' unless ($whois_server);
59 2 50       15 $whois_port = 43 unless ($whois_port);
60              
61 2         7 return $self->lookup($query, $whois_server, $whois_port, $client_ip);
62             }
63              
64             sub get {
65 8     8 1 26 my ($self, $key) = @_;
66 8         23 $key=lc($key);
67 8 50       38 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 8         40 return $self->{$key};
73             }
74              
75             sub lookup {
76 2     2 1 6 my ($self, $query, $whois_server, $whois_port, $client_ip) = @_;
77              
78 2         5 my ($line, $text);
79              
80             #$client_ip = undef;
81            
82 2         30 my $sock = IO::Socket::INET6->new (
83             PeerAddr => $whois_server,
84             PeerPort => $whois_port,
85             Proto => 'tcp',
86             Timeout => 10,
87             );
88              
89 2 50       124590 unless($sock) {
90 2         23 $self->{errno} = $WHOIS_LOOKUP_ERR_NO_CONN;
91 2         21 return $self;
92             }
93              
94 0           $query = Encode::encode('UTF-8', $query);
95            
96 0 0         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           print $sock "-V v0,$client_ip -c utf-8 $query\n";
103             } else {
104 0           print $sock "-c utf-8 $query\n";
105             }
106            
107             # Read all answer lines into one long LF separated $text
108 0           while ($line = <$sock>) {
109 0           $text .= $line;
110             }
111 0           close $sock;
112 0           $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 0           $self->_parse($text);
118              
119 0 0         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 0           my ($dmy, $dtxt, $htxt) = split ('NORID Handle', $text);
132              
133 0           my $holder_whois;
134 0           my $domain_whois = NOLookup::Whois::WhoisLookup->new;
135              
136             #print STDERR "\n------\nparse domain text: '$dtxt'\n";
137 0           $domain_whois->_parse("\nNORID Handle" . $dtxt);
138              
139 0 0         if ($htxt) {
140 0           $holder_whois = NOLookup::Whois::WhoisLookup->new;
141             #print STDERR "\n------\nparse holder text: '$htxt'\n";
142 0           $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 0           return $self, $domain_whois, $holder_whois;
149              
150             }
151              
152 0 0         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           $self->{name_server_list} = 1;
156             }
157              
158             #print STDERR "\n\n====\nself after $query: ", Dumper $self;
159 0           return $self;
160             }
161              
162             sub _parse {
163 0     0     my ($self, $text)=@_;
164              
165 0           foreach my $line (split("\n",$text)) {
166             # Map all elements into the object key method and set the value
167 0           my ($key, $ix, $value);
168              
169             # Parse DNSSEC stuff, if present
170 0 0         if (($key,$value) = $line =~ m/^(DNSSEC)\.+:\s*(.+)$/) {
    0          
    0          
    0          
    0          
171 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           $key =~ y/ -/_/;
178             # multiple '_' are collapsed to one '_'
179 0           $key =~ s/_+/_/g;
180 0           $key = lc($key);
181             $self->{dnssec}->{$ix}->{$key} =
182 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 0           $key =~ y/ -/_/;
189 0           $key = lc($key);
190             $self->{$key} =
191 0 0         ($self->{$key} ? $self->{$key}."\n$value" : $value);
192              
193             } elsif (($key,$value) = $line =~ m/^(Created|Last updated):\s*(.+)$/) {
194 0           $key =~ y/ -/_/;
195 0           $key = lc($key);
196             $self->{$key} =
197 0 0         ($self->{$key} ? $self->{$key}."\n$value" : $value);
198              
199             } elsif (($key,$value) = $line =~ m/^(% )(.+)$/) {
200              
201 0 0         if ($value =~ m/(No match)$/) {
    0          
    0          
    0          
    0          
202 0           $self->{errno} = $WHOIS_LOOKUP_ERR_NO_MATCH;
203              
204             } elsif ($value =~ m/(Quota exceeded)$/) {
205 0           $self->{errno} = $WHOIS_LOOKUP_ERR_QUOTA_EXCEEDED;
206              
207             } elsif ($value =~ m/(Access denied)$/) {
208 0           $self->{errno} = $WHOIS_LOOKUP_ERR_NO_ACCESS;
209              
210             } elsif ($value =~ m/(Referral denied)$/) {
211 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           $self->{errno} = $WHOIS_LOOKUP_ERR_OTHER;
216            
217             } else {
218 0           $key = 'copyright';
219             $self->{$key} =
220 0 0         ($self->{$key} ? $self->{$key}."\n$value" : $value);
221             }
222             }
223             }
224 0           $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 0           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;