]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Handle RPZ CNAME chains, post-policies on cache hits as well
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 18 Aug 2020 08:45:30 +0000 (10:45 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 24 Aug 2020 15:29:25 +0000 (17:29 +0200)
pdns/filterpo.cc
pdns/filterpo.hh
pdns/pdns_recursor.cc
pdns/syncres.cc
pdns/syncres.hh

index 4b329734b93e0985161ea8ca8f49c27b21794c39..6a9dc540c93e024a3d54be7dd90630ca806b8a86 100644 (file)
@@ -285,38 +285,51 @@ bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_
 }
 
 bool DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
+{
+  for (const auto& record : records) {
+    if (getPostPolicy(record, discardedPolicies, pol)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
 {
   ComboAddress ca;
-  for (const auto& r : records) {
-    if (r.d_place != DNSResourceRecord::ANSWER)
-      continue;
-    if (r.d_type == QType::A) {
-      if (auto rec = getRR<ARecordContent>(r)) {
-        ca = rec->getCA();
-      }
+  if (record.d_place != DNSResourceRecord::ANSWER) {
+    return false;
+  }
+
+  if (record.d_type == QType::A) {
+    if (auto rec = getRR<ARecordContent>(record)) {
+      ca = rec->getCA();
     }
-    else if(r.d_type == QType::AAAA) {
-      if (auto rec = getRR<AAAARecordContent>(r)) {
-        ca = rec->getCA();
-      }
+  }
+  else if(record.d_type == QType::AAAA) {
+    if (auto rec = getRR<AAAARecordContent>(record)) {
+      ca = rec->getCA();
     }
-    else
-      continue;
+  }
+  else {
+    return false;
+  }
 
-    for (const auto& z : d_zones) {
-      if (z->getPriority() >= pol.getPriority()) {
-        break;
-      }
-      const auto& zoneName = z->getName();
-      if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
-        continue;
-      }
+  for (const auto& z : d_zones) {
+    if (z->getPriority() >= pol.getPriority()) {
+      break;
+    }
+    const auto& zoneName = z->getName();
+    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+      return false;
+    }
 
-      if (z->findResponsePolicy(ca, pol)) {
-       return true;
-      }
+    if (z->findResponsePolicy(ca, pol)) {
+      return true;
     }
   }
+
   return false;
 }
 
index 3828505bf1d510d524e4302bc5501d25d7ebed90..021aee75e4204b390de8506b4c42c567ebe5ba8f 100644 (file)
@@ -347,6 +347,7 @@ public:
   bool getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
   bool getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
   bool getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+  bool getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
 
   // A few convenience methods for the unit test code
   Policy getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
index 6234460d4e160a33e29849bcb05e44d7644ea268..eb3cd54ecc4a7a229ed488bbaf58441497cde9a5 100644 (file)
@@ -881,6 +881,75 @@ static bool addRecordToPacket(DNSPacketWriter& pw, const DNSRecord& rec, uint32_
   return true;
 }
 
+enum class PolicyResult : uint8_t { NoAction, HaveAnswer, Drop };
+
+static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& dc, SyncRes& sr, int& res, vector<DNSRecord>& ret, DNSPacketWriter& pw, bool post)
+{
+  /* don't account truncate actions for TCP queries, since they are not applied */
+  if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !dc->d_tcp) {
+    ++g_stats.policyResults[appliedPolicy.d_kind];
+  }
+
+  switch (appliedPolicy.d_kind) {
+
+  case DNSFilterEngine::PolicyKind::NoAction:
+      return PolicyResult::NoAction;
+
+  case DNSFilterEngine::PolicyKind::Drop:
+    ++g_stats.policyDrops;
+    return PolicyResult::Drop;
+
+  case DNSFilterEngine::PolicyKind::NXDOMAIN:
+    ret.clear();
+    res = RCode::NXDomain;
+    return PolicyResult::HaveAnswer;
+
+  case DNSFilterEngine::PolicyKind::NODATA:
+    ret.clear();
+    res = RCode::NoError;
+    return PolicyResult::HaveAnswer;
+
+  case DNSFilterEngine::PolicyKind::Truncate:
+    if (!dc->d_tcp) {
+      ret.clear();
+      res = RCode::NoError;
+      pw.getHeader()->tc = 1;
+      return PolicyResult::HaveAnswer;
+    }
+    return PolicyResult::NoAction;
+
+  case DNSFilterEngine::PolicyKind::Custom:
+    res = RCode::NoError;
+    {
+      auto spoofed = appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+      for (auto& dr : spoofed) {
+        ret.push_back(dr);
+        try {
+          handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
+        }
+        catch (const ImmediateServFailException& e) {
+          if (g_logCommonErrors) {
+            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
+          }
+          res = RCode::ServFail;
+          break;
+        }
+        catch (const PolicyHitException& e) {
+          if (g_logCommonErrors) {
+            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << dc->d_mdp.d_qname << "' because another RPZ policy was hit" << endl;
+          }
+          res = RCode::ServFail;
+          break;
+        }
+      }
+
+      return PolicyResult::HaveAnswer;
+    }
+  }
+
+  return PolicyResult::NoAction;
+}
+
 #ifdef HAVE_PROTOBUF
 static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobufServers(const ProtobufExportConfig& config)
 {
@@ -1194,104 +1263,6 @@ int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret)
   return rcode;
 }
 
-enum class PolicyResult : uint8_t { NoAction, HaveAnswer, Drop };
-
-static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& dc, SyncRes& sr, int& res, vector<DNSRecord>& ret, DNSPacketWriter& pw, bool post)
-{
-  /* don't account truncate actions for TCP queries, since they are not applied */
-  if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !dc->d_tcp) {
-    ++g_stats.policyResults[appliedPolicy.d_kind];
-  }
-
-  switch (appliedPolicy.d_kind) {
-
-  case DNSFilterEngine::PolicyKind::NoAction:
-      return PolicyResult::NoAction;
-
-  case DNSFilterEngine::PolicyKind::Drop:
-    ++g_stats.policyDrops;
-    return PolicyResult::Drop;
-
-  case DNSFilterEngine::PolicyKind::NXDOMAIN:
-    ret.clear();
-    res = RCode::NXDomain;
-    return PolicyResult::HaveAnswer;
-
-  case DNSFilterEngine::PolicyKind::NODATA:
-    ret.clear();
-    res = RCode::NoError;
-    return PolicyResult::HaveAnswer;
-
-  case DNSFilterEngine::PolicyKind::Truncate:
-    if (!dc->d_tcp) {
-      ret.clear();
-      res = RCode::NoError;
-      pw.getHeader()->tc = 1;
-      return PolicyResult::HaveAnswer;
-    }
-    return PolicyResult::NoAction;
-
-  case DNSFilterEngine::PolicyKind::Custom:
-    res = RCode::NoError;
-    //cerr << "current answer(" << post << ") Q: " << dc->d_mdp.d_qname << '/' << QType(dc->d_mdp.d_qtype).getName() << endl;
-    //for (auto r : ret) {
-    //  cerr << r.d_place << ' ' << r.d_name << ' ' << QType(r.d_type).getName() << ' ' << r.d_content->getZoneRepresentation() << endl;
-    //}
-    //cerr << "------------" << endl;
-    // In some cases, the policy should extend the result vector and in some cases replace
-    // We extend if the current vector contains a CNAME we found while resolving a non-CNAME
-    // This is all very ugly, but ATM I don't know a better approach...
-    if (dc->d_mdp.d_qtype != QType::CNAME) {
-      bool cname = false;
-      for (const auto& r : ret) {
-        if (r.d_place == DNSResourceRecord::ANSWER && r.d_type == QType::CNAME) {
-          cname = true;
-          break;
-        }
-      }
-      if (!cname) {
-        ret.clear();
-      }
-    }
-    if (post && ret.size() == 0) { // can happen with NS matches, those do not fill the result, fallback to original behaviour
-      auto spoofed = appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
-      for (auto& dr : spoofed) {
-        ret.push_back(dr);
-        try {
-          handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
-        }
-        catch (const ImmediateServFailException& e) {
-          if (g_logCommonErrors) {
-            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
-          }
-          res = RCode::ServFail;
-          break;
-        }
-        catch (const PolicyHitException& e) {
-          if (g_logCommonErrors) {
-            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '"<<dc->d_mdp.d_qname<<"' because another RPZ policy was hit"<<endl;
-          }
-          res = RCode::ServFail;
-          break;
-        }
-      }
-    }
-    // Do we have an answer, or only a CNAME match while not looking for CNAME?
-    // In the latter case we should call SyncRes.beginResolve() to chase the CNAME.
-    bool haveanswer = false;
-    for (const auto& r : ret) {
-      if (r.d_place == DNSResourceRecord::ANSWER && r.d_type == dc->d_mdp.d_qtype) {
-        haveanswer = true;
-        break;
-      }
-    }
-
-    return haveanswer ? PolicyResult::HaveAnswer : PolicyResult::NoAction;
-  }
-
-  return PolicyResult::NoAction;
-}
-
 static void startDoResolve(void *p)
 {
   auto dc=std::unique_ptr<DNSComboWriter>(reinterpret_cast<DNSComboWriter*>(p));
@@ -1423,6 +1394,7 @@ static void startDoResolve(void *p)
     sr.setFrameStreamServers(t_frameStreamServers);
 #endif
     sr.setQuerySource(dc->d_remote, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
+    sr.setQueryReceivedOverTCP(dc->d_tcp);
 
     bool tracedQuery=false; // we could consider letting Lua know about this too
     bool shouldNotValidate = false;
@@ -1487,22 +1459,34 @@ static void startDoResolve(void *p)
       if (luaconfsLocal->dfe.getClientPolicy(dc->d_source, sr.d_discardedPolicies, appliedPolicy)) {
         mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
       }
-      if ((appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
-        if (luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, sr.d_discardedPolicies, appliedPolicy)) {
-          mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
+     }
+
+    /* If we already have an answer generated from gettag_ffi, let's see if the filtering policies
+       should be applied to it */
+    if (dc->d_rcode != boost::none) {
+
+      bool policyOverride = false;
+      /* Unless we already matched on the client IP, time to check the qname.
+         We normally check it in beginResolve() but it will be bypassed since we already have an answer */
+      if (wantsRPZ && appliedPolicy.policyOverridesGettag()) {
+        if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+          // Client IP already matched
+        }
+        else {
+          // no match on the client IP, check the qname
+          if (luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, sr.d_discardedPolicies, appliedPolicy)) {
+            // got a match
+            mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
+          }
+        }
+
+        if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
+          policyOverride = true;
         }
       }
-    }
 
