Store client version and source information in the database.

This will have wesnoth now provide the source where it was downloaded from the wesnothd.
wesnothd will now store that, as well as the client's version in the `game_player_info` table.

Backport of #4446
This commit is contained in:
pentarctagon 2019-10-20 17:15:35 -05:00
parent 0d611acaf8
commit 5dbc990781
No known key found for this signature in database
GPG key ID: 29E48D667D52CCF3
12 changed files with 81 additions and 40 deletions

1
data/dist Normal file
View file

@ -0,0 +1 @@
Default

View file

@ -43,6 +43,8 @@
#include "utils/functional.hpp"
#include <fstream>
static lg::log_domain log_mp("mp/main");
#define DBG_MP LOG_STREAM(debug, log_mp)
#define ERR_MP LOG_STREAM(err, log_mp)
@ -156,6 +158,23 @@ std::pair<wesnothd_connection_ptr, config> open_connection(std::string host)
config cfg;
config res;
cfg["version"] = game_config::version;
// TODO: retest with below string values - dist doesn't exist
std::string info;
std::ifstream infofile("./data/dist");
if(infofile.is_open()){
infofile >> info;
infofile.close();
if(info == "Default" || info == "Steam" || info == "SourceForge" || info == "Flatpak"
|| info == "macOS App Store" || info == "Linux repository" || info == "iOS" || info == "Android") {
cfg["client_source"] = info;
} else {
cfg["client_source"] = "dist file has wrong value!";
}
} else {
cfg["client_source"] = "failed to read dist file!";
}
res.add_child("version", std::move(cfg));
sock->send_data(res);
}

View file

@ -470,10 +470,10 @@ void fuh::db_update_game_end(const std::string& uuid, int game_id, const std::st
}
}
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){
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){
try {
prepared_statement<void>("insert into `" + db_game_player_info_table_ + "`(INSTANCE_UUID, GAME_ID, USER_ID, SIDE_NUMBER, IS_HOST, FACTION) values(?, ?, IFNULL((select user_id from `"+db_users_table_+"` where username = ?), -1), ?, ?, ?)",
uuid, game_id, username, side_number, is_host, faction);
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) values(?, ?, IFNULL((select user_id from `"+db_users_table_+"` where username = ?), -1), ?, ?, ?, ?, ?)",
uuid, game_id, username, side_number, is_host, faction, version, source);
} 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;
}

View file

@ -84,7 +84,7 @@ class fuh : public user_handler {
void db_insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name);
void db_update_game_start(const std::string& uuid, int game_id, const std::string& map_name, const std::string& era_name, int reload);
void db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location);
void 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);
void 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);
void db_insert_modification_info(const std::string& uuid, int game_id, const std::string& modification_name);
void db_set_oos_flag(const std::string& uuid, int game_id);

View file

@ -16,11 +16,12 @@
#include "lexical_cast.hpp"
wesnothd::player::player(const std::string& n, simple_wml::node& cfg,
bool registered, const std::string& version, const size_t max_messages,
bool registered, const std::string& version, const std::string& source, const size_t max_messages,
const size_t time_period,
const bool moderator)
: name_(n)
, version_(version)
, source_(source)
, cfg_(cfg)
, registered_(registered)
, flood_start_(0)

View file

@ -30,7 +30,7 @@ public:
OBSERVING
};
player(const std::string& n, simple_wml::node& cfg, bool registered, const std::string& version,
player(const std::string& n, simple_wml::node& cfg, bool registered, const std::string& version, const std::string& source,
const size_t max_messages=4, const size_t time_period=10,
const bool moderator=false);
@ -46,6 +46,7 @@ public:
const std::string& name() const { return name_; }
const std::string& version() const { return version_; }
const std::string& source() const { return source_; }
const simple_wml::node* config_address() const { return &cfg_; }
bool is_message_flooding();
@ -56,6 +57,7 @@ public:
private:
const std::string name_;
std::string version_;
std::string source_;
simple_wml::node& cfg_;
bool registered_;

View file

@ -247,8 +247,8 @@ void suh::db_update_game_start(const std::string& uuid, int game_id, const std::
void suh::db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location){
std::cout << uuid << " - " << game_id << " - " << replay_location << std::endl;
}
void suh::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){
std::cout << uuid << " - " << game_id << " - " << username << " - " << side_number << " - " << is_host << " - " << faction << std::endl;
void suh::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){
std::cout << uuid << " - " << game_id << " - " << username << " - " << side_number << " - " << is_host << " - " << faction << " - " << version << " - " << source << std::endl;
}
void suh::db_insert_modification_info(const std::string& uuid, int game_id, const std::string& modification_name){
std::cout << uuid << " - " << game_id << " - " << modification_name << std::endl;

View file

@ -71,7 +71,7 @@ class suh : public user_handler {
void db_insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name);
void db_update_game_start(const std::string& uuid, int game_id, const std::string& map_name, const std::string& era_name, int reload);
void db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location);
void 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);
void 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);
void db_insert_modification_info(const std::string& uuid, int game_id, const std::string& modification_name);
void db_set_oos_flag(const std::string& uuid, int game_id);

