summaryrefslogtreecommitdiffstats
path: root/src/lib/dns/master_loader.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dns/master_loader.cc')
-rw-r--r--src/lib/dns/master_loader.cc1073
1 files changed, 1073 insertions, 0 deletions
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
new file mode 100644
index 0000000..568fdea
--- /dev/null
+++ b/src/lib/dns/master_loader.cc
@@ -0,0 +1,1073 @@
+// Copyright (C) 2012-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/master_loader.h>
+#include <dns/master_lexer.h>
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+
+#include <boost/format.hpp>
+#include <boost/algorithm/string/predicate.hpp> // for iequals
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <memory>
+#include <vector>
+
+#include <cstdio> // for sscanf()
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using std::pair;
+using boost::algorithm::iequals;
+using boost::shared_ptr;
+
+namespace isc {
+namespace dns {
+
+namespace {
+
+// An internal exception, used to control the code flow in case of errors.
+// It is thrown during the loading and caught later, not to be propagated
+// outside of the file.
+class InternalException : public isc::Exception {
+public:
+ InternalException(const char* filename, size_t line, const char* what) :
+ Exception(filename, line, what)
+ {}
+};
+
+} // end unnamed namespace
+
+/// \brief Private implementation class for the \c MasterLoader
+///
+/// This class is used internally by the \c MasterLoader and is not
+/// publicly visible. It is present to avoid polluting the public API
+/// with internal implementation details of the \c MasterLoader.
+// cppcheck-suppress noConstructor
+class MasterLoader::MasterLoaderImpl {
+public:
+ /// \brief Constructor.
+ ///
+ /// \param master_file Path to the file to load.
+ /// \param zone_origin The origin of zone to be expected inside
+ /// the master file. Currently unused, but it is expected to
+ /// be used for some validation.
+ /// \param zone_class The class of zone to be expected inside the
+ /// master file.
+ /// \param callbacks The callbacks by which it should report problems.
+ /// Usually, the callback carries a filename and line number of the
+ /// input where the problem happens. There's a special case of empty
+ /// filename and zero line in case the opening of the top-level master
+ /// file fails.
+ /// \param add_callback The callback which would be called with each
+ /// loaded RR.
+ /// \param options Options for the parsing, which is bitwise-or of
+ /// the Options values or DEFAULT. If the MANY_ERRORS option is
+ /// included, the parser tries to continue past errors. If it
+ /// is not included, it stops at first encountered error.
+ /// \throw std::bad_alloc when there's not enough memory.
+ MasterLoaderImpl(const char* master_file,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ MasterLoader::Options options) :
+ lexer_(),
+ zone_origin_(zone_origin),
+ active_origin_(zone_origin),
+ zone_class_(zone_class),
+ callbacks_(callbacks),
+ add_callback_(add_callback),
+ options_(options),
+ master_file_(master_file),
+ initialized_(false),
+ ok_(true),
+ many_errors_((options & MANY_ERRORS) != 0),
+ previous_name_(false),
+ complete_(false),
+ seen_error_(false),
+ warn_rfc1035_ttl_(true),
+ rr_count_(0)
+ {}
+
+ /// \brief Wrapper around \c MasterLexer::pushSource() (file version)
+ ///
+ /// This method is used as a wrapper around the lexer's
+ /// \c pushSource() to also save the current origin and the last
+ /// seen name (to be restored upon \c popSource()). It also calls
+ /// \c pushSource(). See \c doInclude() implementation for more
+ /// details.
+ ///
+ /// \param filename Path to the file to push as a new source.
+ /// \param current_origin The current origin name to save.
+ void pushSource(const std::string& filename, const Name& current_origin) {
+ std::string error;
+ if (!lexer_.pushSource(filename.c_str(), &error)) {
+ if (initialized_) {
+ isc_throw(InternalException, error.c_str());
+ } else {
+ // Top-level file
+ reportError("", 0, error);
+ ok_ = false;
+ }
+ }
+ // Store the current status, so we can recover it upon popSource
+ include_info_.push_back(IncludeInfo(current_origin, last_name_));
+ initialized_ = true;
+ previous_name_ = false;
+ }
+
+ /// \brief Wrapper around \c MasterLexer::pushSource() (stream version)
+ ///
+ /// Similar to \c pushSource(). This method need not save the
+ /// current origin as it is not used with $INCLUDE processing.
+ ///
+ /// \param stream The input stream to use as a new source.
+ void pushStreamSource(std::istream& stream) {
+ lexer_.pushSource(stream);
+ initialized_ = true;
+ }
+
+ /// \brief Implementation of \c MasterLoader::loadIncremental()
+ ///
+ /// See \c MasterLoader::loadIncremental() for details.
+ bool loadIncremental(size_t count_limit);
+
+ /// \brief Return the total size of the input sources pushed so
+ /// far. See \c MasterLexer::getTotalSourceSize().
+ size_t getSize() const { return (lexer_.getTotalSourceSize()); }
+
+ /// \brief Return the line number being parsed in the pushed input
+ /// sources. See \c MasterLexer::getPosition().
+ size_t getPosition() const { return (lexer_.getPosition()); }
+
+private:
+ /// \brief Report an error using the callbacks that were supplied
+ /// during \c MasterLoader construction. Note that this method also
+ /// throws \c MasterLoaderError exception if necessary, so the
+ /// caller need not throw it.
+ void reportError(const std::string& filename, size_t line,
+ const std::string& reason)
+ {
+ seen_error_ = true;
+ callbacks_.error(filename, line, reason);
+ if (!many_errors_) {
+ // In case we don't have the lenient mode, every error is fatal
+ // and we throw
+ ok_ = false;
+ complete_ = true;
+ isc_throw(MasterLoaderError, reason.c_str());
+ }
+ }
+
+ /// \brief Wrapper around \c MasterLexer::popSource()
+ ///
+ /// This method is used as a wrapper around the lexer's
+ /// \c popSource() to also restore the current origin and the last
+ /// seen name (at time of push). It also calls \c popSource(). See
+ /// \c doInclude() implementation for more details.
+ bool popSource() {
+ if (lexer_.getSourceCount() == 1) {
+ return (false);
+ }
+ lexer_.popSource();
+ // Restore original origin and last seen name
+
+ // We move in tandem, there's an extra item included during the
+ // initialization, so we can never run out of them
+ assert(!include_info_.empty());
+ const IncludeInfo& info(include_info_.back());
+ active_origin_ = info.first;
+ last_name_ = info.second;
+ include_info_.pop_back();
+ previous_name_ = false;
+ return (true);
+ }
+
+ /// \brief Get a string token. Handle it as error if it is not string.
+ const string getString() {
+ lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
+ return (string_token_);
+ }
+
+ /// \brief Parse the initial token at the beginning of a line in a
+ /// master file (or stream).
+ ///
+ /// A helper method of \c loadIncremental(), parsing the first token
+ /// of a new line. If it looks like an RR, detect its owner name
+ /// and return a string token for the next field of the RR.
+ ///
+ /// Otherwise, return either \c END_OF_LINE or \c END_OF_FILE token
+ /// depending on whether the loader continues to the next line or
+ /// completes the load, respectively. Other corner cases including
+ /// $-directive handling is done here.
+ ///
+ /// For unexpected errors, it throws an exception, which will be
+ /// handled in \c loadIncremental().
+ MasterToken handleInitialToken();
+
+ /// \brief Helper method for \c doGenerate().
+ ///
+ /// This is a helper method for \c doGenerate() that processes the
+ /// LHS or RHS for a single iteration in the range that is requested
+ /// by the $GENERATE directive and returns a generated string (that
+ /// is used to build a name (LHS) or RDATA (RHS) for an RR). See the
+ /// commented implementation for details.
+ std::string generateForIter(const std::string& str, const int it);
+
+ /// \brief Process the $GENERATE directive.
+ ///
+ /// See the commented implementation for details.
+ void doGenerate();
+
+ /// \brief Process the $ORIGIN directive.
+ void doOrigin(bool is_optional) {
+ // Parse and create the new origin. It is relative to the previous
+ // one.
+ const MasterToken&
+ name_tok(lexer_.getNextToken(MasterToken::QSTRING, is_optional));
+
+ if (name_tok.getType() == MasterToken::QSTRING ||
+ name_tok.getType() == MasterToken::STRING) {
+
+ const MasterToken::StringRegion&
+ name_string(name_tok.getStringRegion());
+ active_origin_ = Name(name_string.beg, name_string.len,
+ &active_origin_);
+ if (name_string.len > 0 &&
+ name_string.beg[name_string.len - 1] != '.') {
+ callbacks_.warning(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "The new origin is relative, did you really"
+ " mean " + active_origin_.toText() + "?");
+ }
+ } else {
+ // If it is not optional, we must not get anything but
+ // a string token.
+ assert(is_optional);
+
+ // We return the newline there. This is because we want to
+ // behave the same if there is or isn't the name, leaving the
+ // newline there.
+ lexer_.ungetToken();
+ }
+ }
+
+ /// \brief Process the $INCLUDE directive.
+ void doInclude() {
+ // First, get the filename to include
+ const string
+ filename(lexer_.getNextToken(MasterToken::QSTRING).getString());
+
+ // There optionally can be an origin, that applies before the include.
+ // We need to save the currently active origin before calling
+ // doOrigin(), because it would update active_origin_ while we need
+ // to pass the active origin before recognizing the new origin to
+ // pushSource. Note: RFC 1035 is not really clear on this: it reads
+ // "regardless of changes... within the included file", but the new
+ // origin is not really specified "within the included file".
+ // Nevertheless, this behavior is probably more likely to be the
+ // intent of the RFC, and it's compatible with BIND 9.
+ const Name current_origin = active_origin_;
+ doOrigin(true);
+
+ pushSource(filename, current_origin);
+ }
+
+ /// \brief Parse RR fields (TTL, CLASS and TYPE).
+ ///
+ /// A helper method for \c loadIncremental(). It parses part of an
+ /// RR until it finds the RR type field. If TTL or RR class is
+ /// specified before the RR type, it also recognizes and validates
+ /// them.
+ ///
+ /// \param explicit_ttl will be set to true if this method finds a
+ /// valid TTL field.
+ /// \param rrparam_token Pass the current (parsed) token here.
+ RRType parseRRParams(bool& explicit_ttl, MasterToken rrparam_token) {
+ // Find TTL, class and type. Both TTL and class are
+ // optional and may occur in any order if they exist. TTL
+ // and class come before type which must exist.
+ //
+ // [<TTL>] [<class>] <type> <RDATA>
+ // [<class>] [<TTL>] <type> <RDATA>
+
+ // named-signzone outputs TTL first, so try parsing it in order
+ // first.
+ if (setCurrentTTL(rrparam_token.getString())) {
+ explicit_ttl = true;
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ } else {
+ // If it's not a TTL here, continue and try again
+ // after the RR class below.
+ }
+
+ boost::scoped_ptr<RRClass> rrclass
+ (RRClass::createFromText(rrparam_token.getString()));
+ if (rrclass) {
+ if (*rrclass != zone_class_) {
+ isc_throw(InternalException, "Class mismatch: " << *rrclass <<
+ " vs. " << zone_class_);
+ }
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ }
+
+ // If we couldn't parse TTL earlier in the stream (above), try
+ // again at current location.
+ if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) {
+ explicit_ttl = true;
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ }
+
+ // Return the current string token's value as the RRType.
+ return (RRType(rrparam_token.getString()));
+ }
+
+ /// \brief Check and limit TTL to maximum value.
+ ///
+ /// Upper limit check when recognizing a specific TTL value from the
+ /// zone file ($TTL, the RR's TTL field, or the SOA minimum). RFC2181
+ /// Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
+ /// and prohibits transmitting a TTL field exceeding this range. We
+ /// guarantee that by limiting the value at the time of zone
+ /// parsing/loading, following what BIND 9 does. Resetting it to 0
+ /// at this point may not be exactly what the RFC states (depending on
+ /// the meaning of 'received'), but the end result would be the same (i.e.,
+ /// the guarantee on transmission). Again, we follow the BIND 9's behavior
+ /// here.
+ ///
+ /// \param ttl the TTL to check. If it is larger than the maximum
+ /// allowed, it is set to 0.
+ /// \param post_parsing should be true iff this method is called
+ /// after parsing the entire RR and the lexer is positioned at the
+ /// next line. It's just for calculating the accurate source line
+ /// when callback is necessary.
+ void limitTTL(RRTTL& ttl, bool post_parsing) {
+ if (ttl > RRTTL::MAX_TTL()) {
+ const size_t src_line = lexer_.getSourceLine() -
+ (post_parsing ? 1 : 0);
+ callbacks_.warning(lexer_.getSourceName(), src_line,
+ "TTL " + ttl.toText() + " > MAXTTL, "
+ "setting to 0 per RFC2181");
+ ttl = RRTTL(0);
+ }
+ }
+
+ /// \brief Set/reset the default TTL.
+ ///
+ /// This should be from either $TTL or SOA minimum TTL (it's the
+ /// caller's responsibility; this method doesn't care about where it
+ /// comes from). See \c limitTTL() for parameter post_parsing.
+ void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
+ assignTTL(default_ttl_, ttl);
+ limitTTL(*default_ttl_, post_parsing);
+ }
+
+ /// \brief Try to set/reset the current TTL from candidate TTL text.
+ ///
+ /// It's possible it that the text does not actually represent a TTL
+ /// (which is not immediately considered an error). Returns \c true
+ /// iff it's recognized as a valid TTL (and only in which case the
+ /// current TTL is set).
+ ///
+ /// \param ttl_txt The text to parse as a TTL.
+ /// \return true if a TTL was parsed (and set as the current TTL).
+ bool setCurrentTTL(const string& ttl_txt) {
+ // We use the factory version instead of RRTTL constructor as we
+ // need to expect cases where ttl_txt does not actually represent a TTL
+ // but an RR class or type.
+ RRTTL* rrttl = RRTTL::createFromText(ttl_txt);
+ if (rrttl) {
+ current_ttl_.reset(rrttl);
+ limitTTL(*current_ttl_, false);
+ return (true);
+ }
+ return (false);
+ }
+
+ /// \brief Determine the TTL of the current RR based on the given
+ /// parsing context.
+ ///
+ /// \c explicit_ttl is true iff the TTL is explicitly specified for that RR
+ /// (in which case current_ttl_ is set to that TTL).
+ /// \c rrtype is the type of the current RR, and \c rdata is its RDATA. They
+ /// only matter if the type is SOA and no available TTL is known. In this
+ /// case the minimum TTL of the SOA will be used as the TTL of that SOA
+ /// and the default TTL for subsequent RRs.
+ const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype,
+ const rdata::ConstRdataPtr& rdata) {
+ // We've completed parsing the full of RR, and the lexer is already
+ // positioned at the next line. If we need to call callback,
+ // we need to adjust the line number.
+ const size_t current_line = lexer_.getSourceLine() - 1;
+
+ if (!current_ttl_ && !default_ttl_) {
+ if (rrtype == RRType::SOA()) {
+ callbacks_.warning(lexer_.getSourceName(), current_line,
+ "no TTL specified; "
+ "using SOA MINTTL instead");
+ const uint32_t ttl_val =
+ dynamic_cast<const rdata::generic::SOA&>(*rdata).
+ getMinimum();
+ setDefaultTTL(RRTTL(ttl_val), true);
+ assignTTL(current_ttl_, *default_ttl_);
+ } else {
+ // On catching the exception we'll try to reach EOL again,
+ // so we need to unget it now.
+ lexer_.ungetToken();
+ throw InternalException(__FILE__, __LINE__,
+ "no TTL specified; load rejected");
+ }
+ } else if (!explicit_ttl && default_ttl_) {
+ assignTTL(current_ttl_, *default_ttl_);
+ } else if (!explicit_ttl && warn_rfc1035_ttl_) {
+ // Omitted (class and) TTL values are default to the last
+ // explicitly stated values (RFC 1035, Sec. 5.1).
+ callbacks_.warning(lexer_.getSourceName(), current_line,
+ "using RFC1035 TTL semantics; default to the "
+ "last explicitly stated TTL");
+ warn_rfc1035_ttl_ = false; // we only warn about this once
+ }
+ assert(current_ttl_);
+ return (*current_ttl_);
+ }
+
+ /// \brief Handle a $DIRECTIVE
+ ///
+ /// This method is called when a $DIRECTIVE is encountered in the
+ /// input stream.
+ void handleDirective(const char* directive, size_t length) {
+ if (iequals(directive, "INCLUDE")) {
+ doInclude();
+ } else if (iequals(directive, "ORIGIN")) {
+ doOrigin(false);
+ eatUntilEOL(true);
+ } else if (iequals(directive, "GENERATE")) {
+ doGenerate();
+ eatUntilEOL(true);
+ } else if (iequals(directive, "TTL")) {
+ setDefaultTTL(RRTTL(getString()), false);
+ eatUntilEOL(true);
+ } else {
+ isc_throw(InternalException, "Unknown directive '" <<
+ string(directive, directive + length) << "'");
+ }
+ }
+
+ /// \brief Skip tokens until end-of-line.
+ void eatUntilEOL(bool reportExtra) {
+ // We want to continue. Try to read until the end of line
+ for (;;) {
+ const MasterToken& token(lexer_.getNextToken());
+ switch (token.getType()) {
+ case MasterToken::END_OF_FILE:
+ callbacks_.warning(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "File does not end with newline");
+ // We don't pop here. The End of file will stay there,
+ // and we'll handle it in the next iteration of
+ // loadIncremental properly.
+ return;
+ case MasterToken::END_OF_LINE:
+ // Found the end of the line. Good.
+ return;
+ default:
+ // Some other type of token.
+ if (reportExtra) {
+ reportExtra = false;
+ reportError(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "Extra tokens at the end of line");
+ }
+ break;
+ }
+ }
+ }
+
+ /// \brief Assign the right RRTTL's value to the left RRTTL. If one
+ /// doesn't exist in the scoped_ptr, make a new RRTTL copy of the
+ /// right argument.
+ static void assignTTL(boost::scoped_ptr<RRTTL>& left, const RRTTL& right) {
+ if (!left) {
+ left.reset(new RRTTL(right));
+ } else {
+ *left = right;
+ }
+ }
+
+private:
+ MasterLexer lexer_;
+ const Name zone_origin_;
+ Name active_origin_; // The origin used during parsing
+ // (modifiable by $ORIGIN)
+ shared_ptr<Name> last_name_; // Last seen name (for INITIAL_WS handling)
+ const RRClass zone_class_;
+ MasterLoaderCallbacks callbacks_;
+ const AddRRCallback add_callback_;
+ boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
+ // unspecified. If NULL no default
+ // is known.
+ boost::scoped_ptr<RRTTL> current_ttl_; // The TTL used most recently.
+ // Initially unset. Once set
+ // always stores a valid
+ // RRTTL.
+ const MasterLoader::Options options_;
+ const std::string master_file_;
+ std::string string_token_;
+ bool initialized_;
+ bool ok_; // Is it OK to continue loading?
+ const bool many_errors_; // Are many errors allowed (or should we abort
+ // on the first)
+ // Some info about the outer files from which we include.
+ // The first one is current origin, the second is the last seen name
+ // in that file.
+ typedef pair<Name, shared_ptr<Name> > IncludeInfo;
+ vector<IncludeInfo> include_info_;
+ bool previous_name_; // True if there was a previous name in this file
+ // (false at the beginning or after an $INCLUDE line)
+
+public:
+ bool complete_; // All work done.
+ bool seen_error_; // Was there at least one error during the
+ // load?
+ bool warn_rfc1035_ttl_; // should warn if implicit TTL determination
+ // from the previous RR is used.
+ size_t rr_count_; // number of RRs successfully loaded
+};
+
+namespace { // begin unnamed namespace
+
+/// \brief Generate a dotted nibble sequence.
+///
+/// This method generates a dotted nibble sequence and returns it as a
+/// string. The nibbles are appended from the least significant digit
+/// (in hex representation of \c num) to the most significant digit with
+/// dots ('.') to separate the digits. If \c width is non-zero and the
+/// dotted nibble sequence has not filled the requested width, the rest
+/// of the width is filled with a dotted nibble sequence of 0 nibbles.
+///
+/// Some sample representations:
+///
+/// num = 0x1234, width = 0
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 1
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 8
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 9
+/// "4.3.2.1."
+///
+/// num = 0x1234, width = 10
+/// "4.3.2.1.0"
+///
+/// num = 0x1234, width = 11
+/// "4.3.2.1.0."
+///
+/// num = 0xabcd, width = 0, uppercase = true
+/// "D.C.B.A"
+///
+/// num = 0, width = 0
+/// "0"
+///
+/// num = 0, width = 1
+/// "0"
+///
+/// num = 0, width = 2
+/// "0."
+///
+/// num = 0, width = 3
+/// "0.0"
+///
+/// \param num The number for which the dotted nibble sequence should be
+/// generated.
+/// \param width The width of the generated string. This is only
+/// meaningful when it is larger than the dotted nibble sequence
+/// representation of \c num.
+/// \param uppercase Whether to use uppercase characters in nibble
+/// sequence.
+/// \return A string containing the dotted nibble sequence.
+std::string
+genNibbles(int num, unsigned int width, bool uppercase) {
+ static const char *hex = "0123456789abcdef0123456789ABCDEF";
+ std::string rstr;
+
+ do {
+ char ch = hex[(num & 0x0f) + (uppercase ? 16 : 0)];
+ num >>= 4;
+ rstr.push_back(ch);
+
+ if (width > 0) {
+ --width;
+ }
+
+ // If width is non zero then we need to add a label separator.
+ // If value is non zero then we need to add another label and
+ // that requires a label separator.
+ if (width > 0 || num != 0) {
+ rstr.push_back('.');
+
+ if (width > 0) {
+ --width;
+ }
+ }
+ } while ((num != 0) || (width > 0));
+
+ return (rstr);
+}
+
+} // end unnamed namespace
+
+std::string
+MasterLoader::MasterLoaderImpl::generateForIter(const std::string& str,
+ const int num)
+{
+ std::string rstr;
+
+ for (std::string::const_iterator it = str.begin(); it != str.end();) {
+ switch (*it) {
+ case '$':
+ // This is the case when the '$' character is encountered in
+ // the LHS or RHS. A computed value is added in its place in
+ // the generated string.
+ ++it;
+ if ((it != str.end()) && (*it == '$')) {
+ rstr.push_back('$');
+ ++it;
+ continue;
+ }
+
+ // The str.end() check is required.
+ if ((it == str.end()) || (*it != '{')) {
+ // There is no modifier (between {}), so just copy the
+ // passed number into the generated string.
+ rstr += boost::str(boost::format("%d") % num);
+ } else {
+ // There is a modifier (between {}). Parse it and handle
+ // the various cases below.
+ const char* scan_str =
+ str.c_str() + std::distance(str.begin(), it);
+ int offset = 0;
+ unsigned int width;
+ char base[2] = {'d', 0}; // char plus null byte
+ // cppcheck-suppress invalidscanf_libc
+ const int n = sscanf(scan_str, "{%d,%u,%1[doxXnN]}",
+ &offset, &width, base);
+ switch (n) {
+ case 1:
+ // Only 1 item was matched (the offset). Copy (num +
+ // offset) into the generated string.
+ rstr += boost::str(boost::format("%d") % (num + offset));
+ break;
+
+ case 2: {
+ // 2 items were matched (the offset and width). Copy
+ // (num + offset) and format it according to the width
+ // into the generated string.
+ const std::string fmt =
+ boost::str(boost::format("%%0%ud") % width);
+ rstr += boost::str(boost::format(fmt) % (num + offset));
+ break;
+ }
+
+ case 3:
+ // 3 items were matched (offset, width and base).
+ if ((base[0] == 'n') || (base[0] == 'N')) {
+ // The base is requesting nibbles. Format it
+ // specially (see genNibbles() documentation).
+ rstr += genNibbles(num + offset, width, (base[0] == 'N'));
+ } else {
+ // The base is not requesting nibbles. Copy (num +
+ // offset) and format it according to the width
+ // and base into the generated string.
+ const std::string fmt =
+ boost::str(boost::format("%%0%u%c") % width % base[0]);
+ rstr += boost::str(boost::format(fmt) % (num + offset));
+ }
+ break;
+
+ default:
+ // Any other case in the modifiers is an error.
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE format modifiers");
+ return ("");
+ }
+
+ // Find the closing brace. Careful that 'it' can be equal
+ // to str.end() here.
+ while ((it != str.end()) && (*it != '}')) {
+ ++it;
+ }
+ // Skip past the closing brace (if there is one).
+ if (it != str.end()) {
+ ++it;
+ }
+ }
+ break;
+
+ case '\\':
+ // This is the case when the '\' character is encountered in
+ // the LHS or RHS. The '\' and the following character are
+ // copied as-is into the generated string. This is usually
+ // used for escaping the $ character.
+ rstr.push_back(*it);
+ ++it;
+ if (it == str.end()) {
+ continue;
+ }
+ rstr.push_back(*it);
+ ++it;
+ break;
+
+ default:
+ // This is the default case that handles all other
+ // characters. They are copied as-is into the generated
+ // string.
+ rstr.push_back(*it);
+ ++it;
+ break;
+ }
+ }
+
+ return (rstr);
+}
+
+void
+MasterLoader::MasterLoaderImpl::doGenerate() {
+ // Parse the range token
+ const MasterToken& range_token = lexer_.getNextToken(MasterToken::STRING);
+ if (range_token.getType() != MasterToken::STRING) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+ const std::string range = range_token.getString();
+
+ // Parse the LHS token
+ const MasterToken& lhs_token = lexer_.getNextToken(MasterToken::STRING);
+ if (lhs_token.getType() != MasterToken::STRING) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+ const std::string lhs = lhs_token.getString();
+
+ // Parse the TTL, RR class and RR type tokens. Note that TTL and RR
+ // class may come in any order, or may be missing (either or
+ // both). If TTL is missing, we expect that it was either specified
+ // explicitly using $TTL, or is implicitly known from a previous RR,
+ // or that this is the SOA RR from which the MINIMUM field is
+ // used. It's unlikely that $GENERATE will be used with an SOA RR,
+ // but it's possible. The parsing happens within the parseRRParams()
+ // helper method which is called below.
+ const MasterToken& param_token = lexer_.getNextToken(MasterToken::STRING);
+ if (param_token.getType() != MasterToken::STRING) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+
+ bool explicit_ttl = false;
+ const RRType rrtype = parseRRParams(explicit_ttl, param_token);
+
+ // Parse the RHS token. It can be a quoted string.
+ const MasterToken& rhs_token = lexer_.getNextToken(MasterToken::QSTRING);
+ if ((rhs_token.getType() != MasterToken::QSTRING) &&
+ (rhs_token.getType() != MasterToken::STRING))
+ {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+ const std::string rhs = rhs_token.getString();
+
+ // Range can be one of two forms: start-stop or start-stop/step. If
+ // the first form is used, then step is set to 1. All of start, stop
+ // and step must be positive.
+ unsigned int start;
+ unsigned int stop;
+ unsigned int step;
+ // cppcheck-suppress invalidscanf_libc
+ const int n = sscanf(range.c_str(), "%u-%u/%u", &start, &stop, &step);
+ if ((n < 2) || (stop < start)) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "$GENERATE: invalid range: " + range);
+ return;
+ }
+
+ if (n == 2) {
+ step = 1;
+ }
+
+ // Generate and add the records.
+ for (unsigned int i = start; i <= stop; i += step) {
+ // Get generated strings for LHS and RHS. LHS goes to form the
+ // name, RHS goes to form the RDATA of the RR.
+ const std::string generated_name = generateForIter(lhs, i);
+ const std::string generated_rdata = generateForIter(rhs, i);
+ if (generated_name.empty() || generated_rdata.empty()) {
+ // The error should have been sent to the callbacks already
+ // by generateForIter().
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "$GENERATE error");
+ return;
+ }
+
+ // generateForIter() can return a string with a trailing '.' in
+ // case of a nibble representation. So we cannot use the
+ // relative Name constructor. We use concatenate() which is
+ // expensive, but keeps the generated LHS-based Name within the
+ // active origin.
+ last_name_.reset
+ (new Name(Name(generated_name).concatenate(active_origin_)));
+ previous_name_ = true;
+
+ const rdata::RdataPtr rdata =
+ rdata::createRdata(rrtype, zone_class_, generated_rdata);
+ // In case we get NULL, it means there was error creating the
+ // Rdata. The errors should have been reported by callbacks_
+ // already. We need to decide if we want to continue or not.
+ if (rdata) {
+ add_callback_(*last_name_, zone_class_, rrtype,
+ getCurrentTTL(explicit_ttl, rrtype, rdata),
+ rdata);
+ // Good, we added another one
+ ++rr_count_;
+ } else {
+ seen_error_ = true;
+ if (!many_errors_) {
+ ok_ = false;
+ complete_ = true;
+ // We don't have the exact error here, but it was
+ // reported by the error callback.
+ isc_throw(MasterLoaderError, "Invalid RR data");
+ }
+ }
+ }
+}
+
+MasterToken
+MasterLoader::MasterLoaderImpl::handleInitialToken() {
+ const MasterToken& initial_token =
+ lexer_.getNextToken(MasterLexer::QSTRING | MasterLexer::INITIAL_WS);
+
+ // The most likely case is INITIAL_WS, and then string/qstring. We
+ // handle them first.
+ if (initial_token.getType() == MasterToken::INITIAL_WS) {
+ const MasterToken& next_token = lexer_.getNextToken();
+ if (next_token.getType() == MasterToken::END_OF_LINE) {
+ return (next_token); // blank line
+ } else if (next_token.getType() == MasterToken::END_OF_FILE) {
+ lexer_.ungetToken(); // handle it in the next iteration.
+ eatUntilEOL(true); // effectively warn about the unexpected EOF.
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+
+ // This means the same name as previous.
+ if (last_name_.get() == NULL) {
+ isc_throw(InternalException, "No previous name to use in "
+ "place of initial whitespace");
+ } else if (!previous_name_) {
+ callbacks_.warning(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Owner name omitted around $INCLUDE, the result "
+ "might not be as expected");
+ }
+ return (next_token);
+ } else if (initial_token.getType() == MasterToken::STRING ||
+ initial_token.getType() == MasterToken::QSTRING) {
+ // If it is name (or directive), handle it.
+ const MasterToken::StringRegion&
+ name_string(initial_token.getStringRegion());
+
+ if (name_string.len > 0 && name_string.beg[0] == '$') {
+ // This should have either thrown (and the error handler
+ // will read up until the end of line) or read until the
+ // end of line.
+
+ // Exclude the $ from the string on this point.
+ handleDirective(name_string.beg + 1, name_string.len - 1);
+ // So, get to the next line, there's nothing more interesting
+ // in this one.
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+
+ // This should be an RR, starting with an owner name. Construct the
+ // name, and some string token should follow.
+ last_name_.reset(new Name(name_string.beg, name_string.len,
+ &active_origin_));
+ previous_name_ = true;
+ return (lexer_.getNextToken(MasterToken::STRING));
+ }
+
+ switch (initial_token.getType()) { // handle less common cases
+ case MasterToken::END_OF_FILE:
+ if (!popSource()) {
+ return (initial_token);
+ } else {
+ // We try to read a token from the popped source
+ // So continue to the next line of that source, but first, make
+ // sure the source is at EOL
+ eatUntilEOL(true);
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+ case MasterToken::END_OF_LINE:
+ return (initial_token); // empty line
+ case MasterToken::ERROR:
+ // Error token here.
+ isc_throw(InternalException, initial_token.getErrorText());
+ default:
+ // Some other token (what could that be?)
+ isc_throw(InternalException, "Parser got confused (unexpected "
+ "token " << initial_token.getType() << ")");
+ }
+}
+
+bool
+MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
+ if (count_limit == 0) {
+ isc_throw(isc::InvalidParameter, "Count limit set to 0");
+ }
+ if (complete_) {
+ isc_throw(isc::InvalidOperation,
+ "Trying to load when already loaded");
+ }
+ if (!initialized_) {
+ pushSource(master_file_, active_origin_);
+ }
+ size_t count = 0;
+ while (ok_ && count < count_limit) {
+ try {
+ const MasterToken next_token = handleInitialToken();
+ if (next_token.getType() == MasterToken::END_OF_FILE) {
+ return (true); // we are done
+ } else if (next_token.getType() == MasterToken::END_OF_LINE) {
+ continue; // nothing more to do in this line
+ }
+ // We are going to parse an RR, have known the owner name,
+ // and are now seeing the next string token in the rest of the RR.
+ assert(next_token.getType() == MasterToken::STRING);
+
+ bool explicit_ttl = false;
+ const RRType rrtype = parseRRParams(explicit_ttl, next_token);
+ // TODO: Check if it is SOA, it should be at the origin.
+
+ const rdata::RdataPtr rdata =
+ rdata::createRdata(rrtype, zone_class_, lexer_,
+ &active_origin_, options_, callbacks_);
+
+ // In case we get NULL, it means there was error creating
+ // the Rdata. The errors should have been reported by
+ // callbacks_ already. We need to decide if we want to continue
+ // or not.
+ if (rdata) {
+ add_callback_(*last_name_, zone_class_, rrtype,
+ getCurrentTTL(explicit_ttl, rrtype, rdata),
+ rdata);
+ // Good, we loaded another one
+ ++count;
+ ++rr_count_;
+ } else {
+ seen_error_ = true;
+ if (!many_errors_) {
+ ok_ = false;
+ complete_ = true;
+ // We don't have the exact error here, but it was reported
+ // by the error callback.
+ isc_throw(MasterLoaderError, "Invalid RR data");
+ }
+ }
+ } catch (const isc::dns::DNSTextError& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ } catch (const MasterLexer::ReadError& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ } catch (const MasterLexer::LexerError& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ } catch (const InternalException& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ }
+ }
+ // When there was a fatal error and ok is false, we say we are done.
+ return (!ok_);
+}
+
+MasterLoader::MasterLoader(const char* master_file,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options)
+{
+ if (!add_callback) {
+ isc_throw(isc::InvalidParameter, "Empty add RR callback");
+ }
+ impl_ = new MasterLoaderImpl(master_file, zone_origin,
+ zone_class, callbacks, add_callback, options);
+}
+
+MasterLoader::MasterLoader(std::istream& stream,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options)
+{
+ if (!add_callback) {
+ isc_throw(isc::InvalidParameter, "Empty add RR callback");
+ }
+ unique_ptr<MasterLoaderImpl>
+ impl(new MasterLoaderImpl("", zone_origin, zone_class,
+ callbacks, add_callback, options));
+ impl->pushStreamSource(stream);
+ impl_ = impl.release();
+}
+
+MasterLoader::~MasterLoader() {
+ delete impl_;
+}
+
+bool
+MasterLoader::loadIncremental(size_t count_limit) {
+ const bool result = impl_->loadIncremental(count_limit);
+ impl_->complete_ = result;
+ return (result);
+}
+
+bool
+MasterLoader::loadedSuccessfully() const {
+ return (impl_->complete_ && !impl_->seen_error_);
+}
+
+size_t
+MasterLoader::getSize() const {
+ return (impl_->getSize());
+}
+
+size_t
+MasterLoader::getPosition() const {
+ return (impl_->getPosition());
+}
+
+} // end namespace dns
+} // end namespace isc