Use mariadbpp rather than the mysql C connector.

Reasoning being:
* The result_set and other APIs are nicer to use.
* We use mariadb on our server rather than mysql, so this would minimize the chance of any incompatibilities.
* The mysql C++ connector 1.1 isn't compatible with with c++17 (https://stackoverflow.com/q/47284705).
This commit is contained in:
Pentarctagon 2020-04-30 10:16:45 -05:00 committed by Pentarctagon
parent f996803cf6
commit 113a4e0913
21 changed files with 786 additions and 572 deletions

View file

@ -1,6 +1,6 @@

# set minimum version
cmake_minimum_required(VERSION 2.8.5)
cmake_minimum_required(VERSION 3.1)
project(wesnoth)
@ -62,6 +62,11 @@ option(ENABLE_HISTORY "Enable using GNU history for history in lua console" ON)
if(NOT CXX_STD)
set(CXX_STD "14")
endif()
set(CMAKE_CXX_STANDARD ${CXX_STD})
# make sure to force using it
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# forbid defaulting to gnu++NN instead of c++NN
set(CMAKE_CXX_EXTENSIONS OFF)
if(ENABLE_GAME OR ENABLE_TESTS)
# find_package(OpenGL REQUIRED)
@ -209,7 +214,7 @@ if(NOT DEFINED CXX_FLAGS_USER)
endif(NOT DEFINED CXX_FLAGS_USER)
set(COMPILER_FLAGS "-std=c++${CXX_STD} -Wall -Wextra -Werror=non-virtual-dtor -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wold-style-cast -Wtrampolines")
set(COMPILER_FLAGS "-Wall -Wextra -Werror=non-virtual-dtor -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wold-style-cast -Wtrampolines")
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(COMPILER_FLAGS "${COMPILER_FLAGS} -Qunused-arguments -Wno-unknown-warning-option -Wmismatched-tags -Wno-conditional-uninitialized -Wno-unused-lambda-capture")

View file

@ -1709,6 +1709,13 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\gui\dialogs\server_info_dialog.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\gui\dialogs\help_browser.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Gui\Dialogs\</ObjectFileName>

View file

@ -596,6 +596,9 @@
<ClCompile Include="..\..\src\gui\dialogs\game_version_dialog.cpp">
<Filter>Gui\Dialogs</Filter>
</ClCompile>
<ClCompile Include="..\..\src\gui\dialogs\server_info_dialog.cpp">
<Filter>Gui\Dialogs</Filter>
</ClCompile>
<ClCompile Include="..\..\src\gui\dialogs\gamestate_inspector.cpp">
<Filter>Gui\Dialogs</Filter>
</ClCompile>

View file

@ -342,6 +342,30 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Server\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\server\dbconn.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Server\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\server\resultsets\tournaments.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Server\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\server\resultsets\ban_check.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Server\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\server\player.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Server\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Server\</ObjectFileName>

View file

@ -39,6 +39,15 @@
<ClCompile Include="..\..\src\server\metrics.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="..\..\src\server\dbconn.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="..\..\src\server\resultsets\tournaments.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="..\..\src\server\resultsets\ban_check.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="..\..\src\server\player.cpp">
<Filter>Server</Filter>
</ClCompile>

View file

@ -2,6 +2,7 @@ server/ban.cpp
server/forum_user_handler.cpp
server/game.cpp
server/metrics.cpp
server/dbconn.cpp
server/player.cpp
server/player_connection.cpp
server/player_network.cpp
@ -9,3 +10,5 @@ server/server.cpp
server/server_base.cpp
server/simple_wml.cpp
server/user_handler.cpp
server/resultsets/tournaments.cpp
server/resultsets/ban_check.cpp

View file

