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

#include <d2/d2_log.h>
#include <d2/d2_cfg_mgr.h>
#include <dhcpsrv/dhcp_parsers.h>
#include <exceptions/exceptions.h>
#include <asiolink/io_error.h>

#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
23
#include <boost/algorithm/string/predicate.hpp>
24

25 26
#include <string>

27 28 29
namespace isc {
namespace d2 {

30 31 32 33 34 35 36 37 38
// *********************** TSIGKeyInfo  *************************

TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
                         const std::string& secret)
    :name_(name), algorithm_(algorithm), secret_(secret) {
}

TSIGKeyInfo::~TSIGKeyInfo() {
}
39 40


41
// *********************** DnsServerInfo  *************************
42

43 44 45
const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";

DnsServerInfo::DnsServerInfo(const std::string& hostname,
46 47
                             isc::asiolink::IOAddress ip_address, uint32_t port,
                             bool enabled)
48
    :hostname_(hostname), ip_address_(ip_address), port_(port),
49 50 51 52 53 54
    enabled_(enabled) {
}

DnsServerInfo::~DnsServerInfo() {
}

55 56 57 58 59 60 61 62 63 64 65 66 67 68
std::string
DnsServerInfo::toText() const {
    std::ostringstream stream;
    stream << (getIpAddress().toText()) << " port:" << getPort();
    return (stream.str());
}


std::ostream&
operator<<(std::ostream& os, const DnsServerInfo& server) {
    os << server.toText();
    return (os);
}

69 70 71 72 73 74 75 76 77 78 79 80
// *********************** DdnsDomain  *************************

DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
                       DnsServerInfoStoragePtr servers)
    : name_(name), key_name_(key_name), servers_(servers) {
}

DdnsDomain::~DdnsDomain() {
}

// *********************** DdnsDomainLstMgr  *************************

81 82
const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";

83
DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
84
    domains_(new DdnsDomainMap()) {
85 86 87 88 89 90
}


DdnsDomainListMgr::~DdnsDomainListMgr () {
}

91 92
void
DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
93
    if (!domains) {
94
        isc_throw(D2CfgError,
95 96 97 98 99
                  "DdnsDomainListMgr::setDomains: Domain list may not be null");
    }

    domains_ = domains;

100 101 102 103 104 105
    // Look for the wild card domain. If present, set the member variable
    // to remember it.  This saves us from having to look for it every time
    // we attempt a match.
    DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
    if (gotit != domains_->end()) {
            wildcard_domain_ = gotit->second;
106 107 108 109 110 111 112 113 114 115 116
    }
}

bool
DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
    // First check the case of one domain to rule them all.
    if ((size() == 1) && (wildcard_domain_)) {
        domain = wildcard_domain_;
        return (true);
    }

117 118 119 120 121 122
    // Iterate over the domain map looking for the domain which matches
    // the longest portion of the given fqdn.

    size_t req_len = fqdn.size();
    size_t match_len = 0;
    DdnsDomainMapPair map_pair;
123
    DdnsDomainPtr best_match;
124 125 126 127 128 129 130
    BOOST_FOREACH (map_pair, *domains_) {
        std::string domain_name = map_pair.first;
        size_t dom_len = domain_name.size();

        // If the domain name is longer than the fqdn, then it cant be match.
        if (req_len < dom_len) {
            continue;
131 132
        }

133 134
        // If the lengths are identical and the names match we're done.
        if (req_len == dom_len) {
135
            if (boost::iequals(fqdn, domain_name)) {
136 137 138 139 140 141 142 143 144 145
                // exact match, done
                domain = map_pair.second;
                return (true);
            }
        } else {
            // The fqdn is longer than the domain name.  Adjust the start
            // point of comparison by the excess in length.  Only do the
            // comparison if the adjustment lands on a boundary. This
            // prevents "onetwo.net" from matching "two.net".
            size_t offset = req_len - dom_len;
146
            if ((fqdn[offset - 1] == '.')  &&
147
               (boost::iequals(fqdn.substr(offset), domain_name))) {
148 149 150 151
                // Fqdn contains domain name, keep it if its better than
                // any we have matched so far.
                if (dom_len > match_len) {
                    match_len = dom_len;
152
                    best_match = map_pair.second;
153 154
                }
            }
155 156 157
        }
    }

