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   242399 use 5.010;
  12         39  
  12         469  
37 12     12   60 use strict;
  12         16  
  12         444  
38 12     12   76 use warnings;
  12         30  
  12         518  
39 12     12   58 use Carp qw (croak);
  12         14  
  12         887  
40 12     12   5979 use Module::Load;
  12         10649  
  12         69  
41 12     12   7214 use IO::Socket::INET;
  12         241533  
  12         84  
42 12     12   13638 use Time::HiRes qw (usleep);
  12         15688  
  12         53  
43              
44             our $VERSION = '0.18';
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 195 my $class = shift;
60 11   50     43 my $self = shift || {};
61 11         28 bless $self, $class;
62 11         76 $self->{tlds} = {};
63 11 50       55 $self->{use_registrar} = undef unless exists $self->{use_registrar};
64 11 50       60 $self->{timeout} = 4 unless exists $self->{timeout};
65 11 50       41 $self->{_request} = \&_send_request unless exists $self->{_request};
66 11         17 our (@modules);
67 11 50       42 @modules = @{$self->{modules}} if exists $self->{modules};
  11         74  
68 11         19 my ($m,$t);
69 11         28 foreach (@modules) {
70 10         33 $m = 'Net::DAS::'.uc($_);
71 10         15 eval {
72 10         47 load($m);
73 10         153 $self->{$m} = $m->register();
74 10         22 foreach my $t (@{$self->{$m}->{tlds}}) {
  10         36  
75 18         50 $self->{tlds}->{$t} = $m;
76             }
77             };
78 10 50       44 if ($@) {
79 0         0 warn "Warning: unable to load module $m: $@\n";
80 0         0 next;
81             }
82             }
83 11         33 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 4349 my ($self,@domains) = @_;
108 49 50       126 return { 'avail'=>-1,'reason'=>'NO DOMAIN SPECIFIED' } unless @domains;
109 49         221 my ($r,$b) = {};
110 49         87 foreach my $i (@domains)
111             {
112 49         98 chomp($i);
113 49         118 $r = {'domain' => $i};
114 49         72 eval {
115 49         117 ($r->{'label'},$r->{'tld'}) = $self->_split_domain($i);
116 49 50       192 croak ("TLD ($r->{'tld'}) not supported") unless ($r->{'module'} = $self->{tlds}->{$r->{'tld'}});
117 49 100       179 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   648 local $SIG{ALRM} = sub { die "TIMEOUT\n" };
  0         0  
121 49         324 alarm $self->{timeout};
122 49         165 chomp ($r->{'response'} = $self->{_request}->($self,$r->{'query'},$r->{module}));
123 49         276 alarm 0;
124              
125 49 100       211 $r->{'avail'} = defined($disp->[1]) ? $disp->[1]->($r->{'response'},$i) : $self->_parse($r->{'response'},$i);
126 49 100       152 $r->{'reason'} = 'AVAILABLE' if $r->{'avail'} == 1;
127 49 100       219 $r->{'reason'} = 'NOT AVAILABLE' if $r->{'avail'} == 0;
128 49 50       98 $r->{'reason'} = 'NOT VALID' if $r->{'avail'} == -1;
129 49 100       91 $r->{'reason'} = 'NOT AUTHORIZED' if $r->{'avail'} == -2;
130 49 100       92 $r->{'reason'} = 'IP BLOCKED' if $r->{'avail'} == -3;
131 49 100       345 $r->{'reason'} = 'UNABLE TO PARSE RESPONSE' if $r->{'avail'} == -100;
132             };
133 49 50       107 if ($@) {
134 0         0 chomp($r->{reason} = $@);
135 0         0 $r->{avail}=-1;
136             }
137 49         139 $b->{$i} = $r;
138             };
139 49         128 $self->_close_ports();
140 49         104 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 48806 my ($self,$dom) = @_;
155 24         69 my $r = $self->lookup($dom);
156 24         106 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   81 my ($self,$i) = @_;
170 51 100 66     448 return ($1,$2) if $i =~ m/(.*)\.(.*\..*)/ && exists $self->{tlds}->{$2};
171 43 50       331 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   24 my $self = shift;
213 22         48 chomp (my $i = uc(shift));
214 22 50       62 return -3 if $i =~ m/IP ADDRESS BLOCKED/;
215 22 100       291 return 1 if $i =~ m/.*STATUS:\sAVAILABLE/;
216 12 100       227 return 0 if $i =~ m/.*STATUS:\sNOT AVAILABLE/;
217 2 50       5 return -1 if $i =~ m/.*STATUS:\sNOT VALID/;
218 2         3 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   65 my $self = shift;
229 60 50       143 return unless defined $self->{modules};
230 60         59 foreach my $k (@{$self->{modules}}) {
  60         118  
231 53         89 my $m = 'NET::DAS'.$k;
232 53 0 33     182 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         80 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   4094 my $self = shift;
247 11 50       71 $self->_close_ports() if defined $self->{modules};
248 11         336 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