Browse Source

Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10

Jeremy C. Reed 13 years ago
parent
commit
7bc93774a4

+ 7 - 0
ChangeLog

@@ -1,3 +1,10 @@
+288.    [bug]       stephen
+	Fixed problem whereby the order in which component files appeared in
+	rdataclass.cc was system dependent, leading to problems on some
+	systems where data types were used before the header file in which
+	they were declared was included.
+	(Trac #1202, git 4a605525cda67bea8c43ca8b3eae6e6749797450)
+
 287.	[bug]*		jinmei
 287.	[bug]*		jinmei
 	Python script files for log messages (xxx_messages.py) should have
 	Python script files for log messages (xxx_messages.py) should have
 	been installed under the "isc" package.  This fix itself should
 	been installed under the "isc" package.  This fix itself should

+ 7 - 0
src/bin/auth/query.cc

@@ -253,6 +253,13 @@ Query::process() const {
                 // Just empty answer with SOA in authority section
                 // Just empty answer with SOA in authority section
                 putSOA(*result.zone_finder);
                 putSOA(*result.zone_finder);
                 break;
                 break;
+            default:
+                // These are new result codes (WILDCARD and WILDCARD_NXRRSET)
+                // They should not happen from the in-memory and the database
+                // backend isn't used yet.
+                // TODO: Implement before letting the database backends in
+                isc_throw(isc::NotImplemented, "Unknown result code");
+                break;
         }
         }
     }
     }
 }
 }

+ 4 - 0
src/bin/auth/tests/query_unittest.cc

@@ -141,6 +141,10 @@ public:
     // Turn this on if you want it to return RRSIGs regardless of FIND_GLUE_OK
     // Turn this on if you want it to return RRSIGs regardless of FIND_GLUE_OK
     void setIncludeRRSIGAnyway(bool on) { include_rrsig_anyway_ = on; }
     void setIncludeRRSIGAnyway(bool on) { include_rrsig_anyway_ = on; }
 
 
+    Name findPreviousName(const Name&) const {
+        isc_throw(isc::NotImplemented, "Mock doesn't support previous name");
+    }
+
 private:
 private:
     typedef map<RRType, ConstRRsetPtr> RRsetStore;
     typedef map<RRType, ConstRRsetPtr> RRsetStore;
     typedef map<Name, RRsetStore> Domains;
     typedef map<Name, RRsetStore> Domains;

+ 10 - 2
src/bin/xfrin/tests/xfrin_test.py

@@ -955,13 +955,20 @@ class TestXfrin(unittest.TestCase):
                 self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text())
                 self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text())
             else:
             else:
                 self.assertIsNone(zone_info.tsig_key)
                 self.assertIsNone(zone_info.tsig_key)
+            if 'ixfr_disabled' in zone_config and\
+               zone_config.get('ixfr_disabled'):
+                self.assertTrue(zone_info.ixfr_disabled)
+            else:
+                # if not set, should default to False
+                self.assertFalse(zone_info.ixfr_disabled)
 
 
     def test_command_handler_zones(self):
     def test_command_handler_zones(self):
         config1 = { 'transfers_in': 3,
         config1 = { 'transfers_in': 3,
                    'zones': [
                    'zones': [
                    { 'name': 'test.example.',
                    { 'name': 'test.example.',
                     'master_addr': '192.0.2.1',
                     'master_addr': '192.0.2.1',
-                    'master_port': 53
+                    'master_port': 53,
+                    'ixfr_disabled': False
                    }
                    }
                  ]}
                  ]}
         self.assertEqual(self.xfr.config_handler(config1)['result'][0], 0)
         self.assertEqual(self.xfr.config_handler(config1)['result'][0], 0)
@@ -972,7 +979,8 @@ class TestXfrin(unittest.TestCase):
                    { 'name': 'test.example.',
                    { 'name': 'test.example.',
                     'master_addr': '192.0.2.2',
                     'master_addr': '192.0.2.2',
                     'master_port': 53,
                     'master_port': 53,
-                    'tsig_key': "example.com:SFuWd/q99SzF8Yzd1QbB9g=="
+                    'tsig_key': "example.com:SFuWd/q99SzF8Yzd1QbB9g==",
+                    'ixfr_disabled': True
                    }
                    }
                  ]}
                  ]}
         self.assertEqual(self.xfr.config_handler(config2)['result'][0], 0)
         self.assertEqual(self.xfr.config_handler(config2)['result'][0], 0)

+ 11 - 0
src/bin/xfrin/xfrin.py.in

@@ -451,6 +451,7 @@ class ZoneInfo:
         self.set_master_port(config_data.get('master_port'))
         self.set_master_port(config_data.get('master_port'))
         self.set_zone_class(config_data.get('class'))
         self.set_zone_class(config_data.get('class'))
         self.set_tsig_key(config_data.get('tsig_key'))
         self.set_tsig_key(config_data.get('tsig_key'))
+        self.set_ixfr_disabled(config_data.get('ixfr_disabled'))
 
 
     def set_name(self, name_str):
     def set_name(self, name_str):
         """Set the name for this zone given a name string.
         """Set the name for this zone given a name string.
@@ -525,6 +526,16 @@ class ZoneInfo:
                 errmsg = "bad TSIG key string: " + tsig_key_str
                 errmsg = "bad TSIG key string: " + tsig_key_str
                 raise XfrinZoneInfoException(errmsg)
                 raise XfrinZoneInfoException(errmsg)
 
 
+    def set_ixfr_disabled(self, ixfr_disabled):
+        """Set ixfr_disabled. If set to False (the default), it will use
+           IXFR for incoming transfers. If set to True, it will use AXFR.
+           At this moment there is no automatic fallback"""
+        # don't care what type it is; if evaluates to true, set to True
+        if ixfr_disabled:
+            self.ixfr_disabled = True
+        else:
+            self.ixfr_disabled = False
+
     def get_master_addr_info(self):
     def get_master_addr_info(self):
         return (self.master_addr.family, socket.SOCK_STREAM,
         return (self.master_addr.family, socket.SOCK_STREAM,
                 (str(self.master_addr), self.master_port))
                 (str(self.master_addr), self.master_port))

+ 5 - 0
src/bin/xfrin/xfrin.spec

@@ -43,6 +43,11 @@
           { "item_name": "tsig_key",
           { "item_name": "tsig_key",
             "item_type": "string",
             "item_type": "string",
             "item_optional": true
             "item_optional": true
+          },
+          { "item_name": "ixfr_disabled",
+            "item_type": "boolean",
+            "item_optional": false,
+            "item_default": false
           }
           }
           ]
           ]
         }
         }

+ 1 - 0
src/lib/datasrc/Makefile.am

@@ -30,6 +30,7 @@ libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
+libdatasrc_la_LIBADD += $(SQLITE_LIBS)
 
 
 BUILT_SOURCES = datasrc_messages.h datasrc_messages.cc
 BUILT_SOURCES = datasrc_messages.h datasrc_messages.cc
 datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes
 datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes

+ 326 - 143
src/lib/datasrc/database.cc

@@ -174,105 +174,42 @@ private:
 };
 };
 }
 }
 
 
