File Coverage

blib/lib/Net/DAS.pm
Criterion Covered Total %
statement 90 116 77.5
branch 35 68 51.4
condition 5 20 25.0
subroutine 14 16 87.5
pod 3 3 100.0
total 147 223 65.9


line stmt bran cond sub pod time code
1             package Net::DAS;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Net::DAS - Simple Domain Availabilty Seach client.
8              
9             =head1 SYNOPSIS
10              
11             # new object
12             my $das = Net::DAS->new();
13             # you can change query timeout, set to use registrar DAS servers (where available), select only specific modules, and override the requst function (normally for testing)
14             my $das = Net::DAS->new({timeout=>2,use_registrar=>1,modules=>['eu','be'],_request=>\&my_request});
15            
16             # lookup() always works in batch mode, so if you are only looking up a single domain you can access that domains result directly
17             my $res =$das->lookup('test.eu')->{'test.eu'};
18             if ($res->{'avail'}) {
19             # do something
20             } else {
21             print $res->{'reason'};
22             }
23              
24             # or with multiple domains
25             my $res =$das->lookup('test.eu','test2.eu','test3.eu');
26             print $res->{'test2.eu'}->{'reason'};
27            
28             =head1 DESCRIPTION
29              
30             Net::DAS is a client that aims to simplify using DAS with multiple registries by having small submodules (see L) to iron out the differences in the servers. It also inclused a shell script L to do lookups from the command line.
31              
32             =head1 PUBLIC METHODS
33              
34             =cut
35              
36 13     13   418250 use 5.010;
  13         56  
37 13     13   82 use strict;
  13         28  
  13         446  
38 13     13   71 use warnings;
  13         31  
  13         801  
39 13     13   99 use Carp qw (croak);
  13         25  
  13         1260  
40 13     13   9497 use Module::Load;
  13         17919  
  13         113  
41 13     13   10802 use IO::Socket::INET;
  13         361509  
  13         119  
42 13     13   18148 use Time::HiRes qw (usleep);
  13         21019  
  13         70  
