database.cc 54.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (C) 2011  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.

15
#include <string>
16
#include <utility>
17 18
#include <vector>

19
#include <datasrc/database.h>
20 21
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
22

23 24
#include <exceptions/exceptions.h>
#include <dns/name.h>
25
#include <dns/rrclass.h>
26
#include <dns/rrttl.h>
27
#include <dns/rrset.h>
28
#include <dns/rdata.h>
29 30
#include <dns/rdataclass.h>

31
#include <datasrc/data_source.h>
Jelte Jansen's avatar
Jelte Jansen committed
32
#include <datasrc/logger.h>
33

Jelte Jansen's avatar
Jelte Jansen committed
34 35
#include <boost/foreach.hpp>

36
using namespace isc::dns;
37
using namespace std;
38
using namespace isc::dns::rdata;
39

40 41 42
namespace isc {
namespace datasrc {

43 44
DatabaseClient::DatabaseClient(RRClass rrclass,
                               boost::shared_ptr<DatabaseAccessor>
45
                               accessor) :
46
    rrclass_(rrclass), accessor_(accessor)
47
{
48
    if (!accessor_) {
49
        isc_throw(isc::InvalidParameter,
50
                  "No database provided to DatabaseClient");
51 52 53 54 55
    }
}

DataSourceClient::FindResult
DatabaseClient::findZone(const Name& name) const {
56
    std::pair<bool, int> zone(accessor_->getZone(name.toText()));
57 58 59
    // Try exact first
    if (zone.first) {
        return (FindResult(result::SUCCESS,
60
                           ZoneFinderPtr(new Finder(accessor_,
61
                                                    zone.second, name))));
62
    }
Jelte Jansen's avatar
Jelte Jansen committed
63
    // Then super domains
64 65
    // Start from 1, as 0 is covered above
    for (size_t i(1); i < name.getLabelCount(); ++i) {
66
        isc::dns::Name superdomain(name.split(i));
67
        zone = accessor_->getZone(superdomain.toText());
68 69
        if (zone.first) {
            return (FindResult(result::PARTIALMATCH,
70
                               ZoneFinderPtr(new Finder(accessor_,
71 72
                                                        zone.second,
                                                        superdomain))));
73 74 75 76 77 78
        }
    }
    // No, really nothing
    return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}

79 80 81
DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor> accessor,
                               int zone_id, const isc::dns::Name& origin) :
    accessor_(accessor),
82 83
    zone_id_(zone_id),
    origin_(origin)
84 85
{ }

Jelte Jansen's avatar
Jelte Jansen committed
86
namespace {
87 88 89 90 91 92 93 94 95 96 97
// Adds the given Rdata to the given RRset
// If the rrset is an empty pointer, a new one is
// created with the given name, class, type and ttl
// The type is checked if the rrset exists, but the
// name is not.
//
// Then adds the given rdata to the set
//
// Raises a DataSourceError if the type does not
// match, or if the given rdata string does not
// parse correctly for the given type and class
98
//
99
// The DatabaseAccessor is passed to print the
100 101
// database name in the log message if the TTL is
// modified
102 103 104 105 106
void addOrCreate(isc::dns::RRsetPtr& rrset,
                    const isc::dns::Name& name,
                    const isc::dns::RRClass& cls,
                    const isc::dns::RRType& type,
                    const isc::dns::RRTTL& ttl,
107
                    const std::string& rdata_str,
108
                    const DatabaseAccessor& db
109
                )
110 111 112 113
{
    if (!rrset) {
        rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
    } else {
Jelte Jansen's avatar
Jelte Jansen committed
114 115
        // This is a check to make sure find() is not messing things up
        assert(type == rrset->getType());
116 117 118 119
        if (ttl != rrset->getTTL()) {
            if (ttl < rrset->getTTL()) {
                rrset->setTTL(ttl);
            }
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
120
            logger.warn(DATASRC_DATABASE_FIND_TTL_MISMATCH)
121
                .arg(db.getDBName()).arg(name).arg(cls)
122
                .arg(type).arg(rrset->getTTL());
123
        }
Jelte Jansen's avatar
Jelte Jansen committed
124
    }
125 126 127 128 129 130 131 132 133 134 135
    try {
        rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
    } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
        // at this point, rrset may have been initialised for no reason,
        // and won't be used. But the caller would drop the shared_ptr
        // on such an error anyway, so we don't care.
        isc_throw(DataSourceError,
                    "bad rdata in database for " << name << " "
                    << type << ": " << ivrt.what());
    }
}
Jelte Jansen's avatar
Jelte Jansen committed
136

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
// This class keeps a short-lived store of RRSIG records encountered
// during a call to find(). If the backend happens to return signatures
// before the actual data, we might not know which signatures we will need
// So if they may be relevant, we store the in this class.
//
// (If this class seems useful in other places, we might want to move
// it to util. That would also provide an opportunity to add unit tests)
class RRsigStore {
public:
    // Adds the given signature Rdata to the store
    // The signature rdata MUST be of the RRSIG rdata type
    // (the caller must make sure of this).
    // NOTE: if we move this class to a public namespace,
    // we should add a type_covered argument, so as not
    // to have to do this cast here.
    void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
        const isc::dns::RRType& type_covered =
            static_cast<isc::dns::rdata::generic::RRSIG*>(
                sig_rdata.get())->typeCovered();
        sigs[type_covered].push_back(sig_rdata);
    }
Jelte Jansen's avatar
Jelte Jansen committed
158

159 160 161 162 163 164 165 166 167
    // If the store contains signatures for the type of the given
    // rrset, they are appended to it.
    void appendSignatures(isc::dns::RRsetPtr& rrset) const {
        std::map<isc::dns::RRType,
                 std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
            found = sigs.find(rrset->getType());
        if (found != sigs.end()) {
            BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
                rrset->addRRsig(sig);
Jelte Jansen's avatar
Jelte Jansen committed
168 169
            }
        }
170
    }
