]> granicus.if.org Git - pdns/commitdiff
rec: Support several types for local RPZ records
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 21 Dec 2018 16:10:13 +0000 (17:10 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 7 Jan 2019 16:41:38 +0000 (17:41 +0100)
pdns/filterpo.cc
pdns/filterpo.hh
pdns/lua-recursor4.cc
pdns/pdns_recursor.cc
pdns/rec-lua-conf.cc
pdns/recursordist/Makefile.am
pdns/recursordist/test-filterpo_cc.cc [new file with mode: 0644]
pdns/recursordist/test-syncres_cc.cc
pdns/rpzloader.cc
regression-tests.recursor-dnssec/test_RPZ.py

index 90179584ae6a9253b7825ebf2b2fef9702bd102d..38df90cea682b8a137fa8e3c82c7c34c79a24575 100644 (file)
@@ -196,107 +196,190 @@ void DNSFilterEngine::assureZones(size_t zone)
     d_zones.resize(zone+1);
 }
 
-void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy pol)
+void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy&& pol)
 {
   pol.d_name = d_name;
   pol.d_type = PolicyType::ClientIP;
-  d_qpolAddr.insert(nm).second=pol;
+  d_qpolAddr.insert(nm).second=std::move(pol);
 }
 
-void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy pol)
+void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol)
 {
   pol.d_name = d_name;
   pol.d_type = PolicyType::ResponseIP;
-  d_postpolAddr.insert(nm).second=pol;
+  d_postpolAddr.insert(nm).second=std::move(pol);
 }
 
-void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy pol)
+void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol)
 {
-  pol.d_name = d_name;
-  pol.d_type = PolicyType::QName;
-  d_qpolName[n]=pol;
+  auto it = d_qpolName.find(n);
+
+  if (it != d_qpolName.end()) {
+    auto& existingPol = it->second;
+
+    if (existingPol.d_kind != pol.d_kind) {
+      throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following QName: " + n.toString());
+    }
+
+    if (existingPol.d_kind != PolicyKind::Custom) {
+      throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following QName: " + n.toString());
+    }
+
+    existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
+
+    for (auto& custom : pol.d_custom) {
+      existingPol.d_custom.emplace_back(std::move(custom));
+    }
+  }
+  else {
+    auto& qpol = d_qpolName.insert({n, std::move(pol)}).first->second;
+    qpol.d_name = d_name;
+    qpol.d_type = PolicyType::QName;
+  }
 }
 
-void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy pol)
+void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy&& pol)
 {
   pol.d_name = d_name;
   pol.d_type = PolicyType::NSDName;
-  d_propolName[n]=pol;
+  d_propolName.insert({n, std::move(pol)});
 }
 
-void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy pol)
+void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy&& pol)
 {
   pol.d_name = d_name;
   pol.d_type = PolicyType::NSIP;
-  d_propolNSAddr.insert(nm).second = pol;
+  d_propolNSAddr.insert(nm).second = std::move(pol);
 }
 
-bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, Policy& pol)
+bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, const Policy& pol)
 {
   d_qpolAddr.erase(nm);
   return true;
 }
 
-bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, Policy& pol)
+bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, const Policy& pol)
 {
   d_postpolAddr.erase(nm);
   return true;
 }
 
-bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, Policy& pol)
+bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
 {
-  d_qpolName.erase(n); // XXX verify we had identical policy?
-  return true;
+  auto it = d_qpolName.find(n);
+  if (it == d_qpolName.end()) {
+    return false;
+  }
+
+  auto& existing = it->second;
+  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
+    d_qpolName.erase(it);
+    return true;
+  }
+
+  /* for custom types, we might have more than one type,
+     and then we need to remove only the right ones. */
+  if (existing.d_custom.size() <= 1) {
+    d_qpolName.erase(it);
+    return true;
+  }
+
+  bool result = false;
+  for (auto& toremove : pol.d_custom) {
+    for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
+      if (**it == *toremove) {
+        existing.d_custom.erase(it);
+        result = true;
+        break;
+      }
+    }
+  }
+
+  return result;
 }
 
-bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& n, Policy& pol)
+bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& n, const Policy& pol)
 {
   d_propolName.erase(n); // XXX verify policy matched? =pol;
   return true;
 }
 
-bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& nm, Policy& pol)
+bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& nm, const Policy& pol)
 {
   d_propolNSAddr.erase(nm);
   return true;
 }
 
