File Coverage

blib/lib/Net/Whois/IANA.pm
Criterion Covered Total %
statement 259 305 84.9
branch 99 160 61.8
condition 72 177 40.6
subroutine 34 36 94.4
pod 2 26 7.6
total 466 704 66.1


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