dhcp_parsers.cc 56.6 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
//
// 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 <dhcp/iface_mgr.h>
16 17
#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
18
#include <dhcpsrv/dhcp_parsers.h>
19
#include <hooks/hooks_manager.h>
20 21 22
#include <util/encode/hex.h>
#include <util/strutil.h>

23
#include <boost/algorithm/string.hpp>
24 25 26 27
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>

#include <map>
28 29
#include <string>
#include <vector>
30 31

using namespace std;
32
using namespace isc::asiolink;
33
using namespace isc::data;
34
using namespace isc::hooks;
35 36 37 38

namespace isc {
namespace dhcp {

39 40 41
// *********************** ParserContext  *************************

ParserContext::ParserContext(Option::Universe universe):
Mukund Sivaraman's avatar
Mukund Sivaraman committed
42 43 44 45
    boolean_values_(new BooleanStorage()),
    uint32_values_(new Uint32Storage()),
    string_values_(new StringStorage()),
    options_(new OptionStorage()),
46
    hooks_libraries_(),
Mukund Sivaraman's avatar
Mukund Sivaraman committed
47 48 49
    universe_(universe)
{
}
50

51
ParserContext::ParserContext(const ParserContext& rhs):
52 53 54 55 56
    boolean_values_(),
    uint32_values_(),
    string_values_(),
    options_(),
    hooks_libraries_(),
Mukund Sivaraman's avatar
Mukund Sivaraman committed
57 58
    universe_(rhs.universe_)
{
59
    copyContext(rhs);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
60
}
61

62
ParserContext&
63 64 65
// The cppcheck version 1.56 doesn't recognize that copyContext
// copies all context fields.
// cppcheck-suppress operatorEqVarError
66
ParserContext::operator=(const ParserContext& rhs) {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
67
    if (this != &rhs) {
68
        copyContext(rhs);
69
    }
70

Mukund Sivaraman's avatar
Mukund Sivaraman committed
71 72
    return (*this);
}
73

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
void
ParserContext::copyContext(const ParserContext& ctx) {
    copyContextPointer(ctx.boolean_values_, boolean_values_);
    copyContextPointer(ctx.uint32_values_, uint32_values_);
    copyContextPointer(ctx.string_values_, string_values_);
    copyContextPointer(ctx.options_, options_);
    copyContextPointer(ctx.hooks_libraries_, hooks_libraries_);
    // Copy universe.
    universe_ = ctx.universe_;
}

template<typename T>
void
ParserContext::copyContextPointer(const boost::shared_ptr<T>& source_ptr,
                                  boost::shared_ptr<T>& dest_ptr) {
    if (source_ptr) {
        dest_ptr.reset(new T(*source_ptr));
    } else {
        dest_ptr.reset();
    }
}

96

97 98 99 100 101 102
// **************************** DebugParser *************************

DebugParser::DebugParser(const std::string& param_name)
    :param_name_(param_name) {
}

103
void
104
DebugParser::build(ConstElementPtr new_config) {
105
    value_ = new_config;
106
    std::cout << "Build for token: [" << param_name_ << "] = ["
107
        << value_->str() << "]" << std::endl;
108 109
}

110
void
111
DebugParser::commit() {
112 113 114 115 116 117 118 119
    // Debug message. The whole DebugParser class is used only for parser
    // debugging, and is not used in production code. It is very convenient
    // to keep it around. Please do not turn this cout into logger calls.
    std::cout << "Commit for token: [" << param_name_ << "] = ["
                  << value_->str() << "]" << std::endl;
}

// **************************** BooleanParser  *************************
120

121
template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
122 123
    // Invoke common code for all specializations of build().
    buildCommon(value);
124 125
    // The Config Manager checks if user specified a
    // valid value for a boolean parameter: True or False.
126 127 128 129
    // We should have a boolean Element, use value directly
    try {
        value_ = value->boolValue();
    } catch (const isc::data::TypeError &) {
130
        isc_throw(BadValue, " Wrong value type for " << param_name_
131 132
                  << " : build called with a non-boolean element "
                  << "(" << value->getPosition() << ").");
133
    }
134 135 136 137
}

// **************************** Uin32Parser  *************************

138
template<> void ValueParser<uint32_t>::build(ConstElementPtr value) {
139 140 141
    // Invoke common code for all specializations of build().
    buildCommon(value);

142 143 144 145 146 147
    int64_t check;
    string x = value->str();
    try {
        check = boost::lexical_cast<int64_t>(x);
    } catch (const boost::bad_lexical_cast &) {
        isc_throw(BadValue, "Failed to parse value " << value->str()
148 149
                  << " as unsigned 32-bit integer "
                  "(" << value->getPosition() << ").");
150 151
    }
    if (check > std::numeric_limits<uint32_t>::max()) {
152 153
        isc_throw(BadValue, "Value " << value->str() << " is too large"
                  " for unsigned 32-bit integer "
154
                  "(" << value->getPosition() << ").");
155 156
    }
    if (check < 0) {
157
        isc_throw(BadValue, "Value " << value->str() << " is negative."
158 159
               << " Only 0 or larger are allowed for unsigned 32-bit integer "
                  "(" << value->getPosition() << ").");
160 161 162 163 164 165 166 167
    }

    // value is small enough to fit
    value_ = static_cast<uint32_t>(check);
}

// **************************** StringParser  *************************

168
template <> void ValueParser<std::string>::build(ConstElementPtr value) {
169 170 171
    // Invoke common code for all specializations of build().
    buildCommon(value);

172 173 174 175
    value_ = value->str();
    boost::erase_all(value_, "\"");
}

176
// ******************** InterfaceListConfigParser *************************
177

178
InterfaceListConfigParser::
179 180 181
InterfaceListConfigParser(const std::string& param_name,
                          ParserContextPtr global_context)
    : param_name_(param_name), global_context_(global_context) {
182
    if (param_name_ != "interfaces") {
183 184 185 186 187
        isc_throw(BadValue, "Internal error. Interface configuration "
            "parser called for the wrong parameter: " << param_name);
    }
}

188
void
189
InterfaceListConfigParser::build(ConstElementPtr value) {
190
    CfgIface cfg_iface;
191
    BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
192
        std::string iface_name = iface->stringValue();
193
        try {
194
            cfg_iface.use(global_context_->universe_ == Option::V4 ?
195
                          AF_INET : AF_INET6, iface_name);
196

197 198 199
        } catch (const std::exception& ex) {
            isc_throw(DhcpConfigError, "Failed to select interface: "
                      << ex.what() << " (" << value->getPosition() << ")");
200
        }
201
    }
202
    CfgMgr::instance().getStagingCfg()->setCfgIface(cfg_iface);
203 204
}

