dhcp_parsers.cc 59.4 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
46
    boolean_values_(new BooleanStorage()),
    uint32_values_(new Uint32Storage()),
    string_values_(new StringStorage()),
    options_(new OptionStorage()),
    option_defs_(new OptionDefStorage()),
47
    hooks_libraries_(),
Mukund Sivaraman's avatar
Mukund Sivaraman committed
48
49
50
    universe_(universe)
{
}
51

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

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

Mukund Sivaraman's avatar
Mukund Sivaraman committed
73
74
    return (*this);
}
75

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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.option_defs_, option_defs_);
    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();
    }
}

99

100
101
102
103
104
105
// **************************** DebugParser *************************

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

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

113
void
114
DebugParser::commit() {
115
116
117
118
119
120
121
122
    // 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  *************************
123

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

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

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

145
146
147
148
149
150
    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()
151
152
                  << " as unsigned 32-bit integer "
                  "(" << value->getPosition() << ").");
153
154
    }
    if (check > std::numeric_limits<uint32_t>::max()) {
155
156
        isc_throw(BadValue, "Value " << value->str() << " is too large"
                  " for unsigned 32-bit integer "
157
                  "(" << value->getPosition() << ").");
158
159
    }
    if (check < 0) {
160
        isc_throw(BadValue, "Value " << value->str() << " is negative."
161
162
               << " Only 0 or larger are allowed for unsigned 32-bit integer "
                  "(" << value->getPosition() << ").");
163
164
165
166
167
168
169
170
    }

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

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

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

175
176
177
178
    value_ = value->str();
    boost::erase_all(value_, "\"");
}

179
// ******************** InterfaceListConfigParser *************************
180

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

190
void
191
InterfaceListConfigParser::build(ConstElementPtr value) {
192
    // Copy the current interface configuration.
193
    ConfigurationPtr config = CfgMgr::instance().getConfiguration();
194
195
    cfg_iface_ = config->cfg_iface_;
    cfg_iface_.reset();
196
    BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
197
        std::string iface_name = iface->stringValue();
198
        try {
199
            cfg_iface_.use(iface_name);
200

201
202
203
        } catch (const std::exception& ex) {
            isc_throw(DhcpConfigError, "Failed to select interface: "
                      << ex.what() << " (" << value->getPosition() << ")");
204
        }
205
206
207
    }
}

208
void
209
InterfaceListConfigParser::commit() {
210
211
    // Use the new configuration created in a build time.
    CfgMgr::instance().getConfiguration()->cfg_iface_ = cfg_iface_;
212
213
214
215
}

bool
InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
216

217
218
219
220
221
222
223
    for (IfaceListStorage::const_iterator it = interfaces_.begin();
         it != interfaces_.end(); ++it) {
        if (iface == *it) {
            return (true);
        }
    }
    return (false);
224
225
}

Stephen Morris's avatar
Stephen Morris committed
226
227
// ******************** HooksLibrariesParser *************************

228
229
230
231
232
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
233
234
235
236
237
        isc_throw(BadValue, "Internal error. Hooks libraries "
            "parser called for the wrong parameter: " << param_name);
    }
}

Mukund Sivaraman's avatar
Mukund Sivaraman committed
238
void
Stephen Morris's avatar
Stephen Morris committed
239
HooksLibrariesParser::build(ConstElementPtr value) {
240
241
242
    // Initialize.
    libraries_.clear();
    changed_ = false;
Stephen Morris's avatar
Stephen Morris committed
243

244
    // Extract the list of libraries.
Stephen Morris's avatar
Stephen Morris committed
245
246
247
    BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
        string libname = iface->str();
        boost::erase_all(libname, "\"");
248
249
250
251
252
253
254
255
256
257
258
259
260
        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.
261
    vector<string> error_libs = HooksManager::validateLibraries(libraries_);
262
    if (!error_libs.empty()) {
263
264
265
266
267
268

        // 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]);
        }
269
        isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
270
271
                  "library or libraries in error are: " << error_list
                  << " (" << value->getPosition() << ")");
Stephen Morris's avatar
Stephen Morris committed
272
    }
273

274
275
276
    // 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
277
278
}

