File Coverage

blib/lib/IO/Socket/DNS.pm
Criterion Covered Total %
statement 12 254 4.7
branch 0 112 0.0
condition 0 41 0.0
subroutine 4 27 14.8
pod 0 13 0.0
total 16 447 3.5


line stmt bran cond sub pod time code
1             package IO::Socket::DNS;
2              
3 2     2   32589 use strict;
  2         5  
  2         71  
4 2     2   10 use warnings;
  2         4  
  2         64  
5 2     2   9 use Carp qw(croak);
  2         7  
  2         131  
6 2     2   10 use base qw(Tie::Handle);
  2         4  
  2         2139  
7              
8             our $VERSION = '0.021';
9              
10             our $count = 0;
11             # DNS Encoding is simply Base32 encoding using the following alphabet:
12             our $a32 = [0..9, "a".."w"];
13              
14             # Max number of bytes to send in each DNS query
15             our $MAX_WRITE = 100;
16              
17             # Sentinel value meaning "Incorrect Password"
18             our $INVALID_PASS = 999;
19              
20             # new
21             # This just returns the tie'd file handle
22             sub new {
23 0     0 0   my $class = shift;
24 0           require IO::Handle;
25 0           my $fh = IO::Handle->new;
26 0 0         my $obj = eval {tie *$fh, $class, @_} or return undef;
  0            
27 0           bless $fh, $class;
28 0           return $fh;
29             }
30              
31             sub _obj {
32 0     0     my $self = shift;
33 0 0         if (my $how = eval {tied *$self}) {
  0            
34 0           $self = $how;
35             }
36 0           return $self;
37             }
38              
39             sub suffix {
40 0     0 0   my $self = _obj(shift);
41 0   0       $self->{Suffix} ||= $ENV{DNS_SUFFIX} || "";
      0        
42 0           $self->{Suffix} = lc $self->{Suffix};
43 0           return $self->{Suffix};
44             }
45              
46             sub TXT_resolver {
47 0     0 0   my $self = shift;
48             return $self->{resolver_txt} ||= eval {
49 0           require Net::DNS::Resolver;
50             } ? sub {
51 0     0     my $name = shift;
52             # Faster method, but Net::DNS must be installed for this to work.
53 0           return eval { [$self->resolver->query($name, "TXT")->answer]->[0]->txtdata };
  0            
54 0 0 0       } : do {
55 0           my %args = $self->resolver_args;
56 0           my $nameservers = $args{nameservers};
57 0 0         if ($nameservers) {
58 0 0         $nameservers = [split m/ /, $nameservers] if !ref $nameservers;
59             }
60 0   0       $nameservers ||= [""];
61 0           warn "WARNING: Unable to find Net::DNS so reverting to nslookup (slow spawn) method ...\n";
62             # Return a closure containing the lexically scoped $nameservers variable.
63             sub {
64 0     0     my $name = shift;
65             # Make sure it is rooted to reduce unnecessary search scanning.
66 0           $name =~ s/\.*$/./;
67             # Try each resolver (if specified) until one works.
68 0           foreach my $server (@$nameservers) {
69             # Yes, it's slower, but is likely to work even if Net::DNS is gone.
70 0 0         if (`nslookup -type=TXT $name $server 2>&1`=~/"(.+)"/) {
71 0           return $1;
72             }
73             }
74 0           return undef;
75 0           };
76             };
77             };
78              
79             sub resolver_args {
80 0     0 0   my $self = _obj(shift);
81 0           my @args = !$self->{Resolver} ? ()
82             : !ref($self->{Resolver}) ? (nameservers => $self->{Resolver})
83 0           : "ARRAY" eq ref($self->{Resolver}) ? (@{ $self->{Resolver} })
84 0 0         : "HASH" eq ref($self->{Resolver}) ? (@{ %{ $self->{Resolver} } })
  0 0          
    0          
    0          
85             : ();
86 0           return @args;
87             }
88              
89             sub resolver {
90 0     0 0   my $self = _obj(shift);
91             return ($self->{net_dns} ||= eval {
92             require Net::DNS::Resolver;
93             return Net::DNS::Resolver->new($self->resolver_args);
94             } || eval {
95             # Try emergency "nslookup"
96             my $suffix = $self->suffix;
97             my $try = `nslookup -type=TXT nslookup.$suffix 2>&1`;
98             if ($try =~ /"(.+)"/) {
99             my $shell = $1;
100             $shell =~ s/\bperl\b/$^X/g;
101             system $shell;
102             warn "Reloading Net::DNS ...\n";
103             delete $INC{"Net/DNS.pm"};
104             delete $INC{"Net/DNS/Resolver.pm"};
105             require Net::DNS::Resolver;
106             return $self->resolver;
107             }
108             return undef;
109 0   0       } or do {
110             warn "Unable to obtain resolver. Please pass in your own net_dns setting: $@";
111             exit 1;
112             });
113             }
114              
115             sub dnsencode {
116 0     0 0   my $self = shift;
117 0           my $decode = shift;
118 0           my $x = unpack "B*", $decode;
119 0           my $encode = "";
120 0           while ($x =~ s/^([01]{1,5})//) {
121 0           my $c = $1;
122 0           $c .= 0 while length $c < 5;
123 0           $encode .= $a32->[unpack("C",pack("B*", "000$c"))];
124             };
125 0           while ($encode =~ s/(\w{62})(\w)/$1.$2/) {}
126 0           return $encode;
127             }
128              
129             sub dnsdecode {
130 0     0 0   my $self = shift;
131 0           my $encode = shift;
132 0           $encode =~ y/0-9a-w//cd;
133 0           $encode =~ y/0-9a-w/\0- /;
134 0           my $x = unpack "B*", $encode;
135 0           $x =~ s/000([01]{5})/$1/g;
136 0           my $decode = "";
137 0           while ($x =~ s/^([01]{8})//) {
138 0           my $c = $1; $decode .= pack("B*", $c);
  0            
139             }
140 0           return $decode;
141             }
142              
143             sub encrypt {
144 0     0 0   my $self = shift;
145 0           my $host = shift;
146 0           my $port = shift;
147 0 0         my $pass = $self->{Password} or return "0";
148             # Get rid of NUL chars:
149 0           my $code = "$host:$port" ^ $pass | "\x80" x 8;
150             # One way crypt:
151 0           my $dig = crypt($code, $host);
152 0           return "z".unpack H26 => $dig;
153             }
154              
155             # pending( [$timeout] )
156             # Check if there are any bytes pending ready for reading
157             # $timeout specifies maximum number of seconds to wait for data.
158             # If $timeout is undef, it will wait forever
159             # If $timeout is 0, it will only check and return immediately.
160             # Returns the number of bytes that are ready for reading.
161             # Returns "0 but true" if the socket is ready to be read from but is closed.
162             sub pending {
163 0     0 0   my $self = _obj(shift);
164 0           my $timeout;
165 0 0         if (@_) {
166 0           $timeout = shift;
167             }
168             else {
169 0           $timeout = 0;
170             }
171 0 0         if (my $ready = length $self->{Buffer_R}) {
172 0           return $ready;
173             }
174 0 0         my $try_until = defined($timeout) ? time() + $timeout : undef;
175 0 0         my $seqid = $self->{seqid} or return "00";
176 0           my $backoff = 0.5;
177 0   0       while (!defined($try_until) || time() <= $try_until) {
178 0           my $name = "$seqid.";
179 0 0         if (length $self->{Buffer_W}) {
180 0           my $chunk = substr($self->{Buffer_W}, 0, $MAX_WRITE, "");
181 0           $chunk = $self->dnsencode($chunk);
182 0           $name .= length($chunk).".$chunk";
183             }
184             else {
185 0           $name .= "z";
186             }
187 0           $name .= ".".$self->suffix;
188 0 0         if (my $txt = eval { $self->TXT_resolver->($name) } ) {
  0            
189 0 0         warn "DEBUG: TXT=[$txt]\n" if $self->{Verbose};
190 0 0         if ($txt =~ /^$seqid\b/) {
191             # Found relevant response
192 0 0         if ($txt eq "$seqid.0") {
193             # Socket closed by peer
194 0           $self->CLOSE;
195 0           return 0;
196             }
197 0 0         if ($txt =~ s/^$seqid\-(\w+)\.//) {
198             # Remember next seqid for later.
199 0           $self->{seqid} = $seqid = $1;
200 0 0         if ($txt eq "0") {
201             # Socket is still open, but no response yet.
202 0 0 0       if (defined $timeout && !$timeout) {
203             # Don't try again with timeout 0
204 0           return 0;
205             }
206              
207 0           select(undef,undef,undef, $backoff);
208 0           $backoff *= 1.25;
209             # Probe again after a little delay.
210 0           next;
211             }
212 0 0 0       if ($txt =~ /^(\d+)\.(.+)$/ and $1 == length($2)) {
213 0           my $encoded = $2;
214 0           $self->{Buffer_R} .= $self->dnsdecode($encoded);
215 0           return length($self->{Buffer_R});
216             }
217 0           warn "READ: Length mismatch in response [$txt]\n";
218 0           return undef;
219             }
220 0           warn "READ: Unimplimented response [$txt]\n";
221 0           return undef;
222             }
223 0           warn "READ: Insane response [$txt] does not begin with sequence [$seqid]?\n";
224 0           return undef;
225             }
226             # Already have seqid but the server suddenly can't respond correctly.
227             # Just wait a while and try again?
228 0 0         if ($backoff > 120) {
229 0           warn "pending: [$name] Got bored waiting for broken responses to stop.\n";
230 0           return undef;
231             }
232 0           select(undef,undef,undef, $backoff);
233 0           $backoff *= 1.3;
234             }
235              
236 0           return 0;
237             }
238              
239             # tie $fh, IO::Socket::DNS,
240             # PeerAddr => "$host:$ip",
241             # Suffix => $dns_suffix,
242             # Returns a blessed object tying the filehandle
243             sub TIEHANDLE {
244 0     0     my $class = shift;
245 0           my @args = @_;
246 0 0         if (@args == 1) {
247 0           @args = (PeerAddr => @args);
248             }
249 0 0         if (@args % 2) {
250 0           croak "Odd number of arguments is not supported";
251             }
252 0           my $self = { @args };
253 0           bless $self, $class;
254 0 0 0       $self->{PeerAddr} ||= $self->{PeerHost}
255             or croak "PeerAddr is required";
256 0 0 0       if (!$self->{PeerPort} and $self->{PeerAddr} =~ s/:(\d+)$//) {
257 0           $self->{PeerPort} = $1;
258             }
259 0           $self->{PeerAddr} =~ s/([^.])\.$/$1/;
260             #$self->{IdleTimeout} ||= 60;
261 0           $self->{Buffer_R} = "";
262 0           $self->{Buffer_W} = "";
263 0   0       $self->{Password} = $ENV{DNS_PASSWORD} || $self->{Password};
264 0 0         my $suffix = $self->suffix
265             or croak "Suffix must be specified";
266              
267             # Choose a fairly random ephemeral ID
268 0           srand($count++ + $$ + $self->{PeerPort} + time());
269 0           my $id = "";
270 0           for (1..6) {
271 0           $id .= $a32->[rand @$a32];
272             }
273              
274             # Send SYN packet
275 0           my $peer = lc("$self->{PeerAddr}");
276 0           my $code = $self->encrypt($peer, $self->{PeerPort});
277 0           my $name = "$peer.T$self->{PeerPort}.$id.$code.$suffix.";
278 0 0         warn "DEBUG: querying for [$name]\n" if $self->{Verbose};
279 0           require POSIX;
280 0 0         if (my $txt = eval { $self->TXT_resolver->($name) } ) {
  0            
281 0 0         warn "DEBUG: SYN=[$txt]\n" if $self->{Verbose};
282 0 0         if ($txt =~ s/^$id\.(\d+)//) {
283 0           my $status = $1;
284 0 0         if ($status) {
285 0 0         if ($status == $INVALID_PASS) {
286 0           require POSIX;
287 0           warn "IO::Socket::DNS Password incorrect.\n";
288 0           $! = POSIX::EACCES();
289             }
290             else {
291 0           $! = $status;
292             }
293 0           return;
294             }
295             # Connected perfectly. Need to grab magic sequence ID
296 0 0         if ($txt =~ s/^\.(\w+)//) {
297             # Found seqid!
298 0           $self->{seqid} = $1;
299             }
300             else {
301             # Missing seqid?
302 0           $! = POSIX::EINVAL();
303 0           return;
304             }
305             # Check for optional content
306 0 0         if ($txt =~ /^\.(\d+)\.(.*)/) {
307 0           my $len = $1;
308 0           my $content = $2;
309 0 0         if ($len == length $content) {
310             # Sanity check passed
311 0           $self->{Buffer_R} .= $self->dnsdecode($content);
312             }
313             else {
314             # Broken response?
315 0           $! = POSIX::ERANGE();
316 0           return;
317             }
318             }
319             else {
320             # Connected, but just no content response yet
321             }
322             }
323             else {
324             # Failed response sanity check?
325 0           $! = POSIX::ESRCH();
326 0           return;
327             }
328             }
329             else {
330 0           $! = POSIX::EHOSTDOWN();
331 0           warn "query: $@";
332 0           return;
333             #EHOSTUNREACH
334             #ENETDOWN
335             }
336              
337 0           return $self;
338             }
339              
340             sub sysread {
341 0     0 0   my $self = _obj(shift());
342 0           my (undef,$length,$offset) = @_;
343 0 0         $length or croak "READ: length is required";
344 0   0       $offset ||= 0;
345 0           my $chunk = "";
346 0           my $backoff = 0.5;
347 0           while (1) {
348 0 0         if ($length <= length $self->{Buffer_R}) {
349 0           $chunk = substr($self->{Buffer_R}, 0, $length, "");
350 0           last;
351             }
352 0 0         if (length $self->{Buffer_R}) {
353 0           $chunk = $self->{Buffer_R};
354 0           $self->{Buffer_R} = "";
355 0           last;
356             }
357 0 0         my $seqid = $self->{seqid} or return 0;
358 0           my $sniff = $self->pending(undef);
359 0 0         if (!defined $sniff) {
360 0           return undef;
361             }
362 0 0         if (!$sniff) {
363 0           return 0;
364             }
365 0 0         die "IMPLEMENTATION BUG: Yes pending but no Buffer_R?" if !length $self->{Buffer_R};
366             }
367              
368 0 0         $_[0] = "" if !defined $_[0];
369 0           substr($_[0], $offset, 0, $chunk);
370 0           return length($chunk);
371             }
372              
373             sub readline {
374 0     0 0   my $self = _obj(shift());
375 0   0       my $EOL = $/ || "\n";
376 0           my $buffer = "";
377 0           while (1) {
378             # Look for EOL
379 0 0         if ($buffer =~ s/^(.*\Q$EOL\E)//) {
380             # Found EOL. Return everything up to it.
381 0           my $line = $1;
382             # If there is anything left,
383             # stuff it onto the beginning for the next read.
384 0           $self->{Buffer_R} = "$buffer$self->{Buffer_R}";
385 0           return $line;
386             }
387 0 0         if (!$self->READ($buffer, 8192, length $buffer)) {
388             # Reached EOF or error
389 0 0 0       if (defined $buffer and length $buffer) {
390             # Just return the whole buffer even though there is no newline
391 0           return $buffer;
392             }
393             else {
394             # Probably EOF
395 0           return undef;
396             }
397             }
398             }
399             # Impossible to get here
400 0           return undef;
401             }
402              
403             sub syswrite {
404 0     0 0   my $self = _obj(shift());
405 0           my $buffer = shift;
406 0           my $bytes = shift;
407 0 0         $bytes = length($buffer) if !defined $bytes;
408 0           $self->{Buffer_W} .= substr($buffer, 0, $bytes);
409 0 0         $self->{seqid} or return undef;
410 0           my $Temp_Buffer_R = "";
411 0   0       while ($self->{seqid} && length $self->{Buffer_W}) {
412 0 0         if ($self->pending) {
413             # Try to clear out Buffer_R so the new Buffer_W bytes can be processed
414 0 0         $self->READ($Temp_Buffer_R, 8192, length $Temp_Buffer_R) or last;
415             }
416             }
417             # Stuff read bytes back into the buffer
418 0           $self->{Buffer_R} = "$Temp_Buffer_R$self->{Buffer_R}";
419 0           return length($buffer);
420             }
421              
422             sub close {
423 0     0 0   my $self = _obj(shift);
424 0           my $suffix = $self->suffix;
425 0 0         if (my $seqid = delete $self->{seqid}) {
426 0           my $name = "$seqid.x.$suffix";
427             eval {
428 0           require Net::DNS::Resolver;
429 0           $self->resolver->bgsend($name, "TXT");
430 0           1;
431 0 0         } or eval {
432 0           $self->TXT_resolver->($name);
433             };
434 0           return 1;
435             }
436 0           return 0;
437             }
438              
439 0     0     sub READ { shift()->sysread(@_) }
440 0     0     sub READLINE { shift()->readline(@_) }
441 0     0     sub WRITE { shift()->syswrite(@_) }
442 0     0     sub CLOSE { shift()->close(@_) }
443              
444             sub UNTIE {
445 0     0     my $self = shift;
446 0           $self->CLOSE;
447             }
448              
449             sub DESTROY {
450 0     0     my $self = shift;
451 0           $self->CLOSE;
452             }
453              
454             1;
455             __END__