205
void
206
InterfaceListConfigParser::commit() {
207
    // Nothing to do.
208 209
}

Stephen Morris's avatar
Stephen Morris committed
210 211
// ******************** HooksLibrariesParser *************************

212 213 214 215 216
HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name)
    : libraries_(), changed_(false)
{
    // Sanity check on the name.
    if (param_name != "hooks-libraries") {
Stephen Morris's avatar
Stephen Morris committed
217 218 219 220 221
        isc_throw(BadValue, "Internal error. Hooks libraries "
            "parser called for the wrong parameter: " << param_name);
    }
}

Mukund Sivaraman's avatar
Mukund Sivaraman committed
222
void
Stephen Morris's avatar
Stephen Morris committed
223
HooksLibrariesParser::build(ConstElementPtr value) {
224 225 226
    // Initialize.
    libraries_.clear();
    changed_ = false;
Stephen Morris's avatar
Stephen Morris committed
227

228
    // Extract the list of libraries.
Stephen Morris's avatar
Stephen Morris committed
229 230 231
    BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
        string libname = iface->str();
        boost::erase_all(libname, "\"");
232 233 234 235 236 237 238 239 240 241 242 243 244
        libraries_.push_back(libname);
    }

    // Check if the list of libraries has changed.  If not, nothing is done
    // - the command "DhcpN libreload" is required to reload the same
    // libraries (this prevents needless reloads when anything else in the
    // configuration is changed).
    vector<string> current_libraries = HooksManager::getLibraryNames();
    if (current_libraries == libraries_) {
        return;
    }

    // Library list has changed, validate each of the libraries specified.
245
    vector<string> error_libs = HooksManager::validateLibraries(libraries_);