Jelte Jansen's avatar
Jelte Jansen committed
171

172 173 174
private:
    std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
};
Jelte Jansen's avatar
Jelte Jansen committed
175 176
}

177
DatabaseClient::Finder::FoundRRsets
178 179
DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
                                  bool check_ns, const string* construct_name)
180 181 182
{
    RRsigStore sig_store;
    bool records_found = false;
183
    std::map<RRType, RRsetPtr> result;
184

185 186
    // Request the context
    DatabaseAccessor::IteratorContextPtr
187
        context(accessor_->getRecords(name, zone_id_));
188
    // It must not return NULL, that's a bug of the implementation
189
    if (!context) {
190
        isc_throw(isc::Unexpected, "Iterator context null at " + name);
191 192
    }

193
    std::string columns[DatabaseAccessor::COLUMN_COUNT];
194 195 196
    if (construct_name == NULL) {
        construct_name = &name;
    }
197

198 199
    const Name construct_name_object(*construct_name);

200
    bool seen_cname(false);
201
    bool seen_ds(false);
202 203 204
    bool seen_other(false);
    bool seen_ns(false);

205
    while (context->getNext(columns)) {
206 207
        // The domain is not empty
        records_found = true;
208 209

        try {
210 211 212
            const RRType cur_type(columns[DatabaseAccessor::TYPE_COLUMN]);

            if (cur_type == RRType::RRSIG()) {
213 214 215 216 217 218 219
                // If we get signatures before we get the actual data, we
                // can't know which ones to keep and which to drop...
                // So we keep a separate store of any signature that may be
                // relevant and add them to the final RRset when we are
                // done.
                // A possible optimization here is to not store them for
                // types we are certain we don't need
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
                sig_store.addSig(rdata::createRdata(cur_type, getClass(),
                     columns[DatabaseAccessor::RDATA_COLUMN]));
            }

            if (types.find(cur_type) != types.end()) {
                // This type is requested, so put it into result
                const RRTTL cur_ttl(columns[DatabaseAccessor::TTL_COLUMN]);
                // Ths sigtype column was an optimization for finding the
                // relevant RRSIG RRs for a lookup. Currently this column is
                // not used in this revised datasource implementation. We
                // should either start using it again, or remove it from use
                // completely (i.e. also remove it from the schema and the
                // backend implementation).
                // Note that because we don't use it now, we also won't notice
                // it if the value is wrong (i.e. if the sigtype column
                // contains an rrtype that is different from the actual value
                // of the 'type covered' field in the RRSIG Rdata).
                //cur_sigtype(columns[SIGTYPE_COLUMN]);
238 239
                addOrCreate(result[cur_type], construct_name_object,
                            getClass(), cur_type, cur_ttl,
240 241 242 243 244 245 246 247
                            columns[DatabaseAccessor::RDATA_COLUMN],
                            *accessor_);
            }

            if (cur_type == RRType::CNAME()) {
                seen_cname = true;
            } else if (cur_type == RRType::NS()) {
                seen_ns = true;
248 249 250 251 252 253 254 255
            } else if (cur_type == RRType::DS()) {
                seen_ds = true;
            } else if (cur_type != RRType::RRSIG() &&
                       cur_type != RRType::NSEC3() &&
                       cur_type != RRType::NSEC()) {
                // NSEC and RRSIG can coexist with anything, otherwise
                // we've seen something that can't live together with potential
                // CNAME or NS
256 257 258
                //
                // NSEC3 lives in separate namespace from everything, therefore
                // we just ignore it here for these checks as well.
259
                seen_other = true;
260
            }
261
        } catch (const InvalidRRType&) {
262 263 264
            isc_throw(DataSourceError, "Invalid RRType in database for " <<
                      name << ": " << columns[DatabaseAccessor::
                      TYPE_COLUMN]);
265
        } catch (const InvalidRRTTL&) {
266 267 268
            isc_throw(DataSourceError, "Invalid TTL in database for " <<
                      name << ": " << columns[DatabaseAccessor::
                      TTL_COLUMN]);
269
        } catch (const rdata::InvalidRdataText&) {
270 271 272 273 274
            isc_throw(DataSourceError, "Invalid rdata in database for " <<
                      name << ": " << columns[DatabaseAccessor::
                      RDATA_COLUMN]);
        }
    }
275
    if (seen_cname && (seen_other || seen_ns || seen_ds)) {
276 277 278 279 280 281
        isc_throw(DataSourceError, "CNAME shares domain " << name <<
                  " with something else");
    }
    if (check_ns && seen_ns && seen_other) {
        isc_throw(DataSourceError, "NS shares domain " << name <<
                  " with something else");
282
    }
283 284 285 286 287 288 289
    // Add signatures to all found RRsets
    for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
         i != result.end(); ++ i) {
        sig_store.appendSignatures(i->second);
    }

    return (FoundRRsets(records_found, result));
