diff --git a/deps/commandline b/deps/commandline deleted file mode 160000 index 3d11606..0000000 --- a/deps/commandline +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3d11606d02b449b8afd40a7132160d2392043eb3 diff --git a/include/Common.h b/include/Common.h index 4541ba3..79d53f8 100644 --- a/include/Common.h +++ b/include/Common.h @@ -40,6 +40,9 @@ public: , ServerDesc("BeamMP Default Description") , Resource("Resources") , MapName("/levels/gridmap_v2/info.json") + , SSLKeyPath("./.ssl/HttpServer/key.pem") + , SSLCertPath("./.ssl/HttpServer/cert.pem") + , HTTPServerPort(8080) , MaxPlayers(10) , Private(true) , MaxCars(1) @@ -52,6 +55,8 @@ public: std::string Resource; std::string MapName; std::string Key; + std::string SSLKeyPath; + std::string SSLCertPath; int MaxPlayers; bool Private; int MaxCars; @@ -61,6 +66,7 @@ public: bool SendErrors; bool SendErrorsMessageEnabled; [[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); } + int HTTPServerPort; }; using TShutdownHandler = std::function; @@ -162,6 +168,12 @@ void RegisterThread(const std::string& str); Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \ } \ } while (false) +#define beammp_event(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \ + } \ + }while(false) // for those times when you just need to ignore something :^) // explicity disables a [[nodiscard]] warning #define beammp_ignore(x) (void)x diff --git a/include/Http.h b/include/Http.h index 568054b..e5c1495 100644 --- a/include/Http.h +++ b/include/Http.h @@ -1,8 +1,20 @@ #pragma once +#include +#include +#include +#include +#include +#include #include #include +namespace fs = std::filesystem; + +namespace Crypto { +constexpr size_t RSA_DEFAULT_KEYLENGTH { 2048 }; +} + namespace Http { std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr); std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr); @@ -10,4 +22,36 @@ namespace Status { std::string ToString(int code); } const std::string ErrorString = "-1"; + +namespace Server { + void SetupEnvironment(); + // todo: Add non TLS Server Instance, this one is TLS only + class THttpServerInstance : IThreaded { + public: + THttpServerInstance(); + static fs::path KeyFilePath; + static fs::path CertFilePath; + + protected: + void operator()(); + + private: + /** + * the shared pointer is necessary because httplib is a blocking library and due lacking thread-safety + * will "forget" about its environment, when configured across multiple threads. + * So we need to able to start the server (make it "listen()") in a single Thread. + */ + std::shared_ptr mHttpLibServerInstancePtr; + }; + // todo: all of these functions are likely unsafe, + // todo: replace with something that's managed by a domain specific crypto library + class Tx509KeypairGenerator { + public: + static long GenerateRandomId(); + static bool EnsureTLSConfigExists(); + static X509* GenerateCertificate(EVP_PKEY& pkey); + static EVP_PKEY* GenerateKey(); + static void GenerateAndWriteToDisk(const fs::path& KeyFilePath, const fs::path& CertFilePath); + }; +} } diff --git a/include/TConfig.h b/include/TConfig.h index f20766f..bd8f46d 100644 --- a/include/TConfig.h +++ b/include/TConfig.h @@ -20,7 +20,8 @@ private: void PrintDebug(); void ParseOldFormat(); - + bool IsDefault(); bool mFailed { false }; std::string mConfigFileName; }; + diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 63e5c3d..3b2e070 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -20,6 +20,9 @@ using TLuaStateId = std::string; namespace fs = std::filesystem; +/** + * std::variant means, that TLuaArgTypes may be one of the Types listed as template args + */ using TLuaArgTypes = std::variant; static constexpr size_t TLuaArgTypes_String = 0; static constexpr size_t TLuaArgTypes_Int = 1; @@ -114,11 +117,21 @@ public: void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false); void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName); template + /** + * + * @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means + * @param EventName Name of the event + * @param IgnoreId + * @param Args + * @return + */ [[nodiscard]] std::vector> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) { std::unique_lock Lock(mLuaEventsMutex); - if (mLuaEvents.find(EventName) == mLuaEvents.end()) { + beammp_event(EventName); + if (mLuaEvents.find(EventName) == mLuaEvents.end()) { // if no event handler is defined for 'EventName', return immediately return {}; } + std::vector> Results; for (const auto& Event : mLuaEvents.at(EventName)) { for (const auto& Function : Event.second) { @@ -127,7 +140,7 @@ public: } } } - return Results; + return Results; // } std::set GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId); void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS); diff --git a/src/Http.cpp b/src/Http.cpp index 609a4e8..598ae7b 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -1,11 +1,13 @@ #include "Http.h" #include "Common.h" +#include "CustomAssert.h" #include - -#include - +#include +#include +fs::path Http::Server::THttpServerInstance::KeyFilePath; +fs::path Http::Server::THttpServerInstance::CertFilePath; // TODO: Add sentry error handling back std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) { @@ -115,10 +117,163 @@ static std::map Map = { { 530, "(CDN) 1XXX Internal Error" }, }; -std::string Http::Status::ToString(int code) { - if (Map.find(code) != Map.end()) { - return Map.at(code); +std::string Http::Status::ToString(int Code) { + if (Map.find(Code) != Map.end()) { + return Map.at(Code); } else { - return std::to_string(code); + return std::to_string(Code); } } + +long Http::Server::Tx509KeypairGenerator::GenerateRandomId() { + std::random_device R; + std::default_random_engine E1(R()); + std::uniform_int_distribution UniformDist(0, ULONG_MAX); + return UniformDist(E1); +} + +// Http::Server::THttpServerInstance::THttpServerInstance() { } +EVP_PKEY* Http::Server::Tx509KeypairGenerator::GenerateKey() { + /** + * Allocate memory for the pkey + */ + EVP_PKEY* PKey = EVP_PKEY_new(); + if (PKey == nullptr) { + beammp_error("Could not allocate memory for X.509 private key (PKEY) generation."); + throw std::runtime_error { std::string { "X.509 PKEY allocation error" } }; + } + BIGNUM* E = BN_new(); + beammp_assert(E); // TODO: replace all these asserts with beammp_errors + unsigned char three = 3; + BIGNUM* EErr = BN_bin2bn(&three, sizeof(three), E); + beammp_assert(EErr); + RSA* Rsa = RSA_new(); + beammp_assert(Rsa); + int Ret = RSA_generate_key_ex(Rsa, Crypto::RSA_DEFAULT_KEYLENGTH, E, nullptr); + beammp_assert(Ret == 1); + BN_free(E); + if (!EVP_PKEY_assign_RSA(PKey, Rsa)) { + EVP_PKEY_free(PKey); + beammp_error(std::string("Could not generate " + std::to_string(Crypto::RSA_DEFAULT_KEYLENGTH) + "-bit RSA key.")); + throw std::runtime_error { std::string("X.509 RSA key generation error") }; + } + // todo: figure out if returning by reference instead of passing pointers is a security breach + return PKey; +} + +X509* Http::Server::Tx509KeypairGenerator::GenerateCertificate(EVP_PKEY& PKey) { + X509* X509 = X509_new(); + if (X509 == nullptr) { + X509_free(X509); + beammp_error("Could not allocate memory for X.509 certificate generation."); + throw std::runtime_error { std::string("X.509 certificate generation error") }; + } + + /**Set the metadata of the certificate*/ + ASN1_INTEGER_set(X509_get_serialNumber(X509), GenerateRandomId()); + + /**Set the cert validity to a year*/ + X509_gmtime_adj(X509_get_notBefore(X509), 0); + X509_gmtime_adj(X509_get_notAfter(X509), 31536000L); + + /**Set the public key of the cert*/ + X509_set_pubkey(X509, &PKey); + + X509_NAME* Name = X509_get_subject_name(X509); + + /**Set cert metadata*/ + X509_NAME_add_entry_by_txt(Name, "C", MBSTRING_ASC, (unsigned char*)"GB", -1, -1, 0); + X509_NAME_add_entry_by_txt(Name, "O", MBSTRING_ASC, (unsigned char*)"BeamMP Ltd.", -1, -1, 0); + X509_NAME_add_entry_by_txt(Name, "CN", MBSTRING_ASC, (unsigned char*)"localhost", -1, -1, 0); + + X509_set_issuer_name(X509, Name); + + // TODO: Hashing with sha256 might cause problems, check later + if (!X509_sign(X509, &PKey, EVP_sha1())) { + X509_free(X509); + beammp_error("Could not sign X.509 certificate."); + throw std::runtime_error { std::string("X.509 certificate signing error") }; + } + return X509; +} + +void Http::Server::Tx509KeypairGenerator::GenerateAndWriteToDisk(const fs::path& KeyFilePath, const fs::path& CertFilePath) { + // todo: generate directories for ssl keys + FILE* KeyFile = std::fopen(KeyFilePath.c_str(), "wb"); + if (!KeyFile) { + beammp_error("Could not create file 'key.pem', check your permissions"); + throw std::runtime_error("Could not create file 'key.pem'"); + } + + EVP_PKEY* PKey = Http::Server::Tx509KeypairGenerator::GenerateKey(); + + bool WriteOpResult = PEM_write_PrivateKey(KeyFile, PKey, nullptr, nullptr, 0, nullptr, nullptr); + fclose(KeyFile); + + if (!WriteOpResult) { + beammp_error("Could not write to file 'key.pem', check your permissions"); + throw std::runtime_error("Could not write to file 'key.pem'"); + } + + FILE* CertFile = std::fopen(CertFilePath.c_str(), "wb"); // x509 file + if (!CertFile) { + beammp_error("Could not create file 'cert.pem', check your permissions"); + throw std::runtime_error("Could not create file 'cert.pem'"); + } + + X509* x509 = Http::Server::Tx509KeypairGenerator::GenerateCertificate(*PKey); + WriteOpResult = PEM_write_X509(CertFile, x509); + fclose(CertFile); + + if (!WriteOpResult) { + beammp_error("Could not write to file 'cert.pem', check your permissions"); + throw std::runtime_error("Could not write to file 'cert.pem'"); + } + EVP_PKEY_free(PKey); + X509_free(x509); + return; +} +bool Http::Server::Tx509KeypairGenerator::EnsureTLSConfigExists() { + if (fs::is_regular_file(Application::Settings.SSLKeyPath) + && fs::is_regular_file(Application::Settings.SSLCertPath)) { + return true; + } else { + return false; + } +} + +void Http::Server::SetupEnvironment() { + auto parent = fs::path(Application::Settings.SSLKeyPath).parent_path(); + if (!fs::exists(parent)) + fs::create_directories(parent); + + Application::TSettings defaultSettings {}; + if (!Tx509KeypairGenerator::EnsureTLSConfigExists()) { + beammp_warn(std::string("No default TLS Key / Cert found. " + "IF YOU HAVE NOT MODIFIED THE SSLKeyPath OR SSLCertPath VALUES " + "THIS IS NORMAL ON FIRST STARTUP! BeamMP will generate it's own certs in the default directory " + "(Check for permissions or corrupted key-/certfile)")); + Tx509KeypairGenerator::GenerateAndWriteToDisk(defaultSettings.SSLKeyPath, defaultSettings.SSLCertPath); + Http::Server::THttpServerInstance::KeyFilePath = defaultSettings.SSLKeyPath; + Http::Server::THttpServerInstance::CertFilePath = defaultSettings.SSLCertPath; + } else { + Http::Server::THttpServerInstance::KeyFilePath = Application::Settings.SSLKeyPath; + Http::Server::THttpServerInstance::CertFilePath = Application::Settings.SSLCertPath; + } +} + +Http::Server::THttpServerInstance::THttpServerInstance() { + Start(); +} +void Http::Server::THttpServerInstance::operator()() { + // todo: make this IP agnostic so people can set their own IP + this->mHttpLibServerInstancePtr = std::make_shared(Application::Settings.SSLCertPath.c_str(), Application::Settings.SSLKeyPath.c_str()); + this->mHttpLibServerInstancePtr->Get("/", [](const httplib::Request&, httplib::Response& res) { + res.set_content("