246
    if (!error_libs.empty()) {
247 248 249 250 251 252

        // Construct the list of libraries in error for the message.
        string error_list = error_libs[0];
        for (int i = 1; i < error_libs.size(); ++i) {
            error_list += (string(", ") + error_libs[i]);
        }
253
        isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
254 255
                  "library or libraries in error are: " << error_list
                  << " (" << value->getPosition() << ")");
Stephen Morris's avatar
Stephen Morris committed
256
    }
257

258 259 260
    // The library list has changed and the libraries are valid, so flag for
    // update when commit() is called.
    changed_ = true;
Stephen Morris's avatar
Stephen Morris committed
261 262
}

Mukund Sivaraman's avatar
Mukund Sivaraman committed
263
void
Stephen Morris's avatar
Stephen Morris committed
264
HooksLibrariesParser::commit() {
265 266 267
    /// Commits the list of libraries to the configuration manager storage if
    /// the list of libraries has changed.
    if (changed_) {
268 269
        // TODO Delete any stored CalloutHandles before reloading the
        // libraries
270 271 272 273 274 275 276 277 278 279
        HooksManager::loadLibraries(libraries_);
    }
}

// Method for testing
void
HooksLibrariesParser::getLibraries(std::vector<std::string>& libraries,
                                   bool& changed) {
    libraries = libraries_;
    changed = changed_;
Stephen Morris's avatar
Stephen Morris committed
280 281
}

282
// **************************** OptionDataParser *************************
283 284
OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
                                  ParserContextPtr global_context)
285 286 287
    : boolean_values_(new BooleanStorage()),
    string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
    options_(options), option_descriptor_(false),
288
    global_context_(global_context) {
289
    if (!options_) {
290
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
291 292
             << "options storage may not be NULL");
    }
293 294

    if (!global_context_) {
295
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
296 297
             << "context may may not be NULL");
    }
298 299
}

300
void
301
OptionDataParser::build(ConstElementPtr option_data_entries) {
302 303 304 305
    BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
        ParserPtr parser;
        if (param.first == "name" || param.first == "data" ||
            param.first == "space") {
306 307
            StringParserPtr name_parser(new StringParser(param.first,
                                        string_values_));
308 309
            parser = name_parser;
        } else if (param.first == "code") {
310 311
            Uint32ParserPtr code_parser(new Uint32Parser(param.first,
                                       uint32_values_));
312 313
            parser = code_parser;
        } else if (param.first == "csv-format") {
314 315
            BooleanParserPtr value_parser(new BooleanParser(param.first,
                                         boolean_values_));
316 317 318
            parser = value_parser;
        } else {
            isc_throw(DhcpConfigError,
319 320
                      "option-data parameter not supported: " << param.first
                      << " (" << param.second->getPosition() << ")");
321 322 323 324 325 326 327 328 329 330 331 332 333
        }

        parser->build(param.second);
        // Before we can create an option we need to get the data from
        // the child parsers. The only way to do it is to invoke commit
        // on them so as they store the values in appropriate storages
        // that this class provided to them. Note that this will not
        // modify values stored in the global storages so the configuration
        // will remain consistent even parsing fails somewhere further on.
        parser->commit();
    }

    // Try to create the option instance.
334
    createOption(option_data_entries);
335 336
}

337
void
338
OptionDataParser::commit() {
339
    if (!option_descriptor_.option) {
340
        // Before we can commit the new option should be configured. If it is
341
        // not than somebody must have called commit() before build().
342
        isc_throw(isc::InvalidOperation,
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
            "parser logic error: no option has been configured and"
            " thus there is nothing to commit. Has build() been called?");
    }

    uint16_t opt_type = option_descriptor_.option->getType();
    Subnet::OptionContainerPtr options = options_->getItems(option_space_);
    // The getItems() should never return NULL pointer. If there are no
    // options configured for the particular option space a pointer
    // to an empty container should be returned.
    assert(options);
    Subnet::OptionContainerTypeIndex& idx = options->get<1>();
    // Try to find options with the particular option code in the main
    // storage. If found, remove these options because they will be
    // replaced with new one.
    Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type);
    if (std::distance(range.first, range.second) > 0) {
        idx.erase(range.first, range.second);
    }

    // Append new option to the main storage.
    options_->addItem(option_descriptor_, option_space_);
}