Mukund Sivaraman's avatar
Mukund Sivaraman committed
279
void
Stephen Morris's avatar
Stephen Morris committed
280
HooksLibrariesParser::commit() {
281
282
283
    /// Commits the list of libraries to the configuration manager storage if
    /// the list of libraries has changed.
    if (changed_) {
284
285
        // TODO Delete any stored CalloutHandles before reloading the
        // libraries
286
287
288
289
290
291
292
293
294
295
        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
296
297
}

298
// **************************** OptionDataParser *************************
299
300
OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
                                  ParserContextPtr global_context)
301
302
303
    : boolean_values_(new BooleanStorage()),
    string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
    options_(options), option_descriptor_(false),
304
    global_context_(global_context) {
305
    if (!options_) {
306
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
307
308
             << "options storage may not be NULL");
    }
309
310

    if (!global_context_) {
311
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
312
313
             << "context may may not be NULL");
    }
314
315
}

316
void
317
OptionDataParser::build(ConstElementPtr option_data_entries) {
318
319
320
321
    BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
        ParserPtr parser;
        if (param.first == "name" || param.first == "data" ||
            param.first == "space") {
322
323
            StringParserPtr name_parser(new StringParser(param.first,
                                        string_values_));
324
325
            parser = name_parser;
        } else if (param.first == "code") {
326
327
            Uint32ParserPtr code_parser(new Uint32Parser(param.first,
                                       uint32_values_));
328
329
            parser = code_parser;
        } else if (param.first == "csv-format") {
330
331
            BooleanParserPtr value_parser(new BooleanParser(param.first,
                                         boolean_values_));
332
333
334
            parser = value_parser;
        } else {
            isc_throw(DhcpConfigError,
335
336
                      "option-data parameter not supported: " << param.first
                      << " (" << param.second->getPosition() << ")");
337
338
339
340
341
342
343
344
345
346
347
348
349
        }

        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.
350
    createOption(option_data_entries);
351
352
}

353
void
354
OptionDataParser::commit() {
355
    if (!option_descriptor_.option) {
356
        // Before we can commit the new option should be configured. If it is
357
        // not than somebody must have called commit() before build().
358
        isc_throw(isc::InvalidOperation,
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
            "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_);
}

382
void
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
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);

401
402
    // 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
403
404
    // does not exceed range of uint8_t for DHCPv4, uint16_t for DHCPv6 and
    // is not zero.
405
    if (code == 0) {
406
407
        isc_throw(DhcpConfigError, "option code must not be zero "
                  "(" << uint32_values_->getPosition("code") << ")");
408
409

    } else if (global_context_->universe_ == Option::V4 &&
410
411
               code > std::numeric_limits<uint8_t>::max()) {
        isc_throw(DhcpConfigError, "invalid option code '" << code
412
                << "', it must not exceed '"
413
                  << static_cast<int>(std::numeric_limits<uint8_t>::max())
414
                  << "' (" << uint32_values_->getPosition("code") << ")");
415
416

    } else if (global_context_->universe_ == Option::V6 &&
417
418
               code > std::numeric_limits<uint16_t>::max()) {
        isc_throw(DhcpConfigError, "invalid option code '" << code
419
420
                << "', it must not exceed '"
                  << std::numeric_limits<uint16_t>::max()
421
                  << "' (" << uint32_values_->getPosition("code") << ")");
422

423
424
    }

425
426
    // Check that the option name is non-empty and does not contain spaces
    if (name.empty()) {
427
        isc_throw(DhcpConfigError, "name of the option with code '"
428
                  << code << "' is empty ("
429
                  << string_values_->getPosition("name") << ")");
430
431
    } else if (name.find(" ") != std::string::npos) {
        isc_throw(DhcpConfigError, "invalid option name '" << name
432
433
                  << "', space character is not allowed ("
                  << string_values_->getPosition("name") << ")");
434
435
    }

436
    if (!OptionSpace::validateName(space)) {
437
        isc_throw(DhcpConfigError, "invalid option space name '"
438
439
                << space << "' specified for option '"
                << name << "', code '" << code
440
                  << "' (" << string_values_->getPosition("space") << ")");
441
442
443
444
    }

    // Find the Option Definition for the option by its option code.
    // findOptionDefinition will throw if not found, no need to test.
445
446
447
    // 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.
448
    OptionDefinitionPtr def;
449
    try {
450
        def = findServerSpaceOptionDefinition(space, code);
451
452
453
454
455
456

    } catch (const std::exception& ex) {
        isc_throw(DhcpConfigError, ex.what()
                  << " (" << string_values_->getPosition("space") << ")");
    }
    if (!def) {
457
458
459
460
        // 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.
461
        OptionDefContainerPtr defs =
462
            global_context_->option_defs_->getItems(space);
463
464
465
466
467
468

        // 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>();
469
        OptionDefContainerTypeRange range = idx.equal_range(code);
470
471
472
        if (std::distance(range.first, range.second) > 0) {
            def = *range.first;
        }
473
474
475
476

        // It's ok if we don't have option format if the option is
        // specified as hex
        if (!def && csv_format) {
477
            isc_throw(DhcpConfigError, "definition for the option '"
478
479
                      << space << "." << name
                      << "' having code '" << code
480
481
                      << "' does not exist ("
                      << string_values_->getPosition("name") << ")");
482
483
484
485
486
487
488
489
490
491
492
493
        }
    }

    // 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.
494
        data_tokens = isc::util::str::tokens(data, ",");
495
496
497
498
    } else {
        // Otherwise, the option data is specified as a string of
        // hexadecimal digits that we have to turn into binary format.
        try {
499
500
501
            // 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.
502
503
            if (!data.empty() && data.length() % 2) {
                data = data.insert(0, "0");
504
            }
505
            util::encode::decodeHex(data, binary);
506
507
        } catch (...) {
            isc_throw(DhcpConfigError, "option data is not a valid"
508
                      << " string of hexadecimal digits: " << data
509
                      << " (" << string_values_->getPosition("data") << ")");
510
511
512
513
514
515
516
517
        }
    }

    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"
