master_loader.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. // Copyright (C) 2012 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 <dns/master_loader.h>
  15. #include <dns/master_lexer.h>
  16. #include <dns/name.h>
  17. #include <dns/rrttl.h>
  18. #include <dns/rrclass.h>
  19. #include <dns/rrtype.h>
  20. #include <dns/rdata.h>
  21. #include <string>
  22. #include <memory>
  23. #include <vector>
  24. #include <boost/algorithm/string/predicate.hpp> // for iequals
  25. #include <boost/shared_ptr.hpp>
  26. using std::string;
  27. using std::auto_ptr;
  28. using std::vector;
  29. using std::pair;
  30. using boost::algorithm::iequals;
  31. using boost::shared_ptr;
  32. namespace isc {
  33. namespace dns {
  34. namespace {
  35. // An internal exception, used to control the code flow in case of errors.
  36. // It is thrown during the loading and caught later, not to be propagated
  37. // outside of the file.
  38. class InternalException : public isc::Exception {
  39. public:
  40. InternalException(const char* filename, size_t line, const char* what) :
  41. Exception(filename, line, what)
  42. {}
  43. };
  44. } // end unnamed namespace
  45. class MasterLoader::MasterLoaderImpl {
  46. public:
  47. MasterLoaderImpl(const char* master_file,
  48. const Name& zone_origin,
  49. const RRClass& zone_class,
  50. const MasterLoaderCallbacks& callbacks,
  51. const AddRRCallback& add_callback,
  52. MasterLoader::Options options) :
  53. lexer_(),
  54. zone_origin_(zone_origin),
  55. active_origin_(zone_origin),
  56. zone_class_(zone_class),
  57. callbacks_(callbacks),
  58. add_callback_(add_callback),
  59. options_(options),
  60. master_file_(master_file),
  61. initialized_(false),
  62. ok_(true),
  63. many_errors_((options & MANY_ERRORS) != 0),
  64. complete_(false),
  65. seen_error_(false)
  66. {}
  67. void reportError(const std::string& filename, size_t line,
  68. const std::string& reason)
  69. {
  70. seen_error_ = true;
  71. callbacks_.error(filename, line, reason);
  72. if (!many_errors_) {
  73. // In case we don't have the lenient mode, every error is fatal
  74. // and we throw
  75. ok_ = false;
  76. complete_ = true;
  77. isc_throw(MasterLoaderError, reason.c_str());
  78. }
  79. }
  80. void pushSource(const std::string& filename) {
  81. std::string error;
  82. if (!lexer_.pushSource(filename.c_str(), &error)) {
  83. if (initialized_) {
  84. isc_throw(InternalException, error.c_str());
  85. } else {
  86. // Top-level file
  87. reportError("", 0, error);
  88. ok_ = false;
  89. }
  90. }
  91. // Store the current status, so we can recover it upon popSource
  92. include_info_.push_back(IncludeInfo(active_origin_, last_name_));
  93. initialized_ = true;
  94. }
  95. bool popSource() {
  96. if (lexer_.getSourceCount() == 1) {
  97. return (false);
  98. }
  99. lexer_.popSource();
  100. // Restore original origin and last seen name
  101. // We move in tandem, there's an extra item included during the
  102. // initialization, so we can never run out of them
  103. assert(!include_info_.empty());
  104. const IncludeInfo& info(include_info_.back());
  105. active_origin_ = info.first;
  106. last_name_ = info.second;
  107. include_info_.pop_back();
  108. return (true);
  109. }
  110. void pushStreamSource(std::istream& stream) {
  111. lexer_.pushSource(stream);
  112. initialized_ = true;
  113. }
  114. // Get a string token. Handle it as error if it is not string.
  115. const string getString() {
  116. lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
  117. return (string_token_);
  118. }
  119. bool loadIncremental(size_t count_limit);
  120. void doInclude() {
  121. // First, get the filename to include
  122. const string
  123. filename(lexer_.getNextToken(MasterToken::QSTRING).getString());
  124. // There could be an origin (or maybe not). So try looking
  125. const MasterToken name_tok(lexer_.getNextToken(MasterToken::QSTRING,
  126. true));
  127. if (name_tok.getType() == MasterToken::QSTRING ||
  128. name_tok.getType() == MasterToken::STRING) {
  129. // There's an optional name, meaning origin. Extract it
  130. // and store.
  131. const MasterToken::StringRegion&
  132. name_string(name_tok.getStringRegion());
  133. active_origin_ = Name(name_string.beg, name_string.len,
  134. &active_origin_);
  135. } else {
  136. // We return the newline there. This is because after we pop
  137. // the source, we want to call eatUntilEOL and this would
  138. // eat to the next one.
  139. lexer_.ungetToken();
  140. }
  141. pushSource(filename);
  142. }
  143. void doOrigin() {
  144. // Parse and create the new origin. It is relative to the previous
  145. // one.
  146. const MasterToken::StringRegion&
  147. name_string(lexer_.getNextToken(MasterToken::QSTRING).
  148. getStringRegion());
  149. active_origin_ = Name(name_string.beg, name_string.len,
  150. &active_origin_);
  151. // Make sure there's the EOLN
  152. eatUntilEOL(true);
  153. }
  154. void handleDirective(const char* directive, size_t length) {
  155. if (iequals(directive, "INCLUDE")) {
  156. doInclude();
  157. } else if (iequals(directive, "ORIGIN")) {
  158. doOrigin();
  159. } else if (iequals(directive, "TTL")) {
  160. // TODO: Implement
  161. isc_throw(isc::NotImplemented,
  162. "TTL directive not implemented yet");
  163. } else {
  164. isc_throw(InternalException, "Unknown directive '" <<
  165. string(directive, directive + length) << "'");
  166. }
  167. }
  168. void eatUntilEOL(bool reportExtra) {
  169. // We want to continue. Try to read until the end of line
  170. for (;;) {
  171. const MasterToken& token(lexer_.getNextToken());
  172. switch (token.getType()) {
  173. case MasterToken::END_OF_FILE:
  174. callbacks_.warning(lexer_.getSourceName(),
  175. lexer_.getSourceLine(),
  176. "File does not end with newline");
  177. // We don't pop here. The End of file will stay there,
  178. // and we'll handle it in the next iteration of
  179. // loadIncremental properly.
  180. return;
  181. case MasterToken::END_OF_LINE:
  182. // Found the end of the line. Good.
  183. return;
  184. default:
  185. // Some other type of token.
  186. if (reportExtra) {
  187. reportExtra = false;
  188. reportError(lexer_.getSourceName(),
  189. lexer_.getSourceLine(),
  190. "Extra tokens at the end of line");
  191. }
  192. break;
  193. }
  194. }
  195. }
  196. private:
  197. MasterLexer lexer_;
  198. const Name zone_origin_;
  199. Name active_origin_; // The origin used during parsing
  200. // (modifiable by $ORIGIN)
  201. shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
  202. const RRClass zone_class_;
  203. MasterLoaderCallbacks callbacks_;
  204. AddRRCallback add_callback_;
  205. const MasterLoader::Options options_;
  206. const std::string master_file_;
  207. std::string string_token_;
  208. bool initialized_;
  209. bool ok_; // Is it OK to continue loading?
  210. const bool many_errors_; // Are many errors allowed (or should we abort
  211. // on the first)
  212. // Some info about the outer files from which we include.
  213. // The first one is current origin, the second is the last seen name
  214. // in that file.
  215. typedef pair<Name, shared_ptr<Name> > IncludeInfo;
  216. vector<IncludeInfo> include_info_;
  217. public:
  218. bool complete_; // All work done.
  219. bool seen_error_; // Was there at least one error during the
  220. // load?
  221. };
  222. bool
  223. MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
  224. if (count_limit == 0) {
  225. isc_throw(isc::InvalidParameter, "Count limit set to 0");
  226. }
  227. if (complete_) {
  228. isc_throw(isc::InvalidOperation,
  229. "Trying to load when already loaded");
  230. }
  231. if (!initialized_) {
  232. pushSource(master_file_);
  233. }
  234. size_t count = 0;
  235. while (ok_ && count < count_limit) {
  236. try {
  237. MasterToken initial_token(MasterToken::NO_TOKEN_PRODUCED);
  238. // Skip all EOLNs (empty lines) and finish on EOF
  239. do {
  240. initial_token = lexer_.getNextToken(MasterLexer::QSTRING |
  241. MasterLexer::INITIAL_WS);
  242. if (initial_token.getType() == MasterToken::INITIAL_WS) {
  243. // The INITIAL_WS is interesting only if something is
  244. // after it. So peek there and if there's EOLN or EOF,
  245. // ignore it.
  246. const MasterToken& peek_token(lexer_.getNextToken());
  247. if (peek_token.getType() == MasterToken::END_OF_LINE ||
  248. peek_token.getType() == MasterToken::END_OF_FILE) {
  249. initial_token = peek_token;
  250. } else {
  251. // It is something interesting. Return it back and
  252. // keep the whitespace.
  253. lexer_.ungetToken();
  254. }
  255. }
  256. if (initial_token.getType() == MasterToken::END_OF_FILE) {
  257. if (!popSource()) {
  258. return (true);
  259. } else {
  260. // We try to read a token from the popped source
  261. // So retry the loop, but first, make sure the source
  262. // is at EOL
  263. eatUntilEOL(true);
  264. continue;
  265. }
  266. }
  267. } while (initial_token.getType() == MasterToken::END_OF_LINE ||
  268. initial_token.getType() == MasterToken::END_OF_FILE);
  269. if (initial_token.getType() == MasterToken::QSTRING ||
  270. initial_token.getType() == MasterToken::STRING) {
  271. // If it is name (or directive), handle it.
  272. const MasterToken::StringRegion&
  273. name_string(initial_token.getStringRegion());
  274. if (name_string.len > 0 && name_string.beg[0] == '$') {
  275. // This should have either thrown (and the error handler
  276. // will read up until the end of line) or read until the
  277. // end of line.
  278. // Exclude the $ from the string on this point.
  279. handleDirective(name_string.beg + 1, name_string.len - 1);
  280. // So, get to the next line, there's nothing more interesting
  281. // in this one.
  282. continue;
  283. }
  284. last_name_.reset(new Name(name_string.beg, name_string.len,
  285. &active_origin_));
  286. } else if (initial_token.getType() == MasterToken::INITIAL_WS) {
  287. // This means the same name as previous.
  288. if (last_name_.get() == NULL) {
  289. isc_throw(InternalException, "No previous name to use in "
  290. "place of initial whitespace");
  291. }
  292. } else if (initial_token.getType() == MasterToken::ERROR) {
  293. // Error token here.
  294. isc_throw(InternalException, initial_token.getErrorText());
  295. } else {
  296. // Some other token (what could that be?)
  297. isc_throw(InternalException, "Parser got confused (unexpected "
  298. "token " << initial_token.getType() << ")");
  299. }
  300. // TODO: Some more flexibility. We don't allow omitting
  301. // anything yet
  302. // The parameters
  303. const RRTTL ttl(getString());
  304. const RRClass rrclass(getString());
  305. const RRType rrtype(getString());
  306. // TODO: Some more validation?
  307. if (rrclass != zone_class_) {
  308. // It doesn't really matter much what type of exception
  309. // we throw, we catch it just below.
  310. isc_throw(isc::BadValue, "Class mismatch: " << rrclass <<
  311. "vs. " << zone_class_);
  312. }
  313. // TODO: Check if it is SOA, it should be at the origin.
  314. const rdata::RdataPtr data(rdata::createRdata(rrtype, rrclass,
  315. lexer_,
  316. &active_origin_,
  317. options_,
  318. callbacks_));
  319. // In case we get NULL, it means there was error creating
  320. // the Rdata. The errors should have been reported by
  321. // callbacks_ already. We need to decide if we want to continue
  322. // or not.
  323. if (data) {
  324. add_callback_(*last_name_, rrclass, rrtype, ttl, data);
  325. // Good, we loaded another one
  326. ++count;
  327. } else {
  328. seen_error_ = true;
  329. if (!many_errors_) {
  330. ok_ = false;
  331. complete_ = true;
  332. // We don't have the exact error here, but it was reported
  333. // by the error callback.
  334. isc_throw(MasterLoaderError, "Invalid RR data");
  335. }
  336. }
  337. } catch (const MasterLoaderError&) {
  338. // This is a hack. We exclude the MasterLoaderError from the
  339. // below case. Once we restrict the below to some smaller
  340. // exception, we should remove this.
  341. throw;
  342. } catch (const isc::Exception& e) {
  343. // TODO: Once we do #2518, catch only the DNSTextError here,
  344. // not isc::Exception. The rest should be just simply
  345. // propagated.
  346. reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
  347. e.what());
  348. eatUntilEOL(false);
  349. }
  350. }
  351. // When there was a fatal error and ok is false, we say we are done.
  352. return (!ok_);
  353. }
  354. MasterLoader::MasterLoader(const char* master_file,
  355. const Name& zone_origin,
  356. const RRClass& zone_class,
  357. const MasterLoaderCallbacks& callbacks,
  358. const AddRRCallback& add_callback,
  359. Options options)
  360. {
  361. if (add_callback.empty()) {
  362. isc_throw(isc::InvalidParameter, "Empty add RR callback");
  363. }
  364. impl_ = new MasterLoaderImpl(master_file, zone_origin,
  365. zone_class, callbacks, add_callback, options);
  366. }
  367. MasterLoader::MasterLoader(std::istream& stream,
  368. const Name& zone_origin,
  369. const RRClass& zone_class,
  370. const MasterLoaderCallbacks& callbacks,
  371. const AddRRCallback& add_callback,
  372. Options options)
  373. {
  374. if (add_callback.empty()) {
  375. isc_throw(isc::InvalidParameter, "Empty add RR callback");
  376. }
  377. auto_ptr<MasterLoaderImpl> impl(new MasterLoaderImpl("", zone_origin,
  378. zone_class, callbacks,
  379. add_callback,
  380. options));
  381. impl->pushStreamSource(stream);
  382. impl_ = impl.release();
  383. }
  384. MasterLoader::~MasterLoader() {
  385. delete impl_;
  386. }
  387. bool
  388. MasterLoader::loadIncremental(size_t count_limit) {
  389. const bool result = impl_->loadIncremental(count_limit);
  390. impl_->complete_ = result;
  391. return (result);
  392. }
  393. bool
  394. MasterLoader::loadedSucessfully() const {
  395. return (impl_->complete_ && !impl_->seen_error_);
  396. }
  397. } // end namespace dns
  398. } // end namespace isc