diff --git a/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js b/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js index ca8be61b27..4b38bec046 100644 --- a/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ b/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -165,6 +165,7 @@ function getConnectionFailReason(reason) { 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 }); diff --git a/source/lobby/XmppClient.cpp b/source/lobby/XmppClient.cpp index da99589b52..f25871ae2d 100644 --- a/source/lobby/XmppClient.cpp +++ b/source/lobby/XmppClient.cpp @@ -942,7 +942,18 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq) 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(); diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp index e9bb732537..f52e9c463c 100644 --- a/source/network/NetServer.cpp +++ b/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 @@ void CNetServer::SetConnectionData(const CStr& ip, const u16 port, bool useSTUN) 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) diff --git a/source/network/NetServer.h b/source/network/NetServer.h index 2694f1d4a2..449ef56d5c 100644 --- a/source/network/NetServer.h +++ b/source/network/NetServer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -155,7 +156,20 @@ public: 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 +180,7 @@ private: u16 m_PublicPort; CStr m_PublicIp; CStr m_Password; + std::unordered_map m_FailedAttempts; }; /** diff --git a/source/network/scripting/JSInterface_Network.cpp b/source/network/scripting/JSInterface_Network.cpp index 2e5ec93c13..70a852ca0d 100644 --- a/source/network/scripting/JSInterface_Network.cpp +++ b/source/network/scripting/JSInterface_Network.cpp @@ -168,7 +168,7 @@ void JSI_Network::StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, c } } -void JSI_Network::StartNetworkJoinLobby(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& hostJID, const CStr& password) +void JSI_Network::StartNetworkJoinLobby(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const CStrW& playerName, const CStr& hostJID, const CStr& password) { ENSURE(!!g_XmppClient); ENSURE(!g_NetClient);