File Coverage

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


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