Browse Source

Merge branch 'master' of /var/bind10/git/bind10

JINMEI Tatuya 13 years ago
parent
commit
b8cc8c8192

+ 9 - 0
ChangeLog

@@ -1,3 +1,12 @@
+368.	[func]*		jinmei
+	libdatasrc: the interface of ZoneFinder() was changed: WILDCARD
+	related result codes were deprecated and removed, and the
+	corresponding information is now provided via a separate accessor
+	method on FindResult.  Other separate FindResult methods will
+	also tell the caller whether the zone is signed with NSEC or NSEC3
+	(when necessary and applicable).
+	(Trac #1611, git c175c9c06034b4118e0dfdbccd532c2ebd4ba7e8)
+
 367.	[bug]		jinmei
 	libdatasrc: in-memory data source could incorrectly reject to load
 	zones containing RRSIG records.  For example, it didn't allow

+ 15 - 52
src/bin/auth/query.cc

@@ -30,42 +30,6 @@ using namespace isc::dns;
 using namespace isc::datasrc;
 using namespace isc::dns::rdata;
 
-namespace {
-// This is a temporary wrapper to convert old format of FindResult to new
-// one, until we update all supported data source implementations.
-ZoneFinder::FindResult
-findWrapper(const ZoneFinder::FindResult& orig_result) {
-    // Retrieve the original flags
-    ZoneFinder::FindResultFlags flags = ZoneFinder::RESULT_DEFAULT;
-    if (orig_result.isWildcard()) {
-        flags = flags | ZoneFinder::RESULT_WILDCARD;
-    }
-    if (orig_result.isNSECSigned()) {
-        flags = flags | ZoneFinder::RESULT_NSEC_SIGNED;
-    }
-    if (orig_result.isNSEC3Signed()) {
-        flags = flags | ZoneFinder::RESULT_NSEC3_SIGNED;
-    }
-
-    // Convert older code to new one, adjusting flags if necessary
-    ZoneFinder::Result code = orig_result.code;
-    if (code == ZoneFinder::WILDCARD) {
-        code = ZoneFinder::SUCCESS;
-        flags = flags | ZoneFinder::RESULT_WILDCARD;
-    } else if (code == ZoneFinder::WILDCARD_CNAME) {
-        code = ZoneFinder::CNAME;
-        flags = flags | ZoneFinder::RESULT_WILDCARD;
-    } else if (code == ZoneFinder::WILDCARD_NXRRSET) {
-        code = ZoneFinder::NXRRSET;
-        flags = flags | ZoneFinder::RESULT_WILDCARD;
-    }
-    if (orig_result.rrset && orig_result.rrset->getType() == RRType::NSEC()) {
-        flags = flags | ZoneFinder::RESULT_NSEC_SIGNED;
-    }
-    return (ZoneFinder::FindResult(code, orig_result.rrset, flags));
-}
-}
-
 namespace isc {
 namespace auth {
 
@@ -105,8 +69,8 @@ Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
 
     // Find A rrset
     if (qname_ != qname || qtype_ != RRType::A()) {
-        ZoneFinder::FindResult a_result =
-            findWrapper(zone.find(qname, RRType::A(), options | dnssec_opt_));
+        ZoneFinder::FindResult a_result = zone.find(qname, RRType::A(),
+                                                    options | dnssec_opt_);
         if (a_result.code == ZoneFinder::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
                     boost::const_pointer_cast<RRset>(a_result.rrset), dnssec_);
@@ -115,9 +79,8 @@ Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
 
     // Find AAAA rrset
     if (qname_ != qname || qtype_ != RRType::AAAA()) {
-        ZoneFinder::FindResult aaaa_result =
-            findWrapper(zone.find(qname, RRType::AAAA(),
-                                  options | dnssec_opt_));
+        ZoneFinder::FindResult aaaa_result = zone.find(qname, RRType::AAAA(),
+                                                       options | dnssec_opt_);
         if (aaaa_result.code == ZoneFinder::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
                     boost::const_pointer_cast<RRset>(aaaa_result.rrset),
@@ -128,9 +91,9 @@ Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
 
 void
 Query::addSOA(ZoneFinder& finder) {
-    ZoneFinder::FindResult soa_result =
-        findWrapper(finder.find(finder.getOrigin(),
-                                RRType::SOA(), dnssec_opt_));
+    ZoneFinder::FindResult soa_result = finder.find(finder.getOrigin(),
+                                                    RRType::SOA(),
+                                                    dnssec_opt_);
     if (soa_result.code != ZoneFinder::SUCCESS) {
         isc_throw(NoSOA, "There's no SOA record in zone " <<
             finder.getOrigin().toText());
@@ -186,7 +149,7 @@ Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
     // otherwise we shouldn't have got NXDOMAIN for the original query in
     // the first place).
     const ZoneFinder::FindResult fresult =
-        findWrapper(finder.find(wildname, RRType::NSEC(), dnssec_opt_));
+        finder.find(wildname, RRType::NSEC(), dnssec_opt_);
     if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
         fresult.rrset->getRdataCount() == 0) {
         isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
@@ -210,8 +173,8 @@ Query::addWildcardProof(ZoneFinder& finder) {
     // substitution.  Confirm that by specifying NO_WILDCARD.  It should result
     // in NXDOMAIN and an NSEC RR that proves it should be returned.
     const ZoneFinder::FindResult fresult =
-        findWrapper(finder.find(qname_, RRType::NSEC(),
-                                dnssec_opt_ | ZoneFinder::NO_WILDCARD));
+        finder.find(qname_, RRType::NSEC(),
+                    dnssec_opt_ | ZoneFinder::NO_WILDCARD);
     if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
         fresult.rrset->getRdataCount() == 0) {
         isc_throw(BadNSEC, "Unexpected result for wildcard proof");
@@ -230,8 +193,8 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
     }
     
     const ZoneFinder::FindResult fresult =
-        findWrapper(finder.find(qname_, RRType::NSEC(),
-                                dnssec_opt_ | ZoneFinder::NO_WILDCARD));
+        finder.find(qname_, RRType::NSEC(),
+                    dnssec_opt_ | ZoneFinder::NO_WILDCARD);
     if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
         fresult.rrset->getRdataCount() == 0) {
         isc_throw(BadNSEC, "Unexpected result for no match QNAME proof");
@@ -249,8 +212,8 @@ void
 Query::addAuthAdditional(ZoneFinder& finder) {
     // Fill in authority and addtional sections.
     ZoneFinder::FindResult ns_result =
-        findWrapper(finder.find(finder.getOrigin(), RRType::NS(),
-                                dnssec_opt_));
+        finder.find(finder.getOrigin(), RRType::NS(), dnssec_opt_);
+
     // zone origin name should have NS records
     if (ns_result.code != ZoneFinder::SUCCESS) {
         isc_throw(NoApexNS, "There's no apex NS records in zone " <<
@@ -295,7 +258,7 @@ Query::process() {
         find = boost::bind(&ZoneFinder::find, &zfinder, qname_, qtype_,
                            dnssec_opt_);
     }
-    ZoneFinder::FindResult db_result(findWrapper(find()));
+    ZoneFinder::FindResult db_result(find());
     switch (db_result.code) {
         case ZoneFinder::DNAME: {
             // First, put the dname into the answer

+ 5 - 5
src/bin/xfrin/tests/xfrin_test.py

@@ -167,14 +167,14 @@ class MockDataSourceClient():
 
         '''
         if name == TEST_ZONE_NAME and rrtype == RRType.SOA():
-            return (ZoneFinder.SUCCESS, begin_soa_rrset)
+            return (ZoneFinder.SUCCESS, begin_soa_rrset, 0)
         if name == Name('no-soa.example'):
-            return (ZoneFinder.NXDOMAIN, None)
+            return (ZoneFinder.NXDOMAIN, None, 0)
         if name == Name('dup-soa.example'):
             dup_soa_rrset = RRset(name, TEST_RRCLASS, RRType.SOA(), RRTTL(0))
             dup_soa_rrset.add_rdata(begin_soa_rdata)
             dup_soa_rrset.add_rdata(soa_rdata)
-            return (ZoneFinder.SUCCESS, dup_soa_rrset)
+            return (ZoneFinder.SUCCESS, dup_soa_rrset, 0)
         raise ValueError('Unexpected input to mock finder: bug in test case?')
 
     def get_updater(self, zone_name, replace, journaling=False):
@@ -1751,7 +1751,7 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
     def get_zone_serial(self):
         result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
         self.assertEqual(DataSourceClient.SUCCESS, result)
-        result, soa = finder.find(TEST_ZONE_NAME, RRType.SOA())
+        result, soa, _ = finder.find(TEST_ZONE_NAME, RRType.SOA())
         self.assertEqual(ZoneFinder.SUCCESS, result)
         self.assertEqual(1, soa.get_rdata_count())
         return get_soa_serial(soa.get_rdata()[0])
@@ -1759,7 +1759,7 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
     def record_exist(self, name, type):
         result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
         self.assertEqual(DataSourceClient.SUCCESS, result)
-        result, soa = finder.find(name, type)
+        result, soa, _ = finder.find(name, type)
         return result == ZoneFinder.SUCCESS
 
     def test_do_ixfrin_sqlite3(self):

+ 1 - 1
src/bin/xfrin/xfrin.py.in

@@ -583,7 +583,7 @@ class XfrinConnection(asyncore.dispatcher):
             result, finder = self._datasrc_client.find_zone(self._zone_name)
         if result != DataSourceClient.SUCCESS:
             return None
-        result, soa_rrset = finder.find(self._zone_name, RRType.SOA())
+        result, soa_rrset, _ = finder.find(self._zone_name, RRType.SOA())
         if result != ZoneFinder.SUCCESS:
             logger.info(XFRIN_ZONE_NO_SOA, self.zone_str())
             return None

+ 4 - 4
src/bin/xfrout/tests/xfrout_test.py.in

@@ -105,16 +105,16 @@ class MockDataSrcClient:
 
         '''
         if name == Name('nosoa.example.com') and rrtype == RRType.SOA():
-            return (ZoneFinder.NXDOMAIN, None)
+            return (ZoneFinder.NXDOMAIN, None, 0)
         elif name == Name('multisoa.example.com') and rrtype == RRType.SOA():
             soa_rrset = create_soa(SOA_CURRENT_VERSION)
             soa_rrset.add_rdata(soa_rrset.get_rdata()[0])
-            return (ZoneFinder.SUCCESS, soa_rrset)
+            return (ZoneFinder.SUCCESS, soa_rrset, 0)
         elif name == Name('maxserial.example.com'):
             soa_rrset = create_soa(0xffffffff)
-            return (ZoneFinder.SUCCESS, soa_rrset)
+            return (ZoneFinder.SUCCESS, soa_rrset, 0)
         elif rrtype == RRType.SOA():
-            return (ZoneFinder.SUCCESS, create_soa(SOA_CURRENT_VERSION))
+            return (ZoneFinder.SUCCESS, create_soa(SOA_CURRENT_VERSION), 0)
         raise ValueError('Unexpected input to mock finder: bug in test case?')
 
     def get_iterator(self, zone_name, adjust_ttl=False):

+ 1 - 1
src/bin/xfrout/xfrout.py.in

@@ -335,7 +335,7 @@ class XfroutSession():
         result, finder = self._datasrc_client.find_zone(zone_name)
         if result != DataSourceClient.SUCCESS:
             return (Rcode.NOTAUTH(), None)
-        result, soa_rrset = finder.find(zone_name, RRType.SOA())
+        result, soa_rrset, _ = finder.find(zone_name, RRType.SOA())
         if result != ZoneFinder.SUCCESS:
             return (Rcode.SERVFAIL(), None)
         # Especially for database-based zones, a working zone may be in

+ 44 - 31
src/lib/datasrc/database.cc

@@ -616,7 +616,7 @@ DatabaseClient::Finder::findWildcardMatch(
                           DATASRC_DATABASE_WILDCARD_CANCEL_NS).
                     arg(accessor_->getDBName()).arg(wildcard).
                     arg(dresult.first_ns->getName());
-                return (ZoneFinder::FindResult(DELEGATION, dresult.first_ns));
+                return (FindResult(DELEGATION, dresult.first_ns));
 
             } else if (!hasSubdomains(name.split(i - 1).toText())) {
                 // The wildcard match is the best one, find the final result
@@ -630,7 +630,7 @@ DatabaseClient::Finder::findWildcardMatch(
                           DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
                     arg(accessor_->getDBName()).arg(wildcard).
                     arg(name).arg(superdomain);
-                return (ZoneFinder::FindResult(NXDOMAIN, ConstRRsetPtr()));
+                return (FindResult(NXDOMAIN, ConstRRsetPtr()));
             }
 
         } else if (hasSubdomains(wildcard)) {
@@ -641,22 +641,23 @@ DatabaseClient::Finder::findWildcardMatch(
             if ((options & FIND_DNSSEC) != 0) {
                 ConstRRsetPtr nsec = findNSECCover(Name(wildcard));
                 if (nsec) {
-                    return (ZoneFinder::FindResult(WILDCARD_NXRRSET, nsec));
+                    return (FindResult(NXRRSET, nsec,
+                                       RESULT_WILDCARD | RESULT_NSEC_SIGNED));
                 }
             }
-            return (ZoneFinder::FindResult(NXRRSET, ConstRRsetPtr()));
+            return (FindResult(NXRRSET, ConstRRsetPtr(), RESULT_WILDCARD));
         }
     }
 
     // Nothing found at any level.
-    return (ZoneFinder::FindResult(NXDOMAIN, ConstRRsetPtr()));
+    return (FindResult(NXDOMAIN, ConstRRsetPtr()));
 }
 
 ZoneFinder::FindResult
 DatabaseClient::Finder::logAndCreateResult(
     const Name& name, const string* wildname, const RRType& type,
     ZoneFinder::Result code, ConstRRsetPtr rrset,
-    const isc::log::MessageID& log_id) const
+    const isc::log::MessageID& log_id, FindResultFlags flags) const
 {
     if (rrset) {
         if (wildname == NULL) {
@@ -679,7 +680,7 @@ DatabaseClient::Finder::logAndCreateResult(
                 arg(getClass()).arg(*wildname);
         }
     }
-    return (ZoneFinder::FindResult(code, rrset));
+    return (ZoneFinder::FindResult(code, rrset, flags));
 }
 
 ZoneFinder::FindResult
@@ -693,12 +694,23 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
                                          target)
 {
     const bool wild = (wildname != NULL);
+    FindResultFlags flags = wild ? RESULT_WILDCARD : RESULT_DEFAULT;
 
     // Get iterators for the different types of records we are interested in -
     // CNAME, NS and Wanted types.
     const FoundIterator nsi(found.second.find(RRType::NS()));
     const FoundIterator cni(found.second.find(RRType::CNAME()));
     const FoundIterator wti(found.second.find(type));
+    // For wildcard case with DNSSEC required, the caller would need to know
+    // whether it's NSEC or NSEC3 signed.  So we need to do an additional
+    // search here, even though the NSEC RR may not be returned.
+    // TODO: this part should be revised when we support NSEC3; ideally we
+    // should use more effective and efficient way to identify (whether and)
+    // in which way the zone is signed.
+    if (wild && (options & FIND_DNSSEC) != 0 &&
+        found.second.find(RRType::NSEC()) != found.second.end()) {
+        flags = flags | RESULT_NSEC_SIGNED;
+    }
 
     if (!is_origin && ((options & FIND_GLUE_OK) == 0) &&
         nsi != found.second.end()) {
@@ -709,7 +721,8 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
         return (logAndCreateResult(name, wildname, type, DELEGATION,
                                    nsi->second,
                                    wild ? DATASRC_DATABASE_WILDCARD_NS :
-                                   DATASRC_DATABASE_FOUND_DELEGATION_EXACT));
+                                   DATASRC_DATABASE_FOUND_DELEGATION_EXACT,
+                                   flags));
 
     } else if (type != RRType::CNAME() && cni != found.second.end()) {
         // We are not searching for a CNAME but nevertheless we have found one
@@ -722,10 +735,10 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
                       cni->second->getRdataCount() << " rdata at " << name <<
                       ", expected 1");
         }
-        return (logAndCreateResult(name, wildname, type,
-                                   wild ? WILDCARD_CNAME : CNAME, cni->second,
+        return (logAndCreateResult(name, wildname, type, CNAME, cni->second,
                                    wild ? DATASRC_DATABASE_WILDCARD_CNAME :
-                                   DATASRC_DATABASE_FOUND_CNAME));
+                                   DATASRC_DATABASE_FOUND_CNAME,
+                                   flags));
 
     } else if (wti != found.second.end()) {
         bool any(type == RRType::ANY());
@@ -748,9 +761,8 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
         // includes the case where we were explicitly querying for a CNAME and
         // found it.  It also includes the case where we were querying for an
         // NS RRset and found it at the apex of the zone.)
-        return (logAndCreateResult(name, wildname, type,
-                                   wild ? WILDCARD : SUCCESS, wti->second,
-                                   lid));
+        return (logAndCreateResult(name, wildname, type, SUCCESS,
+                                   wti->second, lid, flags));
     }
 
     // If we get here, we have found something at the requested name but not
@@ -778,15 +790,13 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
     if (nsec_rrset) {
         // This log message covers both normal and wildcard cases, so we pass
         // NULL for 'wildname'.
-        return (logAndCreateResult(name, NULL, type,
-                                   wild ? WILDCARD_NXRRSET : NXRRSET,
-                                   nsec_rrset,
-                                   DATASRC_DATABASE_FOUND_NXRRSET_NSEC));
+        return (logAndCreateResult(name, NULL, type, NXRRSET, nsec_rrset,
+                                   DATASRC_DATABASE_FOUND_NXRRSET_NSEC,
+                                   flags | RESULT_NSEC_SIGNED));
     }
-    return (logAndCreateResult(name, wildname, type,
-                               wild ? WILDCARD_NXRRSET : NXRRSET, nsec_rrset,
+    return (logAndCreateResult(name, wildname, type, NXRRSET, nsec_rrset,
                                wild ? DATASRC_DATABASE_WILDCARD_NXRRSET :
-                               DATASRC_DATABASE_FOUND_NXRRSET));
+                               DATASRC_DATABASE_FOUND_NXRRSET, flags));
 }
 
 ZoneFinder::FindResult
@@ -809,9 +819,10 @@ DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
         LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                   DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
             arg(accessor_->getDBName()).arg(name);
-        return (FindResult(NXRRSET, dnssec_data ? findNSECCover(name) :
-                           ConstRRsetPtr()));
-
+        const ConstRRsetPtr nsec = dnssec_data ? findNSECCover(name) :
+            ConstRRsetPtr();
+        return (FindResult(NXRRSET, nsec,
+                           nsec ? RESULT_NSEC_SIGNED : RESULT_DEFAULT));
     } else if ((options & NO_WILDCARD) == 0) {
         // It's not an empty non-terminal and wildcard matching is not
         // disabled, so check for wildcards. If there is a wildcard match
@@ -820,7 +831,7 @@ DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
         const ZoneFinder::FindResult wresult =
             findWildcardMatch(name, type, options, dresult, target);
         if (wresult.code != NXDOMAIN) {
-            return (FindResult(wresult.code, wresult.rrset));
+            return (wresult);
         }
     }
 
@@ -828,15 +839,16 @@ DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
     // NSEC records if requested).
     LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_NO_MATCH).
               arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
-    return (FindResult(NXDOMAIN, dnssec_data ? findNSECCover(name) :
-                           ConstRRsetPtr()));
+    const ConstRRsetPtr nsec = dnssec_data ? findNSECCover(name) :
+        ConstRRsetPtr();
+    return (FindResult(NXDOMAIN, nsec,
+                       nsec ? RESULT_NSEC_SIGNED : RESULT_DEFAULT));
 }
 
 ZoneFinder::FindResult
-DatabaseClient::Finder::findInternal(const isc::dns::Name& name,
-                             const isc::dns::RRType& type,
-                             std::vector<isc::dns::ConstRRsetPtr>* target,
-                             const FindOptions options)
+DatabaseClient::Finder::findInternal(const Name& name, const RRType& type,
+                                     std::vector<ConstRRsetPtr>* target,
+                                     const FindOptions options)
 {
     LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
               .arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
@@ -864,6 +876,7 @@ DatabaseClient::Finder::findInternal(const isc::dns::Name& name,
     // presence of the delegation.)
     const DelegationSearchResult dresult = findDelegationPoint(name, options);
     if (dresult.rrset) {
+        // In this case no special flags are needed.
         return (FindResult(dresult.code, dresult.rrset));
     }
 

+ 2 - 1
src/lib/datasrc/database.h

@@ -1056,7 +1056,8 @@ public:
                                       const isc::dns::RRType& type,
                                       ZoneFinder::Result code,
                                       isc::dns::ConstRRsetPtr rrset,
-                                      const isc::log::MessageID& log_id) const;
+                                      const isc::log::MessageID& log_id,
+                                      FindResultFlags flags) const;
 
         /// \brief Checks if something lives below this domain.
         ///

+ 81 - 52
src/lib/datasrc/tests/database_unittest.cc

@@ -1416,12 +1416,20 @@ doFindTest(ZoneFinder& finder,
            ZoneFinder::Result expected_result,
            const std::vector<std::string>& expected_rdatas,
            const std::vector<std::string>& expected_sig_rdatas,
+           ZoneFinder::FindResultFlags expected_flags =
+           ZoneFinder::RESULT_DEFAULT,
            const isc::dns::Name& expected_name = isc::dns::Name::ROOT_NAME(),
            const ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT)
 {
     SCOPED_TRACE("doFindTest " + name.toText() + " " + type.toText());
     const ZoneFinder::FindResult result = finder.find(name, type, options);
     ASSERT_EQ(expected_result, result.code) << name << " " << type;
+    EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+              result.isWildcard());
+    EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0,
+              result.isNSECSigned());
+    EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0,
+              result.isNSEC3Signed());
     if (!expected_rdatas.empty() && result.rrset) {
         checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
                    name, finder.getClass(), expected_type, expected_ttl,
@@ -1838,17 +1846,17 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
     doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
                this->qtype_, isc::dns::RRType::NS(),
                this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
-               this->expected_sig_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("delegation.example.org."));
     doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
                this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
-               this->expected_sig_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("delegation.example.org."));
     doFindTest(*finder, isc::dns::Name("deep.below.delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
                this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
-               this->expected_sig_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("delegation.example.org."));
 
     // Even when we check directly at the delegation point, we should get
@@ -1876,15 +1884,18 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
     doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
                this->qtype_, isc::dns::RRType::DNAME(),
                this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
-               this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+               isc::dns::Name("dname.example.org."));
     doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
                this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