158
    if (!best_match) {
159 160 161 162 163 164 165 166 167
        // There's no match. If they specified a wild card domain use it
        // otherwise there's no domain for this entry.
        if (wildcard_domain_) {
            domain = wildcard_domain_;
            return (true);
        }

        LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
        return (false);
168
    }
169

170
    domain = best_match;
171
    return (true);
172 173 174 175
}

// *************************** PARSERS ***********************************

176 177 178
// *********************** TSIGKeyInfoParser  *************************

TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
179
                                     TSIGKeyInfoMapPtr keys)
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
    : entry_name_(entry_name), keys_(keys), local_scalars_() {
    if (!keys_) {
        isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
                  " key storage cannot be null");
    }
}

TSIGKeyInfoParser::~TSIGKeyInfoParser() {
}

void
TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
    isc::dhcp::ConfigPair config_pair;
    // For each element in the key configuration:
    // 1. Create a parser for the element.
    // 2. Invoke the parser's build method passing in the element's
    // configuration.
    // 3. Invoke the parser's commit method to store the element's parsed
    // data to the parser's local storage.
    BOOST_FOREACH (config_pair, key_config->mapValue()) {
        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
        parser->build(config_pair.second);
        parser->commit();
    }

    std::string name;
    std::string algorithm;
    std::string secret;

    // Fetch the key configuration's parsed scalar values from parser's
    // local storage.
    local_scalars_.getParam("name", name);
    local_scalars_.getParam("algorithm", algorithm);
    local_scalars_.getParam("secret", secret);

215
    // @todo Validation here is very superficial. This will expand as TSIG
216 217 218 219 220 221 222
    // Key use is more fully implemented.

    // Name cannot be blank.
    if (name.empty()) {
        isc_throw(D2CfgError, "TSIG Key Info must specify name");
    }

223
    // Algorithm cannot be blank.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    if (algorithm.empty()) {
        isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
    }

    // Secret cannot be blank.
    if (secret.empty()) {
        isc_throw(D2CfgError, "TSIG Key Info must specify secret");
    }

    // Currently, the premise is that key storage is always empty prior to
    // parsing so we are always adding keys never replacing them. Duplicates
    // are not allowed and should be flagged as a configuration error.
    if (keys_->find(name) != keys_->end()) {
        isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
    }

    TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));

    // Add the new TSIGKeyInfo to the key storage.
    (*keys_)[name]=key_info;
}

246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
isc::dhcp::ParserPtr
TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
    DhcpConfigParser* parser = NULL;
    // Based on the configuration id of the element, create the appropriate
    // parser. Scalars are set to use the parser's local scalar storage.
    if ((config_id == "name")  ||
        (config_id == "algorithm") ||
        (config_id == "secret")) {
        parser = new isc::dhcp::StringParser(config_id,
                                             local_scalars_.getStringStorage());
    } else {
        isc_throw(NotImplemented,
                  "parser error: TSIGKeyInfo parameter not supported: "
                  << config_id);
    }

    // Return the new parser instance.
    return (isc::dhcp::ParserPtr(parser));
}

void
TSIGKeyInfoParser::commit() {
268 269 270
    /// @todo if at some point  TSIG keys need some form of runtime resource
    /// initialization, such as creating some sort of hash instance in
    /// crytpolib.  Once TSIG is fully implemented under Trac #3432 we'll know.
271 272
}

273 274 275 276
// *********************** TSIGKeyInfoListParser  *************************

TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
                                       TSIGKeyInfoMapPtr keys)
277
    :list_name_(list_name), keys_(keys), local_keys_(new TSIGKeyInfoMap()),
278
     parsers_() {
279 280 281 282 283 284
    if (!keys_) {
        isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
                  " key storage cannot be null");
    }
}