366
void
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
OptionDataParser::createOption(ConstElementPtr option_data) {
    // Check if mandatory parameters are specified.
    uint32_t code;
    std::string name;
    std::string data;
    try {
        code = uint32_values_->getParam("code");
        name = string_values_->getParam("name");
        data = string_values_->getParam("data");
    } catch (const std::exception& ex) {
        isc_throw(DhcpConfigError,
                  ex.what() << "(" << option_data->getPosition() << ")");
    }
    // Check parameters having default values.
    std::string space = string_values_->getOptionalParam("space",
              global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6");
    bool csv_format = boolean_values_->getOptionalParam("csv-format", false);

385 386
    // Option code is held in the uint32_t storage but is supposed to
    // be uint16_t value. We need to check that value in the configuration
387 388
    // does not exceed range of uint8_t for DHCPv4, uint16_t for DHCPv6 and
    // is not zero.
389
    if (code == 0) {
390 391
        isc_throw(DhcpConfigError, "option code must not be zero "
                  "(" << uint32_values_->getPosition("code") << ")");
392 393

    } else if (global_context_->universe_ == Option::V4 &&
394 395
               code > std::numeric_limits<uint8_t>::max()) {
        isc_throw(DhcpConfigError, "invalid option code '" << code
396
                << "', it must not exceed '"
397
                  << static_cast<int>(std::numeric_limits<uint8_t>::max())
398
                  << "' (" << uint32_values_->getPosition("code") << ")");
399 400

    } else if (global_context_->universe_ == Option::V6 &&
401 402
               code > std::numeric_limits<uint16_t>::max()) {
        isc_throw(DhcpConfigError, "invalid option code '" << code
403 404
                << "', it must not exceed '"
                  << std::numeric_limits<uint16_t>::max()
405
                  << "' (" << uint32_values_->getPosition("code") << ")");
406

407 408
    }

409 410
    // Check that the option name is non-empty and does not contain spaces
    if (name.empty()) {
411
        isc_throw(DhcpConfigError, "name of the option with code '"
412
                  << code << "' is empty ("
413
                  << string_values_->getPosition("name") << ")");
414 415
    } else if (name.find(" ") != std::string::npos) {
        isc_throw(DhcpConfigError, "invalid option name '" << name
416 417
                  << "', space character is not allowed ("
                  << string_values_->getPosition("name") << ")");
418 419
    }

420
    if (!OptionSpace::validateName(space)) {
421
        isc_throw(DhcpConfigError, "invalid option space name '"
422 423
                << space << "' specified for option '"
                << name << "', code '" << code
424
                  << "' (" << string_values_->getPosition("space") << ")");
425 426 427 428
    }

    // Find the Option Definition for the option by its option code.
    // findOptionDefinition will throw if not found, no need to test.
429 430 431
    // Find the definition for the option by its code. This function
    // may throw so we catch exceptions to log the culprit line of the
    // configuration.
432
    OptionDefinitionPtr def;
433
    try {
434
        def = findServerSpaceOptionDefinition(space, code);
435 436 437 438 439 440

    } catch (const std::exception& ex) {
        isc_throw(DhcpConfigError, ex.what()
                  << " (" << string_values_->getPosition("space") << ")");
    }
    if (!def) {
441 442 443 444
        // If we are not dealing with a standard option then we
        // need to search for its definition among user-configured
        // options. They are expected to be in the global storage
        // already.
445
        OptionDefContainerPtr defs =
446
            CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->getAll(space);
447 448 449 450 451 452

        // The getItems() should never return the NULL pointer. If there are
        // no option definitions for the particular option space a pointer
        // to an empty container should be returned.
        assert(defs);
        const OptionDefContainerTypeIndex& idx = defs->get<1>();
453
        OptionDefContainerTypeRange range = idx.equal_range(code);
454 455 456
        if (std::distance(range.first, range.second) > 0) {
            def = *range.first;
        }
457 458 459 460

        // It's ok if we don't have option format if the option is
        // specified as hex
        if (!def && csv_format) {
461
            isc_throw(DhcpConfigError, "definition for the option '"
462 463
                      << space << "." << name
                      << "' having code '" << code
464 465
                      << "' does not exist ("
                      << string_values_->getPosition("name") << ")");
466 467 468 469 470 471 472 473 474 475 476 477
        }
    }

    // Transform string of hexadecimal digits into binary format.
    std::vector<uint8_t> binary;
    std::vector<std::string> data_tokens;

    if (csv_format) {
        // If the option data is specified as a string of comma
        // separated values then we need to split this string into
        // individual values - each value will be used to initialize
        // one data field of an option.
478
        data_tokens = isc::util::str::tokens(data, ",");
479 480 481 482
    } else {
        // Otherwise, the option data is specified as a string of
        // hexadecimal digits that we have to turn into binary format.
        try {
483 484 485
            // The decodeHex function expects that the string contains an
            // even number of digits. If we don't meet this requirement,
            // we have to insert a leading 0.
486 487
            if (!data.empty() && data.length() % 2) {
                data = data.insert(0, "0");
488
            }
489
            util::encode::decodeHex(data, binary);
490 491
        } catch (...) {
            isc_throw(DhcpConfigError, "option data is not a valid"
492
                      << " string of hexadecimal digits: " << data
493
                      << " (" << string_values_->getPosition("data") << ")");
494 495 496 497 498 499 500 501
        }
    }

    OptionPtr option;
    if (!def) {
        if (csv_format) {
            isc_throw(DhcpConfigError, "the CSV option data format can be"
                      " used to specify values for an option that has a"
502
                      " definition. The option with code " << code
503 504
                      << " does not have a definition ("
                      << boolean_values_->getPosition("csv-format") << ")");
505 506
        }

507
        // @todo We have a limited set of option definitions initalized at
508 509
        // the moment.  In the future we want to initialize option definitions
        // for all options.  Consequently an error will be issued if an option
510 511
        // definition does not exist for a particular option code. For now it is
        // ok to create generic option if definition does not exist.
512
        OptionPtr option(new Option(global_context_->universe_,
513
                        static_cast<uint16_t>(code), binary));
514 515 516
        // The created option is stored in option_descriptor_ class member
        // until the commit stage when it is inserted into the main storage.
        // If an option with the same code exists in main storage already the
517
        // old option is replaced.
518 519 520 521 522 523 524 525 526
        option_descriptor_.option = option;
        option_descriptor_.persistent = false;
    } else {

        // Option name should match the definition. The option name
        // may seem to be redundant but in the future we may want
        // to reference options and definitions using their names
        // and/or option codes so keeping the option name in the
        // definition of option value makes sense.
527
        if (def->getName() != name) {
528
            isc_throw(DhcpConfigError, "specified option name '"
529 530
                      << name << "' does not match the "
                      << "option definition: '" << space
531 532
                      << "." << def->getName() << "' ("
                      << string_values_->getPosition("name") << ")");
533 534 535 536 537 538
        }

        // Option definition has been found so let's use it to create
        // an instance of our option.
        try {
            OptionPtr option = csv_format ?
539
                def->optionFactory(global_context_->universe_,
540
                                  code, data_tokens) :
541
                def->optionFactory(global_context_->universe_,
542
                                   code, binary);
543 544 545 546 547
            Subnet::OptionDescriptor desc(option, false);
            option_descriptor_.option = option;
            option_descriptor_.persistent = false;
        } catch (const isc::Exception& ex) {
            isc_throw(DhcpConfigError, "option data does not match"
548 549
                      << " option definition (space: " << space
                      << ", code: " << code << "): "
550 551
                      << ex.what() << " ("
                      << string_values_->getPosition("data") << ")");
552 553 554 555
        }
    }

    // All went good, so we can set the option space name.
556
    option_space_ = space;
557 558 559
}

// **************************** OptionDataListParser *************************
560
OptionDataListParser::OptionDataListParser(const std::string&,
561 562
    OptionStoragePtr options, ParserContextPtr global_context,
    OptionDataParserFactory* optionDataParserFactory)
563
    : options_(options), local_options_(new OptionStorage()),
564
    global_context_(global_context),
565 566
    optionDataParserFactory_(optionDataParserFactory) {
    if (!options_) {
567
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
568 569 570
             << "options storage may not be NULL");
    }

571
    if (!options_) {
572
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
573 574 575
             << "context may not be NULL");
    }

