query.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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. }
  108. // Add the NSEC proving NXDOMAIN to the authority section.
  109. response_.addRRset(Message::SECTION_AUTHORITY,
  110. boost::const_pointer_cast<RRset>(nsec), dnssec_);
  111. // Next, identify the best possible wildcard name that would match
  112. // the query name. It's the longer common suffix with the qname
  113. // between the owner or the next domain of the NSEC that proves NXDOMAIN,
  114. // prefixed by the wildcard label, "*". For example, for query name
  115. // a.b.example.com, if the NXDOMAIN NSEC is
  116. // b.example.com. NSEC c.example.com., the longer suffix is b.example.com.,
  117. // and the best possible wildcard is *.b.example.com. If the NXDOMAIN
  118. // NSEC is a.example.com. NSEC c.b.example.com., the longer suffix
  119. // is the next domain of the NSEC, and we get the same wildcard name.
  120. const int qlabels = qname_.getLabelCount();
  121. const int olabels = qname_.compare(nsec->getName()).getCommonLabels();
  122. const int nlabels = qname_.compare(
  123. dynamic_cast<const generic::NSEC&>(nsec->getRdataIterator()->
  124. getCurrent()).
  125. getNextName()).getCommonLabels();
  126. const int common_labels = std::max(olabels, nlabels);
  127. const Name wildname(Name("*").concatenate(qname_.split(qlabels -
  128. common_labels)));
  129. // Confirm the wildcard doesn't exist (this should result in NXDOMAIN;
  130. // otherwise we shouldn't have got NXDOMAIN for the original query in
  131. // the first place).
  132. const ZoneFinder::FindResult fresult = finder.find(wildname,
  133. RRType::NSEC(), NULL,
  134. dnssec_opt_);
  135. if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
  136. fresult.rrset->getRdataCount() == 0) {
  137. isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
  138. }
  139. // Add the (no-) wildcard proof only when it's different from the NSEC
  140. // that proves NXDOMAIN; sometimes they can be the same.
  141. // Note: name comparison is relatively expensive. When we are at the
  142. // stage of performance optimization, we should consider optimizing this
  143. // for some optimized data source implementations.
  144. if (nsec->getName() != fresult.rrset->getName()) {
  145. response_.addRRset(Message::SECTION_AUTHORITY,
  146. boost::const_pointer_cast<RRset>(fresult.rrset),
  147. dnssec_);
  148. }
  149. }
  150. void
  151. Query::addWildcardProof(ZoneFinder& finder) {
  152. // The query name shouldn't exist in the zone if there were no wildcard
  153. // substitution. Confirm that by specifying NO_WILDCARD. It should result
  154. // in NXDOMAIN and an NSEC RR that proves it should be returned.
  155. const ZoneFinder::FindResult fresult =
  156. finder.find(qname_, RRType::NSEC(), NULL,
  157. dnssec_opt_ | ZoneFinder::NO_WILDCARD);
  158. if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
  159. fresult.rrset->getRdataCount() == 0) {
  160. isc_throw(BadNSEC, "Unexpected result for wildcard proof");
  161. }
  162. response_.addRRset(Message::SECTION_AUTHORITY,
  163. boost::const_pointer_cast<RRset>(fresult.rrset),
  164. dnssec_);
  165. }
  166. void
  167. Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
  168. // There should be one NSEC RR which was found in the zone to prove
  169. // that there is not matched <QNAME,QTYPE> via wildcard expansion.
  170. if (nsec->getRdataCount() == 0) {
  171. isc_throw(BadNSEC, "NSEC for WILDCARD_NXRRSET is empty");
  172. }
  173. // Add this NSEC RR to authority section.
  174. response_.addRRset(Message::SECTION_AUTHORITY,
  175. boost::const_pointer_cast<RRset>(nsec), dnssec_);
  176. const ZoneFinder::FindResult fresult =
  177. finder.find(qname_, RRType::NSEC(), NULL,
  178. dnssec_opt_ | ZoneFinder::NO_WILDCARD);
  179. if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
  180. fresult.rrset->getRdataCount() == 0) {
  181. isc_throw(BadNSEC, "Unexpected result for no match QNAME proof");
  182. }
  183. if (nsec->getName() != fresult.rrset->getName()) {
  184. // one NSEC RR proves wildcard_nxrrset that no matched QNAME.
  185. response_.addRRset(Message::SECTION_AUTHORITY,
  186. boost::const_pointer_cast<RRset>(fresult.rrset),
  187. dnssec_);
  188. }
  189. }
  190. void
  191. Query::addAuthAdditional(ZoneFinder& finder) {
  192. // Fill in authority and addtional sections.
  193. ZoneFinder::FindResult ns_result = finder.find(finder.getOrigin(),
  194. RRType::NS(), NULL,
  195. dnssec_opt_);
  196. // zone origin name should have NS records
  197. if (ns_result.code != ZoneFinder::SUCCESS) {
  198. isc_throw(NoApexNS, "There's no apex NS records in zone " <<
  199. finder.getOrigin().toText());
  200. } else {
  201. response_.addRRset(Message::SECTION_AUTHORITY,
  202. boost::const_pointer_cast<RRset>(ns_result.rrset), dnssec_);
  203. // Handle additional for authority section
  204. addAdditional(finder, *ns_result.rrset);
  205. }
  206. }
  207. void
  208. Query::process() {
  209. bool keep_doing = true;
  210. const bool qtype_is_any = (qtype_ == RRType::ANY());
  211. response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
  212. const DataSourceClient::FindResult result =
  213. datasrc_client_.findZone(qname_);
  214. // If we have no matching authoritative zone for the query name, return
  215. // REFUSED. In short, this is to be compatible with BIND 9, but the
  216. // background discussion is not that simple. See the relevant topic
  217. // at the BIND 10 developers's ML:
  218. // https://lists.isc.org/mailman/htdig/bind10-dev/2010-December/001633.html
  219. if (result.code != result::SUCCESS &&
  220. result.code != result::PARTIALMATCH) {
  221. response_.setRcode(Rcode::REFUSED());
  222. return;
  223. }
  224. ZoneFinder& zfinder = *result.zone_finder;
  225. // Found a zone which is the nearest ancestor to QNAME, set the AA bit
  226. response_.setHeaderFlag(Message::HEADERFLAG_AA);
  227. response_.setRcode(Rcode::NOERROR());
  228. while (keep_doing) {
  229. keep_doing = false;
  230. std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
  231. const ZoneFinder::FindResult db_result(
  232. zfinder.find(qname_, qtype_, target.get(), dnssec_opt_));
  233. switch (db_result.code) {
  234. case ZoneFinder::DNAME: {
  235. // First, put the dname into the answer
  236. response_.addRRset(Message::SECTION_ANSWER,
  237. boost::const_pointer_cast<RRset>(db_result.rrset),
  238. dnssec_);
  239. /*
  240. * Empty DNAME should never get in, as it is impossible to
  241. * create one in master file.
  242. *
  243. * FIXME: Other way to prevent this should be done
  244. */
  245. assert(db_result.rrset->getRdataCount() > 0);
  246. // Get the data of DNAME
  247. const rdata::generic::DNAME& dname(
  248. dynamic_cast<const rdata::generic::DNAME&>(
  249. db_result.rrset->getRdataIterator()->getCurrent()));
  250. // The yet unmatched prefix dname
  251. const Name prefix(qname_.split(0, qname_.getLabelCount() -
  252. db_result.rrset->getName().getLabelCount()));
  253. // If we put it together, will it be too long?
  254. // (The prefix contains trailing ., which will be removed
  255. if (prefix.getLength() - Name::ROOT_NAME().getLength() +
  256. dname.getDname().getLength() > Name::MAX_WIRE) {
  257. /*
  258. * In case the synthesized name is too long, section 4.1
  259. * of RFC 2672 mandates we return YXDOMAIN.
  260. */
  261. response_.setRcode(Rcode::YXDOMAIN());
  262. return;
  263. }
  264. // The new CNAME we are creating (it will be unsigned even
  265. // with DNSSEC, the DNAME is signed and it can be validated
  266. // by that)
  267. RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
  268. RRType::CNAME(), db_result.rrset->getTTL()));
  269. // Construct the new target by replacing the end
  270. cname->addRdata(rdata::generic::CNAME(qname_.split(0,
  271. qname_.getLabelCount() -
  272. db_result.rrset->getName().getLabelCount()).
  273. concatenate(dname.getDname())));
  274. response_.addRRset(Message::SECTION_ANSWER, cname, dnssec_);
  275. break;
  276. }
  277. case ZoneFinder::CNAME:
  278. case ZoneFinder::WILDCARD_CNAME:
  279. /*
  280. * We don't do chaining yet. Therefore handling a CNAME is
  281. * mostly the same as handling SUCCESS, but we didn't get
  282. * what we expected. It means no exceptions in ANY or NS
  283. * on the origin (though CNAME in origin is probably
  284. * forbidden anyway).
  285. *
  286. * So, just put it there.
  287. */
  288. response_.addRRset(Message::SECTION_ANSWER,
  289. boost::const_pointer_cast<RRset>(db_result.rrset),
  290. dnssec_);
  291. // If the answer is a result of wildcard substitution,
  292. // add a proof that there's no closer name.
  293. if (dnssec_ && db_result.code == ZoneFinder::WILDCARD_CNAME) {
  294. addWildcardProof(*result.zone_finder);
  295. }
  296. break;
  297. case ZoneFinder::SUCCESS:
  298. case ZoneFinder::WILDCARD:
  299. if (qtype_is_any) {
  300. // If quety type is ANY, insert all RRs under the domain
  301. // into answer section.
  302. BOOST_FOREACH(RRsetPtr rrset, *target) {
  303. response_.addRRset(Message::SECTION_ANSWER, rrset,
  304. dnssec_);
  305. // Handle additional for answer section
  306. addAdditional(*result.zone_finder, *rrset.get());
  307. }
  308. } else {
  309. response_.addRRset(Message::SECTION_ANSWER,
  310. boost::const_pointer_cast<RRset>(db_result.rrset),
  311. dnssec_);
  312. // Handle additional for answer section
  313. addAdditional(*result.zone_finder, *db_result.rrset);
  314. }
  315. // If apex NS records haven't been provided in the answer
  316. // section, insert apex NS records into the authority section
  317. // and AAAA/A RRS of each of the NS RDATA into the additional
  318. // section.
  319. if (qname_ != result.zone_finder->getOrigin() ||
  320. db_result.code != ZoneFinder::SUCCESS ||
  321. (qtype_ != RRType::NS() && !qtype_is_any))
  322. {
  323. addAuthAdditional(*result.zone_finder);
  324. }
  325. // If the answer is a result of wildcard substitution,
  326. // add a proof that there's no closer name.
  327. if (dnssec_ && db_result.code == ZoneFinder::WILDCARD) {
  328. addWildcardProof(*result.zone_finder);
  329. }
  330. break;
  331. case ZoneFinder::DELEGATION:
  332. response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
  333. response_.addRRset(Message::SECTION_AUTHORITY,
  334. boost::const_pointer_cast<RRset>(db_result.rrset),
  335. dnssec_);
  336. addAdditional(*result.zone_finder, *db_result.rrset);
  337. break;
  338. case ZoneFinder::NXDOMAIN:
  339. response_.setRcode(Rcode::NXDOMAIN());
  340. addSOA(*result.zone_finder);
  341. if (dnssec_ && db_result.rrset) {
  342. addNXDOMAINProof(zfinder, db_result.rrset);
  343. }
  344. break;
  345. case ZoneFinder::NXRRSET:
  346. addSOA(*result.zone_finder);
  347. if (dnssec_ && db_result.rrset) {
  348. response_.addRRset(Message::SECTION_AUTHORITY,
  349. boost::const_pointer_cast<RRset>(
  350. db_result.rrset),
  351. dnssec_);
  352. }
  353. break;
  354. case ZoneFinder::WILDCARD_NXRRSET:
  355. addSOA(*result.zone_finder);
  356. if (dnssec_ && db_result.rrset) {
  357. addWildcardNXRRSETProof(zfinder, db_result.rrset);
  358. }
  359. break;
  360. default:
  361. // This is basically a bug of the data source implementation,
  362. // but could also happen in the middle of development where
  363. // we try to add a new result code.
  364. isc_throw(isc::NotImplemented, "Unknown result code");
  365. break;
  366. }
  367. }
  368. }
  369. }
  370. }