File Coverage

blib/lib/Net/DAS.pm
Criterion Covered Total %
statement 91 116 78.4
branch 36 66 54.5
condition 5 20 25.0
subroutine 14 16 87.5
pod 3 3 100.0
total 149 221 67.4


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 12     12   307919 use 5.010;
  12         43  
  12         523  
37 12     12   72 use strict;
  12         17  
  12         487  
38 12     12   65 use warnings;
  12         35  
  12         571  
39 12     12   68 use Carp qw (croak);
  12         21  
  12         944  
40 12     12   7127 use Module::Load;
  12         12125  
  12         72  
41 12     12   7986 use IO::Socket::INET;
  12         267796  
  12         97  
42 12     12   14469 use Time::HiRes qw (usleep);
  12         18400  
  12         61  
43              
44             our $VERSION = '0.17';
45             our @modules = qw (EU BE NO LT UK SI IT GENT SE NU);
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 11     11 1 5551 my $class = shift;
60 11   50     48 my $self = shift || {};
61 11         23 bless $self, $class;
62 11         72 $self->{tlds} = {};
63 11 50       59 $self->{use_registrar} = undef unless exists $self->{use_registrar};
64 11 50       69 $self->{timeout} = 4 unless exists $self->{timeout};
65 11 50       44 $self->{_request} = \&_send_request unless exists $self->{_request};
66 11         19 our (@modules);
67 11 50       43 @modules = @{$self->{modules}} if exists $self->{modules};
  11         75  
