File Coverage

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


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