-               this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+               isc::dns::Name("dname.example.org."));
     doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
                this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
-               this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+               isc::dns::Name("dname.example.org."));
 
     // Asking direcly for DNAME should give SUCCESS
     doFindTest(*finder, isc::dns::Name("dname.example.org."),
@@ -1944,12 +1955,14 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
                this->rrttl_, ZoneFinder::NXRRSET,
                this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("ns.delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
     doFindTest(*finder, isc::dns::Name("nothere.delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
                this->rrttl_, ZoneFinder::NXDOMAIN,
                this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("nothere.delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
     this->expected_rdatas_.push_back("192.0.2.1");
@@ -1957,6 +1970,7 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
                this->qtype_, this->qtype_,
                this->rrttl_, ZoneFinder::SUCCESS,
                this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("ns.delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
     this->expected_rdatas_.clear();
@@ -1972,6 +1986,7 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
                isc::dns::RRType::NS(), isc::dns::RRType::NS(),
                this->rrttl_, ZoneFinder::SUCCESS,
                this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
     this->expected_rdatas_.clear();
@@ -1983,12 +1998,12 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
     doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
                this->qtype_, isc::dns::RRType::DNAME(),
                this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
-               this->expected_sig_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
     doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
                this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
-               this->expected_sig_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
 }
 
@@ -2003,21 +2018,24 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
                                          "FAKEFAKEFAKE");
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
                this->qtype_, this->qtype_, this->rrttl_,
-               ZoneFinder::WILDCARD, this->expected_rdatas_,
-               this->expected_sig_rdatas_);
+               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_WILDCARD);
     doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
-               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::WILDCARD,
-               this->expected_rdatas_, this->expected_sig_rdatas_);
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_WILDCARD);
     this->expected_rdatas_.clear();
     this->expected_sig_rdatas_.clear();
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
-               this->expected_rdatas_, this->expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_WILDCARD);
     doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