-    // If we are doing RPZ and a policy was matched on the qname, it normally takes precedence over an answer from gettag.
-    // So process the gettag_ffi answer only if no RPZ action was matched or the policy indicates gettag should
-    // have precedence.
-    // Note that this case will only be entered if the policy hit was on the client or on the original qname,
-    // hits while chasing CNAMEs will not be known here yet, since they only will be discovered by SyncRes.beginResolve().
-    if (!wantsRPZ || !appliedPolicy.policyOverridesGettag() || appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
-      if (dc->d_rcode != boost::none) {
-        /* we have a response ready to go, most likely from gettag_ffi */
+      if (policyOverride) {
+        /* No RPZ or gettag overrides it anyway */
         ret = std::move(dc->d_records);
         res = *dc->d_rcode;
         if (res == RCode::NoError && dc->d_followCNAMERecords) {
@@ -1544,13 +1528,24 @@ static void startDoResolve(void *p)
         res = sr.beginResolve(dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_mdp.d_qclass, ret);
         shouldNotValidate = sr.wasOutOfBand();
       }
-      catch(const ImmediateServFailException &e) {
+      catch (const ImmediateQueryDropException& e) {
+#warning We need to export a protobuf (and NOD lookup?) message if requested!
+        g_stats.policyDrops++;
+        g_log<<Logger::Debug<<"Dropping query because of a filtering policy "<<makeLoginfo(dc)<<endl;
+        return;
+      }
+      catch (const ImmediateServFailException &e) {
         if(g_logCommonErrors) {
           g_log<<Logger::Notice<<"Sending SERVFAIL to "<<dc->getRemote()<<" during resolve of '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
         }
         res = RCode::ServFail;
       }
-      catch(const PolicyHitException& e) {
+      catch (const SendTruncatedAnswerException& e) {
+        ret.clear();
+        res = RCode::NoError;
+        pw.getHeader()->tc = 1;
+      }
+      catch (const PolicyHitException& e) {
         res = -2;
       }
       dq.validationState = sr.getValidationState();
@@ -1571,12 +1566,6 @@ static void startDoResolve(void *p)
         }
       }
 
-      if (wantsRPZ && (appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
-        if (luaconfsLocal->dfe.getPostPolicy(ret, sr.d_discardedPolicies, appliedPolicy)) {
-          mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
-        }
-      }
-
       if (t_pdl || (g_dns64Prefix && dq.qtype == QType::AAAA && dq.validationState != vState::Bogus)) {
         if (res == RCode::NoError) {
           auto i = ret.cbegin();
@@ -1606,23 +1595,9 @@ static void startDoResolve(void *p)
           shouldNotValidate = true;
         }
       }
-
-      if (wantsRPZ) { //XXX This block is repeated, see above
-
-        auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, true);
-        if (policyResult == PolicyResult::HaveAnswer) {
-          goto haveAnswer;
-        }
-        else if (policyResult == PolicyResult::Drop) {
-          return;
-        }
-      }
     }
+
   haveAnswer:;
-    if(res == PolicyDecision::DROP) {
-      g_stats.policyDrops++;
-      return;
-    }
     if(tracedQuery || res == -1 || res == RCode::ServFail || pw.getHeader()->rcode == RCode::ServFail)
     { 
       string trace(sr.getTrace());
@@ -2002,13 +1977,13 @@ static void startDoResolve(void *p)
 
     //    cout<<dc->d_mdp.d_qname<<"\t"<<MT->getUsec()<<"\t"<<sr.d_outqueries<<endl;
   }
-  catch(PDNSException &ae) {
+  catch (const PDNSException &ae) {
     g_log<<Logger::Error<<"startDoResolve problem "<<makeLoginfo(dc)<<": "<<ae.reason<<endl;
   }
-  catch(const MOADNSException &mde) {
+  catch (const MOADNSException &mde) {
     g_log<<Logger::Error<<"DNS parser error "<<makeLoginfo(dc) <<": "<<dc->d_mdp.d_qname<<", "<<mde.what()<<endl;
   }
-  catch(std::exception& e) {
+  catch (const std::exception& e) {
     g_log<<Logger::Error<<"STL error "<< makeLoginfo(dc)<<": "<<e.what();
 
     // Luawrapper nests the exception from Lua, so we unnest it here
index b89e1dd4846211b299f7aaf4cd2241917d039c69..6254c4b718f7521083007bedb758f82cbcadf583 100644 (file)
@@ -638,72 +638,27 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
   return ret;
 }
 
-bool SyncRes::qnameRPZHit(const DNSFilterEngine& dfe, DNSName& target, const QType& qtype, vector<DNSRecord> &ret, unsigned int depth)
-{
-  if (!d_wantsRPZ) {
-    return false;
-  }
-  if (s_maxdepth && depth > s_maxdepth) {
-    string prefix = d_prefix;
-    prefix.append(depth, ' ');
-    string msg = "More than " + std::to_string(s_maxdepth) + " (max-recursion-depth) levels of recursion needed while resolving " + target.toLogString();
-    LOG(prefix << target << ": " << msg << endl);
-    throw ImmediateServFailException(msg);
-  }
+#define QLOG(x) LOG(prefix << " child=" <<  child << ": " << x << endl)
 
-  bool match = dfe.getQueryPolicy(target, d_discardedPolicies, d_appliedPolicy, true);
-  if (!match) {
-    return false;
-  }
+int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state) {
 
-  mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-  if (d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction) {
-    return false;
-  }
-  LOG(": (hit by RPZ policy '" + d_appliedPolicy.getName() + "')" << endl);
-  if (d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::Truncate) {
-    // XXX We don't know if we're doing TCP here....
-    return false;
-  }
-  if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Custom) {
-    return true;
-  }
-  auto spoofed = d_appliedPolicy.getCustomRecords(target, qtype.getCode());
+  string prefix = d_prefix;
+  prefix.append(depth, ' ');
+  auto luaconfsLocal = g_luaconfs.getLocal();
 
-  // Add the record to the result vector being built, chase if we hit a CNAME
-  for (const auto& dr : spoofed) {
-    if (dr.d_place != DNSResourceRecord::ANSWER) {
-      continue;
-    }
-    ret.push_back(dr);
-    switch (dr.d_type) {
-    case QType::CNAME:
-      auto cnamecontent = getRR<CNAMERecordContent>(dr);
-      if (cnamecontent) {
-        target = cnamecontent->getTarget();
-        // This call wil return true if we hit a policy that needs an throw PolicyHitException
-        // For CNAME chasing, we don't want that since resolving should continue with the new target
-        return qnameRPZHit(dfe, target, qtype, ret, depth + 1);
+  /* Apply qname (including CNAME chain) filtering policies */
+  if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+    if (luaconfsLocal->dfe.getQueryPolicy(qname, d_discardedPolicies, d_appliedPolicy)) {
+      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+      bool done = false;
+      int rcode = RCode::NoError;
+      handlePolicyHit(prefix, qname, qtype, ret, done, rcode, depth);
+      if (done) {
+        return rcode;
       }
-      break;
     }
   }
 
-  return true;
-}
-
-#define QLOG(x) LOG(prefix << " child=" <<  child << ": " << x << endl)
-
-int SyncRes::doResolve(const DNSName &qnameArg, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state) {
-
-  DNSName qname(qnameArg);
-  auto luaconfsLocal = g_luaconfs.getLocal();
-
-  // Can change qname
-  bool hit = qnameRPZHit(luaconfsLocal->dfe, qname, qtype, ret, depth + 1);
-  if (hit) {
-    throw PolicyHitException();
-  }
   // In the auth or recursive forward case, it does not make sense to do qname-minimization
   if (!getQNameMinimization() || isRecursiveForwardOrAuth(qname)) {
     return doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
@@ -725,8 +680,6 @@ int SyncRes::doResolve(const DNSName &qnameArg, const QType &qtype, vector<DNSRe
   // moves to three labels per iteration after three iterations.
 
   DNSName child;
-  string prefix = d_prefix;
-  prefix.append(depth, ' ');
   prefix.append(string("QM ") + qname.toString() + "|" + qtype.getName());
 
   QLOG("doResolve");
@@ -917,23 +870,46 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty
       }
     }
 
-    if(doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
+    if (doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
       d_wasOutOfBand = wasAuthZone;
       // Do not set *fromCache; res does not reflect the final result in all cases
+        /* Apply Post filtering policies */
+
+      if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+        auto luaLocal = g_luaconfs.getLocal();
+        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          bool done = false;
+          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+        }
+      }
+
       return res;
     }
 
-    if(doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
+    if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
       // we done
       d_wasOutOfBand = wasAuthZone;
-      if (fromCache)
+      if (fromCache) {
         *fromCache = true;
+      }
+
+      if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+        auto luaLocal = g_luaconfs.getLocal();
+        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          bool done = false;
+          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+        }
+      }
+
       return res;
     }
   }
 
-  if(d_cacheonly)
+  if (d_cacheonly) {
     return 0;
+  }
 
   LOG(prefix<<qname<<": No cache hit for '"<<qname<<"|"<<qtype.getName()<<"', trying to find an appropriate NS record"<<endl);
 
@@ -956,8 +932,21 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty
 
   LOG(prefix<<qname<<": initial validation status for "<<qname<<" is "<<state<<endl);
 
-  if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation)))
+  res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation);
+
+  /* Apply Post filtering policies */
+  if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+    auto luaLocal = g_luaconfs.getLocal();
+    if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+      bool done = false;
+      handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+    }
+  }
+
+  if (!res) {
     return 0;
+  }
 
   LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
 
