]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Implement ZONEMD when processing zone-to-cache via AXFR
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Fri, 14 Jan 2022 13:46:29 +0000 (14:46 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Fri, 21 Jan 2022 11:06:01 +0000 (12:06 +0100)
pdns/recursordist/Makefile.am
pdns/recursordist/rec-zonetocache.cc
pdns/recursordist/rec-zonetocache.hh
pdns/zonemd.cc
pdns/zonemd.hh

index 776bf3ffe6d4195cd9ca5dd1d7551493bf9d1c19..f35c9374fd697886378a30fb46a230e0680d704b 100644 (file)
@@ -206,6 +206,7 @@ pdns_recursor_SOURCES = \
        ws-api.cc ws-api.hh \
        ws-recursor.cc ws-recursor.hh \
        xpf.cc xpf.hh \
+       zonemd.cc zonemd.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
 if !HAVE_LUA_HPP
@@ -346,6 +347,7 @@ testrunner_SOURCES = \
        validate-recursor.cc validate-recursor.hh \
        validate.cc validate.hh \
        xpf.cc xpf.hh \
+       zonemd.cc zonemd.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
 testrunner_LDFLAGS = \
index fa3894bb9c5763a1df620138009c44197f4b79b4..b85a1d0e4790acf183930f8e21798f0a394483f3 100644 (file)
@@ -30,6 +30,7 @@
 #include "logging.hh"
 #include "threadname.hh"
 #include "rec-lua-conf.hh"
+#include "zonemd.hh"
 
 #ifdef HAVE_LIBCURL
 #include "minicurl.hh"
@@ -58,7 +59,7 @@ struct ZoneData
 
   bool isRRSetAuth(const DNSName& qname, QType qtype) const;
   void parseDRForCache(DNSRecord& dr);
-  void getByAXFR(const RecZoneToCache::Config&);
+  pdns::ZoneMD::Result getByAXFR(const RecZoneToCache::Config&);
   void ZoneToCache(const RecZoneToCache::Config& config, uint64_t gen);
 };
 
@@ -125,7 +126,7 @@ void ZoneData::parseDRForCache(DNSRecord& dr)
   }
 }
 
-void ZoneData::getByAXFR(const RecZoneToCache::Config& config)
+pdns::ZoneMD::Result ZoneData::getByAXFR(const RecZoneToCache::Config& config)
 {
   ComboAddress primary = ComboAddress(config.d_sources.at(0), 53);
   uint16_t axfrTimeout = config.d_timeout;
@@ -142,8 +143,12 @@ void ZoneData::getByAXFR(const RecZoneToCache::Config& config)
   time_t axfrStart = time(nullptr);
   time_t axfrNow = time(nullptr);
 
+  vector<DNSRecord> v;
   while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
     for (auto& dr : chunk) {
+      if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
+        v.push_back(dr);
+      }
       parseDRForCache(dr);
     }
     axfrNow = time(nullptr);
@@ -151,6 +156,19 @@ void ZoneData::getByAXFR(const RecZoneToCache::Config& config)
       throw std::runtime_error("Total AXFR time for zoneToCache exceeded!");
     }
   }
+  if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
+    auto zonemd = pdns::ZoneMD(d_zone);
+    zonemd.readRecords(v);
+    bool validationDone, validationSuccess;
+    zonemd.verify(validationDone, validationSuccess);
+    if (!validationDone) {
+      return pdns::ZoneMD::Result::NoValidationDone;
+    }
+    if (!validationSuccess) {
+      return pdns::ZoneMD::Result::ValidationFailure;
+    }
+  }
+  return pdns::ZoneMD::Result::OK;
 }
 
 static std::vector<std::string> getLinesFromFile(const std::string& file)
@@ -201,9 +219,10 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config, uint64_t config
   // A this moment, we ignore NSEC and NSEC3 records. It is not clear to me yet under which conditions
   // they could be entered in into the (neg)cache.
 
+  pdns::ZoneMD::Result result = pdns::ZoneMD::Result::OK;
   if (config.d_method == "axfr") {
     d_log->info("Getting zone by AXFR");
-    getByAXFR(config);
+    result = getByAXFR(config);
   }
   else {
     vector<string> lines;
@@ -224,6 +243,30 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config, uint64_t config
       DNSRecord dr(drr);
       parseDRForCache(dr);
     }
