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   13867 use strict;
  8         30  
  8         259  
4 8     8   44 use warnings;
  8         15  
  8         456  
5             our $VERSION = (qw$Id: NSEC3.pm 1910 2023-03-30 19:16:30Z willem $)[2];
6              
7 8     8   54 use base qw(Net::DNS::RR::NSEC);
  8         23  
  8         4173  
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         16  
  8         63  
17              
18 8     8   231 use base qw(Exporter);
  8         20  
  8         746  
19             our @EXPORT_OK = qw(name2hash);
20              
21 8     8   56 use Carp;
  8         16  
  8         20608  
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   290 my ( $self, $data, $offset ) = @_;
30              
31 157         236 my $limit = $offset + $self->{rdlength};
32 157         359 my $ssize = unpack "\@$offset x4 C", $$data;
33 157         519 my ( $algorithm, $flags, $iterations, $saltbin ) = unpack "\@$offset CCnx a$ssize", $$data;
34 157         322 @{$self}{qw(algorithm flags iterations saltbin)} = ( $algorithm, $flags, $iterations, $saltbin );
  157         577  
35 157         247 $offset += 5 + $ssize;
36 157         335 my $hsize = unpack "\@$offset C", $$data;
37 157         441 $self->{hnxtname} = unpack "\@$offset x a$hsize", $$data;
38 157         236 $offset += 1 + $hsize;
39 157         395 $self->{typebm} = substr $$data, $offset, ( $limit - $offset );
40 157         319 $self->{hashfn} = _hashfn( $algorithm, $iterations, $saltbin );
41 157         372 return;
42             }
43              
44              
45             sub _encode_rdata { ## encode rdata as wire-format octet string
46 20     20   31 my $self = shift;
47              
48 20         42 my $salt = $self->saltbin;
49 20         34 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         42 $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     11 my @rdata = (
61             $self->algorithm, $self->flags, $self->iterations,
62             $self->salt || '-', $self->hnxtname, $self->typelist
63             );
64 4         19 return @rdata;
65             }
66              
67              
68             sub _parse_rdata { ## populate RR from rdata in argument list
69 29     29   93 my ( $self, @argument ) = @_;
70              
71 29         69 my $alg = $self->algorithm( shift @argument );
72 29         88 $self->flags( shift @argument );
73 29         69 my $iter = $self->iterations( shift @argument );
74 29         56 my $salt = shift @argument;
75 29 100       101 $self->salt($salt) unless $salt eq '-';
76 29         83 $self->hnxtname( shift @argument );
77 29         151 $self->typelist(@argument);
78 29         75 $self->{hashfn} = _hashfn( $alg, $iter, $self->{saltbin} );
79 29         108 return;
80             }
81              
82              
83             sub _defaults { ## specify RR attribute default values
84 6     6   13 my $self = shift;
85              
86 6         23 $self->_parse_rdata( 1, 0, 0, '' );
87 6         20 return;
88             }
89              
90              
91             sub algorithm {
92 64     64 1 149 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       17 return $argn =~ /[^0-9]/ ? _digestbyname($argn) : _digestbyval($argn);
97             }
98              
99 61 100       175 return $self->{algorithm} unless defined $arg;
100 33 100       81 return _digestbyval( $self->{algorithm} ) if $arg =~ /MNEMONIC/i;
101 32         69 return $self->{algorithm} = _digestbyname($arg);
102             }
103              
104              
105             sub flags {
106 55     55 1 772 my ( $self, @value ) = @_;
107 55         99 for (@value) { $self->{flags} = 0 + $_ }
  30         76  
108 55   100     212 return $self->{flags} || 0;
109             }
110              
111              
112             sub optout {
113 6     6 1 1088 my ( $self, @value ) = @_;
114 6         14 for ( $self->{flags} |= 0 ) {
115 6 100       23 if ( scalar @value ) {
116 2         3 $_ |= 0x01;
117 2 100       7 $_ ^= 0x01 unless shift @value;
118             }
119             }
120 6         20 return $self->{flags} & 0x01;
121             }
122              
123              
124             sub iterations {
125 55     55 1 796 my ( $self, @value ) = @_;
126 55         96 for (@value) { $self->{iterations} = 0 + $_ }
  30         67  
127 55   100     280 return $self->{iterations} || 0;
128             }
129              
130              
131             sub salt {
132 36     36 1 794 my ( $self, @value ) = @_;
133 36 100       100 return unpack "H*", $self->saltbin() unless scalar @value;
134 30 100       60 my @hex = map { /^"*([\dA-Fa-f]*)"*$/ || croak("corrupt hex"); $1 } @value;
  30         458  
  29         119  
135 29         196 return $self->saltbin( pack "H*", join "", @hex );
136             }
137              
138              
139             sub saltbin {
140 55     55 1 108 my ( $self, @value ) = @_;
141 55         100 for (@value) { $self->{saltbin} = $_ }
  29         55  
142 55   100     217 return $self->{saltbin} || "";
143             }
144              
145              
146             sub hnxtname {
147 36     36 1 778 my ( $self, @name ) = @_;
148 36         54 for (@name) { $self->{hnxtname} = _decode_base32hex($_) }
  31         77  
149 36 100       107 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         6 my ($owner) = $self->{owner}->label;
157 2         5 my $ownerhash = _decode_base32hex($owner);
158              
159 2         7 my $hashfn = $self->{hashfn};
160 2         13 return $ownerhash eq &$hashfn($name);
161             }
162              
163             sub covers {
164 16     16 1 78 my ( $self, $name ) = @_;
165              
166 16         49 my ( $owner, @zone ) = $self->{owner}->label;
167 16         32 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         35 my @close = @label;
172 16         21 foreach (@zone) { pop(@close) } # strip zone labels
  16         29  
173 16 100       58 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         49 my $hash = &$hashfn( join '.', @label );
179 21         37 my $cmp1 = $hash cmp $ownerhash;
180 21 100       38 last unless $cmp1; # stop at provable encloser
181 16 100       54 return 1 if ( $cmp1 + ( $nexthash cmp $hash ) ) == 2;
182 11         19 shift @label;
183             }
184 9         70 return;
185             }
186              
187              
188             sub encloser {
189 4     4 1 1787 my ( $self, $qname ) = @_;
190              
191 4         16 my ( $owner, @zone ) = $self->{owner}->label;
192 4         9 my $ownerhash = _decode_base32hex($owner);
193 4         13 my $nexthash = $self->{hnxtname};
194              
195 4         13 my @label = Net::DNS::DomainName->new($qname)->label;
196 4         9 my @close = @label;
197 4         6 foreach (@zone) { pop(@close) } # strip zone labels
  4         6  
198 4 100       20 return if lc($qname) ne lc( join '.', @close, @zone ); # out of zone
199              
200 3         13 my $hashfn = $self->{hashfn};
201              
202 3         7 my $encloser = $qname;
203 3         4 foreach (@close) {
204 10         15 my $nextcloser = $encloser;
205 10         12 shift @label;
206 10         26 my $hash = &$hashfn( $encloser = join '.', @label );
207 10 100       27 next if $hash ne $ownerhash;
208 2         4 $self->{nextcloser} = $nextcloser; # next closer name
209 2         5 $self->{wildcard} = "*.$encloser"; # wildcard at provable encloser
210 2         8 return $encloser; # provable encloser
211             }
212 1         3 return;
213             }
214              
215              
216 2     2 1 15 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   53 my $arg = shift;
237 33         62 my $key = uc $arg; # synthetic key
238 33         82 $key =~ s/[\W_]//g; # strip non-alphanumerics
239 33         62 my $val = $digestbyname{$key};
240 33 100       180 croak qq[unknown algorithm $arg] unless defined $val;
241 32         126 return $val;
242             }
243              
244             sub _digestbyval {
245 3     3   8 my $value = shift;
246 3   100     18 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   216 local $_ = shift || '';
257 63         137 tr [0-9A-Va-v\060-\071\101-\126\141-\166] [\000-\037\012-\037\000-\037\012-\037];
258 63         119 my $l = ( 5 * length ) & ~7;
259 63         406 return pack "B$l", join '', map { unpack( 'x3a5', unpack 'B8', $_ ) } split //;
  1573         3459  
260             }
261              
262              
263             sub _encode_base32hex {
264 27     27   17584 my @split = grep {length} split /(\S{5})/, unpack 'B*', shift;
  1264         1676  
265 27         117 local $_ = join '', map { pack( 'B*', "000$_" ) } @split;
  636         1221  
266 27         86 tr [\000-\037] [0-9a-v];
267 27         228 return $_;
268             }
269              
270              
271             my ( $cache1, $cache2, $limit ) = ( {}, {}, 10 );
272              
273             sub _hashfn {
274 199     199   296 my $hashalg = shift;
275 199   100     388 my $iterations = shift || 0;
276 199   100     428 my $salt = shift || '';
277              
278 199         342 my $hash = $digest{$hashalg};
279 1     1   127 return sub { croak "algorithm $hashalg not supported" }
280 199 100       424 unless $hash;
281 198         1093 my $clone = $hash->clone;
282              
283 198         698 my $key_adjunct = pack 'Cna*', $hashalg, $iterations, $salt;
284              
285             return sub {
286 45     45   125 my $name = Net::DNS::DomainName->new(shift)->canonical;
287 45         106 my $key = join '', $name, $key_adjunct;
288 45   100     153 my $cache = $$cache1{$key} ||= $$cache2{$key}; # two layer cache
289 45 100       99 return $cache if defined $cache;
290 27 100       80 ( $cache1, $cache2, $limit ) = ( {}, $cache1, 50 ) unless $limit--; # recycle cache
291              
292 27         104 $clone->add($name);
293 27         67 $clone->add($salt);
294 27         133 my $digest = $clone->digest;
295 27         46 my $count = $iterations;
296 27         72 while ( $count-- ) {
297 312         696 $clone->add($digest);
298 312         584 $clone->add($salt);
299 312         1051 $digest = $clone->digest;
300             }
301 27         77 return $$cache1{$key} = $digest;
302 198         1350 };
303             }
304              
305              
306 2     2 0 674 sub hashalgo { return &algorithm; } # uncoverable pod
307              
308             sub name2hash {
309 13     13 0 5093 my $hashalg = shift; # uncoverable pod
310 13         23 my $name = shift;
311 13   100     42 my $iterations = shift || 0;
312 13   100     58 my $salt = pack 'H*', shift || '';
313 13         28 my $hash = _hashfn( $hashalg, $iterations, $salt );
314 13         40 return _encode_base32hex( &$hash($name) );
315             }
316              
317             ########################################
318              
319              
320             1;
321             __END__