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