query.cc 17.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15
#include <algorithm>            // for std::max
Michal Vaner's avatar
Michal Vaner committed
16 17
#include <vector>
#include <boost/foreach.hpp>
18 19
#include <boost/bind.hpp>
#include <boost/function.hpp>
Michal Vaner's avatar
Michal Vaner committed
20

21 22
#include <dns/message.h>
#include <dns/rcode.h>
23
#include <dns/rdataclass.h>
24

25
#include <datasrc/client.h>
26 27 28 29 30

#include <auth/query.h>

using namespace isc::dns;
using namespace isc::datasrc;
31
using namespace isc::dns::rdata;
32 33 34

namespace isc {
namespace auth {
35

36
void
37
Query::addAdditional(ZoneFinder& zone, const AbstractRRset& rrset) {
chenzhengzhang's avatar
chenzhengzhang committed
38 39 40 41 42 43
    RdataIteratorPtr rdata_iterator(rrset.getRdataIterator());
    for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
        const Rdata& rdata(rdata_iterator->getCurrent());
        if (rrset.getType() == RRType::NS()) {
            // Need to perform the search in the "GLUE OK" mode.
            const generic::NS& ns = dynamic_cast<const generic::NS&>(rdata);
44
            addAdditionalAddrs(zone, ns.getNSName(), ZoneFinder::FIND_GLUE_OK);
chenzhengzhang's avatar
chenzhengzhang committed
45 46
        } else if (rrset.getType() == RRType::MX()) {
            const generic::MX& mx(dynamic_cast<const generic::MX&>(rdata));
47
            addAdditionalAddrs(zone, mx.getMXName());
48
        }
49 50 51 52
    }
}

void
53 54
Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
                          const ZoneFinder::FindOptions options)
55
{
56 57 58 59 60 61
    // Out of zone name
    NameComparisonResult result = zone.getOrigin().compare(qname);
    if ((result.getRelation() != NameComparisonResult::SUPERDOMAIN) &&
        (result.getRelation() != NameComparisonResult::EQUAL))
        return;

62 63 64 65 66 67 68 69
    // Omit additional data which has already been provided in the answer
    // section from the additional.
    //
    // All the address rrset with the owner name of qname have been inserted
    // into ANSWER section.
    if (qname_ == qname && qtype_ == RRType::ANY())
        return;

70
    // Find A rrset
71
    if (qname_ != qname || qtype_ != RRType::A()) {
72 73
        ZoneFinder::FindResult a_result = zone.find(qname, RRType::A(),
                                                    options | dnssec_opt_);
74
        if (a_result.code == ZoneFinder::SUCCESS) {
75
            response_.addRRset(Message::SECTION_ADDITIONAL,
76
                    boost::const_pointer_cast<AbstractRRset>(a_result.rrset), dnssec_);
77
        }
78
    }
79

80
    // Find AAAA rrset
81
    if (qname_ != qname || qtype_ != RRType::AAAA()) {
82 83
        ZoneFinder::FindResult aaaa_result = zone.find(qname, RRType::AAAA(),
                                                       options | dnssec_opt_);
84
        if (aaaa_result.code == ZoneFinder::SUCCESS) {
85
            response_.addRRset(Message::SECTION_ADDITIONAL,
86
                    boost::const_pointer_cast<AbstractRRset>(aaaa_result.rrset),
87
                    dnssec_);
88
        }
89 90 91
    }
}

Michal Vaner's avatar
Michal Vaner committed
92
void
93
Query::addSOA(ZoneFinder& finder) {
94 95 96
    ZoneFinder::FindResult soa_result = finder.find(finder.getOrigin(),
                                                    RRType::SOA(),
                                                    dnssec_opt_);
97
    if (soa_result.code != ZoneFinder::SUCCESS) {
Michal Vaner's avatar
Michal Vaner committed
98
        isc_throw(NoSOA, "There's no SOA record in zone " <<
99
            finder.getOrigin().toText());
Michal Vaner's avatar
Michal Vaner committed
100 101 102 103 104 105 106
    } else {
        /*
         * FIXME:
         * The const-cast is wrong, but the Message interface seems
         * to insist.
         */
        response_.addRRset(Message::SECTION_AUTHORITY,
107
            boost::const_pointer_cast<AbstractRRset>(soa_result.rrset), dnssec_);
Michal Vaner's avatar
Michal Vaner committed
108 109 110
    }
}