518
                      " definition. The option with code " << code
519
520
                      << " does not have a definition ("
                      << boolean_values_->getPosition("csv-format") << ")");
521
522
        }

523
        // @todo We have a limited set of option definitions initalized at
524
525
        // the moment.  In the future we want to initialize option definitions
        // for all options.  Consequently an error will be issued if an option
526
527
        // definition does not exist for a particular option code. For now it is
        // ok to create generic option if definition does not exist.
528
        OptionPtr option(new Option(global_context_->universe_,
529
                        static_cast<uint16_t>(code), binary));
530
531
532
        // 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
533
        // old option is replaced.
534
535
536
537
538
539
540
541
542
        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.
543
        if (def->getName() != name) {
544
            isc_throw(DhcpConfigError, "specified option name '"
545
546
                      << name << "' does not match the "
                      << "option definition: '" << space
547
548
                      << "." << def->getName() << "' ("
                      << string_values_->getPosition("name") << ")");
549
550
551
552
553
554
        }

        // Option definition has been found so let's use it to create
        // an instance of our option.
        try {
            OptionPtr option = csv_format ?
555
                def->optionFactory(global_context_->universe_,
556
                                  code, data_tokens) :
557
                def->optionFactory(global_context_->universe_,
558
                                   code, binary);
559
560
561
562
563
            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"
564
565
                      << " option definition (space: " << space
                      << ", code: " << code << "): "
566
567
                      << ex.what() << " ("
                      << string_values_->getPosition("data") << ")");
568
569
570
571
        }
    }

    // All went good, so we can set the option space name.
572
    option_space_ = space;
573
574
575
}

// **************************** OptionDataListParser *************************
576
OptionDataListParser::OptionDataListParser(const std::string&,
577
578
    OptionStoragePtr options, ParserContextPtr global_context,
    OptionDataParserFactory* optionDataParserFactory)
579
    : options_(options), local_options_(new OptionStorage()),
580
    global_context_(global_context),
581
582
    optionDataParserFactory_(optionDataParserFactory) {
    if (!options_) {
583
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
584
585
586
             << "options storage may not be NULL");
    }

587
    if (!options_) {
588
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
589
590
591
             << "context may not be NULL");
    }

592
    if (!optionDataParserFactory_) {
593
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
594
595
596
597
             << "option data parser factory may not be NULL");
    }
}

