File Coverage

blib/lib/Net/DNS/RR/NSEC3.pm
Criterion Covered Total %
statement 178 178 100.0
branch 38 38 100.0
condition 23 23 100.0
subroutine 32 32 100.0
pod 12 14 100.0
total 283 285 100.0


line stmt bran cond sub pod time code
1             package Net::DNS::RR::NSEC3;
2              
3 8     8   1241336 use strict;
  8         33  
  8         370  
4 8     8   47 use warnings;
  8         15  
  8         835  
5             our $VERSION = (qw$Id: NSEC3.pm 2003 2025-01-21 12:06:06Z willem $)[2];
6              
7 8     8   55 use base qw(Net::DNS::RR::NSEC);
  8         34  
  8         5322  
8              
9              
10             =head1 NAME
11              
12             Net::DNS::RR::NSEC3 - DNS NSEC3 resource record
13              
14             =cut
15              
16 8     8   59 use integer;
  8         16  
  8         41  
17              
18 8     8   300 use base qw(Exporter);
  8         17  
  8         1039  
19             our @EXPORT_OK = qw(name2hash);
20              
21 8     8   60 use Carp;
  8         21  
  8         25328  
22              
23             require Net::DNS::DomainName;
24              
25             eval { require Digest::SHA }; ## optional for simple Net::DNS RR
26              
27              
28             sub _decode_rdata { ## decode rdata from wire-format octet string
29 160     160   380 my ( $self, $data, $offset ) = @_;
30              
31 160         287 my $limit = $offset + $self->{rdlength};
32 160         399 my $ssize = unpack "\@$offset x4 C", $$data;
33 160         609 my ( $algorithm, $flags, $iterations, $saltbin ) = unpack "\@$offset CCnx a$ssize", $$data;
34 160         297 @{$self}{qw(algorithm flags iterations saltbin)} = ( $algorithm, $flags, $iterations, $saltbin );
  160         520  
35 160         262 $offset += 5 + $ssize;
36 160         351 my $hsize = unpack "\@$offset C", $$data;
37 160         801 $self->{hnxtname} = unpack "\@$offset x a$hsize", $$data;
38 160         280 $offset += 1 + $hsize;
39 160         450 $self->{typebm} = substr $$data, $offset, ( $limit - $offset );
40 160         347 $self->{hashfn} = _hashfn( $algorithm, $iterations, $saltbin );
41 160         520 return;
42             }
43              
44              
45             sub _encode_rdata { ## encode rdata as wire-format octet string
46 20     20   47 my $self = shift;
47              
48 20         100 my $salt = $self->saltbin;
49 20         48 my $hash = $self->{hnxtname};
50             return pack 'CCn C a* C a* a*', $self->algorithm, $self->flags, $self->iterations,
51             length($salt), $salt,
52             length($hash), $hash,
53 20         48 $self->{typebm};
54             }
55              
56              
57             sub _format_rdata { ## format rdata portion of RR string.
58 4     4   9 my $self = shift;
59              
60 4   100     12 my @rdata = (
61             $self->algorithm, $self->flags, $self->iterations,
62             $self->salt || '-', $self->hnxtname, $self->typelist
63             );
64 4         25 return @rdata;
65             }
66              
67              
68             sub _parse_rdata { ## populate RR from rdata in argument list
69 29     29   123 my ( $self, @argument ) = @_;
70              
71 29         82 my $alg = $self->algorithm( shift @argument );
72 29         121 $self->flags( shift @argument );
73 29         76 my $iter = $self->iterations( shift @argument );
74 29         69 my $salt = shift @argument;
75 29 100       121 $self->salt($salt) unless $salt eq '-';
76 29         87 $self->hnxtname( shift @argument );
77 29         156 $self->typelist(@argument);
78 29         88 $self->{hashfn} = _hashfn( $alg, $iter, $self->{saltbin} );
79 29         116 return;
80             }
81              
82              
83             sub _defaults { ## specify RR attribute default values
84 6     6   16 my $self = shift;
85              
86 6         46 $self->_parse_rdata( 1, 0, 0, '' );
87 6         19 return;
88             }
89              
90              
91             sub algorithm {
92 64     64 1 174 my ( $self, $arg ) = @_;
93              
94 64 100       186 unless ( ref($self) ) { ## class method or simple function
95 3         8 my $argn = pop;
96 3 100       19 return $argn =~ /[^0-9]/ ? _digestbyname($argn) : _digestbyval($argn);
97             }
98              
99 61 100       290 return $self->{algorithm} unless defined $arg;
100 33 100       128 return _digestbyval( $self->{algorithm} ) if $arg =~ /MNEMONIC/i;
101 32         78 return $self->{algorithm} = _digestbyname($arg);
102             }
103              
104              
105             sub flags {
106 55     55 1 1042 my ( $self, @value ) = @_;
107 55         116 for (@value) { $self->{flags} = 0 + $_ }
  30         91  
108 55   100     197 return $self->{flags} || 0;
109             }
110              
111              
112             sub optout {
113 6     6 1 1820 my ( $self, @value ) = @_;
114 6 100       22 if ( scalar @value ) {
115 3         10 for ( $self->{flags} |= 0x01 ) {
116 3 100       9 $_ ^= 0x01 unless shift @value;
117             }
118             }
119 6         24 return $self->{flags} & 0x01;
120             }
121              
122              
123             sub iterations {
124 55     55 1 1072 my ( $self, @value ) = @_;
125 55         103 for (@value) { $self->{iterations} = 0 + $_ }
  30         101  
126 55   100     332 return $self->{iterations} || 0;
127             }
128              
129              
130             sub salt {
131 36     36 1 1038 my ( $self, @value ) = @_;
132 36 100       89 return unpack "H*", $self->saltbin() unless scalar @value;
133 30 100       62 my @hex = map { /^"*([\dA-Fa-f]*)"*$/ || croak("corrupt hex"); $1 } @value;
  30         537  
  29         132  
134 29         195 return $self->saltbin( pack "H*", join "", @hex );
135             }
136              
137              
138             sub saltbin {
139 55     55 1 182 my ( $self, @value ) = @_;
140 55         111 for (@value) { $self->{saltbin} = $_ }
  29         63  
141 55   100     265 return $self->{saltbin} || "";
142             }
143              
144              
145             sub hnxtname {
146 36     36 1 2224 my ( $self, @name ) = @_;
147 36         62 for (@name) { $self->{hnxtname} = _decode_base32hex($_) }
  31         68  
148 36 100       136 return defined(wantarray) ? _encode_base32hex( $self->{hnxtname} ) : undef;
149             }
150              
151              
152             sub match {
153 2     2 1 14 my ( $self, $name ) = @_;
154              
155 2         7 my ($owner) = $self->{owner}->label;
156 2         8 my $ownerhash = _decode_base32hex($owner);
157              
158 2         10 my $hashfn = $self->{hashfn};
159 2         5 return $ownerhash eq &$hashfn($name);
160             }
161              
162             sub covers {
163 16     16 1 88 my ( $self, $name ) = @_;
164              
165 16         56 my ( $owner, @zone ) = $self->{owner}->label;
166 16         39 my $ownerhash = _decode_base32hex($owner);
167 16         71 my $nexthash = $self->{hnxtname};
168              
169 16         68 my @label = Net::DNS::DomainName->new($name)->label;
170 16         530 my @close = @label;
171 16         41 foreach (@zone) { pop(@close) } # strip zone labels
  16         30  
172 16 100       78 return if lc($name) ne lc( join '.', @close, @zone ); # out of zone
173              
174 14         28 my $hashfn = $self->{hashfn};
175              
176 14         26 foreach (@close) {
177 21         70 my $hash = &$hashfn( join '.', @label );
178 21         50 my $cmp1 = $hash cmp $ownerhash;
179 21 100       57 last unless $cmp1; # stop at provable encloser
180 16 100       77 return 1 if ( $cmp1 + ( $nexthash cmp $hash ) ) == 2;
181 11         23 shift @label;
182             }
183 9         117 return;
184             }
185              
186              
187             sub encloser {
188 4     4 1 3058 my ( $self, $qname ) = @_;
189              
190 4         24 my ( $owner, @zone ) = $self->{owner}->label;
191 4         40 my $ownerhash = _decode_base32hex($owner);
192 4         21 my $nexthash = $self->{hnxtname};
193              
194 4         35 my @label = Net::DNS::DomainName->new($qname)->label;
195 4         13 my @close = @label;
196 4         9 foreach (@zone) { pop(@close) } # strip zone labels
  4         9  
197 4 100       27 return if lc($qname) ne lc( join '.', @close, @zone ); # out of zone
198              
199 3         9 my $hashfn = $self->{hashfn};
200              
201 3         5 my $encloser = $qname;
202 3         6 foreach (@close) {
203 10         20 my $nextcloser = $encloser;
204 10         16 shift @label;
205 10         66 my $hash = &$hashfn( $encloser = join '.', @label );
206 10 100       57 next if $hash ne $ownerhash;
207 2         7 $self->{nextcloser} = $nextcloser; # next closer name
208 2         7 $self->{wildcard} = "*.$encloser"; # wildcard at provable encloser
209 2         13 return $encloser; # provable encloser
210             }
211 1         7 return;
212             }
213              
214              
215 2     2 1 23 sub nextcloser { return shift->{nextcloser}; }
216              
217 2     2 1 12 sub wildcard { return shift->{wildcard}; }
218              
219              
220             ########################################
221              
222             my @digestbyname = (
223             'SHA-1' => 1, # [RFC3658]
224             );
225              
226             my @digestalias = ( 'SHA' => 1 );
227              
228             my %digestbyval = reverse @digestbyname;
229              
230             foreach (@digestbyname) { s/[\W_]//g; } # strip non-alphanumerics
231             my @digestrehash = map { /^\d/ ? ($_) x 3 : uc($_) } @digestbyname;
232             my %digestbyname = ( @digestalias, @digestrehash ); # work around broken cperl
233              
234             sub _digestbyname {
235 33     33   61 my $arg = shift;
236 33         66 my $key = uc $arg; # synthetic key
237 33         100 $key =~ s/[\W_]//g; # strip non-alphanumerics
238 33         74 my $val = $digestbyname{$key};
239 33 100       334 croak qq[unknown algorithm $arg] unless defined $val;
240 32         184 return $val;
241             }
242              
243             sub _digestbyval {
244 3     3   7 my $value = shift;
245 3   100     23 return $digestbyval{$value} || return $value;
246             }
247              
248              
249             my %digest = (
250             '1' => scalar( eval { Digest::SHA->new(1) } ), # RFC3658
251             );
252              
253              
254             sub _decode_base32hex {
255 63   100 63   291 local $_ = shift || '';
256 63         146 tr [0-9A-Va-v\060-\071\101-\126\141-\166] [\000-\037\012-\037\000-\037\012-\037];
257 63         167 my $l = ( 5 * length ) & ~7;
258 63         535 return pack "B$l", join '', map { unpack( 'x3a5', unpack 'B8', $_ ) } split //;
  1573         7102  
259             }
260              
261              
262             sub _encode_base32hex {
263 27     27   23178 my @split = grep {length} split /(\S{5})/, unpack 'B*', shift;
  1264         1833  
264 27         156 local $_ = join '', map { pack( 'B*', "000$_" ) } @split;
  636         1066  
265 27         92 tr [\000-\037] [0-9a-v];
266 27         240 return $_;
267             }
268              
269              
270             my ( $cache1, $cache2, $limit ) = ( {}, {}, 10 );
271              
272             sub _hashfn {
273 202     202   345 my $hashalg = shift;
274 202   100     707 my $iterations = shift || 0;
275 202   100     643 my $salt = shift || '';
276              
277 202         415 my $hash = $digest{$hashalg};
278 1     1   191 return sub { croak "algorithm $hashalg not supported" }
279 202 100       507 unless $hash;
280 201         1302 my $clone = $hash->clone;
281              
282 201         775 my $key_adjunct = pack 'Cna*', $hashalg, $iterations, $salt;
283              
284             return sub {
285 45     45   155 my $name = Net::DNS::DomainName->new(shift)->canonical;
286 45         119 my $key = join '', $name, $key_adjunct;
287 45   100     218 my $cache = $$cache1{$key} ||= $$cache2{$key}; # two layer cache
288 45 100       112 return $cache if defined $cache;
289 27 100       59 ( $cache1, $cache2, $limit ) = ( {}, $cache1, 50 ) unless $limit--; # recycle cache
290              
291 27         117 $clone->add($name);
292 27         59 $clone->add($salt);
293 27         665 my $digest = $clone->digest;
294 27         45 my $count = $iterations;
295 27         63 while ( $count-- ) {
296 312         685 $clone->add($digest);
297 312         620 $clone->add($salt);
298 312         1189 $digest = $clone->digest;
299             }
300 27         100 return $$cache1{$key} = $digest;
301 201         1766 };
302             }
303              
304              
305 2     2 0 1350 sub hashalgo { return &algorithm; } # uncoverable pod
306              
307             sub name2hash {
308 13     13 0 5296 my $hashalg = shift; # uncoverable pod
309 13         19 my $name = shift;
310 13   100     30 my $iterations = shift || 0;
311 13   100     45 my $salt = pack 'H*', shift || '';
312 13         26 my $hash = _hashfn( $hashalg, $iterations, $salt );
313 13         29 return _encode_base32hex( &$hash($name) );
314             }
315              
316             ########################################
317              
318              
319             1;
320             __END__