@@ -1951,13 +1940,100 @@ static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
   ret.insert(ret.end(), ne.DNSSECRecords.signatures.begin(), ne.DNSSECRecords.signatures.end());
 }
 
+static bool rpzHitShouldReplaceContent(const DNSName& qname, const QType& qtype, const std::vector<DNSRecord>& records)
+{
+  if (qtype == QType::CNAME) {
+    return true;
+  }
+
+  for (const auto& record : records) {
+    if (record.d_type == QType::CNAME) {
+      if (auto content = getRR<CNAMERecordContent>(record)) {
+        if (qname == content->getTarget()) {
+          /* we have a CNAME whose target matches the entry we are about to
+             generate, so it will complete the current records, not replace
+             them
+          */
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, const QType& qtype, std::vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth)
+{
+  /* don't account truncate actions for TCP queries, since they are not applied */
+  if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !d_queryReceivedOverTCP) {
+    ++g_stats.policyResults[d_appliedPolicy.d_kind];
+  }
+
+  cerr<<"Handling policy hit for "<<qname<<" of type "<<(int)d_appliedPolicy.d_kind<<endl;
+  switch (d_appliedPolicy.d_kind) {
+
+  case DNSFilterEngine::PolicyKind::NoAction:
+      return;
+
+  case DNSFilterEngine::PolicyKind::Drop:
+    ++g_stats.policyDrops;
+    throw ImmediateQueryDropException();
+
+  case DNSFilterEngine::PolicyKind::NXDOMAIN:
+    ret.clear();
+    rcode = RCode::NXDomain;
+    done = true;
+    return;
+
+  case DNSFilterEngine::PolicyKind::NODATA:
+    ret.clear();
+    rcode = RCode::NoError;
+    done = true;
+    return;
+
+  case DNSFilterEngine::PolicyKind::Truncate:
+    if (!d_queryReceivedOverTCP) {
+      ret.clear();
+      rcode = RCode::NoError;
+      throw SendTruncatedAnswerException();
+    }
+    return;
+
+  case DNSFilterEngine::PolicyKind::Custom:
+    {
+      cerr<<"custom"<<endl;
+      if (rpzHitShouldReplaceContent(qname, qtype, ret)) {
+        cerr<<"clearing"<<endl;
+        ret.clear();
+      }
+
+      rcode = RCode::NoError;
+      done = true;
+      auto spoofed = d_appliedPolicy.getCustomRecords(qname, qtype.getCode());
+      for (auto& dr : spoofed) {
+        ret.push_back(dr);
+
+        if (dr.d_name == qname && dr.d_type == QType::CNAME && qtype != QType::CNAME) {
+          if (auto content = getRR<CNAMERecordContent>(dr)) {
+#warning FIXME shall we stop RPZ processing from here?
+            // https://tools.ietf.org/html/draft-vixie-dnsop-dns-rpz-00#section-6
+            vState newTargetState = vState::Indeterminate;
+            handleNewTarget(prefix, qname, content->getTarget(), qtype.getCode(), ret, rcode, depth, {}, newTargetState);
+          }
+        }
+      }
+    }
+  }
+}
+
 bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
 {
   /* we skip RPZ processing if:
      - it was disabled (d_wantsRPZ is false) ;
      - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
      the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
-     process any further RPZ rules.
+     process any further RPZ rules. Except that we need to process rules of higher priority..
   */
   if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
     for (auto const &ns : nameservers) {
@@ -1992,7 +2068,7 @@ bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAd
      - it was disabled (d_wantsRPZ is false) ;
      - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
      the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
-     process any further RPZ rules.
+     process any further RPZ rules. Except that we need to process rules of higher priority..
   */
   if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
     bool match = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies, d_appliedPolicy);
@@ -2321,6 +2397,8 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
   }
 
   const bool oldCacheOnly = setCacheOnly(false);
+  const bool oldWantsRPZ = d_wantsRPZ;
+  d_wantsRPZ = false;
 
   dsmap_t ds;
   vState cutState = getDSRecords(end, ds, false, depth);
@@ -2329,6 +2407,7 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
 
   if (!shouldValidate()) {
     setCacheOnly(oldCacheOnly);
+    d_wantsRPZ = oldWantsRPZ;
     return;
   }
 
@@ -2397,6 +2476,7 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
     }
   }
   setCacheOnly(oldCacheOnly);
+  d_wantsRPZ = oldWantsRPZ;
 }
 
 vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth)