-std::pair<bool, isc::dns::RRsetPtr>
-DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
-                                 const isc::dns::RRType* type,
-                                 bool want_cname, bool want_dname,
-                                 bool want_ns,
-                                 const isc::dns::Name* construct_name)
+DatabaseClient::Finder::FoundRRsets
+DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
+                                  bool check_ns, const string* construct_name)
 {
 {
     RRsigStore sig_store;
     RRsigStore sig_store;
     bool records_found = false;
     bool records_found = false;
-    isc::dns::RRsetPtr result_rrset;
+    std::map<RRType, RRsetPtr> result;
 
 
     // Request the context
     // Request the context
     DatabaseAccessor::IteratorContextPtr
     DatabaseAccessor::IteratorContextPtr
-        context(accessor_->getRecords(name.toText(), zone_id_));
+        context(accessor_->getRecords(name, zone_id_));
     // It must not return NULL, that's a bug of the implementation
     // It must not return NULL, that's a bug of the implementation
     if (!context) {
     if (!context) {
-        isc_throw(isc::Unexpected, "Iterator context null at " +
-                  name.toText());
+        isc_throw(isc::Unexpected, "Iterator context null at " + name);
     }
     }
 
 
     std::string columns[DatabaseAccessor::COLUMN_COUNT];
     std::string columns[DatabaseAccessor::COLUMN_COUNT];
     if (construct_name == NULL) {
     if (construct_name == NULL) {
         construct_name = &name;
         construct_name = &name;
     }
     }
+
+    const Name construct_name_object(*construct_name);
+
+    bool seen_cname(false);
+    bool seen_ds(false);
+    bool seen_other(false);
+    bool seen_ns(false);
+
     while (context->getNext(columns)) {
     while (context->getNext(columns)) {
-        if (!records_found) {
-            records_found = true;
-        }
+        // The domain is not empty
+        records_found = true;
 
 
         try {
         try {
-            const isc::dns::RRType cur_type(columns[DatabaseAccessor::
-                                            TYPE_COLUMN]);
-            const isc::dns::RRTTL cur_ttl(columns[DatabaseAccessor::
-                                          TTL_COLUMN]);
-            // Ths sigtype column was an optimization for finding the
-            // relevant RRSIG RRs for a lookup. Currently this column is
-            // not used in this revised datasource implementation. We
-            // should either start using it again, or remove it from use
-            // completely (i.e. also remove it from the schema and the
-            // backend implementation).
-            // Note that because we don't use it now, we also won't notice
-            // it if the value is wrong (i.e. if the sigtype column
-            // contains an rrtype that is different from the actual value
-            // of the 'type covered' field in the RRSIG Rdata).
-            //cur_sigtype(columns[SIGTYPE_COLUMN]);
-
-            // Check for delegations before checking for the right type.
-            // This is needed to properly delegate request for the NS
-            // record itself.
-            //
-            // This happens with NS only, CNAME must be alone and DNAME
-            // is not checked in the exact queried domain.
-            if (want_ns && cur_type == isc::dns::RRType::NS()) {
-                if (result_rrset &&
-                    result_rrset->getType() != isc::dns::RRType::NS()) {
-                    isc_throw(DataSourceError, "NS found together with data"
-                              " in non-apex domain " + name.toText());
-                }
-                addOrCreate(result_rrset, *construct_name, getClass(),
-                            cur_type, cur_ttl,
-                            columns[DatabaseAccessor::RDATA_COLUMN],
-                            *accessor_);
-            } else if (type != NULL && cur_type == *type) {
-                if (result_rrset &&
-                    result_rrset->getType() == isc::dns::RRType::CNAME()) {
-                    isc_throw(DataSourceError, "CNAME found but it is not "
-                              "the only record for " + name.toText());
-                } else if (result_rrset && want_ns &&
-                           result_rrset->getType() == isc::dns::RRType::NS()) {
-                    isc_throw(DataSourceError, "NS found together with data"
-                              " in non-apex domain " + name.toText());
-                }
-                addOrCreate(result_rrset, *construct_name, getClass(),
-                            cur_type, cur_ttl,
-                            columns[DatabaseAccessor::RDATA_COLUMN],
-                            *accessor_);
-            } else if (want_cname && cur_type == isc::dns::RRType::CNAME()) {
-                // There should be no other data, so result_rrset should
-                // be empty.
-                if (result_rrset) {
-                    isc_throw(DataSourceError, "CNAME found but it is not "
-                              "the only record for " + name.toText());
-                }
-                addOrCreate(result_rrset, *construct_name, getClass(),
-                            cur_type, cur_ttl,
-                            columns[DatabaseAccessor::RDATA_COLUMN],
-                            *accessor_);
-            } else if (want_dname && cur_type == isc::dns::RRType::DNAME()) {
-                // There should be max one RR of DNAME present
-                if (result_rrset &&
-                    result_rrset->getType() == isc::dns::RRType::DNAME()) {
-                    isc_throw(DataSourceError, "DNAME with multiple RRs in " +
-                              name.toText());
-                }
-                addOrCreate(result_rrset, *construct_name, getClass(),
-                            cur_type, cur_ttl,
-                            columns[DatabaseAccessor::RDATA_COLUMN],
-                            *accessor_);
-            } else if (cur_type == isc::dns::RRType::RRSIG()) {
+            const RRType cur_type(columns[DatabaseAccessor::TYPE_COLUMN]);
+
+            if (cur_type == RRType::RRSIG()) {
                 // If we get signatures before we get the actual data, we
                 // If we get signatures before we get the actual data, we
                 // can't know which ones to keep and which to drop...
                 // can't know which ones to keep and which to drop...
                 // So we keep a separate store of any signature that may be
                 // So we keep a separate store of any signature that may be
@@ -280,27 +217,76 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
                 // done.
                 // done.
                 // A possible optimization here is to not store them for
                 // A possible optimization here is to not store them for
                 // types we are certain we don't need
                 // types we are certain we don't need
-                sig_store.addSig(isc::dns::rdata::createRdata(cur_type,
-                    getClass(), columns[DatabaseAccessor::RDATA_COLUMN]));
+                sig_store.addSig(rdata::createRdata(cur_type, getClass(),
+                     columns[DatabaseAccessor::RDATA_COLUMN]));
+            }
+
+            if (types.find(cur_type) != types.end()) {
+                // This type is requested, so put it into result
+                const RRTTL cur_ttl(columns[DatabaseAccessor::TTL_COLUMN]);
+                // Ths sigtype column was an optimization for finding the
+                // relevant RRSIG RRs for a lookup. Currently this column is
+                // not used in this revised datasource implementation. We
+                // should either start using it again, or remove it from use
+                // completely (i.e. also remove it from the schema and the
+                // backend implementation).
+                // Note that because we don't use it now, we also won't notice
+                // it if the value is wrong (i.e. if the sigtype column
+                // contains an rrtype that is different from the actual value
+                // of the 'type covered' field in the RRSIG Rdata).
+                //cur_sigtype(columns[SIGTYPE_COLUMN]);
+                addOrCreate(result[cur_type], construct_name_object,
+                            getClass(), cur_type, cur_ttl,
+                            columns[DatabaseAccessor::RDATA_COLUMN],
+                            *accessor_);
             }
             }
-        } catch (const isc::dns::InvalidRRType& irt) {
+
+            if (cur_type == RRType::CNAME()) {
+                seen_cname = true;
+            } else if (cur_type == RRType::NS()) {
+                seen_ns = true;
+            } else if (cur_type == RRType::DS()) {
+                seen_ds = true;
+            } else if (cur_type != RRType::RRSIG() &&
+                       cur_type != RRType::NSEC3() &&
+                       cur_type != RRType::NSEC()) {
+                // NSEC and RRSIG can coexist with anything, otherwise
+                // we've seen something that can't live together with potential
+                // CNAME or NS
+                //
+                // NSEC3 lives in separate namespace from everything, therefore
+                // we just ignore it here for these checks as well.
+                seen_other = true;
+            }
+        } catch (const InvalidRRType&) {
             isc_throw(DataSourceError, "Invalid RRType in database for " <<
             isc_throw(DataSourceError, "Invalid RRType in database for " <<
                       name << ": " << columns[DatabaseAccessor::
                       name << ": " << columns[DatabaseAccessor::
                       TYPE_COLUMN]);
                       TYPE_COLUMN]);
-        } catch (const isc::dns::InvalidRRTTL& irttl) {
+        } catch (const InvalidRRTTL&) {
             isc_throw(DataSourceError, "Invalid TTL in database for " <<
             isc_throw(DataSourceError, "Invalid TTL in database for " <<
                       name << ": " << columns[DatabaseAccessor::
                       name << ": " << columns[DatabaseAccessor::
                       TTL_COLUMN]);
                       TTL_COLUMN]);
-        } catch (const isc::dns::rdata::InvalidRdataText& ird) {
+        } catch (const rdata::InvalidRdataText&) {
             isc_throw(DataSourceError, "Invalid rdata in database for " <<
             isc_throw(DataSourceError, "Invalid rdata in database for " <<
                       name << ": " << columns[DatabaseAccessor::
                       name << ": " << columns[DatabaseAccessor::
                       RDATA_COLUMN]);
                       RDATA_COLUMN]);
         }
         }
     }
     }
-    if (result_rrset) {
-        sig_store.appendSignatures(result_rrset);
+    if (seen_cname && (seen_other || seen_ns || seen_ds)) {
+        isc_throw(DataSourceError, "CNAME shares domain " << name <<
+                  " with something else");
+    }
+    if (check_ns && seen_ns && seen_other) {
+        isc_throw(DataSourceError, "NS shares domain " << name <<
+                  " with something else");
     }
     }
-    return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
+    // Add signatures to all found RRsets
+    for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
+         i != result.end(); ++ i) {
+        sig_store.appendSignatures(i->second);
+    }
+
+    return (FoundRRsets(records_found, result));
 }
 }
 
 
 bool
 bool
@@ -317,6 +303,92 @@ DatabaseClient::Finder::hasSubdomains(const std::string& name) {
     return (context->getNext(columns));
     return (context->getNext(columns));
 }
 }
 
 
