]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/filterpo.cc
dnsdist: Add HTTPStatusAction to return a specific HTTP response
[thirdparty/pdns.git] / pdns / filterpo.cc
index c73b202e6b07745e6c04c8701e0f0652ba91d28d..72f7c8b856418347c9a6b2796eaf72e2e9d4cf99 100644 (file)
@@ -31,14 +31,14 @@ DNSFilterEngine::DNSFilterEngine()
 {
 }
 
-bool DNSFilterEngine::Zone::findQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
+bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
 {
-  return findNamedPolicy(d_qpolName, qname, pol);
+  return findExactNamedPolicy(d_qpolName, qname, pol);
 }
 
-bool DNSFilterEngine::Zone::findNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
+bool DNSFilterEngine::Zone::findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
 {
-  return findNamedPolicy(d_propolName, qname, pol);
+  return findExactNamedPolicy(d_propolName, qname, pol);
 }
 
 bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
@@ -68,8 +68,12 @@ bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilter
   return false;
 }
 
-bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol) const
+bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
 {
+  if (polmap.empty()) {
+    return false;
+  }
+
   /* for www.powerdns.com, we need to check:
      www.powerdns.com.
        *.powerdns.com.
@@ -96,21 +100,70 @@ bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DN
   return false;
 }
 
+bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
+{
+  if (polmap.empty()) {
+    return false;
+  }
+
+  const auto& it = polmap.find(qname);
+  if (it != polmap.end()) {
+    pol = it->second;
+    return true;
+  }
+
+  return false;
+}
+
 DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies) const
 {
-  //  cout<<"Got question for nameserver name "<<qname<<endl;
-  Policy pol;
-  for(const auto& z : d_zones) {
+  // cout<<"Got question for nameserver name "<<qname<<endl;
+  std::vector<bool> zoneEnabled(d_zones.size());
+  size_t count = 0;
+  bool allEmpty = true;
+  for (const auto& z : d_zones) {
+    bool enabled = true;
     const auto zoneName = z->getName();
-    if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
-      continue;
+    if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+      enabled = false;
+    }
+    else {
+      if (z->hasNSPolicies()) {
+        allEmpty = false;
+      }
+      else {
+        enabled = false;
+      }
     }
 
-    if(z->findNSPolicy(qname, pol)) {
-      //      cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
-      return pol;
+    zoneEnabled[count] = enabled;
+    ++count;
+  }
+
+  Policy pol;
+  if (!allEmpty) {
+    count = 0;
+    for(const auto& z : d_zones) {
+      if (zoneEnabled[count] && z->findExactNSPolicy(qname, pol)) {
+        // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+        return pol;
+      }
+      ++count;
+    }
+
+    DNSName s(qname);
+    while(s.chopOff()){
+      count = 0;
+      for(const auto& z : d_zones) {
+        if (zoneEnabled[count] && z->findExactNSPolicy(g_wildcarddnsname+s, pol)) {
+          // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+          return pol;
+        }
+        ++count;
+      }
     }
   }
+
   return pol;
 }
 
@@ -135,19 +188,55 @@ DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const ComboAddress&
 DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies) const
 {
   //  cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
-  Policy pol;
-  for(const auto& z : d_zones) {
+  std::vector<bool> zoneEnabled(d_zones.size());
+  size_t count = 0;
+  bool allEmpty = true;
+  for (const auto& z : d_zones) {
+    bool enabled = true;
     const auto zoneName = z->getName();
-    if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
-      continue;
+    if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+      enabled = false;
+    }
+    else {
+      if (z->hasQNamePolicies()) {
+        allEmpty = false;
+      }
+      else {
+        enabled = false;
+      }
     }
 
-    if(z->findQNamePolicy(qname, pol)) {
-      //      cerr<<"Had a hit on the name of the query"<<endl;
-      return pol;
+    zoneEnabled[count] = enabled;
+    ++count;
+  }
+
+  Policy pol;
+  if (!allEmpty) {
+    count = 0;
+    for(const auto& z : d_zones) {
+      if (zoneEnabled[count] && z->findExactQNamePolicy(qname, pol)) {
+        //      cerr<<"Had a hit on the name of the query"<<endl;
+        return pol;
+      }
+      ++count;
     }
 
-    if(z->findClientPolicy(ca, pol)) {
+    DNSName s(qname);
+    while(s.chopOff()){
+      count = 0;
+      for(const auto& z : d_zones) {
+        if (zoneEnabled[count] && z->findExactQNamePolicy(g_wildcarddnsname+s, pol)) {
+          //      cerr<<"Had a hit on the name of the query"<<endl;
+          return pol;
+        }
+        ++count;
+      }
+    }
+  }
+
+  count = 0;
+  for(const auto& z : d_zones) {
+    if (zoneEnabled[count] && z->findClientPolicy(ca, pol)) {
       //       cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
       return pol;
     }
@@ -196,107 +285,188 @@ 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, bool ignoreDuplicate)
 {
-  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 (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+      throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following QName: " + n.toLogString());
+    }
+
+    if (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
+      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.toLogString());
+    }
+
+    existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
+
+    std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_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,35 +482,59 @@ 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
+void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol)
 {
-  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)
 {
   int bits = nm.getBits();
   DNSName res(std::to_string(bits));
-  const auto addr = nm.getNetwork();
+  const auto& addr = nm.getNetwork();
 
   if (addr.isIPv4()) {
     const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&addr.sin4.sin_addr.s_addr);
@@ -381,13 +575,15 @@ DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
 }
 
 
-void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol) const
+void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol)
 {
   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