diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 1deb2e6074..b359d09616 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -164,6 +164,20 @@ This debug message is issued when the server starts processing the Hostname option sent in the client's query. The argument includes the client and transaction identification information. +% DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY %1: sanitizing client's Hostname option '%2' yielded an empty string +Logged at debug log level 50. +This debug message is issued when the result of sanitizing the +hostname option(12) sent by the client is an empty string. When this occurs +the server will ignore the hostname option. The arguments include the +client and the hostname option it sent. + +% DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY %1: sanitizing client's FQDN option '%2' yielded an empty string +Logged at debug log level 50. +This debug message is issued when the result of sanitizing the +FQDN option(81) sent by the client is an empty string. When this occurs +the server will ignore the FQDN option. The arguments include the +client and the FQDN option it sent. + % DHCP4_CLIENT_NAME_PROC_FAIL %1: failed to process the fqdn or hostname sent by a client: %2 Logged at debug log level 55. This debug message is issued when the DHCP server was unable to process the diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 0701ed41e9..a6be662889 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -2714,8 +2714,15 @@ Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) { } else { // Adjust the domain name based on domain name value and type sent by the // client and current configuration. - d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, - *(ex.getContext()->getDdnsParams())); + try { + d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, + *(ex.getContext()->getDdnsParams())); + } catch (const FQDNScrubbedEmpty& scrubbed) { + LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY) + .arg(ex.getQuery()->getLabel()) + .arg(scrubbed.what()); + return; + } } // Add FQDN option to the response message. Note that, there may be some @@ -2857,7 +2864,15 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) { ex.getContext()->getDdnsParams()->getHostnameSanitizer(); if (sanitizer) { - hostname = sanitizer->scrub(hostname); + auto tmp = sanitizer->scrub(hostname); + if (tmp.empty()) { + LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY) + .arg(ex.getQuery()->getLabel()) + .arg(hostname); + return; + } + + hostname = tmp; } // Convert hostname to lower case. diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index fff50ed367..79fc984ff5 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -1167,3 +1167,10 @@ such modification. The clients will remember previous server-id, and will use it to extend their leases. As a result, they will have to go through a rebinding phase to re-acquire their leases and associate them with a new server id. + +% DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY %1: sanitizing client's FQDN option '%2' yielded an empty string +Logged at debug log level 50. +This debug message is issued when the result of sanitizing the +FQDN option(39) sent by the client is an empty string. When this occurs +the server will ignore the FQDN option. The arguments include the +client and the FQDN option it sent. diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 417960b126..f999c3178f 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -2332,7 +2332,14 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, } else { // Adjust the domain name based on domain name value and type sent by // the client and current configuration. - d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, *ddns_params); + try { + d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, *ddns_params); + } catch(const FQDNScrubbedEmpty& scrubbed) { + LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY) + .arg(question->getLabel()) + .arg(scrubbed.what()); + return; + } } // Once we have the FQDN setup to use it for the lease hostname. This diff --git a/src/lib/dhcpsrv/d2_client_mgr.cc b/src/lib/dhcpsrv/d2_client_mgr.cc index 84ee11d9fb..54c815176e 100644 --- a/src/lib/dhcpsrv/d2_client_mgr.cc +++ b/src/lib/dhcpsrv/d2_client_mgr.cc @@ -186,10 +186,15 @@ std::string D2ClientMgr::qualifyName(const std::string& partial_name, const DdnsParams& ddns_params, const bool trailing_dot) const { + if (partial_name.empty()) { + isc_throw(BadValue, "D2ClientMgr::qualifyName" + " - partial_name cannot be an empty string"); + } + std::ostringstream gen_name; gen_name << partial_name; std::string suffix = ddns_params.getQualifyingSuffix(); - if (!suffix.empty() && partial_name.back() != '.') { + if (!suffix.empty() && (partial_name.back() != '.')) { bool suffix_present = true; std::string str = gen_name.str(); auto suffix_rit = suffix.rbegin(); @@ -241,7 +246,7 @@ D2ClientMgr::qualifyName(const std::string& partial_name, // If the trailing dot should not be appended but it is present, // remove it. if ((len > 0) && (str[len - 1] == '.')) { - gen_name.str(str.substr(0,len-1)); + gen_name.str(str.substr(0, len-1)); } } diff --git a/src/lib/dhcpsrv/d2_client_mgr.h b/src/lib/dhcpsrv/d2_client_mgr.h index 7344f19a40..238fd0a415 100644 --- a/src/lib/dhcpsrv/d2_client_mgr.h +++ b/src/lib/dhcpsrv/d2_client_mgr.h @@ -30,6 +30,14 @@ namespace isc { namespace dhcp { +/// @brief Exception thrown when host name sanitizing reduces +/// the domain name to an empty string. +class FQDNScrubbedEmpty : public Exception { +public: + FQDNScrubbedEmpty(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { } +}; + /// @brief Defines the type for D2 IO error handler. /// This callback is invoked when a send to kea-dhcp-ddns completes with a /// failed status. This provides the application layer (Kea) with a means to @@ -197,6 +205,7 @@ public: /// suffix itself is empty (i.e. ""). /// /// @return std::string containing the qualified name. + /// @throw BadValue if partial_name is empty. std::string qualifyName(const std::string& partial_name, const DdnsParams& ddns_params, const bool trailing_dot) const; @@ -264,6 +273,9 @@ public: /// @param ddns_params DDNS behavioral configuration parameters /// @tparam T FQDN Option class containing the FQDN data such as /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn + /// + /// @throw FQDNScrubbedEmpty if hostname sanitizing reduces the input domain + /// name to an empty string. template void adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddns_params); @@ -515,7 +527,12 @@ D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddn ss << sanitizer->scrub(label); } - client_name = ss.str(); + std::string clean_name = ss.str(); + if (clean_name.empty() || clean_name == ".") { + isc_throw(FQDNScrubbedEmpty, client_name); + } + + client_name = clean_name; } // If the supplied name is partial, qualify it by adding the suffix.