598
void
599
OptionDataListParser::build(ConstElementPtr option_data_list) {
600
    BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
601
602
        boost::shared_ptr<OptionDataParser>
            parser((*optionDataParserFactory_)("option-data",
603
                    local_options_, global_context_));
604
605
606
607
608
609
610
611
612
613

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

614
void
615
OptionDataListParser::commit() {
616
617
618
619
620
621
622
    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.
623
    std::swap(*local_options_, *options_);
624
625
626
}

// ******************************** OptionDefParser ****************************
627
OptionDefParser::OptionDefParser(const std::string&,
628
629
630
631
632
633
634
                                 OptionDefStoragePtr storage,
                                 ParserContextPtr global_context)
    : storage_(storage),
      boolean_values_(new BooleanStorage()),
      string_values_(new StringStorage()),
      uint32_values_(new Uint32Storage()),
      global_context_(global_context) {
635
    if (!storage_) {
636
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
637
638
639
640
             << "options storage may not be NULL");
    }
}

641
void
642
OptionDefParser::build(ConstElementPtr option_def) {
643
    // Parse the elements that make up the option definition.
644
    BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
645
646
        std::string entry(param.first);
        ParserPtr parser;
647
        if (entry == "name" || entry == "type" || entry == "record-types"
648
            || entry == "space" || entry == "encapsulate") {
649
            StringParserPtr str_parser(new StringParser(entry,
650
                                       string_values_));
651
652
            parser = str_parser;
        } else if (entry == "code") {
653
            Uint32ParserPtr code_parser(new Uint32Parser(entry,
654
                                        uint32_values_));
655
656
            parser = code_parser;
        } else if (entry == "array") {
657
            BooleanParserPtr array_parser(new BooleanParser(entry,
658
                                         boolean_values_));
659
660
            parser = array_parser;
        } else {
661
662
            isc_throw(DhcpConfigError, "invalid parameter '" << entry
                      << "' (" << param.second->getPosition() << ")");
663
664
665
666
667
668
        }

        parser->build(param.second);
        parser->commit();
    }
    // Create an instance of option definition.
669
    createOptionDef(option_def);
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684

    // Get all items we collected so far for the particular option space.
    OptionDefContainerPtr defs = storage_->getItems(option_space_name_);

    // Check if there are any items with option code the same as the
    // one specified for the definition we are now creating.
    const OptionDefContainerTypeIndex& idx = defs->get<1>();
    const OptionDefContainerTypeRange& range =
            idx.equal_range(option_definition_->getCode());

    // If there are any items with this option code already we need
    // to issue an error because we don't allow duplicates for
    // option definitions within an option space.
    if (std::distance(range.first, range.second) > 0) {
        isc_throw(DhcpConfigError, "duplicated option definition for"
685
686
                  << " code '" << option_definition_->getCode() << "' ("
                  << option_def->getPosition() << ")");
687
688
689
    }
}

690
void
691
OptionDefParser::commit() {
692
693
694
695
696
697
    if (storage_ && option_definition_ &&
        OptionSpace::validateName(option_space_name_)) {
            storage_->addItem(option_definition_, option_space_name_);
    }
}

698
void
699
700
OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
    // Check if mandatory parameters have been specified.
701
702
703
    std::string name;
    uint32_t code;
    std::string type;
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
    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", "");

721
722
    if (!OptionSpace::validateName(space)) {
        isc_throw(DhcpConfigError, "invalid option space name '"
723
724
                  << space << "' ("
                  << string_values_->getPosition("space") << ")");
725
726
727
728
729
730
731
732
733
734
735
    }

    // 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"
736
                      << " fields may not encapsulate any option space ("
737
                      << option_def_element->getPosition() << ")");
738
739
740
741
742

        } else if (encapsulates == space) {
            isc_throw(DhcpConfigError, "option must not encapsulate"
                      << " an option space it belongs to: '"
                      << space << "." << name << "' is set to"
743
                      << " encapsulate '" << space << "' ("
744
                      << option_def_element->getPosition() << ")");
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769

        } 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: "
770
771
                      << ex.what() << " ("
                      << string_values_->getPosition("record-types") << ")");
