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/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); 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.cpp b/source/server/api.cpp new file mode 100644 index 00000000..7a09fbc5 --- /dev/null +++ b/source/server/api.cpp @@ -0,0 +1,340 @@ +/* + 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 "config.h" +#include "json/json.h" + +#include + +#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. + * + * \param ip_addr + */ + bool Client::GetPublicIp(std::string &ip_addr) + { + 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 ); + } + + /** + * \brief Call the API to determine if it's callable. + * + * \return bool True or False if we can call the API. + */ + bool Client::Callable() + { + 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 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. + */ + bool Client::Authenticated() + { + return true; + } + + /** + * \brief Call the API to register our server with the server list. + * + * \return ApiErrorState Error state from the API. + */ + ApiErrorState Client::CreateServer() + { + HttpResponse response; + ApiErrorState error_code; + + Json::Value data(Json::objectValue); + 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(); + + HttpRequest request(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() + { + HttpResponse response; + ApiErrorState error_code; + + char url[300] = ""; + sprintf(url, "/servers/%d", 10000); + + Json::Value data(Json::objectValue); + + HttpRequest request(HttpMethod::UPDATE, url, 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() + { + HttpResponse response; + ApiErrorState error_code; + + Json::Value data(Json::objectValue); + + HttpRequest request(HttpMethod::PATCH, "/servers", 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. + * + * \param status The current power state to report to the server list. + * \return ApiErrorState Error state from the API. + */ + ApiErrorState Client::SyncServerPowerState(std::string status) + { + 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; + + 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 + // 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) + { + HttpResponse response; + ApiErrorState error_code; + + // Need to maybe look into whether or not C++20 has better string formatting? + char url[300] = ""; + 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.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); + + 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); + + 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 " + Config::GetApiKeyKey()); + } + + // 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 + 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; + + if (curl_result != CURLE_OK) + { + Logger::Log(LOG_ERROR, "curl 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 = API_UNKNOWN_ERROR; // the HTTP response code may end up being 0 when endpoint isn't configured. + + 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; + } + + 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 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..9c09c443 --- /dev/null +++ b/source/server/api.h @@ -0,0 +1,115 @@ +/* + 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 "win32_wrapper.h" // Guard against clashes from preceding includes +#include "config.h" + +#include +#include + +#include + +/** + * \brief Enum representing different API error states + */ +enum ApiErrorState +{ + API_NO_ERROR = 0, + API_CLIENT_ERROR = 1, + API_SERVER_ERROR = 2, + API_UNKNOWN_ERROR = 999, +}; + +namespace Api +{ + class Client + { + /** + * \brief Enum representing different HTTP methods + */ + enum HttpMethod + { + GET, + POST, + PUT, + DELETE, + PATCH, + UPDATE + }; + + /** + * \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 + { + HttpMethod method = HttpMethod::GET; + 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(Config::GetApiHost() + uri), + body(body), + headers(headers), + content_type(content_type), + user_agent(user_agent) + { + } + }; + + public: + Client(); + bool GetPublicIp(std::string &ip_addr); + bool Callable(); + bool Authenticated(); + ApiErrorState CreateServer(); + ApiErrorState UpdateServer(); + ApiErrorState SyncServer(); + ApiErrorState SyncServerPowerState(std::string status); + ApiErrorState VerifyClientSession(std::string challenge); + + private: + ApiErrorState HandleHttpRequestErrors(HttpResponse &response); + bool HasError(int status_code); + HttpResponse ApiHttpQuery(HttpRequest &request); + const char *HttpMethodToString(HttpMethod method); + bool m_api_active = false; + }; +} // namespace Api diff --git a/source/server/config.cpp b/source/server/config.cpp index e713aeb9..01e53b05 100644 --- a/source/server/config.cpp +++ b/source/server/config.cpp @@ -85,6 +85,9 @@ 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(""); +static std::string s_api_host("http://127.0.0.1:8080"); + // ============================== Functions =================================== namespace Config { @@ -360,6 +363,10 @@ 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) { if (name.empty()) return false; s_scriptname = name; @@ -439,6 +446,10 @@ namespace Config { void setSpamFilterGagDurationSec(int sec) { s_spamfilter_gag_duration_sec = sec; } + 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); @@ -521,6 +532,9 @@ 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 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 0d098de8..20075200 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 @@ -97,6 +110,10 @@ namespace Config { unsigned int GetHeartbeatIntervalSec(); + const std::string &GetApiKeyKey(); + + const std::string& GetApiHost(); + bool GetShowHelp(); bool GetShowVersion(); @@ -165,6 +182,9 @@ namespace Config { void setSpamFilterMsgIntervalSec(int sec); void setSpamFilterMsgCount(int count); void setSpamFilterGagDurationSec(int sec); + + void setApiKeyKey(const std::string &key); + void setApiHost(const std::string& host); //!@} } // 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 62de763f..103edc15 100644 --- a/source/server/rorserver.cpp +++ b/source/server/rorserver.cpp @@ -1,24 +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. +/// @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" @@ -28,6 +32,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 +63,7 @@ along with Foobar. If not, see . static Sequencer s_sequencer; static MasterServer::Client s_master_server; +static Api::Client s_api_client; static bool s_exit_requested = false; #ifndef _WIN32 @@ -90,8 +96,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_client.Authenticated()) { + s_api_client.SyncServerPowerState("offline"); } s_sequencer.Close(); } @@ -124,10 +131,10 @@ BOOL WINAPI WindowsConsoleHandlerRoutine(DWORD ctrl_type) return TRUE; // Means 'event handled' } - if (s_master_server.IsRegistered()) + if (s_api_client.Authenticated()) { Logger::Log(LOG_INFO, "Unregistering..."); - s_master_server.UnRegister(); + 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)"); @@ -264,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 (!MasterServer::RetrievePublicIp()) { + if (s_api_client.GetPublicIp(ip_addr) != API_NO_ERROR) { Logger::Log(LOG_ERROR, "Failed to auto-detect public IP, exit."); return -1; } @@ -315,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(); 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 4f38fcfd..cd4ec3c2 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()); } @@ -151,42 +153,33 @@ 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) { - // 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(); +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; - 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); + // 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. + if (!session_token.empty() || session_token[0] == '\000') + { + 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 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; } 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); 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 + +