-               this->expected_rdatas_, this->expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_WILDCARD);
 
     // Direct request for this wildcard
     this->expected_rdatas_.push_back("192.0.2.5");
@@ -2067,14 +2085,14 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
     doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
                this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
                ZoneFinder::DELEGATION, this->expected_rdatas_,
-               this->expected_sig_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("delegatedwild.example.org"));
     // FIXME: This doesn't look logically OK, GLUE_OK should make it transparent,
     // so the match should either work or be canceled, but return NXDOMAIN
     doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
                this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
                ZoneFinder::DELEGATION, this->expected_rdatas_,
-               this->expected_sig_rdatas_,
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
                isc::dns::Name("delegatedwild.example.org"),
                ZoneFinder::FIND_GLUE_OK);
 
@@ -2097,16 +2115,10 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
     this->expected_rdatas_.clear();
     const char* negative_names[] = {
         "a.foo.example.org.",
-        "*.foo.example.org.",
-        "foo.example.org.",
         "wild.bar.foo.example.org.",
         "baz.foo.*.bar.example.org",
         "baz.foo.baz.bar.example.org",
         "*.foo.baz.bar.example.org",
-        "*.foo.*.bar.example.org",
-        "foo.*.bar.example.org",
-        "*.bar.example.org",
-        "bar.example.org",
         NULL
     };
     // Unless FIND_DNSSEC is specified, this is no different from other
@@ -2114,10 +2126,11 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
     for (const char** name = negative_names; *name != NULL; ++ name) {
         doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
                    this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
-                   this->expected_rdatas_, this->expected_sig_rdatas_);
+                   this->expected_rdatas_, this->expected_sig_rdatas_,
+                   ZoneFinder::RESULT_WILDCARD);
     }
 
-    // With FIND_DNSSEC, it should result in WILDCARD_NXRRSET.
+    // With FIND_DNSSEC, it should have NSEC_SIGNED flag.
     const char* negative_dnssec_names[] = {
         "a.bar.example.org.",
         "foo.baz.bar.example.org.",
@@ -2129,8 +2142,9 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
     this->expected_sig_rdatas_.clear();
     for (const char** name = negative_dnssec_names; *name != NULL; ++ name) {
         doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
-                   RRType::NSEC(), this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+                   RRType::NSEC(), this->rrttl_, ZoneFinder::NXRRSET,
                    this->expected_rdatas_, this->expected_sig_rdatas_,
+                   ZoneFinder::RESULT_WILDCARD | ZoneFinder::RESULT_NSEC_SIGNED,
                    Name("bao.example.org."), ZoneFinder::FIND_DNSSEC);
     }
 
@@ -2140,19 +2154,18 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
     this->expected_sig_rdatas_.clear();
     doFindTest(*finder, isc::dns::Name("a.cnamewild.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::CNAME(),
-               this->rrttl_, ZoneFinder::WILDCARD_CNAME,
-               this->expected_rdatas_, this->expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::CNAME,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_WILDCARD);
 
     // DNAME on a wildcard.  In our implementation we ignore DNAMEs on a
     // wildcard, but at a higher level we say the behavior is "unspecified".
     // rfc2672bis strongly discourages the mixture of DNAME and wildcard
     // (with SHOULD NOT).
-    this->expected_rdatas_.clear();
-    this->expected_sig_rdatas_.clear();
     doFindTest(*finder, Name("a.dnamewild.example.org."),
                this->qtype_, this->qtype_, this->rrttl_,
-               ZoneFinder::WILDCARD_NXRRSET, this->expected_rdatas_,
-               this->expected_sig_rdatas_);
+               ZoneFinder::NXRRSET, this->empty_rdatas_,
+               this->empty_rdatas_, ZoneFinder::RESULT_WILDCARD);
 
     // Some strange things in the wild node
     this->expected_rdatas_.clear();
