mDNSexport CacheRecord *NSECRecordIsDelegation(mDNS *const m, domainname *name, mDNSu16 qtype) { CacheGroup *cg; CacheRecord *cr; mDNSu32 slot, namehash; slot = HashSlot(name); namehash = DomainNameHashValue(name); cg = CacheGroupForName(m, (const mDNSu32)slot, namehash, name); if (!cg) { LogDNSSEC("NSECRecordForName: cg NULL for %##s", name); return mDNSNULL; } for (cr = cg->members; cr; cr = cr->next) { if (cr->resrec.RecordType == kDNSRecordTypePacketNegative && cr->resrec.rrtype == qtype) { CacheRecord *ncr; for (ncr = cr->nsec; ncr; ncr = ncr->next) { if (ncr->resrec.rrtype == kDNSType_NSEC && SameDomainName(ncr->resrec.name, name)) { // See the Insecure Delegation Proof section in dnssec-bis: DS bit and SOA bit // should be absent if (RRAssertsExistence(&ncr->resrec, kDNSType_SOA) || RRAssertsExistence(&ncr->resrec, kDNSType_DS)) { LogDNSSEC("NSECRecordForName: found record %s for %##s (%s), but DS or SOA bit set", CRDisplayString(m, ncr), name, DNSTypeName(qtype)); return mDNSNULL; } // Section 2.3 of RFC 4035 states that: // // Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST // have an NSEC resource record. // // So, if we have an NSEC record matching the question name with the NS bit set, // then this is a delegation. // if (RRAssertsExistence(&ncr->resrec, kDNSType_NS)) { LogDNSSEC("NSECRecordForName: found record %s for %##s (%s)", CRDisplayString(m, ncr), name, DNSTypeName(qtype)); return ncr; } else { LogDNSSEC("NSECRecordForName: found record %s for %##s (%s), but NS bit is not set", CRDisplayString(m, ncr), name, DNSTypeName(qtype)); return mDNSNULL; } } } } } return mDNSNULL; }
mDNSlocal int DoQuery(DNSQuestion *q, char *qname, mDNSu16 qtype, const mDNSAddr *target, mDNSQuestionCallback callback) { DoOneQuery(q, qname, qtype, target, callback); if (StopNow == 0 && NumAnswers == 0 && target && target->type) { mprintf("%##s %s Trying multicast\n", q->qname.c, DNSTypeName(q->qtype)); DoOneQuery(q, qname, qtype, NULL, callback); } if (StopNow == 0 && NumAnswers == 0) mprintf("%##s %s *** No Answer ***\n", q->qname.c, DNSTypeName(q->qtype)); return(StopNow); }
mDNSlocal void DNSSECProbeCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { if (!AddRecord) return; mDNS_Lock(m); if ((m->timenow - question->StopTime) >= 0) { mDNS_Unlock(m); LogDNSSEC("DNSSECProbeCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype)); mDNS_StopQuery(m, question); return; } mDNS_Unlock(m); // Wait till we get the DNSSEC results. If we get a negative response e.g., no DNS servers, the // question will be restarted by the core and we should have the DNSSEC results eventually. if (AddRecord != QC_dnssec) { LogDNSSEC("DNSSECProbeCallback: Question %##s (%s)", question->qname.c, DNSTypeName(question->qtype), RRDisplayString(m, answer)); return; } LogDNSSEC("DNSSECProbeCallback: Question %##s (%s), DNSSEC status %s", question->qname.c, DNSTypeName(question->qtype), DNSSECStatusName(question->ValidationStatus)); mDNS_StopQuery(m, question); }
mDNSlocal mDNSBool VerifyNSEC3(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr, CacheRecord *closestEncloser, CacheRecord *closerEncloser, CacheRecord *wildcard, DNSSECVerifierCallback callback) { mStatus status; RRVerifier *r; // We have three NSEC3s. If any of two are same, we should just prove one of them. // This is just not an optimization; DNSSECNegativeValidationCB does not handle // identical NSEC3s very well. if (closestEncloser == closerEncloser) closestEncloser = mDNSNULL; if (closerEncloser == wildcard) closerEncloser = mDNSNULL; if (closestEncloser == wildcard) closestEncloser = mDNSNULL; dv->pendingNSEC = mDNSNULL; if (closestEncloser) { r = AllocateRRVerifier(&closestEncloser->resrec, &status); if (!r) return mDNSfalse; r->next = dv->pendingNSEC; dv->pendingNSEC = r; } if (closerEncloser) { r = AllocateRRVerifier(&closerEncloser->resrec, &status); if (!r) return mDNSfalse; r->next = dv->pendingNSEC; dv->pendingNSEC = r; } if (wildcard) { r = AllocateRRVerifier(&wildcard->resrec, &status); if (!r) return mDNSfalse; r->next = dv->pendingNSEC; dv->pendingNSEC = r; } if (!dv->pendingNSEC) { LogMsg("VerifyNSEC3: ERROR!! pending NSEC null"); return mDNSfalse; } r = dv->pendingNSEC; dv->pendingNSEC = r->next; r->next = mDNSNULL; LogDNSSEC("VerifyNSEC3: Verifying %##s (%s)", r->name.c, DNSTypeName(r->rrtype)); if (!dv->pendingNSEC) VerifyNSEC(m, mDNSNULL, r, dv, ncr, mDNSNULL); else VerifyNSEC(m, mDNSNULL, r, dv, ncr, callback); return mDNStrue; }
// Section 8.5, 8.6 of RFC 5155 first paragraph mDNSlocal mDNSBool NSEC3NoDataError(mDNS *const m, CacheRecord *ncr, domainname *name, mDNSu16 qtype, CacheRecord **closestEncloser) { const domainname *ce = mDNSNULL; *closestEncloser = mDNSNULL; // Note: This also covers ENT in which case the bitmap is empty if (NSEC3Find(m, NSEC3ClosestEncloser, ncr, name, closestEncloser, mDNSNULL, &ce, qtype)) { int bmaplen; mDNSu8 *bmap; mDNSBool ns, soa; NSEC3Parse(&(*closestEncloser)->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap); if (BitmapTypeCheck(bmap, bmaplen, qtype) || BitmapTypeCheck(bmap, bmaplen, kDNSType_CNAME)) { LogMsg("NSEC3NoDataError: qtype %s exists in %s", DNSTypeName(qtype), CRDisplayString(m, *closestEncloser)); return mDNSfalse; } ns = BitmapTypeCheck(bmap, bmaplen, kDNSType_NS); soa = BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA); 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("NSEC3NoDataError: Parent side NSEC %s, can't use for child qname %##s (%s)", CRDisplayString(m, *closestEncloser), name->c, DNSTypeName(qtype)); return mDNSfalse; } } else { if (soa) { LogDNSSEC("NSEC3NoDataError: Child side NSEC %s, can't use for parent qname %##s (%s)", CRDisplayString(m, *closestEncloser), name->c, DNSTypeName(qtype)); return mDNSfalse; } } LogDNSSEC("NSEC3NoDataError: Name -%##s- exists, but qtype %s does not exist in %s", name->c, DNSTypeName(qtype), CRDisplayString(m, *closestEncloser)); return mDNStrue; } return mDNSfalse; }
mDNSexport void NameErrorNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status) { RRVerifier *rv; DNSSECVerifier *pdv; CacheRecord *ncr; LogDNSSEC("NameErrorNSECCallback: called"); if (!dv->parent) { LogMsg("NameErrorNSECCCallback: no parent DV"); FreeDNSSECVerifier(m, dv); return; } if (dv->ac) { // Before we free the "dv", we need to update the // parent with our AuthChain information UpdateParent(dv); } pdv = dv->parent; // We don't care about the "dv" that was allocated in VerifyNSEC // as it just verifies one of the nsecs. Get the original verifier and // verify the other NSEC like we did the first time. dv->parent = mDNSNULL; FreeDNSSECVerifier(m, dv); if (status != DNSSEC_Secure) { goto error; } ncr = NSECParentForQuestion(m, &pdv->q); if (!ncr) { LogMsg("NameErrorNSECCallback: Can't find NSEC Parent for %##s (%s)", pdv->q.qname.c, DNSTypeName(pdv->q.qtype)); goto error; } rv = pdv->pendingNSEC; pdv->pendingNSEC = rv->next; // We might have more than one pendingNSEC in the case of NSEC3. If this is the last one, // we don't need to come back here; let the regular NSECCallback call the original callback. rv->next = mDNSNULL; LogDNSSEC("NameErrorNSECCallback: Verifying %##s (%s)", rv->name.c, DNSTypeName(rv->rrtype)); if (!pdv->pendingNSEC) VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, mDNSNULL); else VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, NameErrorNSECCallback); return; error: pdv->DVCallback(m, pdv, status); }
mDNSlocal void InfoCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { (void)m; // Unused (void)question; // Unused (void)AddRecord; // Unused if (answer->rrtype == kDNSType_A) { if (!id.NotAnInteger) id = lastid; NumAnswers++; NumAddr++; mprintf("%##s %s %.4a\n", answer->name->c, DNSTypeName(answer->rrtype), &answer->rdata->u.ipv4); hostaddr.type = mDNSAddrType_IPv4; // Prefer v4 target to v6 target, for now hostaddr.ip.v4 = answer->rdata->u.ipv4; } else if (answer->rrtype == kDNSType_AAAA) { if (!id.NotAnInteger) id = lastid; NumAnswers++; NumAAAA++; mprintf("%##s %s %.16a\n", answer->name->c, DNSTypeName(answer->rrtype), &answer->rdata->u.ipv6); if (!hostaddr.type) // Prefer v4 target to v6 target, for now { hostaddr.type = mDNSAddrType_IPv6; hostaddr.ip.v6 = answer->rdata->u.ipv6; } } else if (answer->rrtype == kDNSType_HINFO) { mDNSu8 *p = answer->rdata->u.data; strncpy(hardware, (char*)(p+1), p[0]); hardware[p[0]] = 0; p += 1 + p[0]; strncpy(software, (char*)(p+1), p[0]); software[p[0]] = 0; NumAnswers++; NumHINFO++; } // If we've got everything we're looking for, don't need to wait any more if (/*NumHINFO && */ (NumAddr || NumAAAA)) StopNow = 1; }
mDNSlocal void NameCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { (void)m; // Unused (void)question; // Unused (void)AddRecord; // Unused if (!id.NotAnInteger) id = lastid; if (answer->rrtype == kDNSType_PTR || answer->rrtype == kDNSType_CNAME) { ConvertDomainNameToCString(&answer->rdata->u.name, hostname); StopNow = 1; mprintf("%##s %s %##s\n", answer->name->c, DNSTypeName(answer->rrtype), answer->rdata->u.name.c); } }
mDNSlocal void ServicesCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) { (void)m; // Unused (void)question; // Unused (void)AddRecord; // Unused // Right now the mDNSCore targeted-query code is incomplete -- // it issues targeted queries, but accepts answers from anywhere // For now, we'll just filter responses here so we don't get confused by responses from someone else if (answer->rrtype == kDNSType_PTR && mDNSSameAddress(&lastsrc, &target)) { NumAnswers++; mprintf("%##s %s %##s\n", answer->name->c, DNSTypeName(answer->rrtype), answer->rdata->u.name.c); } }
mDNSexport void InitializeAnonInfoForQuestion(mDNS *const m, CacheRecord **McastNSEC3Records, DNSQuestion *q) { CacheRecord *nsec3CR; if (q->qtype != kDNSType_PTR) return; nsec3CR = FindMatchingNSEC3ForName(m, McastNSEC3Records, &q->qname); if (nsec3CR) { q->AnonInfo = AllocateAnonInfo(mDNSNULL, mDNSNULL, 0, &nsec3CR->resrec); if (q->AnonInfo) { debugf("InitializeAnonInfoForQuestion: Found a matching NSEC3 record %s, for %##s (%s)", RRDisplayString(m, q->AnonInfo->nsec3RR), q->qname.c, DNSTypeName(q->qtype)); } ReleaseCacheRecord(m, nsec3CR); } }
mDNSlocal CacheRecord *NSECParentForQuestion(mDNS *const m, DNSQuestion *q) { CacheGroup *cg; CacheRecord *cr; mDNSu32 slot; mDNSu32 namehash; slot = HashSlot(&q->qname); namehash = DomainNameHashValue(&q->qname); cg = CacheGroupForName(m, slot, namehash, &q->qname); if (!cg) { LogDNSSEC("NSECParentForQuestion: Cannot find cg for %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); return mDNSNULL; } for (cr = cg->members; cr; cr = cr->next) if (SameNameRecordAnswersQuestion(&cr->resrec, q)) return cr; return mDNSNULL; }
mDNSlocal mDNSBool NSECNoWildcard(mDNS *const m, ResourceRecord *rr, domainname *qname, mDNSu16 qtype) { const domainname *ce; domainname wild; // If the query name is c.x.w.example and if the name does not exist, we should get // get a nsec back that looks something like this: // // w.example NSEC a.w.example // // First, we need to get the closest encloser which in this case is w.example. Wild // card synthesis works by finding the closest encloser first and then look for // a "*" label (assuming * label does not appear in the question). If it does not // exists, it would return the NSEC at that name. And the wildcard name at the // closest encloser "*.w.example" would be covered by such an NSEC. (Appending "*" // makes it bigger than w.example and "* is smaller than "a" for the above NSEC) // ce = NSECClosestEncloser(rr, qname); if (!ce) { LogMsg("NSECNoWildcard: No closest encloser for rr %s, qname %##s (%s)", qname->c, DNSTypeName(qtype)); return mDNSfalse; } wild.c[0] = 1; wild.c[1] = '*'; wild.c[2] = 0; if (!AppendDomainName(&wild, ce)) { LogMsg("NSECNoWildcard: ERROR!! Can't append domainname closest encloser name %##s, qname %##s (%s)", ce->c, qname->c, DNSTypeName(qtype)); return mDNSfalse; } if (NSECNameExists(m, rr, &wild, qtype) != 0) { LogDNSSEC("NSECNoWildcard: Wildcard name %##s exists or not valid qname %##s (%s)", wild.c, qname->c, DNSTypeName(qtype)); return mDNSfalse; } LogDNSSEC("NSECNoWildcard: Wildcard name %##s does not exist for record %s, qname %##s (%s)", wild.c, RRDisplayString(m, rr), qname->c, DNSTypeName(qtype)); return mDNStrue; }
mDNSlocal void StartInsecureProof(mDNS * const m, DNSSECVerifier *dv) { domainname trigger; DNSSECVerifier *prevdv = mDNSNULL; // Remember the name that triggered the insecure proof AssignDomainName(&trigger, &dv->q.qname); while (dv->parent) { prevdv = dv; dv = dv->parent; } if (prevdv) { prevdv->parent = mDNSNULL; FreeDNSSECVerifier(m, prevdv); } // For Optional DNSSEC, we are opportunistically verifying dnssec. We don't care // if something results in bogus as we still want to deliver results to the // application e.g., CNAME processing results in bogus because the path is broken, // but we still want to follow CNAMEs so that we can deliver the final results to // the application. if (dv->ValidationRequired == DNSSEC_VALIDATION_SECURE_OPTIONAL) { LogDNSSEC("StartInsecureProof: Aborting insecure proof for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype)); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } LogDNSSEC("StartInsecureProof for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype)); // Don't start the insecure proof again after we finish the one that we start here by // setting InsecureProofDone. dv->InsecureProofDone = 1; ProveInsecure(m, dv, mDNSNULL, &trigger); return; }
mDNSexport void InitializeAnonInfoForCR(mDNS *const m, CacheRecord **McastNSEC3Records, CacheRecord *cr) { CacheRecord *nsec3CR; if (!(*McastNSEC3Records)) return; // If already initialized or not a PTR type, we don't have to do anything if (cr->resrec.AnonInfo || cr->resrec.rrtype != kDNSType_PTR) return; nsec3CR = FindMatchingNSEC3ForName(m, McastNSEC3Records, cr->resrec.name); if (nsec3CR) { cr->resrec.AnonInfo = AllocateAnonInfo(mDNSNULL, mDNSNULL, 0, &nsec3CR->resrec); if (cr->resrec.AnonInfo) { debugf("InitializeAnonInfoForCR: Found a matching NSEC3 record %s, for %##s (%s)", RRDisplayString(m, cr->resrec.AnonInfo->nsec3RR), cr->resrec.name->c, DNSTypeName(cr->resrec.rrtype)); } ReleaseCacheRecord(m, nsec3CR); } }
mDNSexport void ValidateWithNSECS(mDNS *const m, DNSSECVerifier *dv, CacheRecord *cr) { LogDNSSEC("ValidateWithNSECS: called for %s", CRDisplayString(m, cr)); // If we are encountering a break in the chain of trust i.e., NSEC/NSEC3s for // DS query, then do the insecure proof. This is important because if we // validate these NSECs normally and prove that they are "secure", we will // end up delivering the secure result to the original question where as // these NSEC/NSEC3s actually prove that DS does not exist and hence insecure. // // This break in the chain can happen after we have partially validated the // path (dv->ac is non-NULL) or the first time (dv->ac is NULL) after we // fetched the DNSKEY (dv->key is non-NULL). We don't want to do this // if we have just started the non-existence proof (dv->key is NULL) as // it does not indicate a break in the chain of trust. // // If we are already doing a insecurity proof, don't start another one. In // the case of NSECs, it is possible that insecurity proof starts and it // gets NSECs and as part of validating that we receive more NSECS in which // case we don't want to start another insecurity proof. if (dv->ValidationRequired != DNSSEC_VALIDATION_INSECURE && (!dv->parent || dv->parent->ValidationRequired != DNSSEC_VALIDATION_INSECURE)) { if ((dv->ac && dv->q.qtype == kDNSType_DS) || (!dv->ac && dv->key && dv->q.qtype == kDNSType_DS)) { LogDNSSEC("ValidateWithNSECS: Starting insecure proof: name %##s ac %p, key %p, parent %p", dv->q.qname.c, dv->ac, dv->key, dv->parent); StartInsecureProof(m, dv); return; } } // "parent" is set when we are validating a NSEC and we should not be here in // the normal case when parent is set. For example, we are looking up the A // record for www.example.com and following can happen. // // a) Record does not exist and we get a NSEC // b) While validating (a), we get an NSEC for the first DS record that we look up // c) Record exists but we get NSECs for the first DS record // d) We are able to partially validate (a) or (b), but we get NSECs somewhere in // the chain // // For (a), parent is not set as we are not validating the NSEC yet. Hence we would // start the validation now. // // For (b), the parent is set, but should be caught by the above "if" block because we // should have gotten the DNSKEY at least. In the case of nested insecurity proof, // we would end up here and fail with bogus. // // For (c), the parent is not set and should be caught by the above "if" block because we // should have gotten the DNSKEY at least. // // For (d), the above "if" block would catch it as "dv->ac" is non-NULL. // // Hence, we should not come here in the normal case. Possible pathological cases are: // Insecure proof getting NSECs while validating NSECs, getting NSECs for DNSKEY for (c) // above etc. if (dv->parent) { LogDNSSEC("ValidateWithNSECS: dv parent set for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype)); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) { mDNSu8 rcode; CacheRecord *neg = cr->nsec; mDNSBool nsecs_seen = mDNSfalse; while (neg) { // The list can only have NSEC or NSEC3s. This was checked when we added the // NSECs to the cache record. if (neg->resrec.rrtype == kDNSType_NSEC) nsecs_seen = mDNStrue; LogDNSSEC("ValidateWithNSECS: NSECCached Record %s", CRDisplayString(m, neg)); neg = neg->next; } rcode = (mDNSu8)(cr->responseFlags.b[1] & kDNSFlag1_RC_Mask); if (rcode == kDNSFlag1_RC_NoErr) { if (nsecs_seen) NoDataProof(m, dv, cr); else NSEC3NoDataProof(m, dv, cr); } else if (rcode == kDNSFlag1_RC_NXDomain) { if (nsecs_seen) NameErrorProof(m, dv, cr); else NSEC3NameErrorProof(m, dv, cr); } else { LogDNSSEC("ValidateWithNSECS: Rcode %d invalid", rcode); dv->DVCallback(m, dv, DNSSEC_Bogus); } } else { LogMsg("ValidateWithNSECS: Not a valid cache record %s for NSEC proofs", CRDisplayString(m, cr)); dv->DVCallback(m, dv, DNSSEC_Bogus); return; } }
// 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; }
mDNSexport void NSEC3NoDataProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr) { CacheRecord *closerEncloser = mDNSNULL; CacheRecord *closestEncloser = mDNSNULL; CacheRecord *wildcard = mDNSNULL; const domainname *ce = mDNSNULL; domainname wild; // Section 8.5, 8.6 of RFC 5155 if (NSEC3NoDataError(m, ncr, &dv->q.qname, dv->q.qtype, &closestEncloser)) { goto verify; } // Section 8.6, 8.7: if we can't find the NSEC3 RR, verify the closest encloser proof // for QNAME and the "next closer" should have the opt out if (!NSEC3ClosestEncloserProof(m, ncr, &dv->q.qname, &closestEncloser, &closerEncloser, &ce, dv->q.qtype)) { goto error; } // Section 8.7: find a matching NSEC3 for *.closestEncloser wild.c[0] = 1; wild.c[1] = '*'; wild.c[2] = 0; if (!AppendDomainName(&wild, ce)) { LogMsg("NSEC3NameErrorProof: Can't append domainname to closest encloser name %##s", ce->c); goto error; } if (!NSEC3Find(m, NSEC3ClosestEncloser, ncr, &wild, &wildcard, mDNSNULL, &ce, dv->q.qtype)) { // Not a wild card case. Section 8.6 second para applies. LogDNSSEC("NSEC3NoDataProof: Cannot find encloser for wildcard, perhaps not a wildcard case"); if (!NSEC3OptOut(closerEncloser)) { LogDNSSEC("NSEC3DataProof: opt out not set for %##s (%s), bogus", dv->q.qname.c, DNSTypeName(dv->q.qtype)); goto error; } LogDNSSEC("NSEC3DataProof: opt out set, proof complete for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype)); dv->flags |= NSEC3_OPT_OUT; } else { int bmaplen; mDNSu8 *bmap; NSEC3Parse(&wildcard->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap); if (BitmapTypeCheck(bmap, bmaplen, dv->q.qtype) || BitmapTypeCheck(bmap, bmaplen, kDNSType_CNAME)) { LogDNSSEC("NSEC3NoDataProof: qtype %s exists in %s", DNSTypeName(dv->q.qtype), CRDisplayString(m, wildcard)); goto error; } if (dv->q.qtype == kDNSType_DS && BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA)) { LogDNSSEC("NSEC3NoDataProof: Child side wildcard NSEC3 %s, can't use for parent qname %##s (%s)", CRDisplayString(m, wildcard), dv->q.qname.c, DNSTypeName(dv->q.qtype)); goto error; } else if (dv->q.qtype != kDNSType_DS && !BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA) && BitmapTypeCheck(bmap, bmaplen, kDNSType_NS)) { // Don't use the parent side record for this LogDNSSEC("NSEC3NoDataProof: Parent side wildcard NSEC3 %s, can't use for child qname %##s (%s)", CRDisplayString(m, wildcard), dv->q.qname.c, DNSTypeName(dv->q.qtype)); goto error; } LogDNSSEC("NSEC3NoDataProof: Wildcard %##s matched by %s", wild.c, CRDisplayString(m, wildcard)); } verify: if (!VerifyNSEC3(m, dv, ncr, closestEncloser, closerEncloser, wildcard, NoDataNSECCallback)) goto error; else return; error: dv->DVCallback(m, dv, DNSSEC_Bogus); }
// If the answer was result of a wildcard match, then this function proves // that a proper wildcard was used to answer the question and that the // original name does not exist mDNSexport void WildcardAnswerProof(mDNS *const m, DNSSECVerifier *dv) { CacheRecord *ncr; CacheRecord **rp; const domainname *ce; DNSQuestion q; CacheRecord **nsec3 = mDNSNULL; LogDNSSEC("WildcardAnswerProof: Question %##s (%s)", dv->origName.c, DNSTypeName(dv->origType)); // // RFC 4035: Section 3.1.3.3 // // 1) We used a wildcard because the qname does not exist, so verify // that the qname does not exist // // 2) Is the wildcard the right one ? // // Unfortunately, this is not well explained in that section. Refer to // RFC 5155 section 7.2.6. // Walk the list of nsecs we received and see if they prove that // the name does not exist mDNSPlatformMemZero(&q, sizeof(DNSQuestion)); q.ThisQInterval = -1; InitializeQuestion(m, &q, dv->InterfaceID, &dv->origName, dv->origType, mDNSNULL, mDNSNULL); ncr = NSECParentForQuestion(m, &q); if (!ncr) { LogMsg("WildcardAnswerProof: Can't find NSEC Parent for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); goto error; } else { LogDNSSEC("WildcardAnswerProof: found %s", CRDisplayString(m, ncr)); } rp = &(ncr->nsec); while (*rp) { if ((*rp)->resrec.rrtype == kDNSType_NSEC) { CacheRecord *cr = *rp; if (!NSECNameExists(m, &cr->resrec, &dv->origName, dv->origType)) break; } else if ((*rp)->resrec.rrtype == kDNSType_NSEC3) { nsec3 = rp; } rp=&(*rp)->next; } if (!(*rp)) { mDNSBool ret = mDNSfalse; if (nsec3) { ret = NSEC3WildcardAnswerProof(m, ncr, dv); } if (!ret) { LogDNSSEC("WildcardAnswerProof: NSEC3 wildcard proof failed for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); goto error; } rp = nsec3; } else { ce = NSECClosestEncloser(&((*rp)->resrec), &dv->origName); if (!ce) { LogMsg("WildcardAnswerProof: ERROR!! Closest Encloser NULL for %##s (%s)", q.qname.c, DNSTypeName(q.qtype)); goto error; } if (!SameDomainName(ce, dv->wildcardName)) { LogMsg("WildcardAnswerProof: ERROR!! Closest Encloser %##s does not match wildcard name %##s", q.qname.c, dv->wildcardName->c); goto error; } } VerifyNSEC(m, &((*rp)->resrec), mDNSNULL, dv, ncr, mDNSNULL); return; error: dv->DVCallback(m, dv, DNSSEC_Bogus); }
// 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; } }
// 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 get a NXDOMAIN error with no records in answer section. This proves // that qname does not exist. mDNSlocal void NameErrorProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr) { CacheRecord **rp; ResourceRecord *nsec_wild = mDNSNULL; ResourceRecord *nsec_noname = mDNSNULL; mStatus status; // NXDOMAIN Error. We need to prove that the qname does not exist and there // is no wildcard that can be used to answer the question. rp = &(ncr->nsec); while (*rp) { if ((*rp)->resrec.rrtype == kDNSType_NSEC) { CacheRecord *cr = *rp; if (!NSECNameExists(m, &cr->resrec, &dv->q.qname, dv->q.qtype)) { LogDNSSEC("NameErrorProof: NSEC %s proves name does not exist for %##s (%s)", RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); // If we have a wildcard, then we should check to see if the closest // encloser is the same as the wildcard. dv->flags |= NSEC_PROVES_NONAME_EXISTS; nsec_noname = &cr->resrec; } if (NSECNoWildcard(m, &cr->resrec, &dv->q.qname, dv->q.qtype)) { dv->flags |= WILDCARD_PROVES_NONAME_EXISTS; nsec_wild = &cr->resrec; LogDNSSEC("NameErrorProof: NSEC %s proves wildcard cannot answer question for %##s (%s)", RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); } } rp=&(*rp)->next; } if (!nsec_noname || !nsec_wild) { LogMsg("NameErrorProof: Proof failed for %##s (%s) noname %p, wild %p", dv->q.qname.c, DNSTypeName(dv->q.qtype), nsec_noname, nsec_wild); goto error; } // First verify wildcard NSEC and then when we are done, we will verify the noname nsec. // Sometimes a single NSEC can prove both that the "qname" does not exist and a wildcard // could not have produced qname. These are a few examples where this can happen. // // 1. If the zone is example.com and you look up *.example.com and if there are no wildcards, // you will get a NSEC back "example.com NSEC a.example.com". This proves that both the // name does not exist and *.example.com also does not exist // // 2. If the zone is example.com and it has a record like this: // // example.com NSEC d.example.com // // any name you lookup in between like a.example.com,b.example.com etc. you will get a single // NSEC back. In that case we just have to verify only once. // if (nsec_wild != nsec_noname) { RRVerifier *r = AllocateRRVerifier(nsec_noname, &status); if (!r) goto error; dv->pendingNSEC = r; LogDNSSEC("NoDataProof: Verifying wild %s", RRDisplayString(m, nsec_wild)); VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, NameErrorNSECCallback); } else { LogDNSSEC("NoDataProof: Verifying only one %s", RRDisplayString(m, nsec_wild)); VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, mDNSNULL); } return; error: dv->DVCallback(m, dv, DNSSEC_Bogus); }
// We get a NODATA error with no records in answer section. This proves // that qname does not exist. mDNSlocal void NoDataProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr) { CacheRecord **rp; domainname *wildcard = mDNSNULL; const domainname *ce = mDNSNULL; ResourceRecord *nsec_wild = mDNSNULL; ResourceRecord *nsec_noname = mDNSNULL; // NODATA Error could mean two things. The name exists with no type or there is a // wildcard that matches the name but no type. This is done by NSECNoDataError. // // If it is the case of wildcard, there are two NSECs. One is the wildcard NSEC and // the other NSEC to prove that there is no other closer match. wildcard = mDNSNULL; rp = &(ncr->nsec); while (*rp) { if ((*rp)->resrec.rrtype == kDNSType_NSEC) { CacheRecord *cr = *rp; if (NSECNoDataError(m, &cr->resrec, &dv->q.qname, dv->q.qtype, &wildcard)) { if (wildcard) { dv->flags |= WILDCARD_PROVES_NONAME_EXISTS; LogDNSSEC("NoDataProof: NSEC %s proves NODATA error for %##s (%s)", RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); } else { dv->flags |= NSEC_PROVES_NOTYPE_EXISTS; LogDNSSEC("NoDataProof: NSEC %s proves NOTYPE error for %##s (%s)", RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); } nsec_wild = &cr->resrec; } if (!NSECNameExists(m, &cr->resrec, &dv->q.qname, dv->q.qtype)) { LogDNSSEC("NoDataProof: NSEC %s proves that name %##s (%s) does not exist", RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype)); // If we have a wildcard, then we should check to see if the closest // encloser is the same as the wildcard. ce = NSECClosestEncloser(&cr->resrec, &dv->q.qname); dv->flags |= NSEC_PROVES_NONAME_EXISTS; nsec_noname = &cr->resrec; } } rp=&(*rp)->next; } if (!nsec_noname && !nsec_wild) { LogDNSSEC("NoDataProof: No valid NSECs for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype)); goto error; } // If the type exists, then we have to verify just that NSEC if (!(dv->flags & NSEC_PROVES_NOTYPE_EXISTS)) { // If we have a wildcard, then we should have a "ce" which matches the wildcard // If we don't have a wildcard, then we should have proven that the name does not // exist which means we would have set the "ce". if (wildcard && !ce) { LogMsg("NoDataProof: Cannot prove that the name %##s (%s) does not exist", dv->q.qname.c, DNSTypeName(dv->q.qtype)); goto error; } if (wildcard && !SameDomainName(wildcard, ce)) { LogMsg("NoDataProof: wildcard %##s does not match closest encloser %##s", wildcard->c, ce->c); goto error; } // If a single NSEC can prove both, then we just have validate that one NSEC. if (nsec_wild == nsec_noname) { nsec_noname = mDNSNULL; dv->flags &= ~NSEC_PROVES_NONAME_EXISTS; } } if ((dv->flags & (WILDCARD_PROVES_NONAME_EXISTS|NSEC_PROVES_NONAME_EXISTS)) == (WILDCARD_PROVES_NONAME_EXISTS|NSEC_PROVES_NONAME_EXISTS)) { mStatus status; RRVerifier *r = AllocateRRVerifier(nsec_noname, &status); if (!r) goto error; // First verify wildcard NSEC and then when we are done, we // will verify the noname nsec dv->pendingNSEC = r; LogDNSSEC("NoDataProof: Verifying wild and noname %s", RRDisplayString(m, nsec_wild)); VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, NoDataNSECCallback); } else if ((dv->flags & WILDCARD_PROVES_NONAME_EXISTS) || (dv->flags & NSEC_PROVES_NOTYPE_EXISTS)) { LogDNSSEC("NoDataProof: Verifying wild %s", RRDisplayString(m, nsec_wild)); VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, mDNSNULL); } else if (dv->flags & NSEC_PROVES_NONAME_EXISTS) { LogDNSSEC("NoDataProof: Verifying noname %s", RRDisplayString(m, nsec_noname)); VerifyNSEC(m, nsec_noname, mDNSNULL, dv, ncr, mDNSNULL); } return; error: LogDNSSEC("NoDataProof: Error return"); dv->DVCallback(m, dv, DNSSEC_Bogus); }
// returns -1 if the caller should ignore the result // returns 1 if the record answers the question // returns 0 if the record does not answer the question mDNSexport int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q) { mDNSexport mDNS mDNSStorage; ResourceRecord *nsec3RR; int i; AnonymousInfo *qai, *rai; mDNSu8 *AnonData; int AnonDataLen; rdataNSEC3 *nsec3; int hlen; const mDNSu8 hashName[NSEC3_MAX_HASH_LEN]; int nxtLength; mDNSu8 *nxtName; debugf("AnonInfoAnswersQuestion: question qname %##s", q->qname.c); // Currently only PTR records can have anonymous information if (q->qtype != kDNSType_PTR) { return -1; } // We allow anonymous questions to be answered by both normal services (without the // anonymous information) and anonymous services that are part of the same set. And // normal questions discover normal services and all anonymous services. // // The three cases have been enumerated clearly even though they all behave the // same way. if (!q->AnonInfo) { debugf("AnonInfoAnswersQuestion: not a anonymous type question"); if (!rr->AnonInfo) { // case 1 return -1; } else { // case 2 debugf("AnonInfoAnswersQuestion: Question %##s not answered using anonymous record %##s", q->qname.c, rr->name->c); return -1; } } else { // case 3 if (!rr->AnonInfo) { debugf("AnonInfoAnswersQuestion: not a anonymous type record"); return -1; } } // case 4: We have the anonymous information both in the question and the record. We need // two sets of information to validate. // // 1) Anonymous data that identifies the set/group // 2) NSEC3 record that contains the hash and the salt // // If the question is a remote one, it does not have the anonymous information to validate (just // the NSEC3 record) and hence the anonymous data should come from the local resource record. If the // question is local, it can come from either of them and if there is a mismatch between the // question and record, it won't validate. qai = q->AnonInfo; rai = rr->AnonInfo; if (qai->AnonData && rai->AnonData) { // Before a cache record is created, if there is a matching question i.e., part // of the same set, then when the cache is created we also set the anonymous // information. Otherwise, the cache record contains just the NSEC3 record and we // won't be here for that case. // // It is also possible that a local question is matched against the local AuthRecord // as that is also the case for which the AnonData would be non-NULL for both. // We match questions against AuthRecords (rather than the cache) for LocalOnly case and // to see whether a .local query should be suppressed or not. The latter never happens // because PTR queries are never suppressed. // If they don't belong to the same anonymous set, then no point in validating. if ((qai->AnonDataLen != rai->AnonDataLen) || mDNSPlatformMemCmp(qai->AnonData, rai->AnonData, qai->AnonDataLen) != 0) { debugf("AnonInfoAnswersQuestion: AnonData mis-match for record %s question %##s ", RRDisplayString(&mDNSStorage, rr), q->qname.c); return 0; } // AnonData matches i.e they belong to the same group and the same service. LogInfo("AnonInfoAnswersQuestion: Answering qname %##s, rname %##s, without validation", q->qname.c, rr->name->c); return 1; } else { debugf("AnonInfoAnswersQuestion: question %p, record %p", qai->AnonData, rai->AnonData); } if (qai->AnonData) { // If there is AnonData, then this is a local question. The // NSEC3 RR comes from the resource record which could be part // of the cache or local auth record. The cache entry could // be from a remote host or created when we heard our own // announcements. In any case, we use that to see if it matches // the question. AnonData = qai->AnonData; AnonDataLen = qai->AnonDataLen; nsec3RR = rai->nsec3RR; } else { // Remote question or hearing our own question back AnonData = rai->AnonData; AnonDataLen = rai->AnonDataLen; nsec3RR = qai->nsec3RR; } if (!AnonData || !nsec3RR) { // AnonData can be NULL for the cache entry and if we are hearing our own question back, AnonData is NULL for // that too and we can end up here for that case. debugf("AnonInfoAnswersQuestion: AnonData %p or nsec3RR %p, NULL for question %##s, record %s", AnonData, nsec3RR, q->qname.c, RRDisplayString(&mDNSStorage, rr)); return 0; } debugf("AnonInfoAnswersQuestion: Validating question %##s, ResourceRecord %s", q->qname.c, RRDisplayString(&mDNSStorage, nsec3RR)); nsec3 = (rdataNSEC3 *)nsec3RR->rdata->u.data; if (!NSEC3HashName(nsec3RR->name, nsec3, AnonData, AnonDataLen, hashName, &hlen)) { LogMsg("AnonInfoAnswersQuestion: NSEC3HashName failed for ##s", nsec3RR->name->c); return mDNSfalse; } if (hlen != SHA1_HASH_LENGTH) { LogMsg("AnonInfoAnswersQuestion: hlen wrong %d", hlen); return mDNSfalse; } NSEC3Parse(nsec3RR, mDNSNULL, &nxtLength, &nxtName, mDNSNULL, mDNSNULL); if (hlen != nxtLength) { LogMsg("AnonInfoAnswersQuestion: ERROR!! hlen %d not same as nxtLength %d", hlen, nxtLength); return mDNSfalse; } for (i = 0; i < nxtLength; i++) { if (nxtName[i] != hashName[i]) { debugf("AnonInfoAnswersQuestion: mismatch output %x, digest %x, i %d", nxtName[i+1], hashName[i], i); return 0; } } LogInfo("AnonInfoAnswersQuestion: ResourceRecord %s matched question %##s (%s)", RRDisplayString(&mDNSStorage, nsec3RR), q->qname.c, DNSTypeName(q->qtype)); return 1; }