290 291
}

292 293
bool
DatabaseClient::Finder::hasSubdomains(const std::string& name) {
294 295
    // Request the context
    DatabaseAccessor::IteratorContextPtr
296
        context(accessor_->getRecords(name, zone_id_, true));
297 298 299
    // It must not return NULL, that's a bug of the implementation
    if (!context) {
        isc_throw(isc::Unexpected, "Iterator context null at " + name);
300
    }
301 302 303

    std::string columns[DatabaseAccessor::COLUMN_COUNT];
    return (context->getNext(columns));
304
}
Jelte Jansen's avatar
Jelte Jansen committed
305

306 307 308
// Some manipulation with RRType sets
namespace {

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
// Bunch of functions to construct specific sets of RRTypes we will
// ask from it.
typedef std::set<RRType> WantedTypes;

const WantedTypes&
NSEC_TYPES() {
    static bool initialized(false);
    static WantedTypes result;

    if (!initialized) {
        result.insert(RRType::NSEC());
        initialized = true;
    }
    return (result);
}

const WantedTypes&
DELEGATION_TYPES() {
    static bool initialized(false);
    static WantedTypes result;

    if (!initialized) {
        result.insert(RRType::DNAME());
        result.insert(RRType::NS());
        initialized = true;
    }
    return (result);
}

const WantedTypes&
FINAL_TYPES() {
    static bool initialized(false);
    static WantedTypes result;

    if (!initialized) {
        result.insert(RRType::CNAME());
        result.insert(RRType::NS());
        result.insert(RRType::NSEC());
        initialized = true;
    }
    return (result);
350 351 352 353
}

}

354
ConstRRsetPtr
355 356 357 358 359
DatabaseClient::Finder::findNSECCover(const Name& name) {
    try {
        // Which one should contain the NSEC record?
        const Name coverName(findPreviousName(name));
        // Get the record and copy it out
JINMEI Tatuya's avatar
JINMEI Tatuya committed
360 361
        const FoundRRsets found = getRRsets(coverName.toText(), NSEC_TYPES(),
                                            coverName != getOrigin());
362 363 364 365 366 367 368
        const FoundIterator
            nci(found.second.find(RRType::NSEC()));
        if (nci != found.second.end()) {
            return (nci->second);
        } else {
            // The previous doesn't contain NSEC.
            // Badly signed zone or a bug?
369 370 371 372 373 374

            // FIXME: Currently, if the zone is not signed, we could get
            // here. In that case we can't really throw, but for now, we can't
            // recognize it. So we don't throw at all, enable it once
            // we have a is_signed flag or something.
#if 0
375 376 377 378
            isc_throw(DataSourceError, "No NSEC in " +
                      coverName.toText() + ", but it was "
                      "returned as previous - "
                      "accessor error? Badly signed zone?");
379
#endif
380 381 382 383 384 385 386 387 388
        }
    }
    catch (const isc::NotImplemented&) {
        // Well, they want DNSSEC, but there is no available.
        // So we don't provide anything.
        LOG_INFO(logger, DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED).
            arg(accessor_->getDBName()).arg(name);
    }
    // We didn't find it, return nothing
389
    return (ConstRRsetPtr());
390 391
}