-DNSRecord DNSFilterEngine::Policy::getCustomRecord(const DNSName& qname) const
+DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<DNSRecordContent>& custom) const
+{
+  DNSRecord dr;
+  dr.d_name = qname;
+  dr.d_type = custom->getType();
+  dr.d_ttl = d_ttl;
+  dr.d_class = QClass::IN;
+  dr.d_place = DNSResourceRecord::ANSWER;
+  dr.d_content = custom;
+
+  if (dr.d_type == QType::CNAME) {
+    const auto content = std::dynamic_pointer_cast<CNAMERecordContent>(custom);
+    if (content) {
+      DNSName target = content->getTarget();
+      if (target.isWildcard()) {
+        target.chopOff();
+        dr.d_content = std::make_shared<CNAMERecordContent>(qname + target);
+      }
+    }
+  }
+
+  return dr;
+}
+
+std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName& qname, uint16_t qtype) const
 {
   if (d_kind != PolicyKind::Custom) {
     throw std::runtime_error("Asking for a custom record from a filtering policy of a non-custom type");
   }
 
-  DNSRecord result;
-  result.d_name = qname;
-  result.d_type = d_custom->getType();
-  result.d_ttl = d_ttl;
-  result.d_class = QClass::IN;
-  result.d_place = DNSResourceRecord::ANSWER;
-  result.d_content = d_custom;
+  std::vector<DNSRecord> result;
 
-  if (result.d_type == QType::CNAME) {
-    const auto content = std::dynamic_pointer_cast<CNAMERecordContent>(d_custom);
-    if (content) {
-      DNSName target = content->getTarget();
-      if (target.isWildcard()) {
-        target.chopOff();
-        result.d_content = std::make_shared<CNAMERecordContent>(qname + target);
+  for (const auto& custom : d_custom) {
+    if (qtype != QType::ANY && qtype != custom->getType() && custom->getType() != QType::CNAME) {
+      continue;
+    }
+
+    DNSRecord dr;
+    dr.d_name = qname;
+    dr.d_type = custom->getType();
+    dr.d_ttl = d_ttl;
+    dr.d_class = QClass::IN;
+    dr.d_place = DNSResourceRecord::ANSWER;
+    dr.d_content = custom;
+
+    if (dr.d_type == QType::CNAME) {
+      const auto content = std::dynamic_pointer_cast<CNAMERecordContent>(custom);
+      if (content) {
+        DNSName target = content->getTarget();
+        if (target.isWildcard()) {
+          target.chopOff();
+          dr.d_content = std::make_shared<CNAMERecordContent>(qname + target);
+        }
       }
     }
+
+    result.emplace_back(getRecordFromCustom(qname, custom));
   }
 
   return result;
 }
 
-std::string DNSFilterEngine::Policy::getKindToString() const
+std::string DNSFilterEngine::getKindToString(DNSFilterEngine::PolicyKind kind)
 {
   static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
   static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
     rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
   static const std::string rpzPrefix("rpz-");
 
-  switch(d_kind) {
+  switch(kind) {
   case DNSFilterEngine::PolicyKind::NoAction:
     return noaction.toString();
   case DNSFilterEngine::PolicyKind::Drop:
@@ -312,28 +395,52 @@ std::string DNSFilterEngine::Policy::getKindToString() const
   }
 }
 
-DNSRecord DNSFilterEngine::Policy::getRecord(const DNSName& qname) const
+std::string DNSFilterEngine::getTypeToString(DNSFilterEngine::PolicyType type)
 {
-  DNSRecord dr;
+  switch(type) {
+  case DNSFilterEngine::PolicyType::None:
+    return "none";
+  case DNSFilterEngine::PolicyType::QName:
+    return "QName";
+  case DNSFilterEngine::PolicyType::ClientIP:
+    return "Client IP";
+  case DNSFilterEngine::PolicyType::ResponseIP:
+    return "Response IP";
+  case DNSFilterEngine::PolicyType::NSDName:
+    return "Name Server Name";
+  case DNSFilterEngine::PolicyType::NSIP:
+    return "Name Server IP";
+  default:
+    throw std::runtime_error("Unexpected DNSFilterEngine::Policy type");
+  }
+}
+
+std::vector<DNSRecord> DNSFilterEngine::Policy::getRecords(const DNSName& qname) const
+{
+  std::vector<DNSRecord> result;
 
   if (d_kind == PolicyKind::Custom) {
-    dr = getCustomRecord(qname);
+    result = getCustomRecords(qname, QType::ANY);
   }
   else {
+    DNSRecord dr;
     dr.d_name = qname;
     dr.d_ttl = static_cast<uint32_t>(d_ttl);
     dr.d_type = QType::CNAME;
     dr.d_class = QClass::IN;
-    dr.d_content = DNSRecordContent::mastermake(QType::CNAME, QClass::IN, getKindToString());
+    dr.d_content = DNSRecordContent::mastermake(QType::CNAME, QClass::IN, getKindToString(d_kind));
+    result.push_back(std::move(dr));
   }
 
-  return dr;
+  return result;
 }
 
 void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol) const
 {
-  DNSRecord dr = pol.getRecord(name);
-  fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str());
+  auto records = pol.getRecords(name);
+  for (const auto& dr : records) {
+    fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str());
+  }
 }
 
 DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
@@ -386,8 +493,10 @@ void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DN
   DNSName full = maskToRPZ(nm);
   full += name;
 
-  DNSRecord dr = pol.getRecord(full);
-  fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str());
+  auto records = pol.getRecords(full);
+  for (const auto& dr : records) {
+    fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str());
+  }
 }
 
 void DNSFilterEngine::Zone::dump(FILE* fp) const
