query.cc 18 KB

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