]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Make sure we don't miss insecure cuts, fix several DNSSEC issues
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 4 Feb 2021 15:21:13 +0000 (16:21 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 4 Mar 2021 09:58:14 +0000 (10:58 +0100)
pdns/recursordist/test-syncres_cc5.cc
pdns/recursordist/test-syncres_cc6.cc
pdns/recursordist/test-syncres_cc8.cc
pdns/syncres.cc
pdns/syncres.hh
pdns/validate.cc

index 042dd6eacbda01967355b9f814e8a6dc9accfe29..ed076331c9d3e73727aa597649e82ac37a74921a 100644 (file)
@@ -932,7 +932,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 
   /* again, to test the cache */
   ret.clear();
@@ -944,7 +944,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
     /* check that we applied the lowest TTL, here this is from the NSEC proving that the exact name did not exist */
     BOOST_CHECK_LE(rec.d_ttl, 60U);
   }
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
@@ -1054,7 +1054,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 
   /* again, to test the cache */
   ret.clear();
@@ -1066,7 +1066,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
     /* check that we applied the lowest TTL, here this is from the NSEC proving that the exact name did not exist */
     BOOST_CHECK_LE(rec.d_ttl, 60U);
   }
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard)
@@ -1534,7 +1534,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 8U);
 
   /* again, to test the cache */
   ret.clear();
@@ -1546,7 +1546,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
     /* check that we applied the lowest TTL, here this is from the NSEC3 proving that the exact name did not exist (next closer) */
     BOOST_CHECK_LE(rec.d_ttl, 60U);
   }
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
@@ -1653,7 +1653,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 
   /* again, to test the cache */
   ret.clear();
@@ -1661,7 +1661,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
@@ -1759,7 +1759,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusInvalidDenial);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 
   /* again, to test the cache */
   ret.clear();