index f07d37102ea9f14a33071aee7401ebe846af9a71..22900cb37d73e2e3b6d1c3bb153aae5db1f35c41 100644 (file)
@@ -68,25 +68,36 @@ public:
   enum class PolicyKind { NoAction, Drop, NXDOMAIN, NODATA, Truncate, Custom};
   enum class PolicyType { None, QName, ClientIP, ResponseIP, NSDName, NSIP };
 
+  static std::string getKindToString(PolicyKind kind);
+  static std::string getTypeToString(PolicyType type);
+
   struct Policy
   {
-    Policy(): d_custom(nullptr), d_name(nullptr), d_kind(PolicyKind::NoAction), d_type(PolicyType::None), d_ttl(0)
+    Policy(): d_name(nullptr), d_kind(PolicyKind::NoAction), d_type(PolicyType::None), d_ttl(0)
+    {
+    }
+
+    Policy(PolicyKind kind, PolicyType type, int32_t ttl=0, std::shared_ptr<std::string> name=nullptr, const std::vector<std::shared_ptr<DNSRecordContent>>& custom={}): d_custom(custom), d_name(name), d_kind(kind), d_type(type), d_ttl(ttl)
     {
     }
+
     bool operator==(const Policy& rhs) const
     {
-      return d_kind == rhs.d_kind; // XXX check d_custom too!
+      return d_kind == rhs.d_kind && d_type == rhs.d_type && d_ttl == rhs.d_ttl && d_custom == rhs.d_custom;
     }
-    std::string getKindToString() const;
-    DNSRecord getCustomRecord(const DNSName& qname) const;
-    DNSRecord getRecord(const DNSName& qname) const;
+    std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
+    std::vector<DNSRecord> getRecords(const DNSName& qname) const;
 
-    std::shared_ptr<DNSRecordContent> d_custom;
-    std::shared_ptr<std::string> d_name;
+    std::vector<std::shared_ptr<DNSRecordContent>> d_custom;
+    std::shared_ptr<std::string> d_name; // the name of the policy
     PolicyKind d_kind;
     PolicyType d_type;
+    /* Yup, we are currently using the same TTL for every record for a given name */
     int32_t d_ttl;
-  };
+
+  private:
+    DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<DNSRecordContent>& custom) const;
+};
 
   class Zone {
   public:
@@ -133,6 +144,11 @@ public:
       return d_refresh;
     }
 
+    uint32_t getSerial() const
+    {
+      return d_serial;
+    }
+
     size_t size() const
     {
       return d_qpolAddr.size() + d_postpolAddr.size() + d_propolName.size() + d_propolNSAddr.size() + d_qpolName.size();
@@ -141,17 +157,17 @@ public:
 
     void dump(FILE * fp) const;
 
-    void addClientTrigger(const Netmask& nm, Policy pol);
-    void addQNameTrigger(const DNSName& nm, Policy pol);
-    void addNSTrigger(const DNSName& dn, Policy pol);
-    void addNSIPTrigger(const Netmask& nm, Policy pol);
-    void addResponseTrigger(const Netmask& nm, Policy pol);
-
-    bool rmClientTrigger(const Netmask& nm, Policy& pol);
-    bool rmQNameTrigger(const DNSName& nm, Policy& pol);
-    bool rmNSTrigger(const DNSName& dn, Policy& pol);
-    bool rmNSIPTrigger(const Netmask& nm, Policy& pol);
-    bool rmResponseTrigger(const Netmask& nm, Policy& pol);
+    void addClientTrigger(const Netmask& nm, Policy&& pol);
+    void addQNameTrigger(const DNSName& nm, Policy&& pol);
+    void addNSTrigger(const DNSName& dn, Policy&& pol);
+    void addNSIPTrigger(const Netmask& nm, Policy&& pol);
+    void addResponseTrigger(const Netmask& nm, Policy&& pol);
+
+    bool rmClientTrigger(const Netmask& nm, const Policy& pol);
+    bool rmQNameTrigger(const DNSName& nm, const Policy& pol);
+    bool rmNSTrigger(const DNSName& dn, const Policy& pol);
+    bool rmNSIPTrigger(const Netmask& nm, const Policy& pol);
+    bool rmResponseTrigger(const Netmask& nm, const Policy& pol);
 
     bool findQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
     bool findNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
index d0f1af10a26c06c7b71186734326444c6470ef6a..e6afc0a5c38dae881fad3184ac5aa5183e6c9d1c 100644 (file)
@@ -253,13 +253,24 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
   d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
     [](const DNSFilterEngine::Policy& pol) -> std::string {
-      if(pol.d_custom)
-        return pol.d_custom->getZoneRepresentation();
-      return std::string();
+      std::string result;
+      if (pol.d_kind != DNSFilterEngine::PolicyKind::Custom) {
+        return result;
+      }
+
+      for (const auto& dr : pol.d_custom) {
+        if (!result.empty()) {
+          result += "\n";
+        }
+        result += dr->getZoneRepresentation();
+      }
+
+      return result;
     },
     [](DNSFilterEngine::Policy& pol, const std::string& content) {
       // Only CNAMES for now, when we ever add a d_custom_type, there will be pain
-      pol.d_custom = DNSRecordContent::mastermake(QType::CNAME, 1, content);
+      pol.d_custom.clear();
+      pol.d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN, content));
     }
   );
   d_lw->registerFunction("getDH", &DNSQuestion::getDH);