392 393
DatabaseClient::Finder::DelegationSearchResult
DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
394 395
                                            const FindOptions options)
{
396
    // Result of search
JINMEI Tatuya's avatar
JINMEI Tatuya committed
397
    isc::dns::ConstRRsetPtr result_rrset;
Jelte Jansen's avatar
Jelte Jansen committed
398
    ZoneFinder::Result result_status = SUCCESS;
399 400

    // Are we searching for glue?
401 402 403 404 405 406 407 408 409 410 411
    const bool glue_ok = ((options & FIND_GLUE_OK) != 0);

    // This next declaration is an optimisation.  When we search the database
    // for glue records, we generally ignore delegations. (This allows for
    // the case where e.g. the delegation to zone example.com refers to
    // nameservers within the zone, e.g. ns1.example.com.  When conducting the
    // search for ns1.example.com, we have to search past the NS records at
    // example.com.)
    //
    // The one case where this is forbidden is when we search past the zone
    // cut but the match we find for the glue is a wildcard match.  In that
412 413 414
    // case, we return the delegation instead (see RFC 1034, section 4.3.3).
    // To save a new search, we record the location of the delegation cut when
    // we encounter it here. 
415
    isc::dns::ConstRRsetPtr first_ns;
416

417 418
    // We want to search from the apex down.  We are given the full domain
    // name so we have to do some manipulation to ensure that when we start
419 420 421 422
    // checking superdomains, we start from the the domain name of the zone
    // (e.g. if the name is b.a.example.com. and we are in the example.com.
    // zone, we check example.com., a.example.com. and b.a.example.com.  We
    // don't need to check com. or .).
423 424 425 426 427
    //
    // Set the number of labels in the origin (i.e. apex of the zone) and in
    // the last known non-empty domain (which, at this point, is the origin).
    const size_t origin_label_count = getOrigin().getLabelCount();
    size_t last_known = origin_label_count;
428

429 430 431
    // Set how many labels we remove to get origin: this is the number of
    // labels we have to process in our search.
    const size_t remove_labels = name.getLabelCount() - origin_label_count;
Jelte Jansen's avatar
Jelte Jansen committed
432

433
    // Go through all superdomains from the origin down searching for nodes
434
    // that indicate a delegation (.e. NS or DNAME).
435
    for (int i = remove_labels; i > 0; --i) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
436
        const Name superdomain(name.split(i));
437

438 439 440
        // Note if this is the origin. (We don't count NS records at the origin
        // as a delegation so this controls whether NS RRs are included in
        // the results of some searches.)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
441
        const bool not_origin = (i != remove_labels);
442

443 444
        // Look if there's NS or DNAME at this point of the tree, but ignore
        // the NS RRs at the apex of the zone.
JINMEI Tatuya's avatar
JINMEI Tatuya committed
445 446
        const FoundRRsets found = getRRsets(superdomain.toText(),
                                            DELEGATION_TYPES(), not_origin);
447
        if (found.first) {
448
            // This node contains either NS or DNAME RRs so it does exist.
449 450
            const FoundIterator nsi(found.second.find(RRType::NS()));
            const FoundIterator dni(found.second.find(RRType::DNAME()));
451

452 453 454 455 456 457 458
            // An optimisation.  We know that there is an exact match for
            // something at this point in the tree so remember it.  If we have
            // to do a wildcard search, as we search upwards through the tree
            // we don't need to pass this point, which is an exact match for
            // the domain name.
            last_known = superdomain.getLabelCount();

459 460
            if (glue_ok && !first_ns && not_origin &&
                    nsi != found.second.end()) {
461 462 463 464 465
                // If we are searching for glue ("glue OK" mode), store the
                // highest NS record that we find that is not the apex.  This
                // is another optimisation for later, where we need the
                // information if the domain we are looking for matches through
                // a wildcard.
466
                first_ns = nsi->second;
467 468

            } else if (!glue_ok && not_origin && nsi != found.second.end()) {
469 470 471
                // Not searching for glue and we have found an NS RRset that is
                // not at the apex.  We have found a delegation - return that
                // fact, there is no need to search further down the tree.
472
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
473
                          DATASRC_DATABASE_FOUND_DELEGATION).
474
                    arg(accessor_->getDBName()).arg(superdomain);
475
                result_rrset = nsi->second;
476
                result_status = DELEGATION;
477
                break;
478

479
            } else if (dni != found.second.end()) {
480 481
                // We have found a DNAME so again stop searching down the tree
                // and return the information.
482
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
483
                          DATASRC_DATABASE_FOUND_DNAME).
484
                    arg(accessor_->getDBName()).arg(superdomain);
485
                result_rrset = dni->second;
486
                result_status = DNAME;
487 488 489 490 491 492
                if (result_rrset->getRdataCount() != 1) {
                    isc_throw(DataSourceError, "DNAME at " << superdomain <<
                              " has " << result_rrset->getRdataCount() <<
                              " rdata, 1 expected");
                }
                break;
493
            }
494 495
        }
    }
496 497 498 499
    return (DelegationSearchResult(result_status, result_rrset, first_ns,
                                   last_known));
}

500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
// This method is called when we have not found an exact match and when we
// know that the name is not an empty non-terminal.  So the only way that
// the name can match something in the zone is through a wildcard match.
//
// During an earlier stage in the search for this name, we made a record of
// the lowest superdomain for which we know an RR exists. (Note the "we
// know" qualification - there may be lower superdomains (ones with more
// labels) that hold an RR, but as we weren't searching for them, we don't
// know about them.)
//
// In the search for a wildcard match (which starts at the given domain
// name and goes up the tree to successive superdomains), this is the level
// at which we can stop - there can't be a wildcard at or beyond that
// point.
//
// At each level that can stop the search, we should consider several cases:
//
// - If we found a wildcard match for a glue record below a
// delegation point, we don't return the match,
// instead we return the delegation.  (Note that if we didn't
// a wildcard match at all, we would return NXDOMAIN, not the
// the delegation.)
//
// - If we found a wildcard match and we are sure that the match
// is not an empty non-terminal, return the result taking into account CNAME,
// on a zone cut, and NXRRSET.
// (E.g. searching for a match
// for c.b.a.example.com, we found that b.a.example.com did
// not exist but that *.a.example.com. did. Checking
// b.a.example.com revealed no subdomains, so we can use the
// wilcard match we found.)
//
// - If we found a more specified match, the wildcard search
// is canceled, resulting in NXDOMAIN.  (E.g. searching for a match
// for c.b.a.example.com, we found that b.a.example.com did
// not exist but that *.a.example.com. did. Checking
// b.a.example.com found subdomains.  So b.example.com is
// an empty non-terminal and so should not be returned in
// the wildcard matching process.  In other words,
// b.example.com does exist in the DNS space, it just doesn't
// have any RRs associated with it.)
//
// - If we found a match, but it is an empty non-terminal asterisk (E.g.#
// subdomain.*.example.com.  is present, but there is nothing at
// *.example.com.),  return an NXRRSET indication;
// the wildcard exists in the DNS space, there's just nothing
// associated with it.  If DNSSEC data is required, return the
// covering NSEC record.
//
// If none of the above applies in any level, the search fails with NXDOMAIN.
550
ZoneFinder::FindResult
JINMEI Tatuya's avatar
JINMEI Tatuya committed
551 552
DatabaseClient::Finder::findWildcardMatch(
    const isc::dns::Name& name, const isc::dns::RRType& type,
553
    const FindOptions options, const DelegationSearchResult& dresult)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
