master_loader.cc 19.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <dns/master_loader.h>
16 17
#include <dns/master_lexer.h>
#include <dns/name.h>
18
#include <dns/rdataclass.h>
19 20 21
#include <dns/rrttl.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
22
#include <dns/rdata.h>
23

24 25
#include <boost/scoped_ptr.hpp>

26
#include <string>
27
#include <memory>
28
#include <boost/algorithm/string/predicate.hpp> // for iequals
29

30
using std::string;
31
using std::auto_ptr;
32
using boost::algorithm::iequals;
33 34 35 36

namespace isc {
namespace dns {

37 38 39 40 41 42 43 44 45 46
// 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)
    {}
};

47 48 49 50
class MasterLoader::MasterLoaderImpl {
public:
    MasterLoaderImpl(const char* master_file,
                     const Name& zone_origin,
51 52
                     const RRClass& zone_class,
                     const MasterLoaderCallbacks& callbacks,
53
                     const AddRRCallback& add_callback,
54
                     MasterLoader::Options options) :
55
        MAX_TTL(0x7fffffff),
56 57 58 59
        lexer_(),
        zone_origin_(zone_origin),
        zone_class_(zone_class),
        callbacks_(callbacks),
60
        add_callback_(add_callback),
61 62 63
        options_(options),
        master_file_(master_file),
        initialized_(false),
64
        ok_(true),
65
        many_errors_((options & MANY_ERRORS) != 0),
66
        source_count_(0),
67
        complete_(false),
68 69
        seen_error_(false),
        warn_rfc1035_ttl_(true)
70 71 72 73 74
    {}

    void pushSource(const std::string& filename) {
        std::string error;
        if (!lexer_.pushSource(filename.c_str(), &error)) {
75
            if (initialized_) {
76
                isc_throw(InternalException, error.c_str());
77 78 79 80 81
            } else {
                // Top-level file
                reportError("", 0, error);
                ok_ = false;
            }
82
        }
83
        initialized_ = true;
84 85 86
        ++source_count_;
    }

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
    void pushStreamSource(std::istream& stream) {
        lexer_.pushSource(stream);
        initialized_ = true;
        ++source_count_;
    }

    bool loadIncremental(size_t count_limit);

private:
    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());
        }
    }

110
    bool popSource() {
111 112 113
        if (--source_count_ == 0) {
            return (false);
        }
114
        lexer_.popSource();
115
        return (true);
116 117
    }

118 119
    // Get a string token. Handle it as error if it is not string.
    const string getString() {
120
        lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
121
        return (string_token_);
122 123
    }

124 125
    void doInclude() {
        // First, get the filename to include
126 127
        const string
            filename(lexer_.getNextToken(MasterToken::QSTRING).getString());
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

        // There could be an origin (or maybe not). So try looking
        const MasterToken name_tok(lexer_.getNextToken(MasterToken::QSTRING,
                                                       true));

        if (name_tok.getType() == MasterToken::QSTRING ||
            name_tok.getType() == MasterToken::STRING) {
            // TODO: Handle the origin. Once we complete #2427.
        } else {
            // We return the newline there. This is because after we pop
            // the source, we want to call eatUntilEOL and this would
            // eat to the next one.
            lexer_.ungetToken();
        }

        pushSource(filename);
144 145
    }

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    // 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 unsigned 32-bit integers,
    // 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, but the end
    // result would be the same.  Again, we follow the BIND 9's behavior here.
    //
    // post_parsing is 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 > 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);
        }
    }

169
    // Set/reset the default TTL.  Either from $TTL or SOA minimum TTL.
170 171
    // see LimitTTL() for parameter post_parsing.
    void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
172
        if (!default_ttl_) {
173
            default_ttl_.reset(new RRTTL(ttl));
174
        } else {
175
            *default_ttl_ = ttl;
176
        }
177
        limitTTL(*default_ttl_, post_parsing);
178 179
    }

180 181
    // Set/reset the TTL currently being used.  This can be used the last
    // resort TTL when no other TTL is known for an RR.
182 183 184 185 186 187 188 189
    void setCurrentTTL(const RRTTL& ttl) {
        if (!current_ttl_) {
            current_ttl_.reset(new RRTTL(ttl));
        } else {
            *current_ttl_ = ttl;
        }
    }

190 191 192 193
    // Try to set/reset the current TTL from a candidate TTL.  It's possible
    // it does not actually represent a TTL (which is not immediately
    // considered an error).  Return true iff it's recognized as a valid TTL
    // (and only in which case the current TTL is set).
