database.cc 57.5 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
DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
179 180
                                  bool check_ns, const string* construct_name,
                                  bool any)
181 182 183
{
    RRsigStore sig_store;
    bool records_found = false;
184
    std::map<RRType, RRsetPtr> result;
185

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

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

199 200
    const Name construct_name_object(*construct_name);

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

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

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

            if (cur_type == RRType::RRSIG()) {
214 215 216 217 218 219 220
                // 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
221 222 223 224
                sig_store.addSig(rdata::createRdata(cur_type, getClass(),
                     columns[DatabaseAccessor::RDATA_COLUMN]));
            }

225
            if (types.find(cur_type) != types.end() || any) {
226 227 228 229 230 231 232 233 234 235 236 237 238
                // 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]);
239 240
                addOrCreate(result[cur_type], construct_name_object,
                            getClass(), cur_type, cur_ttl,
241 242 243 244 245 246 247 248
                            columns[DatabaseAccessor::RDATA_COLUMN],
                            *accessor_);
            }

            if (cur_type == RRType::CNAME()) {
                seen_cname = true;
            } else if (cur_type == RRType::NS()) {
                seen_ns = true;
249 250 251 252 253 254 255 256
            } 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
257 258 259
                //
                // NSEC3 lives in separate namespace from everything, therefore
                // we just ignore it here for these checks as well.
260
                seen_other = true;
261
            }
262
        } catch (const InvalidRRType&) {
263 264 265
            isc_throw(DataSourceError, "Invalid RRType in database for " <<
                      name << ": " << columns[DatabaseAccessor::
                      TYPE_COLUMN]);
266
        } catch (const InvalidRRTTL&) {
267 268 269
            isc_throw(DataSourceError, "Invalid TTL in database for " <<
                      name << ": " << columns[DatabaseAccessor::
                      TTL_COLUMN]);
270
        } catch (const rdata::InvalidRdataText&) {
271 272 273 274 275
            isc_throw(DataSourceError, "Invalid rdata in database for " <<
                      name << ": " << columns[DatabaseAccessor::
                      RDATA_COLUMN]);
        }
    }
276
    if (seen_cname && (seen_other || seen_ns || seen_ds)) {
277 278 279 280 281 282
        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");
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);
    }

290 291 292 293 294 295
    if (records_found && any) {
        result[RRType::ANY()] = RRsetPtr();
        // These will be sitting on the other RRsets.
        result.erase(RRType::RRSIG());
    }

296
    return (FoundRRsets(records_found, result));
297 298
}

299 300
bool
DatabaseClient::Finder::hasSubdomains(const std::string& name) {
301 302
    // Request the context
    DatabaseAccessor::IteratorContextPtr
303
        context(accessor_->getRecords(name, zone_id_, true));
304 305 306
    // It must not return NULL, that's a bug of the implementation
    if (!context) {
        isc_throw(isc::Unexpected, "Iterator context null at " + name);
307
    }
308 309 310

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

313 314 315
// Some manipulation with RRType sets
namespace {

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 350 351 352 353 354 355 356
// 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);
357 358 359 360
}

}

361
ConstRRsetPtr
362 363 364 365 366
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
367 368
        const FoundRRsets found = getRRsets(coverName.toText(), NSEC_TYPES(),
                                            coverName != getOrigin());
369 370 371 372 373 374 375
        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?
376 377 378 379 380 381

            // 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
382 383 384 385
            isc_throw(DataSourceError, "No NSEC in " +
                      coverName.toText() + ", but it was "
                      "returned as previous - "
                      "accessor error? Badly signed zone?");
386
#endif
387 388 389 390 391 392 393 394 395
        }
    }
    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
396
    return (ConstRRsetPtr());
397 398
}

399
ZoneFinder::FindResult
400 401 402
DatabaseClient::Finder::findAll(const isc::dns::Name& name,
                                std::vector<isc::dns::ConstRRsetPtr>& target,
                                const FindOptions options)
403
{
404
    return (findInternal(name, RRType::ANY(), &target, options));
405 406 407
}

ZoneFinder::FindResult
408 409
DatabaseClient::Finder::find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
410
                             const FindOptions options)
