diff options
Diffstat (limited to 'src/lib/d2srv/d2_config.cc')
-rw-r--r-- | src/lib/d2srv/d2_config.cc | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/src/lib/d2srv/d2_config.cc b/src/lib/d2srv/d2_config.cc new file mode 100644 index 0000000..a5633f3 --- /dev/null +++ b/src/lib/d2srv/d2_config.cc @@ -0,0 +1,674 @@ +// Copyright (C) 2013-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 <d2srv/d2_log.h> +#include <d2srv/d2_cfg_mgr.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> +#include <exceptions/exceptions.h> +#include <asiolink/io_error.h> + +#include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/algorithm/string/predicate.hpp> + +#include <sstream> +#include <string> + +using namespace isc::process; +using namespace isc::data; + +namespace isc { +namespace d2 { + +// *********************** D2Params ************************* + +D2Params::D2Params(const isc::asiolink::IOAddress& ip_address, + const size_t port, + const size_t dns_server_timeout, + const dhcp_ddns::NameChangeProtocol& ncr_protocol, + const dhcp_ddns::NameChangeFormat& ncr_format) + : ip_address_(ip_address), + port_(port), + dns_server_timeout_(dns_server_timeout), + ncr_protocol_(ncr_protocol), + ncr_format_(ncr_format) { + validateContents(); +} + +D2Params::D2Params() + : ip_address_(isc::asiolink::IOAddress("127.0.0.1")), + port_(53001), dns_server_timeout_(500), + ncr_protocol_(dhcp_ddns::NCR_UDP), + ncr_format_(dhcp_ddns::FMT_JSON) { + validateContents(); +} + +D2Params::~D2Params(){}; + +void +D2Params::validateContents() { + if ((ip_address_.toText() == "0.0.0.0") || (ip_address_.toText() == "::")) { + isc_throw(D2CfgError, + "D2Params: IP address cannot be \"" << ip_address_ << "\""); + } + + if (port_ == 0) { + isc_throw(D2CfgError, "D2Params: port cannot be 0"); + } + + if (dns_server_timeout_ < 1) { + isc_throw(D2CfgError, + "D2Params: DNS server timeout must be larger than 0"); + } + + if (ncr_format_ != dhcp_ddns::FMT_JSON) { + isc_throw(D2CfgError, "D2Params: NCR Format:" + << dhcp_ddns::ncrFormatToString(ncr_format_) + << " is not yet supported"); + } + + if (ncr_protocol_ != dhcp_ddns::NCR_UDP) { + isc_throw(D2CfgError, "D2Params: NCR Protocol:" + << dhcp_ddns::ncrProtocolToString(ncr_protocol_) + << " is not yet supported"); + } +} + +std::string +D2Params::getConfigSummary() const { + std::ostringstream s; + s << "listening on " << getIpAddress() << ", port " << getPort() + << ", using " << ncrProtocolToString(ncr_protocol_); + return (s.str()); +} + +bool +D2Params::operator == (const D2Params& other) const { + return ((ip_address_ == other.ip_address_) && + (port_ == other.port_) && + (dns_server_timeout_ == other.dns_server_timeout_) && + (ncr_protocol_ == other.ncr_protocol_) && + (ncr_format_ == other.ncr_format_)); +} + +bool +D2Params::operator != (const D2Params& other) const { + return (!(*this == other)); +} + +std::string +D2Params::toText() const { + std::ostringstream stream; + + stream << ", ip-address: " << ip_address_.toText() + << ", port: " << port_ + << ", dns-server-timeout_: " << dns_server_timeout_ + << ", ncr-protocol: " + << dhcp_ddns::ncrProtocolToString(ncr_protocol_) + << ", ncr-format: " << ncr_format_ + << dhcp_ddns::ncrFormatToString(ncr_format_); + + return (stream.str()); +} + +std::ostream& +operator<<(std::ostream& os, const D2Params& config) { + os << config.toText(); + return (os); +} + +// *********************** TSIGKeyInfo ************************* +// Note these values match corresponding values for Bind9's +// dnssec-keygen +const char* TSIGKeyInfo::HMAC_MD5_STR = "HMAC-MD5"; +const char* TSIGKeyInfo::HMAC_SHA1_STR = "HMAC-SHA1"; +const char* TSIGKeyInfo::HMAC_SHA224_STR = "HMAC-SHA224"; +const char* TSIGKeyInfo::HMAC_SHA256_STR = "HMAC-SHA256"; +const char* TSIGKeyInfo::HMAC_SHA384_STR = "HMAC-SHA384"; +const char* TSIGKeyInfo::HMAC_SHA512_STR = "HMAC-SHA512"; + +TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm, + const std::string& secret, uint32_t digestbits) + :name_(name), algorithm_(algorithm), secret_(secret), + digestbits_(digestbits), tsig_key_() { + remakeKey(); +} + +TSIGKeyInfo::~TSIGKeyInfo() { +} + +const dns::Name& +TSIGKeyInfo::stringToAlgorithmName(const std::string& algorithm_id) { + if (boost::iequals(algorithm_id, HMAC_MD5_STR)) { + return (dns::TSIGKey::HMACMD5_NAME()); + } else if (boost::iequals(algorithm_id, HMAC_SHA1_STR)) { + return (dns::TSIGKey::HMACSHA1_NAME()); + } else if (boost::iequals(algorithm_id, HMAC_SHA224_STR)) { + return (dns::TSIGKey::HMACSHA224_NAME()); + } else if (boost::iequals(algorithm_id, HMAC_SHA256_STR)) { + return (dns::TSIGKey::HMACSHA256_NAME()); + } else if (boost::iequals(algorithm_id, HMAC_SHA384_STR)) { + return (dns::TSIGKey::HMACSHA384_NAME()); + } else if (boost::iequals(algorithm_id, HMAC_SHA512_STR)) { + return (dns::TSIGKey::HMACSHA512_NAME()); + } + + isc_throw(BadValue, "Unknown TSIG Key algorithm: " << algorithm_id); +} + +void +TSIGKeyInfo::remakeKey() { + try { + // Since our secret value is base64 encoded already, we need to + // build the input string for the appropriate D2TsigKey constructor. + // If secret isn't a valid base64 value, the constructor will throw. + std::ostringstream stream; + stream << dns::Name(name_).toText() << ":" + << secret_ << ":" + << stringToAlgorithmName(algorithm_); + if (digestbits_ > 0) { + stream << ":" << digestbits_; + } + + tsig_key_.reset(new D2TsigKey(stream.str())); + } catch (const std::exception& ex) { + isc_throw(D2CfgError, "Cannot make D2TsigKey: " << ex.what()); + } +} + +ElementPtr +TSIGKeyInfo::toElement() const { + ElementPtr result = Element::createMap(); + // Set user-context + contextToElement(result); + // Set name + result->set("name", Element::create(name_)); + // Set algorithm + result->set("algorithm", Element::create(algorithm_)); + // Set secret + result->set("secret", Element::create(secret_)); + // Set digest-bits + result->set("digest-bits", + Element::create(static_cast<int64_t>(digestbits_))); + + return (result); +} + +// *********************** DnsServerInfo ************************* +DnsServerInfo::DnsServerInfo(const std::string& hostname, + isc::asiolink::IOAddress ip_address, + uint32_t port, + bool enabled, + const TSIGKeyInfoPtr& tsig_key_info, + bool inherited_key) + : hostname_(hostname), ip_address_(ip_address), port_(port), + enabled_(enabled), tsig_key_info_(tsig_key_info), + inherited_key_(inherited_key) { +} + +DnsServerInfo::~DnsServerInfo() { +} + +const std::string +DnsServerInfo::getKeyName() const { + if (tsig_key_info_) { + return (tsig_key_info_->getName()); + } + + return (""); +} + +std::string +DnsServerInfo::toText() const { + std::ostringstream stream; + stream << (getIpAddress().toText()) << " port:" << getPort(); + return (stream.str()); +} + +ElementPtr +DnsServerInfo::toElement() const { + ElementPtr result = Element::createMap(); + // Set user-context + contextToElement(result); + // Set hostname + result->set("hostname", Element::create(hostname_)); + // Set ip-address + result->set("ip-address", Element::create(ip_address_.toText())); + // Set port + result->set("port", Element::create(static_cast<int64_t>(port_))); + // Set key-name + if (tsig_key_info_ && !inherited_key_) { + result->set("key-name", Element::create(tsig_key_info_->getName())); + } + + return (result); +} + +std::ostream& +operator<<(std::ostream& os, const DnsServerInfo& server) { + os << server.toText(); + return (os); +} + +// *********************** DdnsDomain ************************* + +DdnsDomain::DdnsDomain(const std::string& name, + DnsServerInfoStoragePtr servers, + const std::string& key_name) + : name_(name), servers_(servers), key_name_(key_name) { +} + +DdnsDomain::~DdnsDomain() { +} + +ElementPtr +DdnsDomain::toElement() const { + ElementPtr result = Element::createMap(); + // Set user-context + contextToElement(result); + // Set name + result->set("name", Element::create(name_)); + // Set servers + ElementPtr servers = Element::createList(); + for (DnsServerInfoStorage::const_iterator server = servers_->begin(); + server != servers_->end(); ++server) { + ElementPtr dns_server = (*server)->toElement(); + servers->add(dns_server); + } + // the dns server list may not be empty + if (!servers->empty()) { + result->set("dns-servers", servers); + } + // Set key-name + if (!key_name_.empty()) { + result->set("key-name", Element::create(key_name_)); + } + + return (result); +} + +// *********************** DdnsDomainLstMgr ************************* + +const char* DdnsDomainListMgr::wildcard_domain_name_ = "*"; + +DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name), + domains_(new DdnsDomainMap()) { +} + + +DdnsDomainListMgr::~DdnsDomainListMgr () { +} + +void +DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) { + if (!domains) { + isc_throw(D2CfgError, + "DdnsDomainListMgr::setDomains: Domain list may not be null"); + } + + domains_ = domains; + + // Look for the wild card domain. If present, set the member variable + // to remember it. This saves us from having to look for it every time + // we attempt a match. + DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_); + if (gotit != domains_->end()) { + wildcard_domain_ = gotit->second; + } +} + +bool +DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) { + // First check the case of one domain to rule them all. + if ((size() == 1) && (wildcard_domain_)) { + domain = wildcard_domain_; + return (true); + } + + // Iterate over the domain map looking for the domain which matches + // the longest portion of the given fqdn. + + size_t req_len = fqdn.size(); + size_t match_len = 0; + DdnsDomainMapPair map_pair; + DdnsDomainPtr best_match; + BOOST_FOREACH (map_pair, *domains_) { + std::string domain_name = map_pair.first; + size_t dom_len = domain_name.size(); + + // If the domain name is longer than the fqdn, then it cant be match. + if (req_len < dom_len) { + continue; + } + + // If the lengths are identical and the names match we're done. + if (req_len == dom_len) { + if (boost::iequals(fqdn, domain_name)) { + // exact match, done + domain = map_pair.second; + return (true); + } + } else { + // The fqdn is longer than the domain name. Adjust the start + // point of comparison by the excess in length. Only do the + // comparison if the adjustment lands on a boundary. This + // prevents "onetwo.net" from matching "two.net". + size_t offset = req_len - dom_len; + if ((fqdn[offset - 1] == '.') && + (boost::iequals(fqdn.substr(offset), domain_name))) { + // Fqdn contains domain name, keep it if its better than + // any we have matched so far. + if (dom_len > match_len) { + match_len = dom_len; + best_match = map_pair.second; + } + } + } + } + + if (!best_match) { + // There's no match. If they specified a wild card domain use it + // otherwise there's no domain for this entry. + if (wildcard_domain_) { + domain = wildcard_domain_; + return (true); + } + + LOG_WARN(dhcp_to_d2_logger, DHCP_DDNS_NO_MATCH).arg(fqdn); + return (false); + } + + domain = best_match; + return (true); +} + +ElementPtr +DdnsDomainListMgr::toElement() const { + ElementPtr result = Element::createList(); + // Iterate on ddns domains + for (DdnsDomainMap::const_iterator domain = domains_->begin(); + domain != domains_->end(); ++domain) { + ElementPtr ddns_domain = domain->second->toElement(); + result->add(ddns_domain); + } + + return (result); +} + +// *************************** PARSERS *********************************** + +// *********************** TSIGKeyInfoParser ************************* + +TSIGKeyInfoPtr +TSIGKeyInfoParser::parse(ConstElementPtr key_config) { + std::string name = getString(key_config, "name"); + std::string algorithm = getString(key_config, "algorithm"); + uint32_t digestbits = getInteger(key_config, "digest-bits"); + std::string secret = getString(key_config, "secret"); + ConstElementPtr user_context = key_config->get("user-context"); + + // Algorithm must be valid. + try { + TSIGKeyInfo::stringToAlgorithmName(algorithm); + } catch (const std::exception& ex) { + isc_throw(D2CfgError, "tsig-key : " << ex.what() + << " (" << getPosition("algorithm", key_config) << ")"); + } + + // Non-zero digest-bits must be an integral number of octets, greater + // than 80 and at least half of the algorithm key length. It defaults + // to zero and JSON parsing ensures it's a multiple of 8. + if ((digestbits > 0) && + ((digestbits < 80) || + (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA224_STR) + && (digestbits < 112)) || + (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA256_STR) + && (digestbits < 128)) || + (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA384_STR) + && (digestbits < 192)) || + (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA512_STR) + && (digestbits < 256)))) { + isc_throw(D2CfgError, "tsig-key: digest-bits too small : (" + << getPosition("digest-bits", key_config) + << ")"); + } + + // Everything should be valid, so create the key instance. + // It is possible for the D2TsigKey constructor to fail such as + // with an invalid secret content. + TSIGKeyInfoPtr key_info; + try { + key_info.reset(new TSIGKeyInfo(name, algorithm, secret, digestbits)); + } catch (const std::exception& ex) { + isc_throw(D2CfgError, ex.what() << " (" + << key_config->getPosition() << ")"); + } + + // Add user-context + if (user_context) { + key_info->setContext(user_context); + } + + return (key_info); +} + +// *********************** TSIGKeyInfoListParser ************************* + +TSIGKeyInfoMapPtr +TSIGKeyInfoListParser::parse(ConstElementPtr key_list) { + TSIGKeyInfoMapPtr keys(new TSIGKeyInfoMap()); + ConstElementPtr key_config; + TSIGKeyInfoParser key_parser; + BOOST_FOREACH(key_config, key_list->listValue()) { + TSIGKeyInfoPtr key = key_parser.parse(key_config); + + // Duplicates are not allowed and should be flagged as an error. + if (keys->find(key->getName()) != keys->end()) { + isc_throw(D2CfgError, "Duplicate TSIG key name specified : " + << key->getName() + << " (" << getPosition("name", key_config) << ")"); + } + + (*keys)[key->getName()] = key; + } + + return (keys); +} + +// *********************** DnsServerInfoParser ************************* + +DnsServerInfoPtr +DnsServerInfoParser::parse(ConstElementPtr server_config, + ConstElementPtr domain_config, + const TSIGKeyInfoMapPtr keys) { + std::string hostname = getString(server_config, "hostname"); + std::string ip_address = getString(server_config, "ip-address"); + uint32_t port = getInteger(server_config, "port"); + std::string key_name = getString(server_config, "key-name"); + ConstElementPtr user_context = server_config->get("user-context"); + + // Key name is optional. If it is not blank, then find the key in the + // list of defined keys. + TSIGKeyInfoPtr tsig_key_info; + bool inherited_key = true; + if (key_name.empty()) { + std::string domain_key_name = getString(domain_config, "key-name"); + if (!domain_key_name.empty()) { + key_name = domain_key_name; + } + } else { + inherited_key = false; + } + if (!key_name.empty()) { + if (keys) { + TSIGKeyInfoMap::iterator kit = keys->find(key_name); + if (kit != keys->end()) { + tsig_key_info = kit->second; + } + } + + if (!tsig_key_info) { + if (inherited_key) { + isc_throw(D2CfgError, "DdnsDomain : specifies an " + << "undefined key: " << key_name << " (" + << getPosition("key-name", domain_config) << ")"); + } else { + isc_throw(D2CfgError, "Dns Server : specifies an " + << "undefined key: " << key_name << " (" + << getPosition("key-name", server_config) << ")"); + } + } + } + + // The configuration must specify one or the other. + if (hostname.empty() == ip_address.empty()) { + isc_throw(D2CfgError, "Dns Server must specify one or the other" + " of hostname or IP address" + << " (" << server_config->getPosition() << ")"); + } + + DnsServerInfoPtr server_info; + if (!hostname.empty()) { + /// @todo when resolvable hostname is supported we create the entry + /// as follows: + /// + /// @code + /// // When hostname is specified, create a valid, blank IOAddress + /// // and then create the DnsServerInfo. + /// serverInfo.reset(new DnsServerInfo(hostname, io_addr, port)); + /// + /// @endcode + /// + /// Resolution will be done prior to connection during transaction + /// processing. + /// Until then we'll throw unsupported. + isc_throw(D2CfgError, "Dns Server : hostname is not yet supported" + << " (" << getPosition("hostname", server_config) << ")"); + } else { + try { + // Create an IOAddress from the IP address string given and then + // create the DnsServerInfo. + isc::asiolink::IOAddress io_addr(ip_address); + server_info.reset(new DnsServerInfo(hostname, io_addr, port, + true, tsig_key_info, + inherited_key)); + } catch (const isc::asiolink::IOError& ex) { + isc_throw(D2CfgError, "Dns Server : invalid IP address : " + << ip_address + << " (" << getPosition("ip-address", server_config) << ")"); + } + } + + // Add user-context + if (user_context) { + server_info->setContext(user_context); + } + + return (server_info); +} + +// *********************** DnsServerInfoListParser ************************* + +DnsServerInfoStoragePtr +DnsServerInfoListParser::parse(ConstElementPtr server_list, + ConstElementPtr domain_config, + const TSIGKeyInfoMapPtr keys) { + DnsServerInfoStoragePtr servers(new DnsServerInfoStorage()); + ConstElementPtr server_config; + DnsServerInfoParser parser; + BOOST_FOREACH(server_config, server_list->listValue()) { + DnsServerInfoPtr server = + parser.parse(server_config, domain_config, keys); + servers->push_back(server); + } + + return (servers); +} + +// *********************** DdnsDomainParser ************************* + +DdnsDomainPtr DdnsDomainParser::parse(ConstElementPtr domain_config, + const TSIGKeyInfoMapPtr keys) { + std::string name = getString(domain_config, "name"); + std::string key_name = getString(domain_config, "key-name"); + ConstElementPtr user_context = domain_config->get("user-context"); + + // Parse the list of DNS servers + ConstElementPtr servers_config; + try { + servers_config = domain_config->get("dns-servers"); + } catch (const std::exception& ex) { + isc_throw(D2CfgError, "DdnsDomain : missing dns-server list" + << " (" << servers_config->getPosition() << ")"); + } + + DnsServerInfoListParser server_parser; + DnsServerInfoStoragePtr servers = + server_parser.parse(servers_config, domain_config, keys); + if (servers->size() == 0) { + isc_throw(D2CfgError, "DNS server list cannot be empty" + << servers_config->getPosition()); + } + + // Instantiate the new domain and add it to domain storage. + DdnsDomainPtr domain(new DdnsDomain(name, servers, key_name)); + + // Add user-context + if (user_context) { + domain->setContext(user_context); + } + + return (domain); +} + +// *********************** DdnsDomainListParser ************************* + +DdnsDomainMapPtr DdnsDomainListParser::parse(ConstElementPtr domain_list, + const TSIGKeyInfoMapPtr keys) { + DdnsDomainMapPtr domains(new DdnsDomainMap()); + DdnsDomainParser parser; + ConstElementPtr domain_config; + BOOST_FOREACH(domain_config, domain_list->listValue()) { + DdnsDomainPtr domain = parser.parse(domain_config, keys); + + // Duplicates are not allowed + if (domains->find(domain->getName()) != domains->end()) { + isc_throw(D2CfgError, "Duplicate domain specified:" + << domain->getName() + << " (" << getPosition("name", domain_config) << ")"); + } + + (*domains)[domain->getName()] = domain; + } + + return (domains); +} + +// *********************** DdnsDomainListMgrParser ************************* + +DdnsDomainListMgrPtr +DdnsDomainListMgrParser::parse(ConstElementPtr mgr_config, + const std::string& mgr_name, + const TSIGKeyInfoMapPtr keys) { + DdnsDomainListMgrPtr mgr(new DdnsDomainListMgr(mgr_name)); + + // Parse the list of domains + ConstElementPtr domains_config = mgr_config->get("ddns-domains"); + if (domains_config) { + DdnsDomainListParser domain_parser; + DdnsDomainMapPtr domains = domain_parser.parse(domains_config, keys); + + // Add the new domain to the domain storage. + mgr->setDomains(domains); + } + + return(mgr); +} + +} // end of isc::dhcp namespace +} // end of isc namespace |