576
    if (!optionDataParserFactory_) {
577
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
578 579 580 581
             << "option data parser factory may not be NULL");
    }
}

582
void
583
OptionDataListParser::build(ConstElementPtr option_data_list) {
584
    BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
585 586
        boost::shared_ptr<OptionDataParser>
            parser((*optionDataParserFactory_)("option-data",
587
                    local_options_, global_context_));
588 589 590 591 592 593 594 595 596 597

        // options_ member will hold instances of all options thus
        // each OptionDataParser takes it as a storage.
        // Build the instance of a single option.
        parser->build(option_value);
        // Store a parser as it will be used to commit.
        parsers_.push_back(parser);
    }
}

598
void
599
OptionDataListParser::commit() {
600 601 602 603 604 605 606
    BOOST_FOREACH(ParserPtr parser, parsers_) {
        parser->commit();
    }

    // Parsing was successful and we have all configured
    // options in local storage. We can now replace old values
    // with new values.
607
    std::swap(*local_options_, *options_);
608 609 610
}

// ******************************** OptionDefParser ****************************
611
OptionDefParser::OptionDefParser(const std::string&,
612
                                 ParserContextPtr global_context)
613
    : boolean_values_(new BooleanStorage()),
614 615 616
      string_values_(new StringStorage()),
      uint32_values_(new Uint32Storage()),
      global_context_(global_context) {
617 618
}