View file

@ -615,6 +615,9 @@ void server::read_version(socket_ptr socket, std::shared_ptr<simple_wml::documen
const simple_wml::string_span& version_str_span = (*version)["version"];
const std::string version_str(version_str_span.begin(), version_str_span.end());
const simple_wml::string_span& source_str_span = (*version)["client_source"];
const std::string source_str(source_str_span.begin(), source_str_span.end());
// Check if it is an accepted version.
auto accepted_it = std::find_if(accepted_versions_.begin(), accepted_versions_.end(),
std::bind(&utils::wildcard_string_match, version_str, _1));
@ -622,7 +625,7 @@ void server::read_version(socket_ptr socket, std::shared_ptr<simple_wml::documen
if(accepted_it != accepted_versions_.end()) {
LOG_SERVER << client_address(socket) << "\tplayer joined using accepted version " << version_str
<< ":\ttelling them to log in.\n";
async_send_doc(socket, login_response_, std::bind(&server::login, this, _1, version_str));
async_send_doc(socket, login_response_, std::bind(&server::login, this, _1, version_str, source_str));
return;
}
@ -659,23 +662,23 @@ void server::read_version(socket_ptr socket, std::shared_ptr<simple_wml::documen
}
}
void server::login(socket_ptr socket, std::string version)
void server::login(socket_ptr socket, std::string version, std::string source)
{
async_receive_doc(socket, std::bind(&server::handle_login, this, _1, _2, version));
async_receive_doc(socket, std::bind(&server::handle_login, this, _1, _2, version, source));
}
void server::handle_login(socket_ptr socket, std::shared_ptr<simple_wml::document> doc, std::string version)
void server::handle_login(socket_ptr socket, std::shared_ptr<simple_wml::document> doc, std::string version, std::string source)
{
if(const simple_wml::node* const login = doc->child("login")) {
if(!is_login_allowed(socket, login, version)) {
server::login(socket, version); // keep reading logins from client until we get a successful one
if(!is_login_allowed(socket, login, version, source)) {
server::login(socket, version, source); // keep reading logins from client until we get a successful one
}
} else {
async_send_error(socket, "You must login first.", MP_MUST_LOGIN);
}
}
bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& version)
bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& version, const std::string& source)
{
// Check if the username is valid (all alpha-numeric plus underscore and hyphen)
std::string username = (*login)["username"].to_string();
@ -712,7 +715,7 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
// Check for password
bool registered;
if(!authenticate(socket, username, (*login)["password"].to_string(), version, name_taken, registered))
if(!authenticate(socket, username, (*login)["password"].to_string(), version, source, name_taken, registered))
return true; // it's a failed login but we don't want to call server::login again
// because send_password_request() will handle the next network write and read instead
@ -790,13 +793,14 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
}
}
async_send_doc(socket, join_lobby_response_, [this, username, registered, version](socket_ptr socket) {
async_send_doc(socket, join_lobby_response_, [this, username, registered, version, source](socket_ptr socket) {
simple_wml::node& player_cfg = games_and_users_list_.root().add_child("user");
add_player(socket, wesnothd::player(
username,
player_cfg,
registered,
version,
source,
default_max_messages_,
default_time_period_,
user_handler_ && user_handler_->user_is_moderator(username)
@ -849,7 +853,7 @@ bool server::is_login_allowed(socket_ptr socket, const simple_wml::node* const l
}
bool server::authenticate(
socket_ptr socket, const std::string& username, const std::string& password, const std::string& version, bool name_taken, bool& registered)
socket_ptr socket, const std::string& username, const std::string& password, const std::string& version, const std::string& source, bool name_taken, bool& registered)
{
// Current login procedure for registered nicks is:
// - Client asks to log in with a particular nick
@ -879,13 +883,13 @@ bool server::authenticate(
if(password.empty()) {
if(!name_taken) {
send_password_request(socket, "The nickname '" + username + "' is registered on this server.",
username, version, MP_PASSWORD_REQUEST);
username, version, source, MP_PASSWORD_REQUEST);
} else {
send_password_request(socket,
"The nickname '" + username + "' is registered on this server."
"\n\nWARNING: There is already a client using this username, "
"logging in will cause that client to be kicked!",
username, version, MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME, true
username, version, source, MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME, true
);
}
@ -895,7 +899,7 @@ bool server::authenticate(
// A password (or hashed password) was provided, however
// there is no seed
if(seeds_[socket.get()].empty()) {
send_password_request(socket, "Please try again.", username, version, MP_NO_SEED_ERROR);
send_password_request(socket, "Please try again.", username, version, source, MP_NO_SEED_ERROR);
return false;
}
@ -936,7 +940,7 @@ bool server::authenticate(
"You have made too many failed login attempts.", MP_TOO_MANY_ATTEMPTS_ERROR);
} else {
send_password_request(socket,
"The password you provided for the nickname '" + username + "' was incorrect.", username,version,
"The password you provided for the nickname '" + username + "' was incorrect.", username,version,source,
MP_INCORRECT_PASSWORD_ERROR);
}
@ -962,6 +966,7 @@ void server::send_password_request(socket_ptr socket,
const std::string& msg,
const std::string& user,
const std::string& version,
const std::string& source,
const char* error_code,
bool force_confirmation)
{
@ -980,7 +985,7 @@ void server::send_password_request(socket_ptr socket,
"cannot log in due to an error in the hashing algorithm. "
"Logging into your forum account on https://forums.wesnoth.org "
"may fix this problem.");
login(socket, version);
login(socket, version, source);
return;
}
@ -998,7 +1003,7 @@ void server::send_password_request(socket_ptr socket,
e.set_attr("error_code", error_code);
}
async_send_doc(socket, doc, std::bind(&server::login, this, _1, version));
async_send_doc(socket, doc, std::bind(&server::login, this, _1, version, source));
}
void server::add_player(socket_ptr socket, const wesnothd::player& player)
@ -1744,7 +1749,19 @@ void server::handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml
const simple_wml::node::child_list& sides = g.get_sides_list();
for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
const simple_wml::node& side = *sides[side_index];
user_handler_->db_insert_game_player_info(uuid_, g.id(), side["player_id"].to_string(), side["side"].to_int(), side["is_host"].to_bool(), side["faction"].to_string());
const auto player = player_connections_.get<name_t>().find(side["player_id"].to_string());
std::string version;
std::string source;
// if "Nobody" is chosen for a side, for example
if(player == player_connections_.get<name_t>().end()){
version = "";
source = "";
} else {
version = player->info().version();
source = player->info().source();
}
user_handler_->db_insert_game_player_info(uuid_, g.id(), side["player_id"].to_string(), side["side"].to_int(), side["is_host"].to_bool(), side["faction"].to_string(), version, source);
}
const std::string mods = multiplayer["active_mods"].to_string();

View file

@ -42,14 +42,13 @@ private:
void handle_version(socket_ptr socket);
void read_version(socket_ptr socket, std::shared_ptr<simple_wml::document> doc);
void login(socket_ptr socket, std::string version);
void handle_login(socket_ptr socket, std::shared_ptr<simple_wml::document> doc, std::string version);
bool is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& version);
bool authenticate(socket_ptr socket, const std::string& username, const std::string& password, const std::string& version, bool name_taken, bool& registered);
void login(socket_ptr socket, std::string version, std::string source);
void handle_login(socket_ptr socket, std::shared_ptr<simple_wml::document> doc, std::string version, std::string source);
bool is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& version, const std::string& source);
bool authenticate(socket_ptr socket, const std::string& username, const std::string& password, const std::string& version, const std::string& source, bool name_taken, bool& registered);
void send_password_request(socket_ptr socket, const std::string& msg,
const std::string& user, const std::string& version, const char* error_code = "", bool force_confirmation = false);
const std::string& user, const std::string& version, const std::string& source, const char* error_code = "", bool force_confirmation = false);
bool accepting_connections() const { return !graceful_restart; }
void add_player(socket_ptr socket, const wesnothd::player&);
void read_from_player(socket_ptr socket);
void handle_read_from_player(socket_ptr socket, std::shared_ptr<simple_wml::document> doc);

View file

@ -175,7 +175,7 @@ class user_handler {
virtual void db_insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name) =0;
virtual void db_update_game_start(const std::string& uuid, int game_id, const std::string& map_name, const std::string& era_name, int reload) =0;
virtual void db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location) =0;
virtual void 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) =0;
virtual void 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) =0;
virtual void db_insert_modification_info(const std::string& uuid, int game_id, const std::string& modification_name) =0;
virtual void db_set_oos_flag(const std::string& uuid, int game_id) =0;

