diff options
Diffstat (limited to 'src/zenhorde/hordeclient.cpp')
| -rw-r--r-- | src/zenhorde/hordeclient.cpp | 89 |
1 files changed, 42 insertions, 47 deletions
diff --git a/src/zenhorde/hordeclient.cpp b/src/zenhorde/hordeclient.cpp index fb981f0ba..762edce06 100644 --- a/src/zenhorde/hordeclient.cpp +++ b/src/zenhorde/hordeclient.cpp @@ -4,6 +4,7 @@ #include <zencore/iobuffer.h> #include <zencore/logging.h> #include <zencore/memoryview.h> +#include <zencore/string.h> #include <zencore/trace.h> #include <zenhorde/hordeclient.h> #include <zenhttp/httpclient.h> @@ -14,7 +15,7 @@ ZEN_THIRD_PARTY_INCLUDES_END namespace zen::horde { -HordeClient::HordeClient(const HordeConfig& Config) : m_Config(Config), m_Log(zen::logging::Get("horde.client")) +HordeClient::HordeClient(HordeConfig Config) : m_Config(std::move(Config)), m_Log("horde.client") { } @@ -32,19 +33,24 @@ HordeClient::Initialize() Settings.RetryCount = 1; Settings.ExpectedErrorCodes = {HttpResponseCode::ServiceUnavailable, HttpResponseCode::TooManyRequests}; - if (!m_Config.AuthToken.empty()) + if (m_Config.AccessTokenProvider) { + Settings.AccessTokenProvider = m_Config.AccessTokenProvider; + } + else if (!m_Config.AuthToken.empty()) + { + // Static tokens have no wire-provided expiry. Synthesising \"now + 24h\" is wrong + // in both directions: if the real token expires before 24h we keep sending it after + // it dies; if it's long-lived we force unnecessary re-auth churn every day. Use the + // never-expires sentinel, matching zenhttp's CreateFromStaticToken. Settings.AccessTokenProvider = [token = m_Config.AuthToken]() -> HttpClientAccessToken { - HttpClientAccessToken Token; - Token.Value = token; - Token.ExpireTime = HttpClientAccessToken::Clock::now() + std::chrono::hours{24}; - return Token; + return HttpClientAccessToken(token, HttpClientAccessToken::TimePoint::max()); }; } m_Http = std::make_unique<zen::HttpClient>(m_Config.ServerUrl, Settings); - if (!m_Config.AuthToken.empty()) + if (Settings.AccessTokenProvider) { if (!m_Http->Authenticate()) { @@ -66,24 +72,21 @@ HordeClient::BuildRequestBody() const Requirements["pool"] = m_Config.Pool; } - std::string Condition; -#if ZEN_PLATFORM_WINDOWS ExtendableStringBuilder<256> CondBuf; +#if ZEN_PLATFORM_WINDOWS CondBuf << "(OSFamily == 'Windows' || WineEnabled == '" << (m_Config.AllowWine ? "true" : "false") << "')"; - Condition = std::string(CondBuf); #elif ZEN_PLATFORM_MAC - Condition = "OSFamily == 'MacOS'"; + CondBuf << "OSFamily == 'MacOS'"; #else - Condition = "OSFamily == 'Linux'"; + CondBuf << "OSFamily == 'Linux'"; #endif if (!m_Config.Condition.empty()) { - Condition += " "; - Condition += m_Config.Condition; + CondBuf << " " << m_Config.Condition; } - Requirements["condition"] = Condition; + Requirements["condition"] = std::string(CondBuf); Requirements["exclusive"] = true; json11::Json::object Connection; @@ -159,39 +162,27 @@ HordeClient::ResolveCluster(const std::string& RequestBody, ClusterInfo& OutClus return false; } - OutCluster.ClusterId = ClusterIdVal.string_value(); - return true; -} - -bool -HordeClient::ParseHexBytes(std::string_view Hex, uint8_t* Out, size_t OutSize) -{ - if (Hex.size() != OutSize * 2) + // A server-returned ClusterId is interpolated directly into the request URL below + // (api/v2/compute/<ClusterId>), so a compromised or MITM'd Horde server could + // otherwise inject additional path segments or query strings. Constrain to a + // conservative identifier alphabet. + const std::string& ClusterIdStr = ClusterIdVal.string_value(); + if (ClusterIdStr.size() > 64) { + ZEN_WARN("rejecting overlong clusterId ({} bytes) in cluster resolution response", ClusterIdStr.size()); return false; } - - for (size_t i = 0; i < OutSize; ++i) + static constexpr AsciiSet ValidClusterIdCharactersSet{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"}; + if (!AsciiSet::HasOnly(ClusterIdStr, ValidClusterIdCharactersSet)) { - auto HexToByte = [](char c) -> int { - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return -1; - }; - - const int Hi = HexToByte(Hex[i * 2]); - const int Lo = HexToByte(Hex[i * 2 + 1]); - if (Hi < 0 || Lo < 0) - { - return false; - } - Out[i] = static_cast<uint8_t>((Hi << 4) | Lo); + ZEN_WARN("rejecting clusterId with unsafe character in cluster resolution response"); + return false; } + OutCluster.ClusterId = ClusterIdStr; + + ZEN_DEBUG("cluster resolution succeeded: clusterId='{}'", OutCluster.ClusterId); + return true; } @@ -200,8 +191,6 @@ HordeClient::RequestMachine(const std::string& RequestBody, const std::string& C { ZEN_TRACE_CPU("HordeClient::RequestMachine"); - ZEN_INFO("requesting machine from Horde with cluster '{}'", ClusterId.empty() ? "default" : ClusterId.c_str()); - ExtendableStringBuilder<128> ResourcePath; ResourcePath << "api/v2/compute/" << (ClusterId.empty() ? "default" : ClusterId.c_str()); @@ -321,11 +310,15 @@ HordeClient::RequestMachine(const std::string& RequestBody, const std::string& C } else if (Prop.starts_with("LogicalCores=")) { - LogicalCores = static_cast<uint16_t>(std::atoi(Prop.c_str() + 13)); + LogicalCores = ParseInt<uint16_t>(std::string_view(Prop).substr(13)).value_or(0); } else if (Prop.starts_with("PhysicalCores=")) { - PhysicalCores = static_cast<uint16_t>(std::atoi(Prop.c_str() + 14)); + PhysicalCores = ParseInt<uint16_t>(std::string_view(Prop).substr(14)).value_or(0); + } + else if (Prop.starts_with("Pool=")) + { + OutMachine.Pool = Prop.substr(5); } } } @@ -370,10 +363,12 @@ HordeClient::RequestMachine(const std::string& RequestBody, const std::string& C OutMachine.LeaseId = LeaseIdVal.string_value(); } - ZEN_INFO("Horde machine assigned [{}:{}] cores={} lease={}", + ZEN_INFO("Horde machine assigned [{}:{}] mode={} cores={} pool={} lease={}", OutMachine.GetConnectionAddress(), OutMachine.GetConnectionPort(), + ToString(OutMachine.Mode), OutMachine.LogicalCores, + OutMachine.Pool, OutMachine.LeaseId); return true; |