285
TSIGKeyInfoListParser::~TSIGKeyInfoListParser() {
286 287 288 289
}

void
TSIGKeyInfoListParser::
290
build(isc::data::ConstElementPtr key_list) {
291 292 293 294 295 296 297 298 299 300 301
    int i = 0;
    isc::data::ConstElementPtr key_config;
    // For each key element in the key list:
    // 1. Create a parser for the key element.
    // 2. Invoke the parser's build method passing in the key's
    // configuration.
    // 3. Add the parser to a local collection of parsers.
    BOOST_FOREACH(key_config, key_list->listValue()) {
        // Create a name for the parser based on its position in the list.
        std::string entry_name = boost::lexical_cast<std::string>(i++);
        isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
302
                                                            local_keys_));
303 304 305
        parser->build(key_config);
        parsers_.push_back(parser);
    }
306 307 308 309

    // Now that we know we have a valid list, commit that list to the
    // area given to us during construction (i.e. to the d2 context).
    *keys_ = *local_keys_;
310 311 312 313 314 315 316 317 318 319 320
}

void
TSIGKeyInfoListParser::commit() {
    // Invoke commit on each server parser. This will cause each one to
    // create it's server instance and commit it to storage.
    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
        parser->commit();
    }
}

321 322 323
// *********************** DnsServerInfoParser  *************************

DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
324
    DnsServerInfoStoragePtr servers)
325 326
    : entry_name_(entry_name), servers_(servers), local_scalars_() {
    if (!servers_) {
327
        isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
328 329 330 331 332 333 334
                  " server storage cannot be null");
    }
}

DnsServerInfoParser::~DnsServerInfoParser() {
}

335
void
336 337 338 339
DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
    isc::dhcp::ConfigPair config_pair;
    // For each element in the server configuration:
    // 1. Create a parser for the element.
340
    // 2. Invoke the parser's build method passing in the element's
341 342 343 344 345 346 347 348
    // configuration.
    // 3. Invoke the parser's commit method to store the element's parsed
    // data to the parser's local storage.
    BOOST_FOREACH (config_pair, server_config->mapValue()) {
        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
        parser->build(config_pair.second);
        parser->commit();
    }
349

350 351
    std::string hostname;
    std::string ip_address;
352
    uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
353

354 355 356 357 358 359
    // Fetch the server configuration's parsed scalar values from parser's
    // local storage.
    local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
    local_scalars_.getParam("ip_address", ip_address,
                            DCfgContextBase::OPTIONAL);
    local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
360 361

    // The configuration must specify one or the other.
362 363 364
    if (hostname.empty() == ip_address.empty()) {
        isc_throw(D2CfgError, "Dns Server must specify one or the other"
                  " of hostname and IP address");
365 366
    }

367 368
    DnsServerInfoPtr serverInfo;
    if (!hostname.empty()) {
369 370
        // When  hostname is specified, create a valid, blank IOAddress and
        // then create the DnsServerInfo.
371 372
        isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
        serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
373 374 375 376 377
    } else {
        try {
            // Create an IOAddress from the IP address string given and then
            // create the DnsServerInfo.
            isc::asiolink::IOAddress io_addr(ip_address);
378
            serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
379 380 381 382 383 384
        } catch (const isc::asiolink::IOError& ex) {
            isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
        }
    }

    // Add the new DnsServerInfo to the server storage.
385
    servers_->push_back(serverInfo);
386 387
}

388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
isc::dhcp::ParserPtr
DnsServerInfoParser::createConfigParser(const std::string& config_id) {
    DhcpConfigParser* parser = NULL;
    // Based on the configuration id of the element, create the appropriate
    // parser. Scalars are set to use the parser's local scalar storage.
    if ((config_id == "hostname")  ||
        (config_id == "ip_address")) {
        parser = new isc::dhcp::StringParser(config_id,
                                             local_scalars_.getStringStorage());
    } else if (config_id == "port") {
        parser = new isc::dhcp::Uint32Parser(config_id,
                                             local_scalars_.getUint32Storage());
    } else {
        isc_throw(NotImplemented,
                  "parser error: DnsServerInfo parameter not supported: "
                  << config_id);
    }

    // Return the new parser instance.
    return (isc::dhcp::ParserPtr(parser));
}