772
773
774
        }
    }

775
    // Validate the definition.
776
777
    try {
        def->validate();
778
779
780
    } catch (const std::exception& ex) {
        isc_throw(DhcpConfigError, ex.what()
                  << " (" << option_def_element->getPosition() << ")");
781
782
783
784
785
786
787
788
    }

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

// ******************************** OptionDefListParser ************************
789
OptionDefListParser::OptionDefListParser(const std::string&,
790
791
                                         ParserContextPtr global_context)
    : storage_(global_context->option_defs_),
792
      global_context_(global_context) {
793
    if (!storage_) {
794
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
795
796
797
798
             << "storage may not be NULL");
    }
}

799
void
800
OptionDefListParser::build(ConstElementPtr option_def_list) {
801
    // Clear existing items in the storage.
802
803
804
805
806
    // We are going to replace all of them.
    storage_->clearItems();

    if (!option_def_list) {
        isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
807
808
                  << " option definitions is NULL ("
                  << option_def_list->getPosition() << ")");
809
810
811
812
    }

    BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
        boost::shared_ptr<OptionDefParser>
813
814
            parser(new OptionDefParser("single-option-def", storage_,
                                       global_context_));
815
816
817
818
819
820
821
822
        parser->build(option_def);
        parser->commit();
    }

    CfgMgr& cfg_mgr = CfgMgr::instance();
    cfg_mgr.deleteOptionDefs();

    // We need to move option definitions from the temporary
823
    // storage to the storage.
824
    std::list<std::string> space_names = storage_->getOptionSpaceNames();
825
826
827
828
829
830
831
832

    BOOST_FOREACH(std::string space_name, space_names) {
        BOOST_FOREACH(OptionDefinitionPtr def,
                    *(storage_->getItems(space_name))) {
            // All option definitions should be initialized to non-NULL
            // values. The validation is expected to be made by the
            // OptionDefParser when creating an option definition.
            assert(def);
833
834
835
836
837
838
839
840
841
            // The Config Manager may thrown an exception if the duplicated
            // definition is being added. Catch the exceptions here to and
            // append the position in the config.
            try {
                cfg_mgr.addOptionDef(def, space_name);
            } catch (const std::exception& ex) {
                isc_throw(DhcpConfigError, ex.what() << " ("
                          << option_def_list->getPosition() << ")");
            }
842
843
844
845
        }
    }
}

846
847
848
849
850
void
OptionDefListParser::commit() {
    // Do nothing.
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
851
//****************************** RelayInfoParser ********************************
852
853
RelayInfoParser::RelayInfoParser(const std::string&,
                                 const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
854
                                 const Option::Universe& family)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
855
856
857
    :storage_(relay_info), local_(isc::asiolink::IOAddress(
                                  family == Option::V4 ? "0.0.0.0" : "::")),
     string_values_(new StringStorage()), family_(family) {
858
    if (!relay_info) {
859
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
                  << "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
875
876
877
878
879
    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 "
880
881
                  "value: " << string_values_->getParam("ip-address")
                  << " (" << string_values_->getPosition("ip-address") << ")");
Tomek Mrugalski's avatar
Tomek Mrugalski committed
882
883
884
885
886
    }

    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
887
                  << "does not have IP address of expected family type: "
888
889
                  << (family_ == Option::V4 ? "IPv4" : "IPv6")
                  << " (" << string_values_->getPosition("ip-address") << ")");
Tomek Mrugalski's avatar
Tomek Mrugalski committed
890
    }
891

Tomek Mrugalski's avatar
Tomek Mrugalski committed
892
    local_.addr_ = *ip;
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
}

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

914
//****************************** PoolsListParser ********************************
915
PoolsListParser::PoolsListParser(const std::string&, PoolStoragePtr pools)
916
917
    :pools_(pools), local_pools_(new PoolStorage()) {
    if (!pools_) {
918
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
919
920
921
922
923
924
925
926
927
928
929
930
931
932
                  << "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);

933
934
        // Let's store the parser, but do not commit anything yet
        parsers_.push_back(parser);
935
936
937
938
    }
}

void PoolsListParser::commit() {
939
940
941
942
943
944
945

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

946
947
948
949
950
951
952
953
    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());
    }
}