411
{
412 413 414
    if (type == RRType::ANY()) {
        isc_throw(isc::Unexpected, "Use findAll to answer ANY");
    }
415 416 417
    return (findInternal(name, type, NULL, options));
}

418 419
DatabaseClient::Finder::DelegationSearchResult
DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
420 421
                                            const FindOptions options)
{
422
    // Result of search
JINMEI Tatuya's avatar
JINMEI Tatuya committed
423
    isc::dns::ConstRRsetPtr result_rrset;
Jelte Jansen's avatar
Jelte Jansen committed
424
    ZoneFinder::Result result_status = SUCCESS;
425 426

    // Are we searching for glue?
427 428 429 430 431 432 433 434 435 436 437
    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
438 439
    // 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
Jeremy C. Reed's avatar
Jeremy C. Reed committed
440
    // we encounter it here.
441
    isc::dns::ConstRRsetPtr first_ns;
442

443 444
    // 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
445 446 447 448
    // 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 .).
449 450 451 452 453
    //
    // 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;
454

455 456 457
    // 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
458

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

464 465 466
        // 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
467
        const bool not_origin = (i != remove_labels);
468

469 470
        // 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
471 472
        const FoundRRsets found = getRRsets(superdomain.toText(),
                                            DELEGATION_TYPES(), not_origin);
473
        if (found.first) {
474
            // This node contains either NS or DNAME RRs so it does exist.
475 476
            const FoundIterator nsi(found.second.find(RRType::NS()));
            const FoundIterator dni(found.second.find(RRType::DNAME()));
477

478 479 480 481 482 483 484
            // 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();

485 486
            if (glue_ok && !first_ns && not_origin &&
                    nsi != found.second.end()) {
487 488 489 490 491
                // 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.
492
                first_ns = nsi->second;
493 494

            } else if (!glue_ok && not_origin && nsi != found.second.end()) {
495 496 497
                // 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.
498
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
499
                          DATASRC_DATABASE_FOUND_DELEGATION).
500
                    arg(accessor_->getDBName()).arg(superdomain);
501
                result_rrset = nsi->second;
502
                result_status = DELEGATION;
503
                break;
504

505
            } else if (dni != found.second.end()) {
506 507
                // We have found a DNAME so again stop searching down the tree
                // and return the information.
508
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
509
                          DATASRC_DATABASE_FOUND_DNAME).
510
                    arg(accessor_->getDBName()).arg(superdomain);
511
                result_rrset = dni->second;
512
                result_status = DNAME;
513 514 515 516 517 518
                if (result_rrset->getRdataCount() != 1) {
                    isc_throw(DataSourceError, "DNAME at " << superdomain <<
                              " has " << result_rrset->getRdataCount() <<
                              " rdata, 1 expected");
                }
                break;
519
            }
520 521
        }
    }
522 523 524 525
    return (DelegationSearchResult(result_status, result_rrset, first_ns,
                                   last_known));
}

526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
// 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.
576
ZoneFinder::FindResult
JINMEI Tatuya's avatar
JINMEI Tatuya committed
577 578
DatabaseClient::Finder::findWildcardMatch(
    const isc::dns::Name& name, const isc::dns::RRType& type,
579 580
    const FindOptions options, const DelegationSearchResult& dresult,
    std::vector<isc::dns::ConstRRsetPtr>* target)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
581
{
582 583 584
    // 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.
585 586 587
    WantedTypes final_types(FINAL_TYPES());
    final_types.insert(type);

588 589
    const size_t remove_labels = name.getLabelCount() - dresult.last_known;
    for (size_t i = 1; i <= remove_labels; ++i) {
590

591
        // Strip off the left-more label(s) in the name and replace with a "*".
592 593 594 595
        const Name superdomain(name.split(i));
        const string wildcard("*." + superdomain.toText());
        const string construct_name(name.toText());

596 597
        // TODO Add a check for DNAME, as DNAME wildcards are discouraged (see
        // RFC 4592 section 4.4).
598
        // Search for a match.  The types are the same as with original query.
599
        FoundRRsets found = getRRsets(wildcard, final_types, true,
600
                                      &construct_name, type == RRType::ANY());
601
        if (found.first) {
602 603
            // Found something - but what?

604
            if (dresult.first_ns) {
605 606 607 608 609 610 611 612
                // 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");
                }
613

614
                // Wildcard match for a glue below a delegation point
615 616 617
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                          DATASRC_DATABASE_WILDCARD_CANCEL_NS).
                    arg(accessor_->getDBName()).arg(wildcard).
618
                    arg(dresult.first_ns->getName());
619
                return (FindResult(DELEGATION, dresult.first_ns));
620

621
            } else if (!hasSubdomains(name.split(i - 1).toText())) {
622 623 624
                // 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,
625
                                         found, &wildcard, target));