@@ -2160,7 +2173,8 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
     doFindTest(*finder, isc::dns::Name("a.nswild.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::NS(),
                this->rrttl_, ZoneFinder::DELEGATION,
-               this->expected_rdatas_, this->expected_sig_rdatas_);
+               this->expected_rdatas_, this->empty_rdatas_,
+               ZoneFinder::RESULT_WILDCARD);
 }
 
 TYPED_TEST(DatabaseClientTest, noWildcard) {
@@ -2178,7 +2192,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
                RRType::NSEC(), RRType::NSEC(), this->rrttl_,
                ZoneFinder::NXDOMAIN, this->expected_rdatas_,
-               this->expected_sig_rdatas_, Name("*.wild.example.org."),
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+               Name("*.wild.example.org."),
                ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
 
     // Should be the same without FIND_DNSSEC (but in this case no RRsets
@@ -2186,7 +2201,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
                RRType::NSEC(), RRType::NSEC(), this->rrttl_,
                ZoneFinder::NXDOMAIN, this->empty_rdatas_,
-               this->empty_rdatas_, Name::ROOT_NAME(), // name is dummy
+               this->empty_rdatas_, ZoneFinder::RESULT_DEFAULT,
+               Name::ROOT_NAME(), // name is dummy
                ZoneFinder::NO_WILDCARD);
 
     // Same for wildcard empty non terminal.
@@ -2195,7 +2211,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
     doFindTest(*finder, isc::dns::Name("a.bar.example.org"),
                RRType::NSEC(), RRType::NSEC(), this->rrttl_,
                ZoneFinder::NXDOMAIN, this->expected_rdatas_,
-               this->empty_rdatas_, Name("wild.*.foo.*.bar.example.org"),
+               this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+               Name("wild.*.foo.*.bar.example.org"),
                ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
 
     // Search for a wildcard name with NO_WILDCARD.  There should be no
@@ -2206,7 +2223,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
     doFindTest(*finder, isc::dns::Name("*.nonterminal.example.org"),
                RRType::NSEC(), RRType::NSEC(), this->rrttl_,
                ZoneFinder::NXDOMAIN, this->expected_rdatas_,
-               this->empty_rdatas_, Name("l.example.org"),
+               this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+               Name("l.example.org"),
                ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
 
     // On the other hand, if there's exact match for the wildcard name
@@ -2220,8 +2238,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
     doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
                this->qtype_, this->qtype_, this->rrttl_,
                ZoneFinder::SUCCESS, this->expected_rdatas_,
-               this->expected_sig_rdatas_, Name("*.wild.example.org"),
-               ZoneFinder::NO_WILDCARD);
+               this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+               Name("*.wild.example.org"), ZoneFinder::NO_WILDCARD);
 }
 
 TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
@@ -2237,7 +2255,8 @@ TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
                isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
                this->rrttl_, ZoneFinder::NXRRSET,
                this->expected_rdatas_, this->expected_sig_rdatas_,
-               Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC);
+               ZoneFinder::RESULT_NSEC_SIGNED, Name::ROOT_NAME(),
+               ZoneFinder::FIND_DNSSEC);
 }
 
 TYPED_TEST(DatabaseClientTest, wildcardNXRRSET_NSEC) {
@@ -2256,8 +2275,9 @@ TYPED_TEST(DatabaseClientTest, wildcardNXRRSET_NSEC) {
     // Note that the NSEC name should NOT be synthesized.
     doFindTest(*finder, isc::dns::Name("a.wild.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
-               this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+               this->rrttl_, ZoneFinder::NXRRSET,
                this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_WILDCARD | ZoneFinder::RESULT_NSEC_SIGNED,
                Name("*.wild.example.org"), ZoneFinder::FIND_DNSSEC);
 }
 
@@ -2273,7 +2293,8 @@ TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
                isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
                this->rrttl_, ZoneFinder::NXDOMAIN,
                this->expected_rdatas_, this->expected_sig_rdatas_,
-               Name("www.example.org."), ZoneFinder::FIND_DNSSEC);
+               ZoneFinder::RESULT_NSEC_SIGNED, Name("www.example.org."),
+               ZoneFinder::FIND_DNSSEC);
     this->expected_rdatas_.clear();
     this->expected_rdatas_.push_back("acnamesig1.example.org. NS A NSEC RRSIG");
     // This tests it works correctly in apex (there was a bug, where a check
@@ -2282,7 +2303,8 @@ TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
                isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
                this->rrttl_, ZoneFinder::NXDOMAIN,
                this->expected_rdatas_, this->expected_sig_rdatas_,
-               Name("example.org."), ZoneFinder::FIND_DNSSEC);
+               ZoneFinder::RESULT_NSEC_SIGNED, Name("example.org."),
+               ZoneFinder::FIND_DNSSEC);
 
     // Check that if the DB doesn't support it, the exception from there
     // is not propagated and it only does not include the NSEC
@@ -2294,8 +2316,9 @@ TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
                                isc::dns::RRType::TXT(),
                                isc::dns::RRType::NSEC(), this->rrttl_,
                                ZoneFinder::NXDOMAIN, this->empty_rdatas_,
-                               this->empty_rdatas_, Name::ROOT_NAME(),
-                               ZoneFinder::FIND_DNSSEC));
+                               this->empty_rdatas_,
+                               ZoneFinder::RESULT_DEFAULT,
+                               Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC));
 }
 
 TYPED_TEST(DatabaseClientTest, emptyNonterminalNSEC) {
@@ -2305,9 +2328,10 @@ TYPED_TEST(DatabaseClientTest, emptyNonterminalNSEC) {
     this->expected_rdatas_.push_back("empty.nonterminal.example.org. NSEC");
     doFindTest(*finder, isc::dns::Name("nonterminal.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(), this->rrttl_,
-               ZoneFinder::NXRRSET,
-               this->expected_rdatas_, this->expected_sig_rdatas_,
-               Name("l.example.org."), ZoneFinder::FIND_DNSSEC);
+               ZoneFinder::NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_NSEC_SIGNED, Name("l.example.org."),
+               ZoneFinder::FIND_DNSSEC);
 
     // Check that if the DB doesn't support it, the exception from there
     // is not propagated and it only does not include the NSEC
@@ -2320,6 +2344,7 @@ TYPED_TEST(DatabaseClientTest, emptyNonterminalNSEC) {
                                isc::dns::RRType::NSEC(),
                                this->rrttl_, ZoneFinder::NXRRSET,
                                this->empty_rdatas_, this->empty_rdatas_,
+                               ZoneFinder::RESULT_DEFAULT,
                                Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC));
 }
 