954
955
956
957
958
//****************************** PoolParser ********************************
PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
        :pools_(pools) {

    if (!pools_) {
959
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
960
961
962
963
                  << "storage may not be NULL");
    }
}

964
void
965
PoolParser::build(ConstElementPtr pool_structure) {
966

967
    ConstElementPtr text_pool = pool_structure->get("pool");
968

969
970
971
972
973
    if (!text_pool) {
        isc_throw(DhcpConfigError, "Mandatory 'pool' entry missing in "
                  "definition: (" << text_pool->getPosition() << ")");
    }

974
975
976
977
    // 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();
978

979
980
981
    // first let's remove any whitespaces
    boost::erase_all(txt, " "); // space
    boost::erase_all(txt, "\t"); // tabulation
982

983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
    // 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() << ")");
1007
        }
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030

        PoolPtr pool(poolMaker(addr, len));
        local_pools_.push_back(pool);
        return;
    }

    // Is this min-max notation?
    pos = txt.find("-");
    if (pos != string::npos) {
        // using min-max notation
        isc::asiolink::IOAddress min(txt.substr(0,pos));
        isc::asiolink::IOAddress max(txt.substr(pos + 1));

        PoolPtr pool(poolMaker(min, max));
        local_pools_.push_back(pool);
        return;
    }

    isc_throw(DhcpConfigError, "invalid pool definition: "
              << text_pool->stringValue() <<
              ". There are two acceptable formats <min address-max address>"
              " or <prefix/len> ("
              << text_pool->getPosition() << ")");
1031
}
1032

1033
void
1034
PoolParser::commit() {
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
    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());
    }
}

//****************************** SubnetConfigParser *************************

1045
SubnetConfigParser::SubnetConfigParser(const std::string&,
1046
1047
                                       ParserContextPtr global_context,
                                       const isc::asiolink::IOAddress& default_addr)
1048
    : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
1049
    pools_(new PoolStorage()), options_(new OptionStorage()),
1050
1051
    global_context_(global_context),
    relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)) {
1052
1053
1054
    // The first parameter should always be "subnet", but we don't check
    // against that here in case some wants to reuse this parser somewhere.
    if (!global_context_) {
1055
        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
1056
1057
1058
1059
                 << "context storage may not be NULL");
    }
}

1060
void
1061
SubnetConfigParser::build(ConstElementPtr subnet) {
1062
    BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
1063
1064
1065
1066
1067
        ParserPtr parser;
        // When unsupported parameter is specified, the function called
        // below will thrown an exception. We have to catch this exception
        // to append the line number where the parameter is.
        try {
1068
            parser.reset(createSubnetConfigParser(param.first));
1069
1070
1071
1072
        } catch (const std::exception& ex) {
            isc_throw(DhcpConfigError, ex.what() << " ("
                      << param.second->getPosition() << ")");
        }
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
        parser->build(param.second);
        parsers_.push_back(parser);
    }

    // In order to create new subnet we need to get the data out
    // of the child parsers first. The only way to do it is to
    // invoke commit on them because it will make them write
    // parsed data into storages we have supplied.
    // Note that triggering commits on child parsers does not
    // affect global data because we supplied pointers to storages
    // local to this object. Thus, even if this method fails
    // later on, the configuration remains consistent.
    BOOST_FOREACH(ParserPtr parser, parsers_) {
        parser->commit();
    }

    // Create a subnet.
1090
1091
1092
1093
1094
1095
1096
    try {
        createSubnet();
    } catch (const std::exception& ex) {
        isc_throw(DhcpConfigError,
                  "subnet configuration failed (" << subnet->getPosition()
                  << "): " << ex.what());
    }
1097
1098
}

1099
1100
void
SubnetConfigParser::appendSubOptions(const std::string& option_space,
1101
                                     OptionPtr& option) {
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
    // Only non-NULL options are stored in option container.
    // If this option pointer is NULL this is a serious error.
    assert(option);

    OptionDefinitionPtr def;
    if (isServerStdOption(option_space, option->getType())) {
        def = getServerStdOptionDefinition(option->getType());
        // Definitions for some of the standard options hasn't been