194 195 196
    bool setCurrentTTL(const string& ttl_txt) {
        try {
            setCurrentTTL(RRTTL(ttl_txt));
197
            limitTTL(*current_ttl_, false);
198 199 200 201 202 203
            return (true);
        } catch (const InvalidRRTTL&) {
            return (false);
        }
    }

204 205 206 207 208 209 210 211 212 213
    // Determine the TTL of the current RR based on the given parsing context.
    //
    // explicit_ttl is true iff the TTL is explicitly specified for that RR
    // (in which case current_ttl_ is set to that TTL).
    // rrtype is the type of the current RR, and 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) {
214 215 216 217 218
        // 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;

219 220
        if (!current_ttl_ && !default_ttl_) {
            if (rrtype == RRType::SOA()) {
221
                callbacks_.warning(lexer_.getSourceName(), current_line,
222 223 224 225 226
                                   "no TTL specified; "
                                   "using SOA MINTTL instead");
                const uint32_t ttl_val =
                    dynamic_cast<const rdata::generic::SOA&>(*rdata).
                    getMinimum();
227
                setDefaultTTL(RRTTL(ttl_val), true);
228
                setCurrentTTL(*default_ttl_);
229 230 231 232 233 234
            } 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");
235 236 237 238 239 240
            }
        } else if (!explicit_ttl && default_ttl_) {
            setCurrentTTL(*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).
241
            callbacks_.warning(lexer_.getSourceName(), current_line,
242 243 244 245 246 247 248
                               "using RFC1035 TTL semantics");
            warn_rfc1035_ttl_ = false; // we only warn about this once
        }
        assert(current_ttl_);
        return (*current_ttl_);
    }

249
    void handleDirective(const char* directive, size_t length) {
250
        if (iequals(directive, "INCLUDE")) {
251
            doInclude();
252
        } else if (iequals(directive, "ORIGIN")) {
253 254 255
            // TODO: Implement
            isc_throw(isc::NotImplemented,
                      "Origin directive not implemented yet");
256
        } else if (iequals(directive, "TTL")) {
257
            setDefaultTTL(RRTTL(getString()), false);
258
            eatUntilEOL(true);
259 260 261 262 263 264
        } else {
            isc_throw(InternalException, "Unknown directive '" <<
                      string(directive, directive + length) << "'");
        }
    }

265 266 267 268 269 270 271 272
    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(),
273
                                       "Unexpected end end of file");
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
                    // 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;
            }
        }
    }

294
private:
295 296 297 298 299
    // RFC2181 Section 8 specifies TTLs are unsigned 32-bit integer,
    // effectively limiting the maximum value to 2^32-1.  This constant
    // represent a TTL of the max value.
    const RRTTL MAX_TTL;

300
    MasterLexer lexer_;
301
    const Name zone_origin_;
302 303
    const RRClass zone_class_;
    MasterLoaderCallbacks callbacks_;
304
    AddRRCallback add_callback_;
305 306 307 308 309 310
    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 set to NULL.  Once set
                                           // always non NULL.
JINMEI Tatuya's avatar
JINMEI Tatuya committed
311
    const MasterLoader::Options options_;
312
    const std::string master_file_;
313
    std::string string_token_;
314
    bool initialized_;
315 316 317
    bool ok_;                   // Is it OK to continue loading?
    const bool many_errors_;    // Are many errors allowed (or should we abort
                                // on the first)
318
    size_t source_count_;       // How many sources are currently pushed.
319
public:
320 321 322
    bool complete_;             // All work done.
    bool seen_error_;           // Was there at least one error during the
                                // load?
323 324
    bool warn_rfc1035_ttl_;     // should warn if implicit TTL determination
                                // from the previous RR is used.
325 326
};

