diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcpsrv/parsers/option_data_parser.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcpsrv/parsers/option_data_parser.cc')
-rw-r--r-- | src/lib/dhcpsrv/parsers/option_data_parser.cc | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.cc b/src/lib/dhcpsrv/parsers/option_data_parser.cc new file mode 100644 index 0000000..6a321cd --- /dev/null +++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc @@ -0,0 +1,479 @@ +// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_space.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <dhcpsrv/parsers/simple_parser4.h> +#include <dhcpsrv/parsers/simple_parser6.h> +#include <util/encode/hex.h> +#include <util/strutil.h> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> +#include <limits> +#include <vector> + +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +// **************************** OptionDataParser ************************* + +OptionDataParser::OptionDataParser(const uint16_t address_family, + CfgOptionDefPtr cfg_option_def) + : address_family_(address_family), cfg_option_def_(cfg_option_def) { +} + +std::pair<OptionDescriptor, std::string> +OptionDataParser::parse(isc::data::ConstElementPtr single_option) { + + // Check parameters. + if (address_family_ == AF_INET) { + checkKeywords(SimpleParser4::OPTION4_PARAMETERS, single_option); + } else { + checkKeywords(SimpleParser6::OPTION6_PARAMETERS, single_option); + } + + // Try to create the option instance. + std::pair<OptionDescriptor, std::string> opt = createOption(single_option); + + if (!opt.first.option_) { + // Should never happen (@todo: update message) + isc_throw(isc::InvalidOperation, + "parser logic error: no option has been configured and" + " thus there is nothing to commit. Has build() been called?"); + } + + return (opt); +} + +Optional<uint32_t> +OptionDataParser::extractCode(ConstElementPtr parent) const { + uint32_t code; + try { + code = getInteger(parent, "code"); + + } catch (const std::exception&) { + // The code parameter was not found. Return an unspecified + // value. + return (Optional<uint32_t>()); + } + + if (address_family_ == AF_INET && + code > std::numeric_limits<uint8_t>::max()) { + isc_throw(DhcpConfigError, "invalid option code '" << code + << "', it must not be greater than '" + << static_cast<int>(std::numeric_limits<uint8_t>::max()) + << "' (" << getPosition("code", parent) + << ")"); + + } else if (address_family_ == AF_INET6 && + code > std::numeric_limits<uint16_t>::max()) { + isc_throw(DhcpConfigError, "invalid option code '" << code + << "', it must not exceed '" + << std::numeric_limits<uint16_t>::max() + << "' (" << getPosition("code", parent) + << ")"); + + } + + return (Optional<uint32_t>(code)); +} + +Optional<std::string> +OptionDataParser::extractName(ConstElementPtr parent) const { + std::string name; + try { + name = getString(parent, "name"); + + } catch (...) { + return (Optional<std::string>()); + } + + if (name.find(" ") != std::string::npos) { + isc_throw(DhcpConfigError, "invalid option name '" << name + << "', space character is not allowed (" + << getPosition("name", parent) << ")"); + } + + return (Optional<std::string>(name)); +} + +Optional<std::string> +OptionDataParser::extractData(ConstElementPtr parent) const { + std::string data; + try { + data = getString(parent, "data"); + + } catch (...) { + // The "data" parameter was not found. Return an empty value. + return (Optional<std::string>()); + } + + return (Optional<std::string>(data)); +} + +Optional<bool> +OptionDataParser::extractCSVFormat(ConstElementPtr parent) const { + bool csv_format = true; + try { + csv_format = getBoolean(parent, "csv-format"); + + } catch (...) { + return (Optional<bool>()); + } + + return (Optional<bool>(csv_format)); +} + +std::string +OptionDataParser::extractSpace(ConstElementPtr parent) const { + std::string space = address_family_ == AF_INET ? + DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE; + try { + space = getString(parent, "space"); + + } catch (...) { + return (space); + } + + try { + if (!OptionSpace::validateName(space)) { + isc_throw(DhcpConfigError, "invalid option space name '" + << space << "'"); + } + + if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) { + isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE + << "' option space name is reserved for DHCPv4 server"); + + } else if ((space == DHCP6_OPTION_SPACE) && + (address_family_ == AF_INET)) { + isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE + << "' option space name is reserved for DHCPv6 server"); + } + + } catch (const std::exception& ex) { + // Append position of the option space parameter. + isc_throw(DhcpConfigError, ex.what() << " (" + << getPosition("space", parent) << ")"); + } + + return (space); +} + +Optional<bool> +OptionDataParser::extractPersistent(ConstElementPtr parent) const { + bool persist = false; + try { + persist = getBoolean(parent, "always-send"); + + } catch (...) { + return (Optional<bool>()); + } + + return (Optional<bool>(persist)); +} + +Optional<bool> +OptionDataParser::extractCancelled(ConstElementPtr parent) const { + bool cancel = false; + try { + cancel = getBoolean(parent, "never-send"); + + } catch (...) { + return (Optional<bool>()); + } + + return (Optional<bool>(cancel)); +} + +OptionDefinitionPtr +OptionDataParser::findOptionDefinition(const std::string& option_space, + const Optional<uint32_t>& option_code, + const Optional<std::string>& option_name) const { + OptionDefinitionPtr def; + if (cfg_option_def_) { + // Check if the definition was given in the constructor + if (option_code.unspecified()) { + def = cfg_option_def_->get(option_space, option_name); + } else { + def = cfg_option_def_->get(option_space, option_code); + } + } + + if (!def) { + // Check if this is a standard option. + if (option_code.unspecified()) { + def = LibDHCP::getOptionDef(option_space, option_name); + } else { + def = LibDHCP::getOptionDef(option_space, option_code); + } + } + + if (!def) { + // Check if this is a vendor-option. If it is, get vendor-specific + // definition. + uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space); + if (vendor_id) { + const Option::Universe u = address_family_ == AF_INET ? + Option::V4 : Option::V6; + if (option_code.unspecified()) { + def = LibDHCP::getVendorOptionDef(u, vendor_id, option_name); + } else { + def = LibDHCP::getVendorOptionDef(u, vendor_id, option_code); + } + } + } + + if (!def) { + // Check if this is an option specified by a user. We used to + // check that in the staging configuration, but when the configuration + // changes are caused by a command the staging configuration doesn't + // exist. What is always available is the container holding runtime + // option definitions in LibDHCP. It holds option definitions from + // the staging configuration in case of the full reconfiguration or + // the definitions from the current configuration in case there is + // no staging configuration (after configuration commit). In other + // words, runtime options are always the ones that we need here. + if (option_code.unspecified()) { + def = LibDHCP::getRuntimeOptionDef(option_space, option_name); + } else { + def = LibDHCP::getRuntimeOptionDef(option_space, option_code); + } + } + + if (!def) { + // Finish by last resort definitions. + if (option_code.unspecified()) { + def = LibDHCP::getLastResortOptionDef(option_space, option_name); + } else { + def = LibDHCP::getLastResortOptionDef(option_space, option_code); + } + } + + return (def); +} + +std::pair<OptionDescriptor, std::string> +OptionDataParser::createOption(ConstElementPtr option_data) { + const Option::Universe universe = address_family_ == AF_INET ? + Option::V4 : Option::V6; + + Optional<uint32_t> code_param = extractCode(option_data); + Optional<std::string> name_param = extractName(option_data); + Optional<bool> csv_format_param = extractCSVFormat(option_data); + Optional<bool> persist_param = extractPersistent(option_data); + Optional<bool> cancel_param = extractCancelled(option_data); + Optional<std::string> data_param = extractData(option_data); + std::string space_param = extractSpace(option_data); + ConstElementPtr user_context = option_data->get("user-context"); + + // Require that option code or option name is specified. + if (code_param.unspecified() && name_param.unspecified()) { + isc_throw(DhcpConfigError, "option data configuration requires one of" + " 'code' or 'name' parameters to be specified" + << " (" << option_data->getPosition() << ")"); + } + + // Try to find a corresponding option definition using option code or + // option name. + OptionDefinitionPtr def = findOptionDefinition(space_param, code_param, name_param); + + // If there is no definition, the user must not explicitly enable the + // use of csv-format. + if (!def) { + // If explicitly requested that the CSV format is to be used, + // the option definition is a must. + if (!csv_format_param.unspecified() && csv_format_param) { + isc_throw(DhcpConfigError, "definition for the option '" + << space_param << "." << name_param + << "' having code '" << code_param + << "' does not exist (" + << getPosition("name", option_data) + << ")"); + + // If there is no option definition and the option code is not specified + // we have no means to find the option code. + } else if (!name_param.unspecified() && code_param.unspecified()) { + isc_throw(DhcpConfigError, "definition for the option '" + << space_param << "." << name_param + << "' does not exist (" + << getPosition("name", option_data) + << ")"); + } + } else { + // Option name is specified it should match the name in the definition. + if (!name_param.unspecified() && (def->getName() != name_param.get())) { + isc_throw(DhcpConfigError, "specified option name '" + << name_param << "' does not match the " + << "option definition: '" << space_param + << "." << def->getName() << "' (" + << getPosition("name", option_data) + << ")"); + } + } + + // No data and cancelled is a supported special case. + if (!cancel_param.unspecified() && cancel_param && + data_param.unspecified()) { + uint16_t code; + if (def) { + code = def->getCode(); + } else { + code = static_cast<uint16_t>(code_param); + } + OptionPtr option(new Option(universe, code)); + bool persistent = !persist_param.unspecified() && persist_param; + OptionDescriptor desc(option, persistent, true, "", user_context); + return (make_pair(desc, space_param)); + } + + // Transform string of hexadecimal digits into binary format. + std::vector<uint8_t> binary; + std::vector<std::string> data_tokens; + + // If the definition is available and csv-format hasn't been explicitly + // disabled, we will parse the data as comma separated values. + if (def && (csv_format_param.unspecified() || csv_format_param)) { + // 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. + // It is the only usage of the escape option: this allows + // to embed commas in individual values and to return + // for instance a string value with embedded commas. + data_tokens = isc::util::str::tokens(data_param, ",", true); + + } else { + // Try to convert the values in quotes into a vector of ASCII codes. + // If the identifier lacks opening and closing quote, this will return + // an empty value, in which case we'll try to decode it as a string of + // hexadecimal digits. + try { + binary = util::str::quotedStringToBinary(data_param); + if (binary.empty()) { + util::str::decodeFormattedHexString(data_param, binary); + } + } catch (...) { + isc_throw(DhcpConfigError, "option data is not a valid" + << " string of hexadecimal digits: " << data_param + << " (" + << getPosition("data", option_data) + << ")"); + } + } + + OptionDescriptor desc(false, false); + + if (!def) { + // @todo We have a limited set of option definitions initialized at + // the moment. In the future we want to initialize option definitions + // for all options. Consequently an error will be issued if an option + // definition does not exist for a particular option code. For now it is + // ok to create generic option if definition does not exist. + OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param), + binary)); + + desc.option_ = option; + desc.persistent_ = !persist_param.unspecified() && persist_param; + desc.cancelled_ = !cancel_param.unspecified() && cancel_param; + } else { + // Option definition has been found so let's use it to create + // an instance of our option. + try { + bool use_csv = csv_format_param.unspecified() || csv_format_param; + OptionPtr option = use_csv ? + def->optionFactory(universe, def->getCode(), data_tokens) : + def->optionFactory(universe, def->getCode(), binary); + desc.option_ = option; + desc.persistent_ = !persist_param.unspecified() && persist_param; + desc.cancelled_ = !cancel_param.unspecified() && cancel_param; + if (use_csv) { + desc.formatted_value_ = data_param; + } + } catch (const isc::Exception& ex) { + isc_throw(DhcpConfigError, "option data does not match" + << " option definition (space: " << space_param + << ", code: " << def->getCode() << "): " + << ex.what() << " (" + << getPosition("data", option_data) + << ")"); + } + } + + // Check PAD and END in (and only in) dhcp4 space. + if (space_param == DHCP4_OPTION_SPACE) { + if (desc.option_->getType() == DHO_PAD) { + isc_throw(DhcpConfigError, "invalid option code '0': " + << "reserved for PAD (" + << option_data->getPosition() << ")"); + } else if (desc.option_->getType() == DHO_END) { + isc_throw(DhcpConfigError, "invalid option code '255': " + << "reserved for END (" + << option_data->getPosition() << ")"); + } + } + + // For dhcp6 space the value 0 is reserved. + if (space_param == DHCP6_OPTION_SPACE) { + if (desc.option_->getType() == 0) { + isc_throw(DhcpConfigError, "invalid option code '0': " + << "reserved value (" + << option_data->getPosition() << ")"); + } + } + + + // Add user context + if (user_context) { + desc.setContext(user_context); + } + + // All went good, so we can set the option space name. + return (make_pair(desc, space_param)); +} + +// **************************** OptionDataListParser ************************* +OptionDataListParser::OptionDataListParser(//const std::string&, + //const CfgOptionPtr& cfg, + const uint16_t address_family, + CfgOptionDefPtr cfg_option_def) + : address_family_(address_family), cfg_option_def_(cfg_option_def) { +} + + +void OptionDataListParser::parse(const CfgOptionPtr& cfg, + isc::data::ConstElementPtr option_data_list, + bool encapsulate) { + auto option_parser = createOptionDataParser(); + BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) { + std::pair<OptionDescriptor, std::string> option = + option_parser->parse(data); + // Use the option description to keep the formatted value + cfg->add(option.first, option.second); + if (encapsulate) { + cfg->encapsulate(); + } + } +} + +boost::shared_ptr<OptionDataParser> +OptionDataListParser::createOptionDataParser() const { + auto parser = boost::make_shared<OptionDataParser>(address_family_, cfg_option_def_); + return (parser); +} + +} // end of namespace isc::dhcp +} // end of namespace isc |