111 112 113 114 115 116 117 118
// Note: unless the data source client implementation or the zone content
// is broken, 'nsec' should be a valid NSEC RR.  Likewise, the call to
// find() in this method should result in NXDOMAIN and an NSEC RR that proves
// the non existent of matching wildcard.  If these assumptions aren't met
// due to a buggy data source implementation or a broken zone, we'll let
// underlying libdns++ modules throw an exception, which would result in
// either an SERVFAIL response or just ignoring the query.  We at least prevent
// a complete crash due to such broken behavior.
119
void
120
Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
121 122 123
    if (nsec->getRdataCount() == 0) {
        isc_throw(BadNSEC, "NSEC for NXDOMAIN is empty");
    }
124

125
    // Add the NSEC proving NXDOMAIN to the authority section.
126
    response_.addRRset(Message::SECTION_AUTHORITY,
127
                       boost::const_pointer_cast<AbstractRRset>(nsec), dnssec_);
128

129 130 131 132 133 134 135 136 137
    // Next, identify the best possible wildcard name that would match
    // the query name.  It's the longer common suffix with the qname
    // between the owner or the next domain of the NSEC that proves NXDOMAIN,
    // prefixed by the wildcard label, "*".  For example, for query name
    // a.b.example.com, if the NXDOMAIN NSEC is
    // b.example.com. NSEC c.example.com., the longer suffix is b.example.com.,
    // and the best possible wildcard is *.b.example.com.  If the NXDOMAIN
    // NSEC is a.example.com. NSEC c.b.example.com., the longer suffix
    // is the next domain of the NSEC, and we get the same wildcard name.
138 139 140
    const int qlabels = qname_.getLabelCount();
    const int olabels = qname_.compare(nsec->getName()).getCommonLabels();
    const int nlabels = qname_.compare(
141 142
        dynamic_cast<const generic::NSEC&>(nsec->getRdataIterator()->
                                           getCurrent()).
143 144 145 146
        getNextName()).getCommonLabels();
    const int common_labels = std::max(olabels, nlabels);
    const Name wildname(Name("*").concatenate(qname_.split(qlabels -
                                                           common_labels)));
147 148 149 150

    // Confirm the wildcard doesn't exist (this should result in NXDOMAIN;
    // otherwise we shouldn't have got NXDOMAIN for the original query in
    // the first place).
151
    const ZoneFinder::FindResult fresult =
152
        finder.find(wildname, RRType::NSEC(), dnssec_opt_);
153 154 155 156
    if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
        fresult.rrset->getRdataCount() == 0) {
        isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
    }
157 158 159 160 161 162 163 164

    // Add the (no-) wildcard proof only when it's different from the NSEC
    // that proves NXDOMAIN; sometimes they can be the same.
    // Note: name comparison is relatively expensive.  When we are at the
    // stage of performance optimization, we should consider optimizing this
    // for some optimized data source implementations.
    if (nsec->getName() != fresult.rrset->getName()) {
        response_.addRRset(Message::SECTION_AUTHORITY,
165
                           boost::const_pointer_cast<AbstractRRset>(fresult.rrset),
166 167 168 169
                           dnssec_);
    }
}

170 171
void
Query::addWildcardProof(ZoneFinder& finder) {
172 173 174
    // The query name shouldn't exist in the zone if there were no wildcard
    // substitution.  Confirm that by specifying NO_WILDCARD.  It should result
    // in NXDOMAIN and an NSEC RR that proves it should be returned.
175
    const ZoneFinder::FindResult fresult =
176 177
        finder.find(qname_, RRType::NSEC(),
                    dnssec_opt_ | ZoneFinder::NO_WILDCARD);
178 179 180 181
    if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
        fresult.rrset->getRdataCount() == 0) {
        isc_throw(BadNSEC, "Unexpected result for wildcard proof");
    }
182
    response_.addRRset(Message::SECTION_AUTHORITY,
183
                       boost::const_pointer_cast<AbstractRRset>(fresult.rrset),
184 185 186
                       dnssec_);
}

187
void
188
Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
189
    // There should be one NSEC RR which was found in the zone to prove
190
    // that there is not matched <QNAME,QTYPE> via wildcard expansion.
191
    if (nsec->getRdataCount() == 0) {
192 193 194 195
        isc_throw(BadNSEC, "NSEC for WILDCARD_NXRRSET is empty");
    }
    
    const ZoneFinder::FindResult fresult =
196 197
        finder.find(qname_, RRType::NSEC(),
                    dnssec_opt_ | ZoneFinder::NO_WILDCARD);
198 199 200 201 202
    if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
        fresult.rrset->getRdataCount() == 0) {
        isc_throw(BadNSEC, "Unexpected result for no match QNAME proof");
    }
   
203
    if (nsec->getName() != fresult.rrset->getName()) {
204
        // one NSEC RR proves wildcard_nxrrset that no matched QNAME.
205
        response_.addRRset(Message::SECTION_AUTHORITY,
206
                           boost::const_pointer_cast<AbstractRRset>(fresult.rrset),
207
                           dnssec_);
208
    }