619
void
620
OptionDefParser::build(ConstElementPtr option_def) {
621
    // Parse the elements that make up the option definition.
622
    BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
623 624
        std::string entry(param.first);
        ParserPtr parser;
625
        if (entry == "name" || entry == "type" || entry == "record-types"
626
            || entry == "space" || entry == "encapsulate") {
627
            StringParserPtr str_parser(new StringParser(entry,
628
                                       string_values_));
629 630
            parser = str_parser;
        } else if (entry == "code") {
631
            Uint32ParserPtr code_parser(new Uint32Parser(entry,
632
                                        uint32_values_));
633 634
            parser = code_parser;
        } else if (entry == "array") {
635
            BooleanParserPtr array_parser(new BooleanParser(entry,
636
                                         boolean_values_));
637 638
            parser = array_parser;
        } else {
639 640
            isc_throw(DhcpConfigError, "invalid parameter '" << entry
                      << "' (" << param.second->getPosition() << ")");
641 642 643 644 645 646
        }

        parser->build(param.second);
        parser->commit();
    }
    // Create an instance of option definition.
647
    createOptionDef(option_def);
648

649
    try {
650 651
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->
            add(option_definition_, option_space_name_);
652

653
    } catch (const std::exception& ex) {
654
        // Append position if there is a failure.
655
        isc_throw(DhcpConfigError, ex.what() << " ("
656
                  << option_def->getPosition() << ")");
657 658 659
    }
}

660
void
661
OptionDefParser::commit() {
662
    // Do nothing.
663 664
}

665
void
666 667
OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
    // Check if mandatory parameters have been specified.