@@ -2387,9 +2412,13 @@ TYPED_TEST(DatabaseClientTest, getAll) {
 
     // And on wildcard. Check the signatures as well.
     target.clear();
-    EXPECT_EQ(ZoneFinder::WILDCARD,
-              finder->findAll(isc::dns::Name("a.wild.example.org"),
-                              target, ZoneFinder::FIND_DNSSEC).code);
+    const ZoneFinder::FindResult result =
+        finder->findAll(Name("a.wild.example.org"), target,
+                        ZoneFinder::FIND_DNSSEC);
+    EXPECT_EQ(ZoneFinder::SUCCESS, result.code);
+    EXPECT_TRUE(result.isWildcard());
+    EXPECT_TRUE(result.isNSECSigned());
+    EXPECT_FALSE(result.isNSEC3Signed());
     ASSERT_EQ(2, target.size());
     a_idx = target[1]->getType() == RRType::A();
     EXPECT_EQ(RRType::A(), target[a_idx]->getType());

+ 102 - 92
src/lib/datasrc/zone.h

@@ -58,88 +58,15 @@ public:
     /// Note: the codes are tentative.  We may need more, or we may find
     /// some of them unnecessary as we implement more details.
     ///
-    /// Some are synonyms of others in terms of RCODE returned to user.
-    /// But they help the logic to decide if it should ask for a NSEC
-    /// that covers something or not (for example, in case of NXRRSET,
-    /// the directly returned NSEC is sufficient, but with wildcard one,
-    /// we need to add one proving there's no exact match and this is
-    /// actually the best wildcard we have). Data sources that don't
-    /// support DNSSEC don't need to distinguish them.
-    ///
-    /// In case of CNAME, if the CNAME is a wildcard (i.e., its owner name
-    /// starts with the label "*"), WILDCARD_CNAME will be returned instead
-    /// of CNAME.
-    ///
-    /// In case of NXDOMAIN, the returned NSEC covers the queried domain
-    /// that proves that the query name does not exist in the zone.  Note that
-    /// this does not necessarily prove it doesn't even match a wildcard
-    /// (even if the result of NXDOMAIN can only happen when there's no
-    /// matching wildcard either).  It is caller's responsibility to provide
-    /// a proof that there is no matching wildcard if that proof is necessary.
-    ///
-    /// Various variants of "no data" cases are complicated, when involves
-    /// DNSSEC and wildcard processing.  Referring to Section 3.1.3 of
-    /// RFC4035, we need to consider the following cases:
-    /// -# (Normal) no data: there is a matching non-wildcard name with a
-    ///    different RR type.  This is the "No Data" case of the RFC.
-    /// -# (Normal) empty non terminal: there is no matching (exact or
-    ///    wildcard) name, but there is a subdomain with an RR of the query
-    ///    name.  This is one case of "Name Error" of the RFC.
-    /// -# Wildcard empty non terminal: similar to 2, but the empty name
-    ///    is a wildcard, and matches the query name by wildcard expansion.
-    ///    This is a special case of "Name Error" of the RFC.
-    /// -# Wildcard no data: there is no exact match name, but there is a
-    ///    wildcard name that matches the query name with a different type
-    ///    of RR.  This is the "Wildcard No Data" case of the RFC.
-    ///
-    /// In any case, \c find() will result in \c NXRRSET with no RRset
-    /// unless the \c FIND_DNSSEC option is specified.  The rest of the
-    /// discussion only applies to the case where this option is specified.
-    ///
-    /// In case 1, \c find() will result in NXRRSET, and return NSEC of the
-    /// matching name.
-    ///
-    /// In case 2, \c find() will result in NXRRSET, and return NSEC for the
-    /// interval where the empty nonterminal lives. The end of the interval
-    /// is the subdomain causing existence of the empty nonterminal (if
-    /// there's sub.x.example.com, and no record in x.example.com, then
-    /// x.example.com exists implicitly - is the empty nonterminal and
-    /// sub.x.example.com is the subdomain causing it).  Note that this NSEC
-    /// proves not only the existence of empty non terminal name but also
-    /// the non existence of possibly matching wildcard name, because
-    /// there can be no better wildcard match than the exact matching empty
-    /// name.
-    ///
-    /// In case 3, \c find() will result in WILDCARD_NXRRSET, and return NSEC
-    /// for the interval where the wildcard empty nonterminal lives.
-    /// Cases 2 and 3 are especially complicated and confusing.  See the
-    /// examples below.
-    ///
-    /// In case 4, \c find() will result in WILDCARD_NXRRSET, and return
-    /// NSEC of the matching wildcard name.
-    ///
-    /// Examples: if zone "example.com" has the following record:
-    /// \code
-    /// a.example.com. NSEC a.b.example.com.
-    /// \endcode
-    /// a call to \c find() for "b.example.com." with the FIND_DNSSEC option
-    /// will result in NXRRSET, and this NSEC will be returned.
-    /// Likewise, if zone "example.org" has the following record,
-    /// \code
-    /// a.example.org. NSEC x.*.b.example.org.
-    /// \endcode
-    /// a call to \c find() for "y.b.example.org" with FIND_DNSSEC will
-    /// result in NXRRSET_NXRRSET, and this NSEC will be returned.
+    /// See the description of \c find() for further details of how
+    /// these results should be interpreted.
     enum Result {
         SUCCESS,                ///< An exact match is found.
         DELEGATION,             ///< The search encounters a zone cut.
         NXDOMAIN, ///< There is no domain name that matches the search name
         NXRRSET,  ///< There is a matching name but no RRset of the search type
         CNAME,    ///< The search encounters and returns a CNAME RR
-        DNAME,    ///< The search encounters and returns a DNAME RR
-        WILDCARD, ///< Succes by wildcard match, for DNSSEC
-        WILDCARD_CNAME, ///< CNAME on wildcard, search returns CNAME, for DNSSEC
-        WILDCARD_NXRRSET ///< NXRRSET on wildcard, for DNSSEC
+        DNAME    ///< The search encounters and returns a DNAME RR
     };
 
     /// Special attribute flags on the result of the \c find() method
@@ -199,7 +126,7 @@ public:
 
         /// Return true when the underlying zone is signed with NSEC.
         ///
-        /// The \c find() implementation allow this to return false if
+        /// The \c find() implementation allows this to return false if
         /// \c FIND_DNSSEC isn't specified regardless of whether the zone
         /// is signed or which of NSEC/NSEC3 is used.
         ///
@@ -212,7 +139,7 @@ public:
 
         /// Return true when the underlying zone is signed with NSEC3.
         ///
-        /// The \c find() implementation allow this to return false if
+        /// The \c find() implementation allows this to return false if
         /// \c FIND_DNSSEC isn't specified regardless of whether the zone
         /// is signed or which of NSEC/NSEC3 is used.
         bool isNSEC3Signed() const {
@@ -277,17 +204,10 @@ public:
     ///
     /// - If the search name belongs under a zone cut, it returns the code
     ///   of \c DELEGATION and the NS RRset at the zone cut.
-    /// - If there is no matching name, it returns the code of \c NXDOMAIN,
-    ///   and, if DNSSEC is requested, the NSEC RRset that proves the
-    ///   non-existence if the zone is signed with NSEC.
+    /// - If there is no matching name, it returns the code of \c NXDOMAIN.
     /// - If there is a matching name but no RRset of the search type, it
-    ///   returns the code of \c NXRRSET, and, if DNSSEC is required,
-    ///   the NSEC RRset for that name if the zone is signed with NSEC.
-    /// - If there is no matching name but there is a matching wild card name,
-    ///   but it doesn't have a requested type of RR, and if DNSSEC is
-    ///   required, then it returns the code of \c WILDCARD_NXRRSET.
-    ///   If the zone is signed with NSEC, it returns corresponding NSEC
-    ///   (see the description of \c Result).
+    ///   returns the code of \c NXRRSET.  This case includes the search name
+    ///   matches an empty node of the zone.
     /// - If there is a CNAME RR of the searched name but there is no
     ///   RR of the searched type of the name (so this type is different from
     ///   CNAME), it returns the code of \c CNAME and that CNAME RR.
@@ -296,6 +216,16 @@ public:
     /// - If the search name matches a delegation point of DNAME, it returns
     ///   the code of \c DNAME and that DNAME RR.
     ///
+    /// No RRset will be returned in the \c NXDOMAIN and \c NXRRSET cases
+    /// (\c rrset member of \c FindResult will be NULL), unless DNSSEC data
+    /// are required.  See below for the cases with DNSSEC.
+    ///
+    /// The returned \c FindResult object can also provide supplemental
+    /// information about the search result via its methods returning a
+    /// boolean value.  Such information may be useful for the caller if
+    /// the caller wants to collect additional DNSSEC proofs based on the
+    /// search result.
+    ///
     /// The \c options parameter specifies customized behavior of the search.
     /// Their semantics is as follows (they are or bit-field):
     ///
@@ -325,6 +255,86 @@ public:
     /// future version.  In any case applications shouldn't call this method
     /// for an out-of-zone name.
     ///
+    /// <b>DNSSEC considerations:</b>
+    /// The result when DNSSEC data are required can be very complicated,
+    /// especially if it involves negative result or wildcard match.
+    /// Specifically, if an application calls this method for DNS query
+    /// processing with DNSSEC data, and if the search result code is
+    /// either \c NXDOMAIN or \c NXRRRSET, and/or \c isWildcard() returns
+    /// true, then the application will need to find additional NSEC or
+    /// NSEC3 records for supplemental proofs.  This method helps the
+    /// application for such post search processing.
+    ///
+    /// First, it tells the application whether the zone is signed with
+    /// NSEC or NSEC3 via the \c isNSEC(3)Signed() method.  Any sanely signed
+    /// zone should be signed with either (and only one) of these two types
+    /// of RRs; however, the application should expect that the zone could
+    /// be broken and these methods could both return false.  But this method
+    /// should ensure that not both of these methods return true.
+    ///
+    /// In case it's signed with NSEC3, there is no further information
+    /// returned from this method.
+    ///
+    /// In case it's signed with NSEC, this method will possibly return
+    /// a related NSEC RRset in the \c rrset member of \c FindResult.
+    /// What kind of NSEC is returned depends on the result code
+    /// (\c NXDOMAIN or \c NXRRSET) and on whether it's a wildcard match:
+    ///
+    /// - In case of NXDOMAIN, the returned NSEC covers the queried domain
+    ///   that proves that the query name does not exist in the zone.  Note
+    ///   that this does not necessarily prove it doesn't even match a
+    ///   wildcard (even if the result of NXDOMAIN can only happen when
+    ///   there's no matching wildcard either).  It is caller's
+    ///   responsibility to provide a proof that there is no matching
+    ///   wildcard if that proof is necessary.
+    /// - In case of NXRRSET, we need to consider the following cases
+    ///   referring to Section 3.1.3 of RFC4035:
+    ///
+    /// -# (Normal) no data: there is a matching non-wildcard name with a
+    ///    different RR type.  This is the "No Data" case of the RFC.
+    /// -# (Normal) empty non terminal: there is no matching (exact or
+    ///    wildcard) name, but there is a subdomain with an RR of the query
+    ///    name.  This is one case of "Name Error" of the RFC.
+    /// -# Wildcard empty non terminal: similar to 2a, but the empty name
+    ///    is a wildcard, and matches the query name by wildcard expansion.
+    ///    This is a special case of "Name Error" of the RFC.
+    /// -# Wildcard no data: there is no exact match name, but there is a
+    ///    wildcard name that matches the query name with a different type
+    ///    of RR.  This is the "Wildcard No Data" case of the RFC.
+    ///
+    /// In case 1, \c find() returns NSEC of the matching name.
+    ///
+    /// In case 2, \c find() will return NSEC for the interval where the
+    /// empty nonterminal lives. The end of the interval is the subdomain
+    /// causing existence of the empty nonterminal (if there's
+    /// sub.x.example.com, and no record in x.example.com, then
+    /// x.example.com exists implicitly - is the empty nonterminal and
+    /// sub.x.example.com is the subdomain causing it).  Note that this NSEC
+    /// proves not only the existence of empty non terminal name but also
+    /// the non existence of possibly matching wildcard name, because
+    /// there can be no better wildcard match than the exact matching empty
+    /// name.
+    ///
+    /// In case 3, \c find() will return NSEC for the interval where the
+    /// wildcard empty nonterminal lives.   Cases 2 and 3 are especially
+    /// complicated and confusing.  See the examples below.
+    ///
+    /// In case 4, \c find() will return NSEC of the matching wildcard name.
+    ///
+    /// Examples: if zone "example.com" has the following record:
+    /// \code
+    /// a.example.com. NSEC a.b.example.com.
+    /// \endcode
+    /// a call to \c find() for "b.example.com." with the FIND_DNSSEC option
+    /// will result in NXRRSET, and this NSEC will be returned.
+    /// Likewise, if zone "example.org" has the following record,
+    /// \code
+    /// a.example.org. NSEC x.*.b.example.org.
+    /// \endcode
+    /// a call to \c find() for "y.b.example.org" with FIND_DNSSEC will
+    /// result in NXRRSET and this NSEC; \c isWildcard() on the returned
+    /// \c FindResult object will return true.
+    ///
     /// \exception std::bad_alloc Memory allocation such as for constructing
     ///  the resulting RRset fails
     /// \exception DataSourceError Derived class specific exception, e.g.
@@ -349,12 +359,12 @@ public:
     ///
     /// This function works almost exactly in the same way as the find one. The
     /// only difference is, when the lookup is successful (eg. the code is
-    /// SUCCESS or WILDCARD), all the RRsets residing in the named node are
+    /// SUCCESS), all the RRsets residing in the named node are
     /// copied into the \c target parameter and the rrset member of the result
     /// is NULL. All the other (unsuccessful) cases are handled the same,
-    /// including returning delegations, NSEC/NSEC3 proofs, etc. The options
-    /// parameter works the same way and it should conform to the same exception
-    /// restrictions.
+    /// including returning delegations, NSEC/NSEC3 availability and NSEC
+    /// proofs, wildcard information etc. The options parameter works the
+    /// same way and it should conform to the same exception restrictions.
     ///
     /// \param name \see find, parameter name
     /// \param target the successfull result is returned through this

+ 9 - 6
src/lib/python/isc/datasrc/datasrc.cc

@@ -129,12 +129,6 @@ initModulePart_ZoneFinder(PyObject* mod) {
                              Py_BuildValue("I", ZoneFinder::CNAME));
         installClassVariable(zonefinder_type, "DNAME",
                              Py_BuildValue("I", ZoneFinder::DNAME));
-        installClassVariable(zonefinder_type, "WILDCARD",
-                             Py_BuildValue("I", ZoneFinder::WILDCARD));
-        installClassVariable(zonefinder_type, "WILDCARD_NXRRSET",
-                             Py_BuildValue("I", ZoneFinder::WILDCARD_NXRRSET));
-        installClassVariable(zonefinder_type, "WILDCARD_CNAME",
-                             Py_BuildValue("I", ZoneFinder::WILDCARD_CNAME));
 
         installClassVariable(zonefinder_type, "FIND_DEFAULT",
                              Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
@@ -144,6 +138,15 @@ initModulePart_ZoneFinder(PyObject* mod) {
                              Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
         installClassVariable(zonefinder_type, "NO_WILDCARD",
                              Py_BuildValue("I", ZoneFinder::NO_WILDCARD));
+
+        installClassVariable(zonefinder_type, "RESULT_WILDCARD",
+                             Py_BuildValue("I", ZoneFinder::RESULT_WILDCARD));
+        installClassVariable(zonefinder_type, "RESULT_NSEC_SIGNED",
+                             Py_BuildValue("I",
+                                           ZoneFinder::RESULT_NSEC_SIGNED));
+        installClassVariable(zonefinder_type, "RESULT_NSEC3_SIGNED",
+                             Py_BuildValue("I",
+                                           ZoneFinder::RESULT_NSEC3_SIGNED));
     } catch (const std::exception& ex) {
         const std::string ex_what =
             "Unexpected failure in ZoneFinder initialization: " +

+ 118 - 19
src/lib/python/isc/datasrc/finder_inc.cc

@@ -47,7 +47,7 @@ Return the RR class of the zone.\n\
 // - NULL->None
 // - exceptions
 const char* const ZoneFinder_find_doc = "\
-find(name, type, options=FIND_DEFAULT) -> (integer, RRset)\n\
+find(name, type, options=FIND_DEFAULT) -> (integer, RRset, integer)\n\
 \n\
 Search the zone for a given pair of domain name and RR type.\n\
 \n\
@@ -58,12 +58,10 @@ answer for the search key. Specifically,\n\
 \n\
 - If the search name belongs under a zone cut, it returns the code of\n\
   DELEGATION and the NS RRset at the zone cut.\n\
-- If there is no matching name, it returns the code of NXDOMAIN, and,\n\
-  if DNSSEC is requested, the NSEC RRset that proves the non-\n\
-  existence.\n\
+- If there is no matching name, it returns the code of NXDOMAIN.\n\
 - If there is a matching name but no RRset of the search type, it\n\
-  returns the code of NXRRSET, and, if DNSSEC is required, the NSEC\n\
-  RRset for that name.\n\
+  returns the code of NXRRSET. This case includes the search name\n\
+  matches an empty node of the zone.\n\
 - If there is a CNAME RR of the searched name but there is no RR of\n\
   the searched type of the name (so this type is different from\n\
   CNAME), it returns the code of CNAME and that CNAME RR. Note that if\n\
@@ -72,6 +70,16 @@ answer for the search key. Specifically,\n\
 - If the search name matches a delegation point of DNAME, it returns\n\
   the code of DNAME and that DNAME RR.\n\
 \n\
+No RRset will be returned in the NXDOMAIN and NXRRSET cases (the\n\
+second element of the tuple will be None), unless DNSSEC data are\n\
+required. See below for the cases with DNSSEC.\n\
+\n\
+The third element of the returned tuple provides supplemental\n\
+information about the search result in the form of a bitmask (called\n\
+\"flags\"). Such information may be useful for the caller if the\n\
+caller wants to collect additional DNSSEC proofs based on the search\n\
+result.\n\
+\n\
 The options parameter specifies customized behavior of the search.\n\
 Their semantics is as follows (they are or bit-field):\n\
 \n\
@@ -96,6 +104,91 @@ should be equal to or a subdomain of the zone origin. Otherwise this\n\
 method will return NXDOMAIN with an empty RRset. But such a case\n\
 should rather be considered a caller's bug.\n\
 \n\
+Note: For this reason it's probably better to throw an exception than\n\
+returning NXDOMAIN. This point should be revisited in a near future\n\
+version. In any case applications shouldn't call this method for an\n\
+out-of-zone name.\n\
+\n\
+DNSSEC considerations: The result when DNSSEC data are required can be\n\
+very complicated, especially if it involves negative result or\n\
+wildcard match. Specifically, if an application calls this method for\n\
+DNS query processing with DNSSEC data, and if the search result code\n\
+is either NXDOMAIN or NXRRRSET, and/or RESULT_WILDCARD\n\
+flag is set in the returned flags value,\n\
+then the application will need to find additional NSEC or NSEC3\n\
+records for supplemental proofs. This method helps the application for\n\
+such post search processing.\n\
+\n\
+First, it tells the application whether the zone is signed with NSEC\n\
+or NSEC3 via the RESULT_NSEC_SIGNED and RESULT_NSEC3_SIGNED flags\n\
+in the returned flags value. Any sanely signed zone\n\
+should be signed with either (and only one) of these two types of RRs;\n\
+however, the application should expect that the zone could be broken\n\
+and these methods could both return false. But this method should\n\
+ensure that not both of these methods return true.\n\
+\n\
+In case it's signed with NSEC3, there is no further information\n\
+returned from this method.\n\
+\n\
+In case it's signed with NSEC, this method will possibly return a\n\
+related NSEC RRset in the second element of the tuple. What kind of\n\
+NSEC is returned depends on the result code (NXDOMAIN or NXRRSET) and\n\
+on whether it's a wildcard match:\n\
+\n\
+- In case of NXDOMAIN, the returned NSEC covers the queried domain\n\
+  that proves that the query name does not exist in the zone. Note\n\
+  that this does not necessarily prove it doesn't even match a\n\
+  wildcard (even if the result of NXDOMAIN can only happen when\n\
+  there's no matching wildcard either). It is caller's responsibility\n\
+  to provide a proof that there is no matching wildcard if that proof\n\
+  is necessary.\n\
+- In case of NXRRSET, we need to consider the following cases\n\
+  referring to Section 3.1.3 of RFC4035:\n\
+\n\
+1. (Normal) no data: there is a matching non-wildcard name with a\n\
+   different RR type. This is the \"No Data\" case of the RFC.\n\
+2. (Normal) empty non terminal: there is no matching (exact or\n\
+   wildcard) name, but there is a subdomain with an RR of the query\n\
+   name. This is one case of \"Name Error\" of the RFC.\n\
+3. Wildcard empty non terminal: similar to 2, but the empty name is\n\
+   a wildcard, and matches the query name by wildcard expansion. This\n\
+   is a special case of \"Name Error\" of the RFC.\n\
+4. Wildcard no data: there is no exact match name, but there is a\n\
+   wildcard name that matches the query name with a different type of RR.\n\
+   This is the \"Wildcard No Data\" case of the RFC.\n\
+\n\
+In case 1, find() returns NSEC of the matching name.\n\
+\n\
+In case 2, find() will return NSEC for the interval where the empty\n\
+nonterminal lives. The end of the interval is the subdomain causing\n\
+existence of the empty nonterminal (if there's sub.x.example.com, and\n\
+no record in x.example.com, then x.example.com exists implicitly - is\n\
+the empty nonterminal and sub.x.example.com is the subdomain causing\n\
+it). Note that this NSEC proves not only the existence of empty non\n\
+terminal name but also the non existence of possibly matching wildcard\n\
+name, because there can be no better wildcard match than the exact\n\
+matching empty name.\n\
+\n\
+In case 3, find() will return NSEC for the interval where the wildcard\n\
+empty nonterminal lives. Cases 2 and 3 are especially complicated and\n\
+confusing. See the examples below.\n\
+\n\
+In case 4, find() will return NSEC of the matching wildcard name.\n\
+\n\
+Examples: if zone \"example.com\" has the following record:\n\
+\n\
+a.example.com. NSEC a.b.example.com.\n\
+\n\
+a call to  find() for \"b.example.com.\" with the FIND_DNSSEC option\n\
+will result in NXRRSET, and this NSEC will be returned.\n\
+Likewise, if zone \"example.org\" has the following record,\n\
+\n\
+a.example.org. NSEC x.*.b.example.org.\n\
+\n\
+a call to  find() for \"y.b.example.org\" with FIND_DNSSEC will\n\
+result in NXRRSET and this NSEC;  RESULT_WILDCARD bit is set in the\n\
+returned flags.\n\
+\n\
 This method raises an isc.datasrc.Error exception if there is an\n\
 internal error in the datasource.\n\
 \n\
@@ -104,28 +197,34 @@ Parameters:\n\
   type       The RR type to be searched for.\n\
   options    The search options.\n\
 \n\
-Return Value(s): A tuple of a result code (integer) and an RRset object\n\
-enclosing the search result (see above).\n\
+Return Value(s): A tuple of a result code (integer), an RRset object\n\
+and flags bitmask (integer).\n\
 ";
 
-const char* const ZoneFinder_find_all_doc = "\
-find_all(isc.dns.Name, options=FIND_DEFAULT) -> (integer, RRset) | (integer, [RRset])\
+const char* const ZoneFinder_findAll_doc = "\
+find_all(isc.dns.Name, options=FIND_DEFAULT) ->\n\
+   (integer, RRset, integer) | (integer, [RRset], integer)\
 \n\
-This acts mostly the same as the find method. The main difference is,\n\
-when the lookup is successful (eg. the first part of the result is either\n\
-SUCCESS or WILDCARD), the second part is list of all RRsets in the given name\n\
-instead of a single RRset as in case of find.\n\
+Finds all RRsets in the given name.\n\
 \n\
-This method raises an isc.datasrc.Error exception if there is an\n\
-internal error in the datasource.\n\
+This function works almost exactly in the same way as the find one.\n\
+The only difference is, when the lookup is successful (eg. the code is\n\
+SUCCESS), all the RRsets residing in the named node are returned in the\n\
+second element of the returned tuple. All\n\
+the other (unsuccessful) cases are handled the same, including\n\
+returning delegations, NSEC/NSEC3 availability and NSEC proofs,\n\
+wildcard information etc. The options parameter works the same way and\n\
+it should conform to the same exception restrictions.\n\
 \n\
 Parameters:\n\
   name       The domain name to be searched for.\n\
   options    The search options.\n\
 \n\
-Return Value(s): A tuple of a result code (integer) and an either RRset object,\n\
-for cases where the result is some kind of delegation, CNAME or similar, or list\n\
-of RRset objects, containing all the results.\n\
+Return Value(s): A tuple of a result code (integer), an either\n\
+RRset object or a list of RRsets, and flags (integer).\n\
+In the second element a single RRset is returned for cases where the\n\
+result is some kind of delegation, CNAME or similar; in other cases\n\
+a list of RRsets is returned, containing all the results.\n\
 ";
 
 const char* const ZoneFinder_find_previous_name_doc = "\

+ 29 - 6
src/lib/python/isc/datasrc/finder_python.cc

@@ -45,6 +45,23 @@ using namespace isc::dns::python;
 using namespace isc::datasrc;
 using namespace isc::datasrc::python;
 
+namespace  {
+ZoneFinder::FindResultFlags
+getFindResultFlags(const ZoneFinder::FindResult& result) {
+    ZoneFinder::FindResultFlags result_flags = ZoneFinder::RESULT_DEFAULT;
+    if (result.isWildcard()) {
+        result_flags = result_flags | ZoneFinder::RESULT_WILDCARD;
+    }
+    if (result.isNSECSigned()) {
+        result_flags = result_flags | ZoneFinder::RESULT_NSEC_SIGNED;
+    }
+    if (result.isNSEC3Signed()) {
+        result_flags = result_flags | ZoneFinder::RESULT_NSEC3_SIGNED;
+    }
+    return (result_flags);
+}
+}
+
 namespace isc_datasrc_internal {
 // This is the shared code for the find() call in the finder and the updater
 // Is is intentionally not available through any header, nor at our standard
@@ -71,11 +88,14 @@ PyObject* ZoneFinder_helper(ZoneFinder* finder, PyObject* args) {
                              options));
             const ZoneFinder::Result r = find_result.code;
             isc::dns::ConstRRsetPtr rrsp = find_result.rrset;
+            ZoneFinder::FindResultFlags result_flags =
+                getFindResultFlags(find_result);
             if (rrsp) {
                 // Use N instead of O so the refcount isn't increased twice
-                return (Py_BuildValue("IN", r, createRRsetObject(*rrsp)));
+                return (Py_BuildValue("INI", r, createRRsetObject(*rrsp),
+                                      result_flags));
             } else {
-                return (Py_BuildValue("IO", r, Py_None));
+                return (Py_BuildValue("IOI", r, Py_None, result_flags));
             }
         } catch (const DataSourceError& dse) {
             PyErr_SetString(getDataSourceException("Error"), dse.what());
@@ -111,7 +131,9 @@ PyObject* ZoneFinder_helper_all(ZoneFinder* finder, PyObject* args) {
                 finder->findAll(PyName_ToName(name), target, options));
             const ZoneFinder::Result r = find_result.code;
             isc::dns::ConstRRsetPtr rrsp = find_result.rrset;
-            if (r == ZoneFinder::SUCCESS || r == ZoneFinder::WILDCARD) {
+            ZoneFinder::FindResultFlags result_flags =
+                getFindResultFlags(find_result);
+            if (r == ZoneFinder::SUCCESS) {
                 // Copy all the RRsets to the result list
                 PyObjectContainer list_container(PyList_New(target.size()));
                 for (size_t i(0); i < target.size(); ++i) {
@@ -126,9 +148,10 @@ PyObject* ZoneFinder_helper_all(ZoneFinder* finder, PyObject* args) {
             } else {
                 if (rrsp) {
                     // Use N instead of O so the refcount isn't increased twice
-                    return (Py_BuildValue("IN", r, createRRsetObject(*rrsp)));
+                    return (Py_BuildValue("INI", r, createRRsetObject(*rrsp),
+                                          result_flags));
                 } else {
-                    return (Py_BuildValue("IO", r, Py_None));
+                    return (Py_BuildValue("IOI", r, Py_None, result_flags));
                 }
             }
         } catch (const DataSourceError& dse) {
@@ -263,7 +286,7 @@ PyMethodDef ZoneFinder_methods[] = {
        ZoneFinder_getOrigin_doc },
     { "get_class", ZoneFinder_getClass, METH_NOARGS, ZoneFinder_getClass_doc },
     { "find", ZoneFinder_find, METH_VARARGS, ZoneFinder_find_doc },
-    { "find_all", ZoneFinder_find_all, METH_VARARGS, ZoneFinder_find_all_doc },
+    { "find_all", ZoneFinder_find_all, METH_VARARGS, ZoneFinder_findAll_doc },
     { "find_previous_name", ZoneFinder_findPreviousName, METH_VARARGS,
       ZoneFinder_find_previous_name_doc },
     { NULL, NULL, 0, NULL }

+ 75 - 68
src/lib/python/isc/datasrc/tests/datasrc_test.py

@@ -63,8 +63,8 @@ def test_findall_common(self, tested):
     object.
     """
     # Some "failure" responses
-    result, rrset = tested.find_all(isc.dns.Name("www.sql1.example.com"),
-                                    ZoneFinder.FIND_DEFAULT)
+    result, rrset, _ = tested.find_all(isc.dns.Name("www.sql1.example.com"),
+                                       ZoneFinder.FIND_DEFAULT)
     self.assertEqual(ZoneFinder.DELEGATION, result)
     expected = RRset(Name('sql1.example.com.'), RRClass.IN(), RRType.NS(),
                      RRTTL(3600))
@@ -76,8 +76,8 @@ def test_findall_common(self, tested):
                              'dns03.example.com.'))
     self.assertTrue(rrsets_equal(expected, rrset))
 
-    result, rrset = tested.find_all(isc.dns.Name("nxdomain.example.com"),
-                                     ZoneFinder.FIND_DEFAULT)
+    result, rrset, _ = tested.find_all(isc.dns.Name("nxdomain.example.com"),
+                                       ZoneFinder.FIND_DEFAULT)
     self.assertEqual(ZoneFinder.NXDOMAIN, result)
     self.assertIsNone(None, rrset)
 
@@ -319,10 +319,15 @@ class DataSrcClient(unittest.TestCase):
         self.assertNotEqual(ZoneFinder.NXDOMAIN, ZoneFinder.NXRRSET)
         self.assertNotEqual(ZoneFinder.NXRRSET, ZoneFinder.CNAME)
         self.assertNotEqual(ZoneFinder.CNAME, ZoneFinder.DNAME)
-        self.assertNotEqual(ZoneFinder.DNAME, ZoneFinder.WILDCARD)
-        self.assertNotEqual(ZoneFinder.WILDCARD, ZoneFinder.WILDCARD_CNAME)
-        self.assertNotEqual(ZoneFinder.WILDCARD_CNAME,
-                            ZoneFinder.WILDCARD_NXRRSET)
+
+    def test_findresultflags(self):
+        '''A simple test just confirming the flags are all different.'''
+        self.assertNotEqual(ZoneFinder.RESULT_WILDCARD,
+                            ZoneFinder.RESULT_NSEC_SIGNED)
+        self.assertNotEqual(ZoneFinder.RESULT_NSEC_SIGNED,
+                            ZoneFinder.RESULT_NSEC3_SIGNED)
+        self.assertNotEqual(ZoneFinder.RESULT_NSEC3_SIGNED,
+                            ZoneFinder.RESULT_WILDCARD)
 
     def test_findall(self):
         """
@@ -345,66 +350,68 @@ class DataSrcClient(unittest.TestCase):
         self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
         self.assertEqual("example.com.", finder.get_origin().to_text())
 
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
 
         # Check the optional parameters are optional
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A())
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A())
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
 
-        result, rrset = finder.find(isc.dns.Name("www.sql1.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.sql1.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.DELEGATION, result)
         self.assertEqual("sql1.example.com. 3600 IN NS dns01.example.com.\n" +
                          "sql1.example.com. 3600 IN NS dns02.example.com.\n" +
                          "sql1.example.com. 3600 IN NS dns03.example.com.\n",
                          rrset.to_text())
 
-        result, rrset = finder.find(isc.dns.Name("doesnotexist.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("doesnotexist.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.NXDOMAIN, result)
         self.assertEqual(None, rrset)
 
-        result, rrset = finder.find(isc.dns.Name("www.some.other.domain"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.some.other.domain"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.NXDOMAIN, result)
         self.assertEqual(None, rrset)
 
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.TXT(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.TXT(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.NXRRSET, result)
         self.assertEqual(None, rrset)
 
-        result, rrset = finder.find(isc.dns.Name("cname-ext.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("cname-ext.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.CNAME, result)
         self.assertEqual(
             "cname-ext.example.com. 3600 IN CNAME www.sql1.example.com.\n",
             rrset.to_text())
 
-        result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
-        self.assertEqual(finder.WILDCARD, result)
+        result, rrset, flags = \
+            finder.find(isc.dns.Name("foo.wild.example.com"),
+                        isc.dns.RRType.A(), finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual(finder.RESULT_WILDCARD, flags)
         self.assertEqual("foo.wild.example.com. 3600 IN A 192.0.2.255\n",
                          rrset.to_text())
 
-        result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
-                                    isc.dns.RRType.TXT(),
-                                    finder.FIND_DEFAULT)
-        self.assertEqual(finder.WILDCARD_NXRRSET, result)
+        result, rrset, _ = finder.find(isc.dns.Name("foo.wild.example.com"),
+                                       isc.dns.RRType.TXT(),
+                                       finder.FIND_DEFAULT)
+        self.assertEqual(finder.NXRRSET, result)
+        self.assertTrue(finder.RESULT_WILDCARD, flags)
         self.assertEqual(None, rrset)
 
         self.assertRaises(TypeError, finder.find,
@@ -463,16 +470,16 @@ class DataSrcUpdater(unittest.TestCase):
         # Check basic behavior of updater's finder
         dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
         updater = dsc.get_updater(isc.dns.Name("example.com"), False)
-        result, rrset = updater.find(isc.dns.Name("www.example.com"),
-                                     isc.dns.RRType.A(),
-                                     ZoneFinder.FIND_DEFAULT)
+        result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+                                        isc.dns.RRType.A(),
+                                        ZoneFinder.FIND_DEFAULT)
         self.assertEqual(ZoneFinder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
 
         # Omit optional parameters
-        result, rrset = updater.find(isc.dns.Name("www.example.com"),
-                                     isc.dns.RRType.A())
+        result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+                                        isc.dns.RRType.A())
         self.assertEqual(ZoneFinder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
@@ -487,9 +494,9 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
         self.assertEqual("example.com.", finder.get_origin().to_text())
 
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
@@ -508,15 +515,15 @@ class DataSrcUpdater(unittest.TestCase):
 
         # The record should be gone in the updater, but not in the original
         # finder (since we have not committed)
-        result, rrset = updater.find(isc.dns.Name("www.example.com"),
-                                     isc.dns.RRType.A(),
-                                     finder.FIND_DEFAULT)
+        result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+                                        isc.dns.RRType.A(),
+                                        finder.FIND_DEFAULT)
         self.assertEqual(finder.NXDOMAIN, result)
         self.assertEqual(None, rrset)
 
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
@@ -526,9 +533,9 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertRaises(isc.datasrc.Error, updater.commit)
 
         # the record should be gone now in the 'real' finder as well
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.NXDOMAIN, result)
         self.assertEqual(None, rrset)
 
@@ -540,9 +547,9 @@ class DataSrcUpdater(unittest.TestCase):
         # second commit should throw
         self.assertRaises(isc.datasrc.Error, updater.commit)
 
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
@@ -564,9 +571,9 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
         self.assertEqual("example.com.", finder.get_origin().to_text())
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
@@ -587,9 +594,9 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
         self.assertEqual("example.com.", finder.get_origin().to_text())
 
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
@@ -608,9 +615,9 @@ class DataSrcUpdater(unittest.TestCase):
 
         # The record should be gone in the updater, but not in the original
         # finder (since we have not committed)
-        result, rrset = updater.find(isc.dns.Name("www.example.com"),
-                                     isc.dns.RRType.A(),
-                                     finder.FIND_DEFAULT)
+        result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+                                        isc.dns.RRType.A(),
+                                        finder.FIND_DEFAULT)
         self.assertEqual(finder.NXDOMAIN, result)
         self.assertEqual(None, rrset)
 
@@ -618,9 +625,9 @@ class DataSrcUpdater(unittest.TestCase):
         updater = None
 
         # the record should still be available in the 'real' finder as well
-        result, rrset = finder.find(isc.dns.Name("www.example.com"),
-                                    isc.dns.RRType.A(),
-                                    finder.FIND_DEFAULT)
+        result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+                                       isc.dns.RRType.A(),
+                                       finder.FIND_DEFAULT)
         self.assertEqual(finder.SUCCESS, result)
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())

+ 1 - 1
src/lib/python/isc/datasrc/updater_python.cc

@@ -217,7 +217,7 @@ PyMethodDef ZoneUpdater_methods[] = {
       METH_NOARGS, ZoneFinder_getClass_doc },
     { "find", ZoneUpdater_find, METH_VARARGS, ZoneFinder_find_doc },
     { "find_all", ZoneUpdater_find_all, METH_VARARGS,
-      ZoneFinder_find_all_doc },
+      ZoneFinder_findAll_doc },
     { NULL, NULL, 0, NULL }
 };
 

+ 5 - 5
src/lib/python/isc/notify/notify_out.py

@@ -284,12 +284,12 @@ class NotifyOut:
                          format_zone_str(zone_name, zone_class))
             return []
 
-        result, ns_rrset = finder.find(zone_name, RRType.NS())
+        result, ns_rrset, _ = finder.find(zone_name, RRType.NS())
         if result is not finder.SUCCESS or ns_rrset is None:
             logger.warn(NOTIFY_OUT_ZONE_NO_NS,
                         format_zone_str(zone_name, zone_class))
             return []
-        result, soa_rrset = finder.find(zone_name, RRType.SOA())
+        result, soa_rrset, _ = finder.find(zone_name, RRType.SOA())
         if result is not finder.SUCCESS or soa_rrset is None or \
                 soa_rrset.get_rdata_count() != 1:
             logger.warn(NOTIFY_OUT_ZONE_BAD_SOA,
@@ -302,11 +302,11 @@ class NotifyOut:
             ns_name = Name(ns_rdata.to_text())
             if soa_mname == ns_name:
                 continue
-            result, rrset = finder.find(ns_name, RRType.A())
+            result, rrset, _ = finder.find(ns_name, RRType.A())
             if result is finder.SUCCESS and rrset is not None:
                 addrs.extend([a.to_text() for a in rrset.get_rdata()])
 
-            result, rrset = finder.find(ns_name, RRType.AAAA())
+            result, rrset, _ = finder.find(ns_name, RRType.AAAA())
             if result is finder.SUCCESS and rrset is not None:
                 addrs.extend([aaaa.to_text() for aaaa in rrset.get_rdata()])
 
@@ -500,7 +500,7 @@ class NotifyOut:
                                            zone_name.to_text() + '/' +
                                            zone_class.to_text() + ' not found')
 
-        result, soa_rrset = finder.find(zone_name, RRType.SOA())
+        result, soa_rrset, _ = finder.find(zone_name, RRType.SOA())
         if result is not finder.SUCCESS or soa_rrset is None or \
                 soa_rrset.get_rdata_count() != 1:
             raise NotifyOutDataSourceError('_get_zone_soa: Zone ' +