+// Some manipulation with RRType sets
+namespace {
+
+// Bunch of functions to construct specific sets of RRTypes we will
+// ask from it.
+typedef std::set<RRType> WantedTypes;
+
+const WantedTypes&
+NSEC_TYPES() {
+    static bool initialized(false);
+    static WantedTypes result;
+
+    if (!initialized) {
+        result.insert(RRType::NSEC());
+        initialized = true;
+    }
+    return (result);
+}
+
+const WantedTypes&
+DELEGATION_TYPES() {
+    static bool initialized(false);
+    static WantedTypes result;
+
+    if (!initialized) {
+        result.insert(RRType::DNAME());
+        result.insert(RRType::NS());
+        initialized = true;
+    }
+    return (result);
+}
+
+const WantedTypes&
+FINAL_TYPES() {
+    static bool initialized(false);
+    static WantedTypes result;
+
+    if (!initialized) {
+        result.insert(RRType::CNAME());
+        result.insert(RRType::NS());
+        result.insert(RRType::NSEC());
+        initialized = true;
+    }
+    return (result);
+}
+
+}
+
+RRsetPtr
+DatabaseClient::Finder::findNSECCover(const Name& name) {
+    try {
+        // Which one should contain the NSEC record?
+        const Name coverName(findPreviousName(name));
+        // Get the record and copy it out
+        const FoundRRsets found = getRRsets(coverName.toText(), NSEC_TYPES(),
+                                            coverName != getOrigin());
+        const FoundIterator
+            nci(found.second.find(RRType::NSEC()));
+        if (nci != found.second.end()) {
+            return (nci->second);
+        } else {
+            // The previous doesn't contain NSEC.
+            // Badly signed zone or a bug?
+
+            // FIXME: Currently, if the zone is not signed, we could get
+            // here. In that case we can't really throw, but for now, we can't
+            // recognize it. So we don't throw at all, enable it once
+            // we have a is_signed flag or something.
+#if 0
+            isc_throw(DataSourceError, "No NSEC in " +
+                      coverName.toText() + ", but it was "
+                      "returned as previous - "
+                      "accessor error? Badly signed zone?");
+#endif
+        }
+    }
+    catch (const isc::NotImplemented&) {
+        // Well, they want DNSSEC, but there is no available.
+        // So we don't provide anything.
+        LOG_INFO(logger, DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED).
+            arg(accessor_->getDBName()).arg(name);
+    }
+    // We didn't find it, return nothing
+    return (RRsetPtr());
+}
+
 ZoneFinder::FindResult
 ZoneFinder::FindResult
 DatabaseClient::Finder::find(const isc::dns::Name& name,
 DatabaseClient::Finder::find(const isc::dns::Name& name,
                              const isc::dns::RRType& type,
                              const isc::dns::RRType& type,
@@ -326,10 +398,12 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
     // This variable is used to determine the difference between
     // This variable is used to determine the difference between
     // NXDOMAIN and NXRRSET
     // NXDOMAIN and NXRRSET
     bool records_found = false;
     bool records_found = false;
-    bool glue_ok(options & FIND_GLUE_OK);
+    bool glue_ok((options & FIND_GLUE_OK) != 0);
+    const bool dnssec_data((options & FIND_DNSSEC) != 0);
+    bool get_cover(false);
     isc::dns::RRsetPtr result_rrset;
     isc::dns::RRsetPtr result_rrset;
     ZoneFinder::Result result_status = SUCCESS;
     ZoneFinder::Result result_status = SUCCESS;
-    std::pair<bool, isc::dns::RRsetPtr> found;
+    FoundRRsets found;
     logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
     logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
         .arg(accessor_->getDBName()).arg(name).arg(type);
         .arg(accessor_->getDBName()).arg(name).arg(type);
     // In case we are in GLUE_OK mode and start matching wildcards,
     // In case we are in GLUE_OK mode and start matching wildcards,
@@ -337,11 +411,11 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
     isc::dns::RRsetPtr first_ns;
     isc::dns::RRsetPtr first_ns;
 
 
     // First, do we have any kind of delegation (NS/DNAME) here?
     // First, do we have any kind of delegation (NS/DNAME) here?
-    Name origin(getOrigin());
-    size_t origin_label_count(origin.getLabelCount());
+    const Name origin(getOrigin());
+    const size_t origin_label_count(origin.getLabelCount());
     // Number of labels in the last known non-empty domain
     // Number of labels in the last known non-empty domain
     size_t last_known(origin_label_count);
     size_t last_known(origin_label_count);
-    size_t current_label_count(name.getLabelCount());
+    const size_t current_label_count(name.getLabelCount());
     // This is how many labels we remove to get origin
     // This is how many labels we remove to get origin
     size_t remove_labels(current_label_count - origin_label_count);
     size_t remove_labels(current_label_count - origin_label_count);
 
 
@@ -349,35 +423,44 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
     for (int i(remove_labels); i > 0; --i) {
     for (int i(remove_labels); i > 0; --i) {
         Name superdomain(name.split(i));
         Name superdomain(name.split(i));
         // Look if there's NS or DNAME (but ignore the NS in origin)
         // Look if there's NS or DNAME (but ignore the NS in origin)
-        found = getRRset(superdomain, NULL, false, true,
-                         i != remove_labels && !glue_ok);
+        found = getRRsets(superdomain.toText(), DELEGATION_TYPES(),
+                          i != remove_labels);
         if (found.first) {
         if (found.first) {
             // It contains some RRs, so it exists.
             // It contains some RRs, so it exists.
             last_known = superdomain.getLabelCount();
             last_known = superdomain.getLabelCount();
-            // In case we are in GLUE_OK, we want to store the highest
-            // encountered RRset.
-            if (glue_ok && !first_ns && i != remove_labels) {
-                first_ns = getRRset(superdomain, NULL, false, false,
-                                    true).second;
-            }
-        }
-        if (found.second) {
-            // We found something redirecting somewhere else
-            // (it can be only NS or DNAME here)
-            result_rrset = found.second;
-            if (result_rrset->getType() == isc::dns::RRType::NS()) {
+
+            const FoundIterator nsi(found.second.find(RRType::NS()));
+            const FoundIterator dni(found.second.find(RRType::DNAME()));
+            // In case we are in GLUE_OK mode, we want to store the
+            // highest encountered NS (but not apex)
+            if (glue_ok && !first_ns && i != remove_labels &&
+                nsi != found.second.end()) {
+                first_ns = nsi->second;
+            } else if (!glue_ok && i != remove_labels &&
+                       nsi != found.second.end()) {
+                // Do a NS delegation, but ignore NS in glue_ok mode. Ignore
+                // delegation in apex
                 LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                 LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                           DATASRC_DATABASE_FOUND_DELEGATION).
                           DATASRC_DATABASE_FOUND_DELEGATION).
                     arg(accessor_->getDBName()).arg(superdomain);
                     arg(accessor_->getDBName()).arg(superdomain);
+                result_rrset = nsi->second;
                 result_status = DELEGATION;
                 result_status = DELEGATION;
-            } else {
+                // No need to go lower, found
+                break;
+            } else if (dni != found.second.end()) {
+                // Very similar with DNAME
                 LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                 LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                           DATASRC_DATABASE_FOUND_DNAME).
                           DATASRC_DATABASE_FOUND_DNAME).
                     arg(accessor_->getDBName()).arg(superdomain);
                     arg(accessor_->getDBName()).arg(superdomain);
+                result_rrset = dni->second;
                 result_status = DNAME;
                 result_status = DNAME;
+                if (result_rrset->getRdataCount() != 1) {
+                    isc_throw(DataSourceError, "DNAME at " << superdomain <<
+                              " has " << result_rrset->getRdataCount() <<
+                              " rdata, 1 expected");
+                }
+                break;
             }
             }
-            // Don't search more
-            break;
         }
         }
     }
     }
 
 
@@ -385,21 +468,37 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
         // Try getting the final result and extract it
         // Try getting the final result and extract it
         // It is special if there's a CNAME or NS, DNAME is ignored here
         // It is special if there's a CNAME or NS, DNAME is ignored here
         // And we don't consider the NS in origin
         // And we don't consider the NS in origin
-        found = getRRset(name, &type, true, false, name != origin && !glue_ok);
+
+        WantedTypes final_types(FINAL_TYPES());
+        final_types.insert(type);
+        found = getRRsets(name.toText(), final_types, name != origin);
         records_found = found.first;
         records_found = found.first;
-        result_rrset = found.second;
-        if (result_rrset && name != origin && !glue_ok &&
-            result_rrset->getType() == isc::dns::RRType::NS()) {
+
+        // NS records, CNAME record and Wanted Type records
+        const FoundIterator nsi(found.second.find(RRType::NS()));
+        const FoundIterator cni(found.second.find(RRType::CNAME()));
+        const FoundIterator wti(found.second.find(type));
+        if (name != origin && !glue_ok && nsi != found.second.end()) {
+            // There's a delegation at the exact node.
             LOG_DEBUG(logger, DBG_TRACE_DETAILED,
             LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                       DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
                       DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
                 arg(accessor_->getDBName()).arg(name);
                 arg(accessor_->getDBName()).arg(name);
             result_status = DELEGATION;
             result_status = DELEGATION;
-        } else if (result_rrset && type != isc::dns::RRType::CNAME() &&
-                   result_rrset->getType() == isc::dns::RRType::CNAME()) {
+            result_rrset = nsi->second;
+        } else if (type != isc::dns::RRType::CNAME() &&
+                   cni != found.second.end()) {
+            // A CNAME here
             result_status = CNAME;
             result_status = CNAME;
-        }
-
-        if (!result_rrset && !records_found) {
+            result_rrset = cni->second;
+            if (result_rrset->getRdataCount() != 1) {
+                isc_throw(DataSourceError, "CNAME with " <<
+                          result_rrset->getRdataCount() <<
+                          " rdata at " << name << ", expected 1");
+            }
+        } else if (wti != found.second.end()) {
+            // Just get the answer
+            result_rrset = wti->second;
+        } else if (!records_found) {
             // Nothing lives here.
             // Nothing lives here.
             // But check if something lives below this
             // But check if something lives below this
             // domain and if so, pretend something is here as well.
             // domain and if so, pretend something is here as well.
@@ -408,23 +507,22 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
                           DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
                           DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
                     arg(accessor_->getDBName()).arg(name);
                     arg(accessor_->getDBName()).arg(name);
                 records_found = true;
                 records_found = true;
+                get_cover = dnssec_data;
             } else {
             } else {
                 // It's not empty non-terminal. So check for wildcards.
                 // It's not empty non-terminal. So check for wildcards.
                 // We remove labels one by one and look for the wildcard there.
                 // We remove labels one by one and look for the wildcard there.
                 // Go up to first non-empty domain.
                 // Go up to first non-empty domain.
 
 
                 remove_labels = current_label_count - last_known;
                 remove_labels = current_label_count - last_known;
-                Name star("*");
                 for (size_t i(1); i <= remove_labels; ++ i) {
                 for (size_t i(1); i <= remove_labels; ++ i) {
                     // Construct the name with *
                     // Construct the name with *
-                    // TODO: Once the underlying DatabaseAccessor takes
-                    // string, do the concatenation on strings, not
-                    // Names
-                    Name superdomain(name.split(i));
-                    Name wildcard(star.concatenate(superdomain));
+                    const Name superdomain(name.split(i));
+                    const string wildcard("*." + superdomain.toText());
+                    const string construct_name(name.toText());
                     // TODO What do we do about DNAME here?
                     // TODO What do we do about DNAME here?
-                    found = getRRset(wildcard, &type, true, false, true,
-                                     &name);
+                    // The types are the same as with original query
+                    found = getRRsets(wildcard, final_types, true,
+                                      &construct_name);
                     if (found.first) {
                     if (found.first) {
                         if (first_ns) {
                         if (first_ns) {
                             // In case we are under NS, we don't
                             // In case we are under NS, we don't
@@ -445,7 +543,42 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
                             // domain, but it could be empty non-terminal. In
                             // domain, but it could be empty non-terminal. In
                             // that case, we need to cancel the match.
                             // that case, we need to cancel the match.
                             records_found = true;
                             records_found = true;
-                            result_rrset = found.second;
+                            const FoundIterator
+                                cni(found.second.find(RRType::CNAME()));
+                            const FoundIterator
+                                nsi(found.second.find(RRType::NS()));
+                            const FoundIterator
+                                nci(found.second.find(RRType::NSEC()));
+                            const FoundIterator wti(found.second.find(type));
+                            if (cni != found.second.end() &&
+                                type != RRType::CNAME()) {
+                                result_rrset = cni->second;
+                                result_status = CNAME;
+                            } else if (nsi != found.second.end()) {
+                                result_rrset = nsi->second;
+                                result_status = DELEGATION;
+                            } else if (wti != found.second.end()) {
+                                result_rrset = wti->second;
+                                result_status = WILDCARD;
+                            } else {
+                                // NXRRSET case in the wildcard
+                                result_status = WILDCARD_NXRRSET;
+                                if (dnssec_data &&
+                                    nci != found.second.end()) {
+                                    // User wants a proof the wildcard doesn't
+                                    // contain it
+                                    //
+                                    // However, we need to get the RRset in the
+                                    // name of the wildcard, not the constructed
+                                    // one, so we walk it again
+                                    found = getRRsets(wildcard, NSEC_TYPES(),
+                                                      true);
+                                    result_rrset =
+                                        found.second.find(RRType::NSEC())->
+                                        second;
+                                }
+                            }
+
                             LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                             LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                                       DATASRC_DATABASE_WILDCARD).
                                       DATASRC_DATABASE_WILDCARD).
                                 arg(accessor_->getDBName()).arg(wildcard).
                                 arg(accessor_->getDBName()).arg(wildcard).
@@ -457,33 +590,63 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
                                 arg(name).arg(superdomain);
                                 arg(name).arg(superdomain);
                         }
                         }
                         break;
                         break;
-                    } else if (hasSubdomains(wildcard.toText())) {
+                    } else if (hasSubdomains(wildcard)) {
                         // Empty non-terminal asterisk
                         // Empty non-terminal asterisk
                         records_found = true;
                         records_found = true;
                         LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                         LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                                   DATASRC_DATABASE_WILDCARD_EMPTY).
                                   DATASRC_DATABASE_WILDCARD_EMPTY).
                             arg(accessor_->getDBName()).arg(wildcard).
                             arg(accessor_->getDBName()).arg(wildcard).
                             arg(name);
                             arg(name);
+                        if (dnssec_data) {
+                            result_rrset = findNSECCover(Name(wildcard));
+                            if (result_rrset) {
+                                result_status = WILDCARD_NXRRSET;
+                            }
+                        }
                         break;
                         break;
                     }
                     }
                 }
                 }