43              
44             our $VERSION = '0.19';
45             our @modules = qw (EU BE NO LT UK SI IT GENT SE NU RO);
46              
47             =pod
48              
49             =head2 new
50              
51             Accepts a hash reference with available options being timeout (integer default 4), use_registrar (bool default 0), modules (array_ref default all), _request (sub - only used for overriding request method for testing)
52              
53             my $das = Net::DAS->new();
54             my $das = Net::DAS->new({timeout=>2,use_registrar=>1,modules=>['eu','be'],_request=>\&my_request});
55              
56             =cut
57              
58             sub new {
59 12     12 1 231 my $class = shift;
60 12   50     67 my $self = shift || {};
61 12         30 bless $self, $class;
62 12         79 $self->{tlds} = {};
63 12 50       63 $self->{use_registrar} = undef unless exists $self->{use_registrar};
64 12 50       93 $self->{timeout} = 4 unless exists $self->{timeout};
65 12 50       55 $self->{_request} = \&_send_request unless exists $self->{_request};
66 12         21 our (@modules);
67 12 50       53 @modules = @{ $self->{modules} } if exists $self->{modules};
  12         98  
68 12         26 my ( $m, $t );
69              
70 12         40 foreach (@modules) {
71 11         46 $m = 'Net::DAS::' . uc($_);
72 11         21 eval {
73 11         81 load($m);
74 11         189 $self->{$m} = $m->register();
75 11         27 foreach my $t ( @{ $self->{$m}->{tlds} } ) {
  11         48  
76 29         83 $self->{tlds}->{$t} = $m;
77             }
78             };
79 11 50       56 if ($@) {
80 0         0 warn "Warning: unable to load module $m: $@\n";
81 0         0 next;
82             }
83             }
84 12         32 return $self;
85             }
86              
87             =pod
88              
89             =head2 lookup
90              
91             Lookup domain availability in batch mode. You can specify 1 or more domains, but always works in batch mode, so if you are only looking up a single domain you can access that domains result directly by using the domain name as a reference. When looking up multiple domains, just send an array and the return will be a hashref with the domain names as the keys
92              
93             my $res =$das->lookup('test.eu')->{'test.eu'};
94             if ($res->{'avail'}) {
95             # do something
96             } else {
97             print $res->{'reason'};
98             }
99              
100             # or with multiple domains
101             my $res =$das->lookup('test.eu','test2.eu','test3.eu');
102             my $res =$das->lookup(@domains);
103             print $res->{'test2.eu'}->{'reason'};
104              
105             =cut
106              
107             sub lookup {
108 53     53 1 8667 my ( $self, @domains ) = @_;
109 53 50       190 return { 'avail' => -1, 'reason' => 'NO DOMAIN SPECIFIED' } unless @domains;
110 53         261 my ( $r, $b ) = {};
111 53         117 foreach my $i (@domains) {
112 53         98 chomp($i);
113 53         177 $r = { 'domain' => $i };
114 53         108 eval {
115 53         142 ( $r->{'label'}, $r->{'tld'} ) = $self->_split_domain($i);
116 53 50       248 croak("TLD ($r->{'tld'}) not supported") unless ( $r->{'module'} = $self->{tlds}->{ $r->{'tld'} } );
117 53 100       232 my ($disp) = defined $self->{ $r->{module} }->{dispatch} ? $self->{ $r->{module} }->{dispatch} : [];
118 53 100       192 chomp( $r->{'query'} = defined( $disp->[0] ) ? $disp->[0]->( $r->{'domain'} ) : $r->{'domain'} );
119              
120 53     0   901 local $SIG{ALRM} = sub { die "TIMEOUT\n" };
  0         0  
121 53         293 alarm $self->{timeout};
122 53         183 chomp( $r->{'response'} = $self->{_request}->( $self, $r->{'query'}, $r->{module} ) );
123 53         354 alarm 0;
124              
125             $r->{'avail'}
126             = defined( $disp->[1] )
127             ? $disp->[1]->( $r->{'response'}, $i )
128 53 100       250 : $self->_parse( $r->{'response'}, $i );
129 53 100       197 $r->{'reason'} = 'AVAILABLE' if $r->{'avail'} == 1;
130 53 100       223 $r->{'reason'} = 'NOT AVAILABLE' if $r->{'avail'} == 0;
131 53 50       121 $r->{'reason'} = 'NOT VALID' if $r->{'avail'} == -1;
132 53 100       131 $r->{'reason'} = 'NOT AUTHORIZED' if $r->{'avail'} == -2;
133 53 100       134 $r->{'reason'} = 'IP BLOCKED' if $r->{'avail'} == -3;
134 53 100       440 $r->{'reason'} = 'UNABLE TO PARSE RESPONSE' if $r->{'avail'} == -100;
135             };
136 53 50       142 if ($@) {
137 0         0 chomp( $r->{reason} = $@ );
138 0         0 $r->{avail} = -1;
139             }
140 53         186 $b->{$i} = $r;
141             }
142 53         151 $self->_close_ports();
143 53         135 return $b;
144             }
145              
146             =pod
147              
148             =head2 available
149              
150             A quick function to lookup availability of a single domain without details. Warning, you should check if the result == 1, as there are different return codes.
151              
152             print "available" if $das->availabile('test.eu')==1;
153              
154             =cut
155              
156             sub available {
157 26     26 1 60595 my ( $self, $dom ) = @_;
158 26         82 my $r = $self->lookup($dom);
159 26         139 return $r->{$dom}->{'avail'};
160             }
161              
162             =pod
163              
164             =head1 PRIVATE METHODS
165              
166             =item _split_domain : splits a domain into an array ($dom,$tld)
167              
168             =cut
169              
170             sub _split_domain {
171 55     55   97 my ( $self, $i ) = @_;
172 55 50 66     385 return ( $1, $2 ) if $i =~ m/(.*)\.(.*\..*)/ && exists $self->{tlds}->{$2};
173 47 50       424 return ( $1, $2 ) if $i =~ m/(.*)\.(.*)/;
174 0         0 croak( 'Invalid domain ' . $i );
175 0         0 return;
176             }
177              
178             =pod
179              
180             =item _send_request : should not be called directly, its called by lookup()
181              
182             =cut
183              
184             sub _send_request {
185 0     0   0 my ( $self, $q, $m ) = @_;
186 0 0 0     0 my $svc = ( $self->{use_registrar} && exists $self->{$m}->{registrar} ) ? 'registrar' : 'public';
187 0         0 my $h = $self->{$m}->{$svc}->{host};
188 0 0       0 my $p = defined $self->{$m}->{$svc}->{port} ? $self->{$m}->{$svc}->{port} : 4343;
189 0 0       0 my $pr = defined $self->{$m}->{$svc}->{proto} ? $self->{$m}->{$svc}->{proto} : 'tcp';
190 0 0       0 my $nl = defined $self->{$m}->{nl} ? $self->{$m}->{nl} : "\n";
191 0 0 0     0 if ( !$self->{$m}->{sock} || !$self->{$m}->{sock}->connected() ) {
192 0   0     0 $self->{$m}->{sock} = IO::Socket::INET->new( PeerAddr => $h, PeerPort => $p, Proto => $pr, Timeout => 30 )
193             || croak("Unable to connect to $h:$p $@");
194             }
195              
196             #usleep($self->{$m}->{delay}) if exists $self->{$m}->{delay};
197 0         0 $self->{$m}->{sock}->syswrite( $q . "$nl" );
198 0         0 my ( $res, $buf );
199 0         0 while ( $self->{$m}->{sock}->sysread( $buf, 1024 ) ) {
200 0         0 $res .= $buf;
201 0 0       0 last if $self->{$m}->{sock}->atmark;
202             }
203 0 0       0 unless ( exists $self->{$m}->{close_cmd} ) {
204 0         0 $self->{$m}->{sock}->close();
205 0         0 undef $self->{$m}->{sock};
206             }
207 0         0 return $res;
208             }
209              
210             =pod
211              
212             =item _parse : should not be called directly, its called by lookup(). This sub is normally overriden by the registry module's parser
213              
214             =cut
215              
216             sub _parse {
217 26     26   45 my $self = shift;
218 26         70 chomp( my $i = uc(shift) );
219 26 50       110 return -3 if $i =~ m/IP ADDRESS BLOCKED/;
220 26 100       235 return 1 if $i =~ m/.*STATUS:\sAVAILABLE/;
221 14 100       123 return 0 if $i =~ m/.*STATUS:\sNOT AVAILABLE/;
222 2 50       4 return -1 if $i =~ m/.*STATUS:\sNOT VALID/;
223 2         3 return (-100);
224             }
225              
226             =pod
227              
228             =item _close_ports : closes any open sockets; you should'nt need to call this.
229              
230             =cut
231              
232             sub _close_ports {
233 65     65   89 my $self = shift;
234 65 50       187 return unless defined $self->{modules};
235 65         87 foreach my $k ( @{ $self->{modules} } ) {
  65         173  
236 58         140 my $m = 'NET::DAS' . $k;
237 58 0 33     1489 next unless exists $self->{$m} && !defined $self->{$m}->{sock} && $self->{$m}->{sock}->connected();
      33        
238 0 0       0 $self->{$m}->{sock}->syswrite( $self->{$m}->{close_cmd} ) if exists $self->{$m}->{close_cmd};
239 0         0 undef $self->{$m}->{sock};
240             }
241 65         111 return;
242             }
243              
244             =pod
245              
246             =item DESTROY: ensures that any open sockets are closed cleanly before closing; you dont need to call this.
247              
248             =cut
249              
250             sub DESTROY {
251 12     12   6224 my $self = shift;
252 12 50       97 $self->_close_ports() if defined $self->{modules};
253 12         585 undef $self->{modules};
254             }
255              
256             1;
257              
258             =pod
259              
260             =head1 AUTHOR
261              
262             Michael Holloway
263              
264             =head1 LICENSE
265              
266             Artistic License
267              
268             =cut