diff --git a/README.md b/README.md index f9a20c7..3134837 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Ubuntu 22.04 git libz-dev rapidjson-dev -liblua5.3 +liblua5.3-dev libssl-dev libwebsocketpp-dev libcurl4-openssl-dev diff --git a/include/Common.h b/include/Common.h index a2bc21f..26d96d7 100644 --- a/include/Common.h +++ b/include/Common.h @@ -49,6 +49,7 @@ public: std::string Resource { "Resources" }; std::string MapName { "/levels/gridmap_v2/info.json" }; std::string Key {}; + std::string Password{}; std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" }; std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" }; bool HTTPServerEnabled { false }; diff --git a/include/TNetwork.h b/include/TNetwork.h index 3b4980e..5e0766c 100644 --- a/include/TNetwork.h +++ b/include/TNetwork.h @@ -52,4 +52,5 @@ private: static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size); }; +std::string HashPassword(const std::string& str); std::vector StringToVector(const std::string& Str); diff --git a/src/TConfig.cpp b/src/TConfig.cpp index 8c93aa4..7957808 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -18,6 +18,7 @@ static constexpr std::string_view StrDescription = "Description"; static constexpr std::string_view StrResourceFolder = "ResourceFolder"; static constexpr std::string_view StrAuthKey = "AuthKey"; static constexpr std::string_view StrLogChat = "LogChat"; +static constexpr std::string_view StrPassword = "Password"; // Misc static constexpr std::string_view StrSendErrors = "SendErrors"; @@ -106,6 +107,8 @@ void TConfig::FlushToFile() { data["General"][StrMap.data()] = Application::Settings.MapName; data["General"][StrDescription.data()] = Application::Settings.ServerDesc; data["General"][StrResourceFolder.data()] = Application::Settings.Resource; + data["General"][StrPassword.data()] = Application::Settings.Password; + SetComment(data["General"][StrPassword.data()].comments(), " Sets a password on this server, which restricts people from joining. To join, a player must enter this exact password. Leave empty ("") to disable the password."); // Misc data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages; SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless."); @@ -189,6 +192,7 @@ void TConfig::ParseFromFile(std::string_view name) { TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource); TryReadValue(data, "General", StrAuthKey, Application::Settings.Key); TryReadValue(data, "General", StrLogChat, Application::Settings.LogChat); + TryReadValue(data, "General", StrPassword, Application::Settings.Password); // Misc TryReadValue(data, "Misc", StrSendErrors, Application::Settings.SendErrors); TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages); @@ -240,6 +244,7 @@ void TConfig::PrintDebug() { beammp_debug(std::string(StrHTTPServerIP) + ": \"" + Application::Settings.HTTPServerIP + "\""); // special! beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + ""); + beammp_debug("Password Protected: " + std::string(Application::Settings.Password.empty() ? "false" : "true")); } void TConfig::ParseOldFormat() { diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 873825a..ab0f5a6 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -149,7 +149,8 @@ std::string THeartbeatThread::GenerateCall() { << "&modstotalsize=" << mResourceManager.MaxModSize() << "&modstotal=" << mResourceManager.ModsLoaded() << "&playerslist=" << GetPlayers() - << "&desc=" << Application::Settings.ServerDesc; + << "&desc=" << Application::Settings.ServerDesc + << "&pass=" << (Application::Settings.Password.empty() ? "false" : "true"); return Ret.str(); } THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server) diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 0b0f024..73144a6 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -11,6 +11,8 @@ #include #include +typedef boost::asio::detail::socket_option::integer rcv_timeout_option; + std::vector StringToVector(const std::string& Str) { return std::vector(Str.data(), Str.data() + Str.size()); } @@ -151,6 +153,7 @@ void TNetwork::TCPServerMain() { if (ec) { beammp_errorf("failed to accept: {}", ec.message()); } + ClientSocket.set_option(rcv_timeout_option{ 120000 }); //timeout of 120seconds TConnection Conn { std::move(ClientSocket), ClientEp }; std::thread ID(&TNetwork::Identify, this, std::move(Conn)); ID.detach(); // TODO: Add to a queue and attempt to join periodically @@ -212,6 +215,15 @@ void TNetwork::HandleDownload(TConnection&& Conn) { }); } +std::string HashPassword(const std::string& str) { + std::stringstream ret; + unsigned char* hash = SHA256(reinterpret_cast(str.c_str()), str.length(), nullptr); + for (int i = 0; i < 32; i++) { + ret << std::hex << static_cast(hash[i]); + } + return ret.str(); +} + std::shared_ptr TNetwork::Authentication(TConnection&& RawConnection) { auto Client = CreateClient(std::move(RawConnection.Socket)); Client->SetIdentifier("ip", RawConnection.SockAddr.address().to_string()); @@ -235,7 +247,8 @@ std::shared_ptr TNetwork::Authentication(TConnection&& RawConnection) { ClientKick(*Client, fmt::format("Invalid version header: '{}' ({})", std::string(reinterpret_cast(Data.data()), Data.size()), Data.size())); return nullptr; } - if (!TCPSend(*Client, StringToVector("S"))) { + + if (!TCPSend(*Client, StringToVector("A"))) { //changed to A for Accepted version // TODO: handle } @@ -279,6 +292,22 @@ std::shared_ptr TNetwork::Authentication(TConnection&& RawConnection) { return nullptr; } + if(!Application::Settings.Password.empty()) { // ask password + if(!TCPSend(*Client, StringToVector("S"))) { + // TODO: handle + } + beammp_info("Waiting for password"); + Data = TCPRcv(*Client); + std::string Pass = std::string(reinterpret_cast(Data.data()), Data.size()); + if(Pass != HashPassword(Application::Settings.Password)) { + beammp_debug(Client->GetName() + " attempted to connect with a wrong password"); + ClientKick(*Client, "Wrong password!"); + return {}; + } else { + beammp_debug(Client->GetName() + " used the correct password"); + } + } + beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles()); mServer.ForEachClient([&](const std::weak_ptr& ClientPtr) -> bool { std::shared_ptr Cl; @@ -328,6 +357,7 @@ std::shared_ptr TNetwork::Authentication(TConnection&& RawConnection) { } else { ClientKick(*Client, "Server full!"); } + return Client; }