+                // This is the NXDOMAIN case (nothing found anywhere). If
+                // they want DNSSEC data, try getting the NSEC record
+                if (dnssec_data && !records_found) {
+                    get_cover = true;
+                }
+            }
+        } else if (dnssec_data) {
+            // This is the "usual" NXRRSET case
+            // So in case they want DNSSEC, provide the NSEC
+            // (which should be available already here)
+            result_status = NXRRSET;
+            const FoundIterator nci(found.second.find(RRType::NSEC()));
+            if (nci != found.second.end()) {
+                result_rrset = nci->second;
             }
             }
         }
         }
     }
     }
 
 
     if (!result_rrset) {
     if (!result_rrset) {
-        if (records_found) {
-            logger.debug(DBG_TRACE_DETAILED,
-                         DATASRC_DATABASE_FOUND_NXRRSET)
-                        .arg(accessor_->getDBName()).arg(name)
-                        .arg(getClass()).arg(type);
-            result_status = NXRRSET;
-        } else {
-            logger.debug(DBG_TRACE_DETAILED,
-                         DATASRC_DATABASE_FOUND_NXDOMAIN)
-                        .arg(accessor_->getDBName()).arg(name)
-                        .arg(getClass()).arg(type);
-            result_status = NXDOMAIN;
+        if (result_status == SUCCESS) {
+            // Should we look for NSEC covering the name?
+            if (get_cover) {
+                result_rrset = findNSECCover(name);
+                if (result_rrset) {
+                    result_status = NXDOMAIN;
+                }
+            }
+            // Something is not here and we didn't decide yet what
+            if (records_found) {
+                logger.debug(DBG_TRACE_DETAILED,
+                             DATASRC_DATABASE_FOUND_NXRRSET)
+                    .arg(accessor_->getDBName()).arg(name)
+                    .arg(getClass()).arg(type);
+                result_status = NXRRSET;
+            } else {
+                logger.debug(DBG_TRACE_DETAILED,
+                             DATASRC_DATABASE_FOUND_NXDOMAIN)
+                    .arg(accessor_->getDBName()).arg(name)
+                    .arg(getClass()).arg(type);
+                result_status = NXDOMAIN;
+            }
         }
         }
     } else {
     } else {
         logger.debug(DBG_TRACE_DETAILED,
         logger.debug(DBG_TRACE_DETAILED,
@@ -494,6 +657,26 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
 }
 }
 
 
 Name
 Name
+DatabaseClient::Finder::findPreviousName(const Name& name) const {
+    const string str(accessor_->findPreviousName(zone_id_,
+                                                 name.reverse().toText()));
+    try {
+        return (Name(str));
+    }
+    /*
+     * To avoid having the same code many times, we just catch all the
+     * exceptions and handle them in a common code below
+     */
+    catch (const isc::dns::EmptyLabel&) {}
+    catch (const isc::dns::TooLongLabel&) {}
+    catch (const isc::dns::BadLabelType&) {}
+    catch (const isc::dns::BadEscape&) {}
+    catch (const isc::dns::TooLongName&) {}
+    catch (const isc::dns::IncompleteName&) {}
+    isc_throw(DataSourceError, "Bad name " + str + " from findPreviousName");
+}
+
+Name
 DatabaseClient::Finder::getOrigin() const {
 DatabaseClient::Finder::getOrigin() const {
     return (origin_);
     return (origin_);
 }
 }

+ 84 - 42
src/lib/datasrc/database.h

@@ -28,6 +28,9 @@
 #include <dns/name.h>
 #include <dns/name.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
+#include <map>
+#include <set>
+
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
 
 
@@ -471,6 +474,34 @@ public:
      * \return the name of the database
      * \return the name of the database
      */
      */
     virtual const std::string& getDBName() const = 0;
     virtual const std::string& getDBName() const = 0;
+
+    /**
+     * \brief It returns the previous name in DNSSEC order.
+     *
+     * This is used in DatabaseClient::findPreviousName and does more
+     * or less the real work, except for working on strings.
+     *
+     * \param rname The name to ask for previous of, in reversed form.
+     *     We use the reversed form (see isc::dns::Name::reverse),
+     *     because then the case insensitive order of string representation
+     *     and the DNSSEC order correspond (eg. org.example.a is followed
+     *     by org.example.a.b which is followed by org.example.b, etc).
+     * \param zone_id The zone to look through.
+     * \return The previous name.
+     * \note This function must return previous name even in case
+     *     the queried rname does not exist in the zone.
+     * \note This method must skip under-the-zone-cut data (glue data).
+     *     This might be implemented by looking for NSEC records (as glue
+     *     data don't have them) in the zone or in some other way.
+     *
+     * \throw DataSourceError if there's a problem with the database.
+     * \throw NotImplemented if this database doesn't support DNSSEC
+     *     or there's no previous name for the queried one (the NSECs
+     *     might be missing or the queried name is less or equal the
+     *     apex of the zone).
+     */
+    virtual std::string findPreviousName(int zone_id,
+                                         const std::string& rname) const = 0;
 };
 };
 
 
 /**
 /**
@@ -587,6 +618,12 @@ public:
                                 const FindOptions options = FIND_DEFAULT);
                                 const FindOptions options = FIND_DEFAULT);
 
 
         /**
         /**
+         * \brief Implementation of ZoneFinder::findPreviousName method.
+         */
+        virtual isc::dns::Name findPreviousName(const isc::dns::Name& query)
+            const;
+
+        /**
          * \brief The zone ID
          * \brief The zone ID
          *
          *
          * This function provides the stored zone ID as passed to the
          * This function provides the stored zone ID as passed to the
@@ -609,54 +646,42 @@ public:
         boost::shared_ptr<DatabaseAccessor> accessor_;
         boost::shared_ptr<DatabaseAccessor> accessor_;
         const int zone_id_;
         const int zone_id_;
         const isc::dns::Name origin_;
         const isc::dns::Name origin_;
-
+        //
+        /// \brief Shortcut name for the result of getRRsets
+        typedef std::pair<bool, std::map<dns::RRType, dns::RRsetPtr> >
+            FoundRRsets;
+        /// \brief Just shortcut for set of types
+        typedef std::set<dns::RRType> WantedTypes;
         /**
         /**
-         * \brief Searches database for an RRset
+         * \brief Searches database for RRsets of one domain.
          *
          *
-         * This method scans RRs of single domain specified by name and finds
-         * RRset with given type or any of redirection RRsets that are
-         * requested.
+         * This method scans RRs of single domain specified by name and
+         * extracts any RRsets found and requested by parameters.
          *
          *
-         * This function is used internally by find(), because this part is
-         * called multiple times with slightly different parameters.
+         * It is used internally by find(), because it is called multiple
+         * times (usually with different domains).
          *
          *
          * \param name Which domain name should be scanned.
          * \param name Which domain name should be scanned.
-         * \param type The RRType which is requested. This can be NULL, in
-         *     which case the method will look for the redirections only.
-         * \param want_cname If this is true, CNAME redirection may be returned
-         *     instead of the RRset with given type. If there's CNAME and
-         *     something else or the CNAME has multiple RRs, it throws
-         *     DataSourceError.
-         * \param want_dname If this is true, DNAME redirection may be returned
-         *     instead. This is with type = NULL only and is not checked in
-         *     other circumstances. If the DNAME has multiple RRs, it throws
-         *     DataSourceError.
-         * \param want_ns This allows redirection by NS to be returned. If
-         *     any other data is met as well, DataSourceError is thrown.
-         * \param construct_name If set to non-NULL, the resulting RRset will
-         *     be constructed for this name instead of the queried one. This
-         *     is useful for wildcards.
-         * \note It may happen that some of the above error conditions are not
-         *     detected in some circumstances. The goal here is not to validate
-         *     the domain in DB, but to avoid bad behaviour resulting from
-         *     broken data.
-         * \return First part of the result tells if the domain contains any
-         *     RRs. This can be used to decide between NXDOMAIN and NXRRSET.
-         *     The second part is the RRset found (if any) with any relevant
-         *     signatures attached to it.
-         * \todo This interface doesn't look very elegant. Any better idea
-         *     would be nice.
+         * \param types List of types the caller is interested in.
+         * \param check_ns If this is set to true, it checks nothing lives
+         *     together with NS record (with few little exceptions, like RRSIG
+         *     or NSEC). This check is meant for non-apex NS records.
+         * \param construct_name If this is NULL, the resulting RRsets have
+         *     their name set to name. If it is not NULL, it overrides the name
+         *     and uses this one (this can be used for wildcard synthesized
+         *     records).
+         * \return A pair, where the first element indicates if the domain
+         *     contains any RRs at all (not only the requested, it may happen
+         *     this is set to true, but the second part is empty). The second
+         *     part is map from RRtypes to RRsets of the corresponding types.
+         *     If the RRset is not present in DB, the RRtype is not there at
+         *     all (so you'll not find NULL pointer in the result).
+         * \throw DataSourceError If there's a low-level error with the
+         *     database or the database contains bad data.
          */
          */