index 39028c183c4c6593024e4763b3f8d34b69b613e4..3e7044c23979cea6a4b3eb8ad5682317272fb73c 100644 (file)
@@ -838,7 +838,7 @@ static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRe
     bool oldWantsRPZ = sr.getWantsRPZ();
     sr.setWantsRPZ(false);
     vector<DNSRecord> ans;
-    res = sr.beginResolve(DNSName(spoofed.d_content->getZoneRepresentation()), qtype, 1, ans);
+    res = sr.beginResolve(DNSName(spoofed.d_content->getZoneRepresentation()), qtype, QClass::IN, ans);
     for (const auto& rec : ans) {
       if(rec.d_place == DNSResourceRecord::ANSWER) {
         ret.push_back(rec);
@@ -1145,7 +1145,7 @@ static void startDoResolve(void *p)
     /* preresolve expects res (dq.rcode) to be set to RCode::NoError by default */
     int res = RCode::NoError;
     DNSFilterEngine::Policy appliedPolicy;
-    DNSRecord spoofed;
+    std::vector<DNSRecord> spoofed;
     RecursorLua4::DNSQuestion dq(dc->d_source, dc->d_destination, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_tcp, variableAnswer, wantsRPZ, logResponse);
     dq.ednsFlags = &edo.d_extFlags;
     dq.ednsOptions = &ednsOpts;
@@ -1223,9 +1223,11 @@ static void startDoResolve(void *p)
           case DNSFilterEngine::PolicyKind::Custom:
             g_stats.policyResults[appliedPolicy.d_kind]++;
             res=RCode::NoError;
-            spoofed=appliedPolicy.getCustomRecord(dc->d_mdp.d_qname);
-            ret.push_back(spoofed);
-            handleRPZCustom(spoofed, QType(dc->d_mdp.d_qtype), sr, res, ret);
+            spoofed=appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+            for (const auto& dr : spoofed) {
+              ret.push_back(dr);
+              handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
+            }
             goto haveAnswer;
           case DNSFilterEngine::PolicyKind::Truncate:
             if(!dc->d_tcp) {
@@ -1283,9 +1285,11 @@ static void startDoResolve(void *p)
           case DNSFilterEngine::PolicyKind::Custom:
             ret.clear();
             res=RCode::NoError;
-            spoofed=appliedPolicy.getCustomRecord(dc->d_mdp.d_qname);
-            ret.push_back(spoofed);
-            handleRPZCustom(spoofed, QType(dc->d_mdp.d_qtype), sr, res, ret);
+            spoofed=appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+            for (const auto& dr : spoofed) {
+              ret.push_back(dr);
+              handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
+            }
             goto haveAnswer;
         }
       }
@@ -1341,9 +1345,11 @@ static void startDoResolve(void *p)
           case DNSFilterEngine::PolicyKind::Custom:
             ret.clear();
             res=RCode::NoError;
-            spoofed=appliedPolicy.getCustomRecord(dc->d_mdp.d_qname);
-            ret.push_back(spoofed);
-            handleRPZCustom(spoofed, QType(dc->d_mdp.d_qtype), sr, res, ret);
+            spoofed=appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+            for (const auto& dr : spoofed) {
+              ret.push_back(dr);
+              handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
+            }
             goto haveAnswer;
         }
       }
index 125d1a043e3542ce3ea6059b8dbf66845a04b4b5..a94c992a2f38f33c521d7bba18689b4225c23642 100644 (file)
@@ -62,10 +62,8 @@ static void parseRPZParameters(const std::unordered_map<string,boost::variant<ui
     defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(constGet(have, "defpol"));
     defpol->d_name = std::make_shared<std::string>(polName);
     if(defpol->d_kind == DNSFilterEngine::PolicyKind::Custom) {
-      defpol->d_custom=
-          DNSRecordContent::mastermake(QType::CNAME, 1,
-                                       boost::get<string>(constGet(have,"defcontent"))
-            );
+      defpol->d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN,
+                                                              boost::get<string>(constGet(have,"defcontent"))));
 
       if(have.count("defttl"))
         defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(constGet(have, "defttl")));
index cca58a8ad8af318671453cbfd17e55e171091ee8..d87c7df2b36d01086c9650a7f0baa81a265f8446 100644 (file)
@@ -256,6 +256,7 @@ testrunner_SOURCES = \
        test-dnsparser_hh.cc \
        test-dnsrecords_cc.cc \
        test-ednsoptions_cc.cc \
+       test-filterpo_cc.cc \
        test-iputils_hh.cc \
        test-ixfr_cc.cc \
        test-misc_hh.cc \