void
DnsServerInfoParser::commit() {
}

414 415 416 417 418 419 420 421 422 423 424 425 426 427
// *********************** DnsServerInfoListParser  *************************

DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
                                       DnsServerInfoStoragePtr servers)
    :list_name_(list_name), servers_(servers), parsers_() {
    if (!servers_) {
        isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
                  " server storage cannot be null");
    }
}

DnsServerInfoListParser::~DnsServerInfoListParser(){
}

428
void
429 430 431 432
DnsServerInfoListParser::
build(isc::data::ConstElementPtr server_list){
    int i = 0;
    isc::data::ConstElementPtr server_config;
433
    // For each server element in the server list:
434
    // 1. Create a parser for the server element.
435
    // 2. Invoke the parser's build method passing in the server's
436 437 438 439 440
    // configuration.
    // 3. Add the parser to a local collection of parsers.
    BOOST_FOREACH(server_config, server_list->listValue()) {
        // Create a name for the parser based on its position in the list.
        std::string entry_name = boost::lexical_cast<std::string>(i++);
441
        isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
442 443 444 445 446 447 448 449 450
                                                            servers_));
        parser->build(server_config);
        parsers_.push_back(parser);
    }

    // Domains must have at least one server.
    if (parsers_.size() == 0) {
        isc_throw (D2CfgError, "Server List must contain at least one server");
    }
451
}
452

453 454 455
void
DnsServerInfoListParser::commit() {
    // Invoke commit on each server parser.
456 457 458 459 460 461 462 463
    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
        parser->commit();
    }
}

// *********************** DdnsDomainParser  *************************

DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
464 465 466
                                   DdnsDomainMapPtr domains,
                                   TSIGKeyInfoMapPtr keys)
    : entry_name_(entry_name), domains_(domains), keys_(keys),
467 468
    local_servers_(new DnsServerInfoStorage()), local_scalars_() {
    if (!domains_) {
469
        isc_throw(D2CfgError,
470 471 472 473 474 475 476 477
                  "DdnsDomainParser ctor, domain storage cannot be null");
    }
}


DdnsDomainParser::~DdnsDomainParser() {
}

478
void
479 480 481
DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
    // For each element in the domain configuration:
    // 1. Create a parser for the element.
482
    // 2. Invoke the parser's build method passing in the element's
483 484 485 486 487 488 489 490 491 492
    // configuration.
    // 3. Invoke the parser's commit method to store the element's parsed
    // data to the parser's local storage.
    isc::dhcp::ConfigPair config_pair;
    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
        parser->build(config_pair.second);
        parser->commit();
    }

493
    // Now construct the domain.
494 495 496 497
    std::string name;
    std::string key_name;

    // Domain name is not optional. The get will throw if its not there.
498
    local_scalars_.getParam("name", name);
499 500

    // Blank domain names are not allowed.
501
    if (name.empty()) {
502 503 504 505 506 507 508 509
        isc_throw(D2CfgError, "Domain name cannot be blank");
    }

    // Currently, the premise is that domain storage is always empty
    // prior to parsing so always adding domains never replacing them.
    // Duplicates are not allowed and should be flagged as a configuration
    // error.
    if (domains_->find(name) != domains_->end()) {
510
        isc_throw(D2CfgError, "Duplicate domain specified:" << name);
511 512
    }

513 514 515 516 517 518 519 520 521
    // Key name is optional. If it is not blank, then validate it against
    // the defined list of keys.
    local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
    if (!key_name.empty()) {
        if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
            isc_throw(D2CfgError, "DdnsDomain :" << name <<
                     " specifies and undefined key:" << key_name);
        }
    }
522 523 524 525 526

    // Instantiate the new domain and add it to domain storage.
    DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));

    // Add the new domain to the domain storage.
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
    (*domains_)[name] = domain;
}

