File Coverage

blib/lib/POE/Component/Client/Whois/Smart.pm
Criterion Covered Total %
statement 160 273 58.6
branch 46 102 45.1
condition 35 84 41.6
subroutine 20 25 80.0
pod 1 6 16.6
total 262 490 53.4


line stmt bran cond sub pod time code
1             package POE::Component::Client::Whois::Smart;
2              
3 1     1   1714 use strict;
  1         3  
  1         49  
4 1     1   6 use warnings;
  1         1  
  1         43  
5 1     1   18 use Socket;
  1         1  
  1         868  
6 1     1   7 use POE qw(Filter::Line Wheel::ReadWrite Wheel::SocketFactory Component::Client::HTTP);
  1         1  
  1         10  
7 1     1   756775 use HTTP::Request;
  1         648  
  1         39  
8 1     1   521 use Net::Whois::Raw::Common;
  1         196975  
  1         71  
9 1     1   13 use Net::Whois::Raw::Data;
  1         2  
  1         39  
10 1     1   6 use Storable;
  1         2  
  1         4389  
11             #use Data::Dumper;
12              
13             our $VERSION = '0.12_01';
14             our $DEBUG;
15             our @local_ips = ();
16             our %servers_ban = ();
17             our %POSTPROCESS;
18             #our $rism_all; # = Request per Ip per Server per Minute =)
19              
20             # init whois query
21             sub whois {
22 4     4 1 1527 my $class = shift;
23 4         13 my %args = @_;
24              
25 4   33     27 $args{session} = $args{session} || $poe_kernel->get_active_session();
26            
27 4         54 POE::Session->create(
28             inline_states => {
29             _start => \&_start_manager,
30             _query_done => \&_query_done,
31             },
32             args => [ \%args ],
33             );
34            
35 4         326 undef;
36             }
37              
38             # start manager, which manages all process and returns result to caller
39             sub _start_manager {
40 4     4   478 my ($heap, $session, $arg_ref) = @_[HEAP, SESSION, ARG0];
41 4         18 my %args = %$arg_ref;
42            
43 4 50       18 $args{referral} = 1 unless defined $args{referral};
44 4         16 $heap->{params}->{referral} = $args{referral};
45 4         11 $heap->{params}->{event} = delete $args{event};
46 4         10 $heap->{params}->{session} = delete $args{session};
47 4         9 $heap->{params}->{use_cnames} = delete $args{use_cnames};
48 4         8 $heap->{params}->{cache_dir} = $args{cache_dir};
49 4   50     25 $heap->{params}->{cache_time} = $args{cache_time} ||= 1;
50 4 50       16 $heap->{params}->{omit_msg}
51             = defined $args{omit_msg} ? delete $args{omit_msg} : 2;
52 4 50       15 $heap->{params}->{exceed_wait}
53             = defined $args{exceed_wait} ? $args{exceed_wait} : 0;
54              
55 4         15 $args{host} = delete $args{server},
56             $args{manager_id} = $session->ID();
57 4         17 $args{event} = "_query_done";
58 4   50     21 $args{timeout} = $args{timeout} || 30;
59            
60 4         6 $heap->{tasks} = 0;
61            
62 0         0 @local_ips = @{$args{local_ips}}
  0         0  
63             if $args{local_ips}
64 4 50 33     17 && (join '', sort @local_ips) ne (join '', sort @{$args{local_ips}});
65            
66 4         5 delete $args{local_ips};
67              
68 4         8 my (@query_list) = @{$args{query}};
  4         14  
69 4         9 delete $args{query};
70              
71 4         9 foreach my $query (@query_list) {
72 11         22 $heap->{tasks}++;
73 11         25 $args{query} = lc $query;
74 11         22 $args{original_query} = lc $query;
75 11         57 __PACKAGE__->get_whois(%args);
76             }
77            
78 4         27 undef;
79             }
80              
81             # caches retrieved whois-info, return result if no more tasks
82             sub _query_done {
83 15     15   5352 my ($kernel, $heap, $session, $response) = @_[KERNEL, HEAP, SESSION, ARG0];
84              
85 15         27 my ($whois, $error);
86 15 50       117 if ($response->{error}) {
    50          
    50          
87 0         0 $error = $response->{error};
88             } elsif($response->{from_cache}) {
89 0         0 $whois = $response->{whois};
90 0         0 $heap->{result}->{$response->{original_query}} = delete $response->{cache};
91             } elsif ($response->{host} eq "www_whois") {
92 0         0 $whois = $response->{whois};
93 0         0 $error = $response->{error};
94             } else {
95 15 50       49 $whois = defined $response->{reply} ? join "\n", @{$response->{reply}} : "";
  15         336  
96 15         105 delete $response->{reply};
97 15         120 ($whois, $error) = Net::Whois::Raw::Common::process_whois(
98             $response->{original_query},
99             $response->{host},
100             $whois,
101             1, # remove addition whois data verification
102             $heap->{params}->{omit_msg},
103             2,
104             );
105             }
106            
107             # exceed
108 15 50 66     993600 if ($error && $error eq 'Connection rate exceeded') {
109 0   0     0 my $current_ip = $response->{local_ip} || 'localhost';
110 0         0 $servers_ban{$response->{host}}->{$current_ip} = time;
111 0 0       0 print "Connection rate exceeded for IP: $current_ip, server: "
112             .$response->{host}."\n"
113             if $DEBUG;
114            
115 0 0       0 if ($heap->{params}->{exceed_wait}) {
116 0         0 my %args = %$response;
117 0         0 delete $args{local_ip};
118 0         0 delete $args{error};
119 0         0 delete $args{whois};
120 0         0 $args{manager_id} = $session->ID();
121 0         0 __PACKAGE__->get_whois(%args);
122 0         0 return undef;
123             }
124             }
125            
126 15         56 $heap->{tasks}--;
127            
128 15 50 66     139 if (!$response->{from_cache} && ( !$error || !$heap->{result}->{$response->{original_query}} ) ) {
      33        
129 15         165 my %result = (
130             query => $response->{query},
131             server => $response->{host},
132             query_real => $response->{query_real},
133             whois => $whois,
134             error => $error,
135             from_cache => $response->{from_cache},
136             );
137            
138 15         26 push @{ $heap->{result}->{$response->{original_query}} }, \%result;
  15         92  
139            
140 11         57 my ($new_server, $new_query) = get_recursion(
141             $result{whois},
142             $result{server},
143             $result{query},
144 15 100 66     138 @{ $heap->{result}->{$response->{original_query}} },
145             ) if $result{whois} && $response->{host} ne "www_whois";
146            
147 15 100 66     127 if ( $heap->{params}->{referral} # NOT AN QRY_FIRST
      33        
148             && ( $new_server && ! $result{from_cache} ) ) {
149 4         40 my %args = %$response;
150 4         9 delete $args{reply};
151            
152 4         36 $args{manager_id} = $session->ID();
153 4         28 $args{event} = "_query_done";
154 4         8 $args{query} = $new_query;
155 4         7 $args{host} = $new_server;
156            
157 4         10 $heap->{tasks}++;
158 4         52 __PACKAGE__->get_whois(%args);
159             }
160             }
161            
162 15 100       59 unless ($heap->{tasks}) {
163 4         9 my @result;
164 4         5 foreach my $query (keys %{$heap->{result}}) {
  4         24  
165 11 50       30 my $num = $heap->{params}->{referral} == 0 ? 0 : -1;
166 11         66 my %res = (
167             query => $query,
168             whois => $heap->{result}->{$query}->[$num]->{whois},
169             server => $heap->{result}->{$query}->[$num]->{server},
170             error => $heap->{result}->{$query}->[$num]->{error},
171             );
172            
173 11 50 33     38 Net::Whois::Raw::Common::write_to_cache(
174             $query,
175             $heap->{result}->{$query},
176             $heap->{params}->{cache_dir}
177             ) if $heap->{params}->{cache_dir} && !$res{from_cache};
178            
179 11 50       25 $res{subqueries} = $heap->{result}->{$query}
180             if $heap->{params}->{referral} == 2;
181            
182 11         29 push @result, \%res;
183             }
184            
185 4         47 $kernel->post( $heap->{params}->{session},
186             $heap->{params}->{event}, \@result )
187             }
188            
189 15         716 undef;
190             }
191              
192             # get whois-server and start socket or http session
193             sub get_whois {
194 15     15 0 26 my $package = shift;
195 15         109 my %args = @_;
196              
197 15         178 $args{lc $_} = delete $args{$_} for keys %args;
198              
199 15 100       50 unless ( $args{host} ) {
200 10         64 my $whois_server = Net::Whois::Raw::Common::get_server($args{query}, $args{params}->{use_cnames});
201 10 50       285924 unless ( $whois_server ) {
202 0         0 warn "Could not determine whois server from query string, defaulting to internic \n";
203 0         0 $whois_server = 'whois.internic.net';
204             }
205 10         37 $args{host} = $whois_server;
206             }
207              
208 15 50       102 $args{query_real} = Net::Whois::Raw::Common::get_real_whois_query($args{query}, $args{host})
209             unless ($args{host} eq "www_whois");
210              
211 15         282 my $self = bless { request => \%args }, $package;
212              
213 15         191 $self->{session_id} = POE::Session->create(
214             object_states => [
215             $self => [
216             qw( _start _connect _connect_http _http_down
217             _sock_input _sock_down _sock_up _sock_failed _time_out)
218             ],
219             ],
220             options => { trace => 0 },
221             )->ID();
222              
223 15         1814 return $self;
224             }
225              
226              
227             # init session
228             sub _start {
229 15     15   4082 my ($kernel,$self) = @_[KERNEL,OBJECT];
230 15         70 $self->{session_id} = $_[SESSION]->ID();
231            
232 15 50       114 if ($self->{request}->{cache_dir}) {
233 0         0 my $result = Net::Whois::Raw::Common::get_from_cache(
234             $self->{request}->{query},
235             $self->{request}->{cache_dir},
236             $self->{request}->{cache_time},
237             );
238 0 0       0 if ($result) {
239 0         0 my $request = delete $self->{request};
240 0         0 my $session = delete $request->{manager_id};
241            
242             #$request->{whois} = $whois;
243             #$request->{host} = $server;
244            
245 0         0 my $res;
246 0         0 foreach (@{$result}) {
  0         0  
247 0         0 $_->{server} = delete $_->{srv};
248 0         0 $_->{whois} = delete $_->{text};
249 0         0 push @{$res}, $_;
  0         0  
250             }
251            
252 0         0 $request->{cache} = $res;
253 0         0 $request->{from_cache} = 1;
254 0         0 $kernel->post( $session => $request->{event} => $request );
255 0         0 return undef;
256             }
257             }
258            
259 15 50       53 if ($self->{request}->{host} eq "www_whois") {
260 0         0 $kernel->yield( '_connect_http' );
261             } else {
262 15         56 $kernel->yield( '_connect' );
263             }
264            
265 15         1180 undef;
266             }
267              
268             # connects to whois-server (socket)
269             sub _connect {
270 15     15   3571 my ($kernel,$self) = @_[KERNEL,OBJECT];
271 15         127 my $local_ip = next_local_ip(
272             $self->{request}->{host},
273             $self->{request}->{clientname},
274             $self->{request}->{rism},
275             );
276            
277 15 50       61 unless ($local_ip) {
278 0         0 my $unban_time = unban_time(
279             $self->{request}->{host},
280             $self->{request}->{clientname},
281             $self->{request}->{rism},
282             );
283 0         0 my $delay_err = $kernel->delay_add('_connect', $unban_time);
284 0 0       0 warn "All IPs banned for server ".$self->{request}->{host}.
285             ", waiting: $unban_time sec\n"
286             if $DEBUG;
287 0         0 return undef;
288             }
289            
290 15 50       43 print "Query '".$self->{request}->{query_real}.
291             "' to ".$self->{request}->{host}.
292             " from $local_ip\n"
293             if $DEBUG;
294            
295 15 50       45 $local_ip = undef if $local_ip eq 'localhost';
296            
297 15   50     211 $self->{factory} = POE::Wheel::SocketFactory->new(
298             SocketDomain => AF_INET,
299             SocketType => SOCK_STREAM,
300             SocketProtocol => 'tcp',
301             RemoteAddress => $self->{request}->{host},
302             RemotePort => $self->{request}->{port} || 43,
303             BindAddress => $local_ip,
304             SuccessEvent => '_sock_up',
305             FailureEvent => '_sock_failed',
306             );
307            
308 15         530940 undef;
309             }
310              
311             # connects to whois-server (http)
312             sub _connect_http {
313 0     0   0 my ($kernel,$self) = @_[KERNEL,OBJECT];
314 0         0 POE::Component::Client::HTTP->spawn(
315             Alias => 'ua',
316             Timeout => $self->{request}->{timeout},
317             );
318            
319 0         0 my ($url, %form) = Net::Whois::Raw::Common::get_http_query_url($self->{request}->{query});
320 0         0 my ($name, $tld) = Net::Whois::Raw::Common::split_domain($self->{request}->{query});
321            
322 0         0 $self->{request}->{tld} = $tld;
323 0 0 0     0 my $referer = delete $form{referer} if %form && $form{referer};
324 0 0       0 my $method = scalar(keys %form) ? 'POST' : 'GET';
325            
326 0         0 my $header = HTTP::Headers->new;
327 0 0       0 $header->header('Referer' => $referer) if $referer;
328 0         0 my $req = new HTTP::Request $method, $url, $header;
329              
330 0 0       0 if ($method eq 'POST') {
331 0         0 my $curl = url("http:");
332 0         0 $req->content_type('application/x-www-form-urlencoded');
333 0         0 $curl->query_form(%form);
334 0         0 $req->content($curl->equery);
335             }
336            
337 0         0 $kernel->post("ua", "request", "_http_down", $req);
338            
339 0         0 undef;
340             }
341              
342             # cach result from http whois-server
343             sub _http_down {
344 0     0   0 my ($kernel, $heap, $self, $request_packet, $response_packet)
345             = @_[KERNEL, HEAP, OBJECT, ARG0, ARG1];
346              
347             # response obj
348 0         0 my $response = $response_packet->[0];
349             # response content
350 0         0 my $content = $response->content();
351            
352 0         0 $self->{request}->{whois}
353             = Net::Whois::Raw::Common::parse_www_content($content, $self->{request}->{tld});
354            
355 0         0 my $request = delete $self->{request};
356 0         0 my $session = delete $request->{manager_id};
357              
358 0 0       0 if ($request->{whois}) {
359 0         0 delete $request->{error};
360             } else {
361 0         0 $request->{error} = "No information";
362             }
363 0         0 $kernel->post( $session => $request->{event} => $request );
364            
365 0         0 undef;
366             }
367              
368             # socket error
369             sub _sock_failed {
370 0     0   0 my ($kernel, $self, $op, $errno, $errstr) = @_[KERNEL, OBJECT, ARG0..ARG2];
371              
372 0         0 delete $self->{factory};
373 0         0 $self->{request}->{error} = "$op error $errno: $errstr";
374 0         0 my $request = delete $self->{request};
375 0         0 my $session = delete $request->{manager_id};
376              
377 0         0 $kernel->post( $session => $request->{event} => $request );
378            
379 0         0 undef;
380             }
381              
382             # connection with socket established, send query
383             sub _sock_up {
384 15     15   11707 my ($kernel, $self, $session, $socket) = @_[KERNEL, OBJECT, SESSION, ARG0];
385 15         106 delete $self->{factory};
386              
387 15         364 $self->{'socket'} = new POE::Wheel::ReadWrite(
388             Handle => $socket,
389             Driver => POE::Driver::SysRW->new(),
390             Filter => POE::Filter::Line->new( InputRegexp => "(\x0D\x0A?|\x0A\x0D?)",
391             OutputLiteral => "\015\012" ),
392             InputEvent => '_sock_input',
393             ErrorEvent => '_sock_down',
394             );
395              
396 15 50       5082 unless ( $self->{'socket'} ) {
397 0         0 my $request = delete $self->{request};
398 0         0 my $session = delete $request->{manager_id};
399 0         0 $request->{error} = "Couldn\'t create a Wheel::ReadWrite on socket for whois";
400 0         0 $kernel->post( $session => $request->{event} => $request );
401            
402 0         0 return undef;
403             }
404              
405 15         76 $kernel->delay_add( '_time_out' => $self->{request}->{timeout});
406 15         1073 $self->{'socket'}->put( $self->{request}->{query_real} );
407            
408 15         1139 undef;
409             }
410              
411             # connection with socket finished, post result to manager
412             sub _sock_down {
413 15     15   4730 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
414 15 50 33     214 if ( $self->{socket} && $self->{socket}->[2] && $self->{socket}->[2]->[0] ) {
      33        
415 0         0 push @{ $self->{request}->{reply} }, $self->{socket}->[2]->[0];
  0         0  
416             }
417            
418 15         96 delete $self->{socket};
419 15         4851 $kernel->delay( '_time_out' => undef );
420              
421 15         2400 my $request = delete $self->{request};
422 15         49 my $session = delete $request->{manager_id};
423              
424 15 50 33     132 if ( defined ( $request->{reply} ) and ref( $request->{reply} ) eq 'ARRAY' ) {
425 15         34 delete $request->{error};
426             } else {
427 0         0 $request->{error} = "No information received from remote host";
428             }
429 15         112 $kernel->post( $session => $request->{event} => $request );
430            
431 15         1906 undef;
432             }
433              
434             # got input from socket, save it
435             sub _sock_input {
436 831     831   515932 my ($kernel,$self,$line) = @_[KERNEL,OBJECT,ARG0];
437 831         958 push @{ $self->{request}->{reply} }, $line;
  831         2090  
438            
439 831         1935 undef;
440             }
441              
442             # socket timeout, abort connection
443             sub _time_out {
444 0     0   0 my ($kernel,$self) = @_[KERNEL,OBJECT];
445 0         0 delete $self->{'socket'};
446 0         0 warn "Timeout!";
447            
448 0         0 my $request = delete $self->{request};
449 0         0 my $session = delete $request->{manager_id};
450 0         0 $request->{error} = "Timeout";
451 0         0 $kernel->post( $session => $request->{event} => $request );
452            
453 0         0 undef;
454             }
455              
456             # check whois-info, if it has referrals, return new server and query
457             sub get_recursion {
458 11     11 0 33 my ($whois, $server, $query, @prev_results) = @_;
459              
460 11         17 my ($new_server, $registrar);
461 11         23 my $new_query = $query;
462            
463 11         199 foreach (split "\n", $whois) {
464 494   66     1612 $registrar ||= /Registrar/ || /Registered through/;
      100        
465            
466 494 100 100     4240 if ($registrar && /Whois Server:\s*([A-Za-z0-9\-_\.]+)/) {
    50 66        
    100 66        
    50 33        
    100          
    50          
467 2         11 $new_server = lc $1;
468             #last;
469             } elsif ($whois =~ /To single out one record, look it up with \"xxx\",/s) {
470 0         0 $new_server = $server;
471 0         0 $new_query = "=$query";
472 0         0 last;
473             } elsif (/ReferralServer: whois:\/\/([-.\w]+)/) {
474             #warn "SEX!!!!\n";
475 2         11 $new_server = $1;
476 2         4 last;
477             } elsif (/Contact information can be found in the (\S+)\s+database/) {
478 0         0 $new_server = $Net::Whois::Raw::Data::ip_whois_servers{ $1 };
479             #last;
480             } elsif ((/OrgID:\s+(\w+)/ || /descr:\s+(\w+)/) && Net::Whois::Raw::Common::is_ipaddr($query)) {
481 4         35 my $value = $1;
482 4 50       16 if($value =~ /^(?:RIPE|APNIC|KRNIC|LACNIC)$/) {
483 0         0 $new_server = $Net::Whois::Raw::Data::ip_whois_servers{$value};
484 0         0 last;
485             }
486             } elsif (/^\s+Maintainer:\s+RIPE\b/ && Net::Whois::Raw::Common::is_ipaddr($query)) {
487 0         0 $new_server = $Net::Whois::Raw::Data::servers{RIPE};
488 0         0 last;
489             }
490             }
491            
492 11 100       90 if ($new_server) {
493 4         12 foreach my $result (@prev_results) {
494 4 50 33     37 return undef if $result->{query} eq $new_query
495             && $result->{server} eq $new_server;
496             }
497             }
498            
499 11         44 return $new_server, $new_query;
500             }
501              
502             sub next_local_ip {
503 15     15 0 50 my ($server, $clientname, $rism) = @_;
504 15         38 clean_bans();
505             #clean_rism($rism) if $rism;
506            
507 15         31 my $i = 0;
508 15         54 while ($i <= @local_ips) {
509 15         22 $i++;
510 15   50     92 my $next_ip = shift @local_ips || 'localhost';
511 15 50       49 push @local_ips, $next_ip
512             unless $next_ip eq 'localhost';
513 15 50 33     76 if (!$servers_ban{$server} || !$servers_ban{$server}->{$next_ip}) {
514             #if ($clientname && $rism
515             # && $rism_all->{$clientname}->{$next_ip}->{$server}->{count} < $rism) {
516             # $rism_all->{$clientname}->{$next_ip}->{$server}->{count}++;
517             # return $next_ip;
518             #} else {
519             # return $next_ip;
520             #}
521 15         44 return $next_ip;
522             }
523             }
524            
525 0         0 return undef;
526             }
527              
528             #sub clean_rism {
529             # my ($rism) = @_;
530             # # brainfuck!
531             # foreach my $clientname (keys %$rism_all) {
532             # foreach my $ip (keys %{$rism_all->{$clientname}} ) {
533             # foreach my $server (keys %{$rism_all->{$clientname}->{$ip}} ) {
534             # if (
535             # $rism_all->{$clientname}->{$ip}->{$server}
536             # && ($rism_all->{$clientname}->{$ip}->{$server}->{start} + 61 < time)
537             # ) {
538             # $rism_all->{$clientname}->{$ip}->{$server}->{start} = time;
539             # $rism_all->{$clientname}->{$ip}->{$server}->{count} = 0;
540             # }
541             # }
542             # }
543             # }
544             #}
545              
546             sub clean_bans {
547             #my (@my_local_ips) = @local_ips || ('localhost');
548 15     15 0 59 foreach my $server (keys %servers_ban) {
549 0           foreach my $ip (keys %{$servers_ban{$server}}) {
  0            
550             #print $Net::Whois::Raw::Data::ban_time{$server}."\n";
551 0 0 0       delete $servers_ban{$server}->{$ip}
552             if time - $servers_ban{$server}->{$ip}
553             >=
554             (
555             $Net::Whois::Raw::Data::ban_time{$server}
556             || $Net::Whois::Raw::Data::default_ban_time
557             )
558             ;
559             }
560 0 0         delete $servers_ban{$server} unless %{$servers_ban{$server}};
  0            
561             }
562             }
563              
564             sub unban_time {
565 0     0 0   my ($server, $clientname, $rism) = @_;
566 0           my $unban_time;
567            
568 0   0       my (@my_local_ips) = @local_ips || ('localhost');
569            
570 0           foreach my $ip (@my_local_ips) {
571 0   0       my $ip_unban_time
572             = (
573             $Net::Whois::Raw::Data::ban_time{$server}
574             || $Net::Whois::Raw::Data::default_ban_time
575             )
576             - (time - $servers_ban{$server}->{$ip});
577 0 0         $ip_unban_time = 0 if $ip_unban_time < 0;
578 0 0 0       $unban_time = $ip_unban_time
579             if !defined $unban_time || $unban_time > $ip_unban_time;
580             }
581              
582 0           return $unban_time+1;
583             }
584              
585             1;
586             __END__