diff --git a/pdns/recursordist/test-filterpo_cc.cc b/pdns/recursordist/test-filterpo_cc.cc
new file mode 100644 (file)
index 0000000..fbfa184
--- /dev/null
@@ -0,0 +1,358 @@
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+
+#include "dnsrecords.hh"
+#include "filterpo.hh"
+
+BOOST_AUTO_TEST_CASE(test_filter_policies_basic) {
+  DNSFilterEngine dfe;
+
+  std::string zoneName("Unit test policy 0");
+  auto zone = std::make_shared<DNSFilterEngine::Zone>();
+  zone->setName(zoneName);
+  BOOST_CHECK_EQUAL(*(zone->getName()), zoneName);
+  zone->setDomain(DNSName("powerdns.com."));
+  BOOST_CHECK_EQUAL(zone->getDomain(), DNSName("powerdns.com."));
+  zone->setSerial(42);
+  BOOST_CHECK_EQUAL(zone->getSerial(), 42);
+  zone->setRefresh(99);
+  BOOST_CHECK_EQUAL(zone->getRefresh(), 99);
+
+  const ComboAddress nsIP("192.0.2.1");
+  const DNSName nsName("ns.bad.wolf.");
+  const ComboAddress clientIP("192.0.2.128");
+  const DNSName blockedName("blocked.");
+  const ComboAddress responseIP("192.0.2.254");
+  BOOST_CHECK_EQUAL(zone->size(), 0);
+  zone->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::ClientIP));
+  BOOST_CHECK_EQUAL(zone->size(), 1);
+  zone->addQNameTrigger(blockedName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::QName));
+  BOOST_CHECK_EQUAL(zone->size(), 2);
+  zone->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::NSIP));
+  BOOST_CHECK_EQUAL(zone->size(), 3);
+  zone->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::NSDName));
+  BOOST_CHECK_EQUAL(zone->size(), 4);
+  zone->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::ResponseIP));
+  BOOST_CHECK_EQUAL(zone->size(), 5);
+
+  size_t zoneIdx = dfe.addZone(zone);
+
+  BOOST_CHECK_EQUAL(dfe.size(), 1);
+  BOOST_CHECK(dfe.getZone(zoneName) == zone);
+  BOOST_CHECK(dfe.getZone(zoneIdx) == zone);
+
+  dfe.setZone(zoneIdx, zone);
+
+  BOOST_CHECK_EQUAL(dfe.size(), 1);
+  BOOST_CHECK(dfe.getZone(zoneName) == zone);
+  BOOST_CHECK(dfe.getZone(zoneIdx) == zone);
+
+  {
+    /* blocked NS name */
+    const auto matchingPolicy = dfe.getProcessingPolicy(nsName, std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSDName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findNSPolicy(nsName, zonePolicy));
+    BOOST_CHECK(zonePolicy == matchingPolicy);
+  }
+
+  {
+    /* allowed NS name */
+    const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("ns.bad.rabbit."), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findNSPolicy(DNSName("ns.bad.rabbit."), zonePolicy) == false);
+  }
+
+  {
+    /* blocked NS IP */
+    const auto matchingPolicy = dfe.getProcessingPolicy(nsIP, std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findNSIPPolicy(nsIP, zonePolicy));
+    BOOST_CHECK(zonePolicy == matchingPolicy);
+  }
+
+  {
+    /* allowed NS IP */
+    const auto matchingPolicy = dfe.getProcessingPolicy(ComboAddress("192.0.2.142"), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findNSIPPolicy(ComboAddress("192.0.2.142"), zonePolicy) == false);
+  }
+
+  {
+    /* blocked qname */
+    const auto matchingPolicy = dfe.getQueryPolicy(blockedName, ComboAddress("192.0.2.142"), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findQNamePolicy(blockedName, zonePolicy));
+    BOOST_CHECK(zonePolicy == matchingPolicy);
+  }
+
+  {
+    /* blocked client IP */
+    const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), clientIP, std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findClientPolicy(clientIP, zonePolicy));
+    BOOST_CHECK(zonePolicy == matchingPolicy);
+  }
+
+  {
+    /* not blocked */
+    const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), ComboAddress("192.0.2.142"), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findClientPolicy(ComboAddress("192.0.2.142"), zonePolicy) == false);
+    BOOST_CHECK(zone->findQNamePolicy(DNSName("totally.legit."), zonePolicy) == false);
+  }
+
+  {
+    /* blocked A */
+    DNSRecord dr;
+    dr.d_type = QType::A;
+    dr.d_content = DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString());
+    const auto matchingPolicy = dfe.getPostPolicy({ dr }, std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ResponseIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findResponsePolicy(responseIP, zonePolicy));
+    BOOST_CHECK(zonePolicy == matchingPolicy);
+  }
+
+  {
+    /* allowed A */
+    DNSRecord dr;
+    dr.d_type = QType::A;
+    dr.d_content = DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.142");
+    const auto matchingPolicy = dfe.getPostPolicy({ dr }, std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+    DNSFilterEngine::Policy zonePolicy;
+    BOOST_CHECK(zone->findResponsePolicy(ComboAddress("192.0.2.142"), zonePolicy) == false);
+  }
+
+  BOOST_CHECK_EQUAL(zone->size(), 5);
+  zone->rmClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::ClientIP));
+  BOOST_CHECK_EQUAL(zone->size(), 4);
+  zone->rmQNameTrigger(blockedName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::QName));
+  BOOST_CHECK_EQUAL(zone->size(), 3);
+  zone->rmNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::NSIP));
+  BOOST_CHECK_EQUAL(zone->size(), 2);
+  zone->rmNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::NSDName));
+  BOOST_CHECK_EQUAL(zone->size(), 1);
+  zone->rmResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Drop, DNSFilterEngine::PolicyType::ResponseIP));
+  BOOST_CHECK_EQUAL(zone->size(), 0);
+
+  /* DNSFilterEngine::clear() calls clear() on all zones, but keeps the zones */
+  dfe.clear();
+  BOOST_CHECK_EQUAL(dfe.size(), 1);
+  BOOST_CHECK(dfe.getZone(zoneName) == zone);
+  BOOST_CHECK(dfe.getZone(zoneIdx) == zone);
+}
+
+BOOST_AUTO_TEST_CASE(test_filter_policies_local_data) {
+  DNSFilterEngine dfe;
+
+  std::string zoneName("Unit test policy local data");
+  auto zone = std::make_shared<DNSFilterEngine::Zone>();
+  zone->setName(zoneName);
+
+  const DNSName bad1("bad1.example.com.");
+  const DNSName bad2("bad2.example.com.");
+
+  zone->addQNameTrigger(bad1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, { DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden.example.net.") } ));
+  BOOST_CHECK_EQUAL(zone->size(), 1);
+
+  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, { DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1") } ));
+  BOOST_CHECK_EQUAL(zone->size(), 2);
+
+  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, { DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.2") } ));
+  BOOST_CHECK_EQUAL(zone->size(), 2);
+
+  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, { DNSRecordContent::mastermake(QType::MX, QClass::IN, "10 garden-mail.example.net.") } ));
+  BOOST_CHECK_EQUAL(zone->size(), 2);
+
+  dfe.addZone(zone);
+
+  {
+    /* exact type does not exist, but we have a CNAME */
+    const auto matchingPolicy = dfe.getQueryPolicy(bad1, ComboAddress("192.0.2.142"), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(bad1, QType::A);
+    BOOST_CHECK_EQUAL(records.size(), 1);
+    const auto& record = records.at(0);
+    BOOST_CHECK(record.d_type == QType::CNAME);
+    BOOST_CHECK(record.d_class == QClass::IN);
+    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    BOOST_CHECK(content != nullptr);
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden.example.net.");
+  }
+
+  {
+    /* exact type exists */
+    const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+
+    {
+      auto records = matchingPolicy.getCustomRecords(bad2, QType::A);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      {
+        const auto& record = records.at(0);
+        BOOST_CHECK(record.d_type == QType::A);
+        BOOST_CHECK(record.d_class == QClass::IN);
+        auto content = std::dynamic_pointer_cast<ARecordContent>(record.d_content);
+        BOOST_CHECK(content != nullptr);
+        BOOST_CHECK_EQUAL(content->getCA().toString(), "192.0.2.1");
+      }
+      {
+        const auto& record = records.at(1);
+        BOOST_CHECK(record.d_type == QType::A);
+        BOOST_CHECK(record.d_class == QClass::IN);
+        auto content = std::dynamic_pointer_cast<ARecordContent>(record.d_content);
+        BOOST_CHECK(content != nullptr);
+        BOOST_CHECK_EQUAL(content->getCA().toString(), "192.0.2.2");
+      }
+    }
+
+    {
+      auto records = matchingPolicy.getCustomRecords(bad2, QType::MX);
+      BOOST_CHECK_EQUAL(records.size(), 1);
+      const auto& record = records.at(0);
+      BOOST_CHECK(record.d_type == QType::MX);
+      BOOST_CHECK(record.d_class == QClass::IN);
+      auto content = std::dynamic_pointer_cast<MXRecordContent>(record.d_content);
+      BOOST_CHECK(content != nullptr);
+      BOOST_CHECK_EQUAL(content->d_mxname.toString(), "garden-mail.example.net.");
+    }
+
+    {
+      /* the name exists but there is no CNAME nor matching type, so NODATA */
+      auto records = matchingPolicy.getCustomRecords(bad2, QType::AAAA);
+      BOOST_CHECK_EQUAL(records.size(), 0);
+    }
+  }
+
+  /* remove only one entry, one of the A local records */
+  zone->rmQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, { DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1") } ));
+  BOOST_CHECK_EQUAL(zone->size(), 2);
+
+  {
+    /* exact type exists */
+    const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+
+    {
+      auto records = matchingPolicy.getCustomRecords(bad2, QType::A);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      {
+        const auto& record = records.at(0);
+        BOOST_CHECK(record.d_type == QType::A);
+        BOOST_CHECK(record.d_class == QClass::IN);
+        auto content = std::dynamic_pointer_cast<ARecordContent>(record.d_content);
+        BOOST_CHECK(content != nullptr);
+        BOOST_CHECK_EQUAL(content->getCA().toString(), "192.0.2.2");
+      }
+    }
+
+    {
+      auto records = matchingPolicy.getCustomRecords(bad2, QType::MX);
+      BOOST_CHECK_EQUAL(records.size(), 1);
+      const auto& record = records.at(0);
+      BOOST_CHECK(record.d_type == QType::MX);
+      BOOST_CHECK(record.d_class == QClass::IN);
+      auto content = std::dynamic_pointer_cast<MXRecordContent>(record.d_content);
+      BOOST_CHECK(content != nullptr);
+      BOOST_CHECK_EQUAL(content->d_mxname.toString(), "garden-mail.example.net.");
+    }
+
+    {
+      /* the name exists but there is no CNAME nor matching type, so NODATA */
+      auto records = matchingPolicy.getCustomRecords(bad2, QType::AAAA);
+      BOOST_CHECK_EQUAL(records.size(), 0);
+    }
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multiple_filter_policies) {
+  DNSFilterEngine dfe;
+
+  auto zone1 = std::make_shared<DNSFilterEngine::Zone>();
+  zone1->setName("Unit test policy 0");
+
+  auto zone2 = std::make_shared<DNSFilterEngine::Zone>();
+  zone2->setName("Unit test policy 1");
+
+  const DNSName bad("bad.example.com.");
+
+  zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, { DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1.example.net.") } ));
+  zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, { DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2.example.net.") } ));
+
+  dfe.addZone(zone1);
+  dfe.addZone(zone2);
+
+  {
+    /* zone 1 should match first */
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string,bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+    BOOST_CHECK_EQUAL(records.size(), 1);
+    const auto& record = records.at(0);
+    BOOST_CHECK(record.d_type == QType::CNAME);
+    BOOST_CHECK(record.d_class == QClass::IN);
+    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    BOOST_CHECK(content != nullptr);
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1.example.net.");
+  }
+
+  {
+    /* zone 1 should still match if zone 2 has been disabled */
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), { { *(zone2->getName()), true } });
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+    BOOST_CHECK_EQUAL(records.size(), 1);
+    const auto& record = records.at(0);
+    BOOST_CHECK(record.d_type == QType::CNAME);
+    BOOST_CHECK(record.d_class == QClass::IN);
+    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    BOOST_CHECK(content != nullptr);
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1.example.net.");
+  }
+
+  {
+    /* if zone 1 is disabled, zone 2 should match */
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), { { *(zone1->getName()), true } });
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+    BOOST_CHECK_EQUAL(records.size(), 1);
+    const auto& record = records.at(0);
+    BOOST_CHECK(record.d_type == QType::CNAME);
+    BOOST_CHECK(record.d_class == QClass::IN);
+    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    BOOST_CHECK(content != nullptr);
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2.example.net.");
+  }
+
+  {
+    /* if both zones are disabled, we should not match */
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), { { *(zone1->getName()), true }, { *(zone2->getName()), true } });
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+  }
+
+}
index 5a361b15febd1c5e0a073fc420a87c2a8ab266b7..783dfefabcb1933cc4c8b2479835f2be0a63deca 100644 (file)
@@ -2797,7 +2797,7 @@ BOOST_AUTO_TEST_CASE(test_nameserver_ipv4_rpz) {
   pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
   std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
   zone->setName("Unit test policy 0");
-  zone->addNSIPTrigger(Netmask(ns, 32), pol);
+  zone->addNSIPTrigger(Netmask(ns, 32), std::move(pol));
   auto luaconfsCopy = g_luaconfs.getCopy();
   luaconfsCopy.dfe.addZone(zone);
   g_luaconfs.setState(luaconfsCopy);
@@ -2838,7 +2838,7 @@ BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz) {
   pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
   std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
   zone->setName("Unit test policy 0");
-  zone->addNSIPTrigger(Netmask(ns, 128), pol);
+  zone->addNSIPTrigger(Netmask(ns, 128), std::move(pol));
   auto luaconfsCopy = g_luaconfs.getCopy();
   luaconfsCopy.dfe.addZone(zone);
   g_luaconfs.setState(luaconfsCopy);
@@ -2880,7 +2880,7 @@ BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz) {
   pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
   std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
   zone->setName("Unit test policy 0");
-  zone->addNSTrigger(nsName, pol);
+  zone->addNSTrigger(nsName, std::move(pol));
   auto luaconfsCopy = g_luaconfs.getCopy();
   luaconfsCopy.dfe.addZone(zone);
   g_luaconfs.setState(luaconfsCopy);
@@ -2922,8 +2922,8 @@ BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz_disabled) {
   pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
   std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
   zone->setName("Unit test policy 0");
-  zone->addNSIPTrigger(Netmask(ns, 128), pol);
-  zone->addNSTrigger(nsName, pol);
+  zone->addNSIPTrigger(Netmask(ns, 128), DNSFilterEngine::Policy(pol));
+  zone->addNSTrigger(nsName, std::move(pol));
   auto luaconfsCopy = g_luaconfs.getCopy();
   luaconfsCopy.dfe.addZone(zone);
   g_luaconfs.setState(luaconfsCopy);
index eaa258b7b5d141f7f83007a142057f81198eb1f8..d878053e2ab52c8c1b5289e1c2a45522c082840a 100644 (file)
@@ -116,7 +116,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
     }
     else {
       pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom = dr.d_content;
+      pol.d_custom.emplace_back(dr.d_content);
       // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
     }
   }