209
}
210 211

void
212
Query::addDS(ZoneFinder& finder, const Name& dname) {
213
    ZoneFinder::FindResult ds_result =
214
        finder.find(dname, RRType::DS(), dnssec_opt_);
215 216
    if (ds_result.code == ZoneFinder::SUCCESS) {
        response_.addRRset(Message::SECTION_AUTHORITY,
217
                           boost::const_pointer_cast<AbstractRRset>(ds_result.rrset),
218
                           dnssec_);
219
    } else if (ds_result.code == ZoneFinder::NXRRSET) {
220
        addNXRRsetProof(finder, ds_result);
221 222 223
    } else {
        // Any other case should be an error
        isc_throw(BadDS, "Unexpected result for DS lookup for delegation");
224 225
    }
}
226 227

void
228 229
Query::addNXRRsetProof(ZoneFinder& finder,
                       const ZoneFinder::FindResult& db_result)
230
{
231 232
    if (db_result.isNSECSigned() && db_result.rrset) {
        response_.addRRset(Message::SECTION_AUTHORITY,
233
                           boost::const_pointer_cast<AbstractRRset>(
234 235 236 237 238 239 240 241
                               db_result.rrset),
                           dnssec_);
        if (db_result.isWildcard()) {
            addWildcardNXRRSETProof(finder, db_result.rrset);
        }
    }
}

242
void
243
Query::addAuthAdditional(ZoneFinder& finder) {
244
    // Fill in authority and addtional sections.
245
    ZoneFinder::FindResult ns_result =
246 247
        finder.find(finder.getOrigin(), RRType::NS(), dnssec_opt_);

248
    // zone origin name should have NS records
249
    if (ns_result.code != ZoneFinder::SUCCESS) {
Jerry's avatar
Jerry committed
250
        isc_throw(NoApexNS, "There's no apex NS records in zone " <<
251
                finder.getOrigin().toText());
Jerry's avatar
Jerry committed
252 253
    } else {
        response_.addRRset(Message::SECTION_AUTHORITY,
254
            boost::const_pointer_cast<AbstractRRset>(ns_result.rrset), dnssec_);
255
        // Handle additional for authority section
256
        addAdditional(finder, *ns_result.rrset);
Jerry's avatar
Jerry committed
257
    }
258 259
}

260
void
261
Query::process() {
262
    const bool qtype_is_any = (qtype_ == RRType::ANY());
chenzhengzhang's avatar
chenzhengzhang committed
263

Jerry's avatar
Jerry committed
264
    response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
265 266
    const DataSourceClient::FindResult result =
        datasrc_client_.findZone(qname_);
267

268 269 270 271 272
    // If we have no matching authoritative zone for the query name, return
    // REFUSED.  In short, this is to be compatible with BIND 9, but the
    // background discussion is not that simple.  See the relevant topic
    // at the BIND 10 developers's ML:
    // https://lists.isc.org/mailman/htdig/bind10-dev/2010-December/001633.html
273 274
    if (result.code != result::SUCCESS &&
        result.code != result::PARTIALMATCH) {
275
        response_.setRcode(Rcode::REFUSED());
276 277
        return;
    }
278
    ZoneFinder& zfinder = *result.zone_finder;
279

Jerry's avatar
Jerry committed
280 281
    // Found a zone which is the nearest ancestor to QNAME, set the AA bit
    response_.setHeaderFlag(Message::HEADERFLAG_AA);
282
    response_.setRcode(Rcode::NOERROR());
283 284 285 286 287 288 289 290 291
    std::vector<ConstRRsetPtr> target;
    boost::function0<ZoneFinder::FindResult> find;
    if (qtype_is_any) {
        find = boost::bind(&ZoneFinder::findAll, &zfinder, qname_,
                           boost::ref(target), dnssec_opt_);
    } else {
        find = boost::bind(&ZoneFinder::find, &zfinder, qname_, qtype_,
                           dnssec_opt_);
    }
292
    ZoneFinder::FindResult db_result(find());
293 294 295 296
    switch (db_result.code) {
        case ZoneFinder::DNAME: {
            // First, put the dname into the answer
            response_.addRRset(Message::SECTION_ANSWER,
297
                boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
                dnssec_);
            /*
             * Empty DNAME should never get in, as it is impossible to
             * create one in master file.
             *
             * FIXME: Other way to prevent this should be done
             */
            assert(db_result.rrset->getRdataCount() > 0);
            // Get the data of DNAME
            const rdata::generic::DNAME& dname(
                dynamic_cast<const rdata::generic::DNAME&>(
                db_result.rrset->getRdataIterator()->getCurrent()));
            // The yet unmatched prefix dname
            const Name prefix(qname_.split(0, qname_.getLabelCount() -
                db_result.rrset->getName().getLabelCount()));
            // If we put it together, will it be too long?
            // (The prefix contains trailing ., which will be removed
            if (prefix.getLength() - Name::ROOT_NAME().getLength() +
                dname.getDname().getLength() > Name::MAX_WIRE) {
317
                /*
318 319
                 * In case the synthesized name is too long, section 4.1
                 * of RFC 2672 mandates we return YXDOMAIN.
320
                 */
321 322
                response_.setRcode(Rcode::YXDOMAIN());
                return;
323
            }
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
            // The new CNAME we are creating (it will be unsigned even
            // with DNSSEC, the DNAME is signed and it can be validated
            // by that)
            RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
                RRType::CNAME(), db_result.rrset->getTTL()));
            // Construct the new target by replacing the end
            cname->addRdata(rdata::generic::CNAME(qname_.split(0,
                qname_.getLabelCount() -
                db_result.rrset->getName().getLabelCount()).
                concatenate(dname.getDname())));
            response_.addRRset(Message::SECTION_ANSWER, cname, dnssec_);
            break;
        }
        case ZoneFinder::CNAME:
            /*
             * We don't do chaining yet. Therefore handling a CNAME is
             * mostly the same as handling SUCCESS, but we didn't get
             * what we expected. It means no exceptions in ANY or NS
             * on the origin (though CNAME in origin is probably
             * forbidden anyway).
             *
             * So, just put it there.
             */
            response_.addRRset(Message::SECTION_ANSWER,
348
                boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
349
                dnssec_);