668 669 670
    std::string name;
    uint32_t code;
    std::string type;
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
    try {
        name = string_values_->getParam("name");
        code = uint32_values_->getParam("code");
        type = string_values_->getParam("type");
    } catch (const std::exception& ex) {
        isc_throw(DhcpConfigError, ex.what() << " ("
                  << option_def_element->getPosition() << ")");
    }

    bool array_type = boolean_values_->getOptionalParam("array", false);
    std::string record_types =
        string_values_->getOptionalParam("record-types", "");
    std::string space = string_values_->getOptionalParam("space",
              global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6");
    std::string encapsulates =
        string_values_->getOptionalParam("encapsulate", "");

688 689
    if (!OptionSpace::validateName(space)) {
        isc_throw(DhcpConfigError, "invalid option space name '"
690 691
                  << space << "' ("
                  << string_values_->getPosition("space") << ")");
692 693 694 695 696 697 698 699 700 701 702
    }

    // Create option definition.
    OptionDefinitionPtr def;
    // We need to check if user has set encapsulated option space
    // name. If so, different constructor will be used.
    if (!encapsulates.empty()) {
        // Arrays can't be used together with sub-options.
        if (array_type) {
            isc_throw(DhcpConfigError, "option '" << space << "."
                      << "name" << "', comprising an array of data"
703
                      << " fields may not encapsulate any option space ("
704
                      << option_def_element->getPosition() << ")");
705 706 707 708 709

        } else if (encapsulates == space) {
            isc_throw(DhcpConfigError, "option must not encapsulate"
                      << " an option space it belongs to: '"
                      << space << "." << name << "' is set to"
710
                      << " encapsulate '" << space << "' ("
711
                      << option_def_element->getPosition() << ")");
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

        } else {
            def.reset(new OptionDefinition(name, code, type,
                        encapsulates.c_str()));
        }

    } else {
        def.reset(new OptionDefinition(name, code, type, array_type));

    }

    // Split the list of record types into tokens.
    std::vector<std::string> record_tokens =
    isc::util::str::tokens(record_types, ",");
    // Iterate over each token and add a record type into
    // option definition.
    BOOST_FOREACH(std::string record_type, record_tokens) {
        try {
            boost::trim(record_type);
            if (!record_type.empty()) {
                    def->addRecordField(record_type);
            }
        } catch (const Exception& ex) {
            isc_throw(DhcpConfigError, "invalid record type values"
                      << " specified for the option definition: "
737 738
                      << ex.what() << " ("
                      << string_values_->getPosition("record-types") << ")");
739 740 741
        }
    }

742
    // Validate the definition.
743 744
    try {
        def->validate();
745 746 747
    } catch (const std::exception& ex) {
        isc_throw(DhcpConfigError, ex.what()
                  << " (" << option_def_element->getPosition() << ")");
748 749 750 751 752 753 754 755
    }

    // Option definition has been created successfully.
    option_space_name_ = space;
    option_definition_ = def;
}

// ******************************** OptionDefListParser ************************
756
OptionDefListParser::OptionDefListParser(const std::string&,
757
                                         ParserContextPtr global_context)
758
    : global_context_(global_context) {
759 760
}

761
void
762
OptionDefListParser::build(ConstElementPtr option_def_list) {
763 764
    if (!option_def_list) {
        isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
765 766
                  << " option definitions is NULL ("
                  << option_def_list->getPosition() << ")");
767 768 769 770
    }

    BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
        boost::shared_ptr<OptionDefParser>
771
            parser(new OptionDefParser("single-option-def", global_context_));
772 773 774 775
        parser->build(option_def);
    }
}

776 777 778 779 780
void
OptionDefListParser::commit() {
    // Do nothing.
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
781
//****************************** RelayInfoParser ********************************
782 783
RelayInfoParser::RelayInfoParser(const std::string&,
                                 const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
784
                                 const Option::Universe& family)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
785 786 787
    :storage_(relay_info), local_(isc::asiolink::IOAddress(
                                  family == Option::V4 ? "0.0.0.0" : "::")),
     string_values_(new StringStorage()), family_(family) {
788
    if (!relay_info) {
789
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
                  << "relay-info storage may not be NULL");
    }

};

void
RelayInfoParser::build(ConstElementPtr relay_info) {

    BOOST_FOREACH(ConfigPair param, relay_info->mapValue()) {
        ParserPtr parser(createConfigParser(param.first));
        parser->build(param.second);
        parser->commit();
    }

    // Get the IP address
Tomek Mrugalski's avatar
Tomek Mrugalski committed
805 806 807 808 809
    boost::scoped_ptr<asiolink::IOAddress> ip;
    try {
        ip.reset(new asiolink::IOAddress(string_values_->getParam("ip-address")));
    } catch (...)  {
        isc_throw(DhcpConfigError, "Failed to parse ip-address "
810 811
                  "value: " << string_values_->getParam("ip-address")
                  << " (" << string_values_->getPosition("ip-address") << ")");
Tomek Mrugalski's avatar
Tomek Mrugalski committed
812 813 814 815 816
    }

    if ( (ip->isV4() && family_ != Option::V4) ||
         (ip->isV6() && family_ != Option::V6) ) {
        isc_throw(DhcpConfigError, "ip-address field " << ip->toText()
Marcin Siodelski's avatar
Marcin Siodelski committed
817
                  << "does not have IP address of expected family type: "
818 819
                  << (family_ == Option::V4 ? "IPv4" : "IPv6")
                  << " (" << string_values_->getPosition("ip-address") << ")");
Tomek Mrugalski's avatar
Tomek Mrugalski committed
820
    }
821

Tomek Mrugalski's avatar
Tomek Mrugalski committed
822
    local_.addr_ = *ip;
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
}

