@@ -25,14 +25,46 @@ static constexpr uint8_t DOMAIN_MAX_LEN{253};
2525/* * Minimum length of a FQDN */
2626static constexpr uint8_t DOMAIN_MIN_LEN{3 };
2727
28+ static constexpr std::string_view SAFE_CHARS_ALPHA{" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" };
2829static constexpr std::string_view SAFE_CHARS_IPV4{" 1234567890." };
2930static constexpr std::string_view SAFE_CHARS_IPV4_6{" abcdefABCDEF1234567890.:[]" };
3031static constexpr std::string_view SAFE_CHARS_RFC1035{" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-" };
32+ static constexpr std::array<std::string_view, 13 > TLDS_BAD{
33+ // ICANN resolution 2018.02.04.12
34+ " .mail" ,
35+ // Infrastructure TLD
36+ " .arpa" ,
37+ // RFC 6761
38+ " .example" , " .invalid" , " .localhost" , " .test" ,
39+ // RFC 6762
40+ " .local" ,
41+ // RFC 6762, Appendix G
42+ " .corp" , " .home" , " .internal" , " .intranet" , " .lan" , " .private" ,
43+ };
3144
3245bool MatchCharsFilter (std::string_view input, std::string_view filter)
3346{
3447 return std::all_of (input.begin (), input.end (), [&filter](char c) { return filter.find (c) != std::string_view::npos; });
3548}
49+
50+ bool MatchSuffix (const std::string& str, Span<const std::string_view> list)
51+ {
52+ if (str.empty ()) return false ;
53+ for (const auto & suffix : list) {
54+ if (suffix.size () > str.size ()) continue ;
55+ if (std::string_view{str}.ends_with (suffix)) return true ;
56+ }
57+ return false ;
58+ }
59+
60+ bool IsAllowedPlatformHTTPPort (uint16_t port)
61+ {
62+ switch (port) {
63+ case 443 :
64+ return true ;
65+ }
66+ return false ;
67+ }
3668} // anonymous namespace
3769
3870bool IsNodeOnMainnet () { return Params ().NetworkIDString () == CBaseChainParams::MAIN; }
@@ -387,6 +419,10 @@ NetInfoStatus ExtNetInfo::ProcessCandidate(const NetInfoPurpose purpose, const N
387419 if (IsAddrPortDuplicate (candidate)) {
388420 return NetInfoStatus::Duplicate;
389421 }
422+ if (candidate.GetDomainPort ().has_value () && purpose != NetInfoPurpose::PLATFORM_HTTPS) {
423+ // Domains only allowed for Platform HTTPS API
424+ return NetInfoStatus::BadInput;
425+ }
390426 if (auto it{m_data.find (purpose)}; it != m_data.end ()) {
391427 // Existing entries list found, check limit
392428 auto & [_, entries] = *it;
@@ -426,6 +462,26 @@ NetInfoStatus ExtNetInfo::ValidateService(const CService& service)
426462 return NetInfoStatus::Success;
427463}
428464
465+ NetInfoStatus ExtNetInfo::ValidateDomainPort (const DomainPort& domain)
466+ {
467+ if (!domain.IsValid ()) {
468+ return NetInfoStatus::BadInput;
469+ }
470+ const uint16_t domain_port{domain.GetPort ()};
471+ if (domain_port == 0 || (IsBadPort (domain_port) && !IsAllowedPlatformHTTPPort (domain_port))) {
472+ return NetInfoStatus::BadPort;
473+ }
474+ const std::string& addr{domain.ToStringAddr ()};
475+ if (MatchSuffix (addr, TLDS_BAD)) {
476+ return NetInfoStatus::BadInput;
477+ }
478+ if (const auto labels{SplitString (addr, ' .' )}; !MatchCharsFilter (labels.at (labels.size () - 1 ), SAFE_CHARS_ALPHA)) {
479+ return NetInfoStatus::BadInput;
480+ }
481+
482+ return NetInfoStatus::Success;
483+ }
484+
429485NetInfoStatus ExtNetInfo::AddEntry (const NetInfoPurpose purpose, const std::string& input)
430486{
431487 if (!IsValidPurpose (purpose)) {
@@ -437,11 +493,25 @@ NetInfoStatus ExtNetInfo::AddEntry(const NetInfoPurpose purpose, const std::stri
437493 std::string addr;
438494 uint16_t port{0 };
439495 SplitHostPort (input, port, addr);
440- // Contains invalid characters, unlikely to pass Lookup(), fast-fail
496+
441497 if (!MatchCharsFilter (addr, SAFE_CHARS_IPV4_6)) {
442- return NetInfoStatus::BadInput;
498+ if (!MatchCharsFilter (addr, SAFE_CHARS_RFC1035)) {
499+ // Neither IP:port safe nor domain-safe, we can safely assume it's bad input
500+ return NetInfoStatus::BadInput;
501+ }
502+
503+ // Not IP:port safe but domain safe, treat as domain.
504+ if (DomainPort domain; domain.Set (addr, port) == DomainPort::Status::Success) {
505+ const auto ret{ValidateDomainPort (domain)};
506+ if (ret == NetInfoStatus::Success) {
507+ return ProcessCandidate (purpose, NetInfoEntry{domain});
508+ }
509+ return ret; /* ValidateDomainPort() failed */
510+ }
511+ return NetInfoStatus::BadInput; /* DomainPort::Set() failed */
443512 }
444513
514+ // IP:port safe, try to parse it as IP:port
445515 if (auto service_opt{Lookup (addr, /* portDefault=*/ port, /* fAllowLookup=*/ false )}) {
446516 const auto ret{ValidateService (*service_opt)};
447517 if (ret == NetInfoStatus::Success) {
@@ -516,6 +586,15 @@ NetInfoStatus ExtNetInfo::Validate() const
516586 // Stores CService underneath but doesn't pass validation rules
517587 return ret;
518588 }
589+ } else if (const auto domain_opt{entry.GetDomainPort ()}) {
590+ if (purpose != NetInfoPurpose::PLATFORM_HTTPS) {
591+ // Domains only allowed for Platform HTTPS API
592+ return NetInfoStatus::BadInput;
593+ }
594+ if (auto ret{ValidateDomainPort (*domain_opt)}; ret != NetInfoStatus::Success) {
595+ // Stores DomainPort underneath but doesn't pass validation rules
596+ return ret;
597+ }
519598 } else {
520599 // Doesn't store valid type underneath
521600 return NetInfoStatus::Malformed;
0 commit comments