Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js =================================================================== --- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -165,6 +165,7 @@ { case "not_server": return translate("Server is not running."); case "invalid_password": return translate("Password is invalid."); + case "banned": return translate("You have been banned."); default: warn("Unknown connection failure reason: " + reason); return sprintf(translate("\\[Invalid value %(reason)s]"), { "reason": reason }); Index: source/lobby/XmppClient.cpp =================================================================== --- source/lobby/XmppClient.cpp +++ source/lobby/XmppClient.cpp @@ -942,7 +942,18 @@ m_client->send(response); return true; } - if (!g_NetServer->CheckPassword(CStr(cd->m_Password.c_str()))) + if (g_NetServer->IsBanned(iq.from().username())) + { + glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id()); + ConnectionData* connectionData = new ConnectionData(); + connectionData->m_Error = "banned"; + + response.addExtension(connectionData); + + m_client->send(response); + return true; + } + if (!g_NetServer->CheckPasswordAndIncrement(CStr(cd->m_Password.c_str()), iq.from().username())) { glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id()); ConnectionData* connectionData = new ConnectionData(); Index: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -155,7 +155,20 @@ u16 GetPublicPort() const; - bool CheckPassword(const CStr& password) const; + /** + * Check if password is valid. If is not, increase number of failed attempts of the lobby user. + * This is used without established direct session with the client, to prevent brute force attacks + * when guessing password trying to get connection data from the host. + * @return true iff password is valid + */ + bool CheckPasswordAndIncrement(const CStr& password, const std::string& username); + + /** + * Check if user reached certain number of failed attempts. + * @see m_BanAfterNumberOfTries + * @see CheckPasswordAndBan + */ + bool IsBanned(const std::string& username) const; void SetPassword(const CStr& password); @@ -166,6 +179,7 @@ u16 m_PublicPort; CStr m_PublicIp; CStr m_Password; + std::unordered_map m_FailedAttempts; }; /** Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -58,6 +58,7 @@ #define DEFAULT_SERVER_NAME L"Unnamed Server" constexpr int CHANNEL_COUNT = 1; +constexpr int FAILED_PASSWORD_TRIES_BEFORE_BAN = 3; /** * enet_host_service timeout (msecs). @@ -1629,9 +1630,26 @@ m_UseSTUN = useSTUN; } -bool CNetServer::CheckPassword(const CStr& password) const +bool CNetServer::CheckPasswordAndIncrement(const CStr& password, const std::string& username) { - return m_Password == password; + std::unordered_map::iterator it = m_FailedAttempts.find(username); + if (m_Password == password) + { + if (it != m_FailedAttempts.end()) + it->second = 0; + return true; + } + if (it == m_FailedAttempts.end()) + m_FailedAttempts.emplace(username, 1); + else + it->second++; + return false; +} + +bool CNetServer::IsBanned(const std::string& username) const +{ + std::unordered_map::const_iterator it = m_FailedAttempts.find(username); + return it != m_FailedAttempts.end() && it->second >= FAILED_PASSWORD_TRIES_BEFORE_BAN; } void CNetServer::SetPassword(const CStr& password)