Commit 590767d4 authored by Thomas Markwalder's avatar Thomas Markwalder

[#35,!517] Added hostname-char-set to hostname-char-replacement network/subnet scopes

src/lib/dhcpsrv/d2_client_cfg.*
    Added hostname-char-set and hostname-char-replacement to DdnsParms
    DdnsParams::getHostnameSanitizer()  - new function to return a sanitizer

src/lib/dhcpsrv/d2_client_mgr.h
    D2ClientMgr::adjustDomainName() - now gets sanitizer from ddns_params

src/lib/dhcpsrv/network.*
src/lib/dhcpsrv/parsers/base_network_parser.cc
src/lib/dhcpsrv/parsers/simple_parser4.cc
src/lib/dhcpsrv/parsers/simple_parser6.cc
    Added hostname-char-set and hostname-char-replacement to networks and subnets
parent f0d9fef2
......@@ -214,6 +214,23 @@ D2ClientConfig::toElement() const {
return (result);
}
str::StringSanitizerPtr
DdnsParams::getHostnameSanitizer() const {
str::StringSanitizerPtr sanitizer;
// If we have a local char_set we need to create the sanitizer.
if (!hostname_char_set_.empty()) {
try {
sanitizer.reset(new str::StringSanitizer(hostname_char_set_,
hostname_char_replacement_));
} catch (const std::exception& ex) {
isc_throw(BadValue, "hostname_char_set_: '" << hostname_char_set_ <<
"' is not a valid regular expression");
}
}
return (sanitizer);
}
std::ostream&
operator<<(std::ostream& os, const D2ClientConfig& config) {
os << config.toText();
......
......@@ -197,8 +197,6 @@ public:
/// @brief Validates member values.
///
/// Method is used by the constructor to validate member contents.
/// Should be called when parsing is complete to (re)compute
/// the hostname sanitizer.
///
/// @throw D2ClientError if given an invalid protocol or format.
virtual void validateContents();
......@@ -244,7 +242,8 @@ struct DdnsParams {
DdnsParams() :
enable_updates_(false), override_no_update_(false), override_client_update_(false),
replace_client_name_mode_(D2ClientConfig::RCM_NEVER),
generated_prefix_(""), qualifying_suffix_("") {};
generated_prefix_(""), qualifying_suffix_(""), hostname_char_set_(""),
hostname_char_replacement_("") {};
/// @brief Indicates whether or not DHCP DDNS updating is enabled.
bool enable_updates_;
......@@ -265,8 +264,24 @@ struct DdnsParams {
/// @brief Suffix Kea should use when to qualify partial domain-names.
std::string qualifying_suffix_;
/// @brief Pointer to compiled regular expression string sanitizer
util::str::StringSanitizerPtr hostname_sanitizer_;
/// @brief Regular expression describing invalid characters for client hostnames.
/// If empty, host name scrubbing should not be done.
std::string hostname_char_set_;
/// @brief A string to replace invalid characters when scrubbing hostnames.
/// Meaningful only if hostname_char_set_ is not empty.
std::string hostname_char_replacement_;
/// @brief Returns a regular expression string sanitizer
///
/// If hostname_char_set_ is not empty, then it is used in conjunction
/// hostname_char_replacment_ (which may be empty) to create and
/// return a StringSanitizer instance. Otherwise it will return
/// an empty pointer.
///
/// @return pointer to the StringSanitizer instance or an empty pointer
/// @throw BadValue if the compilation fails.
isc::util::str::StringSanitizerPtr getHostnameSanitizer() const;
};
/// @brief Defines a pointer for DdnsParams instances.
......
......@@ -482,7 +482,8 @@ D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddn
// Sanitize the name the client sent us, if we're configured to do so.
std::string client_name = fqdn.getDomainName();
if (ddns_params.hostname_sanitizer_) {
isc::util::str::StringSanitizerPtr sanitizer = ddns_params.getHostnameSanitizer();
if (sanitizer) {
// We need the raw text form, so we can replace escaped chars
dns::Name tmp(client_name);
std::string raw_name = tmp.toRawText();
......@@ -499,7 +500,7 @@ D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddn
ss << ".";
}
ss << ddns_params.hostname_sanitizer_->scrub(*label);
ss << sanitizer->scrub(*label);
}
client_name = ss.str();
......
......@@ -268,6 +268,14 @@ Network::toElement() const {
map->set("ddns-qualifying-suffix", Element::create(ddns_qualifying_suffix_));
}
if (!hostname_char_set_.unspecified()) {
map->set("hostname-char-set", Element::create(hostname_char_set_));
}
if (!hostname_char_replacement_.unspecified()) {
map->set("hostname-char-replacement", Element::create(hostname_char_replacement_));
}
return (map);
}
......
......@@ -206,7 +206,8 @@ public:
host_reservation_mode_(HR_ALL, true), cfg_option_(new CfgOption()),
calculate_tee_times_(), t1_percent_(), t2_percent_(),
ddns_send_updates_(), ddns_override_no_update_(), ddns_override_client_update_(),
ddns_replace_client_name_mode_(), ddns_generated_prefix_(), ddns_qualifying_suffix_() {
ddns_replace_client_name_mode_(), ddns_generated_prefix_(), ddns_qualifying_suffix_(),
hostname_char_set_(), hostname_char_replacement_() {
}
/// @brief Virtual destructor.
......@@ -529,14 +530,14 @@ public:
inheritance, "ddns-send-updates"));
}
/// @brief Sets new ddns-send-updates
/// @brief Sets new ddns-send-updates
///
/// @param ddns_send_updates_ New value to use.
void setDdnsSendUpdates(const util::Optional<bool>& ddns_send_updates) {
ddns_send_updates_ = ddns_send_updates;
}
/// @brief Returns ddns-override-no-update
/// @brief Returns ddns-override-no-update
///
/// @param inheritance inheritance mode to be used.
util::Optional<bool>
......@@ -545,14 +546,14 @@ public:
inheritance, "ddns-override-no-update"));
}
/// @brief Sets new ddns-override-no-update
/// @brief Sets new ddns-override-no-update
///
/// @param ddns_override_no_update New value to use.
void setDdnsOverrideNoUpdate(const util::Optional<bool>& ddns_override_no_update) {
ddns_override_no_update_ = ddns_override_no_update;
}
/// @brief Returns ddns-overridie-client-update
/// @brief Returns ddns-overridie-client-update
///
/// @param inheritance inheritance mode to be used.
util::Optional<bool>
......@@ -561,14 +562,14 @@ public:
inheritance, "ddns-override-client-update"));
}
/// @brief Sets new ddns-override-client-update
/// @brief Sets new ddns-override-client-update
///
/// @param ddns-override-client-update New value to use.
void setDdnsOverrideClientUpdate(const util::Optional<bool>& ddns_override_client_update) {
ddns_override_client_update_ = ddns_override_client_update;
}
/// @brief Returns ddns-replace-client-name-mode
/// @brief Returns ddns-replace-client-name-mode
///
/// @param inheritance inheritance mode to be used.
util::Optional<D2ClientConfig::ReplaceClientNameMode>
......@@ -578,7 +579,7 @@ public:
// Thus we call getProperty here without a global name to check if it
// is specified on network level only.
const util::Optional<D2ClientConfig::ReplaceClientNameMode>& mode
= getProperty<Network>(&Network::getDdnsReplaceClientNameMode,
= getProperty<Network>(&Network::getDdnsReplaceClientNameMode,
ddns_replace_client_name_mode_, inheritance);
// If it is not specified at network level we need this special
......@@ -604,15 +605,15 @@ public:
return (mode);
}
/// @brief Sets new ddns-replace-client-name-mode
/// @brief Sets new ddns-replace-client-name-mode
///
/// @param ddns_replace_client_name_mode New value to use.
void setDdnsReplaceClientNameMode(const util::Optional<D2ClientConfig::ReplaceClientNameMode>&
void setDdnsReplaceClientNameMode(const util::Optional<D2ClientConfig::ReplaceClientNameMode>&
ddns_replace_client_name_mode) {
ddns_replace_client_name_mode_ = ddns_replace_client_name_mode;
}
/// @brief Returns ddns-generated-prefix
/// @brief Returns ddns-generated-prefix
///
/// @param inheritance inheritance mode to be used.
util::Optional<std::string>
......@@ -628,7 +629,7 @@ public:
ddns_generated_prefix_ = ddns_generated_prefix;
}
/// @brief Returns ddns-qualifying-suffix
/// @brief Returns ddns-qualifying-suffix
///
/// @param inheritance inheritance mode to be used.
util::Optional<std::string>
......@@ -637,14 +638,40 @@ public:
inheritance, "ddns-qualifying-suffix"));
}
/// @brief Sets new ddns-qualifying-suffix
/// @brief Sets new ddns-qualifying-suffix
///
/// @param ddns_qualifying_suffix New value to use.
void setDdnsQualifyingSuffix(const util::Optional<std::string>& ddns_qualifying_suffix) {
ddns_qualifying_suffix_ = ddns_qualifying_suffix;
}
/// @brief Return the char set regexp used to sanitize client hostnames.
util::Optional<std::string>
getHostnameCharSet(const Inheritance& inheritance = Inheritance::ALL) const {
return (getProperty<Network>(&Network::getHostnameCharSet, hostname_char_set_,
inheritance, "hostname-char-set"));
}
/// @brief Sets new hostname-char-set
///
/// @param hostname_char_set New value to use.
void setHostnameCharSet(const util::Optional<std::string>& hostname_char_set) {
hostname_char_set_ = hostname_char_set;
}
/// @brief Return the invalid char replacement used to sanitize client hostnames.
util::Optional<std::string>
getHostnameCharReplacement(const Inheritance& inheritance = Inheritance::ALL) const {
return (getProperty<Network>(&Network::getHostnameCharReplacement, hostname_char_replacement_,
inheritance, "hostname-char-replacement"));
}
/// @brief Sets new hostname-char-replacement
///
/// @param hostname_char_replacement New value to use.
void setHostnameCharReplacement(const util::Optional<std::string>& hostname_char_replacement) {
hostname_char_replacement_ = hostname_char_replacement;
}
/// @brief Unparses network object.
///
......@@ -928,6 +955,14 @@ protected:
/// @brief Suffix Kea should use when to qualify partial domain-names.
util::Optional<std::string> ddns_qualifying_suffix_;
/// @brief Regular expression describing invalid characters for client
/// hostnames.
util::Optional<std::string> hostname_char_set_;
/// @brief A string to replace invalid characters when scrubbing hostnames.
/// Meaningful only if hostname_char_set_ is not empty.
util::Optional<std::string> hostname_char_replacement_;
/// @brief Pointer to another network that this network belongs to.
///
/// The most common case is that this instance is a subnet which belongs
......
......@@ -8,6 +8,7 @@
#include <dhcpsrv/triplet.h>
#include <dhcpsrv/parsers/base_network_parser.h>
#include <util/optional.h>
#include <util/strutil.h>
using namespace isc::data;
using namespace isc::util;
......@@ -186,7 +187,7 @@ BaseNetworkParser::parseDdnsParams(const data::ConstElementPtr& network_data,
if (network_data->contains("ddns-replace-client-name")) {
network->setDdnsReplaceClientNameMode(getAndConvert<D2ClientConfig::ReplaceClientNameMode,
D2ClientConfig::stringToReplaceClientNameMode>
(network_data, "ddns-replace-client-name",
(network_data, "ddns-replace-client-name",
"ReplaceClientName mode"));
}
......@@ -197,6 +198,31 @@ BaseNetworkParser::parseDdnsParams(const data::ConstElementPtr& network_data,
if (network_data->contains("ddns-qualifying-suffix")) {
network->setDdnsQualifyingSuffix(getString(network_data, "ddns-qualifying-suffix"));
}
std::string hostname_char_set;
if (network_data->contains("hostname-char-set")) {
hostname_char_set = getString(network_data, "hostname-char-set");
network->setHostnameCharSet(hostname_char_set);
}
std::string hostname_char_replacement;
if (network_data->contains("hostname-char-replacement")) {
hostname_char_replacement = getString(network_data, "hostname-char-replacement");
network->setHostnameCharReplacement(hostname_char_replacement);
}
// We need to validate santizer values here so we can detect problems and
// cause a configuration. We dont' retain the compilation because it's not
// something we can inherit.
if (!hostname_char_set.empty()) {
try {
str::StringSanitizerPtr sanitizer(new str::StringSanitizer(hostname_char_set,
hostname_char_replacement));
} catch (const std::exception& ex) {
isc_throw(BadValue, "hostname-char-set '" << hostname_char_set
<< "' is not a valid regular expression");
}
}
}
} // end of namespace isc::dhcp
......
......@@ -796,7 +796,6 @@ public:
/// The elements currently supported are (see isc::dhcp::D2ClientConfig
/// for details on each):
/// -# enable-updates
/// -# qualifying-suffix
/// -# server-ip
/// -# server-port
/// -# sender-ip
......@@ -804,10 +803,6 @@ public:
/// -# max-queue-size
/// -# ncr-protocol
/// -# ncr-format
/// -# override-no-update
/// -# override-client-update
/// -# replace-client-name
/// -# generated-prefix
///
/// @return returns a pointer to newly created D2ClientConfig.
D2ClientConfigPtr parse(isc::data::ConstElementPtr d2_client_cfg);
......
......@@ -111,7 +111,9 @@ const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = {
{ "ddns-replace-client-name", Element::string, "never" },
{ "ddns-generated-prefix", Element::string, "myhost" },
// TKM should this still be true? qualifying-suffix has no default ??
{ "ddns-generated-suffix", Element::string, "" }
{ "ddns-generated-suffix", Element::string, "" },
{ "hostname-char-set", Element::string, "" },
{ "hostname-char-replacement", Element::string, "" },
};
/// @brief This table defines all option definition parameters.
......@@ -211,6 +213,8 @@ const SimpleKeywords SimpleParser4::SUBNET4_PARAMETERS = {
{ "ddns-replace-client-name", Element::string },
{ "ddns-generated-prefix", Element::string },
{ "ddns-qualifying-suffix", Element::string },
{ "hostname-char-set", Element::string },
{ "hostname-char-replacement", Element::string },
{ "metadata", Element::map },
};
......@@ -275,7 +279,9 @@ const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = {
"ddns-override-client-update",
"ddns-replace-client-name",
"ddns-generated-prefix",
"ddns-qualifying-suffix"
"ddns-qualifying-suffix",
"hostname-char-set",
"hostname-char-replacement"
};
/// @brief This table defines all pool parameters.
......@@ -328,6 +334,8 @@ const SimpleKeywords SimpleParser4::SHARED_NETWORK4_PARAMETERS = {
{ "ddns-replace-client-name", Element::string },
{ "ddns-generated-prefix", Element::string },
{ "ddns-qualifying-suffix", Element::string },
{ "hostname-char-set", Element::string },
{ "hostname-char-replacement", Element::string },
{ "metadata", Element::map },
};
......
......@@ -106,7 +106,9 @@ const SimpleDefaults SimpleParser6::GLOBAL6_DEFAULTS = {
{ "ddns-replace-client-name", Element::string, "never" },
{ "ddns-generated-prefix", Element::string, "myhost" },
// TKM should this still be true? qualifying-suffix has no default ??
{ "ddns-generated-suffix", Element::string, "" }
{ "ddns-generated-suffix", Element::string, "" },
{ "hostname-char-set", Element::string, "" },
{ "hostname-char-replacement", Element::string, "" },
};
/// @brief This table defines all option definition parameters.
......@@ -204,6 +206,8 @@ const SimpleKeywords SimpleParser6::SUBNET6_PARAMETERS = {
{ "ddns-replace-client-name", Element::string },
{ "ddns-generated-prefix", Element::string },
{ "ddns-qualifying-suffix", Element::string },
{ "hostname-char-set", Element::string },
{ "hostname-char-replacement", Element::string },
{ "metadata", Element::map }
};
......@@ -256,7 +260,9 @@ const ParamsList SimpleParser6::INHERIT_TO_SUBNET6 = {
"ddns-override-client-update",
"ddns-replace-client-name",
"ddns-generated-prefix",
"ddns-qualifying-suffix"
"ddns-qualifying-suffix",
"hostname-char-set",
"hostname-char-replacement"
};
/// @brief This table defines all pool parameters.
......@@ -328,6 +334,8 @@ const SimpleKeywords SimpleParser6::SHARED_NETWORK6_PARAMETERS = {
{ "ddns-replace-client-name", Element::string },
{ "ddns-generated-prefix", Element::string },
{ "ddns-qualifying-suffix", Element::string },
{ "hostname-char-set", Element::string },
{ "hostname-char-replacement", Element::string },
{ "metadata", Element::map }
};
......
......@@ -589,6 +589,8 @@ DdnsParamsPtr SrvConfig::getDdnsParams(const Subnet& subnet) const {
params->replace_client_name_mode_= subnet.getDdnsReplaceClientNameMode().get();
params->generated_prefix_ = subnet.getDdnsGeneratedPrefix().get();
params->qualifying_suffix_ = subnet.getDdnsQualifyingSuffix().get();
params->hostname_char_set_ = subnet.getHostnameCharSet().get();
params->hostname_char_replacement_ = subnet.getHostnameCharReplacement().get();
return params;
}
......
......@@ -205,6 +205,8 @@ TEST(CfgSharedNetworks4Test, unparse) {
network1->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
network1->setDdnsGeneratedPrefix("prefix");
network1->setDdnsQualifyingSuffix("example.com.");
network1->setHostnameCharSet("[^A-Z]");
network1->setHostnameCharReplacement("x");
network2->setIface("eth1");
network2->setT1(Triplet<uint32_t>(100));
......@@ -257,7 +259,9 @@ TEST(CfgSharedNetworks4Test, unparse) {
" \"relay\": { \"ip-addresses\": [ \"198.16.1.1\", \"198.16.1.2\" ] },\n"
" \"subnet4\": [ ],\n"
" \"t1-percent\": .35,\n"
" \"t2-percent\": .655\n"
" \"t2-percent\": .655,\n"
" \"hostname-char-replacement\": \"x\",\n"
" \"hostname-char-set\": \"[^A-Z]\"\n"
" }\n"
"]\n";
......
......@@ -206,6 +206,8 @@ TEST(CfgSharedNetworks6Test, unparse) {
network1->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
network1->setDdnsGeneratedPrefix("prefix");
network1->setDdnsQualifyingSuffix("example.com.");
network1->setHostnameCharSet("[^A-Z]");
network1->setHostnameCharReplacement("x");
network2->setIface("eth1");
network2->setT1(Triplet<uint32_t>(100));
......@@ -264,7 +266,9 @@ TEST(CfgSharedNetworks6Test, unparse) {
" \"relay\": { \"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ] },\n"
" \"subnet6\": [ ],\n"
" \"t1-percent\": .35,\n"
" \"t2-percent\": .655\n"
" \"t2-percent\": .655,\n"
" \"hostname-char-replacement\": \"x\",\n"
" \"hostname-char-set\": \"[^A-Z]\"\n"
" }\n"
"]\n";
......
......@@ -20,6 +20,7 @@
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
#include <testutils/gtest_utils.h>
#include <testutils/test_to_element.h>
#include <util/doubles.h>
......@@ -1034,6 +1035,14 @@ TEST(CfgSubnets4Test, unparseSubnet) {
subnet3->setSname("frog");
subnet3->setFilename("/dev/null");
subnet3->setValid(Triplet<uint32_t>(100, 200, 300));
subnet3->setDdnsSendUpdates(true);
subnet3->setDdnsOverrideNoUpdate(true);
subnet3->setDdnsOverrideClientUpdate(true);
subnet3->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
subnet3->setDdnsGeneratedPrefix("prefix");
subnet3->setDdnsQualifyingSuffix("example.com.");
subnet3->setHostnameCharSet("[^A-Z]");
subnet3->setHostnameCharReplacement("x");
data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
subnet1->setContext(ctx1);
......@@ -1100,8 +1109,17 @@ TEST(CfgSubnets4Test, unparseSubnet) {
" \"require-client-classes\": [ \"foo\", \"bar\" ],\n"
" \"calculate-tee-times\": true,\n"
" \"t1-percent\": 0.50,\n"
" \"t2-percent\": 0.65\n"
" \"t2-percent\": 0.65,\n"
" \"ddns-generated-prefix\": \"prefix\",\n"
" \"ddns-override-client-update\": true,\n"
" \"ddns-override-no-update\": true,\n"
" \"ddns-qualifying-suffix\": \"example.com.\",\n"
" \"ddns-replace-client-name\": \"always\",\n"
" \"ddns-send-updates\": true,\n"
" \"hostname-char-replacement\": \"x\",\n"
" \"hostname-char-set\": \"[^A-Z]\"\n"
"} ]\n";
runToElementTest<CfgSubnets4>(expected, cfg);
}
......@@ -1541,4 +1559,61 @@ TEST(CfgSubnets4Test, validLifetimeValidation) {
}
}
// This test verifies the Subnet4 parser's validation logic for
// hostname sanitizer values.
TEST(CfgSubnets4Test, hostnameSanitizierValidation) {
// First we create a set of elements that provides all
// required for a Subnet4.
std::string json =
" {"
" \"id\": 1,\n"
" \"subnet\": \"10.1.2.0/24\", \n"
" \"interface\": \"\", \n"
" \"renew-timer\": 100, \n"
" \"rebind-timer\": 200, \n"
" \"match-client-id\": false, \n"
" \"authoritative\": false, \n"
" \"next-server\": \"\", \n"
" \"server-hostname\": \"\", \n"
" \"boot-file-name\": \"\", \n"
" \"client-class\": \"\", \n"
" \"require-client-classes\": [] \n,"
" \"reservation-mode\": \"all\", \n"
" \"4o6-interface\": \"\", \n"
" \"4o6-interface-id\": \"\", \n"
" \"4o6-subnet\": \"\" \n"
" }";
data::ElementPtr elems;
ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
<< "invalid JSON:" << json << "\n test is broken";
{
SCOPED_TRACE("invalid regular expression");
data::ElementPtr copied = data::copy(elems);
copied->set("hostname-char-set", data::Element::create("^[A-"));
copied->set("hostname-char-replacement", data::Element::create("x"));
Subnet4Ptr subnet;
Subnet4ConfigParser parser;
EXPECT_THROW_MSG(subnet = parser.parse(copied), DhcpConfigError,
"subnet configuration failed: hostname-char-set "
"'^[A-' is not a valid regular expression");
}
{
SCOPED_TRACE("valid regular expression");
data::ElementPtr copied = data::copy(elems);
copied->set("hostname-char-set", data::Element::create("^[A-Z]"));
copied->set("hostname-char-replacement", data::Element::create("x"));
Subnet4Ptr subnet;
Subnet4ConfigParser parser;
ASSERT_NO_THROW(subnet = parser.parse(copied));
EXPECT_EQ("^[A-Z]", subnet->getHostnameCharSet().get());
EXPECT_EQ("x", subnet->getHostnameCharReplacement().get());
}
}
} // end of anonymous namespace
......@@ -15,6 +15,7 @@
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
#include <testutils/gtest_utils.h>
#include <testutils/test_to_element.h>
#include <util/doubles.h>
......@@ -632,6 +633,14 @@ TEST(CfgSubnets6Test, unparseSubnet) {
subnet3->setT2Percent(0.65);
subnet3->setValid(Triplet<uint32_t>(100, 200, 300));
subnet3->setPreferred(Triplet<uint32_t>(50, 100, 150));
subnet3->setDdnsSendUpdates(true);
subnet3->setDdnsOverrideNoUpdate(true);
subnet3->setDdnsOverrideClientUpdate(true);
subnet3->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
subnet3->setDdnsGeneratedPrefix("prefix");
subnet3->setDdnsQualifyingSuffix("example.com.");
subnet3->setHostnameCharSet("[^A-Z]");
subnet3->setHostnameCharReplacement("x");
data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
subnet1->setContext(ctx1);
......@@ -694,8 +703,17 @@ TEST(CfgSubnets6Test, unparseSubnet) {
" \"require-client-classes\": [ \"foo\", \"bar\" ],\n"
" \"calculate-tee-times\": true,\n"
" \"t1-percent\": 0.50,\n"
" \"t2-percent\": 0.65\n"
" \"t2-percent\": 0.65,\n"
" \"ddns-generated-prefix\": \"prefix\",\n"
" \"ddns-override-client-update\": true,\n"
" \"ddns-override-no-update\": true,\n"
" \"ddns-qualifying-suffix\": \"example.com.\",\n"
" \"ddns-replace-client-name\": \"always\",\n"
" \"ddns-send-updates\": true,\n"
" \"hostname-char-replacement\": \"x\",\n"
" \"hostname-char-set\": \"[^A-Z]\"\n"
"} ]\n";
runToElementTest<CfgSubnets6>(expected, cfg);
}
......@@ -1329,4 +1347,54 @@ TEST(CfgSubnets6Test, preferredLifetimeValidation) {
}
}
// This test verifies the Subnet6 parser's validation logic for
// hostname sanitizer values.
TEST(CfgSubnets6Test, hostnameSanitizierValidation) {
// First we create a set of elements that provides all
// required for a Subnet6.
std::string json =
" {"
" \"id\": 1,\n"
" \"subnet\": \"2001:db8:1::/64\", \n"
" \"interface\": \"\", \n"
" \"renew-timer\": 100, \n"
" \"rebind-timer\": 200, \n"
" \"valid-lifetime\": 300, \n"
" \"client-class\": \"\", \n"
" \"require-client-classes\": [] \n,"
" \"reservation-mode\": \"all\" \n"
" }";
data::ElementPtr elems;
ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
<< "invalid JSON:" << json << "\n test is broken";
{
SCOPED_TRACE("invalid regular expression");
data::ElementPtr copied = data::copy(elems);
copied->set("hostname-char-set", data::Element::create("^[A-"));
copied->set("hostname-char-replacement", data::Element::create("x"));
Subnet6Ptr subnet;
Subnet6ConfigParser parser;
EXPECT_THROW_MSG(subnet = parser.parse(copied), DhcpConfigError,
"subnet configuration failed: hostname-char-set "
"'^[A-' is not a valid regular expression");
}
{
SCOPED_TRACE("valid regular expression");
data::ElementPtr copied = data::copy(elems);
copied->set("hostname-char-set", data::Element::create("^[A-Z]"));
copied->set("hostname-char-replacement", data::Element::create("x"));
Subnet6Ptr subnet;
Subnet6ConfigParser parser;
ASSERT_NO_THROW(subnet = parser.parse(copied));
EXPECT_EQ("^[A-Z]", subnet->getHostnameCharSet().get());
EXPECT_EQ("x", subnet->getHostnameCharReplacement().get());
}
}
} // end of anonymous namespace
......@@ -1000,10 +1000,13 @@ TEST(D2ClientMgr, sanitizeFqdnV4) {
ddns_params.replace_client_name_mode_ = D2ClientConfig::RCM_NEVER;
ddns_params.generated_prefix_ = "prefix";
ddns_params.qualifying_suffix_ = "suffix.com";
ddns_params.hostname_char_set_ = "[^A-Za-z0-9-]";
ddns_params.hostname_char_replacement_ = "x";
// Create and assign the sanitizer.
ASSERT_NO_THROW(ddns_params.hostname_sanitizer_.reset(new
isc::util::str::StringSanitizer("[^A-Za-z0-9-]", "x")));
// Get the sanitizer.
str::StringSanitizerPtr hostname_sanitizer;
ASSERT_NO_THROW(hostname_sanitizer = ddns_params.getHostnameSanitizer());
ASSERT_TRUE(hostname_sanitizer);