wesnothd: handle server IP bans more generically (#9602)
Instead of simply returning a string, `is_ip_banned` now returns a struct with an error code, ban reason, and ban time remaining. This avoids doing time duration formatting on the server and allows the error message to be localized on the client. It also makes the ban handling interface more generic in server_base, which should hopefully allow forum bans to be handled this way as well.
This commit is contained in:
parent
7bc844d4be
commit
0f8d9a9940
8 changed files with 77 additions and 42 deletions
|
@ -418,6 +418,12 @@ std::unique_ptr<wesnothd_connection> mp_manager::open_connection(std::string hos
|
|||
} else if(ec == MP_NAME_UNREGISTERED_ERROR) {
|
||||
error_message = VGETTEXT("The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
|
||||
+ _(" This server disallows unregistered nicknames.");
|
||||
} else if(ec == MP_SERVER_IP_BAN_ERROR) {
|
||||
if(extra_data) {
|
||||
error_message = VGETTEXT("Your IP address is banned on this server for $duration|.", i18n_symbols);
|
||||
} else {
|
||||
error_message = _("Your IP address is banned on this server.");
|
||||
}
|
||||
} else if(ec == MP_NAME_AUTH_BAN_USER_ERROR) {
|
||||
if(extra_data) {
|
||||
error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#define MP_NAME_AUTH_BAN_USER_ERROR "107"
|
||||
#define MP_NAME_AUTH_BAN_IP_ERROR "108"
|
||||
#define MP_NAME_AUTH_BAN_EMAIL_ERROR "109"
|
||||
#define MP_SERVER_IP_BAN_ERROR "110"
|
||||
|
||||
#define MP_PASSWORD_REQUEST "200"
|
||||
#define MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME "201"
|
||||
|
|
|
@ -199,27 +199,40 @@ void server_base::serve(boost::asio::yield_context yield, boost::asio::ip::tcp::
|
|||
return;
|
||||
}
|
||||
|
||||
utils::visit([this](auto&& socket) {
|
||||
const std::string ip = client_address(socket);
|
||||
utils::visit(
|
||||
[this](auto&& socket) {
|
||||
const std::string ip = client_address(socket);
|
||||
|
||||
if(const auto ban_info = is_ip_banned(ip)) {
|
||||
const auto& [error_code, reason, time_remaining] = *ban_info;
|
||||
LOG_SERVER << log_address(socket) << "\trejected banned user. Reason: " << reason;
|
||||
|
||||
if(time_remaining) {
|
||||
// Temporary ban
|
||||
async_send_error(socket, "You are banned from this server: " + reason, error_code,
|
||||
{{ "duration", std::to_string(time_remaining->count()) }});
|
||||
} else {
|
||||
// Permanent ban
|
||||
async_send_error(socket, "You are banned from this server: " + reason, error_code);
|
||||
}
|
||||
|
||||
const std::string reason = is_ip_banned(ip);
|
||||
if (!reason.empty()) {
|
||||
LOG_SERVER << ip << "\trejected banned user. Reason: " << reason;
|
||||
async_send_error(socket, "You are banned. Reason: " + reason);
|
||||
return;
|
||||
} else if (ip_exceeds_connection_limit(ip)) {
|
||||
LOG_SERVER << ip << "\trejected ip due to excessive connections";
|
||||
async_send_error(socket, "Too many connections from your IP.");
|
||||
return;
|
||||
} else {
|
||||
if constexpr (utils::decayed_is_same<tls_socket_ptr, decltype(socket)>) {
|
||||
}
|
||||
|
||||
if(ip_exceeds_connection_limit(ip)) {
|
||||
LOG_SERVER << log_address(socket) << "\trejected ip due to excessive connections";
|
||||
async_send_error(socket, "Too many connections from your IP.");
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr(utils::decayed_is_same<tls_socket_ptr, decltype(socket)>) {
|
||||
DBG_SERVER << ip << "\tnew encrypted connection fully accepted";
|
||||
} else {
|
||||
DBG_SERVER << ip << "\tnew connection fully accepted";
|
||||
}
|
||||
this->handle_new_client(socket);
|
||||
}
|
||||
}, final_socket);
|
||||
},
|
||||
final_socket);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "utils/variant.hpp"
|
||||
#include "utils/general.hpp"
|
||||
#include "utils/optional_fwd.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "serialization/unicode_cast.hpp"
|
||||
|
@ -167,9 +168,17 @@ protected:
|
|||
virtual void handle_new_client(tls_socket_ptr socket) = 0;
|
||||
|
||||
virtual bool accepting_connections() const { return true; }
|
||||
virtual std::string is_ip_banned(const std::string&) { return std::string(); }
|
||||
virtual bool ip_exceeds_connection_limit(const std::string&) const { return false; }
|
||||
|
||||
struct login_ban_info
|
||||
{
|
||||
const char* error_code;
|
||||
std::string reason;
|
||||
utils::optional<std::chrono::seconds> time_remaining;
|
||||
};
|
||||
|
||||
virtual utils::optional<login_ban_info> is_ip_banned(const std::string&) { return {}; }
|
||||
|
||||
#ifndef _WIN32
|
||||
boost::asio::posix::stream_descriptor input_;
|
||||
std::string fifo_path_;
|
||||
|
|
|
@ -216,6 +216,16 @@ void banned::write(config& cfg) const
|
|||
}
|
||||
}
|
||||
|
||||
utils::optional<std::chrono::seconds> banned::get_remaining_ban_time() const
|
||||
{
|
||||
if(end_time_) {
|
||||
const auto time_left = *end_time_ - std::chrono::system_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(time_left);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::string banned::get_human_start_time() const
|
||||
{
|
||||
if(start_time_) {
|
||||
|
@ -234,16 +244,6 @@ std::string banned::get_human_end_time() const
|
|||
}
|
||||
}
|
||||
|
||||
std::string banned::get_human_time_span() const
|
||||
{
|
||||
if(!end_time_) {
|
||||
return "permanent";
|
||||
}
|
||||
|
||||
auto remaining = *end_time_ - std::chrono::system_clock::now();
|
||||
return lg::format_timespan(std::chrono::duration_cast<std::chrono::seconds>(remaining));
|
||||
}
|
||||
|
||||
bool banned::operator>(const banned& b) const
|
||||
{
|
||||
static constexpr std::chrono::system_clock::time_point epoch;
|
||||
|
@ -655,20 +655,18 @@ void ban_manager::list_bans(std::ostringstream& out, const std::string& mask)
|
|||
}
|
||||
}
|
||||
|
||||
std::string ban_manager::is_ip_banned(const std::string& ip)
|
||||
banned_ptr ban_manager::get_ban_info(const std::string& ip)
|
||||
{
|
||||
expire_bans();
|
||||
ip_mask pair;
|
||||
ip_mask mask;
|
||||
try {
|
||||
pair = parse_ip(ip);
|
||||
mask = parse_ip(ip);
|
||||
} catch (const banned::error&) {
|
||||
return "";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ban = std::find_if(bans_.begin(), bans_.end(), [pair](const banned_ptr& p) { return p->match_ip(pair); });
|
||||
if (ban == bans_.end()) return "";
|
||||
const std::string& nick = (*ban)->get_nick();
|
||||
return (*ban)->get_reason() + (nick.empty() ? "" : " (" + nick + ")") + " (Remaining ban duration: " + (*ban)->get_human_time_span() + ")";
|
||||
auto ban = std::find_if(bans_.begin(), bans_.end(), [&mask](const banned_ptr& p) { return p->match_ip(mask); });
|
||||
return ban != bans_.end() ? *ban : nullptr;
|
||||
}
|
||||
|
||||
void ban_manager::init_ban_help()
|
||||
|
|
|
@ -87,9 +87,11 @@ public:
|
|||
const auto& get_end_time() const
|
||||
{ return end_time_; }
|
||||
|
||||
/** Returns the seconds remaining until than ban expires, or nullopt if permanent. */
|
||||
utils::optional<std::chrono::seconds> get_remaining_ban_time() const;
|
||||
|
||||
std::string get_human_end_time() const;
|
||||
std::string get_human_start_time() const;
|
||||
std::string get_human_time_span() const;
|
||||
|
||||
std::string get_reason() const
|
||||
{ return reason_; }
|
||||
|
@ -181,7 +183,7 @@ public:
|
|||
void list_deleted_bans(std::ostringstream& out, const std::string& mask = "*") const;
|
||||
void list_bans(std::ostringstream& out, const std::string& mask = "*");
|
||||
|
||||
std::string is_ip_banned(const std::string& ip);
|
||||
banned_ptr get_ban_info(const std::string& ip);
|
||||
|
||||
const std::string& get_ban_help() const
|
||||
{ return ban_help_; }
|
||||
|
|
|
@ -576,15 +576,21 @@ bool server::ip_exceeds_connection_limit(const std::string& ip) const
|
|||
return connections >= concurrent_connections_;
|
||||
}
|
||||
|
||||
std::string server::is_ip_banned(const std::string& ip)
|
||||
utils::optional<server_base::login_ban_info> server::is_ip_banned(const std::string& ip)
|
||||
{
|
||||
if(!tor_ip_list_.empty()) {
|
||||
if(find(tor_ip_list_.begin(), tor_ip_list_.end(), ip) != tor_ip_list_.end()) {
|
||||
return "TOR IP";
|
||||
}
|
||||
if(utils::contains(tor_ip_list_, ip)) {
|
||||
return login_ban_info{ MP_SERVER_IP_BAN_ERROR, "TOR IP", {} };
|
||||
}
|
||||
|
||||
return ban_manager_.is_ip_banned(ip);
|
||||
if(auto server_ban_info = ban_manager_.get_ban_info(ip)) {
|
||||
return login_ban_info{
|
||||
MP_SERVER_IP_BAN_ERROR,
|
||||
server_ban_info->get_reason(),
|
||||
server_ban_info->get_remaining_ban_time()
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void server::start_dump_stats()
|
||||
|
|
|
@ -172,7 +172,7 @@ private:
|
|||
void load_config();
|
||||
|
||||
bool ip_exceeds_connection_limit(const std::string& ip) const;
|
||||
std::string is_ip_banned(const std::string& ip);
|
||||
utils::optional<server_base::login_ban_info> is_ip_banned(const std::string& ip);
|
||||
|
||||
simple_wml::document version_query_response_;
|
||||
simple_wml::document login_response_;
|
||||
|
|
Loading…
Add table
Reference in a new issue