@ -12,6 +12,7 @@ if(ENABLE_MYSQL)
string(REGEX REPLACE "-I" "" MYSQL_CFLAGS "${MYSQL_CFLAGS}")
string(REGEX REPLACE "-DNDEBUG" "" MYSQL_CFLAGS "${MYSQL_CFLAGS}")
execute_process(COMMAND mysql_config --libs OUTPUT_VARIABLE MYSQL_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE)
find_package(mariadbclientpp)
endif(ENABLE_MYSQL)
## some includes ##
@ -343,6 +344,7 @@ if(ENABLE_SERVER)
if(ENABLE_MYSQL)
target_include_directories(wesnothd SYSTEM PRIVATE ${MYSQL_CFLAGS})
target_compile_definitions(wesnothd PRIVATE HAVE_MYSQLPP)
target_link_libraries(wesnothd mariadbclientpp::mariadbclientpp)
endif(ENABLE_MYSQL)
target_link_libraries(wesnothd
@ -366,6 +368,7 @@ if(ENABLE_CAMPAIGN_SERVER)
if(ENABLE_MYSQL)
target_include_directories(campaignd SYSTEM PRIVATE ${MYSQL_CFLAGS})
target_compile_definitions(campaignd PRIVATE HAVE_MYSQLPP)
target_link_libraries(wesnothd mariadbclientpp::mariadbclientpp)
endif(ENABLE_MYSQL)
target_link_libraries(campaignd

View file

@ -175,6 +175,8 @@ if have_client_prereqs:
wesnothd_sources = GetSources("wesnothd")
if env["PLATFORM"] == 'win32':
env.WesnothProgram("wesnothd", wesnothd_sources + libwesnoth_core + env["wesnothd_res"], have_server_prereqs)
elif env["forum_user_handler"]:
env.WesnothProgram("wesnothd", wesnothd_sources + libwesnoth_core + ["/usr/local/lib/libmariadbclientpp.a"], have_server_prereqs)
else:
env.WesnothProgram("wesnothd", wesnothd_sources + libwesnoth_core, have_server_prereqs)

397
src/server/dbconn.cpp Normal file
View file

@ -0,0 +1,397 @@
/*
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifdef HAVE_MYSQLPP
#include "dbconn.hpp"
#include "resultsets/tournaments.hpp"
#include "resultsets/ban_check.hpp"
#include "log.hpp"
static lg::log_domain log_sql_handler("sql_executor");
#define ERR_SQL LOG_STREAM(err, log_sql_handler)
#define WRN_SQL LOG_STREAM(warn, log_sql_handler)
#define LOG_SQL LOG_STREAM(info, log_sql_handler)
#define DBG_SQL LOG_STREAM(debug, log_sql_handler)
dbconn::dbconn(const config& c)
: db_users_table_(c["db_users_table"].str())
, db_banlist_table_(c["db_banlist_table"].str())
, db_extra_table_(c["db_extra_table"].str())
, db_game_info_table_(c["db_game_info_table"].str())
, db_game_player_info_table_(c["db_game_player_info_table"].str())
, db_game_modification_info_table_(c["db_game_modification_info_table"].str())
, db_user_group_table_(c["db_user_group_table"].str())
, db_tournament_query_(c["db_tournament_query"].str())
{
try
{
// NOTE: settings put on the connection, rather than the account, are NOT kept if a reconnect occurs!
account_ = mariadb::account::create(c["db_host"].str(), c["db_user"].str(), c["db_password"].str());
account_->set_connect_option(mysql_option::MYSQL_SET_CHARSET_NAME, std::string("utf8mb4"));
account_->set_schema(c["db_name"].str());
connection_ = mariadb::connection::create(account_);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Failed to connect to the database!", e);
}
}
void dbconn::log_sql_exception(const std::string& text, const mariadb::exception::base& e)
{
ERR_SQL << text << '\n'
<< "what: " << e.what() << '\n'
<< "error id: " << e.error_id() << std::endl;
}
//
// queries
//
std::string dbconn::get_uuid()
{
try
{
return get_single_string("SELECT UUID()");
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Could not retrieve a UUID!", e);
return "";
}
}
std::string dbconn::get_tournaments()
{
if(db_tournament_query_ == "")
{
return "";
}
try
{
tournaments t;
get_complex_results(t, db_tournament_query_);
return t.str();
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Could not retrieve the tournaments!", e);
return "";
}
}
bool dbconn::user_exists(const std::string& name)
{
try
{
return exists("SELECT 1 FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)", name);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Unable to check if user row for '"+name+"' exists!", e);
return false;
}
}
bool dbconn::extra_row_exists(const std::string& name)
{
try
{
return exists("SELECT 1 FROM `"+db_extra_table_+"` WHERE UPPER(username)=UPPER(?)", name);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Unable to check if extra row for '"+name+"' exists!", e);
return false;
}
}
bool dbconn::is_user_in_group(const std::string& name, int group_id)
{
try
{
return exists("SELECT 1 FROM `"+db_users_table_+"` u, `"+db_user_group_table_+"` ug WHERE UPPER(u.username)=UPPER(?) AND u.USER_ID = ug.USER_ID AND ug.GROUP_ID = ?", name, group_id);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Unable to check if the user '"+name+"' is in group '"+std::to_string(group_id)+"'!", e);
return false;
}
}
ban_check dbconn::get_ban_info(const std::string& name, const std::string& ip)
{
try
{
// selected ban_type value must be part of user_handler::BAN_TYPE
ban_check b;
get_complex_results(b, "select ban_userid, ban_email, case when ban_ip != '' then 1 when ban_userid != 0 then 2 when ban_email != '' then 3 end as ban_type, ban_end from `"+db_banlist_table_+"` where (ban_ip = ? or ban_userid = (select user_id from `"+db_users_table_+"` where UPPER(username) = UPPER(?)) or UPPER(ban_email) = (select UPPER(user_email) from `"+db_users_table_+"` where UPPER(username) = UPPER(?))) AND ban_exclude = 0 AND (ban_end = 0 OR ban_end >= ?)", ip, name, name, std::time(nullptr));
return b;
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Failed to check ban info for user '"+name+"' connecting from ip '"+ip+"'!", e);
return ban_check();
}
}
std::string dbconn::get_user_string(const std::string& table, const std::string& column, const std::string& name)
{
try
{
return get_single_string("SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", name);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Unable to query column `"+column+"` from table `"+table+"` for user `"+name+"`", e);
return "";
}
}
int dbconn::get_user_int(const std::string& table, const std::string& column, const std::string& name)
{
try
{
return get_single_int("SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", name);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Unable to query column `"+column+"` from table `"+table+"` for user `"+name+"`", e);
return 0;
}
}
void dbconn::write_user_int(const std::string& column, const std::string& name, int value)
{
try
{
if(!extra_row_exists(name))
{
modify("INSERT INTO `"+db_extra_table_+"` VALUES(?,?,'0')", name, value);
}
modify("UPDATE `"+db_extra_table_+"` SET "+column+"=? WHERE UPPER(username)=UPPER(?)", value, name);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Unable to write `"+std::to_string(value)+"` to column `"+column+"` on table `"+db_extra_table_+"` for user `"+name+"`", e);
}
}
void dbconn::insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name, const std::string& map_name, const std::string& era_name, int reload, int observers, int is_public, int has_password)
{
try
{
modify("INSERT INTO `"+db_game_info_table_+"`(INSTANCE_UUID, GAME_ID, INSTANCE_VERSION, GAME_NAME, MAP_NAME, ERA_NAME, RELOAD, OBSERVERS, PUBLIC, PASSWORD) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
uuid, game_id, version, name, map_name, era_name, reload, observers, is_public, has_password);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Failed to insert game info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
}
}
void dbconn::update_game_end(const std::string& uuid, int game_id, const std::string& replay_location)
{
try
{
modify("UPDATE `"+db_game_info_table_+"` SET END_TIME = CURRENT_TIMESTAMP, REPLAY_NAME = ? WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
replay_location, uuid, game_id);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Failed to update the game end for game info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
}
}
void dbconn::insert_game_player_info(const std::string& uuid, int game_id, const std::string& username, int side_number, int is_host, const std::string& faction, const std::string& version, const std::string& source, const std::string& current_user)
{
try
{
modify("INSERT INTO `"+db_game_player_info_table_+"`(INSTANCE_UUID, GAME_ID, USER_ID, SIDE_NUMBER, IS_HOST, FACTION, CLIENT_VERSION, CLIENT_SOURCE, USER_NAME) VALUES(?, ?, IFNULL((SELECT user_id FROM `"+db_users_table_+"` WHERE username = ?), -1), ?, ?, ?, ?, ?, ?)",
uuid, game_id, username, side_number, is_host, faction, version, source, current_user);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Failed to insert game player info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
}
}
void dbconn::insert_modification_info(const std::string& uuid, int game_id, const std::string& modification_name)
{
try
{
modify("INSERT INTO `"+db_game_modification_info_table_+"`(INSTANCE_UUID, GAME_ID, MODIFICATION_NAME) VALUES(?, ?, ?)",
uuid, game_id, modification_name);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Failed to insert game modification info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
}
}
void dbconn::set_oos_flag(const std::string& uuid, int game_id)
{
try
{
modify("UPDATE `"+db_game_info_table_+"` SET OOS = 1 WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
uuid, game_id);
}
catch(const mariadb::exception::base& e)
{
log_sql_exception("Failed to set the OOS flag for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
}
}
//
// queries can return data with various types that can't be easily fit into a pre-determined structure
// therefore for queries that can return multiple rows of multiple columns, implement a class to define how the results should be read
//
template<typename... Args>
void dbconn::get_complex_results(rs_base& base, const std::string& sql, Args&&... args)
{
mariadb::result_set_ref rslt = select(sql, args...);
base.read(rslt);
}
//
// get single values
//
template<typename... Args>
std::string dbconn::get_single_string(const std::string& sql, Args&&... args)
{
mariadb::result_set_ref rslt = select(sql, args...);
if(rslt->next())
{
return rslt->get_string(0);
}
else
{
throw mariadb::exception::base("No string value found in the database!");
}
}
template<typename... Args>
int dbconn::get_single_int(const std::string& sql, Args&&... args)
{
mariadb::result_set_ref rslt = select(sql, args...);
if(rslt->next())
{
// mariadbpp checks for strict integral equivalence, but we don't care
// so check the type beforehand, call the associated getter, and let it silently get upcast to an int if needed
switch(rslt->column_type(0))
{
case mariadb::value::type::unsigned8:
case mariadb::value::type::signed8:
return rslt->get_signed8(0);
case mariadb::value::type::unsigned16:
case mariadb::value::type::signed16:
return rslt->get_signed16(0);
case mariadb::value::type::unsigned32:
case mariadb::value::type::signed32:
return rslt->get_signed32(0);
default:
throw mariadb::exception::base("Value retrieved was not an int!");
}
}
else
{
throw mariadb::exception::base("No int value found in the database!");
}
}
template<typename... Args>
bool dbconn::exists(const std::string& sql, Args&&... args)
{
mariadb::result_set_ref rslt = select(sql, args...);
return rslt->next();
}
//
// select or modify values
//
template<typename... Args>
mariadb::result_set_ref dbconn::select(const std::string& sql, Args&&... args)
{
try
{
mariadb::statement_ref stmt = query(sql, args...);
return mariadb::result_set_ref(stmt->query());
}
catch(const mariadb::exception::base& e)
{
if(!connection_->connected())
{
ERR_SQL << "Connection is invalid!" << std::endl;
}
ERR_SQL << "SQL query failed for query: `"+sql+"`!" << std::endl;
throw e;
}
}
template<typename... Args>
int dbconn::modify(const std::string& sql, Args&&... args)
{
try
{
mariadb::statement_ref stmt = query(sql, args...);
return stmt->insert();
}
catch(const mariadb::exception::base& e)
{
if(!connection_->connected())
{
ERR_SQL << "Connection is invalid!" << std::endl;
}
ERR_SQL << "SQL query failed for query: `"+sql+"`!" << std::endl;
throw e;
}
}
//
// start of recursive unpacking of variadic template in order to be able to call correct parameterized setters on query
//
template<typename... Args>
mariadb::statement_ref dbconn::query(const std::string& sql, Args&&... args)
{
mariadb::statement_ref stmt = connection_->create_statement(sql);
prepare(stmt, 0, args...);
return stmt;
}
// split off the next parameter
template<typename Arg, typename... Args>
void dbconn::prepare(mariadb::statement_ref stmt, int i, Arg arg, Args&&... args)
{
i = prepare(stmt, i, arg);
prepare(stmt, i, args...);
}
// template specialization for supported parameter types
// there are other parameter setters, but so far there hasn't been a reason to add them
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, int arg)
{
stmt->set_signed32(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, long arg)
{
stmt->set_signed64(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, const char* arg)
{
stmt->set_string(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, std::string arg)
{
stmt->set_string(i++, arg);
return i;
}
// no more parameters, nothing left to do
void dbconn::prepare(mariadb::statement_ref, int){}
#endif //HAVE_MYSQLPP

92
src/server/dbconn.hpp Normal file
View file

@ -0,0 +1,92 @@
/*
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "config.hpp"
#include "resultsets/rs_base.hpp"
#include "resultsets/ban_check.hpp"
#include <mysql/mysql.h>
#include <mariadb++/account.hpp>
#include <mariadb++/connection.hpp>
#include <mariadb++/statement.hpp>
#include <mariadb++/result_set.hpp>
#include <mariadb++/exceptions.hpp>
#include <vector>
#include <unordered_map>
class dbconn
{
public:
dbconn(const config& c);
std::string get_uuid();
std::string get_tournaments();
bool user_exists(const std::string& name);
bool extra_row_exists(const std::string& name);
bool is_user_in_group(const std::string& name, int group_id);
std::string get_user_string(const std::string& table, const std::string& column, const std::string& name);
int get_user_int(const std::string& table, const std::string& column, const std::string& name);
void write_user_int(const std::string& column, const std::string& name, int value);
ban_check get_ban_info(const std::string& name, const std::string& ip);
void insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name, const std::string& map_name, const std::string& era_name, int reload, int observers, int is_public, int has_password);
void update_game_end(const std::string& uuid, int game_id, const std::string& replay_location);
void insert_game_player_info(const std::string& uuid, int game_id, const std::string& username, int side_number, int is_host, const std::string& faction, const std::string& version, const std::string& source, const std::string& current_user);
void insert_modification_info(const std::string& uuid, int game_id, const std::string& modification_name);
void set_oos_flag(const std::string& uuid, int game_id);
private:
mariadb::account_ref account_;
mariadb::connection_ref connection_;
std::string db_users_table_;
std::string db_banlist_table_;
std::string db_extra_table_;
std::string db_game_info_table_;
std::string db_game_player_info_table_;
std::string db_game_modification_info_table_;
std::string db_user_group_table_;
std::string db_tournament_query_;
void log_sql_exception(const std::string& text, const mariadb::exception::base& e);
template<typename... Args>
void get_complex_results(rs_base& base, const std::string& sql, Args&&... args);
template<typename... Args>
std::string get_single_string(const std::string& sql, Args&&... args);
template<typename... Args>
int get_single_int(const std::string& sql, Args&&... args);
template<typename... Args>
bool exists(const std::string& sql, Args&&... args);
template<typename... Args>
mariadb::result_set_ref select(const std::string& sql, Args&&... args);
template<typename... Args>
int modify(const std::string& sql, Args&&... args);
template<typename... Args>
mariadb::statement_ref query(const std::string& sql, Args&&... args);
template<typename Arg, typename... Args>
void prepare(mariadb::statement_ref stmt, int i, Arg arg, Args&&... args);
template<typename Arg>
int prepare(mariadb::statement_ref stmt, int i, Arg arg);
void prepare(mariadb::statement_ref stmt, int i);
};

View file

@ -15,7 +15,6 @@
#ifdef HAVE_MYSQLPP
#include "server/forum_user_handler.hpp"
#include "server/mysql_prepared_statement.ipp"
#include "hash.hpp"
#include "log.hpp"
#include "config.hpp"
@ -35,41 +34,20 @@ namespace {
}
fuh::fuh(const config& c)
: db_name_(c["db_name"].str())
, db_host_(c["db_host"].str())
, db_user_(c["db_user"].str())
, db_password_(c["db_password"].str())
: conn(c)
, db_users_table_(c["db_users_table"].str())
, db_banlist_table_(c["db_banlist_table"].str())
, db_extra_table_(c["db_extra_table"].str())
, db_game_info_table_(c["db_game_info_table"].str())
, db_game_player_info_table_(c["db_game_player_info_table"].str())
, db_game_modification_info_table_(c["db_game_modification_info_table"].str())
, db_user_group_table_(c["db_user_group_table"].str())
, db_tournament_query_(c["db_tournament_query"].str())
, mp_mod_group_(0)
, conn(mysql_init(nullptr))
{
try {
mp_mod_group_ = std::stoi(c["mp_mod_group"].str());
} catch(...) {
ERR_UH << "Failed to convert the mp_mod_group value of '" << c["mp_mod_group"].str() << "' into an int! Defaulting to " << mp_mod_group_ << "." << std::endl;
}
mysql_options(conn, MYSQL_SET_CHARSET_NAME, "utf8mb4");
if(!conn || !mysql_real_connect(conn, db_host_.c_str(), db_user_.c_str(), db_password_.c_str(), db_name_.c_str(), 0, nullptr, 0)) {
ERR_UH << "Could not connect to database: " << mysql_errno(conn) << ": " << mysql_error(conn) << std::endl;
}
}
fuh::~fuh() {
mysql_close(conn);
}
// The hashing code is basically taken from forum_auth.cpp
bool fuh::login(const std::string& name, const std::string& password, const std::string& seed) {
// Retrieve users' password as hash
std::string hash;
try {
@ -131,49 +109,26 @@ void fuh::user_logged_in(const std::string& name) {
}
bool fuh::user_exists(const std::string& name) {
// Make a test query for this username
try {
return prepared_statement<bool>("SELECT 1 FROM `" + db_users_table_ + "` WHERE UPPER(username)=UPPER(?)", name);
} catch (const sql_error& e) {
ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl;
// If the database is down just let all usernames log in
return false;
}
return conn.user_exists(name);
}
bool fuh::user_is_active(const std::string& name) {
try {
int user_type = get_detail_for_user<int>(name, "user_type");
return user_type != USER_INACTIVE && user_type != USER_IGNORE;
} catch (const sql_error& e) {
ERR_UH << "Could not retrieve user type for user '" << name << "' :" << e.message << std::endl;
return false;
}
int user_type = conn.get_user_int(db_users_table_, "user_type", name);
return user_type != USER_INACTIVE && user_type != USER_IGNORE;
}
bool fuh::user_is_moderator(const std::string& name) {
if(!user_exists(name)) return false;
try {
return get_writable_detail_for_user<int>(name, "user_is_moderator") == 1 || (mp_mod_group_ != 0 && is_user_in_group(name, mp_mod_group_));
} catch (const sql_error& e) {
ERR_UH << "Could not query user_is_moderator/MP Moderators group for user '" << name << "' :" << e.message << std::endl;
// If the database is down mark nobody as a mod
if(!user_exists(name)){
return false;
}
return conn.get_user_int(db_extra_table_, "user_is_moderator", name) == 1 || (mp_mod_group_ != 0 && conn.is_user_in_group(name, mp_mod_group_));
}
void fuh::set_is_moderator(const std::string& name, const bool& is_moderator) {
if(!user_exists(name)) return;
try {
write_detail(name, "user_is_moderator", static_cast<int>(is_moderator));
} catch (const sql_error& e) {
ERR_UH << "Could not set is_moderator for user '" << name << "' :" << e.message << std::endl;
if(!user_exists(name)){
return;
}
conn.write_user_int("user_is_moderator", name, is_moderator);
}
fuh::ban_info fuh::user_is_banned(const std::string& name, const std::string& addr)
@ -184,95 +139,24 @@ fuh::ban_info fuh::user_is_banned(const std::string& name, const std::string& ad
// prepared SQL statement API right now. However, they are basically
// never used on forums.wesnoth.org, so this shouldn't be a problem
// for the time being.
//
// NOTE: A ban end time of 0 is a permanent ban.
const std::string& is_extant_ban_sql =
"ban_exclude = 0 AND (ban_end = 0 OR ban_end >=" + std::to_string(std::time(nullptr)) + ")";
// TODO: retrieve full ban info in a single statement instead of issuing
// separate queries to check for a ban's existence and its duration.
try {
if(!addr.empty() && prepared_statement<bool>("SELECT 1 FROM `" + db_banlist_table_ + "` WHERE UPPER(ban_ip) = UPPER(?) AND " + is_extant_ban_sql, addr)) {
ban_check b = conn.get_ban_info(name, addr);
switch(b.get_ban_type())
{
case BAN_NONE:
return {};
case BAN_IP:
LOG_UH << "User '" << name << "' ip " << addr << " banned by IP address\n";
return retrieve_ban_info(BAN_IP, addr);
}
} catch(const sql_error& e) {
ERR_UH << "Could not check forum bans on address '" << addr << "' :" << e.message << '\n';
return {};
return { BAN_IP, b.get_ban_duration() };
case BAN_USER:
LOG_UH << "User '" << name << "' uid " << b.get_user_id() << " banned by uid\n";
return { BAN_USER, b.get_ban_duration() };
case BAN_EMAIL:
LOG_UH << "User '" << name << "' email " << b.get_email() << " banned by email address\n";
return { BAN_EMAIL, b.get_ban_duration() };
default:
ERR_UH << "Invalid ban type '" << b.get_ban_type() << "' returned for user '" << name << "'\n";
return {};
}
if(!user_exists(name)) return {};
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 " + is_extant_ban_sql, uid)) {
LOG_UH << "User '" << name << "' uid " << uid << " banned by uid\n";
return retrieve_ban_info(BAN_USER, uid);
}
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 " + is_extant_ban_sql, email)) {
LOG_UH << "User '" << name << "' email " << email << " banned by email address\n";
return retrieve_ban_info(BAN_EMAIL, email);
}
} catch(const sql_error& e) {
ERR_UH << "Could not check forum bans on user '" << name << "' :" << e.message << '\n';
}
return {};
}
template<typename T>
fuh::ban_info fuh::retrieve_ban_info(fuh::BAN_TYPE type, T detail)
{
std::string col;
switch(type) {
case BAN_USER:
col = "ban_userid";
break;
case BAN_EMAIL:
col = "ban_email";
break;
case BAN_IP:
col = "ban_ip";
break;
default:
return {};
}
try {
return { type, retrieve_ban_duration_internal(col, detail) };
} catch(const sql_error& e) {
//
// NOTE:
// If retrieve_ban_internal() fails to fetch the ban row, odds are the ban was
// lifted in the meantime (it's meant to be called by user_is_banned(), so we
// assume the ban expires in one second instead of returning 0 (permanent ban)
// just to err on the safe side (returning BAN_NONE would be a terrible idea,
// for that matter).
//
return { type, 1 };
}
}
std::time_t fuh::retrieve_ban_duration_internal(const std::string& col, const std::string& detail)
{
const std::time_t end_time = prepared_statement<int>("SELECT `ban_end` FROM `" + db_banlist_table_ + "` WHERE UPPER(" + col + ") = UPPER(?)", detail);
return end_time ? end_time - std::time(nullptr) : 0;
}
std::time_t fuh::retrieve_ban_duration_internal(const std::string& col, unsigned int detail)
{
const std::time_t end_time = prepared_statement<int>("SELECT `ban_end` FROM `" + db_banlist_table_ + "` WHERE " + col + " = ?", detail);
return end_time ? end_time - std::time(nullptr) : 0;
}
std::string fuh::user_info(const std::string& name) {
@ -304,175 +188,47 @@ std::string fuh::user_info(const std::string& name) {
}
std::string fuh::get_hash(const std::string& user) {
try {
return get_detail_for_user<std::string>(user, "user_password");
} catch (const sql_error& e) {
ERR_UH << "Could not retrieve password for user '" << user << "' :" << e.message << std::endl;
return "";
}
return conn.get_user_string(db_users_table_, "user_password", user);
}
std::time_t fuh::get_lastlogin(const std::string& user) {
try {
int time_int = get_writable_detail_for_user<int>(user, "user_lastvisit");
return std::time_t(time_int);
} catch (const sql_error& e) {
ERR_UH << "Could not retrieve last visit for user '" << user << "' :" << e.message << std::endl;
return std::time_t(0);
}
return std::time_t(conn.get_user_int(db_extra_table_, "user_lastvisit", user));
}
std::time_t fuh::get_registrationdate(const std::string& user) {
try {
int time_int = get_detail_for_user<int>(user, "user_regdate");
return std::time_t(time_int);
} catch (const sql_error& e) {
ERR_UH << "Could not retrieve registration date for user '" << user << "' :" << e.message << std::endl;
return std::time_t(0);
}
return std::time_t(conn.get_user_int(db_users_table_, "user_regdate", user));
}
void fuh::set_lastlogin(const std::string& user, const std::time_t& lastlogin) {
try {
write_detail(user, "user_lastvisit", static_cast<int>(lastlogin));
} catch (const sql_error& e) {
ERR_UH << "Could not set last visit for user '" << user << "' :" << e.message << std::endl;
}
}
template<typename T, typename... Args>
inline T fuh::prepared_statement(const std::string& sql, Args&&... args)
{
try {
return ::prepared_statement<T>(conn, sql, std::forward<Args>(args)...);
} catch (const sql_error& e) {
WRN_UH << "caught sql error: " << e.message << std::endl;
WRN_UH << "trying to reconnect and retry..." << std::endl;
//Try to reconnect and execute query again
mysql_close(conn);
conn = mysql_init(nullptr);
mysql_options(conn, MYSQL_SET_CHARSET_NAME, "utf8mb4");
if(!conn || !mysql_real_connect(conn, db_host_.c_str(), db_user_.c_str(), db_password_.c_str(), db_name_.c_str(), 0, nullptr, 0)) {
ERR_UH << "Could not connect to database: " << mysql_errno(conn) << ": " << mysql_error(conn) << std::endl;
throw sql_error("Error querying database.");
}
}
return ::prepared_statement<T>(conn, sql, std::forward<Args>(args)...);
}
template<typename T>
T fuh::get_detail_for_user(const std::string& name, const std::string& detail) {
return prepared_statement<T>(
"SELECT `" + detail + "` FROM `" + db_users_table_ + "` WHERE UPPER(username)=UPPER(?)",
name);
}
template<typename T>
T fuh::get_writable_detail_for_user(const std::string& name, const std::string& detail) {
if(!extra_row_exists(name)) throw sql_error("row doesn't exist");
return prepared_statement<T>(
"SELECT `" + detail + "` FROM `" + db_extra_table_ + "` WHERE UPPER(username)=UPPER(?)",
name);
}
template<typename T>
void fuh::write_detail(const std::string& name, const std::string& detail, T&& value) {
try {
// Check if we do already have a row for this user in the extra table
if(!extra_row_exists(name)) {
// If not create the row
prepared_statement<void>("INSERT INTO `" + db_extra_table_ + "` VALUES(?,?,'0')", name, std::forward<T>(value));
}
prepared_statement<void>("UPDATE `" + db_extra_table_ + "` SET " + detail + "=? WHERE UPPER(username)=UPPER(?)", std::forward<T>(value), name);
} catch (const sql_error& e) {
ERR_UH << "Could not set detail for user '" << name << "': " << e.message << std::endl;
}
}
bool fuh::is_user_in_group(const std::string& name, unsigned int group_id) {
try {
return prepared_statement<bool>("SELECT 1 FROM `" + db_users_table_ + "` u, `" + db_user_group_table_ + "` ug WHERE UPPER(u.username)=UPPER(?) AND u.USER_ID = ug.USER_ID AND ug.GROUP_ID = ?", name, group_id);
} catch (const sql_error& e) {
ERR_UH << "Could not execute test query for user group '" << group_id << "' and username '" << name << "'" << e.message << std::endl;
return false;
}
}
bool fuh::extra_row_exists(const std::string& name) {
// Make a test query for this username
try {
return prepared_statement<bool>("SELECT 1 FROM `" + db_extra_table_ + "` WHERE UPPER(username)=UPPER(?)", name);
} catch (const sql_error& e) {
ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl;
return false;
}
conn.write_user_int("user_lastvisit", user, static_cast<int>(lastlogin));
}
std::string fuh::get_uuid(){
try {
return prepared_statement<std::string>("SELECT UUID()");
} catch (const sql_error& e) {
ERR_UH << "Could not retrieve a UUID:" << e.message << std::endl;
return "";
}
return conn.get_uuid();
}
// TODO - WIP
// select substring(substring_index(topic_title, ']', 1), 2) as STATUS, concat('https://r.wesnoth.org/t', topic_id) as URL, substring_index(topic_title, ']', -1) as TITLE from tournaments where forum_id = 70 and (topic_title like '[Open]%' or topic_title like '[In Progress]%')
std::string fuh::get_tournaments(){
try {
return "";
} catch (const sql_error& e) {
ERR_UH << "TBD:" << e.message << std::endl;
return "";
}
return conn.get_tournaments();
}
void fuh::db_insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name, const std::string& map_name, const std::string& era_name, int reload, int observers, int is_public, int has_password){
try {
prepared_statement<void>("INSERT INTO `" + db_game_info_table_ + "`(INSTANCE_UUID, GAME_ID, INSTANCE_VERSION, GAME_NAME, MAP_NAME, ERA_NAME, RELOAD, OBSERVERS, PUBLIC, PASSWORD) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
uuid, game_id, version, name, map_name, era_name, reload, observers, is_public, has_password);
} catch (const sql_error& e) {
ERR_UH << "Could not insert into table `" + db_game_info_table_ + "`:" << e.message << std::endl;
}
conn.insert_game_info(uuid, game_id, version, name, map_name, era_name, reload, observers, is_public, has_password);
}
void fuh::db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location){
try {
prepared_statement<void>("UPDATE `" + db_game_info_table_ + "` SET END_TIME = CURRENT_TIMESTAMP, REPLAY_NAME = ? WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
replay_location, uuid, game_id);
} catch (const sql_error& e) {
ERR_UH << "Could not update the game's ending information on table `" + db_game_info_table_ + "`:" << e.message << std::endl;
}
conn.update_game_end(uuid, game_id, replay_location);
}
void fuh::db_insert_game_player_info(const std::string& uuid, int game_id, const std::string& username, int side_number, int is_host, const std::string& faction, const std::string& version, const std::string& source, const std::string& current_user){
try {
prepared_statement<void>("INSERT INTO `" + db_game_player_info_table_ + "`(INSTANCE_UUID, GAME_ID, USER_ID, SIDE_NUMBER, IS_HOST, FACTION, CLIENT_VERSION, CLIENT_SOURCE, USER_NAME) VALUES(?, ?, IFNULL((SELECT user_id FROM `"+db_users_table_+"` WHERE username = ?), -1), ?, ?, ?, ?, ?, ?)",
uuid, game_id, username, side_number, is_host, faction, version, source, current_user);
} catch (const sql_error& e) {
ERR_UH << "Could not insert the game's player information on table `" + db_game_player_info_table_ + "`:" << e.message << std::endl;
}
conn.insert_game_player_info(uuid, game_id, username, side_number, is_host, faction, version, source, current_user);
}
void fuh::db_insert_modification_info(const std::string& uuid, int game_id, const std::string& modification_name){
try {
prepared_statement<void>("INSERT INTO `" + db_game_modification_info_table_ + "`(INSTANCE_UUID, GAME_ID, MODIFICATION_NAME) VALUES(?, ?, ?)",
uuid, game_id, modification_name);
} catch (const sql_error& e) {
ERR_UH << "Could not insert the game's modification information on table `" + db_game_modification_info_table_ + "`:" << e.message << std::endl;
}
conn.insert_modification_info(uuid, game_id, modification_name);
}
void fuh::db_set_oos_flag(const std::string& uuid, int game_id){
try {
prepared_statement<void>("UPDATE `" + db_game_info_table_ + "` SET OOS = 1 WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
uuid, game_id);
} catch (const sql_error& e) {
ERR_UH << "Could not update the game's OOS flag on table `" + db_game_info_table_ + "`:" << e.message << std::endl;
}
conn.set_oos_flag(uuid, game_id);
}
#endif //HAVE_MYSQLPP

View file

@ -15,13 +15,12 @@
#pragma once
#include "server/user_handler.hpp"
#include "dbconn.hpp"
#include <vector>
#include <memory>
#include <ctime>
#include <mysql/mysql.h>
// The [user_handler] section in the server configuration
// file could look like this:
//
@ -41,7 +40,6 @@
class fuh : public user_handler {
public:
fuh(const config& c);
~fuh();
bool login(const std::string& name, const std::string& password, const std::string& seed);
@ -64,7 +62,6 @@ class fuh : public user_handler {
ban_info user_is_banned(const std::string& name, const std::string& addr);
// Throws user_handler::error
std::string user_info(const std::string& name);
bool use_phpbb_encryption() const { return true; }
@ -78,40 +75,16 @@ class fuh : public user_handler {
void db_set_oos_flag(const std::string& uuid, int game_id);
private:
dbconn conn;
std::string db_users_table_;
std::string db_extra_table_;
int mp_mod_group_;
std::string get_hash(const std::string& user);
std::time_t get_lastlogin(const std::string& user);
std::time_t get_registrationdate(const std::string& user);
bool is_inactive(const std::string& user);
void set_lastlogin(const std::string& user, const std::time_t& lastlogin);
template<typename T>
ban_info retrieve_ban_info(BAN_TYPE, T detail);
std::time_t retrieve_ban_duration_internal(const std::string& col, const std::string& detail);
std::time_t retrieve_ban_duration_internal(const std::string& col, unsigned int detail);
std::string db_name_, db_host_, db_user_, db_password_, db_users_table_, db_banlist_table_, db_extra_table_, db_game_info_table_, db_game_player_info_table_, db_game_modification_info_table_, db_user_group_table_;
std::string db_tournament_query_;
unsigned int mp_mod_group_;
MYSQL *conn;
template<typename T, typename... Args>
inline T prepared_statement(const std::string& sql, Args&&...);
// Query a detail for a particular user from the database
template<typename T>
T get_detail_for_user(const std::string& name, const std::string& detail);
template<typename T>
T get_writable_detail_for_user(const std::string& name, const std::string& detail);
// Write something to the write table
template<typename T>
void write_detail(const std::string& name, const std::string& detail, T&& value);
// Same as user_exists() but checks if we have a row for this user in the extra table
bool extra_row_exists(const std::string& name);
bool is_user_in_group(const std::string& name, unsigned int group_id);
bool is_user_in_group(const std::string& name, int group_id);
};

View file

@ -1,245 +0,0 @@
/*
Copyright (C) 2016 by Sergey Popov <loonycyborg@gmail.com>
Part of the Battle for Wesnoth Project https://www.wesnoth.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License 2
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef MYSQL_PREPARED_STATEMENT_IPP
#define MYSQL_PREPARED_STATEMENT_IPP
#include <array>
#include <utility>
#include <memory>
#include <string>
#include <string.h>
#define BOOST_SCOPE_EXIT_CONFIG_USE_LAMBDAS
#include <boost/scope_exit.hpp>
#include <mysql/mysql.h>
#if !defined(MARIADB_VERSION_ID) && defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 80000
using my_bool = bool;
#endif
#include "exceptions.hpp"
struct sql_error : public game::error
{
bool no_data = false;
sql_error(const std::string& message, const std::string& sql, bool no_data = false)
: game::error("Error evaluating SQL statement: '" + sql + "': " + message), no_data(no_data) {}
sql_error(const std::string& message) : game::error(message) {}
};
// make_bind functions embed pointers to their arguments in the
// MYSQL_BIND structure returned. It's caller's responsibility
// to ensure that argument's lifetime doesn't end before mysql
// is done with those MYSQL_BINDs
static MYSQL_BIND make_bind(const std::string& str, my_bool* is_null = 0)
{
MYSQL_BIND result;
memset(&result, 0, sizeof (MYSQL_BIND));
result.buffer_type = MYSQL_TYPE_STRING;
result.buffer = const_cast<void*>(static_cast<const void*>(str.c_str()));
result.buffer_length = str.size();
result.is_unsigned = 0;
result.is_null = is_null;
result.length = 0;
return result;
}
static MYSQL_BIND make_bind(char* str, std::size_t* len, my_bool* is_null = 0)
{
MYSQL_BIND result;
memset(&result, 0, sizeof (MYSQL_BIND));
result.buffer_type = MYSQL_TYPE_STRING;
result.buffer = static_cast<void*>(str);
result.buffer_length = *len;
result.is_unsigned = 0;
result.is_null = is_null;
result.length = len;
return result;
}
static MYSQL_BIND make_bind(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 = 0;
result.is_null = is_null;
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)>
{
return { { (make_bind(std::forward<Args>(args))) ... } };
}
template<typename T> T fetch_result(MYSQL_STMT* stmt, const std::string& sql);
template<> std::string fetch_result<std::string>(MYSQL_STMT* stmt, const std::string& sql)
{
char* buf = nullptr;
std::string result;
std::size_t len = 0;
my_bool is_null;
MYSQL_BIND result_bind[1] = { make_bind(buf, &len, &is_null) };
if(mysql_stmt_bind_result(stmt, result_bind) != 0)
throw sql_error(mysql_stmt_error(stmt), sql);
BOOST_SCOPE_EXIT(&stmt) {
mysql_stmt_free_result(stmt);
} ;
mysql_stmt_store_result(stmt);
int res = mysql_stmt_fetch(stmt);
if(res == MYSQL_NO_DATA)
throw sql_error("no data returned", sql, true);
if(is_null)
throw sql_error("null value returned", sql);
if(res != MYSQL_DATA_TRUNCATED)
throw sql_error(mysql_stmt_error(stmt), sql);
if(len > 0) {
buf = new char[len];
result_bind[0].buffer = buf;
result_bind[0].buffer_length = len;
res = mysql_stmt_fetch_column(stmt, result_bind, 0, 0);
result = std::string(buf, len);
delete[] buf;
}
if(res == MYSQL_NO_DATA)
throw sql_error("no data returned", sql, true);
if(res != 0)
throw sql_error("mysql_stmt_fetch_column failed", sql);
return result;
}
template<typename T> T fetch_result_long_internal_(MYSQL_STMT* stmt, const std::string& sql)
{
T result;
my_bool is_null;
MYSQL_BIND result_bind[1] = { make_bind(result, &is_null) };
if(mysql_stmt_bind_result(stmt, result_bind) != 0)
throw sql_error(mysql_stmt_error(stmt), sql);
BOOST_SCOPE_EXIT(&stmt) {
mysql_stmt_free_result(stmt);
} ;
int res = mysql_stmt_fetch(stmt);
if(res == MYSQL_NO_DATA)
throw sql_error("no data returned", sql, true);
if(is_null)
throw sql_error("null value returned", sql);
if(res != 0)
throw sql_error(mysql_stmt_error(stmt), 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;
my_bool is_null;
MYSQL_BIND result_bind[1] = { make_bind(result, &is_null) };
if(mysql_stmt_bind_result(stmt, result_bind) != 0)
throw sql_error(mysql_stmt_error(stmt), sql);
BOOST_SCOPE_EXIT(&stmt) {
mysql_stmt_free_result(stmt);
} ;
int res = mysql_stmt_fetch(stmt);
if(res == MYSQL_NO_DATA)
return false;
if(is_null)
throw sql_error("null value returned", sql);
if(res != 0)
throw sql_error(mysql_stmt_error(stmt), sql);
return true;
}
template<typename C> C fetch_result(MYSQL_STMT* stmt, const std::string& sql)
{
C result;
try {
while(true)
result.push_back(fetch_result<typename C::value_type>(stmt, sql));
} catch(const sql_error& e) {
if(!e.no_data)
throw;
}
return result;
}
template<> void fetch_result<void>(MYSQL_STMT*, const std::string&)
{
}
/**
* Execute an sql query using mysql prepared statements API
* This function can convert its arguments and results to appropriate
* MYSQL_BIND structures automatically based on their C++ type
* though each type requires explicit support. For now only ints and
* std::strings are supported.
* Setting return type to bool causes this function to do a test query
* and return true if there is any data in result set, false otherwise
*/
template<typename R, typename... Args>
R prepared_statement(MYSQL* conn, const std::string& sql, Args&&... args)
{
auto arg_binds = make_binds(args...);
std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)> stmt{mysql_stmt_init(conn), mysql_stmt_close};
if(stmt == nullptr)
throw sql_error("mysql_stmt_init failed", sql);
if(mysql_stmt_prepare(stmt.get(), sql.c_str(), sql.size()) != 0)
throw sql_error(mysql_stmt_error(stmt.get()), sql);
if(mysql_stmt_bind_param(stmt.get(), arg_binds.data()) != 0)
throw sql_error(mysql_stmt_error(stmt.get()), sql);
if(mysql_stmt_execute(stmt.get()) != 0)
throw sql_error(mysql_stmt_error(stmt.get()), sql);
return fetch_result<R>(stmt.get(), sql);
}
#endif // MYSQL_PREPARED_STATEMENT_IPP

View file

@ -0,0 +1,60 @@
/*
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifdef HAVE_MYSQLPP
#include <ctime>
#include "ban_check.hpp"
#include "server/user_handler.hpp"
ban_check::ban_check()
{
ban_type = user_handler::BAN_TYPE::BAN_NONE;
ban_duration = 0;
user_id = 0;
email = "";
}
void ban_check::read(mariadb::result_set_ref rslt)
{
if(rslt->next())
{
ban_type = rslt->get_signed64("ban_type");
ban_duration = rslt->get_signed32("ban_end") != 0 ? rslt->get_signed32("ban_end") - std::time(nullptr) : 0;
user_id = rslt->get_signed32("ban_userid");
email = rslt->get_string("ban_email");
}
}
long ban_check::get_ban_type()
{
return ban_type;
}
int ban_check::get_ban_duration()
{
return ban_duration;
}
int ban_check::get_user_id()
{
return user_id;
}
std::string ban_check::get_email()
{
return email;
}
#endif //HAVE_MYSQLPP

View file

@ -0,0 +1,35 @@
/*
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include <mariadb++/result_set.hpp>
#include "rs_base.hpp"
class ban_check : public rs_base
{
public:
ban_check();
void read(mariadb::result_set_ref rslt);
long get_ban_type();
int get_ban_duration();
int get_user_id();
std::string get_email();
private:
long ban_type;
int ban_duration;
int user_id;
std::string email;
};

View file

@ -0,0 +1,24 @@
/*
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include <mariadb++/result_set.hpp>
class rs_base
{
public:
rs_base(){}
virtual ~rs_base(){}
virtual void read(mariadb::result_set_ref rslt) =0;
};

View file

@ -0,0 +1,36 @@
/*
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifdef HAVE_MYSQLPP
#include "tournaments.hpp"
void tournaments::read(mariadb::result_set_ref rslt)
{
while(rslt->next())
{
rows.push_back(data{ rslt->get_string("TITLE"), rslt->get_string("STATUS"), rslt->get_string("URL") });
}
}
std::string tournaments::str()
{
std::string text;
for(const auto& row : rows)
{
text += "\nThe tournament "+row.title+" is "+row.status+". More information can be found at "+row.url;
}
return text;
}
#endif //HAVE_MYSQLPP

View file

@ -0,0 +1,36 @@
/*
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include <mariadb++/result_set.hpp>
#include <vector>
#include "rs_base.hpp"
class tournaments : public rs_base
{
struct data
{
std::string title;
std::string status;
std::string url;
};
public:
void read(mariadb::result_set_ref rslt);
std::string str();
private:
std::vector<data> rows;
};

View file

@ -78,10 +78,10 @@ class user_handler {
/** Ban type values */
enum BAN_TYPE
{
BAN_NONE, /**< Not a ban */
BAN_USER, /**< User account/name ban */
BAN_IP, /**< IP address ban */
BAN_EMAIL, /**< Account email address ban */
BAN_NONE = 0, /**< Not a ban */
BAN_IP = 1, /**< IP address ban */
BAN_USER = 2, /**< User account/name ban */
BAN_EMAIL = 3, /**< Account email address ban */
};
/** Ban status description */

View file

@ -25,10 +25,4 @@ RUN yes | pip3 install paramiko
# programs
RUN apt install -y -qq openssl gdb xvfb bzip2 git scons cmake make ccache gcc g++ clang lld
# mariadbpp
RUN git clone --depth 1 https://github.com/viaduck/mariadbpp.git mariadbpp && \
cd mariadbpp && git submodule update --init && mkdir build && cd build && \
cmake .. && \
make install
WORKDIR /home/wesnoth-travis

View file

@ -21,7 +21,7 @@
-- table which the forum inserts bans into, which wesnothd checks during login
-- create table ban
-- (
-- BAN_USERID VARCHAR(100) NOT NULL,
-- BAN_USERID INT(10) UNSIGNED NOT NULL,
-- BAN_END INT(10) UNSIGNED NOT NULL DEFAULT 0,
-- BAN_IP VARCHAR(100) DEFAULT NULL,
-- BAN_EMAIL VARCHAR(100) DEFAULT NULL,