@@ -126,7 +126,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
     }
     else {
       pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom = dr.d_content;
+      pol.d_custom.emplace_back(dr.d_content);
       // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": ";
     }
   }
@@ -142,37 +142,37 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
   if(dr.d_name.isPartOf(rpzNSDname)) {
     DNSName filt=dr.d_name.makeRelative(rpzNSDname);
     if(addOrRemove)
-      zone->addNSTrigger(filt, pol);
+      zone->addNSTrigger(filt, std::move(pol));
     else
-      zone->rmNSTrigger(filt, pol);
+      zone->rmNSTrigger(filt, std::move(pol));
   } else if(dr.d_name.isPartOf(rpzClientIP)) {
     DNSName filt=dr.d_name.makeRelative(rpzClientIP);
     auto nm=makeNetmaskFromRPZ(filt);
     if(addOrRemove)
-      zone->addClientTrigger(nm, pol);
+      zone->addClientTrigger(nm, std::move(pol));
     else
-      zone->rmClientTrigger(nm, pol);
+      zone->rmClientTrigger(nm, std::move(pol));
     
   } else if(dr.d_name.isPartOf(rpzIP)) {
     // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
     DNSName filt=dr.d_name.makeRelative(rpzIP);
     auto nm=makeNetmaskFromRPZ(filt);
     if(addOrRemove)
-      zone->addResponseTrigger(nm, pol);
+      zone->addResponseTrigger(nm, std::move(pol));
     else
-      zone->rmResponseTrigger(nm, pol);
+      zone->rmResponseTrigger(nm, std::move(pol));
   } else if(dr.d_name.isPartOf(rpzNSIP)) {
     DNSName filt=dr.d_name.makeRelative(rpzNSIP);
     auto nm=makeNetmaskFromRPZ(filt);
     if(addOrRemove)
-      zone->addNSIPTrigger(nm, pol);
+      zone->addNSIPTrigger(nm, std::move(pol));
     else
-      zone->rmNSIPTrigger(nm, pol);
+      zone->rmNSIPTrigger(nm, std::move(pol));
   } else {
     if(addOrRemove)
-      zone->addQNameTrigger(dr.d_name, pol);
+      zone->addQNameTrigger(dr.d_name, std::move(pol));
     else
-      zone->rmQNameTrigger(dr.d_name, pol);
+      zone->rmQNameTrigger(dr.d_name, std::move(pol));
   }
 }
 
