From 07b2f8a6f9f0dd783c387946ac4886024685c238 Mon Sep 17 00:00:00 2001 From: Zentro Date: Sat, 1 Feb 2025 22:15:01 -0600 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=8E=89=20Api:=20new=20`Api::Client`?= =?UTF-8?q?=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/server/api.cpp | 255 ++++++++++++++++++++++++++++++++++++ source/server/api.h | 91 +++++++++++++ source/server/rorserver.cpp | 2 + 3 files changed, 348 insertions(+) create mode 100644 source/server/api.cpp create mode 100644 source/server/api.h diff --git a/source/server/api.cpp b/source/server/api.cpp new file mode 100644 index 00000000..b5b34c5b --- /dev/null +++ b/source/server/api.cpp @@ -0,0 +1,255 @@ +/* + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2025 Petr Ohlidal + + For more information, see http://www.rigsofrods.org/ + + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. + + Rigs of Rods is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Rigs of Rods. If not, see . +*/ +/// @file api.c +#include "api.h" +#include "logger.h" +#include "json/json.h" + +#include +#include + +size_t CurlStringWriteFunc(void *ptr, size_t size, size_t nmemb, std::string *data) +{ + data->append((char *)ptr, size * nmemb); + return size * nmemb; +} + +namespace Api +{ + Client::Client() {} + + /** + * \brief Call the API to retrieve our server public IP address. + * + * \param ip_addr + */ + bool Client::GetIpAddress(std::string &ip_addr) + { + } + + /** + * \brief Call the API to register our server with the server list. + * + * \return ApiErrorState Error state from the API. + */ + ApiErrorState Client::CreateServer() + { + HttpRequest request; + HttpResponse response; + ApiErrorState error_code; + + Json::Value data(Json::objectValue); + data["name"]; + data["ip"]; + data["port"]; + data["version"]; + data["description"]; + data["max_clients"]; + data["has_password"]; + + request = this->BuildHttpRequestQuery(HttpMethod::POST, + "/servers", + "", + data.asString()); + + response = this->ApiHttpQuery(request); + error_code = this->HandleHttpRequestErrors(response); + + return error_code; + } + + /** + * \brief Call the API to update our server with the server list. + * + * \return ApiErrorState Error state from the API. + */ + ApiErrorState Client::UpdateServer() + { + HttpRequest request; + HttpResponse response; + ApiErrorState error_code; + + Json::Value data(Json::objectValue); + request.body = data.asString(); + + response = this->ApiHttpQuery(request); + error_code = this->HandleHttpRequestErrors(response); + + return error_code; + } + + /** + * \brief Call the API to sync server statuses with the server list. + * + * \return ApiErrorState Error state from the API. + */ + ApiErrorState Client::SyncServer() + { + HttpRequest request; + HttpResponse response; + ApiErrorState error_code; + + Json::Value data(Json::objectValue); + request.method = HttpMethod::POST; + request.body = data.asString(); + + response = this->ApiHttpQuery(request); + error_code = this->HandleHttpRequestErrors(response); + + return error_code; + } + + /** + * \brief Call the API to sync the server power state with the server list. + * + * \return ApiErrorState Error state from the API. + */ + ApiErrorState Client::SyncServerPowerState() + { + HttpRequest request; + HttpResponse response; + ApiErrorState error_code; + + Json::Value data(Json::objectValue); + request.body = data.asString(); + + response = this->ApiHttpQuery(request); + error_code = this->HandleHttpRequestErrors(response); + + return error_code; + } + + /** + * \brief Execute an API HTTP query. + * + * \param request The HTTP request to execute. + * \return HttpResponse The HTTP response received from the API query. + */ + Client::HttpResponse Client::ApiHttpQuery(HttpRequest &request) + { + HttpResponse response; + CURLcode curl_result; + + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, HttpMethodToString(request.method)); + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); +#ifdef _WIN32 + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); +#endif // _WIN32 + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, request.user_agent.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlStringWriteFunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body); + + curl_result = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.status_code); + + curl_easy_cleanup(curl); + curl = nullptr; + + Logger::Log(LOG_INFO, ""); + Logger::Log(LOG_DEBUG, ""); + + if (curl_result != CURLE_OK) + { + Logger::Log(LOG_ERROR, ""); + } + return response; + } + + /** + * \brief Handles and returns the API error code status. + * + * \param response The HTTP response + * \return ApiErrorState Error state from the API + */ + ApiErrorState Client::HandleHttpRequestErrors(HttpResponse &response) + { + ApiErrorState error_code; + LogLevel log_level; + + if (!this->HasError(response.status_code)) + { + error_code = API_NO_ERROR; + } + + if (response.status_code >= 400 && response.status_code < 500) + { + error_code = API_CLIENT_ERROR; + } + + else if (response.status_code >= 500) + { + error_code = API_SERVER_ERROR; + } + + return error_code; + } + + /** + * \brief Checks whether the provided status code represents an error. + * + * \param status_code The status code to check. + * \return True if there is an error, false otherwise. + */ + bool Client::HasError(int status_code) + { + return status_code >= 300 || status_code < 200; + } + + /** + * \brief Build the HTTP request query. + * + * \param method The HTTP method. + * \param uri The URI of the request. + * \param headers The headers of the request. + * \param body The body of the request. + * \return HttpRequest The HTTP request. + */ + Client::HttpRequest Client::BuildHttpRequestQuery(HttpMethod method, std::string uri, std::string headers, std::string body) + { + HttpRequest request; + } + + /** + * \brief Returns a string representation of an HTTP method. + * + * \param HttpMethod The HTTP method. + * \return The HTTP method as a string. + */ + const char *Client::HttpMethodToString(HttpMethod method) + { + switch (method) + { + case HttpMethod::GET: + return "GET"; + case HttpMethod::DELETE: + return "DELETE"; + case HttpMethod::POST: + return "POST"; + case HttpMethod::PUT: + return "PUT"; + default: + return "UNKNOWN"; + } + } +} diff --git a/source/server/api.h b/source/server/api.h new file mode 100644 index 00000000..34ae66ef --- /dev/null +++ b/source/server/api.h @@ -0,0 +1,91 @@ +/* + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2025 Petr Ohlidal + + For more information, see http://www.rigsofrods.org/ + + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. + + Rigs of Rods is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Rigs of Rods. If not, see . +*/ +///@file api.h + +#include + +namespace Api +{ + /** + * \brief Enum representing different API error states + */ + enum ApiErrorState + { + API_NO_ERROR = 0, + API_CLIENT_ERROR = 1, + API_SERVER_ERROR = 2, + API_ERROR_UNKNOWN = 999, + }; + class Client + { + /** + * \brief Enum representing different HTTP methods + */ + enum HttpMethod + { + GET, + POST, + PUT, + DELETE + }; + + /** + * \brief Sructure representing an HTTP response + */ + struct HttpResponse + { + int status_code; + std::string body; + std::string headers; + }; + + /** + * \brief Structure representing an HTTP request + */ + struct HttpRequest + { + std::string body; + std::string url = "https://v2.api.rigsofrods.org/"; + std::string headers; + std::string user_agent = "Rigs of Rods Server"; + HttpMethod method = HttpMethod::GET; + std::string content_type = "Content-Type: application/json"; + }; + + public: + Client(); + bool GetIpAddress(std::string &ip_addr); + ApiErrorState CreateServer(); + ApiErrorState UpdateServer(); + ApiErrorState SyncServer(); + ApiErrorState SyncServerPowerState(); + ApiErrorState CreateClient(); + + private: + ApiErrorState HandleHttpRequestErrors(HttpResponse &response); + bool HasError(int status_code); + HttpRequest BuildHttpRequestQuery(HttpMethod method, std::string uri, std::string headers, std::string body); + HttpResponse ApiHttpQuery(HttpRequest &request); + const char *HttpMethodToString(HttpMethod method); + std::string m_api_key; + bool m_api_active; + }; +} // namespace Api diff --git a/source/server/rorserver.cpp b/source/server/rorserver.cpp index 62de763f..c8f054ff 100644 --- a/source/server/rorserver.cpp +++ b/source/server/rorserver.cpp @@ -28,6 +28,7 @@ along with Foobar. If not, see . #include "listener.h" #include "master-server.h" #include "utils.h" +#include "api.h" #include "sha1_util.h" #include "sha1.h" @@ -58,6 +59,7 @@ along with Foobar. If not, see . static Sequencer s_sequencer; static MasterServer::Client s_master_server; +static Api::Client s_api; static bool s_exit_requested = false; #ifndef _WIN32 From 93db2fc1587c3cb19da248cc2b3adb295217dcaa Mon Sep 17 00:00:00 2001 From: Zentro Date: Sun, 9 Feb 2025 15:58:32 -0600 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=92=A5=20Api:=20begin=20removing=20`M?= =?UTF-8?q?asterServer`=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/server/api.cpp | 111 +++++++++++++++++++++++++++++++----- source/server/api.h | 31 +++++----- source/server/rorserver.cpp | 9 +-- source/server/userauth.cpp | 49 ++++++---------- 4 files changed, 138 insertions(+), 62 deletions(-) diff --git a/source/server/api.cpp b/source/server/api.cpp index b5b34c5b..ccc5fcad 100644 --- a/source/server/api.cpp +++ b/source/server/api.cpp @@ -21,8 +21,11 @@ /// @file api.c #include "api.h" #include "logger.h" +#include "config.h" #include "json/json.h" +#include + #include #include @@ -37,12 +40,36 @@ namespace Api Client::Client() {} /** - * \brief Call the API to retrieve our server public IP address. + * \brief Call the API to retrieve our server public IP. * * \param ip_addr */ - bool Client::GetIpAddress(std::string &ip_addr) + bool Client::GetPublicIp(std::string &ip_addr) { + return true; + } + + /** + * \brief Call the API to determine if it's callable. + * + * \return bool True or False if we can call the API. + */ + bool Client::Callable() + { + return true; + } + + /** + * \brief Check whether we've already authenticated against the API. + * This is typically done to ensure we don't call CreateServer() + * again. This condition is satsified if an API key and server + * unique identifier is present. + * + * \return bool True or False if we're authenticated againsted the API. + */ + bool Client::Authenticated() + { + return true; } /** @@ -56,14 +83,17 @@ namespace Api HttpResponse response; ApiErrorState error_code; + char url[300] = ""; + sprintf(url, "%s/servers", Config::GetServerlistHostC()); + Json::Value data(Json::objectValue); - data["name"]; - data["ip"]; - data["port"]; - data["version"]; - data["description"]; - data["max_clients"]; - data["has_password"]; + data["name"] = Config::getServerName(); + data["ip"] = Config::getIPAddr(); + data["port"] = Config::getListenPort(); + data["version"] = RORNET_VERSION; + data["description"] = "This is temp"; + data["max_clients"] = Config::getMaxClients(); + data["has_password"] = Config::isPublic(); request = this->BuildHttpRequestQuery(HttpMethod::POST, "/servers", @@ -85,10 +115,17 @@ namespace Api { HttpRequest request; HttpResponse response; - ApiErrorState error_code; + ApiErrorState error_code; + + char url[300] = ""; + sprintf(url, "%s/servers/%d", Config::GetServerlistHostC(), 2); Json::Value data(Json::objectValue); - request.body = data.asString(); + + request = this->BuildHttpRequestQuery(HttpMethod::PUT, + url, + "", + data.asString()); response = this->ApiHttpQuery(request); error_code = this->HandleHttpRequestErrors(response); @@ -107,9 +144,14 @@ namespace Api HttpResponse response; ApiErrorState error_code; - Json::Value data(Json::objectValue); - request.method = HttpMethod::POST; - request.body = data.asString(); + // Verify the power status of the server later... + char url[300] = ""; + sprintf(url, "%s/servers/%d/sync", Config::GetServerlistHostC(), 2); + + request = this->BuildHttpRequestQuery(HttpMethod::PATCH, + url, + "", + ""); response = this->ApiHttpQuery(request); error_code = this->HandleHttpRequestErrors(response); @@ -120,17 +162,56 @@ namespace Api /** * \brief Call the API to sync the server power state with the server list. * + * \param status The current power state to report to the server list. * \return ApiErrorState Error state from the API. */ - ApiErrorState Client::SyncServerPowerState() + ApiErrorState Client::SyncServerPowerState(std::string status) { HttpRequest request; HttpResponse response; ApiErrorState error_code; + // We should look into verifying the power status later... + // If we make a weird request while in a state of limbo where the API + // is unable to reach us, we need to avoid making that wasteful call. Json::Value data(Json::objectValue); + data["power_status"] = status; request.body = data.asString(); + // When we send a power status of "online" we are telling the API that + // we're ready to accept people and can be publicly displayed. Otherwise + // we remain hidden. + response = this->ApiHttpQuery(request); + error_code = this->HandleHttpRequestErrors(response); + + return error_code; + } + + /** + * \brief Call the API to verify a challenge for a client. + * + * \param challenge The challenge to us by the client. + * \return ApiErrorState Error state of the API. + */ + ApiErrorState Client::VerifyClientSession(std::string challenge) + { + HttpRequest request; + HttpResponse response; + ApiErrorState error_code; + + // Need to maybe look into whether or not C++20 has better string formatting? + char url[300] = ""; + sprintf(url, "%s/auth/session/%d/verify", Config::GetServerlistHostC(), 2); + + // We don't actually know what is in the claims of the challenge, so + // we'll wait for the API to return a pass or fail on them. + Json::Value data(Json::objectValue); + data["challenge"] = challenge; + request = this->BuildHttpRequestQuery(HttpMethod::GET, + url, + "", + data.asString()); + response = this->ApiHttpQuery(request); error_code = this->HandleHttpRequestErrors(response); diff --git a/source/server/api.h b/source/server/api.h index 34ae66ef..405ea72a 100644 --- a/source/server/api.h +++ b/source/server/api.h @@ -22,18 +22,19 @@ #include +/** + * \brief Enum representing different API error states + */ +enum ApiErrorState +{ + API_NO_ERROR = 0, + API_CLIENT_ERROR = 1, + API_SERVER_ERROR = 2, + API_ERROR_UNKNOWN = 999, +}; + namespace Api { - /** - * \brief Enum representing different API error states - */ - enum ApiErrorState - { - API_NO_ERROR = 0, - API_CLIENT_ERROR = 1, - API_SERVER_ERROR = 2, - API_ERROR_UNKNOWN = 999, - }; class Client { /** @@ -44,7 +45,8 @@ namespace Api GET, POST, PUT, - DELETE + DELETE, + PATCH }; /** @@ -72,12 +74,15 @@ namespace Api public: Client(); - bool GetIpAddress(std::string &ip_addr); + bool GetPublicIp(std::string &ip_addr); + bool Callable(); + bool Authenticated(); ApiErrorState CreateServer(); ApiErrorState UpdateServer(); ApiErrorState SyncServer(); - ApiErrorState SyncServerPowerState(); + ApiErrorState SyncServerPowerState(std::string status); ApiErrorState CreateClient(); + ApiErrorState VerifyClientSession(std::string challenge); private: ApiErrorState HandleHttpRequestErrors(HttpResponse &response); diff --git a/source/server/rorserver.cpp b/source/server/rorserver.cpp index c8f054ff..fa3fccd2 100644 --- a/source/server/rorserver.cpp +++ b/source/server/rorserver.cpp @@ -92,8 +92,9 @@ void handler(int signalnum) { s_sequencer.Close(); } else { Logger::Log(LOG_INFO, "closing server ... unregistering ... "); - if (s_master_server.IsRegistered()) { - s_master_server.UnRegister(); + // We should really have a global var for the server status... + if (s_api.Authenticated()) { + s_api.SyncServerPowerState("offline"); } s_sequencer.Close(); } @@ -126,10 +127,10 @@ BOOL WINAPI WindowsConsoleHandlerRoutine(DWORD ctrl_type) return TRUE; // Means 'event handled' } - if (s_master_server.IsRegistered()) + if (s_api.Authenticated()) { Logger::Log(LOG_INFO, "Unregistering..."); - s_master_server.UnRegister(); + s_api.SyncServerPowerState("offline"); } s_sequencer.Close(); // TODO: This somehow closes (crashes?) the process on Windows, debugger doesn't intercept anything... Logger::Log(LOG_INFO, "Clean exit (Windows)"); diff --git a/source/server/userauth.cpp b/source/server/userauth.cpp index 4f38fcfd..86847071 100644 --- a/source/server/userauth.cpp +++ b/source/server/userauth.cpp @@ -23,7 +23,7 @@ along with Foobar. If not, see . #include "config.h" #include "rornet.h" #include "logger.h" -#include "http.h" +#include "api.h" #include "json/json.h" #include @@ -36,6 +36,8 @@ along with Foobar. If not, see . #endif +static Api::Client s_api; + UserAuth::UserAuth(std::string authFile) { readConfig(authFile.c_str()); } @@ -152,41 +154,28 @@ int UserAuth::sendUserEvent(std::string user_token, std::string type, std::strin } int UserAuth::resolve(std::string user_token, std::string &user_nick, int clientid) { - // initialize the authlevel on none = normal user - int authlevel = RoRnet::AUTH_NONE; - - // contact the master server - char url[512]; - sprintf(url, "/%s/users", Config::GetServerlistPath().c_str()); - Logger::Log(LOG_INFO, "Attempting user authentication (%s)", url); - - Json::Value data(Json::objectValue); - data["username"] = user_nick; - data["user_token"] = user_token; - std::string json_str = data.toStyledString(); - - Http::Response resp; - int result_code = Http::Request(Http::METHOD_GET, - Config::GetServerlistHostC(), url, "application/json", - json_str.c_str(), &resp); - - // 200 means success! - if (result_code == 200) { - Logger::Log(LOG_INFO, "User authentication success, result code: %d", result_code); - authlevel = RoRnet::AUTH_RANKED; - } else { - Logger::Log(LOG_INFO, "User authentication failed, result code: %d", result_code); + int auth_level = RoRnet::AUTH_NONE; + + // The challenge and user token should be seperate... + // We'll call the API to verify the challenge the client sent, we should + // only get back API_NO_ERROR to indicate that the challenge could be + // verified. + ApiErrorState status = s_api.VerifyClientSession(user_token); + if (status == API_NO_ERROR) + { + auth_level = RoRnet::AUTH_RANKED; } - //then check for overrides in the authorizations file (server admins, etc) + // Then, we compare against the local authorizations file and override + // with what the server has for us. if (local_auth.find(user_token) != local_auth.end()) { - // local auth hit! - // the stored nickname can be empty if no nickname is specified. + // Check if the stored nick name is empty, and override with what + // is in the local file. if (!local_auth[user_token].second.empty()) user_nick = local_auth[user_token].second; - authlevel |= local_auth[user_token].first; + auth_level |= local_auth[user_token].first; } - return authlevel; + return auth_level; } From b9b98b7c86f503671d5612baef345733e3737486 Mon Sep 17 00:00:00 2001 From: Rafael Date: Mon, 17 Feb 2025 23:21:06 -0600 Subject: [PATCH 3/9] New session token WIP, changes to RoRnet --- source/protocol/rornet.h | 2 +- source/server/api.cpp | 116 +++++++++++++++++++----------------- source/server/api.h | 36 ++++++++--- source/server/config.cpp | 6 ++ source/server/config.h | 4 ++ source/server/listener.cpp | 2 +- source/server/rorserver.cpp | 4 +- source/server/sequencer.cpp | 4 +- source/server/sequencer.h | 2 +- source/server/userauth.cpp | 12 ++-- source/server/userauth.h | 2 +- 11 files changed, 114 insertions(+), 76 deletions(-) diff --git a/source/protocol/rornet.h b/source/protocol/rornet.h index e6a9350a..7b253d9b 100644 --- a/source/protocol/rornet.h +++ b/source/protocol/rornet.h @@ -172,7 +172,7 @@ struct UserInfo char clientversion[25]; //!< a version number of the client. For example 1 for RoR 0.35 char clientGUID[40]; //!< the clients GUID char sessiontype[10]; //!< the requested session type. For example "normal", "bot", "rcon" - char sessionoptions[128]; //!< reserved for future options + char sessiontoken[300]; //!< the session token to verify the client }; struct VehicleState //!< Formerly `oob_t` diff --git a/source/server/api.cpp b/source/server/api.cpp index ccc5fcad..58c2da30 100644 --- a/source/server/api.cpp +++ b/source/server/api.cpp @@ -46,7 +46,21 @@ namespace Api */ bool Client::GetPublicIp(std::string &ip_addr) { - return true; + HttpResponse response; + ApiErrorState error_code; + + HttpRequest request(HttpMethod::GET, "/ip"); + + response = this->ApiHttpQuery(request); + error_code = this->HandleHttpRequestErrors(response); + + if (error_code == API_NO_ERROR) + { + // OK, we got back a string that we can update the reference with ... + ip_addr = response.body; + }; + + return ( error_code != API_NO_ERROR ); } /** @@ -56,14 +70,21 @@ namespace Api */ bool Client::Callable() { - return true; + HttpResponse response; + ApiErrorState error_code; + + HttpRequest request(HttpMethod::GET, "/"); + + response = this->ApiHttpQuery(request); + error_code = this->HandleHttpRequestErrors(response); + + return ( error_code != API_NO_ERROR ); } /** - * \brief Check whether we've already authenticated against the API. - * This is typically done to ensure we don't call CreateServer() - * again. This condition is satsified if an API key and server - * unique identifier is present. + * \brief This will check if both the server unique identifier and a + * unique server identifier. The unique identifier is something we + * update and the API key is manually configured by the owner. * * \return bool True or False if we're authenticated againsted the API. */ @@ -79,13 +100,9 @@ namespace Api */ ApiErrorState Client::CreateServer() { - HttpRequest request; HttpResponse response; ApiErrorState error_code; - char url[300] = ""; - sprintf(url, "%s/servers", Config::GetServerlistHostC()); - Json::Value data(Json::objectValue); data["name"] = Config::getServerName(); data["ip"] = Config::getIPAddr(); @@ -95,10 +112,7 @@ namespace Api data["max_clients"] = Config::getMaxClients(); data["has_password"] = Config::isPublic(); - request = this->BuildHttpRequestQuery(HttpMethod::POST, - "/servers", - "", - data.asString()); + HttpRequest request(HttpMethod::POST, "/servers", data.asString()); response = this->ApiHttpQuery(request); error_code = this->HandleHttpRequestErrors(response); @@ -113,19 +127,15 @@ namespace Api */ ApiErrorState Client::UpdateServer() { - HttpRequest request; HttpResponse response; ApiErrorState error_code; char url[300] = ""; - sprintf(url, "%s/servers/%d", Config::GetServerlistHostC(), 2); + sprintf(url, "/servers/%d", 10000); Json::Value data(Json::objectValue); - request = this->BuildHttpRequestQuery(HttpMethod::PUT, - url, - "", - data.asString()); + HttpRequest request(HttpMethod::UPDATE, url, data.asString()); response = this->ApiHttpQuery(request); error_code = this->HandleHttpRequestErrors(response); @@ -140,18 +150,12 @@ namespace Api */ ApiErrorState Client::SyncServer() { - HttpRequest request; HttpResponse response; ApiErrorState error_code; - // Verify the power status of the server later... - char url[300] = ""; - sprintf(url, "%s/servers/%d/sync", Config::GetServerlistHostC(), 2); + Json::Value data(Json::objectValue); - request = this->BuildHttpRequestQuery(HttpMethod::PATCH, - url, - "", - ""); + HttpRequest request(HttpMethod::PATCH, "/servers", data.asString()); response = this->ApiHttpQuery(request); error_code = this->HandleHttpRequestErrors(response); @@ -167,7 +171,6 @@ namespace Api */ ApiErrorState Client::SyncServerPowerState(std::string status) { - HttpRequest request; HttpResponse response; ApiErrorState error_code; @@ -176,7 +179,8 @@ namespace Api // is unable to reach us, we need to avoid making that wasteful call. Json::Value data(Json::objectValue); data["power_status"] = status; - request.body = data.asString(); + + HttpRequest request(HttpMethod::UPDATE, "/servers", data.asString()); // When we send a power status of "online" we are telling the API that // we're ready to accept people and can be publicly displayed. Otherwise @@ -195,22 +199,18 @@ namespace Api */ ApiErrorState Client::VerifyClientSession(std::string challenge) { - HttpRequest request; HttpResponse response; ApiErrorState error_code; // Need to maybe look into whether or not C++20 has better string formatting? char url[300] = ""; - sprintf(url, "%s/auth/session/%d/verify", Config::GetServerlistHostC(), 2); + sprintf(url, "/auth/sessions/%s/verify", "ee1b920c-f815-4c9e-b5a2-b60db71dba88"); // We don't actually know what is in the claims of the challenge, so // we'll wait for the API to return a pass or fail on them. Json::Value data(Json::objectValue); - data["challenge"] = challenge; - request = this->BuildHttpRequestQuery(HttpMethod::GET, - url, - "", - data.asString()); + data["challenge"] = challenge.c_str(); // TODO: I really don't appreciate how funky this is ... + HttpRequest request(HttpMethod::GET, url, data.toStyledString()); response = this->ApiHttpQuery(request); error_code = this->HandleHttpRequestErrors(response); @@ -233,6 +233,24 @@ namespace Api curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, HttpMethodToString(request.method)); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + + if (!m_api_key_key.empty()) + { + // If the API key is present, we need to send it even if the call we're + // making does not require authentication. + request.headers.push_back("Authorization: Bearer " + m_api_key_key); + } + + // Let the API know we're prefering JSON or HTML to be sent back. + request.headers.push_back("Accept: application/json"); + + struct curl_slist *headers = nullptr; + for (const auto &header : request.headers) + { + headers = curl_slist_append(headers, header.c_str()); + } + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); #ifdef _WIN32 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); #endif // _WIN32 @@ -247,13 +265,11 @@ namespace Api curl_easy_cleanup(curl); curl = nullptr; - Logger::Log(LOG_INFO, ""); - Logger::Log(LOG_DEBUG, ""); - if (curl_result != CURLE_OK) { - Logger::Log(LOG_ERROR, ""); + Logger::Log(LOG_ERROR, "curl error!"); } + return response; } @@ -266,20 +282,22 @@ namespace Api ApiErrorState Client::HandleHttpRequestErrors(HttpResponse &response) { ApiErrorState error_code; - LogLevel log_level; if (!this->HasError(response.status_code)) { + Logger::Log(LOG_INFO, "no http warning"); error_code = API_NO_ERROR; } if (response.status_code >= 400 && response.status_code < 500) { + Logger::Log(LOG_ERROR, "client error http"); error_code = API_CLIENT_ERROR; } else if (response.status_code >= 500) { + Logger::Log(LOG_ERROR, "server error http"); error_code = API_SERVER_ERROR; } @@ -297,20 +315,6 @@ namespace Api return status_code >= 300 || status_code < 200; } - /** - * \brief Build the HTTP request query. - * - * \param method The HTTP method. - * \param uri The URI of the request. - * \param headers The headers of the request. - * \param body The body of the request. - * \return HttpRequest The HTTP request. - */ - Client::HttpRequest Client::BuildHttpRequestQuery(HttpMethod method, std::string uri, std::string headers, std::string body) - { - HttpRequest request; - } - /** * \brief Returns a string representation of an HTTP method. * diff --git a/source/server/api.h b/source/server/api.h index 405ea72a..3314051c 100644 --- a/source/server/api.h +++ b/source/server/api.h @@ -21,6 +21,9 @@ ///@file api.h #include +#include + +#include /** * \brief Enum representing different API error states @@ -30,7 +33,7 @@ enum ApiErrorState API_NO_ERROR = 0, API_CLIENT_ERROR = 1, API_SERVER_ERROR = 2, - API_ERROR_UNKNOWN = 999, + API_UNKNOWN_ERROR = 999, }; namespace Api @@ -46,7 +49,8 @@ namespace Api POST, PUT, DELETE, - PATCH + PATCH, + UPDATE }; /** @@ -64,12 +68,27 @@ namespace Api */ struct HttpRequest { - std::string body; - std::string url = "https://v2.api.rigsofrods.org/"; - std::string headers; - std::string user_agent = "Rigs of Rods Server"; HttpMethod method = HttpMethod::GET; - std::string content_type = "Content-Type: application/json"; + std::string url; + std::string body; + std::vector headers; + std::string content_type; + std::string user_agent; + + HttpRequest(HttpMethod method, + const std::string &uri, + const std::string &body = "", + const std::vector &headers = {}, + const std::string &content_type = "Content-Type: application/json", + const std::string &user_agent = std::string("Rigs of Rods Server/") + RORNET_VERSION) + : method(method), + url("http://127.0.0.1:8080" + uri), + body(body), + headers(headers), + content_type(content_type), + user_agent(user_agent) + { + } }; public: @@ -87,10 +106,9 @@ namespace Api private: ApiErrorState HandleHttpRequestErrors(HttpResponse &response); bool HasError(int status_code); - HttpRequest BuildHttpRequestQuery(HttpMethod method, std::string uri, std::string headers, std::string body); HttpResponse ApiHttpQuery(HttpRequest &request); const char *HttpMethodToString(HttpMethod method); - std::string m_api_key; + std::string m_api_key_key; bool m_api_active; }; } // namespace Api diff --git a/source/server/config.cpp b/source/server/config.cpp index e713aeb9..7af82104 100644 --- a/source/server/config.cpp +++ b/source/server/config.cpp @@ -85,6 +85,8 @@ static int s_spamfilter_msg_interval_sec(0); // 0 disables spamfilter static int s_spamfilter_msg_count(0); // 0 disables spamfilter static int s_spamfilter_gag_duration_sec(10); +static std::string s_api_key_key(""); + // ============================== Functions =================================== namespace Config { @@ -439,6 +441,8 @@ namespace Config { void setSpamFilterGagDurationSec(int sec) { s_spamfilter_gag_duration_sec = sec; } + void setApiKeyKey(const std::string &key) { s_api_key_key = key; } + void setHeartbeatIntervalSec(unsigned sec) { s_heartbeat_interval_sec = sec; Logger::Log(LOG_VERBOSE, "Hearbeat interval is %d seconds", sec); @@ -521,6 +525,8 @@ namespace Config { else if (strcmp(key, "spamfilter-msg-count") == 0) { setSpamFilterMsgCount(VAL_INT(value)); } else if (strcmp(key, "spamfilter-gag-duration") == 0) { setSpamFilterGagDurationSec(VAL_INT(value)); } + else if (strcmp(key, "apikey") == 0) { setApiKeyKey(VAL_STR(value)); } + else { Logger::Log(LOG_WARN, "Unknown key '%s' (value: '%s') in config file.", key, value); } diff --git a/source/server/config.h b/source/server/config.h index 0d098de8..0fa66418 100644 --- a/source/server/config.h +++ b/source/server/config.h @@ -97,6 +97,8 @@ namespace Config { unsigned int GetHeartbeatIntervalSec(); + const std::string &GetApiKeyKey(); + bool GetShowHelp(); bool GetShowVersion(); @@ -165,6 +167,8 @@ namespace Config { void setSpamFilterMsgIntervalSec(int sec); void setSpamFilterMsgCount(int count); void setSpamFilterGagDurationSec(int sec); + + void setApiKeyKey(const std::string &key); //!@} } // namespace Config diff --git a/source/server/listener.cpp b/source/server/listener.cpp index 1a2b6942..df5d73ab 100644 --- a/source/server/listener.cpp +++ b/source/server/listener.cpp @@ -192,7 +192,7 @@ void Listener::ThreadMain() { // authenticate user->username[RORNET_MAX_USERNAME_LEN - 1] = 0; std::string nickname = Str::SanitizeUtf8(user->username); - user->authstatus = m_sequencer->AuthorizeNick(std::string(user->usertoken, 40), nickname); + user->authstatus = m_sequencer->AuthorizeNick(std::string(user->usertoken, 40), std::string(user->sessiontoken, 300), nickname); strncpy(user->username, nickname.c_str(), RORNET_MAX_USERNAME_LEN - 1); if (Config::isPublic()) { diff --git a/source/server/rorserver.cpp b/source/server/rorserver.cpp index fa3fccd2..80511f9d 100644 --- a/source/server/rorserver.cpp +++ b/source/server/rorserver.cpp @@ -20,6 +20,8 @@ along with Foobar. If not, see . // RoRserver.cpp : Defines the entry point for the console application. +// TODO: the way this entire file is formatted makes my head HURT ... CHANGE !!! + #include "rornet.h" #include "sequencer.h" #include "logger.h" @@ -267,7 +269,7 @@ int main(int argc, char *argv[]) { std::string ip_addr = Config::getIPAddr(); if (ip_addr.empty() || (ip_addr == "0.0.0.0")) { Logger::Log(LOG_WARN, "No IP given, detecting..."); - if (!MasterServer::RetrievePublicIp()) { + if (s_api.GetPublicIp(ip_addr) != API_NO_ERROR) { Logger::Log(LOG_ERROR, "Failed to auto-detect public IP, exit."); return -1; } diff --git a/source/server/sequencer.cpp b/source/server/sequencer.cpp index 2345e01a..74b13474 100644 --- a/source/server/sequencer.cpp +++ b/source/server/sequencer.cpp @@ -434,12 +434,12 @@ int Sequencer::getNumClients() { return (int) m_clients.size(); } -int Sequencer::AuthorizeNick(std::string token, std::string &nickname) { +int Sequencer::AuthorizeNick(const std::string& user_token, const std::string& session_token, std::string &nickname) { std::lock_guard scoped_lock(m_clients_mutex); if (m_auth_resolver == nullptr) { return RoRnet::AUTH_NONE; } - return m_auth_resolver->resolve(token, nickname, m_free_user_id); + return m_auth_resolver->resolve(user_token, session_token, nickname, m_free_user_id); } void Sequencer::KillerThreadMain() diff --git a/source/server/sequencer.h b/source/server/sequencer.h index d6395728..a37145f7 100644 --- a/source/server/sequencer.h +++ b/source/server/sequencer.h @@ -216,7 +216,7 @@ class Sequencer { void frameStepScripts(float dt); void GetHeartbeatUserList(Json::Value &out_array); void UpdateMinuteStats(); - int AuthorizeNick(std::string token, std::string &nickname); + int AuthorizeNick(const std::string& user_token, const std::string& session_token, std::string &nickname); std::vector GetClientListCopy(); int getStartTime(); diff --git a/source/server/userauth.cpp b/source/server/userauth.cpp index 86847071..cd4ec3c2 100644 --- a/source/server/userauth.cpp +++ b/source/server/userauth.cpp @@ -153,17 +153,21 @@ int UserAuth::sendUserEvent(std::string user_token, std::string type, std::strin return -1; } -int UserAuth::resolve(std::string user_token, std::string &user_nick, int clientid) { +int UserAuth::resolve(const std::string& user_token, const std::string& session_token, std::string &user_nick, int clientid) { int auth_level = RoRnet::AUTH_NONE; // The challenge and user token should be seperate... // We'll call the API to verify the challenge the client sent, we should // only get back API_NO_ERROR to indicate that the challenge could be // verified. - ApiErrorState status = s_api.VerifyClientSession(user_token); - if (status == API_NO_ERROR) + if (!session_token.empty() || session_token[0] == '\000') { - auth_level = RoRnet::AUTH_RANKED; + ApiErrorState status = s_api.VerifyClientSession(session_token); + if (status == API_NO_ERROR) + { + Logger::Log(LOG_INFO, "%s was assigned RANKED", user_nick); + auth_level = RoRnet::AUTH_RANKED; + } } // Then, we compare against the local authorizations file and override diff --git a/source/server/userauth.h b/source/server/userauth.h index 02f8a0ee..ab6a3fef 100644 --- a/source/server/userauth.h +++ b/source/server/userauth.h @@ -32,7 +32,7 @@ class UserAuth { public: UserAuth(std::string authFile); - int resolve(std::string user_token, std::string &user_nick, int clientid); + int resolve(const std::string& user_token, const std::string& session_token, std::string &user_nick, int clientid); int setUserAuth(int flags, std::string user_nick, std::string token); From 352126963c61e89a9019c3779937d72bd50b2f7d Mon Sep 17 00:00:00 2001 From: Rafael Date: Sun, 23 Feb 2025 13:30:40 -0600 Subject: [PATCH 4/9] wip: changes --- source/server/config.h | 41 +++++++++++++++--------- source/server/rorserver.cpp | 63 +++++++++++++++++++++++++------------ 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/source/server/config.h b/source/server/config.h index 0fa66418..ca5a0198 100644 --- a/source/server/config.h +++ b/source/server/config.h @@ -1,34 +1,47 @@ /* -This file is part of "Rigs of Rods Server" (Relay mode) + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2025 Petr Ohlidal -Copyright 2007 Pierre-Michel Ricordel -Copyright 2014+ Rigs of Rods Community + For more information, see http://www.rigsofrods.org/ -"Rigs of Rods Server" is free software: you can redistribute it -and/or modify it under the terms of the GNU General Public License -as published by the Free Software Foundation, either version 3 -of the License, or (at your option) any later version. + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. -"Rigs of Rods Server" is distributed in the hope that it will -be useful, but WITHOUT ANY WARRANTY; without even the implied -warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU General Public License for more details. + Rigs of Rods is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -You should have received a copy of the GNU General Public License -along with Foobar. If not, see . + You should have received a copy of the GNU General Public License + along with Rigs of Rods. If not, see . */ +//// \file config.h #pragma once #include "UnicodeStrings.h" -// server modes +/** + * \brief Enum representing the server type + */ enum ServerType { SERVER_LAN = 0, SERVER_INET, SERVER_AUTO }; +/** + * \brief Enum representing the server power state + */ +enum ServerState { + SERVER_STARTING = 0, + SERVER_RUNNING, + SERVER_STOPPING, +}; + namespace Config { //! runs a check that all the required fields are present diff --git a/source/server/rorserver.cpp b/source/server/rorserver.cpp index 80511f9d..11b528f3 100644 --- a/source/server/rorserver.cpp +++ b/source/server/rorserver.cpp @@ -1,26 +1,28 @@ /* -This file is part of "Rigs of Rods Server" (Relay mode) + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2025 Petr Ohlidal -Copyright 2007 Pierre-Michel Ricordel -Copyright 2014+ Rigs of Rods Community + For more information, see http://www.rigsofrods.org/ -"Rigs of Rods Server" is free software: you can redistribute it -and/or modify it under the terms of the GNU General Public License -as published by the Free Software Foundation, either version 3 -of the License, or (at your option) any later version. + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. -"Rigs of Rods Server" is distributed in the hope that it will -be useful, but WITHOUT ANY WARRANTY; without even the implied -warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU General Public License for more details. + Rigs of Rods is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -You should have received a copy of the GNU General Public License -along with Foobar. If not, see . + You should have received a copy of the GNU General Public License + along with Rigs of Rods. If not, see . */ - -// RoRserver.cpp : Defines the entry point for the console application. - -// TODO: the way this entire file is formatted makes my head HURT ... CHANGE !!! +/// @file rorserver.cpp +/// @brief This file defines the entry point for the console application. +/// @author Pierre-Michel Ricordel +/// @author Thomas Fisher, +/// @author Petr Ohlidal #include "rornet.h" #include "sequencer.h" @@ -61,7 +63,7 @@ along with Foobar. If not, see . static Sequencer s_sequencer; static MasterServer::Client s_master_server; -static Api::Client s_api; +static Api::Client s_api_client; static bool s_exit_requested = false; #ifndef _WIN32 @@ -95,8 +97,8 @@ void handler(int signalnum) { } else { Logger::Log(LOG_INFO, "closing server ... unregistering ... "); // We should really have a global var for the server status... - if (s_api.Authenticated()) { - s_api.SyncServerPowerState("offline"); + if (s_api_client.Authenticated()) { + s_api_client.SyncServerPowerState("offline"); } s_sequencer.Close(); } @@ -320,6 +322,27 @@ int main(int argc, char *argv[]) { } s_sequencer.Initialize(); + std::string api_key_key = Config::GetApiKeyKey(); + if (server_mode != SERVER_LAN && api_key_key.empty()) + { + if (server_mode == SERVER_INET) + { + Logger::Log(LOG_ERROR, "The API key was not set or is missing from the config file. Exiting."); + listener.Shutdown(); + return -1; + } + + Logger::Log(LOG_ERROR, "The API key was not set or is missing from the config file, continuing in LAN mode."); + server_mode = SERVER_LAN; + } + + if (server_mode != SERVER_LAN) + { + ApiErrorState api_error; + api_error = s_api_client.CreateServer(); + } + + // Listener is ready, let's register ourselves on serverlist (which will contact us back to check). if (server_mode != SERVER_LAN) { bool registered = s_master_server.Register(); From 4f509aa81737081645de1f66f26a92573c53b780 Mon Sep 17 00:00:00 2001 From: ohlidalp Date: Mon, 10 Feb 2025 22:09:36 +0100 Subject: [PATCH 5/9] Build fix: nasty macro name conflicts with --- source/server/ScriptFileSafe.cpp | 7 +----- source/server/api.h | 2 ++ source/server/utils.cpp | 6 ++--- source/server/win32_wrapper.h | 42 ++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 source/server/win32_wrapper.h diff --git a/source/server/ScriptFileSafe.cpp b/source/server/ScriptFileSafe.cpp index 2104cd57..7657aa6e 100644 --- a/source/server/ScriptFileSafe.cpp +++ b/source/server/ScriptFileSafe.cpp @@ -24,6 +24,7 @@ along with Foobar. If not, see . #ifdef WITH_ANGELSCRIPT #include "ScriptFileSafe.h" +#include "win32_wrapper.h" #include #include #include @@ -32,12 +33,6 @@ along with Foobar. If not, see . #include "logger.h" #include "config.h" -#ifdef _WIN32_WCE -#include // For GetModuleFileName -#ifdef GetObject -#undef GetObject -#endif -#endif using namespace std; diff --git a/source/server/api.h b/source/server/api.h index 3314051c..cc26ad68 100644 --- a/source/server/api.h +++ b/source/server/api.h @@ -20,6 +20,8 @@ */ ///@file api.h +#include "win32_wrapper.h" // Guard against clashes from preceding includes + #include #include diff --git a/source/server/utils.cpp b/source/server/utils.cpp index 7f5e9dd6..e03a6e1d 100644 --- a/source/server/utils.cpp +++ b/source/server/utils.cpp @@ -19,6 +19,7 @@ along with Foobar. If not, see . */ #include "utils.h" +#include "win32_wrapper.h" #include "logger.h" #include @@ -30,10 +31,7 @@ along with Foobar. If not, see . #include #include -#ifdef _WIN32 -#include -#include -#else +#ifndef _WIN32 #include #include diff --git a/source/server/win32_wrapper.h b/source/server/win32_wrapper.h new file mode 100644 index 00000000..cdd40631 --- /dev/null +++ b/source/server/win32_wrapper.h @@ -0,0 +1,42 @@ +/* + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2025 Petr Ohlidal + + For more information, see http://www.rigsofrods.org/ + + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. + + Rigs of Rods is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Rigs of Rods. If not, see . +*/ + +///@file win32_wrapper.h +///@brief Windows specific includes (and conflict resolutions) + +#pragma once + +#ifdef _WIN32 +# include +# include + + // Defined in ~ clashes with AngelScript addons +# ifdef GetObject +# undef GetObject +# endif + + // Defined in ~ clashes with `HttpMethod::DELETE` +# ifdef DELETE +# undef DELETE +# endif +#endif + + From e4c75956fa8fc764514f51fbab9fabe1c6eb2b3c Mon Sep 17 00:00:00 2001 From: ohlidalp Date: Sun, 23 Feb 2025 12:46:09 +0100 Subject: [PATCH 6/9] CurlHelpers: added support for setting headers. --- source/server/CurlHelpers.cpp | 19 +++++++++++-------- source/server/CurlHelpers.h | 5 +++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/source/server/CurlHelpers.cpp b/source/server/CurlHelpers.cpp index 88e10490..5f5399a5 100644 --- a/source/server/CurlHelpers.cpp +++ b/source/server/CurlHelpers.cpp @@ -46,11 +46,18 @@ static size_t CurlXferInfoFunc(void* ptr, curl_off_t filesize_B, curl_off_t down return 0; } -bool GetUrlAsString(const std::string& url, CURLcode& curl_result, long& response_code, std::string& response_payload) +bool GetUrlAsString(const std::string& url, const std::vector& headers, CURLcode& curl_result, long& response_code, std::string& response_payload) { std::string response_header; std::string user_agent = "Rigs of Rods Server"; + struct curl_slist* slist; + slist = NULL; + for (const std::string& header : headers) + { + slist = curl_slist_append(slist, header.c_str()); + } + CURL *curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); @@ -59,6 +66,7 @@ bool GetUrlAsString(const std::string& url, CURLcode& curl_result, long& respons #endif // _WIN32 curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip"); curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlStringWriteFunc); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, CurlXferInfoFunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_payload); @@ -70,12 +78,7 @@ bool GetUrlAsString(const std::string& url, CURLcode& curl_result, long& respons curl_easy_cleanup(curl); curl = nullptr; - if (curl_result != CURLE_OK || response_code != 200) - { - return false; - } - - return true; + return curl_result == CURLE_OK && response_code == 200; } bool CurlRequestThreadFunc(CurlTaskContext context) @@ -84,7 +87,7 @@ bool CurlRequestThreadFunc(CurlTaskContext context) std::string data; CURLcode curl_result = CURLE_OK; long http_response = 0; - if (GetUrlAsString(context.ctc_url, /*out:*/curl_result, /*out:*/http_response, /*out:*/data)) + if (GetUrlAsString(context.ctc_url, context.ctc_headers, /*out:*/curl_result, /*out:*/http_response, /*out:*/data)) { context.ctc_script_engine->curlStatus(CURL_STATUS_SUCCESS, (int)curl_result, (int)http_response, context.ctc_displayname, data); return true; diff --git a/source/server/CurlHelpers.h b/source/server/CurlHelpers.h index 0a640975..d8389b19 100644 --- a/source/server/CurlHelpers.h +++ b/source/server/CurlHelpers.h @@ -38,17 +38,18 @@ class ScriptEngine; #include #include - +#include struct CurlTaskContext { std::string ctc_displayname; std::string ctc_url; + std::vector ctc_headers; ScriptEngine* ctc_script_engine; // Status is reported via new server callback `curlStatus()` }; -bool GetUrlAsString(const std::string& url, CURLcode& curl_result, long& response_code, std::string& response_payload); +bool GetUrlAsString(const std::string& url, const std::vector& headers, CURLcode& curl_result, long& response_code, std::string& response_payload); bool CurlRequestThreadFunc(CurlTaskContext task); From 9138b4527ba0a71f35109c1376a552aafbdc0936 Mon Sep 17 00:00:00 2001 From: ohlidalp Date: Sun, 23 Feb 2025 13:27:59 +0100 Subject: [PATCH 7/9] API client: fixed unitialized variable. the HTTP response code may end up being 0 when endpoint isn't configured. --- source/server/api.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/server/api.cpp b/source/server/api.cpp index 58c2da30..d0d3e233 100644 --- a/source/server/api.cpp +++ b/source/server/api.cpp @@ -281,7 +281,7 @@ namespace Api */ ApiErrorState Client::HandleHttpRequestErrors(HttpResponse &response) { - ApiErrorState error_code; + ApiErrorState error_code = API_UNKNOWN_ERROR; // the HTTP response code may end up being 0 when endpoint isn't configured. if (!this->HasError(response.status_code)) { From 07effe84cef16038c3970a6fc03a5e72cf9aa50f Mon Sep 17 00:00:00 2001 From: ohlidalp Date: Sun, 23 Feb 2025 14:46:37 +0100 Subject: [PATCH 8/9] Config: added 'apihost' - used by API client code. Default value is 'http://127.0.0.1:8080' because that's what Zentro used during development. The production value is 'https://v2.api.rigsofrods.org' but that may obviously change. --- source/server/api.h | 3 ++- source/server/config.cpp | 6 ++++++ source/server/config.h | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/source/server/api.h b/source/server/api.h index cc26ad68..0bd1848a 100644 --- a/source/server/api.h +++ b/source/server/api.h @@ -21,6 +21,7 @@ ///@file api.h #include "win32_wrapper.h" // Guard against clashes from preceding includes +#include "config.h" #include #include @@ -84,7 +85,7 @@ namespace Api const std::string &content_type = "Content-Type: application/json", const std::string &user_agent = std::string("Rigs of Rods Server/") + RORNET_VERSION) : method(method), - url("http://127.0.0.1:8080" + uri), + url(Config::GetApiHost() + uri), body(body), headers(headers), content_type(content_type), diff --git a/source/server/config.cpp b/source/server/config.cpp index 7af82104..ca0ff62c 100644 --- a/source/server/config.cpp +++ b/source/server/config.cpp @@ -86,6 +86,7 @@ static int s_spamfilter_msg_count(0); // 0 disables spamfilter static int s_spamfilter_gag_duration_sec(10); static std::string s_api_key_key(""); +static std::string s_api_host("http://127.0.0.1:8080"); // ============================== Functions =================================== @@ -362,6 +363,8 @@ namespace Config { int getSpamFilterGagDurationSec() { return s_spamfilter_gag_duration_sec; } + const std::string& GetApiHost() { return s_api_host; } + bool setScriptName(const std::string &name) { if (name.empty()) return false; s_scriptname = name; @@ -443,6 +446,8 @@ namespace Config { void setApiKeyKey(const std::string &key) { s_api_key_key = key; } + void setApiHost(const std::string& host) { s_api_host = host; } + void setHeartbeatIntervalSec(unsigned sec) { s_heartbeat_interval_sec = sec; Logger::Log(LOG_VERBOSE, "Hearbeat interval is %d seconds", sec); @@ -526,6 +531,7 @@ namespace Config { else if (strcmp(key, "spamfilter-gag-duration") == 0) { setSpamFilterGagDurationSec(VAL_INT(value)); } else if (strcmp(key, "apikey") == 0) { setApiKeyKey(VAL_STR(value)); } + else if (strcmp(key, "apihost") == 0) { setApiHost(VAL_STR(value)); } else { Logger::Log(LOG_WARN, "Unknown key '%s' (value: '%s') in config file.", key, value); diff --git a/source/server/config.h b/source/server/config.h index ca5a0198..20075200 100644 --- a/source/server/config.h +++ b/source/server/config.h @@ -112,6 +112,8 @@ namespace Config { const std::string &GetApiKeyKey(); + const std::string& GetApiHost(); + bool GetShowHelp(); bool GetShowVersion(); @@ -182,6 +184,7 @@ namespace Config { void setSpamFilterGagDurationSec(int sec); void setApiKeyKey(const std::string &key); + void setApiHost(const std::string& host); //!@} } // namespace Config From d0a7290082916136432b7471eed90c609c19022b Mon Sep 17 00:00:00 2001 From: ohlidalp Date: Mon, 1 Sep 2025 21:52:23 +0200 Subject: [PATCH 9/9] Fixups to API-key handling * always-empty variable `Client::m_api_key_key` causing config not to work. * undefined function `Config::GetApiKeyKey()` causing linking error. * unitialized variable `Client::m_api_active`, causing rorserver to attempt deauth on shutdown even in LAN mode. * misnamed variable in sequencer.cpp, causing compile error. --- source/server/api.cpp | 4 ++-- source/server/api.h | 4 +--- source/server/config.cpp | 2 ++ source/server/rorserver.cpp | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/server/api.cpp b/source/server/api.cpp index d0d3e233..7a09fbc5 100644 --- a/source/server/api.cpp +++ b/source/server/api.cpp @@ -234,11 +234,11 @@ namespace Api curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, HttpMethodToString(request.method)); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - if (!m_api_key_key.empty()) + if (Config::GetApiKeyKey() != "") { // If the API key is present, we need to send it even if the call we're // making does not require authentication. - request.headers.push_back("Authorization: Bearer " + m_api_key_key); + request.headers.push_back("Authorization: Bearer " + Config::GetApiKeyKey()); } // Let the API know we're prefering JSON or HTML to be sent back. diff --git a/source/server/api.h b/source/server/api.h index 0bd1848a..9c09c443 100644 --- a/source/server/api.h +++ b/source/server/api.h @@ -103,7 +103,6 @@ namespace Api ApiErrorState UpdateServer(); ApiErrorState SyncServer(); ApiErrorState SyncServerPowerState(std::string status); - ApiErrorState CreateClient(); ApiErrorState VerifyClientSession(std::string challenge); private: @@ -111,7 +110,6 @@ namespace Api bool HasError(int status_code); HttpResponse ApiHttpQuery(HttpRequest &request); const char *HttpMethodToString(HttpMethod method); - std::string m_api_key_key; - bool m_api_active; + bool m_api_active = false; }; } // namespace Api diff --git a/source/server/config.cpp b/source/server/config.cpp index ca0ff62c..01e53b05 100644 --- a/source/server/config.cpp +++ b/source/server/config.cpp @@ -363,6 +363,8 @@ namespace Config { int getSpamFilterGagDurationSec() { return s_spamfilter_gag_duration_sec; } + const std::string& GetApiKeyKey() { return s_api_key_key; } + const std::string& GetApiHost() { return s_api_host; } bool setScriptName(const std::string &name) { diff --git a/source/server/rorserver.cpp b/source/server/rorserver.cpp index 11b528f3..103edc15 100644 --- a/source/server/rorserver.cpp +++ b/source/server/rorserver.cpp @@ -131,10 +131,10 @@ BOOL WINAPI WindowsConsoleHandlerRoutine(DWORD ctrl_type) return TRUE; // Means 'event handled' } - if (s_api.Authenticated()) + if (s_api_client.Authenticated()) { Logger::Log(LOG_INFO, "Unregistering..."); - s_api.SyncServerPowerState("offline"); + s_api_client.SyncServerPowerState("offline"); } s_sequencer.Close(); // TODO: This somehow closes (crashes?) the process on Windows, debugger doesn't intercept anything... Logger::Log(LOG_INFO, "Clean exit (Windows)"); @@ -271,7 +271,7 @@ int main(int argc, char *argv[]) { std::string ip_addr = Config::getIPAddr(); if (ip_addr.empty() || (ip_addr == "0.0.0.0")) { Logger::Log(LOG_WARN, "No IP given, detecting..."); - if (s_api.GetPublicIp(ip_addr) != API_NO_ERROR) { + if (s_api_client.GetPublicIp(ip_addr) != API_NO_ERROR) { Logger::Log(LOG_ERROR, "Failed to auto-detect public IP, exit."); return -1; }