350

351 352
            // If the answer is a result of wildcard substitution,
            // add a proof that there's no closer name.
353
            if (dnssec_ && db_result.isWildcard()) {
354 355 356 357 358 359 360 361
                addWildcardProof(*result.zone_finder);
            }
            break;
        case ZoneFinder::SUCCESS:
            if (qtype_is_any) {
                // If quety type is ANY, insert all RRs under the domain
                // into answer section.
                BOOST_FOREACH(ConstRRsetPtr rrset, target) {
chenzhengzhang's avatar
chenzhengzhang committed
362
                    response_.addRRset(Message::SECTION_ANSWER,
363
                        boost::const_pointer_cast<AbstractRRset>(rrset), dnssec_);
chenzhengzhang's avatar
chenzhengzhang committed
364
                    // Handle additional for answer section
365
                    addAdditional(*result.zone_finder, *rrset.get());
366
                }
367 368
            } else {
                response_.addRRset(Message::SECTION_ANSWER,
369
                    boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
370
                    dnssec_);
371
                // Handle additional for answer section
372
                addAdditional(*result.zone_finder, *db_result.rrset);
373 374 375 376 377 378 379 380 381 382 383 384 385 386
            }
            // If apex NS records haven't been provided in the answer
            // section, insert apex NS records into the authority section
            // and AAAA/A RRS of each of the NS RDATA into the additional
            // section.
            if (qname_ != result.zone_finder->getOrigin() ||
                db_result.code != ZoneFinder::SUCCESS ||
                (qtype_ != RRType::NS() && !qtype_is_any))
            {
                addAuthAdditional(*result.zone_finder);
            }

            // If the answer is a result of wildcard substitution,
            // add a proof that there's no closer name.
387
            if (dnssec_ && db_result.isWildcard()) {
388 389 390 391 392 393
                addWildcardProof(*result.zone_finder);
            }
            break;
        case ZoneFinder::DELEGATION:
            response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
            response_.addRRset(Message::SECTION_AUTHORITY,
394
                boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
395
                dnssec_);
396 397 398 399 400
            // If DNSSEC is requested, see whether there is a DS
            // record for this delegation.
            if (dnssec_) {
                addDS(*result.zone_finder, db_result.rrset->getName());
            }
401 402 403 404 405 406 407 408 409 410 411
            addAdditional(*result.zone_finder, *db_result.rrset);
            break;
        case ZoneFinder::NXDOMAIN:
            response_.setRcode(Rcode::NXDOMAIN());
            addSOA(*result.zone_finder);
            if (dnssec_ && db_result.rrset) {
                addNXDOMAINProof(zfinder, db_result.rrset);
            }
            break;
        case ZoneFinder::NXRRSET:
            addSOA(*result.zone_finder);
412
            if (dnssec_) {
413
                addNXRRsetProof(zfinder, db_result);
414 415 416 417 418 419 420 421
            }
            break;
        default:
            // This is basically a bug of the data source implementation,
            // but could also happen in the middle of development where
            // we try to add a new result code.
            isc_throw(isc::NotImplemented, "Unknown result code");
            break;
422
    }
423
}
Michal Vaner's avatar
Michal Vaner committed
424

425 426
}
}