File Coverage

blib/lib/Net/Whois/IANA.pm
Criterion Covered Total %
statement 261 305 85.5
branch 101 160 63.1
condition 77 177 43.5
subroutine 34 36 94.4
pod 2 26 7.6
total 475 704 67.4


line stmt bran cond sub pod time code
1             package Net::Whois::IANA;
2             $Net::Whois::IANA::VERSION = '0.47';
3 10     10   1205056 use 5.006;
  10         99  
4              
5 10     10   48 use strict;
  10         14  
  10         189  
6 10     10   35 use warnings;
  10         16  
  10         236  
7              
8 10     10   51 use Carp ();
  10         17  
  10         181  
9 10     10   4438 use IO::Socket ();
  10         187420  
  10         253  
10 10     10   4143 use Net::CIDR ();
  10         44412  
  10         304  
11              
12 10     10   89 use base 'Exporter';
  10         18  
  10         2571  
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   167 %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         76 @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         61 my @accessors = qw{country netname descr status source server inetnum inet6num cidr};
49              
50 10         40 foreach my $accessor (@accessors) {
51 10     10   75 no strict 'refs';
  10         21  
  10         1142  
52             *$accessor = sub {
53 27     27   10481 my ($self) = @_;
54 27 50       84 die qq[$accessor is a method call] unless ref $self;
55 27 50       73 return unless $self->{QUERY};
56 27         178 return $self->{QUERY}->{$accessor};
57 90         544 };
58             }
59              
60 10         38404 *desc = \&descr; # backward compatibility
61             }
62              
63             our @EXPORT = qw( @IANA %IANA );
64              
65             sub new ($) {
66              
67 9     9 0 1844 my $proto = shift;
68 9   33     59 my $class = ref $proto || $proto;
69 9         18 my $self = {};
70              
71 9         21 bless $self, $class;
72              
73 9         25 return $self;
74             }
75              
76             sub whois_connect ($;$$) {
77 21     21 0 1449 my ( $host, $port, $timeout ) = @_;
78              
79 21 100       92 ( $host, $port, $timeout ) = @$host if ref $host;
80              
81 21   66     103 $port ||= $WHOIS_PORT;
82 21   66     89 $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 21         33 my $retries = 2;
88 21         38 my $sleep = 2;
89              
90 21         42 my $sock;
91              
92 21         62 foreach my $iter ( 0 .. $retries ) {
93 21         36 local $@;
94              
95             # catch errors
96 21 50       43 eval {
97 21         217 $sock = IO::Socket::INET->new(
98             PeerAddr => $host,
99             PeerPort => $port,
100             Timeout => $timeout,
101             );
102 21         2491895 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 40 my $ip = shift;
115              
116 22   100     382 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 51 my ($ip) = @_;
166              
167 23 100       67 return unless defined $ip; # shortcut earlier
168 22 50       119 return index( $ip, ':' ) >= 0 ? is_valid_ipv6($ip) : is_valid_ipv4($ip);
169             }
170              
171             sub set_source ($$) {
172              
173 16     16 0 32 my $self = shift;
174 16         43 my $source = shift;
175              
176 16 100 50     69 $self->{source} = {%IANA} || return 0 unless $source;
177 16 100       41 return 0 unless $source;
178 14 50       38 unless ( ref $source ) {
179 14 100       55 if ( $IANA{$source} ) {
180 13         77 $self->{source} = { $source => $IANA{$source} };
181 13         34 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 30 my $self = shift;
195 19         96 my %param = @_;
196              
197 19 100       71 if ( !is_valid_ip( $param{-ip} ) ) {
198 3         206 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         17 return {};
206             }
207              
208 16   66     111 my $set_source = $self->set_source( $param{-whois} || $param{-mywhois} );
209 16 100       81 if ( $set_source == 1 ) {
    50          
210 1         66 warn "Unknown whois server requested. Known servers are:\n";
211 1         58 warn join( ", ", @IANA ) . "\n";
212 1         6 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 65     65 0 129 my ( $self, $source_name ) = @_;
232              
233 65         79 foreach my $server_ref ( @{ $self->{source}{$source_name} } ) {
  65         153  
234 19 50       63 if ( my $sock = whois_connect($server_ref) ) {
235 19         51 my ( $whois_host, $whois_port, $whois_timeout, $query_code ) = @{$server_ref};
  19         159  
236 19 50 33     221 $self->{query_sub} = $query_code
237             && ref $query_code eq 'CODE' ? $query_code : \&default_query;
238 19         127 $self->{whois_host} = $whois_host;
239 19         125 return $sock;
240             }
241             }
242 46         160 return undef;
243             }
244              
245             sub post_process_query (%) {
246              
247 15     15 0 98 my %query = @_;
248 15         117 for my $qkey ( keys %query ) {
249 310 50       553 chomp $query{$qkey} if defined $query{$qkey};
250             $query{abuse} = $query{$qkey} and last
251 310 100 50     623 if $qkey =~ /abuse/i && $query{$qkey} =~ /\@/;
      100        
252             }
253 15 100       60 unless ( $query{abuse} ) {
254 13 100 66     1324 if ( $query{fullinfo} && $query{fullinfo} =~ /(\S*abuse\S*\@\S+)/m ) {
    100 66        
      66        
255 10         46 $query{abuse} = $1;
256             }
257             elsif ( $query{email} || $query{'e-mail'} || $query{orgtechemail} ) {
258             $query{abuse} =
259 1   33     6 $query{email} || $query{'e-mail'} || $query{orgtechemail};
260             }
261             }
262 15 100       56 if ( !ref $query{cidr} ) {
263 4 50 33     22 if ( defined $query{cidr} && $query{cidr} =~ /\,/ ) {
264 0         0 $query{cidr} = [ split( /\s*\,\s*/, $query{cidr} ) ];
265             }
266             else {
267 4         12 $query{cidr} = [ $query{cidr} ];
268             }
269             }
270              
271 15         251 return %query;
272             }
273              
274             sub whois_query ($%) {
275 19     19 1 9022 my ( $self, %params ) = @_;
276              
277 19         107 $self->init_query(%params);
278 19         90 $self->{QUERY} = {};
279              
280 19         55 for my $source_name (@DEFAULT_SOURCE_ORDER) {
281 65 50       128 print STDERR "Querying $source_name ...\n" if $params{-debug};
282 65   100     138 my $sock = $self->source_connect($source_name)
283             || Carp::carp "Connection failed to $source_name." && next;
284 19         109 my %query = $self->{query_sub}( $sock, $params{-ip} );
285              
286 19 100       139 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     93 if $query{permission} && $query{permission} eq 'denied';
289 15         58 $query{server} = uc $source_name;
290 15         107 $self->{QUERY} = { post_process_query(%query) };
291              
292 15         293 return $self->{QUERY};
293             }
294              
295 4         13 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 5     5 0 14 my ( $sock, $ip ) = @_;
306              
307 5         23 my %query = ( fullinfo => '' );
308 5         557 print $sock "-r $ip\n";
309 5         95435 while (<$sock>) {
310 214         413 $query{fullinfo} .= $_;
311 214 50 0     372 close $sock and return ( permission => 'denied' ) if /ERROR:201/;
312 214 100 100     19626 next if ( /^(\%|\#)/ || !/\:/ );
313 117         385 s/\s+$//;
314 117         326 my ( $field, $value ) = split( /:/, $_, 2 );
315 117         325 $value =~ s/^\s+//;
316 117 100       468 $query{ lc($field) } .= ( $query{ lc($field) } ? ' ' : '' ) . $value;
317             }
318 5         585 close $sock;
319 5         105 return %query;
320             }
321              
322             sub ripe_process_query (%) {
323              
324 5     5 0 75 my %query = @_;
325              
326 5 100 33     138 if (
    50 33        
      33        
      33        
      33        
      66        
      66        
      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 1         8 return ();
336             }
337             elsif ( !$query{inet6num} && !$query{inetnum} ) {
338 0         0 return ();
339             }
340             else {
341 4         12 $query{permission} = 'allowed';
342 4   33     48 $query{cidr} = [ Net::CIDR::range2cidr( uc( $query{inet6num} || $query{inetnum} ) ) ];
343             }
344 4         1161 return %query;
345             }
346              
347             sub ripe_query ($$) {
348 5     5 0 19 my ( $sock, $ip ) = @_;
349              
350 5         25 my %query = ripe_read_query( $sock, $ip );
351 5 50       33 return () unless defined $query{country};
352 5         75 return ripe_process_query(%query);
353             }
354              
355             sub apnic_read_query ($$) {
356 6     6 0 17 my ( $sock, $ip ) = @_;
357              
358 6         25 my %query = ( fullinfo => '' );
359 6         13 my %tmp;
360 6         681 print $sock "-r $ip\n";
361 6         28 my $skip_block = 0;
362 6         410325 while (<$sock>) {
363 220         466 $query{fullinfo} .= $_;
364 220 50 0     372 close $sock and return ( permission => 'denied' ) if /^\%201/;
365 220 100       651 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 37 50       81 if (m{^\%.*0\.0\.0\.0\s+}) {
371 0         0 $skip_block = 1;
372 0         0 next;
373             }
374 37         66 $skip_block = 0;
375 37         136 next;
376             }
377 183 50       237 next if $skip_block;
378 183 100       17201 next if ( !/\:/ );
379 135         524 s/\s+$//;
380 135         371 my ( $field, $value ) = split( /:/, $_, 2 );
381 135         292 $value =~ s/^\s+//;
382 135 100       276 if ( $field =~ /^inet6?num$/ ) {
383 7 50       26 next if $value =~ m{0\.0\.0\.0\s+};
384 7         42 %tmp = %query;
385 7         49 %query = ();
386 7         22 $query{fullinfo} = $tmp{fullinfo};
387             }
388 135         184 my $lc_field = lc($field);
389 135 100 100     250 next if $lc_field eq 'country' && defined $query{$lc_field};
390 134 100       535 $query{$lc_field} .= ( $query{$lc_field} ? ' ' : '' ) . $value;
391             }
392 6         609 close $sock;
393 6         39 for ( keys %tmp ) {
394 18 100       70 $query{$_} = $tmp{$_} if !defined $query{$_};
395             }
396 6         88 return %query;
397             }
398              
399             sub apnic_process_query (%) {
400 4     4 0 25 my %query = @_;
401              
402 4 100 66     109 if (
    50 66        
      66        
      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 1         10 return ();
408             }
409             elsif ( !$query{inet6num} && !$query{inetnum} ) {
410 0         0 return ();
411             }
412             else {
413 3         9 $query{permission} = 'allowed';
414 3   33     33 $query{cidr} = [ Net::CIDR::range2cidr( uc( $query{inet6num} || $query{inetnum} ) ) ];
415             }
416              
417 3         1142 return %query;
418             }
419              
420             sub apnic_query ($$) {
421 4     4 0 16 my ( $sock, $ip ) = @_;
422              
423 4         19 my %query = apnic_read_query( $sock, $ip );
424 4         44 return apnic_process_query(%query);
425             }
426              
427             sub arin_read_query ($$) {
428 4     4 0 12 my ( $sock, $ip ) = @_;
429              
430 4         18 my %query = ( fullinfo => '' );
431 4         9 my %tmp = ();
432              
433 4         543 print $sock "+ $ip\n";
434 4         412604 while (<$sock>) {
435 327         567 $query{fullinfo} .= $_;
436 327 50 0     483 close $sock and return ( permission => 'denied' ) if /^\#201/;
437 327 50       497 return () if /no match found for/i;
438 327 100 100     43630 next if ( /^\#/ || !/\:/ );
439 175         559 s/\s+$//;
440 175         422 my ( $field, $value ) = split( /:/, $_, 2 );
441 175         365 $value =~ s/^\s+//;
442 175 100 66     442 if ( $field eq 'OrgName'
443             || $field eq 'CustName' ) {
444 4         69 %tmp = %query;
445 4         22 %query = ();
446 4         12 $query{fullinfo} = $tmp{fullinfo};
447             }
448 175 100       674 $query{ lc($field) } .= ( $query{ lc($field) } ? ' ' : '' ) . $value;
449             }
450 4         277 close $sock;
451              
452 4 50       29 $query{orgname} = $query{custname} if defined $query{custname};
453              
454 4         27 for ( keys %tmp ) {
455 52 100       105 $query{$_} = $tmp{$_} unless defined $query{$_};
456             }
457              
458 4         90 return %query;
459             }
460              
461             sub arin_process_query (%) {
462 4     4 0 33 my %query = @_;
463              
464             return ()
465 4 100 66     64 if $query{orgid} && $query{orgid} =~ /^\s*RIPE|LACNIC|APNIC|AFRINIC\s*$/;
466              
467 2         4 $query{permission} = 'allowed';
468 2         5 $query{descr} = $query{orgname};
469 2         4 $query{remarks} = $query{comment};
470 2         4 $query{status} = $query{nettype};
471 2         5 $query{inetnum} = $query{netrange};
472 2         5 $query{source} = 'ARIN';
473 2 100 66     13 if ( defined $query{cidr} && $query{cidr} =~ /\,/ ) {
474 1         8 $query{cidr} = [ split( /\s*\,\s*/, $query{cidr} ) ];
475             }
476             else {
477 1         2 $query{cidr} = [ $query{cidr} ];
478             }
479              
480 2         43 return %query;
481             }
482              
483             sub arin_query ($$) {
484 4     4 0 13 my ( $sock, $ip ) = @_;
485              
486 4         27 my %query = arin_read_query( $sock, $ip );
487              
488 4         34 return arin_process_query(%query);
489             }
490              
491             sub lacnic_read_query ($$) {
492 4     4 0 13 my ( $sock, $ip ) = @_;
493              
494 4         21 my %query = ( fullinfo => '' );
495              
496 4         364 print $sock "$ip\n";
497              
498 4         9492047 while (<$sock>) {
499 218         377 $query{fullinfo} .= $_;
500 218 50 0     1303 close $sock
      33        
      33        
      33        
501             and return ( permission => 'denied' )
502             if /^\%201/ || /^\% Query rate limit exceeded/ || /^\% Not assigned to LACNIC/ || /\% Permission denied/;
503 218 100       363 if (/^\% (\S+) resource:/) {
504 3         15 my $srv = $1;
505 3 50 0     33 close $sock and return () if $srv !~ /lacnic|brazil/i;
506             }
507 218 100 100     819 next if ( /^\%/ || !/\:/ );
508 134         474 s/\s+$//;
509 134         320 my ( $field, $value ) = split( /:/, $_, 2 );
510 134         289 $value =~ s/^\s+//;
511 134 100 100     255 next if $field eq 'country' && $query{country};
512 129 100       465 $query{ lc($field) } .= ( $query{ lc($field) } ? ' ' : '' ) . $value;
513             }
514 4         471 close $sock;
515 4         103 return %query;
516             }
517              
518             sub lacnic_process_query (%) {
519 4     4 0 26 my %query = @_;
520              
521 4         14 $query{permission} = 'allowed';
522 4         10 $query{descr} = $query{owner};
523 4         11 $query{netname} = $query{ownerid};
524 4         13 $query{source} = 'LACNIC';
525 4 50       25 if ( $query{inetnum} ) {
526 4         11 $query{cidr} = $query{inetnum};
527 4         29 $query{inetnum} = ( Net::CIDR::cidr2range( $query{cidr} ) )[0];
528             }
529 4 100       643 unless ( $query{country} ) {
530 1 50 33     11 if ( $query{nserver} && $query{nserver} =~ /\.(\w\w)$/ ) {
    0 0        
531 1         4 $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 4         65 return %query;
541             }
542              
543             sub lacnic_query ($$) {
544 4     4 0 15 my ( $sock, $ip ) = @_;
545              
546 4         21 my %query = lacnic_read_query( $sock, $ip );
547              
548 4         33 return lacnic_process_query(%query);
549             }
550              
551             *afrinic_read_query = *apnic_read_query;
552              
553             sub afrinic_process_query (%) {
554 2     2 0 12 my %query = @_;
555              
556             return ()
557             if defined $query{remarks} && $query{remarks} =~ /country is really worldwide/
558 2 50 33     25 or defined $query{descr} && $query{descr} =~ /Here for in-addr\.arpa authentication/;
      33        
      33        
559              
560 2 50 33     12 if ( !$query{inet6num} && !$query{inetnum} ) {
561 0         0 return ();
562             }
563              
564 2         6 $query{permission} = 'allowed';
565             $query{cidr} =
566 2   33     23 [ Net::CIDR::range2cidr( uc( $query{inet6num} || $query{inetnum} ) ) ];
567 2         457 return %query;
568             }
569              
570             sub afrinic_query ($$) {
571 2     2 0 6 my ( $sock, $ip ) = @_;
572              
573 2         11 my %query = afrinic_read_query( $sock, $ip );
574              
575 2         16 return afrinic_process_query(%query);
576             }
577              
578             sub is_mine ($$;@) {
579 4     4 1 8772 my ( $self, $ip, @cidr ) = @_;
580              
581 4 50       10 return 0 unless is_valid_ip($ip);
582 4 100       11 if ( !scalar @cidr ) {
583 2         6 my $out = $self->cidr();
584 2 50       7 @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         53 s|(/.*)|$pad$1|;
591 4         15 $_;
592             }
593 4         12 map { split(/\s+/) }
594 4         9 grep { defined $_ } @cidr;
  4         10  
595              
596 4         29 return Net::CIDR::cidrlookup( $ip, @cidr );
597             }
598              
599             1;
600              
601             __END__