327 328
bool
MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
329 330 331
    if (count_limit == 0) {
        isc_throw(isc::InvalidParameter, "Count limit set to 0");
    }
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    if (complete_) {
        isc_throw(isc::InvalidOperation,
                  "Trying to load when already loaded");
    }
    if (!initialized_) {
        pushSource(master_file_);
    }
    size_t count = 0;
    while (ok_ && count < count_limit) {
        try {
            // Skip all EOLNs (empty lines) and finish on EOF
            bool empty = true;
            do {
                const MasterToken& empty_token(lexer_.getNextToken());
                if (empty_token.getType() == MasterToken::END_OF_FILE) {
347 348 349 350
                    if (!popSource()) {
                        return (true);
                    } else {
                        // We try to read a token from the popped source
351 352 353
                        // So retry the loop, but first, make sure the source
                        // is at EOL
                        eatUntilEOL(true);
354 355
                        continue;
                    }
356 357 358 359 360 361
                }
                empty = empty_token.getType() == MasterToken::END_OF_LINE;
            } while (empty);
            // Return the last token, as it was not empty
            lexer_.ungetToken();

362
            const MasterToken::StringRegion&
363 364
                name_string(lexer_.getNextToken(MasterToken::QSTRING).
                            getStringRegion());
365 366 367 368 369 370 371 372 373 374 375 376 377

            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.
                continue;
            }

378 379 380 381 382 383
            const Name name(name_string.beg, name_string.len,
                            &zone_origin_);
            // TODO: Some more flexibility. We don't allow omitting
            // anything yet

            // The parameters
384
            MasterToken rrparam_token = lexer_.getNextToken();
385 386

            bool explicit_ttl = false;
387 388 389
            if (rrparam_token.getType() == MasterToken::STRING) {
                // Try TTL
                if (setCurrentTTL(rrparam_token.getString())) {
390
                    explicit_ttl = true;
391 392 393 394 395
                    rrparam_token = lexer_.getNextToken();
                }
            }

            const RRClass rrclass(rrparam_token.getString());
396 397 398 399 400 401 402 403 404
            const RRType rrtype(getString());

            // TODO: Some more validation?
            if (rrclass != zone_class_) {
                // It doesn't really matter much what type of exception
                // we throw, we catch it just below.
                isc_throw(isc::BadValue, "Class mismatch: " << rrclass <<
                          "vs. " << zone_class_);
            }
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
405
            // TODO: Check if it is SOA, it should be at the origin.
406

407 408 409 410 411
            const rdata::RdataPtr rdata(rdata::createRdata(rrtype, rrclass,
                                                           lexer_,
                                                           &zone_origin_,
                                                           options_,
                                                           callbacks_));
412 413 414 415
            // 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.
416 417 418 419
            if (rdata) {
                add_callback_(name, rrclass, rrtype,
                              getCurrentTTL(explicit_ttl, rrtype, rdata),
                              rdata);
420 421 422

                // Good, we loaded another one
                ++count;
423 424 425 426 427 428 429 430 431
            } 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");
                }
432 433 434 435 436 437 438 439 440 441 442 443
            }
        } catch (const MasterLoaderError&) {
            // This is a hack. We exclude the MasterLoaderError from the
            // below case. Once we restrict the below to some smaller
            // exception, we should remove this.
            throw;
        } catch (const isc::Exception& e) {
            // TODO: Once we do #2518, catch only the DNSTextError here,
            // not isc::Exception. The rest should be just simply
            // propagated.
            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
                        e.what());
444
            eatUntilEOL(false);
445 446 447 448 449 450
        }
    }
    // When there was a fatal error and ok is false, we say we are done.
    return (!ok_);
}

451 452
MasterLoader::MasterLoader(const char* master_file,
                           const Name& zone_origin,
453 454
                           const RRClass& zone_class,
                           const MasterLoaderCallbacks& callbacks,
455
                           const AddRRCallback& add_callback,
456 457
                           Options options)
{
458 459 460
    if (add_callback.empty()) {
        isc_throw(isc::InvalidParameter, "Empty add RR callback");
    }
461
    impl_ = new MasterLoaderImpl(master_file, zone_origin,
462
                                 zone_class, callbacks, add_callback, options);
463 464
}

465 466 467 468 469 470 471 472 473 474
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.empty()) {
        isc_throw(isc::InvalidParameter, "Empty add RR callback");
    }
475 476 477 478 479 480
    auto_ptr<MasterLoaderImpl> impl(new MasterLoaderImpl("", zone_origin,
                                                         zone_class, callbacks,
                                                         add_callback,
                                                         options));
    impl->pushStreamSource(stream);
    impl_ = impl.release();
481 482
}

483 484 485 486 487 488
MasterLoader::~MasterLoader() {
    delete impl_;
}

bool
MasterLoader::loadIncremental(size_t count_limit) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
489
    const bool result = impl_->loadIncremental(count_limit);
490 491
    impl_->complete_ = result;
    return (result);
492 493
}

494 495 496 497 498
bool
MasterLoader::loadedSucessfully() const {
    return (impl_->complete_ && !impl_->seen_error_);
}

499 500
} // end namespace dns
} // end namespace isc