-        std::pair<bool, isc::dns::RRsetPtr> getRRset(const isc::dns::Name&
-                                                     name,
-                                                     const isc::dns::RRType*
-                                                     type,
-                                                     bool want_cname,
-                                                     bool want_dname,
-                                                     bool want_ns, const
-                                                     isc::dns::Name*
-                                                     construct_name = NULL);
-
+        FoundRRsets getRRsets(const std::string& name,
+                              const WantedTypes& types, bool check_ns,
+                              const std::string* construct_name = NULL);
         /**
         /**
          * \brief Checks if something lives below this domain.
          * \brief Checks if something lives below this domain.
          *
          *
@@ -666,6 +691,23 @@ public:
          * \param name The domain to check.
          * \param name The domain to check.
          */
          */
         bool hasSubdomains(const std::string& name);
         bool hasSubdomains(const std::string& name);
+
+        /**
+         * \brief Get the NSEC covering a name.
+         *
+         * This one calls findPreviousName on the given name and extracts an NSEC
+         * record on the result. It handles various error cases. The method exists
+         * to share code present at more than one location.
+         */
+        dns::RRsetPtr findNSECCover(const dns::Name& name);
+
+        /**
+         * \brief Convenience type shortcut.
+         *
+         * To find stuff in the result of getRRsets.
+         */
+        typedef std::map<dns::RRType, dns::RRsetPtr>::const_iterator
+            FoundIterator;
     };
     };
 
 
     /**
     /**

+ 5 - 0
src/lib/datasrc/datasrc_messages.mes

@@ -63,6 +63,11 @@ The maximum allowed number of items of the hotspot cache is set to the given
 number. If there are too many, some of them will be dropped. The size of 0
 number. If there are too many, some of them will be dropped. The size of 0
 means no limit.
 means no limit.
 
 
+% DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED %1 doesn't support DNSSEC when asked for NSEC data covering %2
+The datasource tried to provide an NSEC proof that the named domain does not
+exist, but the database backend doesn't support DNSSEC. No proof is included
+in the answer as a result.
+
 % DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3
 % DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3
 Debug information. The database data source is looking up records with the given
 Debug information. The database data source is looking up records with the given
 name and type in the database.
 name and type in the database.

+ 6 - 0
src/lib/datasrc/memory_datasrc.cc

@@ -661,6 +661,12 @@ InMemoryZoneFinder::getFileName() const {
     return (impl_->file_name_);
     return (impl_->file_name_);
 }
 }
 
 
+isc::dns::Name
+InMemoryZoneFinder::findPreviousName(const isc::dns::Name&) const {
+    isc_throw(NotImplemented, "InMemory data source doesn't support DNSSEC "
+              "yet, can't find previous name");
+}
+
 /// Implementation details for \c InMemoryClient hidden from the public
 /// Implementation details for \c InMemoryClient hidden from the public
 /// interface.
 /// interface.
 ///
 ///

+ 6 - 0
src/lib/datasrc/memory_datasrc.h

@@ -75,6 +75,12 @@ public:
                             isc::dns::RRsetList* target = NULL,
                             isc::dns::RRsetList* target = NULL,
                             const FindOptions options = FIND_DEFAULT);
                             const FindOptions options = FIND_DEFAULT);
 
 
+    /// \brief Imelementation of the ZoneFinder::findPreviousName method
+    ///
+    /// This one throws NotImplemented exception, as InMemory doesn't
+    /// support DNSSEC currently.
+    virtual isc::dns::Name findPreviousName(const isc::dns::Name& query) const;
+
     /// \brief Inserts an rrset into the zone.
     /// \brief Inserts an rrset into the zone.
     ///
     ///
     /// It puts another RRset into the zone.
     /// It puts another RRset into the zone.

+ 79 - 26
src/lib/datasrc/sqlite3_accessor.cc

@@ -47,7 +47,8 @@ enum StatementID {
     ADD_RECORD = 7,
     ADD_RECORD = 7,
     DEL_RECORD = 8,
     DEL_RECORD = 8,
     ITERATE = 9,
     ITERATE = 9,
-    NUM_STATEMENTS = 10
+    FIND_PREVIOUS = 10,
+    NUM_STATEMENTS = 11
 };
 };
 
 
 const char* const text_statements[NUM_STATEMENTS] = {
 const char* const text_statements[NUM_STATEMENTS] = {
@@ -68,7 +69,15 @@ const char* const text_statements[NUM_STATEMENTS] = {
     "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
     "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
     "AND rdtype=?3 AND rdata=?4",
     "AND rdtype=?3 AND rdata=?4",
     "SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
     "SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
-    "WHERE zone_id = ?1 ORDER BY name, rdtype"
+    "WHERE zone_id = ?1 ORDER BY name, rdtype",
+    /*
+     * This one looks for previous name with NSEC record. It is done by
+     * using the reversed name. The NSEC is checked because we need to
+     * skip glue data, which don't have the NSEC.
+     */
+    "SELECT name FROM records " // FIND_PREVIOUS
+    "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
+    "rname < $2 ORDER BY rname DESC LIMIT 1"
 };
 };
 
 
 struct SQLite3Parameters {
 struct SQLite3Parameters {
@@ -391,6 +400,28 @@ SQLite3Accessor::getZone(const std::string& name) const {
     return (std::pair<bool, int>(false, 0));
     return (std::pair<bool, int>(false, 0));
 }
 }
 
 
+namespace {
+
+// Conversion to plain char
+const char*
+convertToPlainChar(const unsigned char* ucp, sqlite3 *db) {
+    if (ucp == NULL) {
+        // The field can really be NULL, in which case we return an
+        // empty string, or sqlite may have run out of memory, in
+        // which case we raise an error
+        if (sqlite3_errcode(db) == SQLITE_NOMEM) {
+            isc_throw(DataSourceError,
+                      "Sqlite3 backend encountered a memory allocation "
+                      "error in sqlite3_column_text()");
+        } else {
+            return ("");
+        }
+    }
+    const void* p = ucp;
+    return (static_cast<const char*>(p));
+}
+
+}
 class SQLite3Accessor::Context : public DatabaseAccessor::IteratorContext {
 class SQLite3Accessor::Context : public DatabaseAccessor::IteratorContext {
 public:
 public:
     // Construct an iterator for all records. When constructed this
     // Construct an iterator for all records. When constructed this
@@ -468,7 +499,8 @@ private:
 
 
     void copyColumn(std::string (&data)[COLUMN_COUNT], int column) {
     void copyColumn(std::string (&data)[COLUMN_COUNT], int column) {
         data[column] = convertToPlainChar(sqlite3_column_text(statement_,
         data[column] = convertToPlainChar(sqlite3_column_text(statement_,
-                                                              column));
+                                                              column),
+                                          accessor_->dbparameters_->db_);
     }
     }
 
 
     void bindZoneId(const int zone_id) {
     void bindZoneId(const int zone_id) {
@@ -495,29 +527,6 @@ private:
         statement_ = NULL;
         statement_ = NULL;
     }
     }
 
 
-    // This helper method converts from the unsigned char* type (used by
-    // sqlite3) to char* (wanted by std::string). Technically these types
-    // might not be directly convertable
-    // In case sqlite3_column_text() returns NULL, we just make it an
-    // empty string, unless it was caused by a memory error
-    const char* convertToPlainChar(const unsigned char* ucp) {
-        if (ucp == NULL) {
-            // The field can really be NULL, in which case we return an
-            // empty string, or sqlite may have run out of memory, in
-            // which case we raise an error
-            if (sqlite3_errcode(accessor_->dbparameters_->db_)
-                                == SQLITE_NOMEM) {
-                isc_throw(DataSourceError,
-                        "Sqlite3 backend encountered a memory allocation "
-                        "error in sqlite3_column_text()");
-            } else {
-                return ("");
-            }
-        }
-        const void* p = ucp;
-        return (static_cast<const char*>(p));
-    }
-
     const IteratorType iterator_type_;
     const IteratorType iterator_type_;
     boost::shared_ptr<const SQLite3Accessor> accessor_;
     boost::shared_ptr<const SQLite3Accessor> accessor_;
     sqlite3_stmt *statement_;
     sqlite3_stmt *statement_;
@@ -658,5 +667,49 @@ SQLite3Accessor::deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
         *dbparameters_, DEL_RECORD, params, "delete record from zone");
         *dbparameters_, DEL_RECORD, params, "delete record from zone");
 }
 }
 
 
+std::string
+SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
+    const
+{
+    sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS]);
+    sqlite3_clear_bindings(dbparameters_->statements_[FIND_PREVIOUS]);
+
+    if (sqlite3_bind_int(dbparameters_->statements_[FIND_PREVIOUS], 1,
+                         zone_id) != SQLITE_OK) {
+        isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (find previous): " <<
+                  sqlite3_errmsg(dbparameters_->db_));
+    }
+    if (sqlite3_bind_text(dbparameters_->statements_[FIND_PREVIOUS], 2,
+                          rname.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) {
+        isc_throw(SQLite3Error, "Could not bind name " << rname <<
+                  " to SQL statement (find previous): " <<
+                  sqlite3_errmsg(dbparameters_->db_));
+    }
+
+    std::string result;
+    const int rc = sqlite3_step(dbparameters_->statements_[FIND_PREVIOUS]);
+    if (rc == SQLITE_ROW) {
+        // We found it
+        result = convertToPlainChar(sqlite3_column_text(dbparameters_->
+            statements_[FIND_PREVIOUS], 0), dbparameters_->db_);
+    }
+    sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS]);
+
+    if (rc == SQLITE_DONE) {
+        // No NSEC records here, this DB doesn't support DNSSEC or
+        // we asked before the apex
+        isc_throw(isc::NotImplemented, "The zone doesn't support DNSSEC or "
+                  "query before apex");
+    }
+
+    if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+        // Some kind of error
+        isc_throw(SQLite3Error, "Could not get data for previous name");
+    }
+
+    return (result);
+}
+
 }
 }
 }
 }