@@ -3050,7 +3130,7 @@ dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, cons
   return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE);
 }
 
-bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherWildcardProof, const unsigned int wildcardLabelsCount)
+bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherWildcardProof, const unsigned int wildcardLabelsCount, int& rcode, unsigned int depth)
 {
   bool done = false;
   DNSName dnameTarget, dnameOwner;
@@ -3073,8 +3153,10 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       LOG(prefix<<qname<<": got negative caching indication for name '"<<qname<<"' (accept="<<rec.d_name.isPartOf(auth)<<"), newtarget='"<<newtarget<<"'"<<endl);
 
       rec.d_ttl = min(rec.d_ttl, s_maxnegttl);
-      if(newtarget.empty()) // only add a SOA if we're not going anywhere after this
+      // only add a SOA if we're not going anywhere after this
+      if (newtarget.empty()) {
         ret.push_back(rec);
+      }
 
       NegCache::NegCacheEntry ne;
 
@@ -3122,7 +3204,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         }
         ret.push_back(rec);
         if (auto content = getRR<CNAMERecordContent>(rec)) {
-          newtarget=content->getTarget();
+          newtarget=DNSName(content->getTarget());
         }
       } else if (rec.d_type == QType::DNAME && qname.isPartOf(rec.d_name)) { // DNAME
         ret.push_back(rec);
@@ -3167,7 +3249,8 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     {
       LOG(prefix<<qname<<": answer is in: resolved to '"<< rec.d_content->getZoneRepresentation()<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"'"<<endl);
 
-      done=true;
+      done = true;
+      rcode = RCode::NoError;
 
       if (state == vState::Secure && needWildcardProof) {
         /* We have a positive answer synthesized from a wildcard, we need to check that we have
@@ -3201,6 +3284,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
           updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
         }
       }
+
       ret.push_back(rec);
     }
     else if((rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::ANSWER) {
@@ -3262,8 +3346,8 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         }
       }
     }
-    else if(!done && rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::SOA &&
-            lwr.d_rcode==RCode::NoError && qname.isPartOf(rec.d_name)) {
+    else if (!done && rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::SOA &&
+            lwr.d_rcode == RCode::NoError && qname.isPartOf(rec.d_name)) {
       LOG(prefix<<qname<<": got negative caching indication for '"<< qname<<"|"<<qtype.getName()<<"'"<<endl);
 
       if(!newtarget.empty()) {
@@ -3311,7 +3395,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     cnamerec.d_type = QType::CNAME;
     cnamerec.d_ttl = dnameTTL;
     cnamerec.d_content = std::make_shared<CNAMERecordContent>(CNAMERecordContent(newtarget));
-    ret.push_back(cnamerec);
+    ret.push_back(std::move(cnamerec));
   }
   return done;
 }
@@ -3464,6 +3548,49 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
   return true;
 }
 
+void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, uint16_t qtype, std::vector<DNSRecord>& ret, int& rcode, int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state)
+{
+  if (newtarget == qname) {
+    LOG(prefix<<qname<<": status=got a CNAME referral to self, returning SERVFAIL"<<endl);
+    ret.clear();
+    rcode = RCode::ServFail;
+    return;
+  }
+
+  if (depth > 10) {
+    LOG(prefix<<qname<<": status=got a CNAME referral, but recursing too deep, returning SERVFAIL"<<endl);
+    rcode = RCode::ServFail;
+    return;
+  }
+
+  // Check to see if we already have seen the new target as a previous target
+  if (scanForCNAMELoop(newtarget, ret)) {
+    LOG(prefix<<qname<<": status=got a CNAME referral that causes a loop, returning SERVFAIL"<<endl);
+    ret.clear();
+    rcode = RCode::ServFail;
+    return;
+  }
+
+  if (qtype == QType::DS || qtype == QType::DNSKEY) {
+    LOG(prefix<<qname<<": status=got a CNAME referral, but we are looking for a DS or DNSKEY"<<endl);
+
+    if (d_doDNSSEC) {
+      addNXNSECS(ret, recordsFromAnswer);
+    }
+
+    rcode = RCode::NoError;
+    return;
+  }
+
+  LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
+
+  set<GetBestNSAnswer> beenthere;
+  vState cnameState = vState::Indeterminate;
+  rcode = doResolve(newtarget, QType(qtype), ret, depth + 1, beenthere, cnameState);
+  LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<state<<" with the state from the CNAME quest: "<<cnameState<<endl);
+  updateValidationState(state, cnameState);
+}
+
 bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state)
 {
   string prefix;
@@ -3503,56 +3630,17 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   DNSName newauth;
   DNSName newtarget;
 
-  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount);
+  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount, *rcode, depth);
 
-  if(done){
+  if (done){
     LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
     LOG(prefix<<qname<<": validation status is "<<state<<endl);
-    *rcode = RCode::NoError;
     return true;
   }
 
-  if(!newtarget.empty()) {
-    if(newtarget == qname) {
-      LOG(prefix<<qname<<": status=got a CNAME referral to self, returning SERVFAIL"<<endl);
-      ret.clear();
-      *rcode = RCode::ServFail;
-      return true;
-    }
-
-    if(depth > 10) {
-      LOG(prefix<<qname<<": status=got a CNAME referral, but recursing too deep, returning SERVFAIL"<<endl);
-      *rcode = RCode::ServFail;
-      return true;
-    }
-
-    // Check to see if we already have seen the new target as a previous target
-    if (scanForCNAMELoop(newtarget, ret)) {
-      LOG(prefix<<qname<<": status=got a CNAME referral that causes a loop, returning SERVFAIL"<<endl);
-      ret.clear();
-      *rcode = RCode::ServFail;
-      return true;
-    }
-
-    if (qtype == QType::DS || qtype == QType::DNSKEY) {
-      LOG(prefix<<qname<<": status=got a CNAME referral, but we are looking for a DS or DNSKEY"<<endl);
-
-      if(d_doDNSSEC)
-        addNXNSECS(ret, lwr.d_records);
-
-      *rcode = RCode::NoError;
-      return true;
-    }
-    else {
-      LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
-
-      set<GetBestNSAnswer> beenthere2;
-      vState cnameState = vState::Indeterminate;
-      *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
-      LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<state<<" with the state from the CNAME quest: "<<cnameState<<endl);
-      updateValidationState(state, cnameState);
-      return true;
-    }
+  if (!newtarget.empty()) {
+    handleNewTarget(prefix, qname, newtarget, qtype.getCode(), ret, *rcode, depth, lwr.d_records, state);
+    return true;
   }
 
   if(lwr.d_rcode == RCode::NXDomain) {
index 48d4ea86f6f996deb653978e2727dfab7b03a45b..a1dbd8ca554190f7fdbc10a0496545e949b4bd75 100644 (file)
@@ -730,6 +730,11 @@ public:
     return d_queryValidationState;
   }
 
+  void setQueryReceivedOverTCP(bool tcp)
+  {
+    d_queryReceivedOverTCP = tcp;
+  }
+
   static thread_local ThreadLocalStorage t_sstorage;
 
   static std::atomic<uint64_t> s_queries;
@@ -823,7 +828,6 @@ private:
   bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType& qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool* truncated);
   bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state);
 
-  bool qnameRPZHit(const DNSFilterEngine& dfe, DNSName& target, const QType& qtype, vector<DNSRecord>& ret, unsigned int depth);
   int doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state);
   int doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL, bool considerforwards = true);
   bool doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res);
@@ -848,7 +852,7 @@ private:
 
   void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
   RCode::rcodes_ updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery);
-  bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherwildcardProof, const unsigned int wildcardLabelsCount);
+  bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherwildcardProof, const unsigned int wildcardLabelsCount, int& rcode, unsigned int depth);
 
   bool doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t qclass, vector<DNSRecord> &ret);
 
@@ -873,6 +877,10 @@ private:
   bool lookForCut(const DNSName& qname, unsigned int depth, const vState existingState, vState& newState);
   void computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned int depth);
 
+  void handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, uint16_t qtype, std::vector<DNSRecord>& ret, int& rcode, int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state);
+
+  void handlePolicyHit(const std::string& prefix, const DNSName& qname, const QType& qtype, vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth);
+
   void setUpdatingRootNS()
   {
     d_updatingRootNS = true;
@@ -905,6 +913,7 @@ private:
   bool d_wasOutOfBand{false};
   bool d_wasVariable{false};
   bool d_qNameMinimization{false};
+  bool d_queryReceivedOverTCP{false};
 
   LogMode d_lm;
 };
@@ -1067,6 +1076,14 @@ class PolicyHitException
 {
 };
 
+class ImmediateQueryDropException
+{
+};
+
+class SendTruncatedAnswerException
+{
+};
+
 typedef boost::circular_buffer<ComboAddress> addrringbuf_t;
 extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;