File Coverage

blib/lib/POE/Component/Client/Whois/Smart/NetWhoisRaw.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             #
2             #===============================================================================
3             #
4             # FILE: NetWhoisRaw.pm
5             #
6             # DESCRIPTION: POE::Component::Client::Whois::Smart::NetWhoisRaw
7             #
8             # FILES: ---
9             # BUGS: ---
10             # NOTES: ---
11             # AUTHOR: Pavel Boldin (),
12             # COMPANY:
13             # VERSION: 1.0
14             # CREATED: 24.05.2009 19:09:08 MSD
15             # REVISION: ---
16             #===============================================================================
17              
18             package POE::Component::Client::Whois::Smart::NetWhoisRaw;
19              
20 1     1   1098 use strict;
  1         2  
  1         55  
21 1     1   6 use warnings;
  1         2  
  1         106  
22              
23 0           use POE qw(Filter::Stream Wheel::ReadWrite Wheel::SocketFactory
24 1     1   649 Component::Client::DNS);
  0            
25             use Socket;
26             use HTTP::Request;
27              
28             use CLASS;
29              
30             use List::Util qw/first/;
31             use Hash::MoreUtils qw/slice/;
32              
33             use Time::HiRes qw( time );
34              
35             use Data::Dumper;
36              
37             use POE::Component::Client::Whois::Smart; # for utility functions
38             use Net::Whois::Raw::Common;
39              
40             sub DEBUG { 1 }
41              
42             our $named;
43              
44             sub initialize {
45             $named = POE::Component::Client::DNS->spawn(
46             Alias => 'named',
47             Timeout => 10,
48             );
49              
50             1;
51             }
52              
53             sub query_order {
54             15
55             }
56              
57             sub plugin_params {
58             return (
59             use_cnames => undef,
60             cache_dir => undef,
61             cache_time => 1,
62             omit_msg => 2,
63             exceed_wait => 0,
64             referral => 1,
65              
66             retry_another_ip => 1,
67             );
68             }
69              
70             sub query {
71             my $self = shift;
72             my $query_list = shift;
73            
74             my @my_queries = @$query_list;
75             @$query_list = ();
76              
77             $self->_query( \@my_queries, @_ );
78             }
79              
80             sub _query {
81             my $package = shift;
82             my $queries = shift;
83             my $heap = shift;
84             my $args_ref = shift;
85              
86             #$args{lc $_} = delete $args{$_} for keys %args;
87              
88             $package->get_whois_for_all( $queries, $heap, $args_ref );
89             }
90              
91             sub get_whois_for_all {
92             my ($package, $queries, $heap, $args_ref) = @_;
93              
94             my %my_params = slice(
95             $heap->{params}, qw/referral exceed_wait omit_msg use_cnames/
96             );
97            
98             foreach my $q (@$queries) {
99             ++$heap->{tasks};
100              
101             my $result = $heap->{result}{ $q } ||= [];
102             $package->get_whois(
103             %$args_ref,
104             retry_another_ip=> $heap->{params}{retry_another_ip},
105             query => $q,
106             original_query => $q,
107             result => $result,
108             params => \%my_params,
109             );
110             }
111             }
112              
113             sub get_whois {
114             my $package = shift;
115             $package = ref($package)|| $package;
116             my %args = @_;
117              
118             if ( $args{query} eq 'pleasetesttimeoutonthisdomainrequest.com' ) {
119             sleep 10;
120             return;
121             }
122              
123             unless ( $args{host} ) {
124             my $whois_server = Net::Whois::Raw::Common::get_server($args{query}, $args{params}->{use_cnames});
125             unless ( $whois_server ) {
126             warn "Could not determine whois server from query string, defaulting to internic \n";
127             $whois_server = 'whois.internic.net';
128             }
129             $args{host} = $whois_server;
130             }
131              
132             my $self = bless {
133             result => delete( $args{result} ),
134             params => delete( $args{params} ),
135             request => \%args,
136             }, $package;
137              
138             $self->{session_id} = POE::Session->create(
139             object_states => [
140             $self => [
141             qw/
142             _start _start_resolve _start_query
143             _sock_input _sock_down
144             _sock_up _sock_failed _time_out
145             /
146             ],
147             ],
148             options => { trace => 0 },
149             )->ID();
150              
151             return $self;
152             }
153              
154             # connects to whois-server (socket)
155             sub _start {
156             my ($kernel, $self) = @_[KERNEL,OBJECT];
157              
158             $kernel->delay_add( '_time_out' => $self->{request}->{timeout} );
159              
160             $kernel->yield('_start_resolve');
161             }
162              
163             sub _start_resolve {
164             my ($kernel, $self) = @_[KERNEL,OBJECT];
165              
166             my $response = $named->resolve(
167             event => "_start_query",
168             host => $self->{request}->{host},
169             timeout => $self->{request}->{timeout},
170             context => { },
171             );
172              
173             if ( $response ) {
174             $self->{resolved} = $response;
175             $kernel->yeild('_start_query');
176             }
177             }
178              
179             sub _start_query {
180             my ($kernel, $self, $resolved) = @_[KERNEL, OBJECT, ARG0];
181              
182             $resolved ||= $self->{resolved};
183              
184             my $resolved_host;
185              
186             if ( $resolved->{response} ) {
187             foreach my $answer ( $resolved->{response}->answer() ) {
188             if ( $answer->type eq 'A' ) {
189             $resolved_host = $answer->rdatastr;
190             last;
191             }
192             }
193             }
194              
195             unless ( $resolved_host ) {
196             $kernel->yield( '_sock_failed',
197             'host resolve of '.$self->{request}{host}.' failed', '', '' );
198             return;
199             }
200              
201             if ( not exists $self->{request}{local_ip} ) {
202             my $local_ip = next_local_ip(
203             $self->{request}->{host},
204             $self->{request}->{clientname},
205             $self->{request}->{rism},
206             );
207            
208             unless ( $local_ip ) {
209             my $unban_time = unban_time(
210             $self->{request}->{host},
211             $self->{request}->{clientname},
212             $self->{request}->{rism},
213             );
214             my $delay_err = $kernel->delay_add('_start', $unban_time);
215             warn "All IPs banned for server ".$self->{request}->{host}.
216             ", waiting: $unban_time sec\n"
217             if DEBUG;
218             return;
219             }
220              
221             #warn $local_ip;
222              
223             $self->{request}{local_ip} = $local_ip eq 'default' ? undef : $local_ip;
224             }
225              
226             # do it here, because we can yeild to _start from referral/another IP retry
227             # and get another query in case of referral retry (new_query, see get_recursion)
228            
229             my $request = $self->{request};
230              
231             $request->{query_real} =
232             Net::Whois::Raw::Common::get_real_whois_query(
233             $request->{query},
234             $request->{host}
235             );
236              
237             $request->{referral_retry} = 0;
238              
239             print time, " $self->{session_id}: Query '".$request->{query_real}.
240             "' to ".$request->{host}.
241             " from ".($request->{local_ip}||'default IP')."\n"
242             if DEBUG;
243              
244             $self->{server} = POE::Wheel::SocketFactory->new(
245             SocketDomain => AF_INET,
246             SocketType => SOCK_STREAM,
247             SocketProtocol => 'tcp',
248             RemoteAddress => $resolved_host,
249             RemotePort => $self->{request}->{port} || 43,
250             BindAddress => $self->{request}->{local_ip},
251             SuccessEvent => '_sock_up',
252             FailureEvent => '_sock_failed',
253             );
254              
255             undef;
256             }
257              
258             # socket error
259             sub _sock_failed {
260             my ($kernel, $self, $op, $errno, $errstr) = @_[KERNEL, OBJECT, ARG0..ARG2];
261              
262             #warn "_sock_failed: $self->{request}{query}";
263              
264             $kernel->delay( '_time_out' => undef );
265              
266             delete $self->{server};
267              
268             $self->{request}->{error} = "$op error $errno: $errstr";
269             my $request = delete $self->{request};
270             my $session = delete $request->{manager_id};
271              
272             return unless $self->process_query( $request );
273              
274             $kernel->post( $session => $request->{event} => $request );
275            
276             undef;
277             }
278              
279             # connection with socket established, send query
280             sub _sock_up {
281             my ($kernel, $self, $session, $socket) = @_[KERNEL, OBJECT, SESSION, ARG0];
282             delete $self->{server};
283              
284             $self->{server} = new POE::Wheel::ReadWrite(
285             Handle => $socket,
286             Driver => POE::Driver::SysRW->new(),
287             Filter => POE::Filter::Stream->new(),
288             InputEvent => '_sock_input',
289             ErrorEvent => '_sock_down',
290             AutoFlush => 1,
291             );
292              
293             unless ( $self->{server} ) {
294             my $request = delete $self->{request};
295             my $session = delete $request->{manager_id};
296             $request->{error} = "Couldn\'t create a Wheel::ReadWrite on socket for whois";
297             $kernel->post( $session => $request->{event} => $request );
298            
299             return undef;
300             }
301              
302             $self->{request}->{whois} = '';
303              
304             $self->{server}->put( $self->{request}->{query_real}."\r\n" );
305            
306             undef;
307             }
308              
309             # connection with socket finished, post result to manager
310             sub _sock_down {
311             my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
312              
313             #warn "_sock_down: $self->{request}{query}\n";
314              
315             delete $self->{server};
316              
317             $kernel->delay( '_time_out' => undef );
318              
319             my $request = $self->{request};
320              
321             if ( $request->{whois} ) {
322             delete $request->{error};
323             } else {
324             $request->{error} = "No information received from remote host";
325             }
326              
327             return unless $self->process_query( $request );
328              
329             $kernel->post( $request->{manager_id} => $request->{event} => $request );
330            
331             undef;
332             }
333              
334             # got input from socket, save it
335             sub _sock_input {
336             my ($kernel,$self,$line) = @_[KERNEL,OBJECT,ARG0];
337             #warn $line;
338              
339             $self->{request}->{whois} .= $line;
340              
341             undef;
342             }
343              
344             # socket timeout, abort connection
345             sub _time_out {
346             my ($kernel,$self) = @_[KERNEL,OBJECT];
347              
348             delete $self->{server};
349              
350             #warn Dumper $self;
351              
352             #warn "_time_out: $self->{request}{query}\n";
353            
354             my $request = delete $self->{request};
355             # my $session = delete $request->{manager_id};
356              
357             #warn Dumper $request;
358              
359             $request->{error} = "Timeout";
360              
361             return unless $self->process_query( $request );
362              
363             $kernel->post( $request->{manager_id} => $request->{event} => $request );
364            
365             undef;
366             }
367              
368             sub process_query {
369             my $self = shift;
370             my $response = shift;
371              
372             my ($whois, $error);
373              
374             #warn Dumper $self;
375              
376             $error = $response->{error};
377              
378             if ( ! $error ) {
379             $whois = $response->{whois};
380              
381             ($whois, $error) = Net::Whois::Raw::Common::process_whois(
382             $response->{original_query},
383             $response->{host},
384             $whois,
385             1,
386             $self->{params}->{omit_msg},
387             2,
388             );
389             }
390              
391             #warn Dumper $error, $response, $self->{result}; #if $error;
392             print time, " $self->{session_id}: DONE: '",$response->{query},
393             "' to ",$response->{host}, "\n" if DEBUG;
394              
395             if ( !$error || ! @{ $self->{result} } ) {
396              
397             my %result = (
398             query => $response->{query},
399             server => $response->{host},
400             query_real => $response->{query_real},
401             whois => $whois,
402             error => $error,
403             );
404            
405             push @{ $self->{result} }, \%result;
406              
407             my ($new_server, $new_query);
408              
409             if ( $result{whois} ) {
410             ($new_server, $new_query) = get_recursion(
411             $result{whois},
412             $result{server},
413             $result{query},
414             @{ $self->{result} },
415             )
416             }
417              
418             if ( $self->{params}->{referral}
419             && $new_server
420             && $response->{referral_retry}++ < 10
421             ) {
422              
423             $response->{query} = $new_query;
424             $response->{host} = $new_server;
425              
426             delete $response->{error};
427             delete $response->{whois};
428              
429             $poe_kernel->yield('_start');
430             return;
431             }
432             }
433              
434             # exceed
435             if ($error && $error eq 'Connection rate exceeded') {
436             my $current_ip = $response->{local_ip} || 'localhost';
437             #$servers_ban{$response->{host}}->{$current_ip} = time;
438             print "Connection rate exceeded for IP: $current_ip, server: "
439             .$response->{host}."\n"
440             if DEBUG;
441            
442             # check for next_local_ip here
443             if ( $response->{retry_another_ip}-- >= 0 ) {
444             #warn "THERE!!!";
445              
446             # try to fetch next IP smart -- only all IP's are equal
447             my $old_local_ip = delete $response->{local_ip};
448             if ( not exists $self->{local_ips} ) {
449             %{ $self->{local_ips} } =
450             map { $_ => 0 } local_ips();
451             }
452              
453             my $i;
454              
455             if ( defined $old_local_ip ) {
456             $i = ++$self->{local_ips}{ $old_local_ip };
457             }
458              
459             # warn "$i ", Dumper $self->{local_ips};
460              
461             $response->{local_ip} =
462             first { $i > $self->{local_ips}{ $_ } } local_ips();
463            
464             $response->{local_ip} ||= next_local_ip();
465              
466             delete $response->{error};
467             delete $response->{whois};
468              
469             $poe_kernel->yield('_start');
470             return;
471             }
472             }
473            
474             return 1;
475             }
476              
477              
478             #---------------------------------------------------------------------------
479             # Utility functions
480             #---------------------------------------------------------------------------
481              
482             # check whois-info, if it has referrals, return new server and query
483             sub get_recursion {
484             my ($whois, $server, $query, @prev_results) = @_;
485              
486             my ($new_server, $registrar);
487             my $new_query = $query;
488            
489             foreach (split "\n", $whois) {
490             $registrar ||= /Registrar/ || /Registered through/;
491            
492             if ($registrar && /Whois Server:\s*([A-Za-z0-9\-_\.]+)/) {
493             $new_server = lc $1;
494             #last;
495             } elsif ($whois =~ /To single out one record, look it up with \"xxx\",/s) {
496             $new_server = $server;
497             $new_query = "=$query";
498             last;
499             } elsif (/ReferralServer: whois:\/\/([-.\w]+)/) {
500             $new_server = $1;
501             last;
502             } elsif (/Contact information can be found in the (\S+)\s+database/) {
503             $new_server = $Net::Whois::Raw::Data::ip_whois_servers{ $1 };
504             #last;
505             } elsif ((/OrgID:\s+(\w+)/ || /descr:\s+(\w+)/) && Net::Whois::Raw::Common::is_ipaddr($query)) {
506             my $value = $1;
507             if($value =~ /^(?:RIPE|APNIC|KRNIC|LACNIC)$/) {
508             $new_server = $Net::Whois::Raw::Data::ip_whois_servers{$value};
509             last;
510             }
511             } elsif (/^\s+Maintainer:\s+RIPE\b/ && Net::Whois::Raw::Common::is_ipaddr($query)) {
512             $new_server = $Net::Whois::Raw::Data::servers{RIPE};
513             last;
514             }
515             }
516            
517             if ($new_server) {
518             foreach my $result (@prev_results) {
519             return undef if $result->{query} eq $new_query
520             && $result->{server} eq $new_server;
521             }
522             }
523            
524             return $new_server, $new_query;
525             }
526              
527             my $pccws = 'POE::Component::Client::Whois::Smart';
528              
529             sub next_local_ip {
530             goto \&{$pccws.'::next_local_ip'};
531             }
532              
533             sub local_ips {
534             goto \&{$pccws.'::local_ips'};
535             }
536              
537             sub unban_time {
538             goto \&{$pccws.'::unban_time'};
539             }
540             1;