diff --git a/MANIFEST b/MANIFEST index e36607c66..c44332561 100644 --- a/MANIFEST +++ b/MANIFEST @@ -239,8 +239,6 @@ t/Test-zone01-A.data t/Test-zone01-A.t t/Test-zone01-B.data t/Test-zone01-B.t -t/Test-zone09-1.data -t/Test-zone09-1.t t/Test-zone09.data t/Test-zone09.t t/Test-zone11-1.data diff --git a/lib/Zonemaster/Engine/Test/Zone.pm b/lib/Zonemaster/Engine/Test/Zone.pm index db583fed0..2083c027e 100644 --- a/lib/Zonemaster/Engine/Test/Zone.pm +++ b/lib/Zonemaster/Engine/Test/Zone.pm @@ -1,6 +1,6 @@ package Zonemaster::Engine::Test::Zone; -use v5.16.0; +use v5.26.0; use warnings; use version; our $VERSION = version->declare( "v1.0.14" ); @@ -175,19 +175,23 @@ sub metadata { ], zone09 => [ qw( + Z09_ARPA_EMAIL_DOMAIN Z09_INCONSISTENT_MX Z09_INCONSISTENT_MX_DATA - Z09_MISSING_MAIL_TARGET + Z09_MISSING_MAIL_EXCHANGE Z09_MX_DATA Z09_MX_FOUND Z09_NON_AUTH_MX_RESPONSE Z09_NO_MX_FOUND + Z09_NO_MX_FOUND_OR_EXPECTED + Z09_NO_SERVERS_MX_RESPONSE Z09_NO_RESPONSE_MX_QUERY Z09_NULL_MX_NON_ZERO_PREF Z09_NULL_MX_WITH_OTHER_MX Z09_ROOT_EMAIL_DOMAIN Z09_TLD_EMAIL_DOMAIN Z09_UNEXPECTED_RCODE_MX + Z09_VALID_NULL_MX TEST_CASE_END TEST_CASE_START ) @@ -423,57 +427,73 @@ Readonly my %TAG_DESCRIPTIONS => ( __x # ZONE:Z01_MNAME_UNEXPECTED_RCODE 'SOA MNAME name server "{ns}" gives unexpected RCODE name ("{rcode}") in response to an SOA query.', @_; }, + Z09_ARPA_EMAIL_DOMAIN => sub { + __ # ZONE:Z09_ARPA_EMAIL_DOMAIN + 'The zone is in the ARPA tree and has an unexpected MX RRset (non-Null MX).'; + }, Z09_INCONSISTENT_MX => sub { - __x # ZONE:Z09_INCONSISTENT_MX - 'Some name servers return an MX RRset while others return none.', @_; + __ # ZONE:Z09_INCONSISTENT_MX + 'Some name servers return an MX RRset while others return none.'; }, Z09_INCONSISTENT_MX_DATA => sub { - __x # ZONE:Z09_INCONSISTENT_MX_DATA - 'The MX RRset data is inconsistent between the name servers.', @_; + __ # ZONE:Z09_INCONSISTENT_MX_DATA + 'The MX RRset data is inconsistent between the name servers.'; }, - Z09_MISSING_MAIL_TARGET => sub { - __x # ZONE:Z09_MISSING_MAIL_TARGET - 'The child zone has no mail target (no MX).', @_; + Z09_MISSING_MAIL_EXCHANGE => sub { + __x # ZONE:Z09_MISSING_MAIL_EXCHANGE + 'The child zone has no mail exchange (no MX), as returned by name servers "{ns_list}".', @_; }, Z09_MX_DATA => sub { __x # ZONE:Z09_MX_DATA - 'Mail targets in the MX RRset "{mailtarget_list}" returned from name servers "{ns_ip_list}".', @_; + 'The MX RDATA in the MX RRset, "{mxrdata_list}", as returned by name servers "{ns_list}".', @_; }, Z09_MX_FOUND => sub { __x # ZONE:Z09_MX_FOUND - 'MX RRset was returned by name servers "{ns_ip_list}".', @_; + 'MX RRset was returned by name servers "{ns_list}".', @_; }, Z09_NON_AUTH_MX_RESPONSE => sub { __x # ZONE:Z09_NON_AUTH_MX_RESPONSE - 'Non-authoritative response on MX query from name servers "{ns_ip_list}".', @_; + 'Non-authoritative response on MX query from name servers "{ns_list}".', @_; }, Z09_NO_MX_FOUND => sub { __x # ZONE:Z09_NO_MX_FOUND - 'No MX RRset was returned by name servers "{ns_ip_list}".', @_; + 'No MX RRset was returned by name servers "{ns_list}".', @_; + }, + Z09_NO_MX_FOUND_OR_EXPECTED => sub { + __ # ZONE:Z09_NO_MX_FOUND_OR_EXPECTED + 'MX RRset was neither found nor expected for the zone.'; + }, + Z09_NO_SERVERS_MX_RESPONSE => sub { + __ # ZONE:Z09_NO_SERVERS_MX_RESPONSE + 'No server responds to MX query.'; }, Z09_NO_RESPONSE_MX_QUERY => sub { __x # ZONE:Z09_NO_RESPONSE_MX_QUERY - 'No response on MX query from name servers "{ns_ip_list}".', @_; + 'No response on MX query from name servers "{ns_list}".', @_; }, Z09_NULL_MX_NON_ZERO_PREF => sub { - __x # ZONE:Z09_NULL_MX_NON_ZERO_PREF - 'The zone has a Null MX with non-zero preference.', @_; + __ # ZONE:Z09_NULL_MX_NON_ZERO_PREF + 'The zone has a Null MX with non-zero preference.'; }, Z09_NULL_MX_WITH_OTHER_MX => sub { - __x # ZONE:Z09_NULL_MX_WITH_OTHER_MX - 'The zone has a Null MX mixed with other MX records.', @_; + __ # ZONE:Z09_NULL_MX_WITH_OTHER_MX + 'The zone has a Null MX mixed with other MX records.'; }, Z09_ROOT_EMAIL_DOMAIN => sub { - __x # ZONE:Z09_ROOT_EMAIL_DOMAIN - 'Root zone with an unexpected MX RRset (non-Null MX).', @_; + __ # ZONE:Z09_ROOT_EMAIL_DOMAIN + 'Root zone with an unexpected MX RRset (non-Null MX).'; }, Z09_TLD_EMAIL_DOMAIN => sub { - __x # ZONE:Z09_TLD_EMAIL_DOMAIN - 'The zone is a TLD and has an unexpected MX RRset (non-Null MX).', @_; + __ # ZONE:Z09_TLD_EMAIL_DOMAIN + 'The zone is a TLD and has an unexpected MX RRset (non-Null MX).'; }, Z09_UNEXPECTED_RCODE_MX => sub { __x # ZONE:Z09_UNEXPECTED_RCODE_MX - 'Unexpected RCODE value ({rcode}) on the MX query from name servers "{ns_ip_list}".', @_; + 'Unexpected RCODE name ({rcode}) in response to MX query. Responses from name servers "{ns_list}".', @_; + }, + Z09_VALID_NULL_MX => sub { + __ # ZONE:Z09_VALID_NULL_MX + 'The zone has a valid Null MX record as the only MX record.'; }, Z11_DIFFERENT_SPF_POLICIES_FOUND => sub { __x # ZONE:Z11_DIFFERENT_SPF_POLICIES_FOUND @@ -666,6 +686,28 @@ sub _retrieve_record_from_zone { =over +=item _is_non_mail_domain() + + _is_non_mail_domain( $domain ); + +Returns true if C<$domain> is a domain that should not be used for email: the +root domain, a TLD or a domain under the C TLD. + +The C<$domain> argument is assumed to be a L +object. + +=back + +=cut + +sub _is_non_mail_domain { + my $domain = shift; + + return ( $domain eq '.' or $domain->next_higher eq '.' or $domain =~ /\.arpa$/i ); +} + +=over + =item _spf_syntax_ok() _spf_syntax_ok( $spf_string ); @@ -1287,9 +1329,33 @@ Returns a list of L objects. sub zone09 { my ( $class, $zone ) = @_; + state sub sort_mx_records { + sort { + $a->preference() <=> $b->preference() or fc $a->exchange() cmp fc $b->exchange() + } @_; + } + + state sub mx_record_to_string { + $_[0]->preference() . " " . lc( name( $_[0]->exchange() ) ); + } + + state sub sort_unique_mx_records { + my %mx_hash = map { mx_record_to_string($_) => $_ } @_; + sort_mx_records( values %mx_hash ); + } + + state sub ns_list { + join( ';', sort @_ ); + } + local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Zone09'; push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ); + my @nss = uniq grep { $_->isa('Zonemaster::Engine::Nameserver') } ( + @{ Zonemaster::Engine::TestMethodsV2->get_del_ns_names_and_ips( $zone ) // [] }, + @{ Zonemaster::Engine::TestMethodsV2->get_zone_ns_names_and_ips( $zone ) // [] } + ); + my %ip_already_processed; my @no_response_mx; @@ -1298,11 +1364,12 @@ sub zone09 { my @no_mx_set; my %mx_set; - my %all_ns; - foreach my $ns ( @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) } ){ - next if exists $ip_already_processed{$ns->address->short}; - $ip_already_processed{$ns->address->short} = 1; + foreach my $ns ( @nss ) { + my $ns_ip = $ns->address->short; + + next if exists $ip_already_processed{$ns_ip}; + $ip_already_processed{$ns_ip} = 1; if ( _ip_disabled_message( \@results, $ns, qw{SOA MX} ) ) { next; @@ -1314,128 +1381,113 @@ sub zone09 { next; } - my $p2 = $ns->query( $zone->name, q{MX}, { fallback => 0, usevc => 0 } ); - - if ( $p2 and $p2->tc ){ - $p2 = $ns->query( $zone->name, q{MX}, { fallback => 0, usevc => 1 } ); - } + my $p2 = $ns->query( $zone->name, q{MX} ); if ( not $p2 ){ - push @no_response_mx, $ns->address->short; + push @no_response_mx, $ns; } - elsif ( $p2->rcode ne q{NOERROR} ){ - push @{ $unexpected_rcode_mx{$p2->rcode} }, $ns->address->short; + elsif ( $p2->rcode ne q{NOERROR} ) { + push @{ $unexpected_rcode_mx{$p2->rcode} }, $ns; } - elsif ( not $p2->aa ){ - push @non_authoritative_mx, $ns->address->short; + elsif ( not $p2->aa ) { + push @non_authoritative_mx, $ns; } - elsif ( not scalar grep { $_->owner eq $zone->name } $p2->get_records_for_name(q{MX}, $zone->name, q{answer}) ){ - push @no_mx_set, $ns->address->short; - } - else{ - push @{ $mx_set{$ns->address->short} }, $p2->get_records_for_name(q{MX}, $zone->name, q{answer}); + else { + my @sorted_mx_rrs = sort_unique_mx_records( $p2->get_records_for_name( q{MX}, $zone->name, q{answer} ) ); + my $mx_rrset_string = join ';', map mx_record_to_string($_), @sorted_mx_rrs; + + if ( !scalar @sorted_mx_rrs ) { + push @no_mx_set, $ns; + } + elsif ( not exists $mx_set{$mx_rrset_string} ) { + $mx_set{$mx_rrset_string} = { + ns => [ $ns->string ], + mx => \@sorted_mx_rrs + }; + } + else { + push @{ $mx_set{$mx_rrset_string}{ns} }, $ns->string; + } } + } - push @{ $all_ns{$ns->name->string} }, $ns->address->short; + if ( scalar @no_response_mx ) { + push @results, _emit_log( Z09_NO_RESPONSE_MX_QUERY => { + ns_list => ns_list( @no_response_mx ) + } ); } - if ( scalar @no_response_mx ){ - push @results, _emit_log( Z09_NO_RESPONSE_MX_QUERY => { ns_ip_list => join( q{;}, sort @no_response_mx ) } ); + foreach my $rcode ( keys %unexpected_rcode_mx ) { + push @results, _emit_log( Z09_UNEXPECTED_RCODE_MX => { + rcode => $rcode, + ns_list => ns_list( @{ $unexpected_rcode_mx{$rcode} } ) + } ); } - if ( scalar %unexpected_rcode_mx ){ - foreach my $rcode ( keys %unexpected_rcode_mx ){ - push @results, _emit_log( Z09_UNEXPECTED_RCODE_MX => { - rcode => $rcode, - ns_ip_list => join( q{;}, sort @{ $unexpected_rcode_mx{$rcode} } ) - } - ); - } + if ( scalar @non_authoritative_mx ) { + push @results, _emit_log( Z09_NON_AUTH_MX_RESPONSE => { + ns_list => ns_list( @non_authoritative_mx ) + } ); } - if ( scalar @non_authoritative_mx ){ - push @results, _emit_log( Z09_NON_AUTH_MX_RESPONSE => { ns_ip_list => join( q{;}, sort @no_response_mx ) } ); + foreach my $mx_string ( keys %mx_set ) { + push @results, _emit_log( Z09_MX_DATA => { + ns_list => ns_list( @{ $mx_set{$mx_string}{ns} } ), + mxrdata_list => $mx_string + } ); } - if ( scalar @no_mx_set and scalar %mx_set ){ - push @results, _emit_log( Z09_INCONSISTENT_MX => {} ); - push @results, _emit_log( Z09_NO_MX_FOUND => { ns_ip_list => join( q{;}, sort @no_mx_set ) } ); - push @results, _emit_log( Z09_MX_FOUND => { ns_ip_list => join( q{;}, sort keys %mx_set ) } ); + if ( scalar @no_mx_set and scalar %mx_set ) { + push @results, + _emit_log( Z09_INCONSISTENT_MX => {} ), + _emit_log( Z09_NO_MX_FOUND => { ns_list => ns_list( @no_mx_set ) } ), + _emit_log( Z09_MX_FOUND => { ns_list => ns_list( map @{$_->{ns}}, values %mx_set ) } ); } - if ( scalar %mx_set ){ - my $data_json; - my $json = JSON::PP->new->canonical->pretty; - my $first = 1; + if ( scalar keys %mx_set > 1 ) { + push @results, _emit_log( Z09_INCONSISTENT_MX_DATA => {} ); + } + elsif ( scalar keys %mx_set == 1 ) { + my @mx_records = @{ ( values %mx_set )[0]->{mx} }; - foreach my $ns ( keys %mx_set ){ - if ( $first ){ - my @data = map { lc $_->string } sort @{ $mx_set{$ns} }; - $data_json = $json->encode( \@data ); - $first = 0; + my ( $null_mx ) = grep { $_->exchange() eq '.' } @mx_records; + if ( defined $null_mx ) { + if ( scalar @mx_records > 1 ) { + push @results, _emit_log( Z09_NULL_MX_WITH_OTHER_MX => {} ); } - else{ - my @next_data = map { lc $_->string } sort @{ $mx_set{$ns} }; - if ( $json->encode( \@next_data ) ne $data_json ){ - push @results, _emit_log( Z09_INCONSISTENT_MX_DATA => {} ); - - foreach my $ns_name ( keys %all_ns ){ - push @results, _emit_log( Z09_MX_DATA => { - mailtarget_list => join( q{;}, map { $_->exchange } @{ $mx_set{@{$all_ns{$ns_name}}[0]} } ), - ns_ip_list => join( q{;}, @{ $all_ns{$ns_name} } ) - } - ) - } - - last; - } + elsif ( $null_mx->preference() != 0 ) { + push @results, _emit_log( Z09_NULL_MX_NON_ZERO_PREF => {} ); + } + else { + push @results, _emit_log( Z09_VALID_NULL_MX => {} ); } } - - unless ( grep{$_->tag eq 'Z09_INCONSISTENT_MX_DATA'} @results ){ - my $has_null_mx = 0; - my ( $ns ) = keys %mx_set; - - foreach my $rr ( @{$mx_set{$ns}} ){ - if ( $rr->exchange eq '.' ){ - if ( scalar @{$mx_set{$ns}} > 1 ){ - push @results, _emit_log( Z09_NULL_MX_WITH_OTHER_MX => {} ) unless grep{$_->tag eq 'Z09_NULL_MX_WITH_OTHER_MX'} @results; - } - - if ( $rr->preference > 0 ){ - push @results, _emit_log( Z09_NULL_MX_NON_ZERO_PREF => {} ) unless grep{$_->tag eq 'Z09_NULL_MX_NON_ZERO_PREF'} @results; - } - - $has_null_mx = 1; - } + else { + if ( $zone->name eq '.' ) { + push @results, _emit_log( Z09_ROOT_EMAIL_DOMAIN => {} ); } - - if ( not $has_null_mx ){ - if ( $zone->name->string eq '.' ){ - push @results, _emit_log( Z09_ROOT_EMAIL_DOMAIN => {} ); - } - - elsif ( $zone->name->next_higher eq '.' ){ - push @results, _emit_log( Z09_TLD_EMAIL_DOMAIN => {} ); - } - - else { - push @results, _emit_log( Z09_MX_DATA => { - ns_ip_list => join( q{;}, keys %mx_set ), - mailtarget_list => join( q{;}, map { map { $_->exchange } @$_ } $mx_set{ (keys %mx_set)[0] } ) - } - ); - } + elsif ( $zone->name->next_higher eq '.' ) { + push @results, _emit_log( Z09_TLD_EMAIL_DOMAIN => {} ); + } + elsif ( $zone->name =~ /\.arpa$/i ) { + push @results, _emit_log( Z09_ARPA_EMAIL_DOMAIN => {} ); } } } - elsif ( scalar @no_mx_set ){ - unless ( $zone->name eq '.' or $zone->name->next_higher eq '.' or $zone->name =~ /\.arpa$/ ){ - push @results, _emit_log( Z09_MISSING_MAIL_TARGET => {} ); + if ( scalar @no_mx_set and !scalar %mx_set ) { + if ( _is_non_mail_domain($zone->name) ) { + push @results, _emit_log( Z09_NO_MX_FOUND_OR_EXPECTED => {} ); + } + else { + push @results, _emit_log( Z09_MISSING_MAIL_EXCHANGE => { ns_list => ns_list( @no_mx_set ) } ); } } + if ( !scalar @no_mx_set and !scalar %mx_set ) { + push @results, _emit_log( Z09_NO_SERVERS_MX_RESPONSE => {} ); + } + return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) ) } ## end sub zone09 @@ -1585,7 +1637,7 @@ sub zone11 { push @results, _emit_log( Z11_UNABLE_TO_CHECK_FOR_SPF => {} ); } elsif ( List::MoreUtils::all { $_ eq '' } keys %spf_ns ) { - if ( $zone->name eq '.' or $zone->name->next_higher eq '.' or $zone->name =~ /\.arpa$/ ) { + if ( _is_non_mail_domain( $zone->name ) ) { push @results, _emit_log( Z11_NO_SPF_NON_MAIL_DOMAIN => { domain => $zone->name } ); } else { @@ -1605,25 +1657,21 @@ sub zone11 { else { my $spf_text = (values %ns_spf)[0][0]; - if ( _spf_syntax_ok($spf_text) ) { - if ( $zone->name eq '.' or $zone->name->next_higher eq '.' or $zone->name =~ /\.arpa$/ ) { - if ( $spf_text =~ /^v=spf1 [\ \t]+ -all [\ \t]* $/ix ) { - push @results, _emit_log( Z11_NULL_SPF_NON_MAIL_DOMAIN => { domain => $zone->name } ); - } - else { - push @results, _emit_log( Z11_NON_NULL_SPF_NON_MAIL_DOMAIN => { domain => $zone->name } ); - } - } - else { - push @results, _emit_log( Z11_SPF_SYNTAX_OK => { domain => $zone->name } ); - } - } - else { + if ( not _spf_syntax_ok( $spf_text ) ) { push @results, _emit_log( Z11_SPF_SYNTAX_ERROR => { ns_list => join( q{;}, sort map { @{ $ip_already_processed{$_} } } keys %ns_spf ), domain => $zone->name } ); } + elsif ( not _is_non_mail_domain( $zone->name ) ) { + push @results, _emit_log( Z11_SPF_SYNTAX_OK => { domain => $zone->name } ); + } + elsif ( $spf_text =~ /^v=spf1 [\ \t]+ -all [\ \t]* $/ix ) { + push @results, _emit_log( Z11_NULL_SPF_NON_MAIL_DOMAIN => { domain => $zone->name } ); + } + else { + push @results, _emit_log( Z11_NON_NULL_SPF_NON_MAIL_DOMAIN => { domain => $zone->name } ); + } } return ( @results, info( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) ) diff --git a/share/profile.json b/share/profile.json index ebf832271..80903ac41 100644 --- a/share/profile.json +++ b/share/profile.json @@ -556,19 +556,23 @@ "Z01_MNAME_NOT_MASTER": "NOTICE", "Z01_MNAME_NOT_RESOLVE": "NOTICE", "Z01_MNAME_UNEXPECTED_RCODE": "NOTICE", - "Z09_INCONSISTENT_MX" : "WARNING", - "Z09_INCONSISTENT_MX_DATA" : "WARNING", - "Z09_MISSING_MAIL_TARGET" : "NOTICE", - "Z09_MX_DATA" : "INFO", - "Z09_MX_FOUND" : "INFO", - "Z09_NON_AUTH_MX_RESPONSE" : "WARNING", - "Z09_NO_MX_FOUND" : "INFO", - "Z09_NO_RESPONSE_MX_QUERY" : "WARNING", - "Z09_NULL_MX_NON_ZERO_PREF" : "NOTICE", - "Z09_NULL_MX_WITH_OTHER_MX" : "WARNING", - "Z09_ROOT_EMAIL_DOMAIN" : "NOTICE", - "Z09_TLD_EMAIL_DOMAIN" : "WARNING", - "Z09_UNEXPECTED_RCODE_MX" : "WARNING", + "Z09_ARPA_EMAIL_DOMAIN": "NOTICE", + "Z09_INCONSISTENT_MX": "WARNING", + "Z09_INCONSISTENT_MX_DATA": "WARNING", + "Z09_MISSING_MAIL_EXCHANGE": "NOTICE", + "Z09_MX_DATA": "INFO", + "Z09_MX_FOUND": "INFO", + "Z09_NON_AUTH_MX_RESPONSE": "WARNING", + "Z09_NO_MX_FOUND": "INFO", + "Z09_NO_MX_FOUND_OR_EXPECTED": "INFO", + "Z09_NO_SERVERS_MX_RESPONSE": "WARNING", + "Z09_NO_RESPONSE_MX_QUERY": "WARNING", + "Z09_NULL_MX_NON_ZERO_PREF": "NOTICE", + "Z09_NULL_MX_WITH_OTHER_MX": "WARNING", + "Z09_ROOT_EMAIL_DOMAIN": "NOTICE", + "Z09_TLD_EMAIL_DOMAIN": "NOTICE", + "Z09_UNEXPECTED_RCODE_MX": "WARNING", + "Z09_VALID_NULL_MX": "INFO", "Z11_DIFFERENT_SPF_POLICIES_FOUND" : "NOTICE", "Z11_INCONSISTENT_SPF_POLICIES" : "WARNING", "Z11_NO_SPF_FOUND" : "NOTICE", diff --git a/t/Test-zone.t b/t/Test-zone.t index ccbc3caac..5d686a9fb 100644 --- a/t/Test-zone.t +++ b/t/Test-zone.t @@ -43,10 +43,6 @@ ok( $res{Z01_MNAME_IS_DOT}, q{SOA 'mname' is dot ('.')} ); %res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{Zone}, q{zone07.zut-root.rd.nic.fr} ); ok( $res{SOA_DEFAULT_TTL_MAXIMUM_VALUE_LOWER}, q{SOA 'minimum' value is too low} ); -$zone = Zonemaster::Engine->zone( q{zone05.zut-root.rd.nic.fr} ); -%res = map { $_->tag => 1 } Zonemaster::Engine->test_method( q{Zone}, q{zone09}, $zone ); -ok( $res{Z09_MISSING_MAIL_TARGET}, q{No MX records} ); - # # zone08 # diff --git a/t/Test-zone09-1.data b/t/Test-zone09-1.data deleted file mode 100644 index aa5d968ad..000000000 Binary files a/t/Test-zone09-1.data and /dev/null differ diff --git a/t/Test-zone09-1.t b/t/Test-zone09-1.t deleted file mode 100644 index d802cdf83..000000000 --- a/t/Test-zone09-1.t +++ /dev/null @@ -1,84 +0,0 @@ -use strict; -use warnings; - -use Test::More; -use File::Basename; -use File::Spec::Functions qw( rel2abs ); -use lib dirname( rel2abs( $0 ) ); - -BEGIN { - use_ok( q{Zonemaster::Engine} ); - use_ok( q{Zonemaster::Engine::Nameserver} ); - use_ok( q{Zonemaster::Engine::Test::Zone} ); - use_ok( q{TestUtil}, qw( perform_testcase_testing ) ); -} - -########### -# zone09 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Zone-TP/zone09.md -my $test_module = q{Zone}; -my $test_case = 'zone09'; -my @all_tags = qw(Z09_INCONSISTENT_MX - Z09_INCONSISTENT_MX_DATA - Z09_MISSING_MAIL_TARGET - Z09_MX_DATA - Z09_MX_FOUND - Z09_NON_AUTH_MX_RESPONSE - Z09_NO_MX_FOUND - Z09_NO_RESPONSE_MX_QUERY - Z09_NULL_MX_NON_ZERO_PREF - Z09_NULL_MX_WITH_OTHER_MX - Z09_ROOT_EMAIL_DOMAIN - Z09_TLD_EMAIL_DOMAIN - Z09_UNEXPECTED_RCODE_MX); - -# Test case specific hints file (test-zone-data/Zone-TP/zone09/hintfile-root-email-domain) -Zonemaster::Engine::Recursor->remove_fake_addresses( '.' ); -Zonemaster::Engine::Recursor->add_fake_addresses( '.', - { 'ns1' => [ '127.19.9.43', 'fda1:b2:c3::127:19:9:43' ], - 'ns2' => [ '127.19.9.44', 'fda1:b2:c3::127:19:9:44' ], - } -); - -# Test zone scenarios -# - Documentation: L -# - Format: { SCENARIO_NAME => [ -# testable, -# zone_name, -# [ MANDATORY_MESSAGE_TAGS ], -# [ FORBIDDEN_MESSAGE_TAGS ], -# [ UNDELEGATED_NS ], -# [ UNDELEGATED_DS ], -# ] } -# -# - One of MANDATORY_MESSAGE_TAGS and FORBIDDEN_MESSAGE_TAGS may be undefined. -# See documentation for the meaning of that. - -my %subtests = ( - 'ROOT-EMAIL-DOMAIN' => [ - 1, - q(.), - [ qw(Z09_ROOT_EMAIL_DOMAIN) ], - undef, - [], - [] - ] -); -########### - -my $datafile = 't/' . basename ($0, '.t') . '.data'; - -if ( not $ENV{ZONEMASTER_RECORD} ) { - die q{Stored data file missing} if not -r $datafile; - Zonemaster::Engine::Nameserver->restore( $datafile ); - Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 ); -} - -Zonemaster::Engine::Profile->effective->merge( Zonemaster::Engine::Profile->from_json( qq({ "test_cases": [ "$test_case" ] }) ) ); - -perform_testcase_testing( $test_case, $test_module, \@all_tags, \%subtests, $ENV{ZONEMASTER_SELECTED_SCENARIOS}, $ENV{ZONEMASTER_DISABLED_SCENARIOS} ); - -if ( $ENV{ZONEMASTER_RECORD} ) { - Zonemaster::Engine::Nameserver->save( $datafile ); -} - -done_testing; diff --git a/t/Test-zone09.data b/t/Test-zone09.data index 95eb7dc9f..8a77d9b50 100644 Binary files a/t/Test-zone09.data and b/t/Test-zone09.data differ diff --git a/t/Test-zone09.t b/t/Test-zone09.t index 9f4893866..f8dae1f40 100644 --- a/t/Test-zone09.t +++ b/t/Test-zone09.t @@ -1,180 +1,302 @@ use strict; use warnings; -use Test::More; -use File::Basename; +use File::Basename qw( dirname ); use File::Spec::Functions qw( rel2abs ); use lib dirname( rel2abs( $0 ) ); +use TestUtil::DSL; -BEGIN { - use_ok( q{Zonemaster::Engine} ); - use_ok( q{Zonemaster::Engine::Nameserver} ); - use_ok( q{Zonemaster::Engine::Test::Zone} ); - use_ok( q{TestUtil}, qw( perform_testcase_testing ) ); -} - -########### -# zone09 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Zone-TP/zone09.md -my $test_module = q{Zone}; -my $test_case = 'zone09'; -my @all_tags = qw(Z09_INCONSISTENT_MX - Z09_INCONSISTENT_MX_DATA - Z09_MISSING_MAIL_TARGET - Z09_MX_DATA - Z09_MX_FOUND - Z09_NON_AUTH_MX_RESPONSE - Z09_NO_MX_FOUND - Z09_NO_RESPONSE_MX_QUERY - Z09_NULL_MX_NON_ZERO_PREF - Z09_NULL_MX_WITH_OTHER_MX - Z09_ROOT_EMAIL_DOMAIN - Z09_TLD_EMAIL_DOMAIN - Z09_UNEXPECTED_RCODE_MX); - -# Common hint file (test-zone-data/COMMON/hintfile) -Zonemaster::Engine::Recursor->remove_fake_addresses( '.' ); -Zonemaster::Engine::Recursor->add_fake_addresses( '.', - { 'ns1' => [ '127.1.0.1', 'fda1:b2:c3::127:1:0:1' ], - 'ns2' => [ '127.1.0.2', 'fda1:b2:c3::127:1:0:2' ], - } -); - -# Test zone scenarios -# - Documentation: L -# - Format: { SCENARIO_NAME => [ -# testable, -# zone_name, -# [ MANDATORY_MESSAGE_TAGS ], -# [ FORBIDDEN_MESSAGE_TAGS ], -# [ UNDELEGATED_NS ], -# [ UNDELEGATED_DS ], -# ] } -# -# - One of MANDATORY_MESSAGE_TAGS and FORBIDDEN_MESSAGE_TAGS may be undefined. -# See documentation for the meaning of that. - -my %subtests = ( - 'NO-RESPONSE-MX-QUERY' => [ - 1, - q(no-response-mx-query.zone09.xa),# - [ qw(Z09_NO_RESPONSE_MX_QUERY) ], - [], - [], - [] - ], - 'UNEXPECTED-RCODE-MX' => [ - 1, - q(unexpected-rcode-mx.zone09.xa),# - [ qw(Z09_UNEXPECTED_RCODE_MX) ], - [], - [], - [] - ], - 'NON-AUTH-MX-RESPONSE' => [ - 0, - q(non-auth-mx-response.zone09.xa),# - [ qw(Z09_NON_AUTH_MX_RESPONSE) ], - [], - [], - [] - ], - 'INCONSISTENT-MX' => [ - 1, - q(inconsistent-mx.zone09.xa),# - [ qw(Z09_INCONSISTENT_MX Z09_MX_FOUND Z09_NO_MX_FOUND Z09_MX_DATA) ], - [ qw(Z09_MISSING_MAIL_TARGET) ], - [], - [] - ], - 'INCONSISTENT-MX-DATA' => [ - 1, - q(inconsistent-mx-data.zone09.xa), - [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA) ], - undef, - [], - [] - ], - 'NULL-MX-WITH-OTHER-MX' => [ - 1, - q(null-mx-with-other-mx.zone09.xa),# - [ qw(Z09_NULL_MX_WITH_OTHER_MX) ], - [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_ROOT_EMAIL_DOMAIN Z09_TLD_EMAIL_DOMAIN) ], - [], - [] - ], - 'NULL-MX-NON-ZERO-PREF' => [ - 1, - q(null-mx-non-zero-pref.zone09.xa),# - [ qw(Z09_NULL_MX_NON_ZERO_PREF) ], - [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_ROOT_EMAIL_DOMAIN Z09_TLD_EMAIL_DOMAIN) ], - [], - [] - ], - 'TLD-EMAIL-DOMAIN' => [ - 1, - q(tld-email-domain-zone09), - [ qw(Z09_TLD_EMAIL_DOMAIN) ], - undef, - [], - [] - ], - 'MX-DATA' => [ - 1, - q(mx-data.zone09.xa), - [ qw(Z09_MX_DATA) ], - undef, - [], - [] - ], - 'NULL-MX' => [ - 1, - q(null-mx.zone09.xa), - [], - undef, - [], - [] - ], - 'NO-MX-SLD' => [ - 1, - q(no-mx-sld.zone09.xa), - [ qw(Z09_MISSING_MAIL_TARGET) ], - undef, - [], - [] - ], - 'NO-MX-TLD' => [ - 1, - q(no-mx-tld-zone09), - [], - undef, - [], - [] - ], - 'NO-MX-ARPA' => [ - 1, - q(no-mx-arpa.zone09.arpa), - [], - undef, - [], - [] - ] -); -########### - -my $datafile = 't/' . basename ($0, '.t') . '.data'; - -if ( not $ENV{ZONEMASTER_RECORD} ) { - die q{Stored data file missing} if not -r $datafile; - Zonemaster::Engine::Nameserver->restore( $datafile ); - Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 ); -} - -Zonemaster::Engine::Profile->effective->merge( Zonemaster::Engine::Profile->from_json( qq({ "test_cases": [ "$test_case" ] }) ) ); - -perform_testcase_testing( $test_case, $test_module, \@all_tags, \%subtests, $ENV{ZONEMASTER_SELECTED_SCENARIOS}, $ENV{ZONEMASTER_DISABLED_SCENARIOS} ); - -if ( $ENV{ZONEMASTER_RECORD} ) { - Zonemaster::Engine::Nameserver->save( $datafile ); -} - -done_testing; +testing_test_case 'Zone', 'zone09'; + +all_tags qw(Z09_ARPA_EMAIL_DOMAIN + Z09_INCONSISTENT_MX + Z09_INCONSISTENT_MX_DATA + Z09_MISSING_MAIL_EXCHANGE + Z09_MX_DATA + Z09_MX_FOUND + Z09_NON_AUTH_MX_RESPONSE + Z09_NO_MX_FOUND + Z09_NO_MX_FOUND_OR_EXPECTED + Z09_NO_SERVERS_MX_RESPONSE + Z09_NO_RESPONSE_MX_QUERY + Z09_NULL_MX_NON_ZERO_PREF + Z09_NULL_MX_WITH_OTHER_MX + Z09_ROOT_EMAIL_DOMAIN + Z09_TLD_EMAIL_DOMAIN + Z09_UNEXPECTED_RCODE_MX + Z09_VALID_NULL_MX); + +root_hints 'ns1' => [ qw(127.19.9.61 fda1:b2:c3::127:19:9:61) ], + 'ns2' => [ qw(127.19.9.62 fda1:b2:c3::127:19:9:62) ]; +zone_name_template '{SCENARIO}.{TESTCASE}.xa'; + +scenario 'NO-RESPONSE-MX-QUERY-1' => sub { + expect Z09_NO_RESPONSE_MX_QUERY => { + ns_list => 'ns2.no-response-mx-query-1.zone09.xa/127.19.9.32;' + . 'ns2.no-response-mx-query-1.zone09.xa/fda1:b2:c3:0:127:19:9:32' + }; + expect Z09_MX_DATA => { + mxrdata_list => '10 mail.no-response-mx-query-1.zone09.xa', + ns_list => 'ns1.no-response-mx-query-1.zone09.xa/127.19.9.31;' + . 'ns1.no-response-mx-query-1.zone09.xa/fda1:b2:c3:0:127:19:9:31' + }; + forbid_others; +}; + +scenario 'NO-RESPONSE-MX-QUERY-2' => sub { + expect Z09_NO_RESPONSE_MX_QUERY => { + ns_list => 'ns1.no-response-mx-query-2.zone09.xa/127.19.9.31;' + . 'ns1.no-response-mx-query-2.zone09.xa/fda1:b2:c3:0:127:19:9:31;' + . 'ns2.no-response-mx-query-2.zone09.xa/127.19.9.32;' + . 'ns2.no-response-mx-query-2.zone09.xa/fda1:b2:c3:0:127:19:9:32' + }; + expect 'Z09_NO_SERVERS_MX_RESPONSE'; + forbid_others; +}; + +scenario 'UNEXPECTED-RCODE-MX' => sub { + expect Z09_UNEXPECTED_RCODE_MX => { + rcode => 'NOTIMPL', + ns_list => 'ns1.unexpected-rcode-mx.zone09.xa/127.19.9.31;' + . 'ns1.unexpected-rcode-mx.zone09.xa/fda1:b2:c3:0:127:19:9:31' + }; + expect 'Z09_MISSING_MAIL_EXCHANGE'; + forbid_others; +}; + +scenario 'NON-AUTH-MX-RESPONSE' => sub { + not_testable 'see https://github.com/zonemaster/zonemaster/pull/1517#discussion_r3595385839'; + + expect Z09_NON_AUTH_MX_RESPONSE => { + ns_list => 'ns4.non-auth-mx-response.zone09.xa/127.19.9.34;' + . 'ns4.non-auth-mx-response.zone09.xa/fda1:b2:c3:0:127:19:9:34' + }; + expect Z09_MX_DATA => { + mxrdata_list => '10 mail.non-auth-mx-response.zone09.xa', + ns_list => sub { split(/;/, $_->{args}{ns_list}) == 4 } + }; + forbid_others; +}; + +scenario 'INCONSISTENT-MX' => sub { + expect 'Z09_INCONSISTENT_MX'; + expect Z09_MX_FOUND => { + ns_list => 'ns1.inconsistent-mx.zone09.xa/127.19.9.31;' + . 'ns1.inconsistent-mx.zone09.xa/fda1:b2:c3:0:127:19:9:31' + }; + expect Z09_NO_MX_FOUND => { + ns_list => 'ns2.inconsistent-mx.zone09.xa/127.19.9.32;' + . 'ns2.inconsistent-mx.zone09.xa/fda1:b2:c3:0:127:19:9:32' + }; + expect Z09_MX_DATA => { + ns_list => 'ns1.inconsistent-mx.zone09.xa/127.19.9.31;' + . 'ns1.inconsistent-mx.zone09.xa/fda1:b2:c3:0:127:19:9:31', + mxrdata_list => '10 mail.inconsistent-mx.zone09.xa' + }; + + forbid_others; +}; + +scenario 'INCONSISTENT-MX-DATA-1' => sub { + expect 'Z09_INCONSISTENT_MX_DATA'; + expect Z09_MX_DATA => { + ns_list => 'ns1.inconsistent-mx-data-1.zone09.xa/127.19.9.31;' + . 'ns1.inconsistent-mx-data-1.zone09.xa/fda1:b2:c3:0:127:19:9:31', + mxrdata_list => '10 mail.inconsistent-mx-data-1.zone09.xa;' + . '10 mail2.inconsistent-mx-data-1.zone09.xa' + }; + expect Z09_MX_DATA => { + ns_list => 'ns2.inconsistent-mx-data-1.zone09.xa/127.19.9.32;' + . 'ns2.inconsistent-mx-data-1.zone09.xa/fda1:b2:c3:0:127:19:9:32', + mxrdata_list => '10 mail2.inconsistent-mx-data-1.zone09.xa' + }; + forbid_others; +}; + +scenario 'INCONSISTENT-MX-DATA-2' => sub { + expect 'Z09_INCONSISTENT_MX_DATA'; + expect Z09_MX_DATA => { + ns_list => 'ns1.inconsistent-mx-data-2.zone09.xa/127.19.9.31;' + . 'ns1.inconsistent-mx-data-2.zone09.xa/fda1:b2:c3:0:127:19:9:31', + mxrdata_list => '20 mail2.inconsistent-mx-data-2.zone09.xa' + }; + expect Z09_MX_DATA => { + ns_list => 'ns2.inconsistent-mx-data-2.zone09.xa/127.19.9.32;' + . 'ns2.inconsistent-mx-data-2.zone09.xa/fda1:b2:c3:0:127:19:9:32', + mxrdata_list => '10 mail2.inconsistent-mx-data-2.zone09.xa' + }; + forbid_others; +}; + +scenario 'INCONSISTENT-MX-DATA-3' => sub { + expect 'Z09_INCONSISTENT_MX_DATA'; + expect Z09_MX_DATA => { + ns_list => 'ns1.inconsistent-mx-data-3.zone09.xa/127.19.9.31;' + . 'ns1.inconsistent-mx-data-3.zone09.xa/fda1:b2:c3:0:127:19:9:31', + mxrdata_list => '10 mail.inconsistent-mx-data-3.zone09.xa;' + . '20 mail2.inconsistent-mx-data-3.zone09.xa' + }; + expect Z09_MX_DATA => { + ns_list => 'ns2.inconsistent-mx-data-3.zone09.xa/127.19.9.32;' + . 'ns2.inconsistent-mx-data-3.zone09.xa/fda1:b2:c3:0:127:19:9:32', + mxrdata_list => '10 mail2.inconsistent-mx-data-3.zone09.xa;' + . '20 mail.inconsistent-mx-data-3.zone09.xa' + }; + forbid_others; +}; + +scenario 'INCONSISTENT-MX-DATA-4' => sub { + expect 'Z09_INCONSISTENT_MX_DATA'; + expect Z09_MX_DATA => { + ns_list => 'ns1.inconsistent-mx-data-4.zone09.xa/127.19.9.31;' + . 'ns1.inconsistent-mx-data-4.zone09.xa/fda1:b2:c3:0:127:19:9:31', + mxrdata_list => '10 mail.inconsistent-mx-data-4.zone09.xa;' + . '20 mail.inconsistent-mx-data-4.zone09.xa' + }; + expect Z09_MX_DATA => { + ns_list => 'ns2.inconsistent-mx-data-4.zone09.xa/127.19.9.32;' + . 'ns2.inconsistent-mx-data-4.zone09.xa/fda1:b2:c3:0:127:19:9:32', + mxrdata_list => '10 mail.inconsistent-mx-data-4.zone09.xa' + }; + forbid_others; +}; + +scenario 'MIXED-CASE-RDATA-1' => sub { + expect Z09_MX_DATA => { + ns_list => 'ns1.mixed-case-rdata-1.zone09.xa/127.19.9.31;' + . 'ns1.mixed-case-rdata-1.zone09.xa/fda1:b2:c3:0:127:19:9:31;' + . 'ns2.mixed-case-rdata-1.zone09.xa/127.19.9.32;' + . 'ns2.mixed-case-rdata-1.zone09.xa/fda1:b2:c3:0:127:19:9:32', + mxrdata_list => '10 mail.mixed-case-rdata-1.zone09.xa;' + . '20 mail2.mixed-case-rdata-1.zone09.xa' + }; + forbid_others; +}; + +scenario 'MIXED-CASE-RDATA-2' => sub { + # This scenario relies on saved packets faithfully reproducing the incoming + # packet in wire format, which is currently not quite the case. + not_testable 'see https://github.com/zonemaster/zonemaster-engine/issues/1554'; + + expect Z09_MX_DATA => { + ns_list => 'ns1.mixed-case-rdata-2.zone09.xa/127.19.9.31;' + . 'ns1.mixed-case-rdata-2.zone09.xa/fda1:b2:c3:0:127:19:9:31;' + . 'ns2.mixed-case-rdata-2.zone09.xa/127.19.9.32;' + . 'ns2.mixed-case-rdata-2.zone09.xa/fda1:b2:c3:0:127:19:9:32', + mxrdata_list => '20 mail2.mixed-case-rdata-2.zone09.xa' + }; + forbid_others; +}; + +scenario 'MIXED-TTL-{1..2}' => sub { + expect 'Z09_MX_DATA'; + forbid_others; +}; + +scenario 'NULL-MX-WITH-OTHER-MX' => sub { + expect 'Z09_NULL_MX_WITH_OTHER_MX'; + expect 'Z09_MX_DATA'; + forbid_others; +}; + +scenario 'NULL-MX-NON-ZERO-PREF' => sub { + expect 'Z09_NULL_MX_NON_ZERO_PREF'; + expect 'Z09_MX_DATA'; + forbid_others; +}; + +scenario 'MX-DATA' => sub { + expect 'Z09_MX_DATA'; + forbid_others; +}; + +scenario 'NULL-MX-SLD' => sub { + expect 'Z09_VALID_NULL_MX'; + expect Z09_MX_DATA => { mxrdata_list => '0 .', ns_list => sub { 1 } }; + forbid_others; +}; + +scenario 'NO-MX-SLD' => sub { + expect 'Z09_MISSING_MAIL_EXCHANGE'; + forbid_others; +}; + +### +### Special cases +### + +# Scenarios that need to test a TLD + +zone_name_template '{SCENARIO}-{TESTCASE}'; + +scenario 'TLD-EMAIL-DOMAIN' => sub { + expect qw(Z09_TLD_EMAIL_DOMAIN Z09_MX_DATA); + forbid_others; +}; + +scenario 'NULL-MX-TLD' => sub { + expect 'Z09_VALID_NULL_MX'; + expect Z09_MX_DATA => { mxrdata_list => '0 .', ns_list => sub { 1 } }; + forbid_others; +}; + +scenario 'NO-MX-TLD' => sub { + expect 'Z09_NO_MX_FOUND_OR_EXPECTED'; + forbid_others; +}; + +# Scenarios that need to test a zone in .ARPA + +zone_name_template '{SCENARIO}.{TESTCASE}.arpa'; + +scenario 'ARPA-EMAIL-DOMAIN' => sub { + expect 'Z09_ARPA_EMAIL_DOMAIN'; + expect 'Z09_MX_DATA'; + forbid_others; +}; + +scenario 'NULL-MX-ARPA' => sub { + expect 'Z09_VALID_NULL_MX'; + expect Z09_MX_DATA => { mxrdata_list => '0 .', ns_list => sub { 1 } }; + forbid_others; +}; + +scenario 'NO-MX-ARPA' => sub { + expect 'Z09_NO_MX_FOUND_OR_EXPECTED'; + forbid_others; +}; + +# Scenarios that need to test the root zone (with different root hints for +# each scenario) + +zone_name_template '.'; + +scenario 'NO-MX-ROOT' => sub { + expect 'Z09_NO_MX_FOUND_OR_EXPECTED'; + forbid_others; +}; + +# Override root hints for scenario ROOT-EMAIL-DOMAIN + +root_hints 'ns1' => [ qw(127.19.9.63 fda1:b2:c3::127:19:9:63) ], + 'ns2' => [ qw(127.19.9.64 fda1:b2:c3::127:19:9:64) ]; + +scenario 'ROOT-EMAIL-DOMAIN' => sub { + expect 'Z09_ROOT_EMAIL_DOMAIN'; + expect 'Z09_MX_DATA'; + forbid_others; +}; + +# Override root hints for scenario NULL-MX-ROOT + +root_hints 'ns1' => [ qw(127.19.9.65 fda1:b2:c3::127:19:9:65) ], + 'ns2' => [ qw(127.19.9.66 fda1:b2:c3::127:19:9:66) ]; + +scenario 'NULL-MX-ROOT' => sub { + expect 'Z09_VALID_NULL_MX'; + expect Z09_MX_DATA => { mxrdata_list => '0 .', ns_list => sub { 1 } }; + forbid_others; +}; + + +no_more_scenarios; diff --git a/t/profiles/Test-zone-all.json b/t/profiles/Test-zone-all.json index 92f518d8d..863f7b31c 100644 --- a/t/profiles/Test-zone-all.json +++ b/t/profiles/Test-zone-all.json @@ -8,7 +8,6 @@ "zone06", "zone07", "zone08", - "zone09", "zone10" ] }