// Empty Non-Terminal (ENT): if the qname is bigger than nsec owner's name and a // subdomain of the nsec's nxt field, then the qname is a empty non-terminal. For // example, if you are looking for (in RFC 4035 example zone) "y.w.example A" // record, if it is a ENT, then it would return // // x.w.example. 3600 NSEC x.y.w.example. MX RRSIG NSEC // // This function is normally called before checking for wildcard matches. If you // find this NSEC, there is no need to look for a wildcard record // that could possibly answer the question. mDNSlocal mDNSBool NSECAnswersENT(const ResourceRecord *const rr, domainname *qname) { const domainname *oname = rr->name; const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data; const domainname *nxt = (const domainname *)&rdb->data; int ret; int subdomain; // Is the owner name smaller than qname? ret = DNSSECCanonicalOrder(oname, qname, mDNSNULL); if (ret < 0) { // Is the next domain field a subdomain of qname ? ret = DNSSECCanonicalOrder(nxt, qname, &subdomain); if (subdomain) { if (ret <= 0) { LogMsg("NSECAnswersENT: ERROR!! DNSSECCanonicalOrder subdomain set " " qname %##s, NSEC %##s", qname->c, rr->name->c); } return mDNStrue; } } return mDNSfalse; }
// This function can be called with NSEC3ClosestEncloser, NSEC3Covers and NSEC3CEProof // // Passing in NSEC3ClosestEncloser means "find an exact match for the origName". // Passing in NSEC3Covers means "find an NSEC3 that covers the origName". // // i.e., in both cases the nsec3 records are iterated to find the best match and returned. // With NSEC3ClosestEncloser, as we are just looking for a name match, extra checks for // the types being present or absent will not be checked. // // If NSEC3CEProof is passed, the name is tried as such first by iterating through all NSEC3s // finding a ClosestEncloser or CloserEncloser and then one label skipped from the left and // retried again till both the closest and closer encloser is found. // // ncr is the negative cache record that has the NSEC3 chain // origName is the name for which we are trying to find the ClosestEncloser etc. // closestEncloser and closerEncloser are the return values of the function // ce is the closest encloser that will be returned if we find one mDNSlocal mDNSBool NSEC3Find(mDNS *const m, NSEC3FindValues val, CacheRecord *ncr, domainname *origName, CacheRecord **closestEncloser, CacheRecord **closerEncloser, const domainname **ce, mDNSu16 qtype) { int i; int labelCount = CountLabels(origName); CacheRecord *cr; rdataNSEC3 *nsec3; (void) qtype; // unused // Pick the first NSEC for the iterations, salt etc. for (cr = ncr->nsec; cr; cr = cr->next) { if (cr->resrec.rrtype == kDNSType_NSEC3) { const RDataBody2 *const rdb = (RDataBody2 *)cr->resrec.rdata->u.data; nsec3 = (rdataNSEC3 *)rdb->data; break; } } if (!cr) { LogMsg("NSEC3Find: cr NULL"); return mDNSfalse; } // Note: The steps defined in this function are for "NSEC3CEProof". As part of NSEC3CEProof, // we need to find both the closestEncloser and closerEncloser which can also be found // by passing NSEC3ClosestEncloser and NSEC3Covers respectively. // // Section 8.3 of RFC 5155. // 1. Set SNAME=QNAME. Clear the flag. // // closerEncloser is the "flag". "name" below is SNAME. if (closestEncloser) { *ce = mDNSNULL; *closestEncloser = mDNSNULL; } if (closerEncloser) *closerEncloser = mDNSNULL; // If we are looking for a closestEncloser or a covering NSEC3, we don't have // to truncate the name. For the give name, try to find the closest or closer // encloser. if (val != NSEC3CEProof) { labelCount = 0; } for (i = 0; i < labelCount + 1; i++) { int hlen; const mDNSu8 hashName[NSEC3_MAX_HASH_LEN]; const domainname *name; const mDNSu8 b32Name[NSEC3_MAX_B32_LEN+1]; int b32len; name = SkipLeadingLabels(origName, i); if (!NSEC3HashName(name, nsec3, mDNSNULL, 0, hashName, &hlen)) { LogMsg("NSEC3Find: NSEC3HashName failed for ##s", name->c); continue; } b32len = baseEncode((char *)b32Name, sizeof(b32Name), (mDNSu8 *)hashName, hlen, ENC_BASE32); if (!b32len) { LogMsg("NSEC3Find: baseEncode of name %##s failed", name->c); continue; } for (cr = ncr->nsec; cr; cr = cr->next) { const domainname *nsecZone; int result, subdomain; if (cr->resrec.rrtype != kDNSType_NSEC3) continue; nsecZone = SkipLeadingLabels(cr->resrec.name, 1); if (!nsecZone) { LogMsg("NSEC3Find: SkipLeadingLabel failed for %s, current name %##s", CRDisplayString(m, cr), name->c); continue; } // NSEC3 owner names are formed by hashing the owner name and then appending // the zone name to it. If we skip the first label, the rest should be // the zone name. See whether it is the subdomain of the name we are looking // for. result = DNSSECCanonicalOrder(origName, nsecZone, &subdomain); // The check can't be a strict subdomain check. When NSEC3ClosestEncloser is // passed in, there can be an exact match. If it is a subdomain or an exact // match, we should continue with the proof. if (!(subdomain || !result)) { LogMsg("NSEC3Find: NSEC3 %s not a subdomain of %##s, result %d", CRDisplayString(m, cr), origName->c, result); continue; } // 2.1) If there is no NSEC3 RR in the response that matches SNAME // (i.e., an NSEC3 RR whose owner name is the same as the hash of // SNAME, prepended as a single label to the zone name), clear // the flag. // // Note: We don't try to determine the actual zone name. We know that // the labels following the hash (nsecZone) is the ancestor and we don't // know where the zone cut is. Hence, we verify just the hash to be // the same. if (val == NSEC3ClosestEncloser || val == NSEC3CEProof) { if (!NSEC3SameName(&cr->resrec.name->c[1], cr->resrec.name->c[0], (const mDNSu8 *)b32Name, b32len)) { int bmaplen; mDNSu8 *bmap; // For NSEC3ClosestEncloser, we are finding an exact match and // "type" specific checks should be done by the caller. if (val != NSEC3ClosestEncloser) { // DNAME bit must not be set and NS bit may be set only if SOA bit is set NSEC3Parse(&cr->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap); if (BitmapTypeCheck(bmap, bmaplen, kDNSType_DNAME)) { LogDNSSEC("NSEC3Find: DNAME bit set in %s, ignoring", CRDisplayString(m, cr)); return mDNSfalse; } // This is the closest encloser and should come from the right zone. if (BitmapTypeCheck(bmap, bmaplen, kDNSType_NS) && !BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA)) { LogDNSSEC("NSEC3Find: NS bit set without SOA bit in %s, ignoring", CRDisplayString(m, cr)); return mDNSfalse; } } LogDNSSEC("NSEC3Find: ClosestEncloser %s found for name %##s", CRDisplayString(m, cr), name->c); if (closestEncloser) { *ce = name; *closestEncloser = cr; } if (val == NSEC3ClosestEncloser) return mDNStrue; else break; } } if ((val == NSEC3Covers || val == NSEC3CEProof) && !(*closerEncloser)) { if (NSEC3CoversName(m, cr, hashName, hlen, b32Name, b32len)) { // 2.2) If there is an NSEC3 RR in the response that covers SNAME, set the flag. if (closerEncloser) *closerEncloser = cr; if (val == NSEC3Covers) return mDNStrue; else break; } } } // 2.3) If there is a matching NSEC3 RR in the response and the flag // was set, then the proof is complete, and SNAME is the closest // encloser. if (val == NSEC3CEProof) { if (*closestEncloser && *closerEncloser) { LogDNSSEC("NSEC3Find: Found closest and closer encloser"); return mDNStrue; } // 2.4) If there is a matching NSEC3 RR in the response, but the flag // is not set, then the response is bogus. // // Note: We don't have to wait till we finish trying all the names. If the matchName // happens, we found the closest encloser which means we should have found the closer // encloser before. if (*closestEncloser && !(*closerEncloser)) { LogDNSSEC("NSEC3Find: Found closest, but not closer encloser"); return mDNSfalse; } } // 3. Truncate SNAME by one label from the left, go to step 2. } LogDNSSEC("NSEC3Find: Cannot find name %##s (%s)", origName->c, DNSTypeName(qtype)); return mDNSfalse; }
// We have a NSEC. Need to see if it proves that NODATA exists for the given name. Note that this // function does not prove anything as proof may require more than one NSEC and this function // processes only one NSEC at a time. // // Returns mDNSfalse if the NSEC does not prove the NODATA error // Returns mDNStrue if the NSEC proves the NODATA error // mDNSlocal mDNSBool NSECNoDataError(mDNS *const m, ResourceRecord *rr, domainname *name, mDNSu16 qtype, domainname **wildcard) { const domainname *oname = rr->name; // owner name if (wildcard) *wildcard = mDNSNULL; // RFC 4035 // // section 3.1.3.1 : Name matches. Prove that the type does not exist and also CNAME is // not set as in that case CNAME should have been returned ( CNAME part is mentioned in // section 4.3 of dnssec-bis-updates.) Without the CNAME check, a positive response can // be converted to a NODATA/NOERROR response. // // section 3.1.3.4 : No exact match for the name but there is a wildcard that could match // the name but not the type. There are two NSECs in this case. One of them is a wildcard // NSEC and another NSEC proving that the qname does not exist. We are called with one // NSEC at a time. We return what we matched and the caller should decide whether all // conditions are met for the proof. if (SameDomainName(oname, name)) { mDNSBool soa = RRAssertsExistence(rr, kDNSType_SOA); mDNSBool ns = RRAssertsExistence(rr, kDNSType_NS); if (qtype != kDNSType_DS) { // For non-DS type questions, we don't want to use the parent side records to // answer it if (ns && !soa) { LogDNSSEC("NSECNoDataError: Parent side NSEC %s, can't use for child qname %##s (%s)", RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); return mDNSfalse; } } else { if (soa) { LogDNSSEC("NSECNoDataError: Child side NSEC %s, can't use for parent qname %##s (%s)", RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); return mDNSfalse; } } if (RRAssertsExistence(rr, qtype) || RRAssertsExistence(rr, kDNSType_CNAME)) { LogMsg("NSECNoDataError: ERROR!! qtype %s exists in %s", DNSTypeName(qtype), RRDisplayString(m, rr)); return mDNSfalse; } LogDNSSEC("NSECNoDataError: qype %s does not exist in %s", DNSTypeName(qtype), RRDisplayString(m, rr)); return mDNStrue; } else { // Name does not exist. Before we check for a wildcard match, make sure that // this is not an ENT. if (NSECAnswersENT(rr, name)) { LogDNSSEC("NSECNoDataError: name %##s exists %s", name->c, RRDisplayString(m, rr)); return mDNSfalse; } // Wildcard check. If this is a wildcard NSEC, then check to see if we could // have answered the question using this wildcard and it should not have the // "qtype" passed in with its bitmap. // // See RFC 4592, on how wildcards are used to synthesize answers. Find the // closest encloser and the qname should be a subdomain i.e if the wildcard // is *.x.example, x.example is the closest encloser and the qname should be // a subdomain e.g., y.x.example or z.y.x.example and so on. if (oname->c[0] == 1 && oname->c[1] == '*') { int r, s; const domainname *ce = SkipLeadingLabels(oname, 1); r = DNSSECCanonicalOrder(name, ce, &s); if (s) { if (RRAssertsExistence(rr, qtype) || RRAssertsExistence(rr, kDNSType_CNAME)) { LogMsg("NSECNoDataError: ERROR!! qtype %s exists in wildcard %s", DNSTypeName(qtype), RRDisplayString(m, rr)); return mDNSfalse; } if (qtype == kDNSType_DS && RRAssertsExistence(rr, kDNSType_SOA)) { LogDNSSEC("NSECNoDataError: Child side wildcard NSEC %s, can't use for parent qname %##s (%s)", RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); return mDNSfalse; } else if (qtype != kDNSType_DS && RRAssertsNonexistence(rr, kDNSType_SOA) && RRAssertsExistence(rr, kDNSType_NS)) { // Don't use the parent side record for this LogDNSSEC("NSECNoDataError: Parent side wildcard NSEC %s, can't use for child qname %##s (%s)", RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); return mDNSfalse; } *wildcard = (domainname *)ce; LogDNSSEC("NSECNoDataError: qtype %s does not exist in wildcard %s", DNSTypeName(qtype), RRDisplayString(m, rr)); return mDNStrue; } } return mDNSfalse; } }
// Assumption: NSEC has been validated outside of this function // // Does the name exist given the name and NSEC rr ? // // Returns -1 if it is an inappropriate nsec // Returns 1 if the name exists // Returns 0 if the name does not exist // mDNSlocal int NSECNameExists(mDNS *const m, ResourceRecord *rr, domainname *name, mDNSu16 qtype) { const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data; const domainname *nxt = (const domainname *)&rdb->data; const domainname *oname = rr->name; // owner name int ret1, subdomain1; int ret2, subdomain2; int ret3, subdomain3; ret1 = DNSSECCanonicalOrder(oname, name, &subdomain1); if (ret1 > 0) { LogDNSSEC("NSECNameExists: owner name %##s is bigger than name %##s", oname->c, name->c); return -1; } // Section 4.1 of draft-ietf-dnsext-dnssec-bis-updates-14: // // Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume non- // existence of any RRs below that zone cut, which include all RRs at // that (original) owner name other than DS RRs, and all RRs below that // owner name regardless of type. // // This also implies that we can't use the child side NSEC for DS question. if (!ret1) { mDNSBool soa = RRAssertsExistence(rr, kDNSType_SOA); mDNSBool ns = RRAssertsExistence(rr, kDNSType_NS); // We are here because the owner name is the same as "name". Make sure the // NSEC has the right NS and SOA bits set. if (qtype != kDNSType_DS && ns && !soa) { LogDNSSEC("NSECNameExists: Parent side NSEC %s can't be used for question %##s (%s)", RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); return -1; } else if (qtype == kDNSType_DS && soa) { LogDNSSEC("NSECNameExists: Child side NSEC %s can't be used for question %##s (%s)", RRDisplayString(m, rr), name->c, DNSTypeName(qtype)); return -1; } LogDNSSEC("NSECNameExists: owner name %##s is same as name %##s", oname->c, name->c); return 1; } // If the name is a.b.com and NSEC's owner name is b.com i.e., a subdomain // and nsec comes from the parent (NS is set and SOA is not set), then this // NSEC can't be used for names below the owner name. // // Similarly if DNAME is set, we can't use it here. See RFC2672-bis-dname // appendix. if (subdomain1 && (RRAssertsExistence(rr, kDNSType_DNAME) || (RRAssertsNonexistence(rr, kDNSType_SOA) && RRAssertsExistence(rr, kDNSType_NS)))) { LogDNSSEC("NSECNameExists: NSEC %s comes from the parent, can't use it here", RRDisplayString(m, rr)); return -1; } // At this stage, we know that name is greater than the owner name and // the nsec is not from the parent side. // // Compare with the next field in the nsec. // ret2 = DNSSECCanonicalOrder(name, nxt, &subdomain2); // Exact match with the nsec next name if (!ret2) { LogDNSSEC("NSECNameExists: name %##s is same as nxt name %##s", name->c, nxt->c); return 1; } ret3 = DNSSECCanonicalOrder(oname, nxt, &subdomain3); if (!ret3) { // Pathological case of a single name in the domain. This means only the // apex of the zone itself exists. Nothing below it. "subdomain2" indicates // that name is a subdmain of "next" and hence below the zone. if (subdomain2) { LogDNSSEC("NSECNameExists: owner name %##s subdomain of nxt name %##s", oname->c, nxt->c); return 0; } else { LogDNSSEC("NSECNameExists: Single name in zone, owner name %##s is same as nxt name %##s", oname->c, nxt->c); return -1; } } if (ret3 < 0) { // Regular NSEC in the zone. Make sure that the "name" lies within // oname and next. oname < name and name < next if (ret1 < 0 && ret2 < 0) { LogDNSSEC("NSECNameExists: Normal NSEC name %##s lies within owner %##s and nxt name %##s", name->c, oname->c, nxt->c); return 0; } else { LogDNSSEC("NSECNameExists: Normal NSEC name %##s does not lie within owner %##s and nxt name %##s", name->c, oname->c, nxt->c); return -1; } } else { // Last NSEC in the zone. The "next" is pointing to the apex. All names // should be a subdomain of that and the name should be bigger than // oname if (ret1 < 0 && subdomain2) { LogDNSSEC("NSECNameExists: Last NSEC name %##s lies within owner %##s and nxt name %##s", name->c, oname->c, nxt->c); return 0; } else { LogDNSSEC("NSECNameExists: Last NSEC name %##s does not lie within owner %##s and nxt name %##s", name->c, oname->c, nxt->c); return -1; } } LogDNSSEC("NSECNameExists: NSEC %s did not match any case", RRDisplayString(m, rr)); return -1; }