isc::dhcp::ParserPtr
RelayInfoParser::createConfigParser(const std::string& parameter) {
    DhcpConfigParser* parser = NULL;
    if (parameter.compare("ip-address") == 0) {
        parser = new StringParser(parameter, string_values_);
    } else {
        isc_throw(NotImplemented,
                  "parser error: RelayInfoParser parameter not supported: "
                  << parameter);
    }

    return (isc::dhcp::ParserPtr(parser));
}

void
RelayInfoParser::commit() {
    *storage_ = local_;
}

844
//****************************** PoolsListParser ********************************
845
PoolsListParser::PoolsListParser(const std::string&, PoolStoragePtr pools)
846 847
    :pools_(pools), local_pools_(new PoolStorage()) {
    if (!pools_) {
848
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
849 850 851 852 853 854 855 856 857 858 859 860 861 862
                  << "storage may not be NULL");
    }
}

void
PoolsListParser::build(ConstElementPtr pools) {
    BOOST_FOREACH(ConstElementPtr pool, pools->listValue()) {

        // Iterate over every structure on the pools list and invoke
        // a separate parser for it.
        ParserPtr parser = poolParserMaker(local_pools_);

        parser->build(pool);

863 864
        // Let's store the parser, but do not commit anything yet
        parsers_.push_back(parser);
865 866 867 868
    }
}

void PoolsListParser::commit() {
869 870 871 872 873 874 875

    // Commit each parser first. It will store the pool structure
    // in pools_.
    BOOST_FOREACH(ParserPtr parser, parsers_) {
        parser->commit();
    }

876 877 878 879 880 881 882 883
    if (pools_) {
        // local_pools_ holds the values produced by the build function.
        // At this point parsing should have completed successfuly so
        // we can append new data to the supplied storage.
        pools_->insert(pools_->end(), local_pools_->begin(), local_pools_->end());
    }
}

884 885 886 887 888
//****************************** PoolParser ********************************
PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
        :pools_(pools) {

    if (!pools_) {
889
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
890 891 892 893
                  << "storage may not be NULL");
    }
}

894
void
895
PoolParser::build(ConstElementPtr pool_structure) {
896

897
    ConstElementPtr text_pool = pool_structure->get("pool");
898

899 900 901 902 903
    if (!text_pool) {
        isc_throw(DhcpConfigError, "Mandatory 'pool' entry missing in "
                  "definition: (" << text_pool->getPosition() << ")");
    }

904 905 906 907
    // That should be a single pool representation. It should contain
    // text is form prefix/len or first - last. Note that spaces
    // are allowed
    string txt = text_pool->stringValue();
908

909 910 911
    // first let's remove any whitespaces
    boost::erase_all(txt, " "); // space
    boost::erase_all(txt, "\t"); // tabulation
912

913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936
    // Is this prefix/len notation?
    size_t pos = txt.find("/");
    if (pos != string::npos) {
        isc::asiolink::IOAddress addr("::");
        uint8_t len = 0;
        try {
            addr = isc::asiolink::IOAddress(txt.substr(0, pos));

            // start with the first character after /
            string prefix_len = txt.substr(pos + 1);

            // It is lexical cast to int and then downcast to uint8_t.
            // Direct cast to uint8_t (which is really an unsigned char)
            // will result in interpreting the first digit as output
            // value and throwing exception if length is written on two
            // digits (because there are extra characters left over).

            // No checks for values over 128. Range correctness will
            // be checked in Pool4 constructor.
            len = boost::lexical_cast<int>(prefix_len);
        } catch (...)  {
            isc_throw(DhcpConfigError, "Failed to parse pool "
                      "definition: " << text_pool->stringValue()
                      << " (" << text_pool->getPosition() << ")");