index d6d13172b4feeea649c872b72b47ea47db5fbdff..9c3c143468e14c9a1149f94f6b09d10cbe213567 100644 (file)
@@ -98,7 +98,18 @@ class RPZServer(object):
                     dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
                     dns.rrset.from_text('d.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
                     dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
-                    dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1', '192.0.2.2'),
+                    dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.MX, '10 mx.example.'),
+                    dns.rrset.from_text('f.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'e.example.'),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
+                    ]
+            elif newSerial == 7:
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
+                    dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1', '192.0.2.2'),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.2'),
                     dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
                     ]
 
@@ -244,6 +255,20 @@ e 3600 IN A 192.0.2.42
     def checkNotBlocked(self, name, adQuery=False):
         self.checkBlocked(name, False, adQuery)
 
+    def checkCustom(self, qname, qtype, expected):
+        query = dns.message.make_query(qname, qtype, want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+
+        self.assertRRsetInAnswer(res, expected)
+
+    def checkNoData(self, qname, qtype):
+        query = dns.message.make_query(qname, qtype, want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+
+        self.assertEqual(len(res.answer), 0)
+
     def waitUntilCorrectSerialIsLoaded(self, serial, timeout=5):
         global rpzServer
 
@@ -318,13 +343,28 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('c.example.')
         self.checkBlocked('d.example.')
 
-        # sixth zone, only e should be blocked
+        # sixth zone, only e should be blocked, f is a local data record
         self.waitUntilCorrectSerialIsLoaded(6)
-        self.checkRPZStats(6, 1, 2, self._xfrDone)
+        self.checkRPZStats(6, 2, 2, self._xfrDone)
+        self.checkNotBlocked('a.example.')
+        self.checkNotBlocked('b.example.')
+        self.checkNotBlocked('c.example.')
+        self.checkNotBlocked('d.example.')
+        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'))
+        self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
+        self.checkNoData('e.example.', 'AAAA')
+        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
+
+        # seventh zone, e should only have one A
+        self.waitUntilCorrectSerialIsLoaded(7)
+        self.checkRPZStats(7, 2, 2, self._xfrDone)
         self.checkNotBlocked('a.example.')
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
-        self.checkBlocked('e.example.')
+        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'))
+        self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
+        self.checkNoData('e.example.', 'AAAA')
+        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
         # check that the policy is disabled for AD=1 queries
         self.checkNotBlocked('e.example.', True)