626
            } else {
627 628

                // more specified match found, cancel wildcard match
629 630 631 632
                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                          DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
                    arg(accessor_->getDBName()).arg(wildcard).
                    arg(name).arg(superdomain);
633
                return (FindResult(NXDOMAIN, ConstRRsetPtr()));
634
            }
635

636
        } else if (hasSubdomains(wildcard)) {
637
            // an empty non-terminal asterisk
638 639
            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                      DATASRC_DATABASE_WILDCARD_EMPTY).
640
                arg(accessor_->getDBName()).arg(wildcard).arg(name);
641 642 643
            if ((options & FIND_DNSSEC) != 0) {
                ConstRRsetPtr nsec = findNSECCover(Name(wildcard));
                if (nsec) {
644 645
                    return (FindResult(NXRRSET, nsec,
                                       RESULT_WILDCARD | RESULT_NSEC_SIGNED));
646 647
                }
            }
648
            return (FindResult(NXRRSET, ConstRRsetPtr(), RESULT_WILDCARD));
649 650 651 652
        }
    }

    // Nothing found at any level.
653
    return (FindResult(NXDOMAIN, ConstRRsetPtr()));
654 655 656 657 658 659
}

ZoneFinder::FindResult
DatabaseClient::Finder::logAndCreateResult(
    const Name& name, const string* wildname, const RRType& type,
    ZoneFinder::Result code, ConstRRsetPtr rrset,
660
    const isc::log::MessageID& log_id, FindResultFlags flags) const
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
{
    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);
        }
    }
683
    return (ZoneFinder::FindResult(code, rrset, flags));
684 685 686 687 688 689 690 691
}

ZoneFinder::FindResult
DatabaseClient::Finder::findOnNameResult(const Name& name,
                                         const RRType& type,
                                         const FindOptions options,
                                         const bool is_origin,
                                         const FoundRRsets& found,
692 693 694
                                         const string* wildname,
                                         std::vector<isc::dns::ConstRRsetPtr>*
                                         target)
695 696
{
    const bool wild = (wildname != NULL);
697
    FindResultFlags flags = wild ? RESULT_WILDCARD : RESULT_DEFAULT;
698 699 700 701 702 703

    // 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));
704 705 706
    // For wildcard case with DNSSEC required, the caller would need to know
    // whether it's NSEC or NSEC3 signed.  So we need to do an additional
    // search here, even though the NSEC RR may not be returned.
707 708 709
    // TODO: this part should be revised when we support NSEC3; ideally we
    // should use more effective and efficient way to identify (whether and)
    // in which way the zone is signed.
710 711 712 713
    if (wild && (options & FIND_DNSSEC) != 0 &&
        found.second.find(RRType::NSEC()) != found.second.end()) {
        flags = flags | RESULT_NSEC_SIGNED;
    }
714 715 716 717 718 719 720 721 722 723

    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 :
724 725
                                   DATASRC_DATABASE_FOUND_DELEGATION_EXACT,
                                   flags));
726 727 728 729 730 731 732 733 734 735 736 737

    } 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");
        }
738
        return (logAndCreateResult(name, wildname, type, CNAME, cni->second,
739
                                   wild ? DATASRC_DATABASE_WILDCARD_CNAME :
740 741
                                   DATASRC_DATABASE_FOUND_CNAME,
                                   flags));