+ 4 - 0
src/lib/datasrc/sqlite3_accessor.h

@@ -170,6 +170,10 @@ public:
     /// "sqlite3_bind10.sqlite3".
     /// "sqlite3_bind10.sqlite3".
     virtual const std::string& getDBName() const { return (database_name_); }
     virtual const std::string& getDBName() const { return (database_name_); }
 
 
+    /// \brief Concrete implementation of the pure virtual method
+    virtual std::string findPreviousName(int zone_id, const std::string& rname)
+        const;
+
 private:
 private:
     /// \brief Private database data
     /// \brief Private database data
     boost::scoped_ptr<SQLite3Parameters> dbparameters_;
     boost::scoped_ptr<SQLite3Parameters> dbparameters_;

+ 267 - 12
src/lib/datasrc/tests/database_unittest.cc

@@ -48,9 +48,11 @@ const char* const TEST_RECORDS[][5] = {
     {"www.example.org.", "A", "3600", "", "192.0.2.1"},
     {"www.example.org.", "A", "3600", "", "192.0.2.1"},
     {"www.example.org.", "AAAA", "3600", "", "2001:db8::1"},
     {"www.example.org.", "AAAA", "3600", "", "2001:db8::1"},
     {"www.example.org.", "AAAA", "3600", "", "2001:db8::2"},
     {"www.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+    {"www.example.org.", "NSEC", "3600", "", "www2.example.org. A AAAA NSEC RRSIG"},
+    {"www.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
 
 
     {"www2.example.org.", "A", "3600", "", "192.0.2.1"},
     {"www2.example.org.", "A", "3600", "", "192.0.2.1"},
-    {"www2.example.org.","AAAA", "3600", "", "2001:db8::1"},
+    {"www2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
     {"www2.example.org.", "A", "3600", "", "192.0.2.2"},
     {"www2.example.org.", "A", "3600", "", "192.0.2.2"},
 
 
     {"cname.example.org.", "CNAME", "3600", "", "www.example.org."},
     {"cname.example.org.", "CNAME", "3600", "", "www.example.org."},
@@ -125,6 +127,7 @@ const char* const TEST_RECORDS[][5] = {
     {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
     {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
     {"delegation.example.org.", "NS", "3600", "",
     {"delegation.example.org.", "NS", "3600", "",
      "ns.delegation.example.org."},
      "ns.delegation.example.org."},
+    {"delegation.example.org.", "DS", "3600", "", "1 RSAMD5 2 abcd"},
     {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
     {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
      "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
      "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
     {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
     {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
@@ -153,6 +156,9 @@ const char* const TEST_RECORDS[][5] = {
     // doesn't break anything
     // doesn't break anything
     {"example.org.", "NS", "3600", "", "ns.example.com."},
     {"example.org.", "NS", "3600", "", "ns.example.com."},
     {"example.org.", "A", "3600", "", "192.0.2.1"},
     {"example.org.", "A", "3600", "", "192.0.2.1"},
+    {"example.org.", "NSEC", "3600", "", "acnamesig1.example.org. NS A NSEC RRSIG"},
+    {"example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 "
+              "20000201000000 12345 example.org. FAKEFAKEFAKE"},
     {"example.org.", "RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
     {"example.org.", "RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
               "20000201000000 12345 example.org. FAKEFAKEFAKE"},
               "20000201000000 12345 example.org. FAKEFAKEFAKE"},
 
 
@@ -162,11 +168,23 @@ const char* const TEST_RECORDS[][5] = {
     // Something for wildcards
     // Something for wildcards
     {"*.wild.example.org.", "A", "3600", "", "192.0.2.5"},
     {"*.wild.example.org.", "A", "3600", "", "192.0.2.5"},
     {"*.wild.example.org.", "RRSIG", "3600", "A", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
     {"*.wild.example.org.", "RRSIG", "3600", "A", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"*.wild.example.org.", "NSEC", "3600", "", "cancel.here.wild.example.org. A NSEC RRSIG"},
+    {"*.wild.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
     {"cancel.here.wild.example.org.", "AAAA", "3600", "", "2001:db8::5"},
     {"cancel.here.wild.example.org.", "AAAA", "3600", "", "2001:db8::5"},
     {"delegatedwild.example.org.", "NS", "3600", "", "ns.example.com."},
     {"delegatedwild.example.org.", "NS", "3600", "", "ns.example.com."},
     {"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
     {"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
     {"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
     {"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
     {"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
     {"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
+    {"bao.example.org.", "NSEC", "3600", "", "wild.*.foo.*.bar.example.org. NSEC"},
+    {"*.cnamewild.example.org.", "CNAME", "3600", "", "www.example.org."},
+    {"*.nswild.example.org.", "NS", "3600", "", "ns.example.com."},
+    // For NSEC empty non-terminal
+    {"l.example.org.", "NSEC", "3600", "", "empty.nonterminal.example.org. NSEC"},
+    {"empty.nonterminal.example.org.", "A", "3600", "", "192.0.2.1"},
+    // Invalid rdata
+    {"invalidrdata.example.org.", "A", "3600", "", "Bunch of nonsense"},
+    {"invalidrdata2.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"invalidrdata2.example.org.", "RRSIG", "3600", "", "Nonsense"},
 
 
     {NULL, NULL, NULL, NULL, NULL},
     {NULL, NULL, NULL, NULL, NULL},
 };
 };
@@ -223,6 +241,10 @@ public:
                   "This database datasource can't be iterated");
                   "This database datasource can't be iterated");
     }
     }
 
 
+    virtual std::string findPreviousName(int, const std::string&) const {
+        isc_throw(isc::NotImplemented,
+                  "This data source doesn't support DNSSEC");
+    }
 private:
 private:
     const std::string database_name_;
     const std::string database_name_;
 
 
@@ -529,6 +551,38 @@ public:
         return (latest_clone_);
         return (latest_clone_);
     }
     }
 
 
+    virtual std::string findPreviousName(int id, const std::string& rname)
+        const
+    {
+        // Hardcoded for now, but we could compute it from the data
+        // Maybe do it when it is needed some time in future?
+        if (id == -1) {
+            isc_throw(isc::NotImplemented, "Test not implemented behaviour");
+        } else if (id == 42) {
+            if (rname == "org.example.nonterminal.") {
+                return ("l.example.org.");
+            } else if (rname == "org.example.aa.") {
+                return ("example.org.");
+            } else if (rname == "org.example.www2." ||
+                       rname == "org.example.www1.") {
+                return ("www.example.org.");
+            } else if (rname == "org.example.badnsec2.") {
+                return ("badnsec1.example.org.");
+            } else if (rname == "org.example.brokenname.") {
+                return ("brokenname...example.org.");
+            } else if (rname == "org.example.bar.*.") {
+                return ("bao.example.org.");
+            } else if (rname == "org.example.notimplnsec." ||
+                       rname == "org.example.wild.here.") {
+                isc_throw(isc::NotImplemented, "Not implemented in this test");
+            } else {
+                isc_throw(isc::Unexpected, "Unexpected name");
+            }
+        } else {
+            isc_throw(isc::Unexpected, "Unknown zone ID");
+        }
+    }
+
 private:
 private:
     // The following member variables are storage and/or update work space
     // The following member variables are storage and/or update work space
     // of the test zone.  The "master"s are the real objects that contain
     // of the test zone.  The "master"s are the real objects that contain
@@ -967,21 +1021,25 @@ doFindTest(ZoneFinder& finder,
     ZoneFinder::FindResult result =
     ZoneFinder::FindResult result =
         finder.find(name, type, NULL, options);
         finder.find(name, type, NULL, options);
     ASSERT_EQ(expected_result, result.code) << name << " " << type;
     ASSERT_EQ(expected_result, result.code) << name << " " << type;
-    if (!expected_rdatas.empty()) {
+    if (!expected_rdatas.empty() && result.rrset) {
         checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
         checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
                    name, finder.getClass(), expected_type, expected_ttl,
                    name, finder.getClass(), expected_type, expected_ttl,
                    expected_rdatas);
                    expected_rdatas);
 
 
-        if (!expected_sig_rdatas.empty()) {
+        if (!expected_sig_rdatas.empty() && result.rrset->getRRsig()) {
             checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
             checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
                        expected_name : name, finder.getClass(),
                        expected_name : name, finder.getClass(),
                        isc::dns::RRType::RRSIG(), expected_ttl,
                        isc::dns::RRType::RRSIG(), expected_ttl,
                        expected_sig_rdatas);
                        expected_sig_rdatas);
-        } else {
+        } else if (expected_sig_rdatas.empty()) {
             EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset->getRRsig());
             EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset->getRRsig());
+        } else {
+            ADD_FAILURE() << "Missing RRSIG";
         }
         }
-    } else {
+    } else if (expected_rdatas.empty()) {
         EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset);
         EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset);
+    } else {
+        ADD_FAILURE() << "Missing result";
     }
     }
 }
 }
 
 
@@ -1422,21 +1480,21 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
                                          "FAKEFAKEFAKE");
                                          "FAKEFAKEFAKE");
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
                this->qtype_, this->qtype_, this->rrttl_,
                this->qtype_, this->qtype_, this->rrttl_,
-               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               ZoneFinder::WILDCARD, this->expected_rdatas_,
                this->expected_sig_rdatas_);
                this->expected_sig_rdatas_);
     doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
     doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
-               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::WILDCARD,
                this->expected_rdatas_, this->expected_sig_rdatas_);
                this->expected_rdatas_, this->expected_sig_rdatas_);
     this->expected_rdatas_.clear();
     this->expected_rdatas_.clear();
     this->expected_sig_rdatas_.clear();
     this->expected_sig_rdatas_.clear();
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
     doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
-               this->expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
     doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
     doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
-               this->expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
 
 
     // Direct request for this wildcard
     // Direct request for this wildcard
     this->expected_rdatas_.push_back("192.0.2.5");
     this->expected_rdatas_.push_back("192.0.2.5");
