File Coverage

blib/lib/Net/Whois/IANA.pm
Criterion Covered Total %
statement 259 305 84.9
branch 100 160 62.5
condition 74 177 41.8
subroutine 34 36 94.4
pod 2 26 7.6
total 469 704 66.6


line stmt bran cond sub pod time code
1             package Net::Whois::IANA;
2             $Net::Whois::IANA::VERSION = '0.50';
3 10     10   1652286 use 5.006;
  10         39  
4              
5 10     10   90 use strict;
  10         68  
  10         315  
6 10     10   49 use warnings;
  10         22  
  10         546  
7              
8 10     10   68 use Carp ();
  10         25  
  10         181  
9 10     10   5812 use IO::Socket ();
  10         274220  
  10         309  
10 10     10   6124 use Net::CIDR ();
  10         73909  
  10         413  
11              
12 10     10   77 use base 'Exporter';
  10         27  
  10         3504  
13              
14             # ABSTRACT: Net::Whois::IANA - A universal WHOIS data extractor.
15              
16             our $WHOIS_PORT = 43;
17             our $WHOIS_TIMEOUT = 30;
18             our @DEFAULT_SOURCE_ORDER = qw(arin ripe apnic lacnic afrinic);
19              
20             our %IANA;
21             our @IANA;
22              
23             BEGIN {
24             # populate the hash at compile time
25              
26 10     10   250 %IANA = (
27             apnic => [ [ 'whois.apnic.net', $WHOIS_PORT, $WHOIS_TIMEOUT, \&apnic_query ], ],
28             ripe => [ [ 'whois.ripe.net', $WHOIS_PORT, $WHOIS_TIMEOUT, \&ripe_query ], ],
29             arin => [ [ 'whois.arin.net', $WHOIS_PORT, $WHOIS_TIMEOUT, \&arin_query ], ],
30             lacnic => [ [ 'whois.lacnic.net', $WHOIS_PORT, $WHOIS_TIMEOUT, \&lacnic_query ], ],
31             afrinic => [ [ 'whois.afrinic.net', $WHOIS_PORT, $WHOIS_TIMEOUT, \&afrinic_query ],
32             ],
33             );
34              
35 10         82 @IANA = sort keys %IANA;
36              
37             # accessors
38             # do not use AUTOLOAD - only accept lowercase function name
39             # define accessors at compile time
40 10         52 my @accessors = qw{country netname descr status source server inetnum inet6num cidr abuse fullinfo};
41              
42 10         27 foreach my $accessor (@accessors) {
43 10     10   82 no strict 'refs';
  10         36  
  10         1882  
44             *$accessor = sub {
45 26     26   35104 my ($self) = @_;
46 26 50       107 die qq[$accessor is a method call] unless ref $self;
47 26 50       103 return unless $self->{QUERY};
48 26         185 return $self->{QUERY}->{$accessor};
49 110         855 };
50             }
51              
52 10         72025 *desc = \&descr; # backward compatibility
53             }
54              
55             our @EXPORT = qw( @IANA %IANA );
56              
57             sub new ($) {
58              
59 9     9 0 3347423 my $proto = shift;
60 9   33     70 my $class = ref $proto || $proto;
61 9         24 my $self = {};
62              
63 9         67 bless $self, $class;
64              
65 9         47 return $self;
66             }
67              
68             sub whois_connect ($;$$) {
69 19     19 0 447984 my ( $host, $port, $timeout ) = @_;
70              
71 19 100       88 ( $host, $port, $timeout ) = @$host if ref $host;
72              
73 19   66     113 $port ||= $WHOIS_PORT;
74 19   66     102 $timeout ||= $WHOIS_TIMEOUT;
75              
76             #my $port = $host_ref->[1] || $WHOIS_PORT;
77             #my $timeout = $host_ref->[2] || $WHOIS_TIMEOUT;
78             #my $host = $host_ref->[0];
79 19         54 my $retries = 2;
80 19         41 my $sleep = 2;
81              
82 19         43 my $sock;
83              
84 19         65 foreach my $iter ( 0 .. $retries ) {
85 19         41 local $@;
86              
87             # catch errors
88 19 50       43 eval {
89 19         287 $sock = IO::Socket::INET->new(
90             PeerAddr => $host,
91             PeerPort => $port,
92             Timeout => $timeout,
93             );
94 19         4086267 1;
95             } and return $sock;
96              
97 0         0 Carp::carp "Cannot connect to $host at port $port";
98 0         0 Carp::carp $@;
99 0 0       0 sleep $sleep unless $iter == $retries; # avoid the last sleep
100             }
101 0         0 return 0;
102             }
103              
104             sub is_valid_ipv4 ($) {
105              
106 22     22 0 52 my $ip = shift;
107              
108 22   100     546 return $ip
109             && $ip =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/
110              
111             # not absolutely correct
112             && ( ( $1 + 0 ) | ( $2 + 0 ) | ( $3 + 0 ) | ( $4 + 0 ) ) < 0x100;
113             }
114              
115             sub is_valid_ipv6 {
116 0     0 0 0 my ($ip) = @_;
117              
118             return
119 0 0 0     0 if $ip =~ /^:[^:]/
120             || $ip =~ /[^:]:$/; # Can't have single : on front or back
121              
122 0         0 my @seg = split /:/, $ip, -1; # -1 to keep trailing empty fields
123             # Clean up leading/trailing double colon effects.
124 0 0       0 shift @seg if $seg[0] eq '';
125 0 0       0 pop @seg if $seg[-1] eq '';
126              
127 0         0 my $max = 8;
128 0 0       0 if ( $seg[-1] =~ tr/.// ) {
129 0 0       0 return unless is_valid_ipv4( pop @seg );
130 0         0 $max -= 2;
131             }
132              
133 0         0 my $cmp;
134 0         0 for my $seg (@seg) {
135 0 0       0 if ( $seg eq '' ) {
136              
137             # Only one compression segment allowed.
138 0 0       0 return if $cmp;
139 0         0 ++$cmp;
140 0         0 next;
141             }
142 0 0       0 return if $seg =~ /[^0-9a-fA-F]/;
143 0 0 0     0 return if length $seg == 0 || length $seg > 4;
144             }
145 0 0       0 if ($cmp) {
146              
147             # If compressed, we need fewer than $max segments, but at least 1
148 0   0     0 return ( @seg && @seg < $max ) && 1; # true returned as 1
149             }
150              
151             # Not compressed, all segments need to be there.
152 0         0 return $max == @seg;
153             }
154              
155             # Is valid IP v4 or IP v6 address.
156             sub is_valid_ip ($) {
157 23     23 0 72 my ($ip) = @_;
158              
159 23 100       100 return unless defined $ip; # shortcut earlier
160 22 50       131 return index( $ip, ':' ) >= 0 ? is_valid_ipv6($ip) : is_valid_ipv4($ip);
161             }
162              
163             sub set_source ($$) {
164              
165 16     16 0 57 my $self = shift;
166 16         35 my $source = shift;
167              
168 16 100 50     62 $self->{source} = {%IANA} || return 0 unless $source;
169 16 100       50 return 0 unless $source;
170 14 50       50 unless ( ref $source ) {
171 14 100       70 if ( $IANA{$source} ) {
172 13         125 $self->{source} = { $source => $IANA{$source} };
173 13         44 return 0;
174             }
175 1         3 return 1;
176             }
177             return 2
178             unless ref $source eq 'HASH'
179 0 0 0     0 && scalar grep { ref $_ && ref $_ eq 'ARRAY' && @{$_} && ref $_->[0] && ref $_->[0] eq 'ARRAY' && @{ $_->[0] } && $_->[0][0] } values %{$source} == scalar keys %{$source};
  0 0 0     0  
  0   0     0  
  0   0     0  
  0   0     0  
  0   0     0  
180 0         0 $self->{source} = $source;
181 0         0 return 0;
182             }
183              
184             sub init_query ($%) {
185              
186 19     19 0 84 my $self = shift;
187 19         64 my %param = @_;
188              
189 19 100       101 if ( !is_valid_ip( $param{-ip} ) ) {
190 3         36 warn q{
191             Method usage:
192             $iana->whois_query(
193             -ip=>$ip,
194             -debug=>$debug, # optional
195             -whois=>$whois | -mywhois=>\%mywhois, # optional
196             };
197 3         14 return {};
198             }
199              
200 16   66     100 my $set_source = $self->set_source( $param{-whois} || $param{-mywhois} );
201 16 100       91 if ( $set_source == 1 ) {
    50          
202 1         33 warn "Unknown whois server requested. Known servers are:\n";
203 1         12 warn join( ", ", @IANA ) . "\n";
204 1         5 return {};
205             }
206             elsif ( $set_source == 2 ) {
207 0         0 warn q{
208             Custom sources must be of form:
209             %source = (
210             source_name1 => [
211             [ source_host, source_port || undef, source_timeout || undef, \&source_query || undef ],
212             ],
213             source_name1 => [
214             [ source_host, source_port || undef, source_timeout || undef, \&source_query || undef ],
215             ],
216             ...,
217             );
218             };
219             }
220             }
221              
222             sub source_connect ($$) {
223 64     64 0 1446 my ( $self, $source_name ) = @_;
224              
225 64         98 foreach my $server_ref ( @{ $self->{source}{$source_name} } ) {
  64         192  
226 17 50       60 if ( my $sock = whois_connect($server_ref) ) {
227 17         88 my ( $whois_host, $whois_port, $whois_timeout, $query_code ) = @{$server_ref};
  17         140  
228 17 50 33     258 $self->{query_sub} = $query_code
229             && ref $query_code eq 'CODE' ? $query_code : \&default_query;
230 17         77 $self->{whois_host} = $whois_host;
231 17         193 return $sock;
232             }
233             }
234 47         224 return undef;
235             }
236              
237             sub post_process_query (%) {
238              
239 14     14 0 161 my %query = @_;
240 14         117 for my $qkey ( keys %query ) {
241 264 50       712 chomp $query{$qkey} if defined $query{$qkey};
242             $query{abuse} = $query{$qkey} and last
243 264 100 50     969 if $qkey =~ /abuse/i && $query{$qkey} =~ /\@/;
      100        
244             }
245 14 100       98 unless ( $query{abuse} ) {
246 12 100 66     2008 if ( $query{fullinfo} && $query{fullinfo} =~ /(\S*abuse\S*\@\S+)/m ) {
    100 66        
      66        
247 9         63 $query{abuse} = $1;
248             }
249             elsif ( $query{email} || $query{'e-mail'} || $query{orgtechemail} ) {
250             $query{abuse} =
251 1   33     14 $query{email} || $query{'e-mail'} || $query{orgtechemail};
252             }
253             }
254 14 100       59 if ( !ref $query{cidr} ) {
255 2 50 33     71 if ( defined $query{cidr} && $query{cidr} =~ /\,/ ) {
256 0         0 $query{cidr} = [ split( /\s*\,\s*/, $query{cidr} ) ];
257             }
258             else {
259 2         11 $query{cidr} = [ $query{cidr} ];
260             }
261             }
262              
263 14         372 return %query;
264             }
265              
266             sub whois_query ($%) {
267 19     19 1 20562 my ( $self, %params ) = @_;
268              
269 19         148 $self->init_query(%params);
270 19         112 $self->{QUERY} = {};
271              
272 19         73 for my $source_name (@DEFAULT_SOURCE_ORDER) {
273 64 50       178 print STDERR "Querying $source_name ...\n" if $params{-debug};
274 64   100     158 my $sock = $self->source_connect($source_name)
275             || Carp::carp "Connection failed to $source_name." && next;
276 17         202 my %query = $self->{query_sub}( $sock, $params{-ip} );
277              
278 17 100       312 next unless keys %query;
279 0         0 do { Carp::carp "Warning: permission denied at $source_name server $self->{whois_host}\n"; next }
  0         0  
280 14 50 33     110 if $query{permission} && $query{permission} eq 'denied';
281 14         63 $query{server} = uc $source_name;
282 14         230 $self->{QUERY} = { post_process_query(%query) };
283              
284 14         487 return $self->{QUERY};
285             }
286              
287 5         24 return {};
288             }
289              
290             sub default_query ($$) {
291              
292 0     0 0 0 return arin_query(@_);
293             }
294              
295             sub ripe_read_query ($$) {
296              
297 4     4 0 12 my ( $sock, $ip ) = @_;
298              
299 4         19 my %query = ( fullinfo => '' );
300 4         434 print $sock "-r $ip\n";
301 4         44206 while (<$sock>) {
302 171         433 $query{fullinfo} .= $_;
303 171 50 0     523 close $sock and return ( permission => 'denied' ) if /ERROR:201/;
304 171 100 100     16567 next if ( /^(\%|\#)/ || !/\:/ );
305 88         388 s/\s+$//;
306 88         299 my ( $field, $value ) = split( /:/, $_, 2 );
307 88         346 $value =~ s/^\s+//;
308 88 100       568 $query{ lc($field) } .= ( $query{ lc($field) } ? ' ' : '' ) . $value;
309             }
310 4         610 close $sock;
311 4         112 return %query;
312             }
313              
314             sub ripe_process_query (%) {
315              
316 4     4 0 73 my %query = @_;
317              
318 4 50 33     137 if (
    50 33        
      33        
      33        
      33        
      33        
      33        
      33        
319             ( defined $query{remarks} && $query{remarks} =~ /The country is really world wide/ )
320             || ( defined $query{netname}
321             && $query{netname} =~ /IANA-BLK/ )
322             || ( defined $query{netname}
323             && $query{netname} =~ /AFRINIC-NET-TRANSFERRED/ )
324             || ( defined $query{country}
325             && $query{country} =~ /world wide/ )
326             ) {
327 0         0 return ();
328             }
329             elsif ( !$query{inet6num} && !$query{inetnum} ) {
330 0         0 return ();
331             }
332             else {
333 4         15 $query{permission} = 'allowed';
334 4   33     63 $query{cidr} = [ Net::CIDR::range2cidr( uc( $query{inet6num} || $query{inetnum} ) ) ];
335             }
336 4         1547 return %query;
337             }
338              
339             sub ripe_query ($$) {
340 4     4 0 15 my ( $sock, $ip ) = @_;
341              
342 4         21 my %query = ripe_read_query( $sock, $ip );
343 4 50       32 return () unless defined $query{country};
344 4         34 return ripe_process_query(%query);
345             }
346              
347             sub apnic_read_query ($$) {
348 5     5 0 16 my ( $sock, $ip ) = @_;
349              
350 5         27 my %query = ( fullinfo => '' );
351 5         12 my %tmp;
352 5         814 print $sock "-r $ip\n";
353 5         29 my $skip_block = 0;
354 5         3571002 while (<$sock>) {
355 190         430 $query{fullinfo} .= $_;
356 190 50 0     406 close $sock and return ( permission => 'denied' ) if /^\%201/;
357 190 100       597 if (m{^\%}) {
358              
359             # Always skip 0.0.0.0 data
360             # It looks like:
361             # % Information related to '0.0.0.0 - 255.255.255.255'
362 32 50       95 if (m{^\%.*0\.0\.0\.0\s+}) {
363 0         0 $skip_block = 1;
364 0         0 next;
365             }
366 32         59 $skip_block = 0;
367 32         152 next;
368             }
369 158 50       308 next if $skip_block;
370 158 100       22033 next if ( !/\:/ );
371 116         456 s/\s+$//;
372 116         385 my ( $field, $value ) = split( /:/, $_, 2 );
373 116         280 $value =~ s/^\s+//;
374 116 100       266 if ( $field =~ /^inet6?num$/ ) {
375 6 50       23 next if $value =~ m{0\.0\.0\.0\s+};
376 6         40 %tmp = %query;
377 6         20 %query = ();
378 6         21 $query{fullinfo} = $tmp{fullinfo};
379             }
380 116         191 my $lc_field = lc($field);
381 116 100 100     309 next if $lc_field eq 'country' && defined $query{$lc_field};
382 115 100       629 $query{$lc_field} .= ( $query{$lc_field} ? ' ' : '' ) . $value;
383             }
384 5         598 close $sock;
385 5         30 for ( keys %tmp ) {
386 17 100       80 $query{$_} = $tmp{$_} if !defined $query{$_};
387             }
388 5         120 return %query;
389             }
390              
391             sub apnic_process_query (%) {
392 3     3 0 23 my %query = @_;
393              
394 3 50 66     96 if (
    50 33        
      33        
      33        
395             ( defined $query{remarks} && $query{remarks} =~ /address range is not administered by APNIC|This network in not allocated/ )
396             || ( defined $query{descr}
397             && $query{descr} =~ /not allocated to|by APNIC|placeholder reference/i )
398             ) {
399 0         0 return ();
400             }
401             elsif ( !$query{inet6num} && !$query{inetnum} ) {
402 0         0 return ();
403             }
404             else {
405 3         12 $query{permission} = 'allowed';
406 3   33     37 $query{cidr} = [ Net::CIDR::range2cidr( uc( $query{inet6num} || $query{inetnum} ) ) ];
407             }
408              
409 3         846 return %query;
410             }
411              
412             sub apnic_query ($$) {
413 3     3 0 11 my ( $sock, $ip ) = @_;
414              
415 3         15 my %query = apnic_read_query( $sock, $ip );
416 3         20 return apnic_process_query(%query);
417             }
418              
419             sub arin_read_query ($$) {
420 3     3 0 9 my ( $sock, $ip ) = @_;
421              
422 3         15 my %query = ( fullinfo => '' );
423 3         8 my %tmp = ();
424              
425 3         441 print $sock "+ $ip\n";
426 3         417123 while (<$sock>) {
427 271         630 $query{fullinfo} .= $_;
428 271 50 0     548 close $sock and return ( permission => 'denied' ) if /^\#201/;
429 271 50       604 return () if /no match found for/i;
430 271 100 100     23494 next if ( /^\#/ || !/\:/ );
431 153         615 s/\s+$//;
432 153         356 my ( $field, $value ) = split( /:/, $_, 2 );
433 153         356 $value =~ s/^\s+//;
434 153 100 66     484 if ( $field eq 'OrgName'
435             || $field eq 'CustName' ) {
436 3         32 %tmp = %query;
437 3         16 %query = ();
438 3         6 $query{fullinfo} = $tmp{fullinfo};
439             }
440 153 100       701 $query{ lc($field) } .= ( $query{ lc($field) } ? ' ' : '' ) . $value;
441             }
442 3         284 close $sock;
443              
444 3 50       24 $query{orgname} = $query{custname} if defined $query{custname};
445              
446 3         19 for ( keys %tmp ) {
447 39 100       114 $query{$_} = $tmp{$_} unless defined $query{$_};
448             }
449              
450 3         103 return %query;
451             }
452              
453             sub arin_process_query (%) {
454 3     3 0 34 my %query = @_;
455              
456             return ()
457 3 100 66     49 if $query{orgid} && $query{orgid} =~ /^\s*RIPE|LACNIC|APNIC|AFRINIC\s*$/;
458              
459 2         6 $query{permission} = 'allowed';
460 2         9 $query{descr} = $query{orgname};
461 2         7 $query{remarks} = $query{comment};
462 2         7 $query{status} = $query{nettype};
463 2         5 $query{inetnum} = $query{netrange};
464 2         10 $query{source} = 'ARIN';
465 2 100 66     17 if ( defined $query{cidr} && $query{cidr} =~ /\,/ ) {
466 1         13 $query{cidr} = [ split( /\s*\,\s*/, $query{cidr} ) ];
467             }
468             else {
469 1         4 $query{cidr} = [ $query{cidr} ];
470             }
471              
472 2         79 return %query;
473             }
474              
475             sub arin_query ($$) {
476 3     3 0 25 my ( $sock, $ip ) = @_;
477              
478 3         20 my %query = arin_read_query( $sock, $ip );
479              
480 3         31 return arin_process_query(%query);
481             }
482              
483             sub lacnic_read_query ($$) {
484 3     3 0 14 my ( $sock, $ip ) = @_;
485              
486 3         17 my %query = ( fullinfo => '' );
487              
488 3         395 print $sock "$ip\n";
489              
490 3         816490 while (<$sock>) {
491 170         214 $query{fullinfo} .= $_;
492 170 50 0     598 close $sock
      33        
      33        
      33        
493             and return ( permission => 'denied' )
494             if /^\%201/ || /^\% Query rate limit exceeded/ || /^\% Not assigned to LACNIC/ || /\% Permission denied/;
495 170 100       233 if (/^\% (\S+) resource:/) {
496 2         9 my $srv = $1;
497 2 50 0     17 close $sock and return () if $srv !~ /lacnic|brazil/i;
498             }
499 170 100 100     448 next if ( /^\%/ || !/\:/ );
500 109         264 s/\s+$//;
501 109         260 my ( $field, $value ) = split( /:/, $_, 2 );
502 109         157 $value =~ s/^\s+//;
503 109 100 100     156 next if $field eq 'country' && $query{country};
504 105 100       269 $query{ lc($field) } .= ( $query{ lc($field) } ? ' ' : '' ) . $value;
505             }
506 3         395 close $sock;
507 3         100 return %query;
508             }
509              
510             sub lacnic_process_query (%) {
511 3     3 0 21 my %query = @_;
512              
513 3         10 $query{permission} = 'allowed';
514 3         9 $query{descr} = $query{owner};
515 3         8 $query{netname} = $query{ownerid};
516 3         7 $query{source} = 'LACNIC';
517 3 50       11 if ( $query{inetnum} ) {
518 3         8 $query{cidr} = $query{inetnum};
519 3         24 $query{inetnum} = ( Net::CIDR::cidr2range( $query{cidr} ) )[0];
520             }
521 3 100       555 unless ( $query{country} ) {
522 1 50 33     14 if ( $query{nserver} && $query{nserver} =~ /\.(\w\w)$/ ) {
    50 33        
523 0         0 $query{country} = uc $1;
524             }
525             elsif ( $query{descr} && $query{descr} =~ /\s(\w\w)$/ ) {
526 0         0 $query{country} = uc $1;
527             }
528             else {
529 1         45 return ();
530             }
531             }
532 2         53 return %query;
533             }
534              
535             sub lacnic_query ($$) {
536 3     3 0 10 my ( $sock, $ip ) = @_;
537              
538 3         20 my %query = lacnic_read_query( $sock, $ip );
539              
540 3         23 return lacnic_process_query(%query);
541             }
542              
543             *afrinic_read_query = *apnic_read_query;
544              
545             sub afrinic_process_query (%) {
546 2     2 0 19 my %query = @_;
547              
548             return ()
549             if defined $query{remarks} && $query{remarks} =~ /country is really worldwide/
550 2 50 33     29 or defined $query{descr} && $query{descr} =~ /Here for in-addr\.arpa authentication/;
      33        
      33        
551              
552 2 50 33     13 if ( !$query{inet6num} && !$query{inetnum} ) {
553 0         0 return ();
554             }
555              
556 2         24 $query{permission} = 'allowed';
557             $query{cidr} =
558 2   33     29 [ Net::CIDR::range2cidr( uc( $query{inet6num} || $query{inetnum} ) ) ];
559 2         681 return %query;
560             }
561              
562             sub afrinic_query ($$) {
563 2     2 0 6 my ( $sock, $ip ) = @_;
564              
565 2         9 my %query = afrinic_read_query( $sock, $ip );
566              
567 2         20 return afrinic_process_query(%query);
568             }
569              
570             sub is_mine ($$;@) {
571 4     4 1 14577 my ( $self, $ip, @cidr ) = @_;
572              
573 4 50       19 return 0 unless is_valid_ip($ip);
574 4 100       19 if ( !scalar @cidr ) {
575 2         10 my $out = $self->cidr();
576 2 50       49 @cidr = @$out if ref $out;
577             }
578              
579             @cidr = map {
580 4         19 my @dots = ( split /\./ );
581 4         15 my $pad = '.0' x ( 4 - @dots );
582 4         48 s|(/.*)|$pad$1|;
583 4         21 $_;
584             }
585 4         22 map { split(/\s+/) }
586 4         14 grep { defined $_ } @cidr;
  4         15  
587              
588 4         28 return Net::CIDR::cidrlookup( $ip, @cidr );
589             }
590              
591             1;
592              
593             __END__