123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- // 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.
- #include <algorithm> // for std::max
- #include <vector>
- #include <boost/foreach.hpp>
- #include <dns/message.h>
- #include <dns/rcode.h>
- #include <dns/rdataclass.h>
- #include <datasrc/client.h>
- #include <auth/query.h>
- using namespace isc::dns;
- using namespace isc::datasrc;
- using namespace isc::dns::rdata;
- namespace isc {
- namespace auth {
- void
- Query::addAdditional(ZoneFinder& zone, const RRset& rrset) {
- 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);
- addAdditionalAddrs(zone, ns.getNSName(), ZoneFinder::FIND_GLUE_OK);
- } else if (rrset.getType() == RRType::MX()) {
- const generic::MX& mx(dynamic_cast<const generic::MX&>(rdata));
- addAdditionalAddrs(zone, mx.getMXName());
- }
- }
- }
- void
- Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
- const ZoneFinder::FindOptions options)
- {
- // Out of zone name
- NameComparisonResult result = zone.getOrigin().compare(qname);
- if ((result.getRelation() != NameComparisonResult::SUPERDOMAIN) &&
- (result.getRelation() != NameComparisonResult::EQUAL))
- return;
- // 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;
- // Find A rrset
- if (qname_ != qname || qtype_ != RRType::A()) {
- ZoneFinder::FindResult a_result = zone.find(qname, RRType::A(), NULL,
- options | dnssec_opt_);
- if (a_result.code == ZoneFinder::SUCCESS) {
- response_.addRRset(Message::SECTION_ADDITIONAL,
- boost::const_pointer_cast<RRset>(a_result.rrset), dnssec_);
- }
- }
- // Find AAAA rrset
- if (qname_ != qname || qtype_ != RRType::AAAA()) {
- ZoneFinder::FindResult aaaa_result =
- zone.find(qname, RRType::AAAA(), NULL, options | dnssec_opt_);
- if (aaaa_result.code == ZoneFinder::SUCCESS) {
- response_.addRRset(Message::SECTION_ADDITIONAL,
- boost::const_pointer_cast<RRset>(aaaa_result.rrset),
- dnssec_);
- }
- }
- }
- void
- Query::addSOA(ZoneFinder& finder) {
- ZoneFinder::FindResult soa_result(finder.find(finder.getOrigin(),
- RRType::SOA(), NULL, dnssec_opt_));
- if (soa_result.code != ZoneFinder::SUCCESS) {
- isc_throw(NoSOA, "There's no SOA record in zone " <<
- finder.getOrigin().toText());
- } else {
- /*
- * FIXME:
- * The const-cast is wrong, but the Message interface seems
- * to insist.
- */
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(soa_result.rrset), dnssec_);
- }
- }
- // 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.
- void
- Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
- if (nsec->getRdataCount() == 0) {
- isc_throw(BadNSEC, "NSEC for NXDOMAIN is empty");
- return;
- }
- // Add the NSEC proving NXDOMAIN to the authority section.
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(nsec), dnssec_);
- // 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.
- const int qlabels = qname_.getLabelCount();
- const int olabels = qname_.compare(nsec->getName()).getCommonLabels();
- const int nlabels = qname_.compare(
- dynamic_cast<const generic::NSEC&>(nsec->getRdataIterator()->
- getCurrent()).
- getNextName()).getCommonLabels();
- const int common_labels = std::max(olabels, nlabels);
- const Name wildname(Name("*").concatenate(qname_.split(qlabels -
- common_labels)));
- // 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).
- const ZoneFinder::FindResult fresult = finder.find(wildname,
- RRType::NSEC(), NULL,
- dnssec_opt_);
- if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
- fresult.rrset->getRdataCount() == 0) {
- isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
- return;
- }
- // 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,
- boost::const_pointer_cast<RRset>(fresult.rrset),
- dnssec_);
- }
- }
- void
- Query::addWildcardProof(ZoneFinder& finder) {
- const ZoneFinder::FindResult fresult =
- finder.find(qname_, RRType::NSEC(), NULL,
- dnssec_opt_ | ZoneFinder::NO_WILDCARD);
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(fresult.rrset),
- dnssec_);
- }
- void
- Query::addAuthAdditional(ZoneFinder& finder) {
- // Fill in authority and addtional sections.
- ZoneFinder::FindResult ns_result = finder.find(finder.getOrigin(),
- RRType::NS(), NULL,
- dnssec_opt_);
- // zone origin name should have NS records
- if (ns_result.code != ZoneFinder::SUCCESS) {
- isc_throw(NoApexNS, "There's no apex NS records in zone " <<
- finder.getOrigin().toText());
- } else {
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(ns_result.rrset), dnssec_);
- // Handle additional for authority section
- addAdditional(finder, *ns_result.rrset);
- }
- }
- void
- Query::process() {
- bool keep_doing = true;
- const bool qtype_is_any = (qtype_ == RRType::ANY());
- response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
- const DataSourceClient::FindResult result =
- datasrc_client_.findZone(qname_);
- // 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
- if (result.code != result::SUCCESS &&
- result.code != result::PARTIALMATCH) {
- response_.setRcode(Rcode::REFUSED());
- return;
- }
- ZoneFinder& zfinder = *result.zone_finder;
- // Found a zone which is the nearest ancestor to QNAME, set the AA bit
- response_.setHeaderFlag(Message::HEADERFLAG_AA);
- response_.setRcode(Rcode::NOERROR());
- while (keep_doing) {
- keep_doing = false;
- std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
- const ZoneFinder::FindResult db_result(
- zfinder.find(qname_, qtype_, target.get(), dnssec_opt_));
- switch (db_result.code) {
- case ZoneFinder::DNAME: {
- // First, put the dname into the answer
- response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- 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) {
- /*
- * In case the synthesized name is too long, section 4.1
- * of RFC 2672 mandates we return YXDOMAIN.
- */
- response_.setRcode(Rcode::YXDOMAIN());
- return;
- }
- // 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,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- dnssec_);
- break;
- case ZoneFinder::SUCCESS:
- case ZoneFinder::WILDCARD:
- if (qtype_is_any) {
- // If quety type is ANY, insert all RRs under the domain
- // into answer section.
- BOOST_FOREACH(RRsetPtr rrset, *target) {
- response_.addRRset(Message::SECTION_ANSWER, rrset,
- dnssec_);
- // Handle additional for answer section
- addAdditional(*result.zone_finder, *rrset.get());
- }
- } else {
- response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- dnssec_);
- // Handle additional for answer section
- addAdditional(*result.zone_finder, *db_result.rrset);
- }
- // 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.
- if (dnssec_ && db_result.code == ZoneFinder::WILDCARD) {
- addWildcardProof(*result.zone_finder);
- }
- break;
- case ZoneFinder::DELEGATION:
- response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- dnssec_);
- 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);
- if (dnssec_ && db_result.rrset) {
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(
- db_result.rrset),
- dnssec_);
- }
- break;
- default:
- // These are new result codes (WILDCARD and WILDCARD_NXRRSET)
- // They should not happen from the in-memory and the database
- // backend isn't used yet.
- // TODO: Implement before letting the database backends in
- isc_throw(isc::NotImplemented, "Unknown result code");
- break;
- }
- }
- }
- }
- }
|