@@ -1767,7 +1767,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusInvalidDenial);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
-  BOOST_CHECK_EQUAL(queriesCount, 6U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_expanded_onto_itself)
index 5f8fb4ef8b5d05adeb17f1f59fd130fb9df3f9ad..acfa4c5c50b7707acd7bfa4394cdc90cc96aac99 100644 (file)
@@ -1281,4 +1281,202 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut)
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_without_ds)
+{
+  /* the last zone has signatures but the DS has not been added
+     to the parent zone yet, so it should be insecure */
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("www.powerdns.com.");
+  const ComboAddress targetAddr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (type == QType::DS) {
+      if (domain == DNSName("powerdns.com.")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, DNSName("com."), QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        addNSECRecordToLW(domain, DNSName("z") + domain, {QType::NS, QType::RRSIG}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return LWResult::Result::Success;
+      }
+      else {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+      }
+    }
+    else if (type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+    }
+    else {
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+        addDS(DNSName("com."), 300, res->d_records, keys);
+        addRRSIG(keys, res->d_records, DNSName("."), 300);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return LWResult::Result::Success;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53")) {
+        if (domain == DNSName("com.")) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        else {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          /* no DS */
+          addNSECRecordToLW(DNSName("powerdns.com."), DNSName("z.powerdns.com."), {QType::NS, QType::RRSIG}, 600, res->d_records);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        return LWResult::Result::Success;
+      }
+      else if (ip == ComboAddress("192.0.2.2:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+        addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+
+        return LWResult::Result::Success;
+      }
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(queriesCount, 6U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(queriesCount, 6U);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_without_ds)
+{
+  /* the last zone has INVALID signatures but the DS has not been added
+     to the parent zone yet, so it should be insecure */
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("www.powerdns.com.");
+  const ComboAddress targetAddr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (type == QType::DS) {
+      if (domain == DNSName("powerdns.com.")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, DNSName("com."), QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        addNSECRecordToLW(domain, DNSName("z") + domain, {QType::NS, QType::RRSIG}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return LWResult::Result::Success;
+      }
+      else {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+      }
+    }
+    else if (type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+    }
+    else {
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+        addDS(DNSName("com."), 300, res->d_records, keys);
+        addRRSIG(keys, res->d_records, DNSName("."), 300);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return LWResult::Result::Success;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53")) {
+        if (domain == DNSName("com.")) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        else {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          /* no DS */
+          addNSECRecordToLW(DNSName("powerdns.com."), DNSName("z.powerdns.com."), {QType::NS, QType::RRSIG}, 600, res->d_records);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        return LWResult::Result::Success;
+      }
+      else if (ip == ComboAddress("192.0.2.2:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+        addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, /* broken SIG */ true);
+
+        return LWResult::Result::Success;
+      }
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(queriesCount, 6U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(queriesCount, 6U);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index b85431750deb5affe5529a870bf4478bcf42df76..747f787f19139d1048c926690deebe1394650ac7 100644 (file)
@@ -807,6 +807,60 @@ BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial)
   BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
+BOOST_AUTO_TEST_CASE(test_nsec3_ent_opt_out)
+{
+  initSR();
+
+  testkeysset_t keys;
+  generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+  vector<DNSRecord> records;
+
+  sortedRecords_t recordContents;
+  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+  /*
+   * RFC 7129 section 5.1:
+   * A recently discovered corner case (see RFC Errata ID 3441 [Err3441])
+   * shows that not only those delegations remain insecure but also the
+   * empty non-terminal space that is derived from those delegations.
+  */
+  /*
+    We have a NSEC3 proving that was.here does exist, and a second
+    one proving that ent.was.here. does not,
+    There NSEC3 are opt-out, so the result should be insecure (and we don't need
+    a wildcard proof).
+  */
+  addNSEC3UnhashedRecordToLW(DNSName("was.here."), DNSName("."), "whatever", {}, 600, records, 10, true /* opt out */);
+  recordContents.insert(records.at(0).d_content);
+  addRRSIG(keys, records, DNSName("."), 300);
+  signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+
+  ContentSigPair pair;
+  pair.records = recordContents;
+  pair.signatures = signatureContents;
+  cspmap_t denialMap;
+  denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
+
+  /* it can not be used to deny any RRs below that owner name either */
+  /* Add NSEC3 for the next closer */
+  recordContents.clear();
+  signatureContents.clear();
+  records.clear();
+  addNSEC3NarrowRecordToLW(DNSName("ent.was.here."), DNSName("."), {QType::RRSIG, QType::NSEC3}, 600, records, 10, true /* opt-out */);
+  recordContents.insert(records.at(0).d_content);
+  addRRSIG(keys, records, DNSName("."), 300);
+  signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+
+  pair.records = recordContents;
+  pair.signatures = signatureContents;
+  denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
+
+  /* Insecure because the opt-out bit is set */
+  dState denialState = getDenial(denialMap, DNSName("ent.was.here."), QType::A, false, true);
+  BOOST_CHECK_EQUAL(denialState, dState::OPTOUT);
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity)
 {
   std::unique_ptr<SyncRes> sr;
index 721ade584a82d127ea38a2e33462e661ae7a2235..93f3b94c3b79f11f8af1887f028f13b44d8695d4 100644 (file)
@@ -1448,7 +1448,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType qtype, vector<
       if (!wasAuthZone && shouldValidate() && (wasAuth || wasForwardRecurse) && state == vState::Indeterminate && d_requireAuthData) {
         /* This means we couldn't figure out the state when this entry was cached */
 
-        vState recordState = getValidationStatus(foundName, signatures, qtype == QType::DS, depth);
+        vState recordState = getValidationStatus(foundName, !signatures.empty(), qtype == QType::DS, depth);
         if (recordState == vState::Secure) {
           LOG(prefix<<qname<<": got vState::Indeterminate state from the "<<foundQT.getName()<<" cache, validating.."<<endl);
           state = SyncRes::validateRecordsWithSigs(depth, qname, qtype, foundName, foundQT, cset, signatures);
@@ -1655,7 +1655,7 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne,
 
     const DNSName& owner = entry.first.name;
 
-    vState recordState = getValidationStatus(owner, entry.second.signatures, qtype == QType::DS, depth);
+    vState recordState = getValidationStatus(owner, !entry.second.signatures.empty(), qtype == QType::DS, depth);
     if (state == vState::Indeterminate) {
       state = recordState;
     }
@@ -1675,8 +1675,8 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne,
   if (state == vState::Secure) {
     vState neValidationState = ne.d_validationState;
     dState expectedState = res == RCode::NXDomain ? dState::NXDOMAIN : dState::NXQTYPE;
-    dState denialState = getDenialValidationState(ne, state, expectedState, false);
-    updateDenialValidationState(neValidationState, ne.d_name, state, denialState, expectedState, qtype == QType::DS || expectedState == dState::NXDOMAIN);
+    dState denialState = getDenialValidationState(ne, expectedState, false);
+    updateDenialValidationState(neValidationState, ne.d_name, state, denialState, expectedState);
   }
   if (state != vState::Indeterminate) {
     /* validation succeeded, let's update the cache entry so we don't have to validate again */
@@ -1799,7 +1799,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
     if (!wasAuthZone && shouldValidate() && (wasCachedAuth || wasForwardRecurse) && cachedState == vState::Indeterminate && d_requireAuthData) {
 
       /* This means we couldn't figure out the state when this entry was cached */
-      vState recordState = getValidationStatus(qname, signatures, qtype == QType::DS, depth);
+      vState recordState = getValidationStatus(qname, !signatures.empty(), qtype == QType::DS, depth);
 
       if (recordState == vState::Secure) {
         LOG(prefix<<sqname<<": got vState::Indeterminate state from the cache, validating.."<<endl);
@@ -2571,7 +2571,7 @@ bool SyncRes::haveExactValidationStatus(const DNSName& domain)
   return false;
 }
 
-vState SyncRes::getValidationStatus(const DNSName& name, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures, bool typeIsDS, unsigned int depth)
+vState SyncRes::getValidationStatus(const DNSName& name, bool hasSignatures, bool typeIsDS, unsigned int depth)
 {
   vState result = vState::Indeterminate;
 
@@ -2610,9 +2610,9 @@ vState SyncRes::getValidationStatus(const DNSName& name, const std::vector<std::
      but we don't know if we missed a cut (or several).
      We could see see if we have DS (or denial of) in cache but let's not worry for now,
      we will if we don't have a signature, or if the signer doesn't match what we expect */
-  if (signatures.empty() && best != subdomain) {
+  if (!hasSignatures && best != subdomain) {
     /* no signatures, we likely missed a cut, let's try to find it */
-    LOG(d_prefix<<": no signatures, we likely missed a cut between "<<best<<" and "<<subdomain<<", looking for it"<<endl);
+    LOG(d_prefix<<": no signatures for "<<name<<", we likely missed a cut between "<<best<<" and "<<subdomain<<", looking for it"<<endl);
     DNSName ds(best);
     std::vector<string> labelsToAdd = subdomain.makeRelative(ds).getRawLabels();
 
@@ -2629,6 +2629,8 @@ vState SyncRes::getValidationStatus(const DNSName& name, const std::vector<std::
       if (foundCut) {
         LOG(d_prefix<<": - Found cut at "<<ds<<endl);
         LOG(d_prefix<<": New state for "<<ds<<" is "<<dsState<<endl);
+        d_cutStates[ds] = dsState;
+
         if (dsState != vState::Secure) {
           return dsState;
         }
@@ -2639,15 +2641,14 @@ vState SyncRes::getValidationStatus(const DNSName& name, const std::vector<std::
     return result;
   }
 
-#warning test me, write a unit test if needed
 #if 0
+  /* we don't need this, we actually do the right thing later */
   DNSName signer = getSigner(signatures);
 
   if (!signer.empty() && name.isPartOf(signer)) {
     if (signer == best) {
       return result;
     }
-
     /* the zone cut is not the one we expected,
        this is fine because we will retrieve the needed DNSKEYs and DSs
        later, and even go Insecure if we missed a cut to Insecure (no DS)
@@ -3008,7 +3009,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
           wildcardLabelsCount = rrsig->d_labels;
         }
 
-        //         cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
+        // cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"' and place "<<rec.d_place<<endl;
         tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
         tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL = std::min(tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL, rec.d_ttl);
       }
@@ -3077,7 +3078,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
       else {
         bool haveLogged = false;
         if (isDNAMEAnswer && rec.d_type == QType::CNAME) {
-          LOG("NO - we already have a DNAME answer for this domain");
+          LOG("NO - we already have a DNAME answer for this domain"<<endl);
           continue;
         }
         if (!t_sstorage.domainmap->empty()) {
@@ -3137,7 +3138,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
 
   for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
 
-    if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
+    if (i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
       continue;
 
     /* Even if the AA bit is set, additional data cannot be considered
@@ -3189,10 +3190,26 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
       continue;
     }
 
+    /*
+     * RFC 6672 section 5.3.1
+     *  In any response, a signed DNAME RR indicates a non-terminal
+     *  redirection of the query.  There might or might not be a server-
+     *  synthesized CNAME in the answer section; if there is, the CNAME will
+     *  never be signed.  For a DNSSEC validator, verification of the DNAME
+     *  RR and then that the CNAME was properly synthesized is sufficient
+     *  proof.
+     *
+     * We do the synthesis check in processRecords, here we make sure we
+     * don't validate the CNAME.
+     */
+    if (isDNAMEAnswer && i->first.type == QType::CNAME) {
+      expectSignature = false;
+    }
+
     vState recordState = vState::Indeterminate;
 
     if (expectSignature && shouldValidate()) {
-      vState initialState = getValidationStatus(i->first.name, i->second.signatures, i->first.type == QType::DS, depth);
+      vState initialState = getValidationStatus(i->first.name, !i->second.signatures.empty(), i->first.type == QType::DS, depth);
       LOG(d_prefix<<": got initial zone status "<<initialState<<" for record "<<i->first.name<<"|"<<DNSRecordContent::NumberToType(i->first.type)<<endl);
 
       if (initialState == vState::Secure) {
@@ -3201,25 +3218,11 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
           recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth);
         }
         else {
-          /*
-           * RFC 6672 section 5.3.1
-           *  In any response, a signed DNAME RR indicates a non-terminal
-           *  redirection of the query.  There might or might not be a server-
-           *  synthesized CNAME in the answer section; if there is, the CNAME will
-           *  never be signed.  For a DNSSEC validator, verification of the DNAME
-           *  RR and then that the CNAME was properly synthesized is sufficient
-           *  proof.
-           *
-           * We do the synthesis check in processRecords, here we make sure we
-           * don't validate the CNAME.
-           */
-          if (!(isDNAMEAnswer && i->first.type == QType::CNAME)) {
-            LOG(d_prefix<<"Validating non-additional "<<QType(i->first.type).getName()<<" record for "<<i->first.name<<endl);
-            recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, QType(i->first.type), i->second.records, i->second.signatures);
-            /* we might have missed a cut (zone cut within the same auth servers), causing the NS query for an Insecure zone to seem Bogus during zone cut determination */
-            if (qtype == QType::NS && i->second.signatures.empty() && vStateIsBogus(recordState) && haveExactValidationStatus(i->first.name) && getValidationStatus(i->first.name, i->second.signatures, i->first.type == QType::DS, depth) == vState::Indeterminate) {
-                recordState = vState::Indeterminate;
-            }
+          LOG(d_prefix<<"Validating non-additional "<<QType(i->first.type).getName()<<" record for "<<i->first.name<<endl);
+          recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, QType(i->first.type), i->second.records, i->second.signatures);
+          /* we might have missed a cut (zone cut within the same auth servers), causing the NS query for an Insecure zone to seem Bogus during zone cut determination */
+          if (qtype == QType::NS && i->second.signatures.empty() && vStateIsBogus(recordState) && haveExactValidationStatus(i->first.name) && initialState == vState::Indeterminate) {
+            recordState = vState::Indeterminate;
           }
         }
       }
@@ -3314,13 +3317,13 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
   return RCode::NoError;
 }
 
-void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool allowOptOut)
+void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState)
 {
   if (denialState == expectedState) {
     neValidationState = vState::Secure;
   }
   else {
-    if (denialState == dState::OPTOUT && allowOptOut) {
+    if (denialState == dState::OPTOUT) {
       LOG(d_prefix<<"OPT-out denial found for "<<neName<<endl);
       /* rfc5155 states:
          "The AD bit, as defined by [RFC4035], MUST NOT be set when returning a
@@ -3350,7 +3353,7 @@ void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSNa
   }
 }
 
-dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned)
+dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned)
 {
   cspmap_t csp = harvestCSPFromNE(ne);
   return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE);
@@ -3394,12 +3397,20 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       ne.d_auth = rec.d_name;
       harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-      if (state == vState::Secure) {
-        dState denialState = getDenialValidationState(ne, state, dState::NXDOMAIN, false);
-        updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN, true);
+      if (vStateIsBogus(state)) {
+        ne.d_validationState = state;
       }
       else {
-        ne.d_validationState = state;
+#warning FIXME: DS ?
+        auto recordState = getValidationStatus(ne.d_name, !ne.authoritySOA.signatures.empty(), false, depth);
+        if (recordState == vState::Secure) {
+          dState denialState = getDenialValidationState(ne, dState::NXDOMAIN, false);
+          updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN);
+        }
+        else {
+          ne.d_validationState = recordState;
+          updateValidationState(state, ne.d_validationState);
+        }
       }
 
       if (vStateIsBogus(ne.d_validationState)) {
@@ -3478,36 +3489,45 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       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
-           proof that the exact name doesn't exist so the wildcard can be used,
-           as described in section 5.3.4 of RFC 4035 and 5.3 of RFC 7129.
-        */
+      if (needWildcardProof) {
         NegCache::NegCacheEntry ne;
-
-        uint32_t lowestTTL = rec.d_ttl;
         ne.d_name = qname;
         ne.d_qtype = QType::ENT; // this encodes 'whole record'
+        uint32_t lowestTTL = rec.d_ttl;
         harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-        cspmap_t csp = harvestCSPFromNE(ne);
-        dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false, wildcardLabelsCount);
-        if (res != dState::NXDOMAIN) {
-          vState st = vState::BogusInvalidDenial;
-          if (res == dState::INSECURE) {
-            /* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
-               this is not enough to warrant a Bogus, but go Insecure. */
-            st = vState::Insecure;
-            LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
-          }
-          else {
-            LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
-            rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
-          }
+        if (vStateIsBogus(state)) {
+          ne.d_validationState = state;
+        }
+        else {
+          auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty(), false, depth);
 
-          updateValidationState(state, st);
-          /* we already stored the record with a different validation status, let's fix it */
-          updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
+          if (recordState == vState::Secure) {
+            /* We have a positive answer synthesized from a wildcard, we need to check that we have
+               proof that the exact name doesn't exist so the wildcard can be used,
+               as described in section 5.3.4 of RFC 4035 and 5.3 of RFC 7129.
+            */
+
+            cspmap_t csp = harvestCSPFromNE(ne);
+            dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false, wildcardLabelsCount);
+            if (res != dState::NXDOMAIN) {
+              vState st = vState::BogusInvalidDenial;
+              if (res == dState::INSECURE) {
+                /* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
+                   this is not enough to warrant a Bogus, but go Insecure. */
+                st = vState::Insecure;
+                LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
+              }
+              else {
+                LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
+                rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
+              }
+
+              updateValidationState(state, st);
+              /* we already stored the record with a different validation status, let's fix it */
+              updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
+            }
+          }
         }
       }
 
@@ -3541,33 +3561,38 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     }
     else if(realreferral && rec.d_place==DNSResourceRecord::AUTHORITY && (rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && newauth.isPartOf(auth)) {
       /* we might have received a denial of the DS, let's check */
-      if (state == vState::Secure) {
-        NegCache::NegCacheEntry ne;
-        ne.d_auth = auth;
-        ne.d_name = newauth;
-        ne.d_qtype = QType::DS;
-        rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
-        uint32_t lowestTTL = rec.d_ttl;
-        harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+      NegCache::NegCacheEntry ne;
+      uint32_t lowestTTL = rec.d_ttl;
+      harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-        dState denialState = getDenialValidationState(ne, state, dState::NXQTYPE, true);
+      if (!vStateIsBogus(state)) {
+        auto recordState = getValidationStatus(newauth, !ne.authoritySOA.signatures.empty(), true, depth);
 
-        if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
-          ne.d_ttd = lowestTTL + d_now.tv_sec;
-          ne.d_validationState = vState::Secure;
-          if (denialState == dState::OPTOUT) {
-            ne.d_validationState = vState::Insecure;
-          }
-          LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
+        if (recordState == vState::Secure) {
+          ne.d_auth = auth;
+          ne.d_name = newauth;
+          ne.d_qtype = QType::DS;
+          rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
+
+          dState denialState = getDenialValidationState(ne, dState::NXQTYPE, true);
+
+          if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
+            ne.d_ttd = lowestTTL + d_now.tv_sec;
+            ne.d_validationState = vState::Secure;
+            if (denialState == dState::OPTOUT) {
+              ne.d_validationState = vState::Insecure;
+            }
+            LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
 
-          if(!wasVariable()) {
-            g_negCache->add(ne);
-          }
+            if (!wasVariable()) {
+              g_negCache->add(ne);
+            }
 
-          if (qname == newauth && qtype == QType::DS) {
-            /* we are actually done! */
-            negindic=true;
-            nsset.clear();
+            if (qname == newauth && qtype == QType::DS) {
+              /* we are actually done! */
+              negindic=true;
+              nsset.clear();
+            }
           }
         }
       }
@@ -3589,12 +3614,19 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         ne.d_qtype = qtype;
         harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-        if (state == vState::Secure) {
-          dState denialState = getDenialValidationState(ne, state, dState::NXQTYPE, false);
-          updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS);
-        } else {
+        if (vStateIsBogus(state)) {
           ne.d_validationState = state;
         }
+        else {
+          auto recordState = getValidationStatus(qname, ne.authoritySOA.signatures.empty(), qtype == QType::DS, depth);
+          if (recordState == vState::Secure) {
+            dState denialState = getDenialValidationState(ne, dState::NXQTYPE, false);
+            updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE);
+          } else {
+            ne.d_validationState = recordState;
+            updateValidationState(state, ne.d_validationState);
+          }
+        }
 
         if (vStateIsBogus(ne.d_validationState)) {
           lowestTTL = min(lowestTTL, s_maxbogusttl);
@@ -3913,7 +3945,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   if (lwr.d_rcode == RCode::NXDomain) {
     LOG(prefix<<qname<<": status=NXDOMAIN, we are done "<<(negindic ? "(have negative SOA)" : "")<<endl);
 
-    auto tempState = getValidationStatus(qname, {}, qtype == QType::DS, depth);
+    auto tempState = getValidationStatus(qname, false, qtype == QType::DS, depth);
     if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
       LOG(prefix<<qname<<": NXDOMAIN without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
       updateValidationState(state, vState::BogusMissingNegativeIndication);
@@ -3929,7 +3961,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   if (nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
     LOG(prefix<<qname<<": status=noerror, other types may exist, but we are done "<<(negindic ? "(have negative SOA) " : "")<<(lwr.d_aabit ? "(have aa bit) " : "")<<endl);
 
-    auto tempState = getValidationStatus(qname, {}, qtype == QType::DS, depth);
+    auto tempState = getValidationStatus(qname, false, qtype == QType::DS, depth);
     if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
       LOG(prefix<<qname<<": NODATA without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
       updateValidationState(state, vState::BogusMissingNegativeIndication);
index 3f198d1db5c701e2bd900a0a69fd13d6b7f60c95..c6c1632881a3ab568b288796b0b7d089b9398ec2 100644 (file)
@@ -868,13 +868,12 @@ private:
   vState validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType qtype, const DNSName& name, const QType type, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures);
   vState validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth);
   vState getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth);
-  dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned);
-  void updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool allowOptOut);
+  dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned);
+  void updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState);
   void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, QType qtype, const int res, vState& state, unsigned int depth);
   vState getTA(const DNSName& zone, dsmap_t& ds);
   bool haveExactValidationStatus(const DNSName& domain);
-
-  vState getValidationStatus(const DNSName& subdomain, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures, bool typeIsDS, unsigned int depth);
+  vState getValidationStatus(const DNSName& subdomain, bool hasSignatures, bool typeIsDS, unsigned int depth);
   void updateValidationStatusInCache(const DNSName &qname, QType qt, bool aa, vState newState) const;
   void initZoneCutsFromTA(const DNSName& from);
 
index 9002c17edb7aa53de6bb3b0fc92ccecc2a83e035..bf37dbe9fef8c61b23cd863c9589304e7a97baa4 100644 (file)
@@ -485,13 +485,17 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
     throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard");
   }
 
-  for(const auto& v : validrrsets) {
+  for (const auto& v : validrrsets) {
     LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
 
-    if(v.first.second==QType::NSEC) {
-      for(const auto& r : v.second.records) {
+    if (v.first.second==QType::NSEC) {
+      for (const auto& r : v.second.records) {
         LOG("\t"<<r->getZoneRepresentation()<<endl);
 
+        if (v.second.signatures.empty()) {
+          continue;
+        }
+
         auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r);
         if (!nsec) {
           continue;
@@ -501,7 +505,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
         const DNSName signer = getSigner(v.second.signatures);
         if (!v.first.first.isPartOf(signer) || !owner.isPartOf(signer) ) {
            continue;
-         }
+        }
 
         /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
            Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
@@ -616,11 +620,16 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
         LOG("Did not deny existence of "<<QType(qtype).getName()<<", "<<v.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
       }
     } else if(v.first.second==QType::NSEC3) {
-      for(const auto& r : v.second.records) {
+      for (const auto& r : v.second.records) {
         LOG("\t"<<r->getZoneRepresentation()<<endl);
         auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r);
-        if(!nsec3)
+        if (!nsec3) {
+          continue;
+        }
+
+        if (v.second.signatures.empty()) {
           continue;
+        }
 
         const DNSName signer = getSigner(v.second.signatures);
         if (!v.first.first.isPartOf(signer)) {
@@ -805,10 +814,11 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
               LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName());
               nextCloserFound = true;
 
-              if ((qtype == QType::DS || qtype == 0) && nsec3->isOptOut()) {
+              if (nsec3->isOptOut()) {
                 LOG(" but is opt-out!");
                 isOptOut = true;
               }
+
               LOG(endl);
               break;
             }