query.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. // Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <algorithm> // for std::max
  15. #include <vector>
  16. #include <boost/foreach.hpp>
  17. #include <dns/message.h>
  18. #include <dns/rcode.h>
  19. #include <dns/rdataclass.h>
  20. #include <datasrc/client.h>
  21. #include <auth/query.h>
  22. using namespace isc::dns;
  23. using namespace isc::datasrc;
  24. using namespace isc::dns::rdata;
  25. namespace isc {
  26. namespace auth {
  27. void
  28. Query::addAdditional(ZoneFinder& zone, const RRset& rrset) {
  29. RdataIteratorPtr rdata_iterator(rrset.getRdataIterator());
  30. for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
  31. const Rdata& rdata(rdata_iterator->getCurrent());
  32. if (rrset.getType() == RRType::NS()) {
  33. // Need to perform the search in the "GLUE OK" mode.
  34. const generic::NS& ns = dynamic_cast<const generic::NS&>(rdata);
  35. addAdditionalAddrs(zone, ns.getNSName(), ZoneFinder::FIND_GLUE_OK);
  36. } else if (rrset.getType() == RRType::MX()) {
  37. const generic::MX& mx(dynamic_cast<const generic::MX&>(rdata));
  38. addAdditionalAddrs(zone, mx.getMXName());
  39. }
  40. }
  41. }
  42. void
  43. Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
  44. const ZoneFinder::FindOptions options)
  45. {
  46. // Out of zone name
  47. NameComparisonResult result = zone.getOrigin().compare(qname);
  48. if ((result.getRelation() != NameComparisonResult::SUPERDOMAIN) &&
  49. (result.getRelation() != NameComparisonResult::EQUAL))
  50. return;
  51. // Omit additional data which has already been provided in the answer
  52. // section from the additional.
  53. //
  54. // All the address rrset with the owner name of qname have been inserted
  55. // into ANSWER section.
  56. if (qname_ == qname && qtype_ == RRType::ANY())
  57. return;
  58. // Find A rrset
  59. if (qname_ != qname || qtype_ != RRType::A()) {
  60. ZoneFinder::FindResult a_result = zone.find(qname, RRType::A(), NULL,
  61. options | dnssec_opt_);
  62. if (a_result.code == ZoneFinder::SUCCESS) {
  63. response_.addRRset(Message::SECTION_ADDITIONAL,
  64. boost::const_pointer_cast<RRset>(a_result.rrset), dnssec_);
  65. }
  66. }
  67. // Find AAAA rrset
  68. if (qname_ != qname || qtype_ != RRType::AAAA()) {
  69. ZoneFinder::FindResult aaaa_result =
  70. zone.find(qname, RRType::AAAA(), NULL, options | dnssec_opt_);
  71. if (aaaa_result.code == ZoneFinder::SUCCESS) {
  72. response_.addRRset(Message::SECTION_ADDITIONAL,
  73. boost::const_pointer_cast<RRset>(aaaa_result.rrset),
  74. dnssec_);
  75. }
  76. }
  77. }
  78. void
  79. Query::addSOA(ZoneFinder& finder) {
  80. ZoneFinder::FindResult soa_result(finder.find(finder.getOrigin(),
  81. RRType::SOA(), NULL, dnssec_opt_));
  82. if (soa_result.code != ZoneFinder::SUCCESS) {
  83. isc_throw(NoSOA, "There's no SOA record in zone " <<
  84. finder.getOrigin().toText());
  85. } else {
  86. /*
  87. * FIXME:
  88. * The const-cast is wrong, but the Message interface seems
  89. * to insist.
  90. */
  91. response_.addRRset(Message::SECTION_AUTHORITY,
  92. boost::const_pointer_cast<RRset>(soa_result.rrset), dnssec_);
  93. }
  94. }
  95. // Note: unless the data source client implementation or the zone content
  96. // is broken, 'nsec' should be a valid NSEC RR. Likewise, the call to
  97. // find() in this method should result in NXDOMAIN and an NSEC RR that proves
  98. // the non existent of matching wildcard. If these assumptions aren't met
  99. // due to a buggy data source implementation or a broken zone, we'll let
  100. // underlying libdns++ modules throw an exception, which would result in
  101. // either an SERVFAIL response or just ignoring the query. We at least prevent
  102. // a complete crash due to such broken behavior.
  103. void
  104. Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
  105. if (nsec->getRdataCount() == 0) {
  106. isc_throw(BadNSEC, "NSEC for NXDOMAIN is empty");
  107. return;
  108. }
  109. // Add the NSEC proving NXDOMAIN to the authority section.
  110. response_.addRRset(Message::SECTION_AUTHORITY,
  111. boost::const_pointer_cast<RRset>(nsec), dnssec_);
  112. // Next, identify the best possible wildcard name that would match
  113. // the query name. It's the longer common suffix with the qname
  114. // between the owner or the next domain of the NSEC that proves NXDOMAIN,
  115. // prefixed by the wildcard label, "*". For example, for query name
  116. // a.b.example.com, if the NXDOMAIN NSEC is
  117. // b.example.com. NSEC c.example.com., the longer suffix is b.example.com.,
  118. // and the best possible wildcard is *.b.example.com. If the NXDOMAIN
  119. // NSEC is a.example.com. NSEC c.b.example.com., the longer suffix
  120. // is the next domain of the NSEC, and we get the same wildcard name.
  121. const int qlabels = qname_.getLabelCount();
  122. const int olabels = qname_.compare(nsec->getName()).getCommonLabels();
  123. const int nlabels = qname_.compare(
  124. dynamic_cast<const generic::NSEC&>(nsec->getRdataIterator()->
  125. getCurrent()).
  126. getNextName()).getCommonLabels();
  127. const int common_labels = std::max(olabels, nlabels);
  128. const Name wildname(Name("*").concatenate(qname_.split(qlabels -
  129. common_labels)));
  130. // Confirm the wildcard doesn't exist (this should result in NXDOMAIN;
  131. // otherwise we shouldn't have got NXDOMAIN for the original query in
  132. // the first place).
  133. const ZoneFinder::FindResult fresult = finder.find(wildname,
  134. RRType::NSEC(), NULL,
  135. dnssec_opt_);
  136. if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
  137. fresult.rrset->getRdataCount() == 0) {
  138. isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
  139. return;
  140. }
  141. // Add the (no-) wildcard proof only when it's different from the NSEC
  142. // that proves NXDOMAIN; sometimes they can be the same.
  143. // Note: name comparison is relatively expensive. When we are at the
  144. // stage of performance optimization, we should consider optimizing this
  145. // for some optimized data source implementations.
  146. if (nsec->getName() != fresult.rrset->getName()) {
  147. response_.addRRset(Message::SECTION_AUTHORITY,
  148. boost::const_pointer_cast<RRset>(fresult.rrset),
  149. dnssec_);
  150. }
  151. }
  152. void
  153. Query::addWildcardProof(ZoneFinder& finder) {
  154. // The query name shouldn't exist in the zone if there were no wildcard
  155. // substitution. Confirm that by specifying NO_WILDCARD. It should result
  156. // in NXDOMAIN and an NSEC RR that proves it should be returned.
  157. const ZoneFinder::FindResult fresult =
  158. finder.find(qname_, RRType::NSEC(), NULL,
  159. dnssec_opt_ | ZoneFinder::NO_WILDCARD);
  160. if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
  161. fresult.rrset->getRdataCount() == 0) {
  162. isc_throw(BadNSEC, "Unexpected result for wildcard proof");
  163. return;
  164. }
  165. response_.addRRset(Message::SECTION_AUTHORITY,
  166. boost::const_pointer_cast<RRset>(fresult.rrset),
  167. dnssec_);
  168. }
  169. void
  170. Query::addWildcardNxrrsetProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
  171. // There should be one NSEC RR which was found in the zone to prove
  172. // that there is not matched <QNAME,QTYPE> via wildcard expansion.
  173. if (nsec->getRdataCount() == 0) {
  174. isc_throw(BadNSEC, "NSEC for WILDCARD_NXRRSET is empty");
  175. return;
  176. }
  177. // Add this NSEC RR to authority section.
  178. response_.addRRset(Message::SECTION_AUTHORITY,
  179. boost::const_pointer_cast<RRset>(nsec), dnssec_);
  180. const ZoneFinder::FindResult fresult =
  181. finder.find(qname_, RRType::NSEC(), NULL,
  182. dnssec_opt_ | ZoneFinder::NO_WILDCARD);
  183. if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
  184. fresult.rrset->getRdataCount() == 0) {
  185. isc_throw(BadNSEC, "Unexpected result for no match QNAME proof");
  186. return;
  187. }
  188. if (nsec->getName() != fresult.rrset->getName()) {
  189. // one NSEC RR proves wildcard_nxrrset that no matched QNAME.
  190. response_.addRRset(Message::SECTION_AUTHORITY,
  191. boost::const_pointer_cast<RRset>(fresult.rrset),
  192. dnssec_);
  193. }
  194. }
  195. void
  196. Query::addAuthAdditional(ZoneFinder& finder) {
  197. // Fill in authority and addtional sections.
  198. ZoneFinder::FindResult ns_result = finder.find(finder.getOrigin(),
  199. RRType::NS(), NULL,
  200. dnssec_opt_);
  201. // zone origin name should have NS records
  202. if (ns_result.code != ZoneFinder::SUCCESS) {
  203. isc_throw(NoApexNS, "There's no apex NS records in zone " <<
  204. finder.getOrigin().toText());
  205. } else {
  206. response_.addRRset(Message::SECTION_AUTHORITY,
  207. boost::const_pointer_cast<RRset>(ns_result.rrset), dnssec_);
  208. // Handle additional for authority section
  209. addAdditional(finder, *ns_result.rrset);
  210. }
  211. }
  212. void
  213. Query::process() {
  214. bool keep_doing = true;
  215. const bool qtype_is_any = (qtype_ == RRType::ANY());
  216. response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
  217. const DataSourceClient::FindResult result =
  218. datasrc_client_.findZone(qname_);
  219. // If we have no matching authoritative zone for the query name, return
  220. // REFUSED. In short, this is to be compatible with BIND 9, but the
  221. // background discussion is not that simple. See the relevant topic
  222. // at the BIND 10 developers's ML:
  223. // https://lists.isc.org/mailman/htdig/bind10-dev/2010-December/001633.html
  224. if (result.code != result::SUCCESS &&
  225. result.code != result::PARTIALMATCH) {
  226. response_.setRcode(Rcode::REFUSED());
  227. return;
  228. }
  229. ZoneFinder& zfinder = *result.zone_finder;
  230. // Found a zone which is the nearest ancestor to QNAME, set the AA bit
  231. response_.setHeaderFlag(Message::HEADERFLAG_AA);
  232. response_.setRcode(Rcode::NOERROR());
  233. while (keep_doing) {
  234. keep_doing = false;
  235. std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
  236. const ZoneFinder::FindResult db_result(
  237. zfinder.find(qname_, qtype_, target.get(), dnssec_opt_));
  238. switch (db_result.code) {
  239. case ZoneFinder::DNAME: {
  240. // First, put the dname into the answer
  241. response_.addRRset(Message::SECTION_ANSWER,
  242. boost::const_pointer_cast<RRset>(db_result.rrset),
  243. dnssec_);
  244. /*
  245. * Empty DNAME should never get in, as it is impossible to
  246. * create one in master file.
  247. *
  248. * FIXME: Other way to prevent this should be done
  249. */
  250. assert(db_result.rrset->getRdataCount() > 0);
  251. // Get the data of DNAME
  252. const rdata::generic::DNAME& dname(
  253. dynamic_cast<const rdata::generic::DNAME&>(
  254. db_result.rrset->getRdataIterator()->getCurrent()));
  255. // The yet unmatched prefix dname
  256. const Name prefix(qname_.split(0, qname_.getLabelCount() -
  257. db_result.rrset->getName().getLabelCount()));
  258. // If we put it together, will it be too long?
  259. // (The prefix contains trailing ., which will be removed
  260. if (prefix.getLength() - Name::ROOT_NAME().getLength() +
  261. dname.getDname().getLength() > Name::MAX_WIRE) {
  262. /*
  263. * In case the synthesized name is too long, section 4.1
  264. * of RFC 2672 mandates we return YXDOMAIN.
  265. */
  266. response_.setRcode(Rcode::YXDOMAIN());
  267. return;
  268. }
  269. // The new CNAME we are creating (it will be unsigned even
  270. // with DNSSEC, the DNAME is signed and it can be validated
  271. // by that)
  272. RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
  273. RRType::CNAME(), db_result.rrset->getTTL()));
  274. // Construct the new target by replacing the end
  275. cname->addRdata(rdata::generic::CNAME(qname_.split(0,
  276. qname_.getLabelCount() -
  277. db_result.rrset->getName().getLabelCount()).
  278. concatenate(dname.getDname())));
  279. response_.addRRset(Message::SECTION_ANSWER, cname, dnssec_);
  280. break;
  281. }
  282. case ZoneFinder::CNAME:
  283. case ZoneFinder::WILDCARD_CNAME:
  284. /*
  285. * We don't do chaining yet. Therefore handling a CNAME is
  286. * mostly the same as handling SUCCESS, but we didn't get
  287. * what we expected. It means no exceptions in ANY or NS
  288. * on the origin (though CNAME in origin is probably
  289. * forbidden anyway).
  290. *
  291. * So, just put it there.
  292. */
  293. response_.addRRset(Message::SECTION_ANSWER,
  294. boost::const_pointer_cast<RRset>(db_result.rrset),
  295. dnssec_);
  296. // If the answer is a result of wildcard substitution,
  297. // add a proof that there's no closer name.
  298. if (dnssec_ && db_result.code == ZoneFinder::WILDCARD_CNAME) {
  299. addWildcardProof(*result.zone_finder);
  300. }
  301. break;
  302. case ZoneFinder::SUCCESS:
  303. case ZoneFinder::WILDCARD:
  304. if (qtype_is_any) {
  305. // If quety type is ANY, insert all RRs under the domain
  306. // into answer section.
  307. BOOST_FOREACH(RRsetPtr rrset, *target) {
  308. response_.addRRset(Message::SECTION_ANSWER, rrset,
  309. dnssec_);
  310. // Handle additional for answer section
  311. addAdditional(*result.zone_finder, *rrset.get());
  312. }
  313. } else {
  314. response_.addRRset(Message::SECTION_ANSWER,
  315. boost::const_pointer_cast<RRset>(db_result.rrset),
  316. dnssec_);
  317. // Handle additional for answer section
  318. addAdditional(*result.zone_finder, *db_result.rrset);
  319. }
  320. // If apex NS records haven't been provided in the answer
  321. // section, insert apex NS records into the authority section
  322. // and AAAA/A RRS of each of the NS RDATA into the additional
  323. // section.
  324. if (qname_ != result.zone_finder->getOrigin() ||
  325. db_result.code != ZoneFinder::SUCCESS ||
  326. (qtype_ != RRType::NS() && !qtype_is_any))
  327. {
  328. addAuthAdditional(*result.zone_finder);
  329. }
  330. // If the answer is a result of wildcard substitution,
  331. // add a proof that there's no closer name.
  332. if (dnssec_ && db_result.code == ZoneFinder::WILDCARD) {
  333. addWildcardProof(*result.zone_finder);
  334. }
  335. break;
  336. case ZoneFinder::DELEGATION:
  337. response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
  338. response_.addRRset(Message::SECTION_AUTHORITY,
  339. boost::const_pointer_cast<RRset>(db_result.rrset),
  340. dnssec_);
  341. addAdditional(*result.zone_finder, *db_result.rrset);
  342. break;
  343. case ZoneFinder::NXDOMAIN:
  344. response_.setRcode(Rcode::NXDOMAIN());
  345. addSOA(*result.zone_finder);
  346. if (dnssec_ && db_result.rrset) {
  347. addNXDOMAINProof(zfinder, db_result.rrset);
  348. }
  349. break;
  350. case ZoneFinder::NXRRSET:
  351. addSOA(*result.zone_finder);
  352. if (dnssec_ && db_result.rrset) {
  353. response_.addRRset(Message::SECTION_AUTHORITY,
  354. boost::const_pointer_cast<RRset>(
  355. db_result.rrset),
  356. dnssec_);
  357. }
  358. break;
  359. case ZoneFinder::WILDCARD_NXRRSET:
  360. addSOA(*result.zone_finder);
  361. if (dnssec_ && db_result.rrset) {
  362. addWildcardNxrrsetProof(zfinder,db_result.rrset);
  363. }
  364. break;
  365. default:
  366. // This is basically a bug of the data source implementation,
  367. // but could also happen in the middle of development where
  368. // we try to add a new result code.
  369. isc_throw(isc::NotImplemented, "Unknown result code");
  370. break;
  371. }
  372. }
  373. }
  374. }
  375. }