68 11         19 my ($m,$t);
69 11         30 foreach (@modules) {
70 10         39 $m = 'Net::DAS::'.uc($_);
71 10         18 eval {
72 10         54 load($m);
73 10         136 $self->{$m} = $m->register();
74 10         90 foreach my $t (@{$self->{$m}->{tlds}}) {
  10         40  
75 18         47 $self->{tlds}->{$t} = $m;
76             }
77             };
78 10 50       46 if ($@) {
79 0         0 warn "Warning: unable to load module $m: $@\n";
80 0         0 next;
81             }
82             }
83 11         32 return $self;
84             }
85              
86             =pod
87              
88             =head2 lookup
89              
90             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
91              
92             my $res =$das->lookup('test.eu')->{'test.eu'};
93             if ($res->{'avail'}) {
94             # do something
95             } else {
96             print $res->{'reason'};
97             }
98              
99             # or with multiple domains
100             my $res =$das->lookup('test.eu','test2.eu','test3.eu');
101             my $res =$das->lookup(@domains);
102             print $res->{'test2.eu'}->{'reason'};
103              
104             =cut
105              
106             sub lookup {
107 49     49 1 6252 my ($self,@domains) = @_;
108 49 50       124 return { 'avail'=>-1,'reason'=>'NO DOMAIN SPECIFIED' } unless @domains;
109 49         242 my ($r,$b) = {};
110 49         91 foreach my $i (@domains)
111             {
112 49         102 chomp($i);
113 49         119 $r = {'domain' => $i};
114 49         77 eval {
115 49         119 ($r->{'label'},$r->{'tld'}) = $self->_split_domain($i);
116 49 50       199 croak ("TLD ($r->{'tld'}) not supported") unless ($r->{'module'} = $self->{tlds}->{$r->{'tld'}});
117 49 100       192 my ($disp) = defined $self->{$r->{module}}->{dispatch} ? $self->{$r->{module}}->{dispatch} : [];
118 49 100       161 chomp ($r->{'query'} = defined($disp->[0]) ? $disp->[0]->($r->{'domain'}) : $r->{'domain'});
119              
120 49     0   629 local $SIG{ALRM} = sub { die "TIMEOUT\n" };
  0         0  
121 49         272 alarm $self->{timeout};
122 49         166 chomp ($r->{'response'} = $self->{_request}->($self,$r->{'query'},$r->{module}));
123 49         276 alarm 0;
124              
125 49 100       191 $r->{'avail'} = defined($disp->[1]) ? $disp->[1]->($r->{'response'},$i) : $self->_parse($r->{'response'},$i);
126 49 100       154 $r->{'reason'} = 'AVAILABLE' if $r->{'avail'} == 1;
127 49 100       130 $r->{'reason'} = 'NOT AVAILABLE' if $r->{'avail'} == 0;
128 49 50       101 $r->{'reason'} = 'NOT VALID' if $r->{'avail'} == -1;
129 49 100       94 $r->{'reason'} = 'NOT AUTHORIZED' if $r->{'avail'} == -2;
130 49 100       103 $r->{'reason'} = 'IP BLOCKED' if $r->{'avail'} == -3;
131 49 100       459 $r->{'reason'} = 'UNABLE TO PARSE RESPONSE' if $r->{'avail'} == -100;
132             };
133 49 50       106 if ($@) {
134 0         0 chomp($r->{reason} = $@);
135 0         0 $r->{avail}=-1;
136             }
137 49         148 $b->{$i} = $r;
138             };
139 49         128 $self->_close_ports();
140 49         100 return $b;
141             }
142              
143             =pod
144              
145             =head2 available
146              
147             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.
148              
149             print "available" if $das->availabile('test.eu')==1;
150              
151             =cut
152              
153             sub available {
154 24     24 1 53541 my ($self,$dom) = @_;
155 24         70 my $r = $self->lookup($dom);
156 24         107 return $r->{$dom}->{'avail'};
157             }
158              
159             =pod
160              
161             =head1 PRIVATE METHODS
162              
163             =item _split_domain : splits a domain into an array ($dom,$tld)
164              
165             =cut
166              
167             sub _split_domain
168             {
169 51     51   78 my ($self,$i) = @_;
170 51 100 66     462 return ($1,$2) if $i =~ m/(.*)\.(.*\..*)/ && exists $self->{tlds}->{$2};
171 43 50       358 return ($1,$2) if $i =~ m/(.*)\.(.*)/;
172 0         0 croak('Invalid domain ' . $i);
173 0         0 return;
174             }
175              
176             =pod
177              
178             =item _send_request : should not be called directly, its called by lookup()
179              
180             =cut
181              
182             sub _send_request {
183 0     0   0 my ($self,$q,$m) = @_;
184 0 0 0     0 my $svc = ($self->{use_registrar} && exists $self->{$m}->{registrar}) ? 'registrar' : 'public';
185 0         0 my $h = $self->{$m}->{$svc}->{host};
186 0 0       0 my $p = defined $self->{$m}->{$svc}->{port} ? $self->{$m}->{public}->{port} : 4343;
187 0 0       0 my $pr = defined $self->{$m}->{$svc}->{proto} ? $self->{$m}->{public}->{proto} : 'tcp';
188 0 0 0     0 if (!$self->{$m}->{sock} || !$self->{$m}->{sock}->connected()) {
189 0   0     0 $self->{$m}->{sock} = IO::Socket::INET->new(PeerAddr => $h, PeerPort => $p, Proto=> $pr, Timeout => 30) || croak("Unable to connect to $h:$p $@");
190             }
191             #usleep($self->{$m}->{delay}) if exists $self->{$m}->{delay};
192 0         0 $self->{$m}->{sock}->syswrite($q."\n");
193 0         0 my ($res,$buf);
194 0         0 while ($self->{$m}->{sock}->sysread($buf,1024)) {
195 0         0 $res .= $buf;
196 0 0       0 last if $self->{$m}->{sock}->atmark;
197             }
198 0 0       0 unless (exists $self->{$m}->{close_cmd}) {
199 0         0 $self->{$m}->{sock}->close();
200 0         0 undef $self->{$m}->{sock};
201             }
202 0         0 return $res;
203             }
204              
205             =pod
206              
207             =item _parse : should not be called directly, its called by lookup(). This sub is normally overriden by the registry module's parser
208              
209             =cut
210              
211             sub _parse {
212 22     22   32 my $self = shift;
213 22         47 chomp (my $i = uc(shift));
214 22 50       84 return -3 if $i =~ m/IP ADDRESS BLOCKED/;
215 22 100       308 return 1 if $i =~ m/.*STATUS:\sAVAILABLE/;
216 12 100       249 return 0 if $i =~ m/.*STATUS:\sNOT AVAILABLE/;
217 2 50       4 return -1 if $i =~ m/.*STATUS:\sNOT VALID/;
218 2         4 return (-100) ;
219             }
220              
221             =pod
222              
223             =item _close_ports : closes any open sockets; you should'nt need to call this.
224              
225             =cut
226              
227             sub _close_ports {
228 60     60   72 my $self = shift;
229 60 50       130 return unless defined $self->{modules};
230 60         68 foreach my $k (@{$self->{modules}}) {
  60         148  
231 53         87 my $m = 'NET::DAS'.$k;
232 53 0 33     191 next unless exists $self->{$m} && !defined $self->{$m}->{sock} && $self->{$m}->{sock}->connected();
      33        
233 0 0       0 $self->{$m}->{sock}->syswrite($self->{$m}->{close_cmd}) if exists $self->{$m}->{close_cmd};
234 0         0 undef $self->{$m}->{sock};
235             }
236 60         78 return;
237             }
238              
239             =pod
240              
241             =item DESTROY: ensures that any open sockets are closed cleanly before closing; you dont need to call this.
242              
243             =cut
244              
245             sub DESTROY {
246 11     11   5524 my $self = shift;
247 11 50       91 $self->_close_ports() if defined $self->{modules};
248 11         390 undef $self->{modules};
249             }
250              
251             1;
252              
253             =pod
254              
255             =head1 AUTHOR
256              
257             Michael Holloway
258              
259             =head1 LICENSE
260              
261             Artistic License
262              
263             =cut