Hello World!

BeamMP Server can now serve HTTP requests!

", "text/html"); + }); + this->mHttpLibServerInstancePtr->Get("/health", [](const httplib::Request& req, httplib::Response& res) { + res.set_content("0", "text/plain"); + res.status = 200; + }); + this->mHttpLibServerInstancePtr->listen("0.0.0.0", Application::Settings.HTTPServerPort); +} diff --git a/src/TConfig.cpp b/src/TConfig.cpp index ba90e39..276d993 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -1,3 +1,4 @@ +#include "Common.h" #define TOML11_PRESERVE_COMMENTS_BY_DEFAULT #include // header-only version of TOML++ @@ -20,6 +21,9 @@ static constexpr std::string_view StrResourceFolder = "ResourceFolder"; static constexpr std::string_view StrAuthKey = "AuthKey"; static constexpr std::string_view StrSendErrors = "SendErrors"; static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage"; +static constexpr std::string_view StrSSLKeyPath = "SSLKeyPath"; +static constexpr std::string_view StrSSLCertPath = "SSLCertPath"; +static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort"; void WriteSendErrors(const std::string& name) { std::ofstream CfgFile { name, std::ios::out | std::ios::app }; @@ -43,7 +47,15 @@ TConfig::TConfig(const std::string& ConfigFileName) ParseFromFile(mConfigFileName); } } - +/** + * @brief Writes out the loaded application state into ServerConfig.toml + * + * This writes out the current state of application settings that are + * applied to the server instance (i.e. the current application settings loaded in the server). + * If the state of the application settings changes during runtime, + * call this function whenever something about the config changes + * whether it is in TConfig.cpp or the configuration file. + */ void TConfig::FlushToFile() { auto data = toml::parse(mConfigFileName); data["General"] = toml::table(); @@ -59,6 +71,9 @@ void TConfig::FlushToFile() { data["General"][StrResourceFolder.data()] = Application::Settings.Resource; data["General"][StrSendErrors.data()] = Application::Settings.SendErrors; data["General"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled; + data["General"][StrSSLKeyPath.data()] = Application::Settings.SSLKeyPath; + data["General"][StrSSLCertPath.data()] = Application::Settings.SSLCertPath; + data["General"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort; std::ofstream Stream(mConfigFileName); Stream << data << std::flush; } @@ -93,6 +108,9 @@ void TConfig::CreateConfigFile(std::string_view name) { data["General"][StrMap.data()] = Application::Settings.MapName; data["General"][StrDescription.data()] = Application::Settings.ServerDesc; data["General"][StrResourceFolder.data()] = Application::Settings.Resource; + data["General"][StrSSLKeyPath.data()] = Application::Settings.SSLKeyPath; + data["General"][StrSSLCertPath.data()] = Application::Settings.SSLCertPath; + data["General"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort; std::ofstream ofs { std::string(name) }; if (ofs.good()) { @@ -124,6 +142,9 @@ void TConfig::ParseFromFile(std::string_view name) { Application::Settings.ServerDesc = data["General"][StrDescription.data()].as_string(); Application::Settings.Resource = data["General"][StrResourceFolder.data()].as_string(); Application::Settings.Key = data["General"][StrAuthKey.data()].as_string(); + Application::Settings.SSLKeyPath = data["General"][StrSSLKeyPath.data()].as_string(); + Application::Settings.SSLCertPath = data["General"][StrSSLCertPath.data()].as_string(); + Application::Settings.HTTPServerPort = data["General"][StrHTTPServerPort.data()].as_integer(); if (!data["General"][StrSendErrors.data()].is_boolean() || !data["General"][StrSendErrorsMessageEnabled.data()].is_boolean()) { WriteSendErrors(std::string(name)); @@ -154,6 +175,8 @@ void TConfig::PrintDebug() { beammp_debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\""); beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\""); beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\""); + beammp_debug(std::string(StrSSLKeyPath) + ": \"" + Application::Settings.SSLKeyPath + "\""); + beammp_debug(std::string(StrSSLCertPath) + ": \"" + Application::Settings.SSLCertPath + "\""); // special! beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + ""); } diff --git a/src/TConsole.cpp b/src/TConsole.cpp index 857941a..f0293bd 100644 --- a/src/TConsole.cpp +++ b/src/TConsole.cpp @@ -106,8 +106,8 @@ void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) { } else { Application::Console().WriteRaw("Entered Lua console. To exit, type `exit()`"); } - mCachedRegularHistory = mCommandline.history(); - mCommandline.set_history(mCachedLuaHistory); + //mCachedRegularHistory = mCommandline.history(); + //mCommandline.set_history(mCachedLuaHistory); mCommandline.set_prompt("lua> "); } } @@ -120,8 +120,8 @@ void TConsole::ChangeToRegularConsole() { } else { Application::Console().WriteRaw("Left Lua console."); } - mCachedLuaHistory = mCommandline.history(); - mCommandline.set_history(mCachedRegularHistory); + //mCachedLuaHistory = mCommandline.history(); + //mCommandline.set_history(mCachedRegularHistory); mCommandline.set_prompt("> "); mStateId = mDefaultStateId; } diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 574eda3..ac7a293 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -6,6 +6,7 @@ #include void THeartbeatThread::operator()() { + return;/* RegisterThread("Heartbeat"); std::string Body; std::string T; @@ -82,6 +83,7 @@ void THeartbeatThread::operator()() { //SocketIO::Get().SetAuthenticated(isAuth); } + */ } std::string THeartbeatThread::GenerateCall() { diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index c3e9169..914bf85 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -211,7 +211,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) { beammp_trace("This thread is ip " + std::string(str)); Client->SetIdentifier("ip", str); - std::string Rc; + std::string Rc; //TODO: figure out why this is not default constructed beammp_info("Identifying new ClientConnection..."); Rc = TCPRcv(*Client); diff --git a/src/main.cpp b/src/main.cpp index be58b64..5701ea4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,7 +17,7 @@ #include #include - +#define CPPHTTPLIB_OPENSSL_SUPPORT 1 static const std::string sCommandlineArguments = R"( USAGE: BeamMP-Server [arguments] @@ -155,6 +155,12 @@ int BeamMPServerMain(MainArguments Arguments) { Application::Console().InitializeLuaConsole(LuaEngine); Application::CheckForUpdates(); + Http::Server::SetupEnvironment(); + Http::Server::THttpServerInstance HttpServerInstance {}; + + beammp_debug("cert.pem is " + std::to_string(fs::file_size("cert.pem")) + " bytes"); + beammp_debug("key.pem is " + std::to_string(fs::file_size("key.pem")) + " bytes"); + RegisterThread("Main(Waiting)"); while (!Shutdown) {