554
{
555 556 557
    // Note that during the search we are going to search not only for the
    // requested type, but also for types that indicate a delegation -
    // NS and DNAME.
558 559 560
    WantedTypes final_types(FINAL_TYPES());
    final_types.insert(type);

561
    for (size_t i = 1; i <= (name.getLabelCount() - dresult.last_known); ++i) {
562

563
        // Strip off the left-more label(s) in the name and replace with a "*".
564 565 566 567
        const Name superdomain(name.split(i));
        const string wildcard("*." + superdomain.toText());
        const string construct_name(name.toText());

568 569
        // TODO Add a check for DNAME, as DNAME wildcards are discouraged (see
        // RFC 4592 section 4.4).
570
        // Search for a match.  The types are the same as with original query.
571 572 573
        FoundRRsets found = getRRsets(wildcard, final_types, true,
                                      &construct_name);
        if (found.first) {
574 575
            // Found something - but what?

576
            if (dresult.first_ns) {
577 578 579 580 581 582 583 584
                // About to use first_ns.  The only way this can be set is if
                // we are searching for glue, so do a sanity check.
                if ((options & FIND_GLUE_OK) == 0) {
                    isc_throw(Unexpected, "Inconsistent conditions during "
                              "cancel of wilcard search for " <<
                              name.toText() << ": find_ns non-null when not "
                              "processing glue request");
                }
585

586
                // Wildcard match for a glue below a delegation point
587 588 589
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                          DATASRC_DATABASE_WILDCARD_CANCEL_NS).
                    arg(accessor_->getDBName()).arg(wildcard).
590
                    arg(dresult.first_ns->getName());
591
                return (ZoneFinder::FindResult(DELEGATION, dresult.first_ns));
592

593
            } else if (!hasSubdomains(name.split(i - 1).toText())) {
594 595 596 597
                // The wildcard match is the best one, find the final result
                // at it.  Note that wildcard should never be the zone origin.
                return (findOnNameResult(name, type, options, false,
                                         found, &wildcard));
598
            } else {
599 600

                // more specified match found, cancel wildcard match
601 602 603 604
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                          DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
                    arg(accessor_->getDBName()).arg(wildcard).
                    arg(name).arg(superdomain);
605
                return (ZoneFinder::FindResult(NXDOMAIN, ConstRRsetPtr()));
606
            }
607

608
        } else if (hasSubdomains(wildcard)) {
609
            // an empty non-terminal asterisk
610 611
            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                      DATASRC_DATABASE_WILDCARD_EMPTY).
612
                arg(accessor_->getDBName()).arg(wildcard).arg(name);
613 614 615 616
            if ((options & FIND_DNSSEC) != 0) {
                ConstRRsetPtr nsec = findNSECCover(Name(wildcard));
                if (nsec) {
                    return (ZoneFinder::FindResult(WILDCARD_NXRRSET, nsec));
617 618
                }
            }
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
            return (ZoneFinder::FindResult(NXRRSET, ConstRRsetPtr()));
        }
    }

    // Nothing found at any level.
    return (ZoneFinder::FindResult(NXDOMAIN, ConstRRsetPtr()));
}

ZoneFinder::FindResult
DatabaseClient::Finder::logAndCreateResult(
    const Name& name, const string* wildname, const RRType& type,
    ZoneFinder::Result code, ConstRRsetPtr rrset,
    const isc::log::MessageID& log_id) const
{
    if (rrset) {
        if (wildname == NULL) {
            LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
                arg(accessor_->getDBName()).arg(name).arg(type).
                arg(getClass()).arg(*rrset);
        } else {
            LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
                arg(accessor_->getDBName()).arg(name).arg(type).
                arg(getClass()).arg(*wildname).arg(*rrset);
        }
    } else {
        if (wildname == NULL) {
            LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
                arg(accessor_->getDBName()).arg(name).arg(type).
                arg(getClass());
        } else {
            LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
                arg(accessor_->getDBName()).arg(name).arg(type).
                arg(getClass()).arg(*wildname);
        }
    }
    return (ZoneFinder::FindResult(code, rrset));
}

