File Coverage

blib/lib/Tie/DNS.pm
Criterion Covered Total %
statement 15 160 9.3
branch 0 66 0.0
condition 0 12 0.0
subroutine 5 21 23.8
pod 5 5 100.0
total 25 264 9.4


line stmt bran cond sub pod time code
1             package Tie::DNS;
2             {
3             $Tie::DNS::VERSION = '1.133260';
4             }
5 1     1   669 use Carp;
  1         2  
  1         88  
6 1     1   6 use strict;
  1         3  
  1         36  
7 1     1   6 use warnings;
  1         2  
  1         38  
8 1     1   1086 use Socket;
  1         4588  
  1         664  
9 1     1   82305 use Net::DNS;
  1         405638  
  1         1991  
10              
11             my $NEW_NETDNS = 0;
12             if (Net::DNS->version >= 0.69) {
13             $NEW_NETDNS = 1;
14             }
15              
16             my %config_rec_defaults = (
17             'AAAA' => 'address',
18             'AFSDB' => 'subtype',
19             'A' => 'address',
20             'CNAME' => 'cname',
21             'EID' => 'rdlength',
22             'HINFO' => 'cpu',
23             'ISDN' => 'address',
24             'LOC' => 'version',
25             'MB' => 'madname',
26             'MG' => 'mgmname',
27             'MINFO' => 'rmailbx',
28             'MR' => 'newname',
29             'MX' => 'exchange',
30             'NAPTR' => 'order',
31             'NIMLOC' => 'rdlength',
32             'NSAP' => 'idp',
33             'NS' => 'nsdname',
34             'NULL' => 'rdlength',
35             'PTR' => 'ptrdname',
36             'PX' => 'preference',
37             'RP' => 'mbox',
38             'RT' => 'intermediate',
39             'SOA' => 'mname',
40             'SRV' => 'target',
41             'TXT' => 'txtdata'
42             );
43              
44             my %config_type = (
45             'AAAA' => ['address','ttl'],
46             'AFSDB' => ['subtype','ttl'],
47             'A' => ['address','ttl'],
48             'CNAME' => ['cname','ttl'],
49             'EID' => ['rdlength','rdata','ttl'],
50             'HINFO' => ['cpu','os','ttl'],
51             'ISDN' => ['address','subaddress','ttl'],
52             'LOC' => [
53             'version','size','horiz_pre','vert_pre',
54             'latitude','longitude','latlon','altitude', 'ttl'
55             ],
56             'MB' => ['madname','ttl'],
57             'MG' => ['mgmname','ttl'],
58             'MINFO' => ['rmailbx','emailbx','ttl'],
59             'MR' => ['newname','ttl'],
60             'MX' => ['exchange','preference'],
61             'NAPTR' => [
62             'order','preference','flags','service',
63             'regexp','replacement','ttl'
64             ],
65             'NIMLOC' => ['rdlength','rdata','ttl'],
66             'NSAP' => [
67             'idp','dsp','afi','idi','dfi','aa',
68             'rsvd','rd','area','id','sel','ttl'
69             ],
70             'NS' => ['nsdname','ttl'],
71             'NULL' => ['rdlength','rdata','ttl'],
72             'PTR' => ['ptrdname','ttl'],
73             'PX' => ['preference','map822','mapx400','ttl'],
74             'RP' => ['mbox','txtdname','ttl'],
75             'RT' => ['intermediate','preference','ttl'],
76             'SOA' => [
77             'mname','rname','serial','refresh',
78             'retry','expire','minimum','ttl'
79             ],
80             'SRV' => ['target','port','weight','priority','ttl'],
81             'TXT' => ['txtdata','ttl']
82             );
83              
84             sub TIEHASH {
85 0     0     my $class = shift;
86 0           my $args = shift;
87              
88 0 0         if (defined $args) {
89 0 0         die 'Bad argument format' unless ref $args eq 'HASH';
90             } else {
91 0           $args = {};
92             }
93              
94 0           my $self = {};
95 0           bless $self, $class;
96            
97 0 0         $self->{'dns'} = new Net::DNS::Resolver(%{($args->{resolver_args} || {})});
  0            
98              
99 0           $self->args($args);
100              
101 0           return $self;
102             }
103              
104             sub STORE {
105 0     0     my $self = shift;
106 0           my $key = shift;
107 0           my $value = shift;
108              
109 0 0         my $root_server = $self->get_root_server
110             or die 'Dynamic update attempted but no (or bad) domain specified.';
111              
112 0           my $update = new Net::DNS::Update($self->_get_arg('domain'));
113 0           my $update_string = sprintf('%s. %s %s %s',
114             $key, $self->{'ttl'}, $self->{'lookup_type'}, $value);
115 0           $update->push('update', rr_add($update_string));
116              
117 0 0         my $res = new Net::DNS::Resolver(%{($self->args->{resolver_args} || {})});
  0            
118 0           $res->nameservers($root_server);
119 0           my $reply = $res->send($update);
120 0 0         if (defined $reply) {
121 0 0         if ($reply->header->rcode eq 'NOERROR') {
122 0           return $value;
123             } else {
124 0           $self->{'errstring'} = $self->{'dns'}->errorstring;
125 0           return undef;
126             }
127             } else {
128 0           $self->{'errstring'} = $self->{'dns'}->errorstring;
129 0           return undef;
130             }
131             }
132              
133             sub args {
134 0     0 1   my $self = shift;
135 0           my $args = shift;
136 0           $self->{'args'} = $args;
137 0           $self->_process_args;
138             }
139              
140             sub FETCH {
141 0     0     my $self = shift;
142 0           my $lookup = shift;
143              
144 0 0         if ( $lookup =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
145 0           return $self->do_reverse_lookup($lookup);
146             } else {
147 0           return $self->do_forward_lookup($lookup);
148             }
149             }
150              
151             sub FIRSTKEY {
152 0     0     my $self = shift;
153 0           my @full_zone = $self->{'dns'}->axfr($self->{'root_name_server'});
154 0 0         if (scalar(@full_zone) == 0) {
155 0           $self->{'errstring'} = $self->{'dns'}->errorstring;
156 0           return undef;
157             }
158              
159 0           my @zone;
160 0           foreach my $rr (@full_zone) {
161 0 0         push @zone, $rr if $rr->type eq 'A';
162             }
163 0           my $rr = shift @zone;
164 0           $self->{'zone'} = \@zone;
165 0           return $rr->name;
166             }
167              
168             sub NEXTKEY {
169 0     0     my $self = shift;
170 0           my @zone = @{$self->{'zone'}};
  0            
171 0 0         if (scalar(@zone) == 0) {
172 0           return undef;
173             }
174 0           my $rr = shift(@zone);
175 0           $self->{'zone'} = \@zone;
176 0           return $rr->name;
177             }
178              
179             sub CLEAR {
180 0     0     my $self = shift;
181              
182             # die ('dynamic DNS updates are not yet available.');
183             }
184              
185             sub DELETE {
186 0     0     my $self = shift;
187 0           die 'Tie::DNS: DELETE function not implemented';
188             }
189              
190             sub DESTROY {
191 0     0     my $self = shift;
192              
193             #There isn't any real Net::DNS requirement to call anything when
194             #we go bye-bye, so we'll just go bye-bye quietly.
195             }
196              
197             sub _process_args {
198 0     0     my $self = shift;
199              
200 0 0         if (defined $self->_get_arg('domain')) { #find the root name
201             #server for this domain
202 0           $self->{'root_name_server'} = $self->get_root_server;
203 0           $self->{'dns'}->nameservers($self->{'root_name_server'});
204             }
205              
206 0 0         if (defined $self->_get_arg('multiple')) { #multiple return
207             #objects
208             #I don't think there's any setup required for this.
209             }
210              
211 0 0         if (defined $self->_get_arg('all_fields')) { #all fields
212             #I don't think there's any setup for this one either.
213             }
214              
215 0 0         if (defined $self->_get_arg('type')) {
216 0 0         if ( !defined($config_type{$self->_get_arg('type')})) {
217 0           die 'Bad record type: ' . $self->_get_arg('type');
218             }
219 0           $self->{'lookup_type'} = $self->_get_arg('type');
220             } else {
221 0           $self->{'lookup_type'} = 'A';
222             }
223              
224 0 0         if (defined $self->_get_arg('ttl')) {
225 0           $self->{'ttl'} = $self->_get_arg('ttl');
226             } else {
227 0           $self->{'ttl'} = 86400;
228             }
229              
230 0 0         if (my $cache_param = $self->_get_arg('cache')) {
231 0           eval { require Tie::Cache; };
  0            
232 0 0         unless ($@) {
233 0           tie my %cache, 'Tie::Cache', $cache_param;
234 0           $self->{cache} = \%cache;
235             }
236             } else {
237 0           delete $self->{'cache'};
238             }
239             }
240              
241             sub get_root_server {
242 0     0 1   my $self = shift;
243 0           my $query = $self->{'dns'}->query($self->_get_arg('domain'), 'SOA');
244 0 0         if ($query) {
245 0           foreach my $rr ($query->answer) {
246 0           print "Root: $rr->mname\n";
247 0           return $rr->mname;
248             }
249             } else {
250 0           die 'Domain specified, but unable to get SOA record: '
251             . $self->{'dns'}->errorstring;
252             }
253             }
254              
255             sub _get_arg {
256 0     0     my $self = shift;
257 0           my $arg_name = shift;
258 0 0         return undef unless defined $self->{'args'};
259              
260 0           return $self->{'args'}{$arg_name};
261             }
262              
263             sub do_reverse_lookup {
264 0     0 1   my $self = shift;
265 0           my $lookup = shift;
266              
267 0           my $query = $self->{'dns'}->search($lookup);
268 0           my @retvals;
269 0 0         if ($query) {
270 0           foreach my $rr ($query->answer) {
271 0 0         next unless $rr->type eq 'PTR';
272 0           push @retvals, $rr->ptrdname;
273             }
274             } else {
275 0           $self->{'errstring'} = $self->{'dns'}->errorstring;
276 0           return undef;
277             }
278 0 0         if (defined $self->_get_arg('multiple')) {
279 0           return \@retvals;
280             } else {
281 0           return shift @retvals;
282             }
283             }
284              
285             sub do_forward_lookup {
286 0     0 1   my $self = shift;
287 0           my $lookup = shift;
288 0           my @things = $self->_lookup_to_thing($lookup);
289 0 0         if (defined $self->_get_arg('multiple')) {
290 0           return \@things;
291             } else {
292 0           return shift @things;
293             }
294             }
295              
296             sub _lookup_to_thing {
297 0     0     my $self = shift;
298 0           my $lookup = shift;
299              
300 0           my $ttl = 0;
301 0           my $now = time();
302 0           my $cache = $self->{cache};
303              
304 0 0 0       if ($cache and my $old = $cache->{$lookup}) {
305 0           my ($expire, $ret) = @$old;
306 0 0         if ($now > $expire) {
307 0           delete $cache->{$lookup};
308             } else {
309 0           return @$ret;
310             }
311             }
312              
313 0           my $query = $self->{'dns'}->search($lookup, $self->{'lookup_type'});
314              
315 0           my @retvals;
316 0 0         if ($query) {
317 0           foreach my $rr ($query->answer) {
318 0   0       $ttl ||= $rr->{ttl};
319 0 0         next unless $rr->type eq $self->{'lookup_type'};
320 0 0         if (defined $self->_get_arg('all_fields')) {
321 0           my %fields;
322 0           foreach my $field (@{$config_type{$self->{'lookup_type'}}}) {
  0            
323 0 0 0       if ($NEW_NETDNS and $field eq 'address') {
324 0           $fields{$field} = inet_ntoa($rr->{$field});
325             } else {
326 0           $fields{$field} = $rr->{$field};
327             }
328             }
329 0           push @retvals,\%fields;
330             } else {
331 0 0 0       if ( $NEW_NETDNS and
332             $config_rec_defaults{$self->{'lookup_type'}}
333             eq 'address') {
334             push @retvals,
335             inet_ntoa(
336             $rr->{
337             $config_rec_defaults{
338 0           $self->{'lookup_type'}
339             }
340             }
341             );
342             } else {
343 0           push
344             @retvals,
345             $rr->{$config_rec_defaults{$self->{'lookup_type'}}};
346             }
347             }
348             }
349             } else {
350 0           $self->{'errstring'} = $self->{'dns'}->errorstring;
351             }
352              
353 0 0         if ($cache) {
354 0           $cache->{$lookup} = [$now + $ttl, \@retvals];
355             }
356 0           @retvals;
357             }
358              
359             sub error {
360 0     0 1   my $self = shift;
361 0           return $self->{'errstring'};
362             }
363              
364             1;
365             __END__
366              
367             =head1 NAME
368              
369             Tie::DNS - Tie interface to Net::DNS
370              
371             =head1 SYNOPSIS
372              
373             use Tie::DNS;
374              
375             tie my %dns, 'Tie::DNS';
376              
377             print "$dns{'foo.bar.com'}\n";
378              
379             print "$dns{'208.180.41.1'}\n";
380              
381             =head1 DESCRIPTION
382              
383             Net::DNS is a very complete, extensive and well-written module.
384             It's completeness, however, makes many comman cases uses a bit
385             wordy, code-wise. Tie::DNS is meant to make common DNS operations
386             trivial, and more complex DNS operations easier.
387              
388             =head1 EXAMPLES
389              
390             =head2 Forward lookup
391              
392             See Above.
393              
394             =head2 Zone transfer
395              
396             Get all of the A records from 'foo.com'. (Sorry foo.com if
397             everyone hits your name server testing this module. :-)
398              
399             tie %dns, 'Tie::DNS', {Domain => 'foo.com'};
400              
401             while (my ($name, $ip) = each %dns) {
402             print "$name = $ip\n";
403             }
404              
405             This obviously requires that your host has zone transfer
406             privileges with a name server hosting that zone. The
407             zone transfer is initiated with the first each, keys or
408             values operation. The tie operation does a SOA query
409             to find the name server for the cited zone.
410              
411             =head2 Fetching multiple records
412              
413             Pass the configuration parameter of 'multiple' to any Perl true
414             value, and all FETCH values from Tie::DNS will be an array
415             reference of records.
416              
417             tie my %dns, 'Tie::DNS', {multiple => 'true'};
418              
419             my $ip_ref = $dns{'cnn.com'};
420             foreach (@{$ip_ref}) {
421             print "Address: $_\n";
422             }
423              
424             =head2 Fetching records of type besides 'A'
425              
426             Pass the configuration parameter of 'type' to one of the
427             Net::DNS supported record types causes all FETCHes to
428             get records of that type.
429              
430             tie %dns, 'Tie::DNS', {
431             multiple' => 'true',
432             type => 'SOA'
433             };
434              
435             my $ip_ref = $dns{'cnn.com'};
436             foreach (@{$ip_ref}) {
437             print "primary nameserver: $_\n";
438             }
439              
440             Here are the most popular types supported:
441              
442             CNAME - Returns the records canonical name.
443             A - Returns the records address field.
444             TXT - Returns the descriptive text.
445             MX - Returns name of this mail exchange.
446             NS - Returns the domain name of the nameserver.
447             PTR - Returns the domain name associated with this record.
448             SOA - Returns the domain name of the original or
449             nameserver for this zone.
450              
451             (The descriptions are right out of the Net::DNS POD.)
452              
453             See Net::DNS documentation for further information about these
454             types and a comprehensive list of all available types.
455              
456             =head2 Fetching all of the fields associated with a given record type.
457              
458             tie %dns, 'Tie::DNS', {type => 'SOA'};
459              
460             my $dns_ref = $dns{'cnn.com'};
461             foreach my $field (keys %{$dns_ref}) {
462             print "$field = " . ${$dns_ref}{$field} . "\n";
463             }
464              
465             This code fragment will print all of the SOA fields associated
466             with cnn.com.
467              
468             =head2 Caching
469              
470             The argument 'cache' will cause the DNS results to be cached. The default
471             is no caching. The 'cache' argument is passed through to L<Tie::Cache>.
472             If L<Tie::Cache> cannot be loaded, caching will be disabled. Entries
473             whose DNS TTL has expired will be re-queried automatically.
474              
475             tie %dns, 'Tie::DNS', {cache => 100};
476             print "$dns{'cnn.com'}\n";
477             print "$dns{'cnn.com'}\n"; ## cached!
478              
479             =head2 Getting all/different fields associated with a record
480              
481             tie %dns, 'Tie::DNS', {all_fields => 'true'};
482             my $dns_ref = $dns{'cnn.com'};
483             print $dns_ref->{'ttl'}, "\n";
484              
485             =head2 Passing arguments to Net::DNS::Resolver->new()
486              
487             tie my %from_localhost, 'Tie::DNS', {
488             resolver_args => {
489             nameservers => ['127.0.0.1']
490             }
491             };
492             print "$from_localhost{'test.local'}\n";
493              
494             You can pass arbitrary arguments to the Net::DNS::Resolver constructor by
495             setting the C<resolver_args> argument. In the example above, an alternative
496             nameserver is used instead of the default one.
497              
498             =head2 Changing various arguments to the tie on the fly
499              
500             tie %dns, 'Tie::DNS', {type => 'SOA'};
501             print "$dns{'cnn.com'}\n";
502              
503             tied(%dns)->args({type => 'A'});
504             print "$dns{'cnn.com'}\n";
505              
506             This code fragment first does an SOA query for cnn.com, and then
507             changes the default mode to A queries, and displays that.
508              
509             =head2 Simple Dynamic Updates
510              
511             Assign into the hash, key DNS name, value IP address, to add a record
512             to the zone in the domain argument. For instance:
513              
514             tie %dns, 'Tie::DNS', {
515             domain => 'realms.lan',
516             multiple => 'true'
517             };
518              
519             $dns{'food.realms.lan.'} = '131.22.40.1';
520              
521             foreach (@{$dns{'food'}}) {
522             print " $_\n";
523             }
524              
525             =head2 Methods
526              
527             =head3 error
528              
529             Returns the last error, either from Tie::DNS or Net::DNS
530              
531             =head3 get_root_server
532              
533             Returns the root name server.
534              
535             =head3 do_forward_lookup
536              
537             Returns the results of a forward lookup.
538              
539             =head3 do_reverse_lookup
540              
541             Returns the results of a reverse lookup.
542              
543             =head3 args
544              
545             Change various arguments to the tie on the fly.
546              
547             =head1 TODO
548              
549             This release supports the basic functionality of
550             Net::DNS. The 1.0 release will support the following:
551              
552             Different access methods for forward and reverse lookups.
553              
554             The 2.0 release will strive to support DNS security options.
555              
556             =head1 AUTHOR
557              
558             Dana M. Diederich <dana@realms.org>
559              
560             =head1 ACKNOWLEDGMENTS
561              
562             kevin Brintnall <kbrint@rufus.net> for Caching patch
563             Alvar Freude <alvar@a-blast.org> for arguments to resolver patch
564             Greg Myran <gmyran@drchico.net> for fixes for Net::DNS >= 0.69
565              
566             =head1 BUGS
567              
568             in-addr.arpa zone transfers aren't yet supported.
569              
570             Patches, flames, opinions, enhancement ideas are all welcome.
571              
572             =head1 COPYRIGHT
573             Copyright (c) 2009,2013 Dana M. Diederich. All Rights Reserved.
574             This module is free software. It may be used, redistributed
575             and/or modified under the terms of the Perl Artistic License
576             (see http://www.perl.com/perl/misc/Artistic.html)
577              
578             =cut