Skip to content

Commit

Permalink
wesnothd: Initial support for checking forum bans during login
Browse files Browse the repository at this point in the history
This adds a user_is_banned() method to the user_handler classes that
returns whether a given username (and optionally IP address) is banned
by the user_handler platform. Obviously right now this is only intended
to work with forum_user_handler and phpBB.

Forum bans are checked against entries in the banlist table using
username (actually user id), IP address, and email address where
applicable. A user matching a ban on any of those three items will not
be permitted into the server *unless* they have the moderator flag set.
It might be worth making an exception for board founders as well,
although that is probably orthogonal to this patchset.

Right now there are a few missing items:

 * The server sends clients an error that allows them to try again with
   a different username/password combination immediately. Nothing stops
   them from causing noise in the server logs this way, so we probably
   need to ensure this counts as an authentication failure for the
   purpose of temporarily and automatically banning the IP address.

 * The user handler doesn't allow retrieving details about the ban, so
   all that the main server code can do is report back to the client as
   their nickname being banned, when this is not necessarily the case
   (email or IP address bans). I need to figure out a better API for
   retrieving this info.

 * Likewise, the server does not log the specifics about the matched ban
   yet unless the mp_user_handler log domain is set to the info log
   level.

 * There's no i18n support on the client side for the error message sent
   by the server -- which is going to change anyway.

 * Testing this patch uncovered an issue with the MP client not
   displaying messages sent during the login sequence, including the mod
   authentication notice.
  • Loading branch information
irydacea committed May 6, 2018
1 parent 851b28e commit f2c06f0
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/multiplayer_error_codes.hpp
Expand Up @@ -26,6 +26,7 @@
#define MP_NAME_RESERVED_ERROR "104"
#define MP_NAME_UNREGISTERED_ERROR "105"
#define MP_NAME_INACTIVE_WARNING "106"
#define MP_NAME_AUTH_BAN_ERROR "107"

#define MP_PASSWORD_REQUEST "200"
#define MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME "201"
Expand Down
36 changes: 36 additions & 0 deletions src/server/forum_user_handler.cpp
Expand Up @@ -40,6 +40,7 @@ fuh::fuh(const config& c)
, db_user_(c["db_user"].str())
, db_password_(c["db_password"].str())
, db_users_table_(c["db_users_table"].str())
, db_banlist_table_(c["db_banlist_table"].str())
, db_extra_table_(c["db_extra_table"].str())
, conn(mysql_init(nullptr))
{
Expand Down Expand Up @@ -171,6 +172,41 @@ void fuh::set_is_moderator(const std::string& name, const bool& is_moderator) {
}
}

bool fuh::user_is_banned(const std::string& name, const std::string& addr)
{
if(!user_exists(name)) {
throw error("No user with the name '" + name + "' exists.");
}

try {
auto uid = get_detail_for_user<unsigned int>(name, "user_id");

if(uid == 0) {
ERR_UH << "Invalid user id for user '" << name << "'\n";
} else if(prepared_statement<bool>("SELECT 1 FROM `" + db_banlist_table_ + "` WHERE ban_userid = ? AND ban_exclude = 0", uid)) {
LOG_UH << "User '" << name << "' uid " << uid << " banned by uid\n";
return true;
}

if(!addr.empty() && prepared_statement<bool>("SELECT 1 FROM `" + db_banlist_table_ + "` WHERE UPPER(ban_ip) = UPPER(?) AND ban_exclude = 0", addr)) {
LOG_UH << "User '" << name << "' ip " << addr << " banned by IP address\n";
return true;
}

auto email = get_detail_for_user<std::string>(name, "user_email");

if(!email.empty() && prepared_statement<bool>("SELECT 1 FROM `" + db_banlist_table_ + "` WHERE UPPER(ban_email) = UPPER(?) AND ban_exclude = 0", email)) {
LOG_UH << "User '" << name << "' email " << email << " banned by email address\n";
return true;
}

} catch(sql_error& e) {
ERR_UH << "Could not check forum bans on user '" << name << "' :" << e.message << '\n';
}

return false;
}

