wesnothd: Initial support for checking forum bans during login
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.
This commit is contained in:
parent
4fb2a52d69
commit
bea9de0885
8 changed files with 100 additions and 4 deletions
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
@ -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.");
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)>
|
||||
{
|
||||
|
@ -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) };
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
|
|
@ -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) {}
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue