master_loader.cc 17.7 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 55 56 57 58
                     MasterLoader::Options options) :
        lexer_(),
        zone_origin_(zone_origin),
        zone_class_(zone_class),
        callbacks_(callbacks),
59
        add_callback_(add_callback),
60 61 62
        options_(options),
        master_file_(master_file),
        initialized_(false),
63
        ok_(true),
64
        many_errors_((options & MANY_ERRORS) != 0),
65
        source_count_(0),
66
        complete_(false),
67 68
        seen_error_(false),
        warn_rfc1035_ttl_(true)
69 70 71 72 73
    {}

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

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    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());
        }
    }

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

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

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

        // 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);
143 144
    }

145
    // Set/reset the default TTL.  Either from $TTL or SOA minimum TTL.
146
    void setDefaultTTL(const RRTTL& ttl) {
147
        if (!default_ttl_) {
148
            default_ttl_.reset(new RRTTL(ttl));
149
        } else {
150
            *default_ttl_ = ttl;
151
        }
152 153
    }

154 155
    // Set/reset the TTL currently being used.  This can be used the last
    // resort TTL when no other TTL is known for an RR.
156 157 158 159 160 161 162 163
    void setCurrentTTL(const RRTTL& ttl) {
        if (!current_ttl_) {
            current_ttl_.reset(new RRTTL(ttl));
        } else {
            *current_ttl_ = ttl;
        }
    }

164 165 166 167
    // 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).
168 169 170 171 172 173 174 175 176
    bool setCurrentTTL(const string& ttl_txt) {
        try {
            setCurrentTTL(RRTTL(ttl_txt));
            return (true);
        } catch (const InvalidRRTTL&) {
            return (false);
        }
    }

177 178 179 180 181 182 183 184 185 186
    // 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) {
187 188 189 190 191
        // 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;

192 193
        if (!current_ttl_ && !default_ttl_) {
            if (rrtype == RRType::SOA()) {
194
                callbacks_.warning(lexer_.getSourceName(), current_line,
195 196 197 198 199 200 201
                                   "no TTL specified; "
                                   "using SOA MINTTL instead");
                const uint32_t ttl_val =
                    dynamic_cast<const rdata::generic::SOA&>(*rdata).
                    getMinimum();
                setDefaultTTL(RRTTL(ttl_val));
                setCurrentTTL(*default_ttl_);
202 203 204 205 206 207
            } 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");
208 209 210 211 212 213
            }
        } 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).
214
            callbacks_.warning(lexer_.getSourceName(), current_line,
215 216 217 218 219 220 221
                               "using RFC1035 TTL semantics");
            warn_rfc1035_ttl_ = false; // we only warn about this once
        }
        assert(current_ttl_);
        return (*current_ttl_);
    }

222
    void handleDirective(const char* directive, size_t length) {
223
        if (iequals(directive, "INCLUDE")) {
224
            doInclude();
225
        } else if (iequals(directive, "ORIGIN")) {
226 227 228
            // TODO: Implement
            isc_throw(isc::NotImplemented,
                      "Origin directive not implemented yet");
229
        } else if (iequals(directive, "TTL")) {
230 231
            setDefaultTTL(RRTTL(getString()));
            eatUntilEOL(true);
232 233 234 235 236 237
        } else {
            isc_throw(InternalException, "Unknown directive '" <<
                      string(directive, directive + length) << "'");
        }
    }

238 239 240 241 242 243 244 245
    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(),
246
                                       "Unexpected end end of file");
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
                    // 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;
            }
        }
    }

267 268
private:
    MasterLexer lexer_;
269
    const Name zone_origin_;
270 271
    const RRClass zone_class_;
    MasterLoaderCallbacks callbacks_;
272
    AddRRCallback add_callback_;
273 274 275 276 277 278
    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
279
    const MasterLoader::Options options_;
280
    const std::string master_file_;
281
    std::string string_token_;
282
    bool initialized_;
283 284 285
    bool ok_;                   // Is it OK to continue loading?
    const bool many_errors_;    // Are many errors allowed (or should we abort
                                // on the first)
286
    size_t source_count_;       // How many sources are currently pushed.
287
public:
288 289 290
    bool complete_;             // All work done.
    bool seen_error_;           // Was there at least one error during the
                                // load?
291 292
    bool warn_rfc1035_ttl_;     // should warn if implicit TTL determination
                                // from the previous RR is used.
293 294
};

295 296
bool
MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
297 298 299
    if (count_limit == 0) {
        isc_throw(isc::InvalidParameter, "Count limit set to 0");
    }
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
    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) {
315 316 317 318
                    if (!popSource()) {
                        return (true);
                    } else {
                        // We try to read a token from the popped source
319 320 321
                        // So retry the loop, but first, make sure the source
                        // is at EOL
                        eatUntilEOL(true);
322 323
                        continue;
                    }
324 325 326 327 328 329
                }
                empty = empty_token.getType() == MasterToken::END_OF_LINE;
            } while (empty);
            // Return the last token, as it was not empty
            lexer_.ungetToken();

330
            const MasterToken::StringRegion&
331 332
                name_string(lexer_.getNextToken(MasterToken::QSTRING).
                            getStringRegion());
333 334 335 336 337 338 339 340 341 342 343 344 345

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

346 347 348 349 350 351
            const Name name(name_string.beg, name_string.len,
                            &zone_origin_);
            // TODO: Some more flexibility. We don't allow omitting
            // anything yet

            // The parameters
352
            MasterToken rrparam_token = lexer_.getNextToken();
353 354

            bool explicit_ttl = false;
355 356 357
            if (rrparam_token.getType() == MasterToken::STRING) {
                // Try TTL
                if (setCurrentTTL(rrparam_token.getString())) {
358
                    explicit_ttl = true;
359 360 361 362 363
                    rrparam_token = lexer_.getNextToken();
                }
            }

            const RRClass rrclass(rrparam_token.getString());
364 365 366 367 368 369 370 371 372
            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
373
            // TODO: Check if it is SOA, it should be at the origin.
374

375 376 377 378 379
            const rdata::RdataPtr rdata(rdata::createRdata(rrtype, rrclass,
                                                           lexer_,
                                                           &zone_origin_,
                                                           options_,
                                                           callbacks_));
380 381 382 383
            // 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.
384 385 386 387
            if (rdata) {
                add_callback_(name, rrclass, rrtype,
                              getCurrentTTL(explicit_ttl, rrtype, rdata),
                              rdata);
388 389 390

                // Good, we loaded another one
                ++count;
391 392 393 394 395 396 397 398 399
            } 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");
                }
400 401 402 403 404 405 406 407 408 409 410 411
            }
        } 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());
412
            eatUntilEOL(false);
413 414 415 416 417 418
        }
    }
    // When there was a fatal error and ok is false, we say we are done.
    return (!ok_);
}

419 420
MasterLoader::MasterLoader(const char* master_file,
                           const Name& zone_origin,
421 422
                           const RRClass& zone_class,
                           const MasterLoaderCallbacks& callbacks,
423
                           const AddRRCallback& add_callback,
424 425
                           Options options)
{
426 427 428
    if (add_callback.empty()) {
        isc_throw(isc::InvalidParameter, "Empty add RR callback");
    }
429
    impl_ = new MasterLoaderImpl(master_file, zone_origin,
430
                                 zone_class, callbacks, add_callback, options);
431 432
}

433 434 435 436 437 438 439 440 441 442
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");
    }
443 444 445 446 447 448
    auto_ptr<MasterLoaderImpl> impl(new MasterLoaderImpl("", zone_origin,
                                                         zone_class, callbacks,
                                                         add_callback,
                                                         options));
    impl->pushStreamSource(stream);
    impl_ = impl.release();
449 450
}

451 452 453 454 455 456
MasterLoader::~MasterLoader() {
    delete impl_;
}

bool
MasterLoader::loadIncremental(size_t count_limit) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
457
    const bool result = impl_->loadIncremental(count_limit);
458 459
    impl_->complete_ = result;
    return (result);
460 461
}

462 463 464 465 466
bool
MasterLoader::loadedSucessfully() const {
    return (impl_->complete_ && !impl_->seen_error_);
}

467 468
} // end namespace dns
} // end namespace isc