ZoneFinder::FindResult
DatabaseClient::Finder::findOnNameResult(const Name& name,
                                         const RRType& type,
                                         const FindOptions options,
                                         const bool is_origin,
                                         const FoundRRsets& found,
                                         const string* wildname)
{
    const bool wild = (wildname != NULL);

    // Get iterators for the different types of records we are interested in -
    // CNAME, NS and Wanted types.
    const FoundIterator nsi(found.second.find(RRType::NS()));
    const FoundIterator cni(found.second.find(RRType::CNAME()));
    const FoundIterator wti(found.second.find(type));

    if (!is_origin && ((options & FIND_GLUE_OK) == 0) &&
        nsi != found.second.end()) {
        // A NS RRset was found at the domain we were searching for.  As it is
        // not at the origin of the zone, it is a delegation and indicates that
        // this zone is not authoritative for the data. Just return the
        // delegation information.
        return (logAndCreateResult(name, wildname, type, DELEGATION,
                                   nsi->second,
                                   wild ? DATASRC_DATABASE_WILDCARD_NS :
                                   DATASRC_DATABASE_FOUND_DELEGATION_EXACT));

    } else if (type != RRType::CNAME() && cni != found.second.end()) {
        // We are not searching for a CNAME but nevertheless we have found one
        // at the name we are searching so we return it. (The caller may
        // want to continue the lookup by replacing the query name with the
        // canonical name and the original RR type.) First though, do a sanity
        // check to ensure that there is only one RR in the CNAME RRset.
        if (cni->second->getRdataCount() != 1) {
            isc_throw(DataSourceError, "CNAME with " <<
                      cni->second->getRdataCount() << " rdata at " << name <<
                      ", expected 1");
        }
        return (logAndCreateResult(name, wildname, type,
                                   wild ? WILDCARD_CNAME : CNAME, cni->second,
                                   wild ? DATASRC_DATABASE_WILDCARD_CNAME :
                                   DATASRC_DATABASE_FOUND_CNAME));

    } else if (wti != found.second.end()) {
        // Found an RR matching the query, so return it.  (Note that this
        // includes the case where we were explicitly querying for a CNAME and
        // found it.  It also includes the case where we were querying for an
        // NS RRset and found it at the apex of the zone.)
        return (logAndCreateResult(name, wildname, type,
                                   wild ? WILDCARD : SUCCESS, wti->second,
                                   wild ? DATASRC_DATABASE_WILDCARD_MATCH :
                                   DATASRC_DATABASE_FOUND_RRSET));
    }

    // If we get here, we have found something at the requested name but not
    // one of the RR types we were interested in. This is the NXRRSET case so
    // return the appropriate status.  If DNSSEC information was requested,
    // provide the NSEC records.  If it's for wildcard, we need to get the
    // NSEC records in the name of the wildcard, not the substituted one,
    // so we need to search the tree again.
    ConstRRsetPtr nsec_rrset;   // possibly used with DNSSEC, otherwise NULL
    if ((options & FIND_DNSSEC) != 0) {
        if (wild) {
            const FoundRRsets wfound = getRRsets(*wildname, NSEC_TYPES(),
                                                 true);
            const FoundIterator nci = wfound.second.find(RRType::NSEC());
            if (nci != wfound.second.end()) {
                nsec_rrset = nci->second;
            }
        } else {
            const FoundIterator nci = found.second.find(RRType::NSEC());
            if (nci != found.second.end()) {
                nsec_rrset = nci->second;
            }
731 732
        }
    }
733 734 735 736 737 738 739 740 741 742 743 744
    if (nsec_rrset) {
        // This log message covers both normal and wildcard cases, so we pass
        // NULL for 'wildname'.
        return (logAndCreateResult(name, NULL, type,
                                   wild ? WILDCARD_NXRRSET : NXRRSET,
                                   nsec_rrset,
                                   DATASRC_DATABASE_FOUND_NXRRSET_NSEC));
    }
    return (logAndCreateResult(name, wildname, type,
                               wild ? WILDCARD_NXRRSET : NXRRSET, nsec_rrset,
                               wild ? DATASRC_DATABASE_WILDCARD_NXRRSET :
                               DATASRC_DATABASE_FOUND_NXRRSET));
745 746
}

747 748 749 750 751
ZoneFinder::FindResult
DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
                                         FindOptions options,
                                         const DelegationSearchResult& dresult)
{
752 753 754 755 756
    const bool dnssec_data = ((options & FIND_DNSSEC) != 0);

    // On entry to this method, we know that the database doesn't have any
    // entry for this name.  Before returning NXDOMAIN, we need to check
    // for special cases.
757 758

    if (hasSubdomains(name.toText())) {
759
        // Does the domain have a subdomain (i.e. it is an empty non-terminal)?
760
        // If so, return NXRRSET instead of NXDOMAIN (as although the name does
761
        // not exist in the database, it does exist in the DNS tree).
762 763 764 765 766
        LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                  DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
            arg(accessor_->getDBName()).arg(name);
        return (FindResult(NXRRSET, dnssec_data ? findNSECCover(name) :
                           ConstRRsetPtr()));
767 768

    } else if ((options & NO_WILDCARD) == 0) {
769
        // It's not an empty non-terminal and wildcard matching is not
770 771 772
        // disabled, so check for wildcards. If there is a wildcard match
        // (i.e. all results except NXDOMAIN) return it; otherwise fall
        // through to the NXDOMAIN case below.
773 774
        const ZoneFinder::FindResult wresult =
            findWildcardMatch(name, type, options, dresult);
775 776
        if (wresult.code != NXDOMAIN) {
            return (FindResult(wresult.code, wresult.rrset));
777 778
        }
    }
779 780 781

    // All avenues to find a match are now exhausted, return NXDOMAIN (plus
    // NSEC records if requested).
782 783
    LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_NO_MATCH).
              arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
784 785
    return (FindResult(NXDOMAIN, dnssec_data ? findNSECCover(name) :
                           ConstRRsetPtr()));
786 787
}

788 789 790 791
ZoneFinder::FindResult
DatabaseClient::Finder::find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
                             isc::dns::RRsetList*,
