Exemple #1
0
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;
}
Exemple #2
0
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;
}
Exemple #6
0
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);
}
Exemple #7
0
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;
}
Exemple #8
0
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);
    }
}
Exemple #9
0
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);
    }
}
Exemple #11
0
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;
}
Exemple #12
0
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;
}
Exemple #13
0
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);
    }
}
Exemple #15
0
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;
    }
}
Exemple #16
0
// 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;
}
Exemple #17
0
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);
}
Exemple #18
0
// 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);
}
Exemple #19
0
// 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;
    }
}
Exemple #20
0
// 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;
}
Exemple #21
0
// 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);
}
Exemple #22
0
// 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;
}