std::string fuh::user_info(const std::string& name) {
if(!user_exists(name)) {
throw error("No user with the name '" + name + "' exists.");
Expand Down
5 changes: 4 additions & 1 deletion src/server/forum_user_handler.hpp
Expand Up @@ -30,6 +30,7 @@
// db_user=root
// db_password=secret
// db_users_table=users
// db_banlist_table=banlist
// db_extra_table=extra_data
//[/user_handler]

Expand Down Expand Up @@ -68,6 +69,8 @@ class fuh : public user_handler {
bool user_is_moderator(const std::string& name);
void set_is_moderator(const std::string& name, const bool& is_moderator);

bool user_is_banned(const std::string& name, const std::string& addr);

// Throws user_handler::error
std::string user_info(const std::string& name);

Expand All @@ -88,7 +91,7 @@ class fuh : public user_handler {

void set_lastlogin(const std::string& user, const time_t& lastlogin);

std::string db_name_, db_host_, db_user_, db_password_, db_users_table_, db_extra_table_;
std::string db_name_, db_host_, db_user_, db_password_, db_users_table_, db_banlist_table_, db_extra_table_;

typedef std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> mysql_result;

Expand Down
25 changes: 23 additions & 2 deletions src/server/mysql_prepared_statement.ipp
Expand Up @@ -76,6 +76,17 @@ static MYSQL_BIND make_bind(int& i, my_bool* is_null = 0)
return result;
}

static MYSQL_BIND make_bind(unsigned int& i, my_bool* is_null = 0)
{
MYSQL_BIND result;
memset(&result, 0, sizeof (MYSQL_BIND));
result.buffer_type = MYSQL_TYPE_LONG;
result.buffer = static_cast<void*>(&i);
result.is_unsigned = 1;
result.is_null = is_null;
return result;
}

template<typename... Args> constexpr auto make_binds(Args&&... args)
-> std::array<MYSQL_BIND, sizeof...(Args)>
{
Expand Down Expand Up @@ -122,9 +133,9 @@ template<> std::string fetch_result<std::string>(MYSQL_STMT* stmt, const std::st
return result;
}

template<> int fetch_result<int>(MYSQL_STMT* stmt, const std::string& sql)
template<typename T> T fetch_result_long_internal_(MYSQL_STMT* stmt, const std::string& sql)
{
int result;
T result;
my_bool is_null;
MYSQL_BIND result_bind[1] = { make_bind(result, &is_null) };

Expand All @@ -145,6 +156,16 @@ template<> int fetch_result<int>(MYSQL_STMT* stmt, const std::string& sql)
return result;
}

template<> int fetch_result<int>(MYSQL_STMT* stmt, const std::string& sql)
{
return fetch_result_long_internal_<int>(stmt, sql);
}

template<> unsigned int fetch_result<unsigned int>(MYSQL_STMT* stmt, const std::string& sql)
{
return fetch_result_long_internal_<unsigned int>(stmt, sql);
}

template<> bool fetch_result<bool>(MYSQL_STMT* stmt, const std::string& sql)
{
int result;
Expand Down
5 changes: 5 additions & 0 deletions src/server/sample_user_handler.cpp
Expand Up @@ -95,6 +95,11 @@ void suh::set_is_moderator(const std::string& name, const bool& is_moderator) {
users_[name].is_moderator = is_moderator;
}

bool suh::user_is_banned(const std::string&, const std::string&) {
// FIXME: stub
return false;
}

void suh::set_mail(const std::string& user, const std::string& mail) {
check_mail(mail);
users_[user].mail = mail;
Expand Down
2 changes: 2 additions & 0 deletions src/server/sample_user_handler.hpp
Expand Up @@ -43,6 +43,8 @@ class suh : public user_handler {
bool user_is_moderator(const std::string& name);
void set_is_moderator(const std::string& name, const bool& is_moderator);

bool user_is_banned(const std::string& name, const std::string&);

std::string user_info(const std::string& name);

struct user {
Expand Down
21 changes: 20 additions & 1 deletion src/server/server.cpp
Expand Up @@ -649,6 +649,21 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
return false;
}

const bool is_moderator = user_handler_ && user_handler_->user_is_moderator(username);
const bool is_auth_banned = user_handler_ && user_handler_->user_is_banned(username, client_address(socket));

if(is_auth_banned) {
if(!is_moderator) {
LOG_SERVER << client_address(socket) << "\t" << username
<< "\tis banned by user_handler\n";
async_send_error(socket, "The nickname '" + username + "' is banned on this server.", MP_NAME_AUTH_BAN_ERROR);
return false;
} else {
LOG_SERVER << client_address(socket) << "\t" << username
<< "\tis banned by user_handler, ignoring due to moderator flag\n";
}
}

if(name_taken) {
if(registered) {
// If there is already a client using this username kick it
Expand All @@ -670,7 +685,7 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
LOG_SERVER << client_address(socket) << "\t" << username
<< "\thas logged on" << (registered ? " to a registered account" : "") << "\n";

if(user_handler_ && user_handler_->user_is_moderator(username)) {
if(is_moderator) {
LOG_SERVER << "Admin automatically recognized: IP: "
<< client_address(socket) << "\tnick: "
<< username << std::endl;
Expand All @@ -679,6 +694,10 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
"If you no longer want to be automatically authenticated use '/query signout'.");
}

if(is_auth_banned) {
send_server_message(socket, "You are currently banned by the forum administration.");
}

// Log the IP
connection_log ip_name = connection_log(username, client_address(socket), 0);
if (std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) {
Expand Down
9 changes: 9 additions & 0 deletions src/server/user_handler.hpp
Expand Up @@ -108,6 +108,15 @@ class user_handler {
/** Mark this user as a moderator */
virtual void set_is_moderator(const std::string& name, const bool& is_moderator) =0;

/**
* Returns true if this user account or IP address is banned.
*
* @note The IP address is only used by the @a forum_user_handler
* subclass. Regular IP ban checks are done by @a server_base
* instead.
*/
virtual bool user_is_banned(const std::string& name, const std::string& addr="") = 0;

struct error : public game::error {
error(const std::string& message) : game::error(message) {}
};
Expand Down

0 comments on commit f2c06f0

Please sign in to comment.