792 793
                             const FindOptions options)
{
794
    LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
795
              .arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
796

797 798 799 800 801
    // First, go through all superdomains from the origin down, searching for
    // nodes that indicate a delegation (i.e. NS or DNAME, ignoring NS records
    // at the apex).  If one is found, the search stops there.
    //
    // (In fact there could be RRs in the database corresponding to subdomains
802 803 804 805 806 807
    // of the delegation.  The reason we do the search for the delegations
    // first is because the delegation means that another zone is authoritative
    // for the data and so should be consulted to retrieve it.  RRs below
    // this delegation point can be found in a search for glue but not
    // otherwise; in the latter case they are said to be occluded by the
    // presence of the delegation.)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
808
    const DelegationSearchResult dresult = findDelegationPoint(name, options);
809 810 811
    if (dresult.rrset) {
        return (FindResult(dresult.code, dresult.rrset));
    }
812

813 814
    // If there is no delegation, look for the exact match to the request
    // name/type/class.  However, there are special cases:
815 816
    // - Requested name has a singleton CNAME record associated with it
    // - Requested name is a delegation point (NS only but not at the zone
817 818
    //   apex - DNAME is ignored here as it redirects DNS names subordinate to
    //   the owner name - the owner name itself is not redirected.)
819
    const bool is_origin = (name == getOrigin());
820 821
    WantedTypes final_types(FINAL_TYPES());
    final_types.insert(type);
822 823
    const FoundRRsets found = getRRsets(name.toText(), final_types,
                                        !is_origin);
824

825 826 827 828 829
    if (found.first) {
        // Something found at the domain name.  Look into it further to get
        // the final result.
        return (findOnNameResult(name, type, options, is_origin, found, NULL));
    } else {
830 831
        // Did not find anything at all at the domain name, so check for
        // subdomains or wildcards.
832
        return (findNoNameResult(name, type, options, dresult));
833
    }
834 835
}

836
Name
837
DatabaseClient::Finder::findPreviousName(const Name& name) const {
838 839 840 841 842
    const string str(accessor_->findPreviousName(zone_id_,
                                                 name.reverse().toText()));
    try {
        return (Name(str));
    }
843 844 845

    // To avoid having the same code many times, we just catch all the
    // exceptions and handle them in a common code below
846 847 848 849 850 851 852
    catch (const isc::dns::EmptyLabel&) {}
    catch (const isc::dns::TooLongLabel&) {}
    catch (const isc::dns::BadLabelType&) {}
    catch (const isc::dns::BadEscape&) {}
    catch (const isc::dns::TooLongName&) {}
    catch (const isc::dns::IncompleteName&) {}
    isc_throw(DataSourceError, "Bad name " + str + " from findPreviousName");
853 854
}

855 856
Name
DatabaseClient::Finder::getOrigin() const {
857
    return (origin_);
858 859 860 861 862 863 864 865
}

isc::dns::RRClass
DatabaseClient::Finder::getClass() const {
    // TODO Implement
    return isc::dns::RRClass::IN();
}

866 867
namespace {

868 869 870 871 872 873
/// This needs, beside of converting all data from textual representation, group
/// together rdata of the same RRsets. To do this, we hold one row of data ahead
/// of iteration. When we get a request to provide data, we create it from this
/// data and load a new one. If it is to be put to the same rrset, we add it.
/// Otherwise we just return what we have and keep the row as the one ahead
/// for next time.
874
class DatabaseIterator : public ZoneIterator {
875
public:
876
    DatabaseIterator(boost::shared_ptr<DatabaseAccessor> accessor,
877
                     const Name& zone_name,
878
                     const RRClass& rrclass,
879
                     bool separate_rrs) :
880
        accessor_(accessor),
881
        class_(rrclass),
882
        ready_(true),
883
        separate_rrs_(separate_rrs)
884
    {
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
        // Get the zone
        const pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
        if (!zone.first) {
            // No such zone, can't continue
            isc_throw(DataSourceError, "Zone " + zone_name.toText() +
                      " can not be iterated, because it doesn't exist "
                      "in this data source");
        }

        // Start a separate transaction.
        accessor_->startTransaction();

        // Find the SOA of the zone (may or may not succeed).  Note that
        // this must be done before starting the iteration context.
        soa_ = DatabaseClient::Finder(accessor_, zone.second, zone_name).
            find(zone_name, RRType::SOA(), NULL).rrset;

        // Request the context
        context_ = accessor_->getAllRecords(zone.second);
        // It must not return NULL, that's a bug of the implementation
        if (!context_) {
            isc_throw(isc::Unexpected, "Iterator context null at " +
                      zone_name.toText());
        }

910 911 912
        // Prepare data for the next time
        getData();
    }
Jelte Jansen's avatar
Jelte Jansen committed
913

914 915 916 917 918 919
    virtual ~DatabaseIterator() {
        if (ready_) {
            accessor_->commit();
        }
    }

920 921 922 923
    virtual ConstRRsetPtr getSOA() const {
        return (soa_);
    }

924 925 926 927 928 929
    virtual isc::dns::ConstRRsetPtr getNextRRset() {
        if (!ready_) {
            isc_throw(isc::Unexpected, "Iterating past the zone end");
        }
        if (!data_ready_) {
            // At the end of zone
930
            accessor_->commit();
931
            ready_ = false;
932 933
            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                      DATASRC_DATABASE_ITERATE_END);
934 935
            return (ConstRRsetPtr());
        }
936 937 938
        const string name_str(name_), rtype_str(rtype_), ttl(ttl_);
        const Name name(name_str);
        const RRType rtype(rtype_str);
939
        RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
Jelte Jansen's avatar
Jelte Jansen committed
940
        while (data_ready_ && name_ == name_str && rtype_str == rtype_) {
941 942 943 944
            if (ttl_ != ttl) {
                if (ttl < ttl_) {
                    ttl_ = ttl;
                    rrset->setTTL(RRTTL(ttl));
945
                }
946 947
                LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
                    arg(name_).arg(class_).arg(rtype_).arg(rrset->getTTL());
948 949 950
            }
            rrset->addRdata(rdata::createRdata(rtype, class_, rdata_));
            getData();
951 952 953
            if (separate_rrs_) {
                break;
            }
954
        }
955 956
        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_NEXT).
            arg(rrset->getName()).arg(rrset->getType());
957 958
        return (rrset);
    }