View file

@ -39,7 +39,7 @@ create table extra
(
USERNAME VARCHAR(100) NOT NULL,
USER_LASTVISIT INT(10) UNSIGNED NOT NULL DEFAULT 0,
USER_IS_MODERATOR BIT(1) NOT NULL DEFAULT 0,
USER_IS_MODERATOR TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (USERNAME)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@ -81,12 +81,14 @@ create table game_info
-- STATUS: the status of the side, currently only updated at game end
create table game_player_info
(
INSTANCE_UUID CHAR(36) NOT NULL,
GAME_ID INT UNSIGNED NOT NULL,
USER_ID INT NOT NULL,
SIDE_NUMBER SMALLINT UNSIGNED NOT NULL,
IS_HOST BIT(1) NOT NULL,
FACTION VARCHAR(255) NOT NULL,
INSTANCE_UUID CHAR(36) NOT NULL,
GAME_ID INT UNSIGNED NOT NULL,
USER_ID INT NOT NULL,
SIDE_NUMBER SMALLINT UNSIGNED NOT NULL,
IS_HOST BIT(1) NOT NULL,
FACTION VARCHAR(255) NOT NULL,
CLIENT_VERSION VARCHAR(255) NOT NULL DEFAULT '',
CLIENT_SOURCE VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (INSTANCE_UUID, GAME_ID, SIDE_NUMBER)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;