@@ -3,15 +3,15 @@ package Zonemaster::Engine::Zone;
33use v5.16.0;
44use warnings;
55
6- use version; our $VERSION = version-> declare(" v1.1.9 " );
6+ use version; our $VERSION = version-> declare(" v1.2.0 " );
77
88use Carp qw( confess croak ) ;
99use List::MoreUtils qw[ uniq] ;
1010
1111use Zonemaster::Engine::DNSName;
1212use Zonemaster::Engine::Recursor;
1313use Zonemaster::Engine::NSArray;
14- use Zonemaster::Engine::Constants qw[ :ip] ;
14+ use Zonemaster::Engine::Constants qw[ :ip :dname ] ;
1515
1616sub new {
1717 my ( $class , $attrs ) = @_ ;
@@ -46,6 +46,16 @@ sub parent {
4646 return $self -> {_parent };
4747}
4848
49+ sub dname {
50+ my ( $self ) = @_ ;
51+
52+ if ( !exists $self -> {_dname } ) {
53+ $self -> {_dname } = $self -> _build_dname;
54+ }
55+
56+ return $self -> {_dname };
57+ }
58+
4959sub glue_names {
5060 my ( $self ) = @_ ;
5161
@@ -96,6 +106,7 @@ sub glue_addresses {
96106 return $self -> {_glue_addresses };
97107}
98108
109+
99110# ##
100111# ## Builders
101112# ##
@@ -113,25 +124,108 @@ sub _build_parent {
113124 return __PACKAGE__ -> new( { name => $pname } );
114125}
115126
127+ sub _build_dname {
128+ my ( $self ) = @_ ;
129+
130+ if ( $self -> name eq ' .' or not $self -> parent ) {
131+ return undef ;
132+ }
133+
134+ my $p = $self -> parent-> query_persistent( $self -> name, ' DNAME' );
135+
136+ return undef unless $p ;
137+
138+ Zonemaster::Engine-> logger-> add( DNAME_FOUND => { name => $self -> name } );
139+
140+ my @dname_rrs = $p -> get_records( ' DNAME' );
141+
142+ # Remove duplicate DNAME RRs
143+ my ( %duplicate_dname_rrs , @original_rrs );
144+ for my $rr ( @dname_rrs ) {
145+ my $rr_hash = $rr -> class . ' /DNAME/' . lc ($rr -> owner) . ' /' . lc ($rr -> dname);
146+
147+ if ( exists $duplicate_dname_rrs {$rr_hash } ) {
148+ $duplicate_dname_rrs {$rr_hash }++;
149+ }
150+ else {
151+ $duplicate_dname_rrs {$rr_hash } = 0;
152+ push @original_rrs , $rr ;
153+ }
154+ }
155+
156+ unless ( scalar @original_rrs == scalar @dname_rrs ) {
157+ @dname_rrs = @original_rrs ;
158+ }
159+
160+ # Break if there are too many records
161+ if ( scalar @dname_rrs > $DNAME_MAX_RECORDS ) {
162+ return undef ;
163+ }
164+
165+ my ( %dnames , %seen_targets , %forbidden_targets );
166+ for my $rr ( @dname_rrs ) {
167+ my $rr_owner = Zonemaster::Engine::DNSName-> new( lc ( $rr -> owner) );
168+ my $rr_target = Zonemaster::Engine::DNSName-> new( lc ( $rr -> dname ) );
169+
170+ # Multiple DNAME records with same owner name
171+ if ( exists $forbidden_targets {$rr_owner } ) {
172+ return undef ;
173+ }
174+
175+ # DNAME owner name is target, or target has already been seen in this response, or owner name cannot be a target
176+ if ( $rr_owner eq $rr_target or exists $seen_targets {$rr_target } or grep { $_ eq $rr_target } ( keys %forbidden_targets ) ) {
177+ return undef ;
178+ }
179+
180+ $seen_targets {$rr_target } = 1;
181+ $forbidden_targets {$rr_owner } = 1;
182+ $dnames {$rr_owner } = $rr_target ;
183+ }
184+
185+ # Get final DNAME target
186+ my $target = $self -> name;
187+ my $dname_counter = 0;
188+ while ( $dnames {$target } ) {
189+ return undef if $dname_counter > $DNAME_MAX_RECORDS ; # Loop protection (for good measure only - data in %dnames is sanitized already)
190+ $target = $dnames {$target };
191+ $dname_counter ++;
192+ }
193+
194+ # Make sure that the DNAME chain from the RRs is not broken
195+ if ( $dname_counter != scalar @dname_rrs ) {
196+ return undef ;
197+ }
198+
199+ return __PACKAGE__ -> new( { name => Zonemaster::Engine::DNSName-> new( $target ) } );
200+ }
201+
116202sub _build_glue_names {
117203 my ( $self ) = @_ ;
204+ my $zname = $self -> name;
205+ my $p ;
118206
119207 if ( not $self -> parent ) {
120208 return [];
121209 }
122210
123- my $p = $self -> parent-> query_persistent( $self -> name, ' NS' );
211+ if ( $self -> dname ) {
212+ $zname = $self -> dname-> name;
213+ $p = $self -> dname-> parent-> query_persistent( $zname , ' NS' );
214+ }
215+ else {
216+ $p = $self -> parent-> query_persistent( $zname , ' NS' );
217+ }
124218
125219 return [] if not defined $p ;
126220
127221 return [ uniq sort map { Zonemaster::Engine::DNSName-> new( lc ( $_ -> nsdname ) ) }
128- $p -> get_records_for_name( ' ns' , $self -> name -> string ) ];
222+ $p -> get_records_for_name( ' ns' , $zname -> string ) ];
129223}
130224
131225sub _build_glue {
132226 my ( $self ) = @_ ;
133- my @glue_names = @{ $self -> glue_names };
134227 my $zname = $self -> name-> string;
228+ my @glue_names = @{$self -> glue_names};
135229
136230 if ( Zonemaster::Engine::Recursor-> has_fake_addresses( $zname ) ) {
137231 my @ns_list ;
@@ -153,24 +247,34 @@ sub _build_glue {
153247
154248sub _build_ns_names {
155249 my ( $self ) = @_ ;
250+ my $zname = $self -> name;
251+ my $servers ;
252+ my $p ;
253+ my $i = 0;
156254
157255 if ( $self -> name eq ' .' ) {
158256 my %u ;
159257 $u {$_ } = $_ for map { $_ -> name } @{ $self -> ns };
160258 return [ sort values %u ];
161259 }
162260
163- my $p ;
164- my $i = 0;
165- while ( my $s = $self -> glue-> [$i ] ) {
166- $p = $s -> query( $self -> name, ' NS' );
261+ if ( $self -> dname ) {
262+ $zname = $self -> dname-> name;
263+ $servers = $self -> dname-> glue;
264+ }
265+ else {
266+ $servers = $self -> glue;
267+ }
268+
269+ while ( my $s = $servers -> [$i ] ) {
270+ $p = $s -> query( $zname , ' NS' );
167271 last if ( defined ( $p ) and ( $p -> type eq ' answer' ) and ( $p -> rcode eq ' NOERROR' ) );
168272 $i += 1;
169273 }
170274 return [] if not defined $p ;
171275
172276 return [ uniq sort map { Zonemaster::Engine::DNSName-> new( lc ( $_ -> nsdname ) ) }
173- $p -> get_records_for_name( ' ns' , $self -> name -> string ) ];
277+ $p -> get_records_for_name( ' ns' , $zname ) ];
174278} # # end sub _build_ns_names
175279
176280sub _build_ns {
@@ -188,12 +292,21 @@ sub _build_ns {
188292
189293sub _build_glue_addresses {
190294 my ( $self ) = @_ ;
295+ my $zname = $self -> name;
296+ my $p ;
191297
192298 if ( not $self -> parent ) {
193299 return [];
194300 }
195301
196- my $p = $self -> parent-> query_one( $self -> name, ' NS' );
302+ if ( $self -> dname ) {
303+ $zname = $self -> dname-> name;
304+ $p = $self -> dname-> parent-> query_one( $zname , ' NS' );
305+ }
306+ else {
307+ $p = $self -> parent-> query_one( $zname , ' NS' );
308+ }
309+
197310 croak " Failed to get glue addresses" if not defined ( $p );
198311
199312 return [ $p -> get_records( ' a' ), $p -> get_records( ' aaaa' ) ];
@@ -406,6 +519,10 @@ A L<Zonemaster::Engine::Zone> object for this domain's parent domain. As a
406519special case, the root zone is considered to be its own parent (so
407520look for that if you recurse up the tree).
408521
522+ =item dname
523+
524+ A L<Zonemaster::Engine::Zone> object which is this zone's DNAME target, if any.
525+
409526=item ns_names
410527
411528A reference to an array of L<Zonemaster::Engine::DNSName> objects, holding the
0 commit comments