@@ -1532,11 +1590,146 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
         doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
         doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
                    this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
                    this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
                    this->expected_rdatas_, this->expected_sig_rdatas_);
                    this->expected_rdatas_, this->expected_sig_rdatas_);
+        // FIXME: What should be returned in this case? How does the
+        // DNSSEC logic handle it?
+    }
+
+    const char* negative_dnssec_names[] = {
+        "a.bar.example.org.",
+        "foo.baz.bar.example.org.",
+        "a.foo.bar.example.org.",
+        NULL
+    };
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("wild.*.foo.*.bar.example.org. NSEC");
+    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,
+                   this->expected_rdatas_, this->expected_sig_rdatas_,
+                   Name("bao.example.org."), ZoneFinder::FIND_DNSSEC);
+    }
+
+    // Some strange things in the wild node
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("www.example.org.");
+    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::CNAME,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("ns.example.com.");
+    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_);
+}
+
+TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
+    // The domain exists, but doesn't have this RRType
+    // So we should get its NSEC
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    this->expected_rdatas_.push_back("www2.example.org. A AAAA NSEC RRSIG");
+    this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("www.example.org."),
+               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);
+}
+
+TYPED_TEST(DatabaseClientTest, wildcardNXRRSET_NSEC) {
+    // The domain exists, but doesn't have this RRType
+    // So we should get its NSEC
+    //
+    // The user will have to query us again to get the correct
+    // answer (eg. prove there's not an exact match)
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    this->expected_rdatas_.push_back("cancel.here.wild.example.org. A NSEC "
+                                     "RRSIG");
+    this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    // 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->expected_rdatas_, this->expected_sig_rdatas_,
+               Name("*.wild.example.org"), ZoneFinder::FIND_DNSSEC);
+}
+
+TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
+    // The domain doesn't exist, so we must get the right NSEC
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    this->expected_rdatas_.push_back("www2.example.org. A AAAA NSEC RRSIG");
+    this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("www1.example.org."),
+               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);
+    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
+    // for NS-alone was there and it would throw).
+    doFindTest(*finder, isc::dns::Name("aa.example.org."),
+               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);
+
+    // Check that if the DB doesn't support it, the exception from there
+    // is not propagated and it only does not include the NSEC
+    if (!this->is_mock_) {
+        return; // We don't make the real DB to throw
+    }
+    EXPECT_NO_THROW(doFindTest(*finder,
+                               isc::dns::Name("notimplnsec.example.org."),
+                               isc::dns::RRType::TXT(),
+                               isc::dns::RRType::NSEC(), this->rrttl_,
+                               ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+                               this->empty_rdatas_, Name::ROOT_NAME(),
+                               ZoneFinder::FIND_DNSSEC));
+}
+
+TYPED_TEST(DatabaseClientTest, emptyNonterminalNSEC) {
+    // Same as NXDOMAIN_NSEC, but with empty non-terminal
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    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);
+
+    // Check that if the DB doesn't support it, the exception from there
+    // is not propagated and it only does not include the NSEC
+    if (!this->is_mock_) {
+        return; // We don't make the real DB to throw
     }
     }
+    EXPECT_NO_THROW(doFindTest(*finder,
+                               isc::dns::Name("here.wild.example.org."),
+                               isc::dns::RRType::TXT(),
+                               isc::dns::RRType::NSEC(),
+                               this->rrttl_, ZoneFinder::NXRRSET,
+                               this->empty_rdatas_, this->empty_rdatas_,
+                               Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC));
 }
 }
 
 
 TYPED_TEST(DatabaseClientTest, getOrigin) {
 TYPED_TEST(DatabaseClientTest, getOrigin) {
-    DataSourceClient::FindResult zone(this->client_->findZone(this->zname_));
+    DataSourceClient::FindResult
+        zone(this->client_->findZone(Name("example.org")));
     ASSERT_EQ(result::SUCCESS, zone.code);
     ASSERT_EQ(result::SUCCESS, zone.code);
     shared_ptr<DatabaseClient::Finder> finder(
     shared_ptr<DatabaseClient::Finder> finder(
         dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
         dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
@@ -2142,4 +2335,66 @@ TYPED_TEST(DatabaseClientTest, compoundUpdate) {
                ZoneFinder::SUCCESS, this->expected_rdatas_,
                ZoneFinder::SUCCESS, this->expected_rdatas_,
                this->empty_rdatas_);
                this->empty_rdatas_);
 }
 }
+
+TYPED_TEST(DatabaseClientTest, previous) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    EXPECT_EQ(Name("www.example.org."),
+              finder->findPreviousName(Name("www2.example.org.")));
+    // Check a name that doesn't exist there
+    EXPECT_EQ(Name("www.example.org."),
+              finder->findPreviousName(Name("www1.example.org.")));
+    if (this->is_mock_) { // We can't really force the DB to throw
+        // Check it doesn't crash or anything if the underlying DB throws
+        DataSourceClient::FindResult
+            zone(this->client_->findZone(Name("bad.example.org")));
+        finder =
+            dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder);
+
+        EXPECT_THROW(finder->findPreviousName(Name("bad.example.org")),
+                     isc::NotImplemented);
+    } else {
+        // No need to test this on mock one, because we test only that
+        // the exception gets through
+
+        // A name before the origin
+        EXPECT_THROW(finder->findPreviousName(Name("example.com")),
+                     isc::NotImplemented);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, invalidRdata) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    EXPECT_THROW(finder->find(Name("invalidrdata.example.org."), RRType::A()),
+                 DataSourceError);
+    EXPECT_THROW(finder->find(Name("invalidrdata2.example.org."), RRType::A()),
+                 DataSourceError);
+}
+
+TEST_F(MockDatabaseClientTest, missingNSEC) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    /*
+     * FIXME: For now, we can't really distinguish this bogus input
+     * from not-signed zone so we can't throw. But once we can,
+     * enable the original test.
+     */
+#if 0
+    EXPECT_THROW(finder->find(Name("badnsec2.example.org."), RRType::A(), NULL,
+                              ZoneFinder::FIND_DNSSEC),
+                 DataSourceError);
+#endif
+    doFindTest(*finder, Name("badnsec2.example.org."), RRType::A(),
+               RRType::A(), this->rrttl_, ZoneFinder::NXDOMAIN,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+}
+
+TEST_F(MockDatabaseClientTest, badName) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    EXPECT_THROW(finder->findPreviousName(Name("brokenname.example.org.")),
+                 DataSourceError);
+}
+
 }
 }

