48 #include <boost/algorithm/string.hpp>
49 #include <boost/scope_exit.hpp>
68 #define ERR_SERVER LOG_STREAM(err, log_server)
71 #define WRN_SERVER LOG_STREAM(warn, log_server)
74 #define LOG_SERVER LOG_STREAM(info, log_server)
75 #define DBG_SERVER LOG_STREAM(debug, log_server)
78 #define ERR_CONFIG LOG_STREAM(err, log_config)
79 #define WRN_CONFIG LOG_STREAM(warn, log_config)
81 using namespace std::chrono_literals;
92 if(!out.
child(
"gamelist_diff")) {
105 assert(!children.empty());
108 index = children.size() - 1;
111 assert(
index <
static_cast<int>(children.size()));
118 const char* gamelist,
123 if(!out.
child(
"gamelist_diff")) {
137 if(itor == children.end()) {
141 const int index = std::distance(children.begin(), itor);
151 const char* gamelist,
156 if(!out.
child(
"gamelist_diff")) {
168 const auto itor =
std::find(children.begin(), children.end(), item);
170 if(itor == children.end()) {
177 const int index = std::distance(children.begin(), itor);
193 auto [
d,
h, m,
s] = chrono::deconstruct_duration<chrono::days, std::chrono::hours, std::chrono::minutes, std::chrono::seconds>(
player.time_logged_on());
194 std::ostringstream out;
197 <<
d.count() <<
" days, "
198 <<
h.count() <<
" hours, "
199 << m.count() <<
" minutes, "
200 <<
s.count() <<
" seconds";
204 const std::string
denied_msg =
"You're not allowed to execute this command.";
206 "Available commands are: adminmsg <msg>,"
207 " ban <mask> <time> <reason>, bans [deleted] [<ipmask>], clones,"
208 " dul|deny_unregistered_login [yes|no], kick <mask> [<reason>],"
209 " k[ick]ban <mask> <time> <reason>, help, games, metrics,"
210 " [lobby]msg <message>, motd [<message>],"
211 " pm|privatemsg <nickname> <message>, requests, roll <sides>, sample, searchlog <mask>,"
212 " signout, stats, status [<mask>], stopgame <nick> [<reason>], reset_queues, unban <ipmask>\n"
213 "Specific strings (those not in between <> like the command names)"
214 " are case insensitive.";
216 server::server(
int port,
218 const std::string& config_file)
224 , user_handler_(nullptr)
225 , die_(static_cast<unsigned>(std::time(nullptr)))
230 , config_file_(config_file)
231 , cfg_(read_config())
232 , accepted_versions_()
233 , redirected_versions_()
235 , disallowed_names_()
242 , default_max_messages_(0)
243 , default_time_period_(0)
244 , concurrent_connections_(0)
245 , graceful_restart(false)
248 , max_ip_log_size_(0)
249 , deny_unregistered_login_(false)
250 , save_replays_(false)
251 , replay_save_path_()
252 , allow_remote_shutdown_(false)
255 , failed_login_limit_()
256 , failed_login_ban_()
257 , failed_login_buffer_size_()
262 , dump_stats_timer_(io_service_)
263 , tournaments_timer_(io_service_)
265 , timer_(io_service_)
266 , lan_server_timer_(io_service_)
267 , dummy_player_timer_(io_service_)
268 , dummy_player_timer_interval_(30)
281 ERR_SERVER <<
"Unable to retrieve UUID from database";
293 WRN_SERVER <<
"SIGHUP caught, reloading config";
306 if(
games().empty()) {
307 process_command(
"msg All games ended. Shutting down now. Reconnect to the new server instance.",
"system");
338 if(res != 0 && errno != EEXIST) {
342 int fifo = open(
input_path_.c_str(), O_RDWR | O_NONBLOCK);
348 LOG_SERVER <<
"opened fifo at '" <<
input_path_ <<
"'. Server commands may be written to this file.";
358 std::cout << error.message() << std::endl;
364 std::getline(is, cmd);
371 if(!cmd.empty() && cmd.at(0) ==
'+') {
374 <<
"[/admin_command_response]";
386 #define SETUP_HANDLER(name, function) \
387 cmd_handlers_[name] = std::bind(function, this, \
388 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
449 #warning No FIFODIR set
450 #define FIFODIR "/var/run/wesnothd"
452 const std::string fifo_path
453 = (
cfg_[
"fifo_path"].
empty() ? std::string(
FIFODIR) +
"/socket" : std::string(
cfg_[
"fifo_path"]));
474 const config&
game = queue.mandatory_child(
"game");
475 queue_info q =
queue_info(queue[
"id"].to_int(), queue[
"display_name"].str(), queue[
"players_required"].to_int(),
game);
479 std::map<int, queue_info> new_queue_info;
481 const config&
game = queue.mandatory_child(
"game");
482 queue_info q =
queue_info(queue[
"id"].to_int(), queue[
"display_name"].str(), queue[
"players_required"].to_int(),
game);
483 new_queue_info.emplace(q.
id, q);
487 for(
auto& [
id,
info] : new_queue_info) {
492 update.set_attr_dup(
"action",
"add");
493 update.set_attr_dup(
"display_name",
info.display_name.c_str());
494 update.set_attr_int(
"players_required",
info.players_required);
506 update.set_attr_dup(
"action",
"update");
507 update.set_attr_dup(
"display_name",
info.display_name.c_str());
508 update.set_attr_int(
"players_required",
info.players_required);
516 if(new_queue_info.count(
id) == 0) {
520 update.set_attr_dup(
"action",
"remove");
540 for(
const std::string& source :
utils::split(
cfg_[
"client_sources"].str())) {
545 if(
cfg_[
"disallow_names"].empty()) {
576 const std::string& versions =
cfg_[
"versions_accepted"];
577 if(versions.empty() ==
false) {
593 for(
const std::string& version :
utils::split(proxy[
"version"])) {
609 ERR_SERVER <<
"The server id must be set when database support is used";
620 if(
cfg_[
"dummy_player_count"].to_int() > 0) {
621 for(
int i = 0;
i <
cfg_[
"dummy_player_count"].to_int();
i++) {
632 if(
cfg_[
"dummy_player_timer_interval"].to_int() > 0) {
645 std::size_t connections = 0;
647 if(
player.client_ip() == ip) {
664 server_ban_info->get_reason(),
665 server_ban_info->get_remaining_ban_time()
681 ERR_SERVER <<
"Error waiting for dump stats timer: " << ec.message();
685 <<
"\tnumber_of_games = " <<
games().size()
699 ERR_SERVER <<
"Error waiting for dummy player timer: " << ec.message();
742 ERR_SERVER <<
"Error waiting for tournament refresh timer: " << ec.message();
753 boost::asio::spawn(
io_service_, [socket,
this](boost::asio::yield_context yield) {
login_client(std::move(yield), socket); }
754 #if BOOST_VERSION >= 108000
755 , [](
const std::exception_ptr&
e) {
if (
e) std::rethrow_exception(
e); }
762 boost::asio::spawn(
io_service_, [socket,
this](boost::asio::yield_context yield) {
login_client(std::move(yield), socket); }
763 #if BOOST_VERSION >= 108000
764 , [](
const std::exception_ptr&
e) {
if (
e) std::rethrow_exception(
e); }
769 template<
class SocketPtr>
777 std::string client_version, client_source;
780 client_version = std::string { version_str_span.
begin(), version_str_span.
end() };
783 client_source = std::string { source_str_span.
begin(), source_str_span.
end() };
791 <<
":\ttelling them to log in.";
800 <<
":\tredirecting them to " << redirect_version.second[
"host"] <<
":"
801 << redirect_version.second[
"port"];
804 for(
const auto& attr : redirect_version.second.attribute_range()) {
805 redirect.set_attr_dup(attr.first.c_str(), attr.second.str().c_str());
814 <<
":\trejecting them";
829 std::string username;
830 bool registered, is_moderator;
837 username = (*login)[
"username"].to_string();
839 if(
is_login_allowed(yield, socket, login, username, registered, is_moderator)) {
865 assert(inserted &&
"unexpected duplicate username");
868 join_lobby_response.
root().
add_child(
"join_lobby").
set_attr(
"is_moderator", is_moderator ?
"yes" :
"no");
875 queue_node.
set_attr_dup(
"display_name", queue.display_name.c_str());
876 queue_node.
set_attr_int(
"players_required", queue.players_required);
882 [
this, socket, new_player](boost::asio::yield_context yield) {
handle_player(std::move(yield), socket, new_player); }
883 #if BOOST_VERSION >= 108000
884 , [](
const std::exception_ptr&
e) {
if (
e) std::rethrow_exception(
e); }
889 << (registered ?
" to a registered account" :
"");
891 std::shared_ptr<game> last_sent;
893 auto g_ptr = record.get_game();
894 if(g_ptr != last_sent) {
896 g_ptr->send_server_message_to_all(username +
" has logged into the lobby");
916 template<
class SocketPtr>
bool server::is_login_allowed(boost::asio::yield_context yield, SocketPtr socket,
const simple_wml::node*
const login,
const std::string& username,
bool& registered,
bool& is_moderator)
921 "The nickname '" + username +
"' contains invalid "
922 "characters. Only alpha-numeric characters, underscores and hyphens are allowed.",
929 if(username.size() > 20) {
930 async_send_error(socket,
"The nickname '" + username +
"' is too long. Nicks must be 20 characters or less.",
939 async_send_error(socket,
"The nickname '" + username +
"' is reserved and cannot be used by players",
958 "The nickname '" + username +
"' is not registered. This server disallows unregistered nicknames.",
973 std::string ban_type_desc;
974 std::string ban_reason;
975 const char* msg_numeric;
978 switch(auth_ban.
type) {
980 ban_type_desc =
"account";
982 ban_reason =
"a ban has been issued on your user account.";
985 ban_type_desc =
"IP address";
987 ban_reason =
"a ban has been issued on your IP address.";
990 ban_type_desc =
"email address";
992 ban_reason =
"a ban has been issued on your email address.";
995 ban_type_desc =
"<unknown ban type>";
997 ban_reason = ban_type_desc;
1000 ban_reason +=
" (" + ban_duration +
")";
1003 LOG_SERVER <<
log_address(socket) <<
"\t" << username <<
"\tis banned by user_handler (" << ban_type_desc
1007 async_send_error(socket,
"You are banned from this server: " + ban_reason, msg_numeric, {{
"duration", ban_duration}});
1010 async_send_error(socket,
"You are banned from this server: " + ban_reason, msg_numeric);
1014 LOG_SERVER <<
log_address(socket) <<
"\t" << username <<
"\tis banned by user_handler (" << ban_type_desc
1015 <<
"), " <<
"ignoring due to moderator flag";
1022 process_command(
"kick " + username +
" autokick by registered user", username);
1025 boost::asio::post(yield);
1034 send_server_message(socket,
"You are currently banned by the forum administration.",
"alert");
1041 SocketPtr socket,
const std::string& username,
const std::string& password,
bool name_taken,
bool& registered)
1057 "The nickname '" + username +
"' is inactive. You cannot claim ownership of this "
1058 "nickname until you activate your account via email or ask an administrator to do it for you.",
1061 const std::string salt =
user_handler_->extract_salt(username);
1064 "Even though your nickname is registered on this server you "
1065 "cannot log in due to an error in the hashing algorithm. "
1066 "Logging into your forum account on https://forums.wesnoth.org "
1067 "may fix this problem.");
1070 const std::string hashed_password =
hash_password(password, salt, username);
1073 if(password.empty()) {
1078 "The nickname '" + username +
"' is registered on this server."
1079 "\n\nWARNING: There is already a client using this username, "
1080 "logging in will cause that client to be kicked!",
1090 if(hashed_password.empty()) {
1095 else if(!(
user_handler_->login(username, hashed_password))) {
1096 const auto steady_now = std::chrono::steady_clock::now();
1122 "Maximum login attempts exceeded",
"automatic",
"", username);
1127 "The password you provided for the nickname '" + username +
"' was incorrect.",
1133 <<
"Login attempt with incorrect password for nickname '" << username <<
"'.";
1147 const std::string&
msg,
1148 const char* error_code,
1149 bool force_confirmation)
1153 e.set_attr_dup(
"message",
msg.c_str());
1154 e.set_attr(
"password_request",
"yes");
1155 e.set_attr(
"force_confirmation", force_confirmation ?
"yes" :
"no");
1157 if(*error_code !=
'\0') {
1158 e.set_attr(
"error_code", error_code);
1169 BOOST_SCOPE_EXIT_ALL(
this, &
player) {
1177 if(!
motd_.empty()) {
1199 if(doc->child(
"refresh_lobby")) {
1261 int offset = request->attr(
"offset").to_int();
1266 if(request->has_attr(
"search_player") && request->attr(
"search_player").to_string() !=
"") {
1267 std::string player_name = request->attr(
"search_player").to_string();
1272 player_id = player_ptr->info().config_address()->attr(
"forum_id").to_int();
1276 std::string search_game_name = request->attr(
"search_game_name").to_string();
1277 int search_content_type = request->attr(
"search_content_type").to_int();
1278 std::string search_content = request->attr(
"search_content").to_string();
1279 LOG_SERVER <<
"Querying game history requested by player `" <<
player->info().
name() <<
"` for player id `" << player_id <<
"`."
1280 <<
"Searching for game name `" << search_game_name <<
"`, search content type `" << search_content_type <<
"`, search content `" << search_content <<
"`.";
1281 user_handler_->async_get_and_send_game_history(
io_service_, *
this,
player->socket(), player_id, offset, search_game_name, search_content_type, search_content);
1289 if((whisper[
"receiver"].empty()) || (whisper[
"message"].empty())) {
1292 "message=\"Invalid number of arguments\"\n"
1293 "sender=\"server\"\n"
1302 whisper.set_attr_dup(
"sender",
player->
name().c_str());
1319 whisper.copy_into(trunc_whisper);
1331 const std::string command(query[
"type"].
to_string());
1332 std::ostringstream response;
1334 const std::string& query_help_msg =
1335 "Available commands are: adminmsg <msg>, help, games, metrics,"
1336 " motd, requests, roll <sides>, sample, stats, status, version, wml.";
1339 if(command ==
"status") {
1342 command.compare(0, 8,
"adminmsg") == 0 ||
1343 command.compare(0, 6,
"report") == 0 ||
1344 command ==
"games" ||
1345 command ==
"metrics" ||
1346 command ==
"motd" ||
1347 command.compare(0, 7,
"version") == 0 ||
1348 command ==
"requests" ||
1349 command.compare(0, 4,
"roll") == 0 ||
1350 command ==
"sample" ||
1351 command ==
"stats" ||
1357 if(command ==
"signout") {
1361 response <<
"You are no longer recognized as an administrator.";
1366 LOG_SERVER <<
"Admin Command: type: " << command <<
"\tIP: " << iter->client_ip()
1371 }
else if(command ==
"help" || command.empty()) {
1372 response << query_help_msg;
1373 }
else if(command ==
"admin" || command.compare(0, 6,
"admin ") == 0) {
1380 if(command.size() >= 6) {
1381 passwd = command.substr(6);
1385 LOG_SERVER <<
"New Admin recognized: IP: " << iter->client_ip() <<
"\tnick: " <<
player.
name();
1388 response <<
"You are now recognized as an administrator.";
1394 WRN_SERVER <<
"FAILED Admin attempt with password: '" << passwd <<
"'\tIP: " << iter->client_ip()
1396 response <<
"Error: wrong password";
1399 response <<
"Error: unrecognized query: '" << command <<
"'\n" << query_help_msg;
1414 if(nickserv.
child(
"info")) {
1416 std::string res =
user_handler_->user_info((*nickserv.
child(
"info"))[
"name"].to_string());
1420 "There was an error looking up the details of the user '"
1421 + (*nickserv.
child(
"info"))[
"name"].to_string() +
"'. "
1422 +
" The error message was: " +
e.message,
"error"
1450 if(user->info().is_message_flooding()) {
1452 "Warning: you are sending too many messages too fast. Your message has not been relayed.",
"error");
1457 message.set_attr_dup(
"sender", user->name().c_str());
1460 message.copy_into(trunc_message);
1466 LOG_SERVER << user->client_ip() <<
"\t<" << user->name()
1469 LOG_SERVER << user->client_ip() <<
"\t<" << user->name() <<
"> " <<
msg;
1479 update.set_attr_int(
"queue_id", queue.
id);
1480 update.set_attr_dup(
"action",
"update");
1493 "This server is shutting down. You aren't allowed to make new games. Please "
1494 "reconnect to the new server.",
"error");
1500 const std::string game_name = create_game[
"name"].to_string();
1501 const std::string game_password = create_game[
"password"].to_string();
1502 const std::string initial_bans = create_game[
"ignored"].to_string();
1504 int queue_id = create_game[
"queue_id"].to_int();
1507 <<
"\tcreates a new game: \"" << game_name <<
"\".";
1520 DBG_SERVER <<
"initial bans: " << initial_bans;
1521 if(initial_bans !=
"") {
1525 if(game_password.empty() ==
false) {
1526 g.set_password(game_password);
1534 if(p_queue.empty()) {
1538 if(
i != p_queue.end()) {
1539 p_queue.erase(
i, p_queue.end());
1554 assert(gamelist !=
nullptr);
1567 const std::size_t
index = std::distance(
games.begin(),
g);
1571 LOG_SERVER <<
"Could not find game (" << game_ptr->
id() <<
", " << game_ptr->
db_id() <<
") to delete in games_and_users_list_.";
1581 int game_id =
join[
"id"].to_int();
1584 const std::string& password =
join[
"password"].to_string();
1588 std::shared_ptr<game>
g;
1590 g = g_iter->get_game();
1596 <<
"\tattempted to join unknown game:\t" << game_id <<
".";
1601 }
else if(!
g->level_init()) {
1603 <<
"\tattempted to join uninitialized game:\t\"" <<
g->name() <<
"\" (" << game_id <<
").";
1612 <<
"\tReject banned player: " <<
player->info().
name()
1613 <<
"\tfrom game:\t\"" <<
g->name() <<
"\" (" << game_id <<
").";
1618 }
else if(!
g->password_matches(password)) {
1620 <<
"\tattempted to join game:\t\"" <<
g->name() <<
"\" (" << game_id <<
") with bad password";
1630 <<
"\tattempted to observe game:\t\"" <<
g->name() <<
"\" (" << game_id
1631 <<
") which doesn't allow observers.";
1635 "Attempt to observe a game that doesn't allow observers. (You probably joined the "
1636 "game shortly after it filled up.)",
"error");
1645 g->describe_slots();
1652 if(diff1 || diff2) {
1660 if(p_queue.empty()) {
1664 if(
i != p_queue.end()) {
1665 p_queue.erase(
i, p_queue.end());
1676 type_node.
set_attr_dup(
"id", option_type[
"id"].str().c_str());
1680 option.
set_attr_dup(
"id", qoption[
"id"].str().c_str());
1681 option.
set_attr_dup(
"value", qoption[
"value"].str().c_str());
1688 int queue_id =
data.attr(
"queue_id").to_int();
1691 ERR_SERVER <<
"player " <<
p->info().name() <<
" attempted to join non-existing server-side queue " <<
data.attr(
"queue_id");
1697 DBG_SERVER <<
"player " <<
p->info().name() <<
" already in server-side queue " <<
data.attr(
"queue_id");
1703 p->info().add_queue(queue.
id);
1721 game.set_attr_dup(
"scenario", scenarios[
index].c_str());
1722 game.set_attr_dup(
"era", queue.
settings[
"era"].str().c_str());
1723 game.set_attr_dup(
"fog", queue.
settings[
"fog"].str().c_str());
1724 game.set_attr_dup(
"shroud", queue.
settings[
"shroud"].str().c_str());
1725 game.set_attr_int(
"village_gold", queue.
settings[
"village_gold"].to_int());
1726 game.set_attr_int(
"village_support", queue.
settings[
"village_support"].to_int());
1727 game.set_attr_int(
"experience_modifier", queue.
settings[
"experience_modifier"].to_int());
1728 game.set_attr_dup(
"random_start_time", queue.
settings[
"random_start_time"].str().c_str());
1729 game.set_attr_dup(
"shuffle_sides", queue.
settings[
"shuffle_sides"].str().c_str());
1731 game.set_attr_dup(
"countdown", queue.
settings[
"countdown"].str().c_str());
1732 game.set_attr_int(
"countdown_init_time", queue.
settings[
"countdown_init_time"].to_int());
1733 game.set_attr_int(
"countdown_turn_bonus", queue.
settings[
"countdown_turn_bonus"].to_int());
1734 game.set_attr_int(
"countdown_reservoir_time", queue.
settings[
"countdown_reservoir_time"].to_int());
1735 game.set_attr_int(
"countdown_action_bonus", queue.
settings[
"countdown_action_bonus"].to_int());
1737 game.set_attr_dup(
"modifications", queue.
settings[
"modifications"].str().c_str());
1750 if(
game->is_open_queue_game(queue.
id)) {
1764 int queue_id =
data.attr(
"queue_id").to_int();
1767 ERR_SERVER <<
"player " <<
p->info().name() <<
" attempted to leave non-existing server-side queue " <<
data.attr(
"queue_id");
1776 p->info().remove_from_queue(queue_id);
1780 ERR_SERVER <<
"player " <<
p->info().name() <<
" already not in server-side queue " <<
data.attr(
"queue_id");
1790 game&
g = *(
p->get_game());
1791 std::weak_ptr<game> g_ptr{
p->get_game()};
1794 if(
data.child(
"snapshot") ||
data.child(
"scenario")) {
1795 if(!
g.is_owner(
p)) {
1805 if(!
g.level_init()) {
1807 <<
g.id() <<
", " <<
g.db_id() <<
").";
1811 assert(gamelist !=
nullptr);
1814 g.level().root().copy_into(desc);
1820 <<
g.name() <<
"\" (" <<
g.id() <<
", " <<
g.db_id() <<
") without a 'multiplayer' child.";
1822 g.set_description(&desc);
1826 "The scenario data is missing the [multiplayer] tag which contains the "
1827 "game settings. Game aborted.",
"error");
1831 g.set_description(&desc);
1835 <<
g.name() <<
"\" (" <<
g.id() <<
", " <<
g.db_id() <<
") although it's already initialized.";
1849 if(!
data[
"mp_shroud"].to_bool()) {
1854 if(!
e->attr(
"require_era").to_bool(
true)) {
1855 desc.
set_attr(
"require_era",
"no");
1859 if(
s[
"require_scenario"].to_bool(
false)) {
1860 desc.
set_attr(
"require_scenario",
"yes");
1869 desc.
child(
"modification")->
set_attr_dup(
"require_modification", m->attr(
"require_modification"));
1873 g.level().swap(
data);
1878 g.update_side_data();
1889 if(
g.q_type() == queue_type::type::server_preset) {
1890 int queue_id =
g.queue_id();
1891 int game_id =
g.id();
1898 std::size_t joined_count = 1;
1900 for(
const std::string& name :
info.players_in_queue) {
1907 if(!player_ptr->get_game()) {
1915 if(joined_count ==
info.players_required) {
1929 }
else if(!
g.level_init()) {
1931 <<
" while the scenario wasn't yet initialized."
1936 if(!
g.is_owner(
p)) {
1940 if(!
g.level_init()) {
1942 <<
"\tsent [store_next_scenario] in game:\t\"" <<
g.name() <<
"\" (" <<
g.id()
1943 <<
", " <<
g.db_id() <<
") while the scenario is not yet initialized.";
1953 g.reset_last_synced_context_id();
1957 scenario->copy_into(
g.level().root());
1960 if(
g.description() ==
nullptr) {
1961 ERR_SERVER <<
p->client_ip() <<
"\tERROR: \"" <<
g.name() <<
"\" (" <<
g.id()
1962 <<
", " <<
g.db_id() <<
") is initialized but has no description_.";
1973 <<
g.name() <<
"\" (" <<
g.id() <<
", " <<
g.db_id() <<
") without a 'multiplayer' child.";
1978 "The scenario data is missing the [multiplayer] tag which contains the game "
1979 "settings. Game aborted.",
"error");
1986 desc.
set_attr_dup(
"map_data",
s[
"mp_shroud"].to_bool() ?
"" :
s[
"map_data"]);
1989 if(!
e->attr(
"require_era").to_bool(
true)) {
1990 desc.
set_attr(
"require_era",
"no");
1994 if(
s[
"require_scenario"].to_bool(
false)) {
1995 desc.
set_attr(
"require_scenario",
"yes");
2001 g.send_data(notify_next_scenario,
p);
2007 }
else if(
data.child(
"load_next_scenario")) {
2008 g.load_next_scenario(
p);
2010 }
else if(
data.child(
"start_game")) {
2011 if(!
g.is_owner(
p)) {
2016 g.perform_controller_tweaks();
2028 std::set<std::string> primary_keys;
2029 for(
const auto& addon : m.
children(
"addon")) {
2030 for(
const auto& content : addon->children(
"content")) {
2031 std::string key =
uuid_+
"-"+
std::to_string(
g.db_id())+
"-"+content->attr(
"type").to_string()+
"-"+content->attr(
"id").to_string()+
"-"+addon->attr(
"id").to_string();
2032 if(primary_keys.count(key) == 0) {
2033 primary_keys.emplace(key);
2034 unsigned long long rows_inserted =
user_handler_->db_insert_game_content_info(
uuid_,
g.db_id(), content->attr(
"type").to_string(), content->attr(
"name").to_string(), content->attr(
"id").to_string(), addon->attr(
"id").to_string(), addon->attr(
"version").to_string());
2035 if(rows_inserted == 0) {
2036 WRN_SERVER <<
"Did not insert content row for [addon] data with uuid '" <<
uuid_ <<
"', game ID '" <<
g.db_id() <<
"', type '" << content->attr(
"type").to_string() <<
"', and content ID '" << content->attr(
"id").to_string() <<
"'";
2041 if(m.
children(
"addon").size() == 0) {
2042 WRN_SERVER <<
"Game content info missing for game with uuid '" <<
uuid_ <<
"', game ID '" <<
g.db_id() <<
"', named '" <<
g.name() <<
"'";
2045 user_handler_->db_insert_game_info(
uuid_,
g.db_id(),
server_id_,
g.name(),
g.is_reload(), m[
"observer"].to_bool(), !m[
"private_replay"].to_bool(),
g.has_password());
2048 for(
unsigned side_index = 0; side_index < sides.size(); ++side_index) {
2051 std::string version;
2070 std::vector<std::string> leaders;
2072 if(side.
attr(
"type") !=
"") {
2077 if(
unit->attr(
"canrecruit") ==
"yes") {
2078 leaders.emplace_back(
unit->attr(
"type").to_string());
2082 for(
const auto leader : side.
children(
"leader")) {
2083 leaders.emplace_back(leader->attr(
"type").to_string());
2086 user_handler_->db_insert_game_player_info(
uuid_,
g.db_id(), side[
"player_id"].to_string(), side[
"side"].to_int(), side[
"is_host"].to_bool(), side[
"faction"].to_string(), version, source, side[
"current_player"].to_string(),
utils::join(leaders));
2093 }
else if(
data.child(
"leave_game")) {
2094 if(
g.remove_player(
p)) {
2097 bool has_diff =
false;
2104 if(
auto gStrong = g_ptr.lock()) {
2105 gStrong->describe_slots();
2126 update.set_attr_dup(
"action",
"update");
2135 if(!
g.is_owner(
p)) {
2139 g.level().root().apply_diff(*scenario_diff);
2143 g.update_side_data();
2152 }
else if(
data.child(
"change_faction")) {
2157 g.transfer_side_control(
p, *change);
2163 }
else if(
data.child(
"muteall")) {
2164 if(!
g.is_owner(
p)) {
2165 g.send_server_message(
"You cannot mute: not the game host.",
p);
2169 g.mute_all_observers();
2173 g.mute_observer(*mute,
p);
2177 g.unmute_observer(*unmute,
p);
2180 }
else if(
data.child(
"kick") ||
data.child(
"ban")) {
2181 bool ban = (
data.child(
"ban") !=
nullptr);
2183 ?
g.ban_user(*
data.child(
"ban"),
p)
2184 :
g.kick_member(*
data.child(
"kick"),
p)};
2205 g.unban_user(*unban,
p);
2209 if(!
g.is_player(
p)) {
2213 if((*
info)[
"type"] ==
"termination") {
2216 g.send_and_record_server_message(
player.
name() +
" reports out of sync errors.");
2224 }
else if(
data.child(
"turn")) {
2232 }
else if(
data.child(
"whiteboard")) {
2233 g.process_whiteboard(
data,
p);
2235 }
else if(
data.child(
"change_turns_wml")) {
2236 g.process_change_turns_wml(
data,
p);
2240 g.handle_choice(*sch,
p);
2242 }
else if(
data.child(
"message")) {
2243 g.process_message(
data,
p);
2245 }
else if(
data.child(
"stop_updates")) {
2250 data.child(
"error") ||
2251 data.child(
"side_secured") ||
2252 data.root().has_attr(
"failed") ||
2253 data.root().has_attr(
"side")
2259 <<
" in game: \"" <<
g.name() <<
"\" (" <<
g.id() <<
", " <<
g.db_id() <<
")\n"
2267 msg.set_attr(
"sender",
"server");
2268 msg.set_attr_esc(
"message", message);
2269 msg.set_attr_dup(
"type",
type.c_str());
2276 utils::visit([](
auto&& socket) {
2278 socket->async_shutdown([socket](...) {});
2279 const char buffer[] =
"";
2280 async_write(*socket, boost::asio::buffer(buffer), [socket](...) { socket->lowest_layer().close(); });
2282 socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
2289 std::string ip = iter->client_ip();
2291 const std::shared_ptr<game>
g = iter->get_game();
2292 bool game_ended =
false;
2294 game_ended =
g->remove_player(iter,
true,
false);
2298 const std::size_t
index =
2299 std::distance(users.begin(),
std::find(users.begin(), users.end(), iter->info().config_address()));
2309 LOG_SERVER << ip <<
"\t" << iter->info().name() <<
"\thas logged off";
2313 user_handler_->db_update_logout(iter->info().get_login_id());
2319 i->log_off = std::chrono::system_clock::now();
2324 if(!queue.players_in_queue.empty()) {
2325 std::vector<std::string>& p_queue = queue.players_in_queue;
2326 p_queue.erase(
std::remove(p_queue.begin(), p_queue.end(), iter->info().name()), p_queue.end());
2388 if(issuer_name ==
"*socket*" && !query.empty() && query.at(0) ==
'+') {
2392 auto issuer_end =
std::find(query.begin(), query.end(),
':');
2394 std::string issuer(query.begin() + 1, issuer_end);
2395 if(!issuer.empty()) {
2396 issuer_name =
"+" + issuer +
"+";
2397 query = std::string(issuer_end + 1, query.end());
2402 const auto i =
std::find(query.begin(), query.end(),
' ');
2405 const std::string command =
utf8::lowercase(std::string(query.begin(),
i));
2407 std::string parameters = (
i == query.end() ?
"" : std::string(
i + 1, query.end()));
2410 std::ostringstream out;
2414 out <<
"Command '" << command <<
"' is not recognized.\n" <<
help_msg;
2416 const cmd_handler& handler = handler_itor->second;
2418 handler(issuer_name, query, parameters, &out);
2419 }
catch(
const std::bad_function_call& ex) {
2420 ERR_SERVER <<
"While handling a command '" << command
2421 <<
"', caught a std::bad_function_call exception.";
2423 out <<
"An internal server error occurred (std::bad_function_call) while executing '" << command
2431 std::string
msg =
"While handling a command, caught an invalid utf8 exception: ";
2434 return (
msg +
'\n');
2440 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
2442 assert(out !=
nullptr);
2449 if(parameters ==
"now") {
2461 "msg The server is shutting down. You may finish your games but can't start new ones. Once all "
2462 "games have ended the server will exit.",
2466 *out <<
"Server is doing graceful shut down.";
2471 const std::string& ,
2473 std::ostringstream* out)
2475 assert(out !=
nullptr);
2483 *out <<
"No restart_command configured! Not restarting.";
2494 "msg The server has been restarted. You may finish current games but can't start new ones and "
2495 "new players can't join this (old) server instance. (So if a player of your game disconnects "
2496 "you have to save, reconnect and reload the game on the new server instance. It is actually "
2497 "recommended to do that right away.)",
2501 *out <<
"New server started.";
2506 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
2508 assert(out !=
nullptr);
2510 if(parameters.empty()) {
2513 }
else if(issuer_name !=
"*socket*") {
2520 *out <<
"Sampling turned off.";
2527 const std::string& ,
2529 std::ostringstream* out)
2531 assert(out !=
nullptr);
2536 const std::string& ,
2538 std::ostringstream* out)
2540 assert(out !=
nullptr);
2546 const std::string& ,
2548 std::ostringstream* out)
2550 assert(out !=
nullptr);
2555 const std::string& ,
2557 std::ostringstream* out)
2559 assert(out !=
nullptr);
2564 const std::string& ,
2565 std::string& parameters,
2566 std::ostringstream* out)
2568 assert(out !=
nullptr);
2569 if(parameters.empty()) {
2576 }
catch(
const std::invalid_argument&) {
2577 *out <<
"The number of die sides must be a number!";
2579 }
catch(
const std::out_of_range&) {
2580 *out <<
"The number of sides is too big for the die!";
2585 *out <<
"The die cannot have less than 1 side!";
2588 std::uniform_int_distribution<int> dice_distro(1, N);
2591 *out <<
"You rolled a die [1 - " + parameters +
"] and got a " +
value +
".";
2598 auto g_ptr = player_ptr->get_game();
2600 g_ptr->send_server_message_to_all(issuer_name +
" rolled a die [1 - " + parameters +
"] and got a " +
value +
".",
player_connections_.project<0>(player_ptr));
2602 *out <<
" (The result is shown to others only in a game.)";
2607 const std::string& ,
2609 std::ostringstream* out)
2611 assert(out !=
nullptr);
2616 const std::string& ,
2618 std::ostringstream* out)
2620 assert(out !=
nullptr);
2625 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
2627 assert(out !=
nullptr);
2629 if(parameters.empty()) {
2630 *out <<
"You must type a message.";
2634 const std::string& sender = issuer_name;
2635 const std::string& message = parameters;
2637 << (message.find(
"/me ") == 0 ? std::string(message.begin() + 3, message.end()) +
">" :
"> " + message);
2641 msg.set_attr_dup(
"sender", (
"admin message from " + sender).c_str());
2642 msg.set_attr_dup(
"message", message.c_str());
2652 bool is_admin =
false;
2662 *out <<
"Your report has been logged and sent to the server administrators. Thanks!";
2666 *out <<
"Your report has been logged and sent to " <<
n <<
" online administrators. Thanks!";
2670 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
2672 assert(out !=
nullptr);
2674 auto first_space =
std::find(parameters.begin(), parameters.end(),
' ');
2675 if(first_space == parameters.end()) {
2676 *out <<
"You must name a receiver.";
2680 const std::string& sender = issuer_name;
2681 const std::string receiver(parameters.begin(), first_space);
2683 std::string message(first_space + 1, parameters.end());
2686 if(message.empty()) {
2687 *out <<
"You must type a message.";
2695 msg.set_attr_dup(
"sender", (
"server message from " + sender).c_str());
2696 msg.set_attr_dup(
"message", message.c_str());
2699 if(receiver !=
player.info().
name().c_str()) {
2704 *out <<
"Message to " << receiver <<
" successfully sent.";
2708 *out <<
"No such nick: " << receiver;
2712 const std::string& ,
2713 std::string& parameters,
2714 std::ostringstream* out)
2716 assert(out !=
nullptr);
2718 if(parameters.empty()) {
2719 *out <<
"You must type a message.";
2726 << (parameters.find(
"/me ") == 0
2727 ? std::string(parameters.begin() + 3, parameters.end()) +
">"
2728 :
"> " + parameters);
2730 *out <<
"message '" << parameters <<
"' relayed to players";
2734 const std::string& ,
2735 std::string& parameters,
2736 std::ostringstream* out)
2738 assert(out !=
nullptr);
2740 if(parameters.empty()) {
2741 *out <<
"You must type a message.";
2747 << (parameters.find(
"/me ") == 0
2748 ? std::string(parameters.begin() + 3, parameters.end()) +
">"
2749 :
"> " + parameters);
2751 *out <<
"message '" << parameters <<
"' relayed to players";
2755 const std::string& ,
const std::string& , std::string& parameters, std::ostringstream* out)
2757 assert(out !=
nullptr);
2759 if(parameters.empty()) {
2766 *out <<
"Player " << parameters <<
" is using wesnoth " <<
player.info().
version();
2771 *out <<
"Player '" << parameters <<
"' not found.";
2775 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
2777 assert(out !=
nullptr);
2779 *out <<
"STATUS REPORT for '" << parameters <<
"'";
2780 bool found_something =
false;
2786 parameters =
player.client_ip();
2787 found_something =
true;
2792 if(!found_something) {
2800 const bool match_ip = ((std::count(parameters.begin(), parameters.end(),
'.') >= 1) || (std::count(parameters.begin(), parameters.end(),
':') >= 1));
2802 if(parameters.empty() || parameters ==
"*" ||
2806 found_something =
true;
2811 if(!found_something) {
2812 *out <<
"\nNo match found. You may want to check with 'searchlog'.";
2817 const std::string& ,
2819 std::ostringstream* out)
2821 assert(out !=
nullptr);
2822 *out <<
"CLONES STATUS REPORT";
2824 std::set<std::string> clones;
2827 if(clones.find(it->client_ip()) != clones.end()) {
2833 if(it->client_ip() == clone->client_ip()) {
2836 clones.insert(it->client_ip());
2845 if(clones.empty()) {
2846 *out << std::endl <<
"No clones found.";
2851 const std::string& ,
2852 std::string& parameters,
2853 std::ostringstream* out)
2855 assert(out !=
nullptr);
2858 if(parameters.empty()) {
2863 std::string mask = parameters.substr(7);
2871 ERR_SERVER <<
"While handling bans, caught an invalid utf8 exception: " <<
e.what();
2876 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
2878 assert(out !=
nullptr);
2881 auto first_space =
std::find(parameters.begin(), parameters.end(),
' ');
2883 if(first_space == parameters.end()) {
2888 auto second_space =
std::find(first_space + 1, parameters.end(),
' ');
2889 const std::string target(parameters.begin(), first_space);
2890 const std::string duration(first_space + 1, second_space);
2898 if(second_space == parameters.end()) {
2902 std::string reason(second_space + 1, parameters.end());
2905 if(reason.empty()) {
2906 *out <<
"You need to give a reason for the ban.";
2910 std::string dummy_group;
2914 if(std::count(target.begin(), target.end(),
'.') >= 1) {
2917 *out <<
ban_manager_.
ban(target, parsed_time, reason, issuer_name, dummy_group);
2927 const std::string ip =
player.client_ip();
2928 *out <<
ban_manager_.
ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2933 *out <<
"Nickname mask '" << target <<
"' did not match, no bans set.";
2939 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
2941 assert(out !=
nullptr);
2944 auto first_space =
std::find(parameters.begin(), parameters.end(),
' ');
2945 if(first_space == parameters.end()) {
2950 auto second_space =
std::find(first_space + 1, parameters.end(),
' ');
2951 const std::string target(parameters.begin(), first_space);
2952 const std::string duration(first_space + 1, second_space);
2960 if(second_space == parameters.end()) {
2964 std::string reason(second_space + 1, parameters.end());
2967 if(reason.empty()) {
2968 *out <<
"You need to give a reason for the ban.";
2972 std::string dummy_group;
2973 std::vector<player_iterator> users_to_kick;
2977 if(std::count(target.begin(), target.end(),
'.') >= 1) {
2980 *out <<
ban_manager_.
ban(target, parsed_time, reason, issuer_name, dummy_group);
2984 users_to_kick.push_back(
player);
2996 const std::string ip =
player->client_ip();
2997 *out <<
ban_manager_.
ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2998 users_to_kick.push_back(
player);
3003 *out <<
"Nickname mask '" << target <<
"' did not match, no bans set.";
3007 for(
auto user : users_to_kick) {
3008 *out <<
"\nKicked " << user->info().name() <<
" (" << user->client_ip() <<
").";
3009 utils::visit([
this,reason](
auto&& socket) {
async_send_error(socket,
"You have been banned. Reason: " + reason); }, user->socket());
3015 const std::string& issuer_name,
const std::string& , std::string& parameters, std::ostringstream* out)
3017 assert(out !=
nullptr);
3020 auto first_space =
std::find(parameters.begin(), parameters.end(),
' ');
3021 if(first_space == parameters.end()) {
3026 auto second_space =
std::find(first_space + 1, parameters.end(),
' ');
3027 const std::string target(parameters.begin(), first_space);
3029 std::string group = std::string(first_space + 1, second_space);
3030 first_space = second_space;
3031 second_space =
std::find(first_space + 1, parameters.end(),
' ');
3033 const std::string duration(first_space + 1, second_space);
3041 if(second_space == parameters.end()) {
3045 std::string reason(second_space + 1, parameters.end());
3048 if(reason.empty()) {
3049 *out <<
"You need to give a reason for the ban.";
3055 if(std::count(target.begin(), target.end(),
'.') >= 1) {
3058 *out <<
ban_manager_.
ban(target, parsed_time, reason, issuer_name, group);
3068 const std::string ip =
player.client_ip();
3069 *out <<
ban_manager_.
ban(ip, parsed_time, reason, issuer_name, group, target);
3074 *out <<
"Nickname mask '" << target <<
"' did not match, no bans set.";
3080 const std::string& ,
3081 std::string& parameters,
3082 std::ostringstream* out)
3084 assert(out !=
nullptr);
3086 if(parameters.empty()) {
3087 *out <<
"You must enter an ipmask to unban.";
3095 const std::string& ,
3096 std::string& parameters,
3097 std::ostringstream* out)
3099 assert(out !=
nullptr);
3101 if(parameters.empty()) {
3102 *out <<
"You must enter an ipmask to ungban.";
3110 const std::string& ,
3111 std::string& parameters,
3112 std::ostringstream* out)
3114 assert(out !=
nullptr);
3116 if(parameters.empty()) {
3117 *out <<
"You must enter a mask to kick.";
3121 auto i =
std::find(parameters.begin(), parameters.end(),
' ');
3122 const std::string kick_mask = std::string(parameters.begin(),
i);
3123 const std::string kick_message = (
i == parameters.end()
3124 ?
"You have been kicked."
3125 :
"You have been kicked. Reason: " + std::string(
i + 1, parameters.end()));
3127 bool kicked =
false;
3130 const bool match_ip = (std::count(kick_mask.begin(), kick_mask.end(),
'.') >= 1);
3132 std::vector<player_iterator> users_to_kick;
3137 users_to_kick.push_back(
player);
3141 for(
const auto&
player : users_to_kick) {
3148 *out <<
"Kicked " <<
player->
name() <<
" (" <<
player->client_ip() <<
"). '"
3149 << kick_message <<
"'";
3151 utils::visit([
this, &kick_message](
auto&& socket) {
async_send_error(socket, kick_message); },
player->socket());
3156 *out <<
"No user matched '" << kick_mask <<
"'.";
3161 const std::string& ,
3162 std::string& parameters,
3163 std::ostringstream* out)
3165 assert(out !=
nullptr);
3167 if(parameters.empty()) {
3168 if(!
motd_.empty()) {
3169 *out <<
"Message of the day:\n" <<
motd_;
3172 *out <<
"No message of the day set.";
3178 *out <<
"Message of the day set to: " <<
motd_;
3182 const std::string& ,
3183 std::string& parameters,
3184 std::ostringstream* out)
3186 assert(out !=
nullptr);
3188 if(parameters.empty()) {
3189 *out <<
"You must enter a mask to search for.";
3193 *out <<
"IP/NICK LOG for '" << parameters <<
"'";
3197 const bool match_ipv4 = (std::count(parameters.begin(), parameters.end(),
'.') >= 1);
3198 const bool match_ipv6 = (std::count(parameters.begin(), parameters.end(),
':') >= 1);
3199 const bool match_ip = match_ipv4 || match_ipv6;
3202 bool found_something =
false;
3205 const std::string& username =
i.nick;
3206 const std::string& ip =
i.ip;
3211 found_something =
true;
3217 *out <<
"\n'" << username <<
"' @ " << ip
3223 if(!found_something) {
3224 *out <<
"\nNo match found.";
3237 const std::string& ,
3238 std::string& parameters,
3239 std::ostringstream* out)
3241 assert(out !=
nullptr);
3244 if(parameters.empty()) {
3252 ERR_SERVER <<
"While handling dul (deny unregistered logins), caught an invalid utf8 exception: " <<
e.what();
3257 const std::string& ,
3258 std::string& parameters,
3259 std::ostringstream* out)
3261 assert(out !=
nullptr);
3263 const std::string nick = parameters.substr(0, parameters.find(
' '));
3264 const std::string reason = parameters.length() > nick.length()+1 ? parameters.substr(nick.length()+1) :
"";
3268 std::shared_ptr<game>
g =
player->get_game();
3270 *out <<
"Player '" << nick <<
"' is in game with id '" <<
g->id() <<
", " <<
g->db_id() <<
"' named '" <<
g->name() <<
"'. Ending game for reason: '" << reason <<
"'...";
3273 *out <<
"Player '" << nick <<
"' is not currently in a game.";
3276 *out <<
"Player '" << nick <<
"' is not currently logged in.";
3281 const std::string& ,
3283 std::ostringstream* out)
3285 assert(out !=
nullptr);
3292 queue.players_in_queue.clear();
3296 *out <<
"Reset all queues";
3306 std::vector<decltype(range_pair)::first_type> range_vctor;
3308 for(
auto it = range_pair.first; it != range_pair.second; ++it) {
3309 range_vctor.push_back(it);
3310 it->info().mark_available();
3316 ERR_SERVER <<
"ERROR: delete_game(): Could not find user in players_.";
3323 for(
const auto& it : range_vctor) {
3330 for(
const auto& it : range_vctor) {
3334 leave_game_doc_reason.
child(
"leave_game")->
set_attr_dup(
"reason", reason.c_str());
3346 if(
auto p_desc =
g.changed_description()) {
3358 bool keep_alive =
false;
3360 srand(
static_cast<unsigned>(std::time(
nullptr)));
3362 std::string config_file;
3371 for(
int arg = 1; arg != argc; ++arg) {
3372 const std::string val(argv[arg]);
3377 if((val ==
"--config" || val ==
"-c") && arg + 1 != argc) {
3378 config_file = argv[++arg];
3379 }
else if(val ==
"--verbose" || val ==
"-v") {
3381 }
else if(val ==
"--dump-wml" || val ==
"-w") {
3383 }
else if(val.substr(0, 6) ==
"--log-") {
3384 std::size_t
p = val.find(
'=');
3385 if(
p == std::string::npos) {
3390 std::string
s = val.substr(6,
p - 6);
3395 }
else if(
s ==
"warning") {
3397 }
else if(
s ==
"info") {
3399 }
else if(
s ==
"debug") {
3406 while(
p != std::string::npos) {
3407 std::size_t q = val.find(
',',
p + 1);
3408 s = val.substr(
p + 1, q == std::string::npos ? q : q - (
p + 1));
3417 }
else if((val ==
"--port" || val ==
"-p") && arg + 1 != argc) {
3418 port = utils::from_chars<int>(argv[++arg]).value_or(0);
3419 }
else if(val ==
"--keepalive") {
3421 }
else if(val ==
"--help" || val ==
"-h") {
3422 std::cout <<
"usage: " << argv[0]
3423 <<
" [-dvwV] [-c path] [-p port]\n"
3424 <<
" -c, --config <path> Tells wesnothd where to find the config file to use.\n"
3425 <<
" -d, --daemon Runs wesnothd as a daemon.\n"
3426 <<
" -h, --help Shows this usage message.\n"
3427 <<
" --log-<level>=<domain1>,<domain2>,...\n"
3428 <<
" sets the severity level of the debug domains.\n"
3429 <<
" 'all' can be used to match any debug domain.\n"
3430 <<
" Available levels: error, warning, info, debug.\n"
3431 <<
" -p, --port <port> Binds the server to the specified port.\n"
3432 <<
" --keepalive Enable TCP keepalive.\n"
3433 <<
" -v --verbose Turns on more verbose logging.\n"
3434 <<
" -V, --version Returns the server version.\n"
3435 <<
" -w, --dump-wml Print all WML sent to clients to stdout.\n";
3437 }
else if(val ==
"--version" || val ==
"-V") {
3440 }
else if(val ==
"--daemon" || val ==
"-d") {
3442 ERR_SERVER <<
"Running as a daemon is not supported on this platform";
3445 const pid_t pid = fork();
3447 ERR_SERVER <<
"Could not fork and run as a daemon";
3449 }
else if(pid > 0) {
3450 std::cout <<
"Started wesnothd as a daemon with process id " << pid <<
"\n";
3456 }
else if(val ==
"--request_sample_frequency" && arg + 1 != argc) {
int main(int argc, char **argv)
A config object defines a single node in a WML file, with access to child nodes.
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
child_itors child_range(std::string_view key)
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
A class to handle the non-SQL logic for connecting to the phpbb forum database.
severity get_severity() const
std::ostream & requests(std::ostream &out) const
std::ostream & games(std::ostream &out) const
void game_terminated(const std::string &reason)
uint32_t get_next_random()
Get a new random number.
Base class for implementing servers that use gzipped-WML network protocol.
void async_send_warning(const SocketPtr &socket, const std::string &msg, const char *warning_code="", const info_table &info={})
std::string hash_password(const std::string &pw, const std::string &salt, const std::string &username)
Handles hashing the password provided by the player before comparing it to the hashed password in the...
boost::asio::signal_set sighup_
boost::asio::streambuf admin_cmd_
void async_send_error(SocketPtr socket, const std::string &msg, const char *error_code="", const info_table &info={})
boost::asio::ip::tcp::acceptor acceptor_v4_
std::unique_ptr< simple_wml::document > coro_receive_doc(const SocketPtr &socket, const boost::asio::yield_context &yield)
Receive WML document from a coroutine.
boost::asio::io_context io_service_
void async_send_doc_queued(const SocketPtr &socket, simple_wml::document &doc)
High level wrapper for sending a WML document.
boost::asio::posix::stream_descriptor input_
void load_tls_config(const config &cfg)
void coro_send_doc(const SocketPtr &socket, simple_wml::document &doc, const boost::asio::yield_context &yield)
Send a WML document from within a coroutine.
boost::asio::ip::tcp::acceptor acceptor_v6_
node & set_attr_dup(const char *key, const char *value)
static std::string stats()
node * child(const char *name)
const string_span & attr(const char *key) const
void remove_child(const char *name, std::size_t index)
const child_list & children(const char *name) const
node & set_attr_int(const char *key, int value)
node * child(const char *name)
std::vector< node * > child_list
node & add_child(const char *name)
node & set_attr(const char *key, const char *value)
node & add_child_at(const char *name, std::size_t index)
void copy_into(node &n) const
node & set_attr_dup(const char *key, const char *value)
std::string to_string() const
const char * begin() const
This class represents a single unit of a specific type.
An interface class to handle nick registration To activate it put a [user_handler] section into the s...
@ BAN_EMAIL
Account email address ban.
@ BAN_USER
User account/name ban.
Thrown by operations encountering invalid UTF-8 data.
Represents version numbers.
std::string str() const
Serializes the version number into string form.
std::string ban(const std::string &ip, const utils::optional< std::chrono::system_clock::time_point > &end_time, const std::string &reason, const std::string &who_banned, const std::string &group, const std::string &nick="")
void list_bans(std::ostringstream &out, const std::string &mask="*")
void unban(std::ostringstream &os, const std::string &ip, bool immediate_write=true)
std::pair< bool, utils::optional< std::chrono::system_clock::time_point > > parse_time(const std::string &duration, std::chrono::system_clock::time_point start_time) const
Parses the given duration and adds it to *time except if the duration is '0' or 'permanent' in which ...
banned_ptr get_ban_info(const std::string &ip)
void unban_group(std::ostringstream &os, const std::string &group)
const std::string & get_ban_help() const
void list_deleted_bans(std::ostringstream &out, const std::string &mask="*") const
void load_config(const config &)
static simple_wml::node * starting_pos(simple_wml::node &data)
The non-const version.
simple_wml::node * description() const
int db_id() const
This ID is not reused between scenarios of MP campaigns.
int id() const
This ID is reused between scenarios of MP campaigns.
const std::string & termination_reason() const
Provides the reason the game was ended.
std::string get_replay_filename()
void set_game(std::shared_ptr< game > new_game)
const std::shared_ptr< game > get_game() const
const std::string & version() const
const simple_wml::node * config_address() const
void set_moderator(bool moderator)
const std::set< int > & get_queues() const
const std::string & name() const
bool is_moderator() const
const std::string & source() const
utils::optional< server_base::login_ban_info > is_ip_banned(const std::string &ip)
void update_game_in_lobby(game &g, utils::optional< player_iterator > exclude={})
void send_to_lobby(simple_wml::document &data, utils::optional< player_iterator > exclude={})
void stopgame_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::unique_ptr< user_handler > user_handler_
void handle_leave_server_queue(player_iterator p, simple_wml::node &data)
const std::string config_file_
boost::asio::steady_timer dummy_player_timer_
void handle_message(player_iterator player, simple_wml::node &message)
void searchlog_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::vector< std::string > disallowed_names_
void clones_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void ban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void start_dummy_player_updates()
void load_config(bool reload)
Parse the server config into local variables.
std::string input_path_
server socket/fifo.
void unban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::map< std::string, config > proxy_versions_
void handle_sighup(const boost::system::error_code &error, int signal_number)
void stats_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::chrono::seconds dummy_player_timer_interval_
void delete_game(int, const std::string &reason="")
void refresh_tournaments(const boost::system::error_code &ec)
std::string recommended_version_
void gban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void pm_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::function< void(const std::string &, const std::string &, std::string &, std::ostringstream *)> cmd_handler
void adminmsg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void start_tournaments_timer()
std::string replay_save_path_
void handle_query(player_iterator player, simple_wml::node &query)
void handle_player_in_game(player_iterator player, simple_wml::document &doc)
std::string restart_command
void lobbymsg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
bool ip_exceeds_connection_limit(const std::string &ip) const
std::chrono::seconds failed_login_ban_
void handle_graceful_timeout(const boost::system::error_code &error)
void cleanup_game(game *)
void handle_whisper(player_iterator player, simple_wml::node &whisper)
bool is_login_allowed(boost::asio::yield_context yield, SocketPtr socket, const simple_wml::node *const login, const std::string &username, bool ®istered, bool &is_moderator)
void handle_new_client(socket_ptr socket)
void status_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void send_queue_update(const queue_info &queue, utils::optional< player_iterator > exclude={})
std::string announcements_
std::deque< connection_log > ip_log_
void bans_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void kick_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void dul_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void handle_join_server_queue(player_iterator p, simple_wml::node &data)
void handle_join_game(player_iterator player, simple_wml::node &join)
std::deque< login_log >::size_type failed_login_buffer_size_
void wml_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void send_server_message(SocketPtr socket, const std::string &message, const std::string &type)
void handle_player(boost::asio::yield_context yield, SocketPtr socket, player_iterator player)
player_connections player_connections_
simple_wml::document games_and_users_list_
void help_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void requests_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void dump_stats(const boost::system::error_code &ec)
void remove_player(player_iterator player)
boost::asio::steady_timer timer_
void handle_read_from_fifo(const boost::system::error_code &error, std::size_t bytes_transferred)
simple_wml::document login_response_
boost::asio::steady_timer tournaments_timer_
std::set< std::string > client_sources_
bool player_is_in_game(player_iterator player) const
void abort_lan_server_timer()
void handle_lan_server_shutdown(const boost::system::error_code &error)
bool authenticate(SocketPtr socket, const std::string &username, const std::string &password, bool name_taken, bool ®istered)
void reset_queues_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::string admin_passwd_
void motd_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
bool deny_unregistered_login_
std::vector< std::string > accepted_versions_
std::chrono::seconds lan_server_
void dummy_player_updates(const boost::system::error_code &ec)
void handle_create_game(player_iterator player, simple_wml::node &create_game)
void send_server_message_to_lobby(const std::string &message, utils::optional< player_iterator > exclude={})
void kickban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void send_server_message_to_all(const std::string &message, utils::optional< player_iterator > exclude={})
void handle_nickserv(player_iterator player, simple_wml::node &nickserv)
std::string process_command(std::string cmd, std::string issuer_name)
Process commands from admins and users.
std::map< int, queue_info > queue_info_
void login_client(boost::asio::yield_context yield, SocketPtr socket)
void handle_ping(player_iterator player, simple_wml::node &nickserv)
std::deque< login_log > failed_logins_
std::size_t default_max_messages_
std::size_t max_ip_log_size_
void restart_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void msg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
boost::asio::steady_timer lan_server_timer_
void shut_down_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::map< std::string, config > redirected_versions_
void metrics_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::map< std::string, cmd_handler > cmd_handlers_
void send_to_player(player_iterator player, simple_wml::document &data)
wesnothd::ban_manager ban_manager_
config read_config() const
Read the server config from file 'config_file_'.
boost::asio::steady_timer dump_stats_timer_
void handle_player_in_lobby(player_iterator player, simple_wml::document &doc)
std::vector< std::string > tor_ip_list_
void roll_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void games_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::deque< std::shared_ptr< game > > games() const
simple_wml::document version_query_response_
void version_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::size_t concurrent_connections_
void send_password_request(SocketPtr socket, const std::string &msg, const char *error_code="", bool force_confirmation=false)
void sample_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
void start_lan_server_timer()
void disconnect_player(player_iterator player)
bool allow_remote_shutdown_
void ungban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
std::chrono::seconds default_time_period_
Definitions for the interface to Wesnoth Markup Language (WML).
Declarations for File-IO.
Interfaces for manipulating version numbers of engine, add-ons, etc.
Standard logging facilities (interface).
Define the errors the server may send during the login procedure.
#define MP_INCORRECT_PASSWORD_ERROR
#define MP_NAME_AUTH_BAN_USER_ERROR
#define MP_NAME_RESERVED_ERROR
#define MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME
#define MP_NAME_AUTH_BAN_EMAIL_ERROR
#define MP_TOO_MANY_ATTEMPTS_ERROR
#define MP_HASHING_PASSWORD_FAILED
#define MP_SERVER_IP_BAN_ERROR
#define MP_NAME_TOO_LONG_ERROR
#define MP_NAME_AUTH_BAN_IP_ERROR
#define MP_PASSWORD_REQUEST
#define MP_NAME_INACTIVE_WARNING
#define MP_NAME_UNREGISTERED_ERROR
#define MP_INVALID_CHARS_IN_NAME_ERROR
#define MP_NAME_TAKEN_ERROR
std::string client_address(const any_socket_ptr &sock)
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
void set_user_data_dir(std::string newprefdir)
const version_info wesnoth_version(VERSION)
void remove()
Removes a tip.
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
config read(std::istream &in, abstract_validator *validator)
bool set_log_domain_severity(const std::string &name, severity severity)
std::string node_to_string(const node &n)
std::string lowercase(std::string_view s)
Returns a lowercased version of the string.
std::string & insert(std::string &str, const std::size_t pos, const std::string &insert)
Insert a UTF-8 string at the specified position.
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
void trim(std::string_view &s)
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
Performs pattern matching with wildcards.
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
void to_sql_wildcards(std::string &str, bool underscores)
Converts '*' to '' and optionally escapes '_'.
bool isvalid_username(const std::string &username)
Check if the username contains only valid characters.
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
void truncate_message(const simple_wml::string_span &str, simple_wml::node &message)
Function to ensure a text message is within the allowed length.
static void make_add_diff(const simple_wml::node &src, const char *gamelist, const char *type, simple_wml::document &out, int index=-1)
static bool make_change_diff(const simple_wml::node &src, const char *gamelist, const char *type, const simple_wml::node *item, simple_wml::document &out)
int request_sample_frequency
const std::string help_msg
static void setup_queue_options(const char *type, const config &qoptions, simple_wml::node &game)
player_connections::const_iterator player_iterator
static std::string player_status(const wesnothd::player_record &player)
static bool make_delete_diff(const simple_wml::node &src, const char *gamelist, const char *type, const simple_wml::node *remove, simple_wml::document &out)
version_info secure_version
const std::string denied_msg
std::string to_string(const Range &range, const Func &op)
std::string::const_iterator iterator
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map &defines)
Function to use the WML preprocessor on a file.
std::shared_ptr< boost::asio::ssl::stream< socket_ptr::element_type > > tls_socket_ptr
std::string log_address(SocketPtr socket)
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
rect src
Non-transparent portion of the surface to compose.
The base template for associating string values with enum values.
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
std::chrono::seconds duration
Ban duration (0 if permanent)
std::size_t players_required
std::vector< std::string > players_in_queue
static map_location::direction n
static map_location::direction s
#define LOG_SERVER
normal events
#define WRN_SERVER
clients send wrong/unexpected data
#define SETUP_HANDLER(name, function)
#define ERR_SERVER
fatal and directly server related errors/warnings, ie not caused by erroneous client data
static lg::log_domain log_server("server")
static lg::log_domain log_config("config")