742 743

    } else if (wti != found.second.end()) {
744 745 746 747
        bool any(type == RRType::ANY());
        isc::log::MessageID lid(wild ? DATASRC_DATABASE_WILDCARD_MATCH :
                                DATASRC_DATABASE_FOUND_RRSET);
        if (any) {
748 749 750 751 752 753 754
            // An ANY query, copy everything to the target instead of returning
            // directly.
            for (FoundIterator it(found.second.begin());
                 it != found.second.end(); ++it) {
                if (it->second) {
                    // Skip over the empty ANY
                    target->push_back(it->second);
755 756
                }
            }
757 758
            lid = wild ? DATASRC_DATABASE_WILDCARD_ANY :
                DATASRC_DATABASE_FOUND_ANY;
759
        }
760 761 762 763
        // 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.)
764 765
        return (logAndCreateResult(name, wildname, type, SUCCESS,
                                   wti->second, lid, flags));
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
    }

    // 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;
            }
788 789
        }
    }
790 791 792
    if (nsec_rrset) {
        // This log message covers both normal and wildcard cases, so we pass
        // NULL for 'wildname'.
793 794 795
        return (logAndCreateResult(name, NULL, type, NXRRSET, nsec_rrset,
                                   DATASRC_DATABASE_FOUND_NXRRSET_NSEC,
                                   flags | RESULT_NSEC_SIGNED));
796
    }
797
    return (logAndCreateResult(name, wildname, type, NXRRSET, nsec_rrset,
798
                               wild ? DATASRC_DATABASE_WILDCARD_NXRRSET :
799
                               DATASRC_DATABASE_FOUND_NXRRSET, flags));
800 801
}

802 803 804
ZoneFinder::FindResult
DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
                                         FindOptions options,
805 806 807
                                         const DelegationSearchResult& dresult,
                                         std::vector<isc::dns::ConstRRsetPtr>*
                                         target)
808
{
809 810 811 812 813
    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.
814 815

    if (hasSubdomains(name.toText())) {
816
        // Does the domain have a subdomain (i.e. it is an empty non-terminal)?
817
        // If so, return NXRRSET instead of NXDOMAIN (as although the name does
818
        // not exist in the database, it does exist in the DNS tree).
819 820 821
        LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                  DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
            arg(accessor_->getDBName()).arg(name);
822 823 824 825
        const ConstRRsetPtr nsec = dnssec_data ? findNSECCover(name) :
            ConstRRsetPtr();
        return (FindResult(NXRRSET, nsec,
                           nsec ? RESULT_NSEC_SIGNED : RESULT_DEFAULT));
826
    } else if ((options & NO_WILDCARD) == 0) {
827
        // It's not an empty non-terminal and wildcard matching is not
828 829 830
        // 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.
831
        const ZoneFinder::FindResult wresult =
832
            findWildcardMatch(name, type, options, dresult, target);
833
        if (wresult.code != NXDOMAIN) {
834
            return (wresult);
835 836
        }
    }
837 838 839

    // All avenues to find a match are now exhausted, return NXDOMAIN (plus
    // NSEC records if requested).
840 841
    LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_NO_MATCH).
              arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
842 843 844 845
    const ConstRRsetPtr nsec = dnssec_data ? findNSECCover(name) :
        ConstRRsetPtr();
    return (FindResult(NXDOMAIN, nsec,
                       nsec ? RESULT_NSEC_SIGNED : RESULT_DEFAULT));
846 847
}

848
ZoneFinder::FindResult
849 850 851
DatabaseClient::Finder::findInternal(const Name& name, const RRType& type,
                                     std::vector<ConstRRsetPtr>* target,
                                     const FindOptions options)
852
{
853
    LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
854
              .arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
855

856 857 858 859 860 861 862 863 864 865
    // find() variants generally expect 'name' to be included in the zone.
    // Otherwise the search algorithm below won't work correctly, so we
    // reject the unexpected case first.
    const NameComparisonResult::NameRelation reln =
        name.compare(getOrigin()).getRelation();
    if (reln != NameComparisonResult::SUBDOMAIN &&
        reln != NameComparisonResult::EQUAL) {
        return (FindResult(NXDOMAIN, ConstRRsetPtr()));
    }

866 867 868 869 870
    // 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
871 872 873 874 875 876
    // 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
877
    const DelegationSearchResult dresult = findDelegationPoint(name, options);
878
    if (dresult.rrset) {
879
        // In this case no special flags are needed.
880 881
        return (FindResult(dresult.code, dresult.rrset));
    }
882

883 884
    // If there is no delegation, look for the exact match to the request
    // name/type/class.  However, there are special cases:
885 886
    // - Requested name has a singleton CNAME record associated with it
    // - Requested name is a delegation point (NS only but not at the zone
887 888
    //   apex - DNAME is ignored here as it redirects DNS names subordinate to
    //   the owner name - the owner name itself is not redirected.)
889
    const bool is_origin = (name == getOrigin());
890 891
    WantedTypes final_types(FINAL_TYPES());
    final_types.insert(type);
892
    const FoundRRsets found = getRRsets(name.toText(), final_types,
893 894
                                        !is_origin, NULL,
                                        type == RRType::ANY());
895

896 897 898
    if (found.first) {
        // Something found at the domain name.  Look into it further to get
        // the final result.
899 900
        return (findOnNameResult(name, type, options, is_origin, found, NULL,
                                 target));
901
    } else {
902 903
        // Did not find anything at all at the domain name, so check for
        // subdomains or wildcards.
904
        return (findNoNameResult(name, type, options, dresult, target));
905
    }
906 907
}

908
ZoneFinder::FindNSEC3Result
909
DatabaseClient::Finder::findNSEC3(const Name&, bool) {
910 911 912 913
    isc_throw(NotImplemented, "findNSEC3 is not yet implemented for database "
              "data source");
}

914
Name
915
DatabaseClient::Finder::findPreviousName(const Name& name) const {
916 917 918 919 920
    const string str(accessor_->findPreviousName(zone_id_,
                                                 name.reverse().toText()));
    try {
        return (Name(str));
    }
921 922 923

    // To avoid having the same code many times, we just catch all the
    // exceptions and handle them in a common code below
924 925 926 927 928 929 930
    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");
931 932
}

933 934
Name
DatabaseClient::Finder::getOrigin() const {
935
    return (origin_);
936 937 938 939 940 941 942 943
}

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

944 945
namespace {

946 947 948 949 950 951
/// 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.
952
class DatabaseIterator : public ZoneIterator {
953
public:
954
    DatabaseIterator(boost::shared_ptr<DatabaseAccessor> accessor,
955
                     const Name& zone_name,
956
                     const RRClass& rrclass,
957
                     bool separate_rrs) :
958
        accessor_(accessor),
959
        class_(rrclass),
960
        ready_(true),
961
        separate_rrs_(separate_rrs)
962
    {
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977
        // 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).
978
            find(zone_name, RRType::SOA()).rrset;
979 980 981 982 983 984 985 986 987

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

988 989 990
        // Prepare data for the next time
        getData();
    }
Jelte Jansen's avatar
Jelte Jansen committed
991

992 993 994 995 996 997
    virtual ~DatabaseIterator() {
        if (ready_) {
            accessor_->commit();
        }
    }

998 999 1000 1001
    virtual ConstRRsetPtr getSOA() const {
        return (soa_);
    }

1002 1003 1004 1005 1006 1007
    virtual isc::dns::ConstRRsetPtr getNextRRset() {
        if (!ready_) {
            isc_throw(isc::Unexpected, "Iterating past the zone end");
        }
        if (!data_ready_) {
            // At the end of zone
1008
            accessor_->commit();
1009
            ready_ = false;
1010 1011
            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                      DATASRC_DATABASE_ITERATE_END);
1012 1013
            return (ConstRRsetPtr());
        }
1014 1015 1016
        const string name_str(name_), rtype_str(rtype_), ttl(ttl_);
        const Name name(name_str);
        const RRType rtype(rtype_str);
1017
        RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
Jelte Jansen's avatar
Jelte Jansen committed
1018
        while (data_ready_ && name_ == name_str && rtype_str == rtype_) {
1019 1020 1021 1022
            if (ttl_ != ttl) {
                if (ttl < ttl_) {
                    ttl_ = ttl;
                    rrset->setTTL(RRTTL(ttl));
1023
                }
1024 1025
                LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
                    arg(name_).arg(class_).arg<