+ 8 - 0
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -395,6 +395,14 @@ public:
 };
 };
 
 
 /**
 /**
+ * \brief Check that findPreviousName throws as it should now.
+ */
+TEST_F(InMemoryZoneFinderTest, findPreviousName) {
+    EXPECT_THROW(zone_finder_.findPreviousName(Name("www.example.org")),
+                 isc::NotImplemented);
+}
+
+/**
  * \brief Test InMemoryZoneFinder::InMemoryZoneFinder constructor.
  * \brief Test InMemoryZoneFinder::InMemoryZoneFinder constructor.
  *
  *
  * Takes the created zone finder and checks its properties they are the same
  * Takes the created zone finder and checks its properties they are the same

+ 39 - 0
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

@@ -351,6 +351,45 @@ TEST_F(SQLite3AccessorTest, getRecords) {
     EXPECT_FALSE(context->getNext(columns));
     EXPECT_FALSE(context->getNext(columns));
 }
 }
 
 
+TEST_F(SQLite3AccessorTest, findPrevious) {
+    EXPECT_EQ("dns01.example.com.",
+              accessor->findPreviousName(1, "com.example.dns02."));
+    // A name that doesn't exist
+    EXPECT_EQ("dns01.example.com.",
+              accessor->findPreviousName(1, "com.example.dns01x."));
+    // Largest name
+    EXPECT_EQ("www.example.com.",
+              accessor->findPreviousName(1, "com.example.wwww"));
+    // Out of zone after the last name
+    EXPECT_EQ("www.example.com.",
+              accessor->findPreviousName(1, "org.example."));
+    // Case insensitive?
+    EXPECT_EQ("dns01.example.com.",
+              accessor->findPreviousName(1, "com.exaMple.DNS02."));
+    // A name that doesn't exist
+    EXPECT_EQ("dns01.example.com.",
+              accessor->findPreviousName(1, "com.exaMple.DNS01X."));
+    // The DB contains foo.bar.example.com., which would be in between
+    // these two names. However, that one does not have an NSEC record,
+    // which is how this database recognizes glue data, so it should
+    // be skipped.
+    EXPECT_EQ("example.com.",
+              accessor->findPreviousName(1, "com.example.cname-ext."));
+    // Throw when we are before the origin
+    EXPECT_THROW(accessor->findPreviousName(1, "com.example."),
+                 isc::NotImplemented);
+    EXPECT_THROW(accessor->findPreviousName(1, "a.example."),
+                 isc::NotImplemented);
+}
+
+TEST_F(SQLite3AccessorTest, findPreviousNoData) {
+    // This one doesn't hold any NSEC records, so it shouldn't work
+    // The underlying DB/data don't support DNSSEC, so it's not implemented
+    // (does it make sense? Or different exception here?)
+    EXPECT_THROW(accessor->findPreviousName(3, "com.example.sql2.www."),
+                 isc::NotImplemented);
+}
+
 // Test fixture for creating a db that automatically deletes it before start,
 // Test fixture for creating a db that automatically deletes it before start,
 // and when done
 // and when done
 class SQLite3Create : public ::testing::Test {
 class SQLite3Create : public ::testing::Test {

+ 69 - 4
src/lib/datasrc/zone.h

@@ -54,13 +54,50 @@ public:
     ///
     ///
     /// Note: the codes are tentative.  We may need more, or we may find
     /// Note: the codes are tentative.  We may need more, or we may find
     /// some of them unnecessary as we implement more details.
     /// 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 NXRRSET related results, the returned NSEC record
+    /// belongs to the domain which would provide the result if it
+    /// contained the correct type (in case of NXRRSET, it is the queried
+    /// domain, in case of WILDCARD_NXRRSET, it is the wildcard domain
+    /// that matched the query name). In case of an empty nonterminal,
+    /// an NSEC is provided 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).
+    ///
+    /// Examples: if zone "example.com" has the following record:
+    /// \code
+    /// a.b.example.com. NSEC c.example.com.
+    /// \endcode
+    /// a call to \c find() for "b.example.com." will result in NXRRSET,
+    /// and if the FIND_DNSSEC option is set this NSEC will be returned.
+    /// Likewise, if zone "example.org" has the following record,
+    /// \code
+    /// x.*.example.org. NSEC a.example.org.
+    /// \endcode
+    /// a call to \c find() for "y.example.org" will result in
+    /// WILDCARD_NXRRSET (*.example.org is an empty nonterminal wildcard node),
+    /// and if the FIND_DNSSEC option is set this NSEC will be returned.
+    ///
+    /// In case of NXDOMAIN, the returned NSEC covers the queried domain.
     enum Result {
     enum Result {
         SUCCESS,                ///< An exact match is found.
         SUCCESS,                ///< An exact match is found.
         DELEGATION,             ///< The search encounters a zone cut.
         DELEGATION,             ///< The search encounters a zone cut.
         NXDOMAIN, ///< There is no domain name that matches the search name
         NXDOMAIN, ///< There is no domain name that matches the search name
         NXRRSET,  ///< There is a matching name but no RRset of the search type
         NXRRSET,  ///< There is a matching name but no RRset of the search type
         CNAME,    ///< The search encounters and returns a CNAME RR
         CNAME,    ///< The search encounters and returns a CNAME RR
-        DNAME     ///< The search encounters and returns a DNAME RR
+        DNAME,    ///< The search encounters and returns a DNAME RR
+        WILDCARD, ///< Succes by wildcard match, for DNSSEC
+        WILDCARD_NXRRSET ///< NXRRSET on wildcard, for DNSSEC
     };
     };
 
 
     /// A helper structure to represent the search result of \c find().
     /// A helper structure to represent the search result of \c find().
@@ -135,7 +172,7 @@ public:
     //@}
     //@}
 
 
     ///
     ///
-    /// \name Search Method
+    /// \name Search Methods
     ///
     ///
     //@{
     //@{
     /// Search the zone for a given pair of domain name and RR type.
     /// Search the zone for a given pair of domain name and RR type.
@@ -167,8 +204,8 @@ public:
     ///   We should revisit the interface before we heavily rely on it.
     ///   We should revisit the interface before we heavily rely on it.
     ///
     ///
     /// The \c options parameter specifies customized behavior of the search.
     /// The \c options parameter specifies customized behavior of the search.
-    /// Their semantics is as follows:
-    /// - \c GLUE_OK Allow search under a zone cut.  By default the search
+    /// Their semantics is as follows (they are or bit-field):
+    /// - \c FIND_GLUE_OK Allow search under a zone cut.  By default the search
     ///   will stop once it encounters a zone cut.  If this option is specified
     ///   will stop once it encounters a zone cut.  If this option is specified
     ///   it remembers information about the highest zone cut and continues
     ///   it remembers information about the highest zone cut and continues
     ///   the search until it finds an exact match for the given name or it
     ///   the search until it finds an exact match for the given name or it
@@ -176,6 +213,9 @@ public:
     ///   RRsets for that name are searched just like the normal case;
     ///   RRsets for that name are searched just like the normal case;
     ///   otherwise, if the search has encountered a zone cut, \c DELEGATION
     ///   otherwise, if the search has encountered a zone cut, \c DELEGATION
     ///   with the information of the highest zone cut will be returned.
     ///   with the information of the highest zone cut will be returned.
+    /// - \c FIND_DNSSEC Request that DNSSEC data (like NSEC, RRSIGs) are
+    ///   returned with the answer. It is allowed for the data source to
+    ///   include them even when not requested.
     ///
     ///
     /// A derived version of this method may involve internal resource
     /// A derived version of this method may involve internal resource
     /// allocation, especially for constructing the resulting RRset, and may
     /// allocation, especially for constructing the resulting RRset, and may
@@ -195,6 +235,31 @@ public:
                             isc::dns::RRsetList* target = NULL,
                             isc::dns::RRsetList* target = NULL,
                             const FindOptions options
                             const FindOptions options
                             = FIND_DEFAULT) = 0;
                             = FIND_DEFAULT) = 0;
+
+    /// \brief Get previous name in the zone
+    ///
+    /// Gets the previous name in the DNSSEC order. This can be used
+    /// to find the correct NSEC records for proving nonexistence
+    /// of domains.
+    ///
+    /// The concrete implementation might throw anything it thinks appropriate,
+    /// however it is recommended to stick to the ones listed here. The user
+    /// of this method should be able to handle any exceptions.
+    ///
+    /// This method does not include under-zone-cut data (glue data).
+    ///
+    /// \param query The name for which one we look for a previous one. The
+    ///     queried name doesn't have to exist in the zone.
+    /// \return The preceding name
+    ///
+    /// \throw NotImplemented in case the data source backend doesn't support
+    ///     DNSSEC or there is no previous in the zone (NSEC records might be
+    ///     missing in the DB, the queried name is less or equal to the apex).
+    /// \throw DataSourceError for low-level or internal datasource errors
+    ///     (like broken connection to database, wrong data living there).
+    /// \throw std::bad_alloc For allocation errors.
+    virtual isc::dns::Name findPreviousName(const isc::dns::Name& query)
+        const = 0;
     //@}
     //@}
 };
 };
 
 

+ 15 - 2
src/lib/dns/gen-rdatacode.py.in

@@ -133,7 +133,15 @@ def import_definitions(classcode2txt, typecode2txt, typeandclass):
     if classdir_mtime < getmtime('@srcdir@/rdata'):
     if classdir_mtime < getmtime('@srcdir@/rdata'):
         classdir_mtime = getmtime('@srcdir@/rdata')
         classdir_mtime = getmtime('@srcdir@/rdata')
 
 
-    for dir in list(os.listdir('@srcdir@/rdata')):
+    # Sort directories before iterating through them so that the directory
+    # list is processed in the same order on all systems.  The resulting
+    # files should compile regardless of the order in which the components
+    # are included but...  Having a fixed order for the directories should
+    # eliminate system-dependent problems.  (Note that the drectory names
+    # in BIND 10 are ASCII, so the order should be locale-independent.)
+    dirlist = os.listdir('@srcdir@/rdata')
+    dirlist.sort()
+    for dir in dirlist:
         classdir = '@srcdir@/rdata' + os.sep + dir
         classdir = '@srcdir@/rdata' + os.sep + dir
         m = re_typecode.match(dir)
         m = re_typecode.match(dir)
         if os.path.isdir(classdir) and (m != None or dir == 'generic'):
         if os.path.isdir(classdir) and (m != None or dir == 'generic'):
@@ -145,7 +153,12 @@ def import_definitions(classcode2txt, typecode2txt, typeandclass):
                 class_code = m.group(2)
                 class_code = m.group(2)
                 if not class_code in classcode2txt:
                 if not class_code in classcode2txt:
                     classcode2txt[class_code] = class_txt
                     classcode2txt[class_code] = class_txt
-            for file in list(os.listdir(classdir)):
+
+            # Same considerations as directories regarding sorted order
+            # also apply to files.
+            filelist = os.listdir(classdir)
+            filelist.sort()
+            for file in filelist:
                 file = classdir + os.sep + file
                 file = classdir + os.sep + file
                 m = re_typecode.match(os.path.split(file)[1])
                 m = re_typecode.match(os.path.split(file)[1])
                 if m != None:
                 if m != None:

+ 1 - 0
src/lib/dns/rdata/any_255/tsig_250.cc

@@ -23,6 +23,7 @@
 #include <util/encode/base64.h>
 #include <util/encode/base64.h>
 
 
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
+#include <dns/name.h>
 #include <dns/rdata.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 #include <dns/tsigerror.h>
 #include <dns/tsigerror.h>

+ 1 - 0
src/lib/dns/rdata/generic/afsdb_18.cc

@@ -26,6 +26,7 @@
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 
 
 using namespace std;
 using namespace std;
+using namespace isc::util;
 using namespace isc::util::str;
 using namespace isc::util::str;
 
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE

+ 1 - 0
src/lib/dns/rdata/generic/minfo_14.cc

@@ -24,6 +24,7 @@
 
 
 using namespace std;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::util;
 
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE

+ 1 - 0
src/lib/dns/rdata/generic/rp_17.cc

@@ -24,6 +24,7 @@
 
 
 using namespace std;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::util;
 
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE

+ 1 - 0
src/lib/dns/rdata/template.cc

@@ -18,6 +18,7 @@
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
+#include <dns/rrtype.h>
 
 
 using namespace std;
 using namespace std;
 using namespace isc::util;
 using namespace isc::util;

+ 1 - 1
src/lib/python/isc/datasrc/Makefile.am

@@ -25,7 +25,7 @@ datasrc_la_LDFLAGS += -module
 datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
 datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
 datasrc_la_LIBADD += $(PYTHON_LIB)
 datasrc_la_LIBADD += $(PYTHON_LIB)
-datasrc_la_LIBADD += $(SQLITE_LIBS)
+#datasrc_la_LIBADD += $(SQLITE_LIBS)
 
 
 EXTRA_DIST = client_inc.cc
 EXTRA_DIST = client_inc.cc
 EXTRA_DIST += finder_inc.cc
 EXTRA_DIST += finder_inc.cc

+ 5 - 2
src/lib/python/isc/datasrc/__init__.py

@@ -1,5 +1,5 @@
-from isc.datasrc.master import *
-from isc.datasrc.sqlite3_ds import *
+import sys
+import os
 
 
 for base in sys.path[:]:
 for base in sys.path[:]:
     datasrc_libdir = os.path.join(base, 'isc/datasrc/.libs')
     datasrc_libdir = os.path.join(base, 'isc/datasrc/.libs')
@@ -7,3 +7,6 @@ for base in sys.path[:]:
         sys.path.insert(0, datasrc_libdir)
         sys.path.insert(0, datasrc_libdir)
 
 
 from datasrc import *
 from datasrc import *
+from isc.datasrc.sqlite3_ds import *
+from isc.datasrc.master import *
+