isc::dhcp::ParserPtr
DdnsDomainParser::createConfigParser(const std::string& config_id) {
    DhcpConfigParser* parser = NULL;
    // Based on the configuration id of the element, create the appropriate
    // parser. Scalars are set to use the parser's local scalar storage.
    if ((config_id == "name")  ||
        (config_id == "key_name")) {
        parser = new isc::dhcp::StringParser(config_id,
                                             local_scalars_.getStringStorage());
    } else if (config_id == "dns_servers") {
       // Server list parser is given in our local server storage. It will pass
       // this down to its server parsers and is where they will write their
       // server instances upon commit.
       parser = new DnsServerInfoListParser(config_id, local_servers_);
    } else {
       isc_throw(NotImplemented,
                "parser error: DdnsDomain parameter not supported: "
                << config_id);
    }

    // Return the new domain parser instance.
    return (isc::dhcp::ParserPtr(parser));
}

void
DdnsDomainParser::commit() {
556 557 558 559 560
}

// *********************** DdnsDomainListParser  *************************

DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
561 562 563
                                           DdnsDomainMapPtr domains,
                                           TSIGKeyInfoMapPtr keys)
    :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
564 565 566 567 568 569 570 571 572
    if (!domains_) {
        isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
                  " domain storage cannot be null");
    }
}

DdnsDomainListParser::~DdnsDomainListParser(){
}

573
void
574 575
DdnsDomainListParser::
build(isc::data::ConstElementPtr domain_list){
576
    // For each domain element in the domain list:
577
    // 1. Create a parser for the domain element.
578
    // 2. Invoke the parser's build method passing in the domain's
579 580 581 582 583 584
    // configuration.
    // 3. Add the parser to the local collection of parsers.
    int i = 0;
    isc::data::ConstElementPtr domain_config;
    BOOST_FOREACH(domain_config, domain_list->listValue()) {
        std::string entry_name = boost::lexical_cast<std::string>(i++);
585 586
        isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
                                                         domains_, keys_));
587 588 589 590 591
        parser->build(domain_config);
        parsers_.push_back(parser);
    }
}

592
void
593 594
DdnsDomainListParser::commit() {
    // Invoke commit on each server parser. This will cause each one to
595
    // create it's server instance and commit it to storage.
596 597 598 599 600 601 602 603 604
    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
        parser->commit();
    }
}


// *********************** DdnsDomainListMgrParser  *************************

DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
605 606 607
                              DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
    : entry_name_(entry_name), mgr_(mgr), keys_(keys),
    local_domains_(new DdnsDomainMap()), local_scalars_() {
608 609 610 611 612 613
}


DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
}

614
void
615 616 617
DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
    // For each element in the domain manager configuration:
    // 1. Create a parser for the element.
618
    // 2. Invoke the parser's build method passing in the element's
619 620 621 622 623 624 625 626 627
    // configuration.
    // 3. Invoke the parser's commit method to store the element's parsed
    // data to the parser's local storage.
    isc::dhcp::ConfigPair config_pair;
    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
        parser->build(config_pair.second);
        parser->commit();
    }
628 629 630

    // Add the new domain to the domain storage.
    mgr_->setDomains(local_domains_);
631 632
}

633
isc::dhcp::ParserPtr
634 635 636 637 638 639
DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
    DhcpConfigParser* parser = NULL;
    if (config_id == "ddns_domains") {
       // Domain list parser is given our local domain storage. It will pass
       // this down to its domain parsers and is where they will write their
       // domain instances upon commit.
640
       parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
641 642 643 644 645 646 647 648 649
    } else {
       isc_throw(NotImplemented, "parser error: "
                 "DdnsDomainListMgr parameter not supported: " << config_id);
    }

    // Return the new domain parser instance.
    return (isc::dhcp::ParserPtr(parser));
}

650
void
651 652 653 654 655 656
DdnsDomainListMgrParser::commit() {
}


}; // end of isc::dhcp namespace
}; // end of isc namespace