+    // XXX ZONEMD processing
+  }
+
+  if (config.d_zonemd == pdns::ZoneMD::Config::Required && result != pdns::ZoneMD::Result::OK) {
+    // We do not accept NoValidationDone in this case
+    throw PDNSException("ZoneMD validation failure");
+    return;
+  }
+  if (config.d_zonemd == pdns::ZoneMD::Config::Process && result == pdns::ZoneMD::Result::ValidationFailure) {
+    throw PDNSException("ZoneMD validation failure");
+    return;
+  }
+  if (config.d_zonemd == pdns::ZoneMD::Config::LogOnly) {
+    switch (result) {
+    case pdns::ZoneMD::Result::ValidationFailure:
+      d_log->info("ZoneMD failure (ignored)");
+      break;
+    case pdns::ZoneMD::Result::NoValidationDone:
+      d_log->info("No ZoneMD validation done");
+      break;
+    case pdns::ZoneMD::Result::OK:
+      d_log->info("ZoneMD validation succeeded");
+      break;
+    }
   }
 
   // Extra check before we are touching the cache
index 8eb6425af2214c3e5042a38d03eabb1c25f1f90c..11064cad44064275ea0a4fbc58a7eb721c04070e 100644 (file)
@@ -25,6 +25,7 @@
 #include "namespaces.hh"
 #include "dns.hh"
 #include "iputils.hh"
+#include "zonemd.hh"
 
 class RecZoneToCache
 {
@@ -40,6 +41,7 @@ public:
     time_t d_retryOnError{60}; // Retry on error
     time_t d_refreshPeriod{24 * 3600}; // Time between refetch
     uint32_t d_timeout{20}; // timeout in seconds
+    pdns::ZoneMD::Config d_zonemd{pdns::ZoneMD::Config::LogOnly};
   };
   static void ZoneToCache(Config config, uint64_t gen);
 };
index 823c316c86f6bde591128fb6f8b6d4aabb13119b..1cce94b2ed9efac16d9084f460f0790a207d394d 100644 (file)
@@ -45,6 +45,33 @@ void pdns::ZoneMD::readRecords(ZoneParserTNG& zpt)
   }
 }
 
+void pdns::ZoneMD::readRecords(const vector<DNSRecord>& records)
+{
+  for (auto& record : records) {
+    if (!record.d_name.isPartOf(d_zone) && record.d_name != d_zone) {
+      continue;
+    }
+    if (record.d_type == QType::SOA && d_soaRecordContent) {
+      continue;
+    }
+
+    if (record.d_type == QType::SOA && record.d_name == d_zone) {
+      d_soaRecordContent = std::dynamic_pointer_cast<SOARecordContent>(record.d_content);
+    }
+    if (record.d_type == QType::ZONEMD && record.d_name == d_zone) {
+      auto zonemd = std::dynamic_pointer_cast<ZONEMDRecordContent>(record.d_content);
+      auto inserted = d_zonemdRecords.insert({pair(zonemd->d_scheme, zonemd->d_hashalgo), {zonemd, false}});
+      if (!inserted.second) {
+        // Mark as duplicate
+        inserted.first->second.duplicate = true;
+      }
+    }
+    RRSetKey_t key = std::pair(record.d_name, record.d_type);
+    d_resourceRecordSets[key].push_back(record.d_content);
+    d_resourceRecordSetTTLs[key] = record.d_ttl;
+  }
+}
+
 void pdns::ZoneMD::verify(bool& validationDone, bool& validationOK)
 {
   validationDone = false;
index 9a5a243b380ec15c910097f63c624a209e6b64be..2f4a932d882a43575a650700b9c061d195940eb0 100644 (file)
@@ -36,10 +36,14 @@ namespace pdns
 class ZoneMD
 {
 public:
+  enum class Config: uint8_t { Ignore, Process, LogOnly, Required };
+  enum class Result: uint8_t { OK, NoValidationDone, ValidationFailure };
+
   ZoneMD(const DNSName& zone) :
     d_zone(zone)
   {}
   void readRecords(ZoneParserTNG& zpt);
+  void readRecords(const std::vector<DNSRecord>& records);
   void verify(bool& validationDone, bool& validationOK);
 
 private: