diff options
Diffstat (limited to 'NorthstarDLL/httprequesthandler.cpp')
-rw-r--r-- | NorthstarDLL/httprequesthandler.cpp | 586 |
1 files changed, 0 insertions, 586 deletions
diff --git a/NorthstarDLL/httprequesthandler.cpp b/NorthstarDLL/httprequesthandler.cpp deleted file mode 100644 index 2681b4a3..00000000 --- a/NorthstarDLL/httprequesthandler.cpp +++ /dev/null @@ -1,586 +0,0 @@ -#include "pch.h" -#include "httprequesthandler.h" -#include "version.h" -#include "squirrel.h" -#include "tier0.h" - -HttpRequestHandler* g_httpRequestHandler; - -bool IsHttpDisabled() -{ - const static bool bIsHttpDisabled = Tier0::CommandLine()->FindParm("-disablehttprequests"); - return bIsHttpDisabled; -} - -bool IsLocalHttpAllowed() -{ - const static bool bIsLocalHttpAllowed = Tier0::CommandLine()->FindParm("-allowlocalhttp"); - return bIsLocalHttpAllowed; -} - -bool DisableHttpSsl() -{ - const static bool bDisableHttpSsl = Tier0::CommandLine()->FindParm("-disablehttpssl"); - return bDisableHttpSsl; -} - -HttpRequestHandler::HttpRequestHandler() -{ - // Cache the launch parameters as early as possible in order to avoid possible exploits that change them at runtime. - IsHttpDisabled(); - IsLocalHttpAllowed(); - DisableHttpSsl(); -} - -void HttpRequestHandler::StartHttpRequestHandler() -{ - if (IsRunning()) - { - spdlog::warn("%s was called while IsRunning() is true!", __FUNCTION__); - return; - } - - m_bIsHttpRequestHandlerRunning = true; - spdlog::info("HttpRequestHandler started."); -} - -void HttpRequestHandler::StopHttpRequestHandler() -{ - if (!IsRunning()) - { - spdlog::warn("%s was called while IsRunning() is false", __FUNCTION__); - return; - } - - m_bIsHttpRequestHandlerRunning = false; - spdlog::info("HttpRequestHandler stopped."); -} - -bool IsHttpDestinationHostAllowed(const std::string& host, std::string& outHostname, std::string& outAddress, std::string& outPort) -{ - CURLU* url = curl_url(); - if (!url) - { - spdlog::error("Failed to call curl_url() for http request."); - return false; - } - - if (curl_url_set(url, CURLUPART_URL, host.c_str(), CURLU_DEFAULT_SCHEME) != CURLUE_OK) - { - spdlog::error("Failed to parse destination URL for http request."); - - curl_url_cleanup(url); - return false; - } - - char* urlHostname = nullptr; - if (curl_url_get(url, CURLUPART_HOST, &urlHostname, 0) != CURLUE_OK) - { - spdlog::error("Failed to parse hostname from destination URL for http request."); - - curl_url_cleanup(url); - return false; - } - - char* urlScheme = nullptr; - if (curl_url_get(url, CURLUPART_SCHEME, &urlScheme, CURLU_DEFAULT_SCHEME) != CURLUE_OK) - { - spdlog::error("Failed to parse scheme from destination URL for http request."); - - curl_url_cleanup(url); - curl_free(urlHostname); - return false; - } - - char* urlPort = nullptr; - if (curl_url_get(url, CURLUPART_PORT, &urlPort, CURLU_DEFAULT_PORT) != CURLUE_OK) - { - spdlog::error("Failed to parse port from destination URL for http request."); - - curl_url_cleanup(url); - curl_free(urlHostname); - curl_free(urlScheme); - return false; - } - - // Resolve the hostname into an address. - addrinfo* result; - addrinfo hints; - std::memset(&hints, 0, sizeof(addrinfo)); - hints.ai_family = AF_UNSPEC; - - if (getaddrinfo(urlHostname, urlScheme, &hints, &result) != 0) - { - spdlog::error("Failed to resolve http request destination {} using getaddrinfo().", urlHostname); - - curl_url_cleanup(url); - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - return false; - } - - bool bFoundIPv6 = false; - sockaddr_in* sockaddr_ipv4 = nullptr; - for (addrinfo* info = result; info; info = info->ai_next) - { - if (info->ai_family == AF_INET) - { - sockaddr_ipv4 = (sockaddr_in*)info->ai_addr; - break; - } - - bFoundIPv6 = bFoundIPv6 || info->ai_family == AF_INET6; - } - - if (sockaddr_ipv4 == nullptr) - { - if (bFoundIPv6) - { - spdlog::error("Only IPv4 destinations are supported for HTTP requests. To allow IPv6, launch the game using -allowlocalhttp."); - } - else - { - spdlog::error("Failed to resolve http request destination {} into a valid IPv4 address.", urlHostname); - } - - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - curl_url_cleanup(url); - - return false; - } - - // Fast checks for private ranges of IPv4. - // clang-format off - { - auto addrBytes = sockaddr_ipv4->sin_addr.S_un.S_un_b; - - if (addrBytes.s_b1 == 10 // 10.0.0.0 - 10.255.255.255 (Class A Private) - || addrBytes.s_b1 == 172 && addrBytes.s_b2 >= 16 && addrBytes.s_b2 <= 31 // 172.16.0.0 - 172.31.255.255 (Class B Private) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 168 // 192.168.0.0 - 192.168.255.255 (Class C Private) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 0 && addrBytes.s_b3 == 0 // 192.0.0.0 - 192.0.0.255 (IETF Assignment) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 0 && addrBytes.s_b3 == 2 // 192.0.2.0 - 192.0.2.255 (TEST-NET-1) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 88 && addrBytes.s_b3 == 99 // 192.88.99.0 - 192.88.99.255 (IPv4-IPv6 Relay) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 >= 18 && addrBytes.s_b2 <= 19 // 192.18.0.0 - 192.19.255.255 (Internet Benchmark) - || addrBytes.s_b1 == 192 && addrBytes.s_b2 == 51 && addrBytes.s_b3 == 100 // 192.51.100.0 - 192.51.100.255 (TEST-NET-2) - || addrBytes.s_b1 == 203 && addrBytes.s_b2 == 0 && addrBytes.s_b3 == 113 // 203.0.113.0 - 203.0.113.255 (TEST-NET-3) - || addrBytes.s_b1 == 169 && addrBytes.s_b2 == 254 // 169.254.00 - 169.254.255.255 (Link-local/APIPA) - || addrBytes.s_b1 == 127 // 127.0.0.0 - 127.255.255.255 (Loopback) - || addrBytes.s_b1 == 0 // 0.0.0.0 - 0.255.255.255 (Current network) - || addrBytes.s_b1 == 100 && addrBytes.s_b2 >= 64 && addrBytes.s_b2 <= 127 // 100.64.0.0 - 100.127.255.255 (Shared address space) - || sockaddr_ipv4->sin_addr.S_un.S_addr == 0xFFFFFFFF // 255.255.255.255 (Broadcast) - || addrBytes.s_b1 >= 224 && addrBytes.s_b2 <= 239 // 224.0.0.0 - 239.255.255.255 (Multicast) - || addrBytes.s_b1 == 233 && addrBytes.s_b2 == 252 && addrBytes.s_b3 == 0 // 233.252.0.0 - 233.252.0.255 (MCAST-TEST-NET) - || addrBytes.s_b1 >= 240 && addrBytes.s_b4 <= 254) // 240.0.0.0 - 255.255.255.254 (Future Use Class E) - { - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - curl_url_cleanup(url); - - return false; - } - } - - // clang-format on - - char resolvedStr[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &sockaddr_ipv4->sin_addr, resolvedStr, INET_ADDRSTRLEN); - - // Use the resolved address as the new request host. - outHostname = urlHostname; - outAddress = resolvedStr; - outPort = urlPort; - - freeaddrinfo(result); - - curl_free(urlHostname); - curl_free(urlScheme); - curl_free(urlPort); - curl_url_cleanup(url); - - return true; -} - -size_t HttpCurlWriteToStringBufferCallback(char* contents, size_t size, size_t nmemb, void* userp) -{ - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; -} - -template <ScriptContext context> int HttpRequestHandler::MakeHttpRequest(const HttpRequest& requestParameters) -{ - if (!IsRunning()) - { - spdlog::warn("%s was called while IsRunning() is false!", __FUNCTION__); - return -1; - } - - if (IsHttpDisabled()) - { - spdlog::warn("NS_InternalMakeHttpRequest called while the game is running with -disablehttprequests." - " Please check if requests are allowed using NSIsHttpEnabled() first."); - return -1; - } - - bool bAllowLocalHttp = IsLocalHttpAllowed(); - - // This handle will be returned to Squirrel so it can wait for the response and assign a callback for it. - int handle = ++m_iLastRequestHandle; - - std::thread requestThread( - [this, handle, requestParameters, bAllowLocalHttp]() - { - std::string hostname, resolvedAddress, resolvedPort; - - if (!bAllowLocalHttp) - { - if (!IsHttpDestinationHostAllowed(requestParameters.baseUrl, hostname, resolvedAddress, resolvedPort)) - { - spdlog::warn( - "HttpRequestHandler::MakeHttpRequest attempted to make a request to a private network. This is only allowed when " - "running the game with -allowlocalhttp."); - g_pSquirrel<context>->AsyncCall( - "NSHandleFailedHttpRequest", - handle, - (int)0, - "Cannot make HTTP requests to private network hosts without -allowlocalhttp. Check your console for more " - "information."); - return; - } - } - - CURL* curl = curl_easy_init(); - if (!curl) - { - spdlog::error("HttpRequestHandler::MakeHttpRequest failed to init libcurl for request."); - g_pSquirrel<context>->AsyncCall( - "NSHandleFailedHttpRequest", handle, static_cast<int>(CURLE_FAILED_INIT), curl_easy_strerror(CURLE_FAILED_INIT)); - return; - } - - // HEAD has no body. - if (requestParameters.method == HttpRequestMethod::HRM_HEAD) - { - curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); - } - - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, HttpRequestMethod::ToString(requestParameters.method).c_str()); - - // Only resolve to IPv4 if we don't allow private network requests. - curl_slist* host = nullptr; - if (!bAllowLocalHttp) - { - curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - host = curl_slist_append(host, fmt::format("{}:{}:{}", hostname, resolvedPort, resolvedAddress).c_str()); - curl_easy_setopt(curl, CURLOPT_RESOLVE, host); - } - - // Ensure we only allow HTTP or HTTPS. - curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - - // Allow redirects - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L); - - // Check if the url already contains a query. - // If so, we'll know to append with & instead of start with ? - std::string queryUrl = requestParameters.baseUrl; - bool bUrlContainsQuery = false; - - // If this fails, just ignore the parsing and trust what the user wants to query. - // Probably will fail but handling it here would be annoying. - CURLU* curlUrl = curl_url(); - if (curlUrl) - { - if (curl_url_set(curlUrl, CURLUPART_URL, queryUrl.c_str(), CURLU_DEFAULT_SCHEME) == CURLUE_OK) - { - char* currentQuery; - if (curl_url_get(curlUrl, CURLUPART_QUERY, ¤tQuery, 0) == CURLUE_OK) - { - if (currentQuery && std::strlen(currentQuery) != 0) - { - bUrlContainsQuery = true; - } - } - - curl_free(currentQuery); - } - - curl_url_cleanup(curlUrl); - } - - // GET requests, or POST-like requests with an empty body, can have query parameters. - // Append them to the base url. - if (HttpRequestMethod::CanHaveQueryParameters(requestParameters.method) && - !HttpRequestMethod::UsesCurlPostOptions(requestParameters.method) || - requestParameters.body.empty()) - { - bool isFirstValue = true; - for (const auto& kv : requestParameters.queryParameters) - { - char* key = curl_easy_escape(curl, kv.first.c_str(), kv.first.length()); - - for (const std::string& queryValue : kv.second) - { - char* value = curl_easy_escape(curl, queryValue.c_str(), queryValue.length()); - - if (isFirstValue && !bUrlContainsQuery) - { - queryUrl.append(fmt::format("?{}={}", key, value)); - isFirstValue = false; - } - else - { - queryUrl.append(fmt::format("&{}={}", key, value)); - } - - curl_free(value); - } - - curl_free(key); - } - } - - // If this method uses POST-like curl options, set those and set the body. - // The body won't be sent if it's empty anyway, meaning the query parameters above, if any, would be. - if (HttpRequestMethod::UsesCurlPostOptions(requestParameters.method)) - { - // Grab the body and set it as a POST field - curl_easy_setopt(curl, CURLOPT_POST, 1L); - - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, requestParameters.body.length()); - curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, requestParameters.body.c_str()); - } - - // Set the full URL for this http request. - curl_easy_setopt(curl, CURLOPT_URL, queryUrl.c_str()); - - std::string bodyBuffer; - std::string headerBuffer; - - // Set up buffers to write the response headers and body. - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HttpCurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &bodyBuffer); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HttpCurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, &headerBuffer); - - // Add all the headers for the request. - curl_slist* headers = nullptr; - - // Content-Type header for POST-like requests. - if (HttpRequestMethod::UsesCurlPostOptions(requestParameters.method) && !requestParameters.body.empty()) - { - headers = curl_slist_append(headers, fmt::format("Content-Type: {}", requestParameters.contentType).c_str()); - } - - for (const auto& kv : requestParameters.headers) - { - for (const std::string& headerValue : kv.second) - { - headers = curl_slist_append(headers, fmt::format("{}: {}", kv.first, headerValue).c_str()); - } - } - - if (headers != nullptr) - { - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - } - - // Disable SSL checks if requested by the user. - if (DisableHttpSsl()) - { - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 0L); - } - - // Enforce the Northstar user agent, unless an override was specified. - if (requestParameters.userAgent.empty()) - { - curl_easy_setopt(curl, CURLOPT_USERAGENT, &NSUserAgent); - } - else - { - curl_easy_setopt(curl, CURLOPT_USERAGENT, requestParameters.userAgent.c_str()); - } - - // Set the timeout for this request. Max 60 seconds so mods can't just spin up native threads all the time. - curl_easy_setopt(curl, CURLOPT_TIMEOUT, std::clamp<long>(requestParameters.timeout, 1, 60)); - - CURLcode result = curl_easy_perform(curl); - if (IsRunning()) - { - if (result == CURLE_OK) - { - // While the curl request is OK, it could return a non success code. - // Squirrel side will handle firing the correct callback. - long httpCode = 0; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); - g_pSquirrel<context>->AsyncCall( - "NSHandleSuccessfulHttpRequest", handle, static_cast<int>(httpCode), bodyBuffer, headerBuffer); - } - else - { - // Pass CURL result code & error. - spdlog::error( - "curl_easy_perform() failed with code {}, error: {}", static_cast<int>(result), curl_easy_strerror(result)); - - // If it's an SSL issue, tell the user they may disable SSL checks using -disablehttpssl. - if (result == CURLE_PEER_FAILED_VERIFICATION || result == CURLE_SSL_CERTPROBLEM || - result == CURLE_SSL_INVALIDCERTSTATUS) - { - spdlog::error("You can try disabling SSL verifications for this issue using the -disablehttpssl launch argument. " - "Keep in mind this is potentially dangerous!"); - } - - g_pSquirrel<context>->AsyncCall( - "NSHandleFailedHttpRequest", handle, static_cast<int>(result), curl_easy_strerror(result)); - } - } - - curl_easy_cleanup(curl); - curl_slist_free_all(headers); - curl_slist_free_all(host); - }); - - requestThread.detach(); - return handle; -} - -template <ScriptContext context> void HttpRequestHandler::RegisterSQFuncs() -{ - g_pSquirrel<context>->AddFuncRegistration( - "int", - "NS_InternalMakeHttpRequest", - "int method, string baseUrl, table<string, array<string> > headers, table<string, array<string> > queryParams, string contentType, " - "string body, " - "int timeout, string userAgent", - "[Internal use only] Passes the HttpRequest struct fields to be reconstructed in native and used for an http request", - SQ_InternalMakeHttpRequest<context>); - - g_pSquirrel<context>->AddFuncRegistration( - "bool", - "NSIsHttpEnabled", - "", - "Whether or not HTTP requests are enabled. You can opt-out by starting the game with -disablehttprequests.", - SQ_IsHttpEnabled<context>); - - g_pSquirrel<context>->AddFuncRegistration( - "bool", - "NSIsLocalHttpAllowed", - "", - "Whether or not HTTP requests can be made to a private network address. You can enable this by starting the game with " - "-allowlocalhttp.", - SQ_IsLocalHttpAllowed<context>); -} - -// int NS_InternalMakeHttpRequest(int method, string baseUrl, table<string, string> headers, table<string, string> queryParams, -// string contentType, string body, int timeout, string userAgent) -template <ScriptContext context> SQRESULT SQ_InternalMakeHttpRequest(HSquirrelVM* sqvm) -{ - if (!g_httpRequestHandler || !g_httpRequestHandler->IsRunning()) - { - spdlog::warn("NS_InternalMakeHttpRequest called while the http request handler isn't running."); - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; - } - - if (IsHttpDisabled()) - { - spdlog::warn("NS_InternalMakeHttpRequest called while the game is running with -disablehttprequests." - " Please check if requests are allowed using NSIsHttpEnabled() first."); - g_pSquirrel<context>->pushinteger(sqvm, -1); - return SQRESULT_NOTNULL; - } - - HttpRequest request; - request.method = static_cast<HttpRequestMethod::Type>(g_pSquirrel<context>->getinteger(sqvm, 1)); - request.baseUrl = g_pSquirrel<context>->getstring(sqvm, 2); - - // Read the tables for headers and query parameters. - SQTable* headerTable = sqvm->_stackOfCurrentFunction[3]._VAL.asTable; - for (int idx = 0; idx < headerTable->_numOfNodes; ++idx) - { - tableNode* node = &headerTable->_nodes[idx]; - - if (node->key._Type == OT_STRING && node->val._Type == OT_ARRAY) - { - SQArray* valueArray = node->val._VAL.asArray; - std::vector<std::string> headerValues; - - for (int vIdx = 0; vIdx < valueArray->_usedSlots; ++vIdx) - { - if (valueArray->_values[vIdx]._Type == OT_STRING) - { - headerValues.push_back(valueArray->_values[vIdx]._VAL.asString->_val); - } - } - - request.headers[node->key._VAL.asString->_val] = headerValues; - } - } - - SQTable* queryTable = sqvm->_stackOfCurrentFunction[4]._VAL.asTable; - for (int idx = 0; idx < queryTable->_numOfNodes; ++idx) - { - tableNode* node = &queryTable->_nodes[idx]; - - if (node->key._Type == OT_STRING && node->val._Type == OT_ARRAY) - { - SQArray* valueArray = node->val._VAL.asArray; - std::vector<std::string> queryValues; - - for (int vIdx = 0; vIdx < valueArray->_usedSlots; ++vIdx) - { - if (valueArray->_values[vIdx]._Type == OT_STRING) - { - queryValues.push_back(valueArray->_values[vIdx]._VAL.asString->_val); - } - } - - request.queryParameters[node->key._VAL.asString->_val] = queryValues; - } - } - - request.contentType = g_pSquirrel<context>->getstring(sqvm, 5); - request.body = g_pSquirrel<context>->getstring(sqvm, 6); - request.timeout = g_pSquirrel<context>->getinteger(sqvm, 7); - request.userAgent = g_pSquirrel<context>->getstring(sqvm, 8); - - int handle = g_httpRequestHandler->MakeHttpRequest<context>(request); - g_pSquirrel<context>->pushinteger(sqvm, handle); - return SQRESULT_NOTNULL; -} - -// bool NSIsHttpEnabled() -template <ScriptContext context> SQRESULT SQ_IsHttpEnabled(HSquirrelVM* sqvm) -{ - g_pSquirrel<context>->pushbool(sqvm, !IsHttpDisabled()); - return SQRESULT_NOTNULL; -} - -// bool NSIsLocalHttpAllowed() -template <ScriptContext context> SQRESULT SQ_IsLocalHttpAllowed(HSquirrelVM* sqvm) -{ - g_pSquirrel<context>->pushbool(sqvm, IsLocalHttpAllowed()); - return SQRESULT_NOTNULL; -} - -ON_DLL_LOAD_RELIESON("client.dll", HttpRequestHandler_ClientInit, ClientSquirrel, (CModule module)) -{ - g_httpRequestHandler->RegisterSQFuncs<ScriptContext::CLIENT>(); - g_httpRequestHandler->RegisterSQFuncs<ScriptContext::UI>(); -} - -ON_DLL_LOAD_RELIESON("server.dll", HttpRequestHandler_ServerInit, ServerSquirrel, (CModule module)) -{ - g_httpRequestHandler->RegisterSQFuncs<ScriptContext::SERVER>(); -} - -ON_DLL_LOAD("engine.dll", HttpRequestHandler_Init, (CModule module)) -{ - g_httpRequestHandler = new HttpRequestHandler; - g_httpRequestHandler->StartHttpRequestHandler(); -} |