959

960 961 962
private:
    // Load next row of data
    void getData() {
963
        string data[DatabaseAccessor::COLUMN_COUNT];
964
        data_ready_ = context_->getNext(data);
965 966 967 968
        name_ = data[DatabaseAccessor::NAME_COLUMN];
        rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
        ttl_ = data[DatabaseAccessor::TTL_COLUMN];
        rdata_ = data[DatabaseAccessor::RDATA_COLUMN];
969
    }
Jelte Jansen's avatar
Jelte Jansen committed
970

971
    // The dedicated accessor
972
    boost::shared_ptr<DatabaseAccessor> accessor_;
973
    // The context
974
    DatabaseAccessor::IteratorContextPtr context_;
975
    // Class of the zone
976
    const RRClass class_;
977 978
    // SOA of the zone, if any (it should normally exist)
    ConstRRsetPtr soa_;
979 980 981
    // Status
    bool ready_, data_ready_;
    // Data of the next row
982
    string name_, rtype_, rdata_, ttl_;
983 984
    // Whether to modify differing TTL values, or treat a different TTL as
    // a different RRset
985
    bool separate_rrs_;
986 987 988 989 990
};

}

ZoneIteratorPtr
991
DatabaseClient::getIterator(const isc::dns::Name& name,
992
                            bool separate_rrs) const
993
{
994 995
    ZoneIteratorPtr iterator = ZoneIteratorPtr(new DatabaseIterator(
                                                   accessor_->clone(), name,
996
                                                   rrclass_, separate_rrs));
997 998
    LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE).
        arg(name);
999 1000

    return (iterator);
1001 1002
}

1003 1004 1005 1006 1007
//
// Zone updater using some database system as the underlying data source.
//
class DatabaseUpdater : public ZoneUpdater {
public:
1008
    DatabaseUpdater(boost::shared_ptr<DatabaseAccessor> accessor, int zone_id,
1009 1010
            const Name& zone_name, const RRClass& zone_class,
            bool journaling) :
1011 1012
        committed_(false), accessor_(accessor), zone_id_(zone_id),
        db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
1013
        zone_class_(zone_class), journaling_(journaling),
1014
        diff_phase_(NOT_STARTED), serial_(0),
1015
        finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
1016 1017 1018
    {
        logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
            .arg(zone_name_).arg(zone_class_).arg(db_name_);
1019 1020
    }

1021 1022
    virtual ~DatabaseUpdater() {
        if (!committed_) {
1023
            try {
1024
                accessor_->rollback();
1025 1026 1027 1028 1029
                logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
                    .arg(zone_name_).arg(zone_class_).arg(db_name_);
            } catch (const DataSourceError& e) {
                // We generally expect that rollback always succeeds, and
                // it should in fact succeed in a way we execute it.  But
1030
                // as the public API allows rollback() to fail and
1031 1032 1033 1034 1035 1036
                // throw, we should expect it.  Obviously we cannot re-throw
                // it.  The best we can do is to log it as a critical error.
                logger.error(DATASRC_DATABASE_UPDATER_ROLLBACKFAIL)
                    .arg(zone_name_).arg(zone_class_).arg(db_name_)
                    .arg(e.what());
            }
1037
        }
1038

1039
        logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
1040
            .arg(zone_name_).arg(zone_class_).arg(db_name_);
1041 1042
    }

1043
    virtual ZoneFinder& getFinder() { return (*finder_); }
1044

1045 1046 1047 1048 1049
    virtual void addRRset(const RRset& rrset);
    virtual void deleteRRset(const RRset& rrset);
    virtual void commit();

private:
1050 1051 1052
    // A short cut typedef only for making the code shorter.
    typedef DatabaseAccessor Accessor;

1053
    bool committed_;
1054
    boost::shared_ptr<DatabaseAccessor> accessor_;
1055 1056 1057 1058
    const int zone_id_;
    const string db_name_;
    const string zone_name_;
    const RRClass zone_class_;
1059
    const bool journaling_;
1060 1061 1062 1063 1064 1065 1066
    // For the journals
    enum DiffPhase {
        NOT_STARTED,
        DELETE,
        ADD
    };
    DiffPhase diff_phase_;