The Battle for Wesnoth  1.19.24+dev
server.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Wesnoth-Server, for multiplayer-games.
19  */
20 
22 
23 #include "config.hpp"
24 #include "filesystem.hpp"
25 #include "log.hpp"
27 #include "serialization/chrono.hpp"
28 #include "serialization/parser.hpp"
32 #include "utils/charconv.hpp"
33 #include "utils/iterable_pair.hpp"
34 #include "game_version.hpp"
35 
36 #include "server/wesnothd/ban.hpp"
37 #include "server/wesnothd/game.hpp"
43 
44 #ifdef HAVE_MYSQLPP
46 #endif
47 
48 #include <boost/algorithm/string.hpp>
49 #include <boost/scope_exit.hpp>
50 
51 #include <algorithm>
52 #include <cassert>
53 #include <cerrno>
54 #include <cstdlib>
55 #include <functional>
56 #include <iostream>
57 #include <map>
58 #include <set>
59 #include <sstream>
60 #include <utility>
61 #include <vector>
62 
63 static lg::log_domain log_server("server");
64 /**
65  * fatal and directly server related errors/warnings,
66  * ie not caused by erroneous client data
67  */
68 #define ERR_SERVER LOG_STREAM(err, log_server)
69 
70 /** clients send wrong/unexpected data */
71 #define WRN_SERVER LOG_STREAM(warn, log_server)
72 
73 /** normal events */
74 #define LOG_SERVER LOG_STREAM(info, log_server)
75 #define DBG_SERVER LOG_STREAM(debug, log_server)
76 
77 static lg::log_domain log_config("config");
78 #define ERR_CONFIG LOG_STREAM(err, log_config)
79 #define WRN_CONFIG LOG_STREAM(warn, log_config)
80 
81 using namespace std::chrono_literals;
82 
83 namespace wesnothd
84 {
85 // we take profiling info on every n requests
88 
89 static void make_add_diff(
90  const simple_wml::node& src, const char* gamelist, const char* type, simple_wml::document& out, int index = -1)
91 {
92  if(!out.child("gamelist_diff")) {
93  out.root().add_child("gamelist_diff");
94  }
95 
96  simple_wml::node* top = out.child("gamelist_diff");
97  if(gamelist) {
98  top = &top->add_child("change_child");
99  top->set_attr_int("index", 0);
100  top = &top->add_child("gamelist");
101  }
102 
103  simple_wml::node& insert = top->add_child("insert_child");
104  const simple_wml::node::child_list& children = src.children(type);
105  assert(!children.empty());
106 
107  if(index < 0) {
108  index = children.size() - 1;
109  }
110 
111  assert(index < static_cast<int>(children.size()));
112  insert.set_attr_int("index", index);
113 
114  children[index]->copy_into(insert.add_child(type));
115 }
116 
118  const char* gamelist,
119  const char* type,
120  const simple_wml::node* remove,
122 {
123  if(!out.child("gamelist_diff")) {
124  out.root().add_child("gamelist_diff");
125  }
126 
127  simple_wml::node* top = out.child("gamelist_diff");
128  if(gamelist) {
129  top = &top->add_child("change_child");
130  top->set_attr_int("index", 0);
131  top = &top->add_child("gamelist");
132  }
133 
134  const simple_wml::node::child_list& children = src.children(type);
135  const auto itor = std::find(children.begin(), children.end(), remove);
136 
137  if(itor == children.end()) {
138  return false;
139  }
140 
141  const int index = std::distance(children.begin(), itor);
142 
143  simple_wml::node& del = top->add_child("delete_child");
144  del.set_attr_int("index", index);
145  del.add_child(type);
146 
147  return true;
148 }
149 
151  const char* gamelist,
152  const char* type,
153  const simple_wml::node* item,
155 {
156  if(!out.child("gamelist_diff")) {
157  out.root().add_child("gamelist_diff");
158  }
159 
160  simple_wml::node* top = out.child("gamelist_diff");
161  if(gamelist) {
162  top = &top->add_child("change_child");
163  top->set_attr_int("index", 0);
164  top = &top->add_child("gamelist");
165  }
166 
167  const simple_wml::node::child_list& children = src.children(type);
168  const auto itor = std::find(children.begin(), children.end(), item);
169 
170  if(itor == children.end()) {
171  return false;
172  }
173 
174  simple_wml::node& diff = *top;
175  simple_wml::node& del = diff.add_child("delete_child");
176 
177  const int index = std::distance(children.begin(), itor);
178 
179  del.set_attr_int("index", index);
180  del.add_child(type);
181 
182  // inserts will be processed first by the client, so insert at index+1,
183  // and then when the delete is processed we'll slide into the right position
184  simple_wml::node& insert = diff.add_child("insert_child");
185  insert.set_attr_int("index", index + 1);
186 
187  children[index]->copy_into(insert.add_child(type));
188  return true;
189 }
190 
191 static std::string player_status(const wesnothd::player_record& player)
192 {
194  std::ostringstream out;
195  out << "'" << player.name() << "' @ " << player.client_ip()
196  << " logged on for "
197  << d.count() << " days, "
198  << h.count() << " hours, "
199  << m.count() << " minutes, "
200  << s.count() << " seconds";
201  return out.str();
202 }
203 
204 const std::string denied_msg = "You're not allowed to execute this command.";
205 const std::string help_msg =
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.";
215 
216 server::server(int port,
217  bool keep_alive,
218  const std::string& config_file)
219  : server_base(port, keep_alive)
220  , ban_manager_()
221  , rng_()
222  , ip_log_()
223  , failed_logins_()
224  , user_handler_(nullptr)
225  , die_(static_cast<unsigned>(std::time(nullptr)))
226 #ifndef _WIN32
227  , input_path_()
228 #endif
229  , uuid_("")
230  , config_file_(config_file)
231  , cfg_(read_config())
232  , accepted_versions_()
233  , redirected_versions_()
234  , proxy_versions_()
235  , disallowed_names_()
236  , admin_passwd_()
237  , motd_()
238  , announcements_()
239  , server_id_()
240  , tournaments_()
241  , information_()
242  , default_max_messages_(0)
243  , default_time_period_(0)
244  , concurrent_connections_(0)
245  , graceful_restart(false)
246  , lan_server_(0)
247  , restart_command()
248  , max_ip_log_size_(0)
249  , deny_unregistered_login_(false)
250  , save_replays_(false)
251  , replay_save_path_()
252  , allow_remote_shutdown_(false)
253  , client_sources_()
254  , tor_ip_list_()
255  , failed_login_limit_()
256  , failed_login_ban_()
257  , failed_login_buffer_size_()
258  , version_query_response_("[version]\n[/version]\n", simple_wml::INIT_COMPRESSED)
259  , login_response_("[mustlogin]\n[/mustlogin]\n", simple_wml::INIT_COMPRESSED)
260  , games_and_users_list_("[gamelist]\n[/gamelist]\n", simple_wml::INIT_STATIC)
261  , metrics_()
262  , dump_stats_timer_(io_service_)
263  , tournaments_timer_(io_service_)
264  , cmd_handlers_()
265  , timer_(io_service_)
266  , lan_server_timer_(io_service_)
267  , dummy_player_timer_(io_service_)
268  , dummy_player_timer_interval_(30)
269 {
270  setup_handlers();
271  load_config(false);
272  ban_manager_.read();
273 
274  start_server();
275 
278  if(user_handler_) {
279  uuid_ = user_handler_->get_uuid();
280  if(uuid_.empty()) {
281  ERR_SERVER << "Unable to retrieve UUID from database";
282  exit(1);
283  }
284  LOG_SERVER << "Retrieved database UUID: " << uuid_;
285  }
286 }
287 
288 #ifndef _WIN32
289 void server::handle_sighup(const boost::system::error_code& error, int)
290 {
291  assert(!error);
292 
293  WRN_SERVER << "SIGHUP caught, reloading config";
294 
295  cfg_ = read_config();
296  load_config(true);
297 
298  sighup_.async_wait(std::bind(&server::handle_sighup, this, std::placeholders::_1, std::placeholders::_2));
299 }
300 #endif
301 
302 void server::handle_graceful_timeout(const boost::system::error_code& error)
303 {
304  assert(!error);
305 
306  if(games().empty()) {
307  process_command("msg All games ended. Shutting down now. Reconnect to the new server instance.", "system");
308  BOOST_THROW_EXCEPTION(server_shutdown("graceful shutdown timeout"));
309  } else {
310  timer_.expires_after(1s);
311  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
312  }
313 }
314 
316 {
317  lan_server_timer_.expires_after(lan_server_);
318  lan_server_timer_.async_wait([this](const boost::system::error_code& ec) { handle_lan_server_shutdown(ec); });
319 }
320 
322 {
323  lan_server_timer_.cancel();
324 }
325 
326 void server::handle_lan_server_shutdown(const boost::system::error_code& error)
327 {
328  if(error)
329  return;
330 
331  BOOST_THROW_EXCEPTION(server_shutdown("lan server shutdown"));
332 }
333 
335 {
336 #ifndef _WIN32
337  const int res = mkfifo(input_path_.c_str(), 0660);
338  if(res != 0 && errno != EEXIST) {
339  ERR_SERVER << "could not make fifo at '" << input_path_ << "' (" << strerror(errno) << ")";
340  return;
341  }
342  int fifo = open(input_path_.c_str(), O_RDWR | O_NONBLOCK);
343  if(fifo == -1) {
344  ERR_SERVER << "could not open fifo at '" << input_path_ << "' (" << strerror(errno) << ")";
345  return;
346  }
347  input_.assign(fifo);
348  LOG_SERVER << "opened fifo at '" << input_path_ << "'. Server commands may be written to this file.";
349  read_from_fifo();
350 #endif
351 }
352 
353 #ifndef _WIN32
354 
355 void server::handle_read_from_fifo(const boost::system::error_code& error, std::size_t)
356 {
357  if(error) {
358  std::cout << error.message() << std::endl;
359  return;
360  }
361 
362  std::istream is(&admin_cmd_);
363  std::string cmd;
364  std::getline(is, cmd);
365 
366  LOG_SERVER << "Admin Command: type: " << cmd;
367 
368  const std::string res = process_command(cmd, "*socket*");
369 
370  // Only mark the response if we fake the issuer (i.e. command comes from IRC or so)
371  if(!cmd.empty() && cmd.at(0) == '+') {
372  LOG_SERVER << "[admin_command_response]\n"
373  << res << "\n"
374  << "[/admin_command_response]";
375  } else {
376  LOG_SERVER << res;
377  }
378 
379  read_from_fifo();
380 }
381 
382 #endif
383 
385 {
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);
389 
404  SETUP_HANDLER("privatemsg", &server::pm_handler);
406  SETUP_HANDLER("lobbymsg", &server::msg_handler);
421  SETUP_HANDLER("deny_unregistered_login", &server::dul_handler);
424 
425 #undef SETUP_HANDLER
426 }
427 
429 {
430  if(config_file_.empty()) {
431  return {};
432  }
433 
434  try {
435  // necessary to avoid assert since preprocess_file() goes through filesystem::get_short_wml_path()
436  filesystem::set_user_data_dir(std::string());
438  LOG_SERVER << "Server configuration from file: '" << config_file_ << "' read.";
439  } catch(const config::error& e) {
440  ERR_CONFIG << "ERROR: Could not read configuration file: '" << config_file_ << "': '" << e.message << "'.";
441  return {};
442  }
443 }
444 
445 void server::load_config(bool reload)
446 {
447 #ifndef _WIN32
448 #ifndef FIFODIR
449 #warning No FIFODIR set
450 #define FIFODIR "/var/run/wesnothd"
451 #endif
452  const std::string fifo_path
453  = (cfg_["fifo_path"].empty() ? std::string(FIFODIR) + "/socket" : std::string(cfg_["fifo_path"]));
454  // Reset (replace) the input stream only if the FIFO path changed.
455  if(fifo_path != input_path_) {
456  input_.close();
457  input_path_ = fifo_path;
458  setup_fifo();
459  }
460 #endif
461 
462  save_replays_ = cfg_["save_replays"].to_bool();
463  replay_save_path_ = cfg_["replay_save_path"].str();
464 
465  tor_ip_list_ = utils::split(cfg_["tor_ip_list_path"].empty()
466  ? ""
467  : filesystem::read_file(cfg_["tor_ip_list_path"]), '\n');
468 
469  // mp tests script doesn't have a config at all, so this child won't be here
470  // LAN also presumably wouldn't have these
471  if(!reload && cfg_.has_child("queues")) {
472  queue_info_.clear();
473  for(const config& queue : cfg_.mandatory_child("queues").child_range("queue")) {
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);
476  queue_info_.emplace(q.id, q);
477  }
478  } else if(reload && cfg_.has_child("queues")) {
479  std::map<int, queue_info> new_queue_info;
480  for(const config& queue : cfg_.mandatory_child("queues").child_range("queue")) {
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);
484  }
485 
486  // check for new or updated queues
487  for(auto& [id, info] : new_queue_info) {
488  if(queue_info_.count(id) == 0) {
489  simple_wml::document queue_update;
490  simple_wml::node& update = queue_update.root().add_child("queue_update");
491  update.set_attr_int("queue_id", info.id);
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);
495 
496  send_to_lobby(queue_update);
497  } else if(
498  queue_info_.count(id) == 1 && (
499  info.display_name != queue_info_.at(id).display_name ||
500  info.players_required != queue_info_.at(id).players_required
501  )
502  ) {
503  simple_wml::document queue_update;
504  simple_wml::node& update = queue_update.root().add_child("queue_update");
505  update.set_attr_int("queue_id", info.id);
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);
509 
510  send_to_lobby(queue_update);
511  }
512  }
513 
514  // check for removed queues
515  for(auto& [id, info] : queue_info_) {
516  if(new_queue_info.count(id) == 0) {
517  simple_wml::document queue_update;
518  simple_wml::node& update = queue_update.root().add_child("queue_update");
519  update.set_attr_int("queue_id", info.id);
520  update.set_attr_dup("action", "remove");
521 
522  send_to_lobby(queue_update);
523  }
524  }
525 
526  queue_info_ = new_queue_info;
527  }
528 
529  admin_passwd_ = cfg_["passwd"].str();
530  motd_ = cfg_["motd"].str();
531  information_ = cfg_["information"].str();
532  announcements_ = cfg_["announcements"].str();
533  server_id_ = cfg_["id"].str();
534  lan_server_ = chrono::parse_duration(cfg_["lan_server"], 0s);
535 
536  deny_unregistered_login_ = cfg_["deny_unregistered_login"].to_bool();
537 
538  allow_remote_shutdown_ = cfg_["allow_remote_shutdown"].to_bool();
539 
540  for(const std::string& source : utils::split(cfg_["client_sources"].str())) {
541  client_sources_.insert(source);
542  }
543 
544  disallowed_names_.clear();
545  if(cfg_["disallow_names"].empty()) {
546  disallowed_names_.push_back("*admin*");
547  disallowed_names_.push_back("*admln*");
548  disallowed_names_.push_back("*server*");
549  disallowed_names_.push_back("player");
550  disallowed_names_.push_back("network");
551  disallowed_names_.push_back("human");
552  disallowed_names_.push_back("computer");
553  disallowed_names_.push_back("ai");
554  disallowed_names_.push_back("ai?");
555  disallowed_names_.push_back("*moderator*");
556  } else {
557  disallowed_names_ = utils::split(cfg_["disallow_names"]);
558  }
559 
560  default_max_messages_ = cfg_["max_messages"].to_int(4);
561  default_time_period_ = chrono::parse_duration(cfg_["messages_time_period"], 10s);
562  concurrent_connections_ = cfg_["connections_allowed"].to_int(5);
563  max_ip_log_size_ = cfg_["max_ip_log_size"].to_int(500);
564 
565  failed_login_limit_ = cfg_["failed_logins_limit"].to_int(10);
566  failed_login_ban_ = chrono::parse_duration(cfg_["failed_logins_ban"], 3600s);
567  failed_login_buffer_size_ = cfg_["failed_logins_buffer_size"].to_int(500);
568 
569  // Example config line:
570  // restart_command="./wesnothd-debug -d -c ~/.wesnoth1.5/server.cfg"
571  // remember to make new one as a daemon or it will block old one
572  restart_command = cfg_["restart_command"].str();
573 
574  recommended_version_ = cfg_["recommended_version"].str();
575  accepted_versions_.clear();
576  const std::string& versions = cfg_["versions_accepted"];
577  if(versions.empty() == false) {
578  accepted_versions_ = utils::split(versions);
579  } else {
581  accepted_versions_.push_back("test");
582  }
583 
584  redirected_versions_.clear();
585  for(const config& redirect : cfg_.child_range("redirect")) {
586  for(const std::string& version : utils::split(redirect["version"])) {
587  redirected_versions_[version] = redirect;
588  }
589  }
590 
591  proxy_versions_.clear();
592  for(const config& proxy : cfg_.child_range("proxy")) {
593  for(const std::string& version : utils::split(proxy["version"])) {
594  proxy_versions_[version] = proxy;
595  }
596  }
597 
599 
600  // If there is a [user_handler] tag in the config file
601  // allow nick registration, otherwise we set user_handler_
602  // to nullptr. Thus we must check user_handler_ for not being
603  // nullptr every time we want to use it.
604  user_handler_.reset();
605 
606 #ifdef HAVE_MYSQLPP
607  if(auto user_handler = cfg_.optional_child("user_handler")) {
608  if(server_id_ == "") {
609  ERR_SERVER << "The server id must be set when database support is used";
610  exit(1);
611  }
612 
613  user_handler_.reset(new fuh(*user_handler));
614  tournaments_ = user_handler_->get_tournaments();
615  }
616 #endif
617 
619 
620  if(cfg_["dummy_player_count"].to_int() > 0) {
621  for(int i = 0; i < cfg_["dummy_player_count"].to_int(); i++) {
622  simple_wml::node& dummy_user = games_and_users_list_.root().add_child_at("user", i);
623  dummy_user.set_attr_dup("available", "yes");
624  dummy_user.set_attr_int("forum_id", i);
625  dummy_user.set_attr_int("game_id", 0);
626  dummy_user.set_attr_dup("location", "");
627  dummy_user.set_attr_dup("moderator", "no");
628  dummy_user.set_attr_dup("name", ("player"+std::to_string(i)).c_str());
629  dummy_user.set_attr_dup("registered", "yes");
630  dummy_user.set_attr_dup("status", "lobby");
631  }
632  if(cfg_["dummy_player_timer_interval"].to_int() > 0) {
633  dummy_player_timer_interval_ = chrono::parse_duration(cfg_["dummy_player_timer_interval"], 0s);
634  }
636  }
637 }
638 
639 bool server::ip_exceeds_connection_limit(const std::string& ip) const
640 {
641  if(concurrent_connections_ == 0) {
642  return false;
643  }
644 
645  std::size_t connections = 0;
646  for(const auto& player : player_connections_) {
647  if(player.client_ip() == ip) {
648  ++connections;
649  }
650  }
651 
652  return connections >= concurrent_connections_;
653 }
654 
655 utils::optional<server_base::login_ban_info> server::is_ip_banned(const std::string& ip)
656 {
657  if(utils::contains(tor_ip_list_, ip)) {
658  return login_ban_info{ MP_SERVER_IP_BAN_ERROR, "TOR IP", {} };
659  }
660 
661  if(auto server_ban_info = ban_manager_.get_ban_info(ip)) {
662  return login_ban_info{
664  server_ban_info->get_reason(),
665  server_ban_info->get_remaining_ban_time()
666  };
667  }
668 
669  return {};
670 }
671 
673 {
674  dump_stats_timer_.expires_after(5min);
675  dump_stats_timer_.async_wait([this](const boost::system::error_code& ec) { dump_stats(ec); });
676 }
677 
678 void server::dump_stats(const boost::system::error_code& ec)
679 {
680  if(ec) {
681  ERR_SERVER << "Error waiting for dump stats timer: " << ec.message();
682  return;
683  }
684  LOG_SERVER << "Statistics:"
685  << "\tnumber_of_games = " << games().size()
686  << "\tnumber_of_users = " << player_connections_.size();
688 }
689 
691 {
693  dummy_player_timer_.async_wait([this](const boost::system::error_code& ec) { dummy_player_updates(ec); });
694 }
695 
696 void server::dummy_player_updates(const boost::system::error_code& ec)
697 {
698  if(ec) {
699  ERR_SERVER << "Error waiting for dummy player timer: " << ec.message();
700  return;
701  }
702 
703  int size = games_and_users_list_.root().children("user").size();
704  LOG_SERVER << "player count: " << size;
705  if(size % 2 == 0) {
706  simple_wml::node* dummy_user = games_and_users_list_.root().children("user").at(size-1);
707 
709  if(make_delete_diff(games_and_users_list_.root(), nullptr, "user", dummy_user, diff)) {
710  send_to_lobby(diff);
711  }
712 
714  } else {
715  simple_wml::node& dummy_user = games_and_users_list_.root().add_child_at("user", size-1);
716  dummy_user.set_attr_dup("available", "yes");
717  dummy_user.set_attr_int("forum_id", size-1);
718  dummy_user.set_attr_int("game_id", 0);
719  dummy_user.set_attr_dup("location", "");
720  dummy_user.set_attr_dup("moderator", "no");
721  dummy_user.set_attr_dup("name", ("player"+std::to_string(size-1)).c_str());
722  dummy_user.set_attr_dup("registered", "yes");
723  dummy_user.set_attr_dup("status", "lobby");
724 
726  make_add_diff(games_and_users_list_.root(), nullptr, "user", diff);
727  send_to_lobby(diff);
728  }
729 
731 }
732 
734 {
735  tournaments_timer_.expires_after(60min);
736  tournaments_timer_.async_wait([this](const boost::system::error_code& ec) { refresh_tournaments(ec); });
737 }
738 
739 void server::refresh_tournaments(const boost::system::error_code& ec)
740 {
741  if(ec) {
742  ERR_SERVER << "Error waiting for tournament refresh timer: " << ec.message();
743  return;
744  }
745  if(user_handler_) {
746  tournaments_ = user_handler_->get_tournaments();
748  }
749 }
750 
752 {
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); }
756 #endif
757  );
758 }
759 
761 {
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); }
765 #endif
766  );
767 }
768 
769 template<class SocketPtr>
770 void server::login_client(boost::asio::yield_context yield, SocketPtr socket)
771 {
772  coro_send_doc(socket, version_query_response_, yield);
773 
774  auto doc { coro_receive_doc(socket, yield) };
775  if(!doc) return;
776 
777  std::string client_version, client_source;
778  if(const simple_wml::node* const version = doc->child("version")) {
779  const simple_wml::string_span& version_str_span = (*version)["version"];
780  client_version = std::string { version_str_span.begin(), version_str_span.end() };
781 
782  const simple_wml::string_span& source_str_span = (*version)["client_source"];
783  client_source = std::string { source_str_span.begin(), source_str_span.end() };
784 
785  // Check if it is an accepted version.
786  auto accepted_it = std::find_if(accepted_versions_.begin(), accepted_versions_.end(),
787  std::bind(&utils::wildcard_string_match, client_version, std::placeholders::_1));
788 
789  if(accepted_it != accepted_versions_.end()) {
790  LOG_SERVER << log_address(socket) << "\tplayer joined using accepted version " << client_version
791  << ":\ttelling them to log in.";
792  coro_send_doc(socket, login_response_, yield);
793  } else {
794  simple_wml::document response;
795 
796  // Check if it is a redirected version
797  for(const auto& redirect_version : redirected_versions_) {
798  if(utils::wildcard_string_match(client_version, redirect_version.first)) {
799  LOG_SERVER << log_address(socket) << "\tplayer joined using version " << client_version
800  << ":\tredirecting them to " << redirect_version.second["host"] << ":"
801  << redirect_version.second["port"];
802 
803  simple_wml::node& redirect = response.root().add_child("redirect");
804  for(const auto& attr : redirect_version.second.attribute_range()) {
805  redirect.set_attr_dup(attr.first.c_str(), attr.second.str().c_str());
806  }
807 
808  async_send_doc_queued(socket, response);
809  return;
810  }
811  }
812 
813  LOG_SERVER << log_address(socket) << "\tplayer joined using unknown version " << client_version
814  << ":\trejecting them";
815 
816  // For compatibility with older clients
817  response.set_attr_dup("version", accepted_versions_.begin()->c_str());
818 
819  simple_wml::node& reject = response.root().add_child("reject");
820  reject.set_attr_dup("accepted_versions", utils::join(accepted_versions_).c_str());
821  async_send_doc_queued(socket, response);
822  return;
823  }
824  } else {
825  LOG_SERVER << log_address(socket) << "\tclient didn't send its version: rejecting";
826  return;
827  }
828 
829  std::string username;
830  bool registered, is_moderator;
831 
832  while(true) {
833  auto login_response { coro_receive_doc(socket, yield) };
834  if(!login_response) return;
835 
836  if(const simple_wml::node* const login = login_response->child("login")) {
837  username = (*login)["username"].to_string();
838 
839  if(is_login_allowed(yield, socket, login, username, registered, is_moderator)) {
840  break;
841  } else continue;
842  }
843 
844  async_send_error(socket, "You must login first.", MP_MUST_LOGIN);
845  }
846 
847  simple_wml::node& player_cfg = games_and_users_list_.root().add_child("user");
848 
849  player_iterator new_player;
850  bool inserted;
851  std::tie(new_player, inserted) = player_connections_.emplace(
852  socket,
853  username,
854  player_cfg,
855  user_handler_ ? user_handler_->get_forum_id(username) : 0,
856  registered,
857  client_version,
858  client_source,
859  user_handler_ ? user_handler_->db_insert_login(username, client_address(socket), client_version) : 0,
862  is_moderator
863  );
864 
865  assert(inserted && "unexpected duplicate username");
866 
867  simple_wml::document join_lobby_response;
868  join_lobby_response.root().add_child("join_lobby").set_attr("is_moderator", is_moderator ? "yes" : "no");
869  simple_wml::node& join_lobby_node = join_lobby_response.root().child("join_lobby")->set_attr_dup("profile_url_prefix", "https://r.wesnoth.org/u");
870  // add server-side queues info
871  simple_wml::node& queues_node = join_lobby_node.add_child("queues");
872  for(const auto& [id, queue] : queue_info_) {
873  simple_wml::node& queue_node = queues_node.add_child("queue");
874  queue_node.set_attr_int("id", queue.id);
875  queue_node.set_attr_dup("display_name", queue.display_name.c_str());
876  queue_node.set_attr_int("players_required", queue.players_required);
877  queue_node.set_attr_dup("current_players", utils::join(queue.players_in_queue).c_str());
878  }
879  coro_send_doc(socket, join_lobby_response, yield);
880 
881  boost::asio::spawn(io_service_,
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); }
885 #endif
886  );
887 
888  LOG_SERVER << log_address(socket) << "\t" << username << "\thas logged on"
889  << (registered ? " to a registered account" : "");
890 
891  std::shared_ptr<game> last_sent;
892  for(const auto& record : player_connections_.get<game_t>()) {
893  auto g_ptr = record.get_game();
894  if(g_ptr != last_sent) {
895  // Note: This string is parsed by the client to identify lobby join messages!
896  g_ptr->send_server_message_to_all(username + " has logged into the lobby");
897  last_sent = g_ptr;
898  }
899  }
900 
901  // Log the IP
902  if(!user_handler_) {
903  connection_log ip_name { username, client_address(socket), {} };
904 
905  if(std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) {
906  ip_log_.push_back(ip_name);
907 
908  // Remove the oldest entry if the size of the IP log exceeds the maximum size
909  if(ip_log_.size() > max_ip_log_size_) {
910  ip_log_.pop_front();
911  }
912  }
913  }
914 }
915 
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)
917 {
918  // Check if the username is valid (all alpha-numeric plus underscore and hyphen)
919  if(!utils::isvalid_username(username)) {
920  async_send_error(socket,
921  "The nickname '" + username + "' contains invalid "
922  "characters. Only alpha-numeric characters, underscores and hyphens are allowed.",
924  );
925 
926  return false;
927  }
928 
929  if(username.size() > 20) {
930  async_send_error(socket, "The nickname '" + username + "' is too long. Nicks must be 20 characters or less.",
932 
933  return false;
934  }
935 
936  // Check if the username is allowed.
937  for(const std::string& d : disallowed_names_) {
939  async_send_error(socket, "The nickname '" + username + "' is reserved and cannot be used by players",
941 
942  return false;
943  }
944  }
945 
946  // Check the username isn't already taken
947  auto p = player_connections_.get<name_t>().find(username);
948  bool name_taken = p != player_connections_.get<name_t>().end();
949 
950  // Check for password
951 
952  if(!authenticate(socket, username, (*login)["password"].to_string(), name_taken, registered))
953  return false;
954 
955  // If we disallow unregistered users and this user is not registered send an error
956  if(user_handler_ && !registered && deny_unregistered_login_) {
957  async_send_error(socket,
958  "The nickname '" + username + "' is not registered. This server disallows unregistered nicknames.",
960  );
961 
962  return false;
963  }
964 
965  is_moderator = user_handler_ && user_handler_->user_is_moderator(username);
966  user_handler::ban_info auth_ban;
967 
968  if(user_handler_) {
969  auth_ban = user_handler_->user_is_banned(username, client_address(socket));
970  }
971 
972  if(auth_ban.type != user_handler::BAN_NONE) {
973  std::string ban_type_desc;
974  std::string ban_reason;
975  const char* msg_numeric;
976  std::string ban_duration = std::to_string(auth_ban.duration.count());
977 
978  switch(auth_ban.type) {
980  ban_type_desc = "account";
981  msg_numeric = MP_NAME_AUTH_BAN_USER_ERROR;
982  ban_reason = "a ban has been issued on your user account.";
983  break;
985  ban_type_desc = "IP address";
986  msg_numeric = MP_NAME_AUTH_BAN_IP_ERROR;
987  ban_reason = "a ban has been issued on your IP address.";
988  break;
990  ban_type_desc = "email address";
991  msg_numeric = MP_NAME_AUTH_BAN_EMAIL_ERROR;
992  ban_reason = "a ban has been issued on your email address.";
993  break;
994  default:
995  ban_type_desc = "<unknown ban type>";
996  msg_numeric = "";
997  ban_reason = ban_type_desc;
998  }
999 
1000  ban_reason += " (" + ban_duration + ")";
1001 
1002  if(!is_moderator) {
1003  LOG_SERVER << log_address(socket) << "\t" << username << "\tis banned by user_handler (" << ban_type_desc
1004  << ")";
1005  if(auth_ban.duration > 0s) {
1006  // Temporary ban
1007  async_send_error(socket, "You are banned from this server: " + ban_reason, msg_numeric, {{"duration", ban_duration}});
1008  } else {
1009  // Permanent ban
1010  async_send_error(socket, "You are banned from this server: " + ban_reason, msg_numeric);
1011  }
1012  return false;
1013  } else {
1014  LOG_SERVER << log_address(socket) << "\t" << username << "\tis banned by user_handler (" << ban_type_desc
1015  << "), " << "ignoring due to moderator flag";
1016  }
1017  }
1018 
1019  if(name_taken) {
1020  if(registered) {
1021  // If there is already a client using this username kick it
1022  process_command("kick " + username + " autokick by registered user", username);
1023  // need to wait for it to process
1024  while(player_connections_.get<name_t>().count(username) > 0) {
1025  boost::asio::post(yield);
1026  }
1027  } else {
1028  async_send_error(socket, "The nickname '" + username + "' is already taken.", MP_NAME_TAKEN_ERROR);
1029  return false;
1030  }
1031  }
1032 
1033  if(auth_ban.type) {
1034  send_server_message(socket, "You are currently banned by the forum administration.", "alert");
1035  }
1036 
1037  return true;
1038 }
1039 
1040 template<class SocketPtr> bool server::authenticate(
1041  SocketPtr socket, const std::string& username, const std::string& password, bool name_taken, bool& registered)
1042 {
1043  // Current login procedure for registered nicks is:
1044  // - Client asks to log in with a particular nick
1045  // - Server sends client a password request (if TLS/database support is enabled)
1046  // - Client sends the plaintext password
1047  // - Server receives plaintext password, hashes it, and compares it to the password in the forum database
1048 
1049  registered = false;
1050 
1051  if(user_handler_) {
1052  const bool exists = user_handler_->user_exists(username);
1053 
1054  // This name is registered but the account is not active
1055  if(exists && !user_handler_->user_is_active(username)) {
1056  async_send_warning(socket,
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.",
1060  } else if(exists) {
1061  const std::string salt = user_handler_->extract_salt(username);
1062  if(salt.empty()) {
1063  async_send_error(socket,
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.");
1068  return false;
1069  }
1070  const std::string hashed_password = hash_password(password, salt, username);
1071 
1072  // This name is registered and no password provided
1073  if(password.empty()) {
1074  if(!name_taken) {
1075  send_password_request(socket, "The nickname '" + username + "' is registered on this server.", MP_PASSWORD_REQUEST);
1076  } else {
1077  send_password_request(socket,
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!",
1082  );
1083  }
1084 
1085  return false;
1086  }
1087 
1088  // hashing the password failed
1089  // note: this could be due to other related problems other than *just* the hashing step failing
1090  if(hashed_password.empty()) {
1091  async_send_error(socket, "Password hashing failed.", MP_HASHING_PASSWORD_FAILED);
1092  return false;
1093  }
1094  // This name is registered and an incorrect password provided
1095  else if(!(user_handler_->login(username, hashed_password))) {
1096  const auto steady_now = std::chrono::steady_clock::now();
1097 
1098  login_log login_ip { client_address(socket), 0, steady_now };
1099  auto i = std::find(failed_logins_.begin(), failed_logins_.end(), login_ip);
1100 
1101  if(i == failed_logins_.end()) {
1102  failed_logins_.push_back(login_ip);
1103  i = --failed_logins_.end();
1104 
1105  // Remove oldest entry if maximum size is exceeded
1107  failed_logins_.pop_front();
1108  }
1109  }
1110 
1111  if(i->first_attempt + failed_login_ban_ < steady_now) {
1112  // Clear and move to the beginning
1113  failed_logins_.erase(i);
1114  failed_logins_.push_back(login_ip);
1115  i = --failed_logins_.end();
1116  }
1117 
1118  i->attempts++;
1119 
1120  if(i->attempts > failed_login_limit_) {
1121  LOG_SERVER << ban_manager_.ban(login_ip.ip, std::chrono::system_clock::now() + failed_login_ban_,
1122  "Maximum login attempts exceeded", "automatic", "", username);
1123 
1124  async_send_error(socket, "You have made too many failed login attempts.", MP_TOO_MANY_ATTEMPTS_ERROR);
1125  } else {
1126  send_password_request(socket,
1127  "The password you provided for the nickname '" + username + "' was incorrect.",
1129  }
1130 
1131  // Log the failure
1132  LOG_SERVER << log_address(socket) << "\t"
1133  << "Login attempt with incorrect password for nickname '" << username << "'.";
1134  return false;
1135  }
1136 
1137  // This name exists and the password was neither empty nor incorrect
1138  registered = true;
1139  user_handler_->user_logged_in(username);
1140  }
1141  }
1142 
1143  return true;
1144 }
1145 
1146 template<class SocketPtr> void server::send_password_request(SocketPtr socket,
1147  const std::string& msg,
1148  const char* error_code,
1149  bool force_confirmation)
1150 {
1152  simple_wml::node& e = doc.root().add_child("error");
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");
1156 
1157  if(*error_code != '\0') {
1158  e.set_attr("error_code", error_code);
1159  }
1160 
1161  async_send_doc_queued(socket, doc);
1162 }
1163 
1164 template<class SocketPtr> void server::handle_player(boost::asio::yield_context yield, SocketPtr socket, player_iterator player)
1165 {
1166  if(lan_server_ > 0s)
1168 
1169  BOOST_SCOPE_EXIT_ALL(this, &player) {
1170  if(!destructed) {
1172  }
1173  };
1174 
1176 
1177  if(!motd_.empty()) {
1179  }
1180  send_server_message(player, information_, "server_info");
1182  if(version_info(player->info().version()) < secure_version ){
1183  send_server_message(player, "You are using version " + player->info().version() + " which has known security issues that can be used to compromise your computer. We strongly recommend updating to a Wesnoth version " + secure_version.str() + " or newer!", "alert");
1184  }
1186  send_server_message(player, "A newer Wesnoth version, " + recommended_version_ + ", is out!", "alert");
1187  }
1188 
1189  // Send other players in the lobby the update that the player has joined
1190  simple_wml::document diff;
1191  make_add_diff(games_and_users_list_.root(), nullptr, "user", diff);
1192  send_to_lobby(diff, player);
1193 
1194  while(true) {
1195  auto doc { coro_receive_doc(socket, yield) };
1196  if(!doc) return;
1197 
1198  // DBG_SERVER << client_address(socket) << "\tWML received:\n" << doc->output();
1199  if(doc->child("refresh_lobby")) {
1201  continue;
1202  }
1203 
1204  if(simple_wml::node* whisper = doc->child("whisper")) {
1205  handle_whisper(player, *whisper);
1206  continue;
1207  }
1208 
1209  if(simple_wml::node* query = doc->child("query")) {
1210  handle_query(player, *query);
1211  continue;
1212  }
1213 
1214  if(simple_wml::node* nickserv = doc->child("nickserv")) {
1215  handle_nickserv(player, *nickserv);
1216  continue;
1217  }
1218 
1219  if(simple_wml::node* query = doc->child("ping")) {
1220  handle_ping(player, *query);
1221  continue;
1222  }
1223 
1224  if(!player_is_in_game(player)) {
1226  } else {
1228  }
1229  }
1230 }
1231 
1233 {
1234  if(simple_wml::node* message = data.child("message")) {
1235  handle_message(player, *message);
1236  return;
1237  }
1238 
1239  if(simple_wml::node* create_game = data.child("create_game")) {
1240  handle_create_game(player, *create_game);
1241  return;
1242  }
1243 
1244  if(simple_wml::node* join = data.child("join")) {
1246  return;
1247  }
1248 
1249  if(simple_wml::node* join_server_queue = data.child("join_server_queue")) {
1250  handle_join_server_queue(player, *join_server_queue);
1251  return;
1252  }
1253 
1254  if(simple_wml::node* leave_server_queue = data.child("leave_server_queue")) {
1255  handle_leave_server_queue(player, *leave_server_queue);
1256  return;
1257  }
1258 
1259  if(simple_wml::node* request = data.child("game_history_request")) {
1260  if(user_handler_) {
1261  int offset = request->attr("offset").to_int();
1262  int player_id = 0;
1263 
1264  // if search_for attribute for offline player -> query the forum database for the forum id
1265  // if search_for attribute for online player -> get the forum id from wesnothd's player info
1266  if(request->has_attr("search_player") && request->attr("search_player").to_string() != "") {
1267  std::string player_name = request->attr("search_player").to_string();
1268  auto player_ptr = player_connections_.get<name_t>().find(player_name);
1269  if(player_ptr == player_connections_.get<name_t>().end()) {
1270  player_id = user_handler_->get_forum_id(player_name);
1271  } else {
1272  player_id = player_ptr->info().config_address()->attr("forum_id").to_int();
1273  }
1274  }
1275 
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);
1282  }
1283  return;
1284  }
1285 }
1286 
1288 {
1289  if((whisper["receiver"].empty()) || (whisper["message"].empty())) {
1290  static simple_wml::document data(
1291  "[message]\n"
1292  "message=\"Invalid number of arguments\"\n"
1293  "sender=\"server\"\n"
1294  "[/message]\n",
1296  );
1297 
1299  return;
1300  }
1301 
1302  whisper.set_attr_dup("sender", player->name().c_str());
1303 
1304  auto receiver_iter = player_connections_.get<name_t>().find(whisper["receiver"].to_string());
1305  if(receiver_iter == player_connections_.get<name_t>().end()) {
1306  send_server_message(player, "Can't find '" + whisper["receiver"].to_string() + "'.", "error");
1307  return;
1308  }
1309 
1310  auto g = player->get_game();
1311  if(g && g->started() && g->is_player(player_connections_.project<0>(receiver_iter))) {
1312  send_server_message(player, "You cannot send private messages to players in a running game you observe.", "error");
1313  return;
1314  }
1315 
1316  simple_wml::document cwhisper;
1317 
1318  simple_wml::node& trunc_whisper = cwhisper.root().add_child("whisper");
1319  whisper.copy_into(trunc_whisper);
1320 
1321  const simple_wml::string_span& msg = trunc_whisper["message"];
1322  chat_message::truncate_message(msg, trunc_whisper);
1323 
1324  send_to_player(player_connections_.project<0>(receiver_iter), cwhisper);
1325 }
1326 
1328 {
1329  wesnothd::player& player = iter->info();
1330 
1331  const std::string command(query["type"].to_string());
1332  std::ostringstream response;
1333 
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.";
1337 
1338  // Commands a player may issue.
1339  if(command == "status") {
1340  response << process_command(command + " " + player.name(), player.name());
1341  } else if(
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" ||
1352  command == "status " + player.name() ||
1353  command == "wml"
1354  ) {
1355  response << process_command(command, player.name());
1356  } else if(player.is_moderator()) {
1357  if(command == "signout") {
1358  LOG_SERVER << "Admin signed out: IP: " << iter->client_ip() << "\tnick: " << player.name();
1359  player.set_moderator(false);
1360  // This string is parsed by the client!
1361  response << "You are no longer recognized as an administrator.";
1362  if(user_handler_) {
1363  user_handler_->set_is_moderator(player.name(), false);
1364  }
1365  } else {
1366  LOG_SERVER << "Admin Command: type: " << command << "\tIP: " << iter->client_ip()
1367  << "\tnick: " << player.name();
1368  response << process_command(command, player.name());
1369  LOG_SERVER << response.str();
1370  }
1371  } else if(command == "help" || command.empty()) {
1372  response << query_help_msg;
1373  } else if(command == "admin" || command.compare(0, 6, "admin ") == 0) {
1374  if(admin_passwd_.empty()) {
1375  send_server_message(iter, "No password set.", "error");
1376  return;
1377  }
1378 
1379  std::string passwd;
1380  if(command.size() >= 6) {
1381  passwd = command.substr(6);
1382  }
1383 
1384  if(passwd == admin_passwd_) {
1385  LOG_SERVER << "New Admin recognized: IP: " << iter->client_ip() << "\tnick: " << player.name();
1386  player.set_moderator(true);
1387  // This string is parsed by the client!
1388  response << "You are now recognized as an administrator.";
1389 
1390  if(user_handler_) {
1391  user_handler_->set_is_moderator(player.name(), true);
1392  }
1393  } else {
1394  WRN_SERVER << "FAILED Admin attempt with password: '" << passwd << "'\tIP: " << iter->client_ip()
1395  << "\tnick: " << player.name();
1396  response << "Error: wrong password";
1397  }
1398  } else {
1399  response << "Error: unrecognized query: '" << command << "'\n" << query_help_msg;
1400  }
1401 
1402  send_server_message(iter, response.str(), "info");
1403 }
1404 
1406 {
1407  // Check if this server allows nick registration at all
1408  if(!user_handler_) {
1409  send_server_message(player, "This server does not allow username registration.", "error");
1410  return;
1411  }
1412 
1413  // A user requested a list of which details can be set
1414  if(nickserv.child("info")) {
1415  try {
1416  std::string res = user_handler_->user_info((*nickserv.child("info"))["name"].to_string());
1417  send_server_message(player, res, "info");
1418  } catch(const user_handler::error& e) {
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"
1423  );
1424  }
1425 
1426  return;
1427  }
1428 }
1429 
1431 {
1432  // IMPORTANT: the time resolution is undefined. It will vary based on client
1433  const simple_wml::string_span& time = data["requested_at"];
1434 
1435  if(time.empty()) {
1436  send_server_message(player, "Ping request time unspecified", "error");
1437  return;
1438  }
1439 
1441  simple_wml::node& ping = res.root().add_child("ping");
1442  ping.set_attr_dup("requested_at", time);
1443  ping.set_attr_int("processed_at", chrono::serialize_timestamp(std::chrono::system_clock::now()));
1444 
1445  send_to_player(player, res);
1446 }
1447 
1449 {
1450  if(user->info().is_message_flooding()) {
1451  send_server_message(user,
1452  "Warning: you are sending too many messages too fast. Your message has not been relayed.", "error");
1453  return;
1454  }
1455 
1456  simple_wml::document relay_message;
1457  message.set_attr_dup("sender", user->name().c_str());
1458 
1459  simple_wml::node& trunc_message = relay_message.root().add_child("message");
1460  message.copy_into(trunc_message);
1461 
1462  const simple_wml::string_span& msg = trunc_message["message"];
1463  chat_message::truncate_message(msg, trunc_message);
1464 
1465  if(msg.size() >= 3 && simple_wml::string_span(msg.begin(), 4) == "/me ") {
1466  LOG_SERVER << user->client_ip() << "\t<" << user->name()
1467  << simple_wml::string_span(msg.begin() + 3, msg.size() - 3) << ">";
1468  } else {
1469  LOG_SERVER << user->client_ip() << "\t<" << user->name() << "> " << msg;
1470  }
1471 
1472  send_to_lobby(relay_message, user);
1473 }
1474 
1475 void server::send_queue_update(const queue_info& queue, utils::optional<player_iterator> exclude)
1476 {
1477  simple_wml::document queue_update;
1478  simple_wml::node& update = queue_update.root().add_child("queue_update");
1479  update.set_attr_int("queue_id", queue.id);
1480  update.set_attr_dup("action", "update");
1481  update.set_attr_dup("current_players", utils::join(queue.players_in_queue).c_str());
1482 
1483  send_to_lobby(queue_update, exclude);
1484 }
1485 
1487 {
1488  if(graceful_restart) {
1489  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
1490  send_to_player(player, leave_game_doc);
1491 
1493  "This server is shutting down. You aren't allowed to make new games. Please "
1494  "reconnect to the new server.", "error");
1495 
1497  return;
1498  }
1499 
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();
1503  const queue_type::type queue_type = queue_type::get_enum(create_game["queue_type"].to_string()).value_or(queue_type::type::normal);
1504  int queue_id = create_game["queue_id"].to_int();
1505 
1506  DBG_SERVER << player->client_ip() << "\t" << player->info().name()
1507  << "\tcreates a new game: \"" << game_name << "\".";
1508 
1509  // Create the new game, remove the player from the lobby
1510  // and set the player as the host/owner.
1511  player_connections_.modify(player, [this, player, &game_name, queue_type, queue_id](player_record& host_record) {
1512  host_record.get_game().reset(
1514  std::bind(&server::cleanup_game, this, std::placeholders::_1)
1515  );
1516  });
1517 
1518  wesnothd::game& g = *player->get_game();
1519 
1520  DBG_SERVER << "initial bans: " << initial_bans;
1521  if(initial_bans != "") {
1522  g.set_name_bans(utils::split(initial_bans,','));
1523  }
1524 
1525  if(game_password.empty() == false) {
1526  g.set_password(game_password);
1527  }
1528 
1529  create_game.copy_into(g.level().root());
1530 
1531  for(int q_index : player->info().get_queues()) {
1532  queue_info& queue = queue_info_.at(q_index);
1533  std::vector<std::string>& p_queue = queue.players_in_queue;
1534  if(p_queue.empty()) {
1535  continue;
1536  }
1537  std::vector<std::string>::iterator i = std::remove(p_queue.begin(), p_queue.end(), player->name());
1538  if(i != p_queue.end()) {
1539  p_queue.erase(i, p_queue.end());
1540  send_queue_update(queue);
1541  }
1542  }
1543 }
1544 
1546 {
1548 
1549  if(user_handler_){
1550  user_handler_->db_update_game_end(uuid_, game_ptr->db_id(), game_ptr->get_replay_filename());
1551  }
1552 
1553  simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
1554  assert(gamelist != nullptr);
1555 
1556  // Send a diff of the gamelist with the game deleted to players in the lobby
1557  simple_wml::document diff;
1558  if(!destructed && make_delete_diff(*gamelist, "gamelist", "game", game_ptr->description(), diff)) {
1559  send_to_lobby(diff);
1560  }
1561 
1562  // Delete the game from the games_and_users_list_.
1563  const simple_wml::node::child_list& games = gamelist->children("game");
1564  const auto g = std::find(games.begin(), games.end(), game_ptr->description());
1565 
1566  if(g != games.end()) {
1567  const std::size_t index = std::distance(games.begin(), g);
1568  gamelist->remove_child("game", index);
1569  } else {
1570  // Can happen when the game ends before the scenario was transferred.
1571  LOG_SERVER << "Could not find game (" << game_ptr->id() << ", " << game_ptr->db_id() << ") to delete in games_and_users_list_.";
1572  }
1573 
1574  if(destructed) game_ptr->emergency_cleanup();
1575 
1576  delete game_ptr;
1577 }
1578 
1580 {
1581  int game_id = join["id"].to_int();
1582 
1583  const bool observer = join.attr("observe").to_bool();
1584  const std::string& password = join["password"].to_string();
1585 
1586  auto g_iter = player_connections_.get<game_t>().find(game_id);
1587 
1588  std::shared_ptr<game> g;
1589  if(g_iter != player_connections_.get<game_t>().end()) {
1590  g = g_iter->get_game();
1591  }
1592 
1593  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
1594  if(!g) {
1595  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1596  << "\tattempted to join unknown game:\t" << game_id << ".";
1597  send_to_player(player, leave_game_doc);
1598  send_server_message(player, "Attempt to join unknown game.", "error");
1600  return;
1601  } else if(!g->level_init()) {
1602  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1603  << "\tattempted to join uninitialized game:\t\"" << g->name() << "\" (" << game_id << ").";
1604  send_to_player(player, leave_game_doc);
1605  send_server_message(player, "Attempt to join an uninitialized game.", "error");
1607  return;
1608  } else if(player->info().is_moderator()) {
1609  // Admins are always allowed to join.
1610  } else if(g->player_is_banned(player, player->info().name())) {
1611  DBG_SERVER << player->client_ip()
1612  << "\tReject banned player: " << player->info().name()
1613  << "\tfrom game:\t\"" << g->name() << "\" (" << game_id << ").";
1614  send_to_player(player, leave_game_doc);
1615  send_server_message(player, "You are banned from this game.", "error");
1617  return;
1618  } else if(!g->password_matches(password)) {
1619  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1620  << "\tattempted to join game:\t\"" << g->name() << "\" (" << game_id << ") with bad password";
1621  send_to_player(player, leave_game_doc);
1622  send_server_message(player, "Incorrect password.", "error");
1624  return;
1625  }
1626 
1627  bool joined = g->add_player(player, observer);
1628  if(!joined) {
1629  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1630  << "\tattempted to observe game:\t\"" << g->name() << "\" (" << game_id
1631  << ") which doesn't allow observers.";
1632  send_to_player(player, leave_game_doc);
1633 
1635  "Attempt to observe a game that doesn't allow observers. (You probably joined the "
1636  "game shortly after it filled up.)", "error");
1637 
1639  return;
1640  }
1641 
1642  player_connections_.modify(player,
1643  std::bind(&player_record::set_game, std::placeholders::_1, g));
1644 
1645  g->describe_slots();
1646 
1647  // send notification of changes to the game and user
1648  simple_wml::document diff;
1649  bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g->changed_description(), diff);
1650  bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user", player->info().config_address(), diff);
1651 
1652  if(diff1 || diff2) {
1653  send_to_lobby(diff);
1654  }
1655 
1656  // remove from any queues they may have joined
1657  for(int q_index : player->info().get_queues()) {
1658  queue_info& queue = queue_info_.at(q_index);
1659  std::vector<std::string>& p_queue = queue.players_in_queue;
1660  if(p_queue.empty()) {
1661  continue;
1662  }
1663  std::vector<std::string>::iterator i = std::remove(p_queue.begin(), p_queue.end(), player->name());
1664  if(i != p_queue.end()) {
1665  p_queue.erase(i, p_queue.end());
1666  send_queue_update(queue);
1667  }
1668  }
1669 }
1670 
1672 {
1673  int queue_id = data.attr("queue_id").to_int();
1674 
1675  if(queue_info_.count(queue_id) == 0) {
1676  ERR_SERVER << "player " << p->info().name() << " attempted to join non-existing server-side queue " << data.attr("queue_id");
1677  return;
1678  }
1679 
1680  queue_info& queue = queue_info_.at(queue_id);
1681  if(utils::contains(queue.players_in_queue, p->info().name())) {
1682  DBG_SERVER << "player " << p->info().name() << " already in server-side queue " << data.attr("queue_id");
1683  return;
1684  }
1685 
1686  // if they're not already in the queue, add them
1687  queue.players_in_queue.emplace_back(p->info().name());
1688  p->info().add_queue(queue.id);
1689  LOG_SERVER << p->client_ip() << "\t" << p->name() << "\tincrementing " << queue.settings["scenario"] << " to " << std::to_string(queue.players_in_queue.size()) << "/" << std::to_string(queue.players_required);
1690 
1691  send_queue_update(queue);
1692 
1693  // if there are enough players in the queue to start a game, then have the final player who joined the queue host it
1694  // else check if there's an existing game that was created for the queue which needs players (ie: player left or failed to join)
1695  // if yes, tell the player to immediately join that game
1696  // if no, leave them in the queue
1697  if(queue.players_required <= queue.players_in_queue.size()) {
1698  simple_wml::document create_game_doc;
1699  simple_wml::node& create_game_node = create_game_doc.root().add_child("create_game");
1700  create_game_node.set_attr_int("queue_id", queue.id);
1701  simple_wml::node& game = create_game_node.add_child("game");
1702 
1703  std::vector<std::string> scenarios = utils::split(queue.settings["scenario"].str());
1704  uint32_t index = rng_.get_next_random() % scenarios.size();
1705 
1706  game.set_attr_dup("scenario", scenarios[index].c_str());
1707  game.set_attr_dup("era", queue.settings["era"].str().c_str());
1708  game.set_attr_int("fog", queue.settings["fog"].to_int());
1709  game.set_attr_int("shroud", queue.settings["shroud"].to_int());
1710  game.set_attr_int("village_gold", queue.settings["village_gold"].to_int());
1711  game.set_attr_int("village_support", queue.settings["village_support"].to_int());
1712  game.set_attr_int("experience_modifier", queue.settings["experience_modifier"].to_int());
1713  game.set_attr_int("countdown", queue.settings["countdown"].to_int());
1714  game.set_attr_int("random_start_time", queue.settings["random_start_time"].to_int());
1715  game.set_attr_int("shuffle_sides", queue.settings["shuffle_sides"].to_int());
1716 
1717  // tell the final player to create and host the game
1718  send_to_player(p, create_game_doc);
1719  } else {
1720  for(const auto& game : games()) {
1721  if(game->is_open_queue_game(queue.id)) {
1722  simple_wml::document join_game_doc;
1723  simple_wml::node& join_game_node = join_game_doc.root().add_child("join_game");
1724  join_game_node.set_attr_int("id", game->id());
1725 
1726  send_to_player(p, join_game_doc);
1727  return;
1728  }
1729  }
1730  }
1731 }
1732 
1734 {
1735  int queue_id = data.attr("queue_id").to_int();
1736 
1737  if(queue_info_.count(queue_id) == 0) {
1738  ERR_SERVER << "player " << p->info().name() << " attempted to leave non-existing server-side queue " << data.attr("queue_id");
1739  return;
1740  }
1741 
1742  queue_info& queue = queue_info_.at(queue_id);
1743 
1744  // if they're in the queue, remove them
1745  if(utils::contains(queue.players_in_queue, p->info().name())) {
1746  queue.players_in_queue.erase(std::remove(queue.players_in_queue.begin(), queue.players_in_queue.end(), p->info().name()), queue.players_in_queue.end());
1747  p->info().clear_queues();
1748 
1749  send_queue_update(queue);
1750  } else {
1751  ERR_SERVER << "player " << p->info().name() << " already not in server-side queue " << data.attr("queue_id");
1752  }
1753 }
1754 
1756 {
1757  DBG_SERVER << "in process_data_game...";
1758 
1759  wesnothd::player& player { p->info() };
1760 
1761  game& g = *(p->get_game());
1762  std::weak_ptr<game> g_ptr{p->get_game()};
1763 
1764  // If this is data describing the level for a game.
1765  if(data.child("snapshot") || data.child("scenario")) {
1766  if(!g.is_owner(p)) {
1767  return;
1768  }
1769 
1770  // If this game is having its level data initialized
1771  // for the first time, and is ready for players to join.
1772  // We should currently have a summary of the game in g.level().
1773  // We want to move this summary to the games_and_users_list_, and
1774  // place a pointer to that summary in the game's description.
1775  // g.level() should then receive the full data for the game.
1776  if(!g.level_init()) {
1777  LOG_SERVER << p->client_ip() << "\t" << player.name() << "\tcreated game:\t\"" << g.name() << "\" ("
1778  << g.id() << ", " << g.db_id() << ").";
1779  // Update our config object which describes the open games,
1780  // and save a pointer to the description in the new game.
1781  simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
1782  assert(gamelist != nullptr);
1783 
1784  simple_wml::node& desc = gamelist->add_child("game");
1785  g.level().root().copy_into(desc);
1786 
1787  if(const simple_wml::node* m = data.child("multiplayer")) {
1788  m->copy_into(desc);
1789  } else {
1790  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1791  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1792  // Set the description so it can be removed in delete_game().
1793  g.set_description(&desc);
1794  delete_game(g.id());
1795 
1797  "The scenario data is missing the [multiplayer] tag which contains the "
1798  "game settings. Game aborted.", "error");
1799  return;
1800  }
1801 
1802  g.set_description(&desc);
1803  desc.set_attr_dup("id", std::to_string(g.id()).c_str());
1804  } else {
1805  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1806  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") although it's already initialized.";
1807  return;
1808  }
1809 
1810  assert(games_and_users_list_.child("gamelist")->children("game").empty() == false);
1811 
1812  simple_wml::node& desc = *g.description_for_writing();
1813 
1814  // Update the game's description.
1815  // If there is no shroud, then tell players in the lobby
1816  // what the map looks like
1818  // fixme: the hanlder of [store_next_scenario] below searches for 'mp_shroud' in [scenario]
1819  // at least of the these cosed is likely wrong.
1820  if(!data["mp_shroud"].to_bool()) {
1821  desc.set_attr_dup("map_data", s["map_data"]);
1822  }
1823 
1824  if(const simple_wml::node* e = data.child("era")) {
1825  if(!e->attr("require_era").to_bool(true)) {
1826  desc.set_attr("require_era", "no");
1827  }
1828  }
1829 
1830  if(s["require_scenario"].to_bool(false)) {
1831  desc.set_attr("require_scenario", "yes");
1832  }
1833 
1834  const simple_wml::node::child_list& mlist = data.children("modification");
1835  for(const simple_wml::node* m : mlist) {
1836  desc.add_child_at("modification", 0);
1837  desc.child("modification")->set_attr_dup("id", m->attr("id"));
1838  desc.child("modification")->set_attr_dup("name", m->attr("name"));
1839  desc.child("modification")->set_attr_dup("addon_id", m->attr("addon_id"));
1840  desc.child("modification")->set_attr_dup("require_modification", m->attr("require_modification"));
1841  }
1842 
1843  // Record the full scenario in g.level()
1844  g.level().swap(data);
1845 
1846  // The host already put himself in the scenario so we just need
1847  // to update_side_data().
1848  // g.take_side(sock);
1849  g.update_side_data();
1850  g.describe_slots();
1851 
1852  // Send the update of the game description to the lobby.
1853  simple_wml::document diff;
1854  make_add_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", diff);
1855  make_change_diff(games_and_users_list_.root(), nullptr, "user", p->info().config_address(), diff);
1856 
1857  send_to_lobby(diff);
1858 
1859  // if this is the creation of a game from a server-side queue, need to tell all the other players in the queue to join
1860  if(g.q_type() == queue_type::type::server_preset) {
1861  int queue_id = g.queue_id();
1862  int game_id = g.id();
1863 
1864  if(queue_info_.count(queue_id) == 0) {
1865  return;
1866  }
1867 
1868  queue_info& info = queue_info_.at(queue_id);
1869  std::size_t joined_count = 1;
1870  DBG_SERVER << p->client_ip() << " queue " << queue_id << " players in queue: " << utils::join(info.players_in_queue);
1871  for(const std::string& name : info.players_in_queue) {
1872  auto player_ptr = player_connections_.get<name_t>().find(name);
1873  if(player_ptr == player_connections_.get<name_t>().end()) {
1874  continue;
1875  }
1876 
1877  // player is still connected and not in a game, tell them to join
1878  if(!player_ptr->get_game()) {
1879  simple_wml::document join_game_doc;
1880  simple_wml::node& join_game_node = join_game_doc.root().add_child("join_game");
1881  join_game_node.set_attr_int("id", game_id);
1882  send_to_player(player_ptr->socket(), join_game_doc);
1883  }
1884 
1885  joined_count++;
1886  if(joined_count == info.players_required) {
1887  break;
1888  }
1889  }
1890 
1891  // send all other players the updated player counts for all queues
1892  for(auto& [id, queue] : queue_info_) {
1893  send_queue_update(queue);
1894  }
1895  }
1896 
1897  /** @todo FIXME: Why not save the level data in the history_? */
1898  return;
1899  // Everything below should only be processed if the game is already initialized.
1900  } else if(!g.level_init()) {
1901  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
1902  << " while the scenario wasn't yet initialized."
1903  << data.output();
1904  return;
1905  // If the host is sending the next scenario data.
1906  } else if(const simple_wml::node* scenario = data.child("store_next_scenario")) {
1907  if(!g.is_owner(p)) {
1908  return;
1909  }
1910 
1911  if(!g.level_init()) {
1912  WRN_SERVER << p->client_ip() << "\tWarning: " << player.name()
1913  << "\tsent [store_next_scenario] in game:\t\"" << g.name() << "\" (" << g.id()
1914  << ", " << g.db_id() << ") while the scenario is not yet initialized.";
1915  return;
1916  }
1917 
1918  g.save_replay();
1919  if(user_handler_){
1920  user_handler_->db_update_game_end(uuid_, g.db_id(), g.get_replay_filename());
1921  }
1922 
1923  g.new_scenario(p);
1924  g.reset_last_synced_context_id();
1925 
1926  // Record the full scenario in g.level()
1927  g.level().clear();
1928  scenario->copy_into(g.level().root());
1929  g.next_db_id();
1930 
1931  if(g.description() == nullptr) {
1932  ERR_SERVER << p->client_ip() << "\tERROR: \"" << g.name() << "\" (" << g.id()
1933  << ", " << g.db_id() << ") is initialized but has no description_.";
1934  return;
1935  }
1936 
1937  simple_wml::node& desc = *g.description_for_writing();
1938 
1939  // Update the game's description.
1940  if(const simple_wml::node* m = scenario->child("multiplayer")) {
1941  m->copy_into(desc);
1942  } else {
1943  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1944  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1945 
1946  delete_game(g.id());
1947 
1949  "The scenario data is missing the [multiplayer] tag which contains the game "
1950  "settings. Game aborted.", "error");
1951  return;
1952  }
1953 
1954  // If there is no shroud, then tell players in the lobby
1955  // what the map looks like.
1956  const simple_wml::node& s = *wesnothd::game::starting_pos(g.level().root());
1957  desc.set_attr_dup("map_data", s["mp_shroud"].to_bool() ? "" : s["map_data"]);
1958 
1959  if(const simple_wml::node* e = data.child("era")) {
1960  if(!e->attr("require_era").to_bool(true)) {
1961  desc.set_attr("require_era", "no");
1962  }
1963  }
1964 
1965  if(s["require_scenario"].to_bool(false)) {
1966  desc.set_attr("require_scenario", "yes");
1967  }
1968 
1969  // Tell everyone that the next scenario data is available.
1970  static simple_wml::document notify_next_scenario(
1971  "[notify_next_scenario]\n[/notify_next_scenario]\n", simple_wml::INIT_COMPRESSED);
1972  g.send_data(notify_next_scenario, p);
1973 
1974  // Send the update of the game description to the lobby.
1976  return;
1977  // A mp client sends a request for the next scenario of a mp campaign.
1978  } else if(data.child("load_next_scenario")) {
1979  g.load_next_scenario(p);
1980  return;
1981  } else if(data.child("start_game")) {
1982  if(!g.is_owner(p)) {
1983  return;
1984  }
1985 
1986  // perform controller tweaks, assigning sides as human for their owners etc.
1987  g.perform_controller_tweaks();
1988 
1989  // Send notification of the game starting immediately.
1990  // g.start_game() will send data that assumes
1991  // the [start_game] message has been sent
1992  g.send_data(data, p);
1993  g.start_game(p);
1994 
1995  if(user_handler_) {
1996  const simple_wml::node& m = *g.level().root().child("multiplayer");
1998  // [addon] info handling
1999  std::set<std::string> primary_keys;
2000  for(const auto& addon : m.children("addon")) {
2001  for(const auto& content : addon->children("content")) {
2002  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();
2003  if(primary_keys.count(key) == 0) {
2004  primary_keys.emplace(key);
2005  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());
2006  if(rows_inserted == 0) {
2007  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() << "'";
2008  }
2009  }
2010  }
2011  }
2012  if(m.children("addon").size() == 0) {
2013  WRN_SERVER << "Game content info missing for game with uuid '" << uuid_ << "', game ID '" << g.db_id() << "', named '" << g.name() << "'";
2014  }
2015 
2016  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());
2017 
2018  const simple_wml::node::child_list& sides = g.get_sides_list();
2019  for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
2020  const simple_wml::node& side = *sides[side_index];
2021  const auto player = player_connections_.get<name_t>().find(side["player_id"].to_string());
2022  std::string version;
2023  std::string source;
2024 
2025  // if "Nobody" is chosen for a side, for example
2026  if(player == player_connections_.get<name_t>().end()){
2027  version = "";
2028  source = "";
2029  } else {
2030  version = player->info().version();
2031  source = player->info().source();
2032 
2033  if(client_sources_.count(source) == 0) {
2034  source = "Default";
2035  }
2036  }
2037 
2038  // approximately determine leader(s) for the side like the client does
2039  // useful generally to know how often leaders are used vs other leaders
2040  // also as an indication for which faction was chosen if a custom recruit list is provided since that results in "Custom" in the faction field
2041  std::vector<std::string> leaders;
2042  // if a type= attribute is specified for the side, add it
2043  if(side.attr("type") != "") {
2044  leaders.emplace_back(side.attr("type").to_string());
2045  }
2046  // add each [unit] in the side that has canrecruit=yes
2047  for(const auto unit : side.children("unit")) {
2048  if(unit->attr("canrecruit") == "yes") {
2049  leaders.emplace_back(unit->attr("type").to_string());
2050  }
2051  }
2052  // add any [leader] specified for the side
2053  for(const auto leader : side.children("leader")) {
2054  leaders.emplace_back(leader->attr("type").to_string());
2055  }
2056 
2057  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));
2058  }
2059  }
2060 
2061  // update the game having changed in the lobby
2063  return;
2064  } else if(data.child("leave_game")) {
2065  if(g.remove_player(p)) {
2066  delete_game(g.id());
2067  } else {
2068  bool has_diff = false;
2069  simple_wml::document diff;
2070 
2071  // After this line, the game object may be destroyed. Don't use `g`!
2072  player_connections_.modify(p, std::bind(&player_record::enter_lobby, std::placeholders::_1));
2073 
2074  // Only run this if the game object is still valid
2075  if(auto gStrong = g_ptr.lock()) {
2076  gStrong->describe_slots();
2077  //Don't update the game if it no longer exists.
2078  has_diff |= make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", gStrong->description(), diff);
2079  }
2080 
2081  // Send all other players in the lobby the update to the gamelist.
2082  has_diff |= make_change_diff(games_and_users_list_.root(), nullptr, "user", player.config_address(), diff);
2083 
2084  if(has_diff) {
2085  send_to_lobby(diff, p);
2086  }
2087 
2088  // Send the player who has quit the gamelist.
2090  }
2091 
2092  // send the current queue counts
2093  for(const auto& [id, info] : queue_info_) {
2094  simple_wml::document queue_update;
2095  simple_wml::node& update = queue_update.root().add_child("queue_update");
2096  update.set_attr_int("queue_id", info.id);
2097  update.set_attr_dup("action", "update");
2098  update.set_attr_dup("current_players", utils::join(info.players_in_queue).c_str());
2099 
2100  send_to_player(p, queue_update);
2101  }
2102 
2103  return;
2104  // If this is data describing side changes by the host.
2105  } else if(const simple_wml::node* scenario_diff = data.child("scenario_diff")) {
2106  if(!g.is_owner(p)) {
2107  return;
2108  }
2109 
2110  g.level().root().apply_diff(*scenario_diff);
2111  const simple_wml::node* cfg_change = scenario_diff->child("change_child");
2112 
2113  if(cfg_change) {
2114  g.update_side_data();
2115  }
2116 
2117  g.describe_slots();
2119 
2120  g.send_data(data, p);
2121  return;
2122  // If a player changes his faction.
2123  } else if(data.child("change_faction")) {
2124  g.send_data(data, p);
2125  return;
2126  // If the owner of a side is changing the controller.
2127  } else if(const simple_wml::node* change = data.child("change_controller")) {
2128  g.transfer_side_control(p, *change);
2129  g.describe_slots();
2131 
2132  return;
2133  // If all observers should be muted. (toggles)
2134  } else if(data.child("muteall")) {
2135  if(!g.is_owner(p)) {
2136  g.send_server_message("You cannot mute: not the game host.", p);
2137  return;
2138  }
2139 
2140  g.mute_all_observers();
2141  return;
2142  // If an observer should be muted.
2143  } else if(const simple_wml::node* mute = data.child("mute")) {
2144  g.mute_observer(*mute, p);
2145  return;
2146  // If an observer should be unmuted.
2147  } else if(const simple_wml::node* unmute = data.child("unmute")) {
2148  g.unmute_observer(*unmute, p);
2149  return;
2150  // The owner is kicking/banning someone from the game.
2151  } else if(data.child("kick") || data.child("ban")) {
2152  bool ban = (data.child("ban") != nullptr);
2153  auto user { ban
2154  ? g.ban_user(*data.child("ban"), p)
2155  : g.kick_member(*data.child("kick"), p)};
2156 
2157  if(user) {
2158  player_connections_.modify(*user, std::bind(&player_record::enter_lobby, std::placeholders::_1));
2159  g.describe_slots();
2160 
2161  update_game_in_lobby(g, user);
2162 
2163  // Send all other players in the lobby the update to the gamelist.
2164  simple_wml::document gamelist_diff;
2165  make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), gamelist_diff);
2166  make_change_diff(games_and_users_list_.root(), nullptr, "user", (*user)->info().config_address(), gamelist_diff);
2167 
2168  send_to_lobby(gamelist_diff, p);
2169 
2170  // Send the removed user the lobby game list.
2172  }
2173 
2174  return;
2175  } else if(const simple_wml::node* unban = data.child("unban")) {
2176  g.unban_user(*unban, p);
2177  return;
2178  // If info is being provided about the game state.
2179  } else if(const simple_wml::node* info = data.child("info")) {
2180  if(!g.is_player(p)) {
2181  return;
2182  }
2183 
2184  if((*info)["type"] == "termination") {
2185  g.set_termination_reason((*info)["condition"].to_string());
2186  if((*info)["condition"].to_string() == "out of sync") {
2187  g.send_and_record_server_message(player.name() + " reports out of sync errors.");
2188  if(user_handler_){
2189  user_handler_->db_set_oos_flag(uuid_, g.db_id());
2190  }
2191  }
2192  }
2193 
2194  return;
2195  } else if(data.child("turn")) {
2196  // Notify the game of the commands, and if it changes
2197  // the description, then sync the new description
2198  // to players in the lobby.
2199  g.process_turn(data, p);
2201 
2202  return;
2203  } else if(data.child("whiteboard")) {
2204  g.process_whiteboard(data, p);
2205  return;
2206  } else if(data.child("change_turns_wml")) {
2207  g.process_change_turns_wml(data, p);
2209  return;
2210  } else if(simple_wml::node* sch = data.child("request_choice")) {
2211  g.handle_choice(*sch, p);
2212  return;
2213  } else if(data.child("message")) {
2214  g.process_message(data, p);
2215  return;
2216  } else if(data.child("stop_updates")) {
2217  g.send_data(data, p);
2218  return;
2219  // Data to ignore.
2220  } else if(
2221  data.child("error") ||
2222  data.child("side_secured") ||
2223  data.root().has_attr("failed") ||
2224  data.root().has_attr("side")
2225  ) {
2226  return;
2227  }
2228 
2229  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
2230  << " in game: \"" << g.name() << "\" (" << g.id() << ", " << g.db_id() << ")\n"
2231  << data.output();
2232 }
2233 
2234 template<class SocketPtr> void server::send_server_message(SocketPtr socket, const std::string& message, const std::string& type)
2235 {
2236  simple_wml::document server_message;
2237  simple_wml::node& msg = server_message.root().add_child("message");
2238  msg.set_attr("sender", "server");
2239  msg.set_attr_esc("message", message);
2240  msg.set_attr_dup("type", type.c_str());
2241 
2242  async_send_doc_queued(socket, server_message);
2243 }
2244 
2246 {
2247  utils::visit([](auto&& socket) {
2248  if constexpr (utils::decayed_is_same<tls_socket_ptr, decltype(socket)>) {
2249  socket->async_shutdown([socket](...) {});
2250  const char buffer[] = "";
2251  async_write(*socket, boost::asio::buffer(buffer), [socket](...) { socket->lowest_layer().close(); });
2252  } else {
2253  socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
2254  }
2255  }, player->socket());
2256 }
2257 
2259 {
2260  std::string ip = iter->client_ip();
2261 
2262  const std::shared_ptr<game> g = iter->get_game();
2263  bool game_ended = false;
2264  if(g) {
2265  game_ended = g->remove_player(iter, true, false);
2266  }
2267 
2269  const std::size_t index =
2270  std::distance(users.begin(), std::find(users.begin(), users.end(), iter->info().config_address()));
2271 
2272  // Notify other players in lobby
2273  simple_wml::document diff;
2274  if(make_delete_diff(games_and_users_list_.root(), nullptr, "user", iter->info().config_address(), diff)) {
2275  send_to_lobby(diff, iter);
2276  }
2277 
2279 
2280  LOG_SERVER << ip << "\t" << iter->info().name() << "\thas logged off";
2281 
2282  // Find the matching nick-ip pair in the log and update the sign off time
2283  if(user_handler_) {
2284  user_handler_->db_update_logout(iter->info().get_login_id());
2285  } else {
2286  connection_log ip_name { iter->info().name(), ip, {} };
2287 
2288  auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name);
2289  if(i != ip_log_.end()) {
2290  i->log_off = std::chrono::system_clock::now();
2291  }
2292  }
2293 
2294  for(auto& [id, queue] : queue_info_) {
2295  if(!queue.players_in_queue.empty()) {
2296  std::vector<std::string>& p_queue = queue.players_in_queue;
2297  p_queue.erase(std::remove(p_queue.begin(), p_queue.end(), iter->info().name()), p_queue.end());
2298  }
2299  send_queue_update(queue, iter);
2300  }
2301 
2302  player_connections_.erase(iter);
2303 
2304  if(lan_server_ > 0s && player_connections_.size() == 0)
2306 
2307  if(game_ended) delete_game(g->id());
2308 }
2309 
2310 void server::send_to_lobby(simple_wml::document& data, utils::optional<player_iterator> exclude)
2311 {
2312  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
2313  auto player { player_connections_.iterator_to(p) };
2314  if(player != exclude) {
2316  }
2317  }
2318 }
2319 
2320 void server::send_server_message_to_lobby(const std::string& message, utils::optional<player_iterator> exclude)
2321 {
2322  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
2323  auto player { player_connections_.iterator_to(p) };
2324  if(player != exclude) {
2325  send_server_message(player, message, "alert");
2326  }
2327  }
2328 }
2329 
2330 void server::send_server_message_to_all(const std::string& message, utils::optional<player_iterator> exclude)
2331 {
2332  for(auto player = player_connections_.begin(); player != player_connections_.end(); ++player) {
2333  if(player != exclude) {
2334  send_server_message(player, message, "alert");
2335  }
2336  }
2337 }
2338 
2340 {
2341  if(restart_command.empty()) {
2342  return;
2343  }
2344 
2345  // Example config line:
2346  // restart_command="./wesnothd-debug -d -c ~/.wesnoth1.5/server.cfg"
2347  // remember to make new one as a daemon or it will block old one
2348  if(std::system(restart_command.c_str())) {
2349  ERR_SERVER << "Failed to start new server with command: " << restart_command;
2350  } else {
2351  LOG_SERVER << "New server started with command: " << restart_command;
2352  }
2353 }
2354 
2355 std::string server::process_command(std::string query, std::string issuer_name)
2356 {
2357  boost::trim(query);
2358 
2359  if(issuer_name == "*socket*" && !query.empty() && query.at(0) == '+') {
2360  // The first argument might be "+<issuer>: ".
2361  // In that case we use +<issuer>+ as the issuer_name.
2362  // (Mostly used for communication with IRC.)
2363  auto issuer_end = std::find(query.begin(), query.end(), ':');
2364 
2365  std::string issuer(query.begin() + 1, issuer_end);
2366  if(!issuer.empty()) {
2367  issuer_name = "+" + issuer + "+";
2368  query = std::string(issuer_end + 1, query.end());
2369  boost::trim(query);
2370  }
2371  }
2372 
2373  const auto i = std::find(query.begin(), query.end(), ' ');
2374 
2375  try {
2376  const std::string command = utf8::lowercase(std::string(query.begin(), i));
2377 
2378  std::string parameters = (i == query.end() ? "" : std::string(i + 1, query.end()));
2379  boost::trim(parameters);
2380 
2381  std::ostringstream out;
2382  auto handler_itor = cmd_handlers_.find(command);
2383 
2384  if(handler_itor == cmd_handlers_.end()) {
2385  out << "Command '" << command << "' is not recognized.\n" << help_msg;
2386  } else {
2387  const cmd_handler& handler = handler_itor->second;
2388  try {
2389  handler(issuer_name, query, parameters, &out);
2390  } catch(const std::bad_function_call& ex) {
2391  ERR_SERVER << "While handling a command '" << command
2392  << "', caught a std::bad_function_call exception.";
2393  ERR_SERVER << ex.what();
2394  out << "An internal server error occurred (std::bad_function_call) while executing '" << command
2395  << "'\n";
2396  }
2397  }
2398 
2399  return out.str();
2400 
2401  } catch(const utf8::invalid_utf8_exception& e) {
2402  std::string msg = "While handling a command, caught an invalid utf8 exception: ";
2403  msg += e.what();
2404  ERR_SERVER << msg;
2405  return (msg + '\n');
2406  }
2407 }
2408 
2409 // Shutdown, restart and sample commands can only be issued via the socket.
2411  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2412 {
2413  assert(out != nullptr);
2414 
2415  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2416  *out << denied_msg;
2417  return;
2418  }
2419 
2420  if(parameters == "now") {
2421  BOOST_THROW_EXCEPTION(server_shutdown("shut down by admin command"));
2422  } else {
2423  // Graceful shut down.
2424  graceful_restart = true;
2425  acceptor_v6_.close();
2426  acceptor_v4_.close();
2427 
2428  timer_.expires_after(10s);
2429  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2430 
2432  "msg The server is shutting down. You may finish your games but can't start new ones. Once all "
2433  "games have ended the server will exit.",
2434  issuer_name
2435  );
2436 
2437  *out << "Server is doing graceful shut down.";
2438  }
2439 }
2440 
2441 void server::restart_handler(const std::string& issuer_name,
2442  const std::string& /*query*/,
2443  std::string& /*parameters*/,
2444  std::ostringstream* out)
2445 {
2446  assert(out != nullptr);
2447 
2448  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2449  *out << denied_msg;
2450  return;
2451  }
2452 
2453  if(restart_command.empty()) {
2454  *out << "No restart_command configured! Not restarting.";
2455  } else {
2456  graceful_restart = true;
2457  acceptor_v6_.close();
2458  acceptor_v4_.close();
2459  timer_.expires_after(10s);
2460  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2461 
2462  start_new_server();
2463 
2465  "msg The server has been restarted. You may finish current games but can't start new ones and "
2466  "new players can't join this (old) server instance. (So if a player of your game disconnects "
2467  "you have to save, reconnect and reload the game on the new server instance. It is actually "
2468  "recommended to do that right away.)",
2469  issuer_name
2470  );
2471 
2472  *out << "New server started.";
2473  }
2474 }
2475 
2477  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2478 {
2479  assert(out != nullptr);
2480 
2481  if(parameters.empty()) {
2482  *out << "Current sample frequency: " << request_sample_frequency;
2483  return;
2484  } else if(issuer_name != "*socket*") {
2485  *out << denied_msg;
2486  return;
2487  }
2488 
2489  request_sample_frequency = utils::from_chars<int>(parameters).value_or(0);
2490  if(request_sample_frequency <= 0) {
2491  *out << "Sampling turned off.";
2492  } else {
2493  *out << "Sampling every " << request_sample_frequency << " requests.";
2494  }
2495 }
2496 
2497 void server::help_handler(const std::string& /*issuer_name*/,
2498  const std::string& /*query*/,
2499  std::string& /*parameters*/,
2500  std::ostringstream* out)
2501 {
2502  assert(out != nullptr);
2503  *out << help_msg;
2504 }
2505 
2506 void server::stats_handler(const std::string& /*issuer_name*/,
2507  const std::string& /*query*/,
2508  std::string& /*parameters*/,
2509  std::ostringstream* out)
2510 {
2511  assert(out != nullptr);
2512 
2513  *out << "Number of games = " << games().size() << "\nTotal number of users = " << player_connections_.size();
2514 }
2515 
2516 void server::metrics_handler(const std::string& /*issuer_name*/,
2517  const std::string& /*query*/,
2518  std::string& /*parameters*/,
2519  std::ostringstream* out)
2520 {
2521  assert(out != nullptr);
2522  *out << metrics_;
2523 }
2524 
2525 void server::requests_handler(const std::string& /*issuer_name*/,
2526  const std::string& /*query*/,
2527  std::string& /*parameters*/,
2528  std::ostringstream* out)
2529 {
2530  assert(out != nullptr);
2531  metrics_.requests(*out);
2532 }
2533 
2534 void server::roll_handler(const std::string& issuer_name,
2535  const std::string& /*query*/,
2536  std::string& parameters,
2537  std::ostringstream* out)
2538 {
2539  assert(out != nullptr);
2540  if(parameters.empty()) {
2541  return;
2542  }
2543 
2544  int N;
2545  try {
2546  N = std::stoi(parameters);
2547  } catch(const std::invalid_argument&) {
2548  *out << "The number of die sides must be a number!";
2549  return;
2550  } catch(const std::out_of_range&) {
2551  *out << "The number of sides is too big for the die!";
2552  return;
2553  }
2554 
2555  if(N < 1) {
2556  *out << "The die cannot have less than 1 side!";
2557  return;
2558  }
2559  std::uniform_int_distribution<int> dice_distro(1, N);
2560  std::string value = std::to_string(dice_distro(die_));
2561 
2562  *out << "You rolled a die [1 - " + parameters + "] and got a " + value + ".";
2563 
2564  auto player_ptr = player_connections_.get<name_t>().find(issuer_name);
2565  if(player_ptr == player_connections_.get<name_t>().end()) {
2566  return;
2567  }
2568 
2569  auto g_ptr = player_ptr->get_game();
2570  if(g_ptr) {
2571  g_ptr->send_server_message_to_all(issuer_name + " rolled a die [1 - " + parameters + "] and got a " + value + ".", player_connections_.project<0>(player_ptr));
2572  } else {
2573  *out << " (The result is shown to others only in a game.)";
2574  }
2575 }
2576 
2577 void server::games_handler(const std::string& /*issuer_name*/,
2578  const std::string& /*query*/,
2579  std::string& /*parameters*/,
2580  std::ostringstream* out)
2581 {
2582  assert(out != nullptr);
2583  metrics_.games(*out);
2584 }
2585 
2586 void server::wml_handler(const std::string& /*issuer_name*/,
2587  const std::string& /*query*/,
2588  std::string& /*parameters*/,
2589  std::ostringstream* out)
2590 {
2591  assert(out != nullptr);
2592  *out << simple_wml::document::stats();
2593 }
2594 
2596  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2597 {
2598  assert(out != nullptr);
2599 
2600  if(parameters.empty()) {
2601  *out << "You must type a message.";
2602  return;
2603  }
2604 
2605  const std::string& sender = issuer_name;
2606  const std::string& message = parameters;
2607  LOG_SERVER << "Admin message: <" << sender
2608  << (message.find("/me ") == 0 ? std::string(message.begin() + 3, message.end()) + ">" : "> " + message);
2609 
2611  simple_wml::node& msg = data.root().add_child("whisper");
2612  msg.set_attr_dup("sender", ("admin message from " + sender).c_str());
2613  msg.set_attr_dup("message", message.c_str());
2614 
2615  int n = 0;
2616  for(const auto& player : player_connections_) {
2617  if(player.info().is_moderator()) {
2618  ++n;
2620  }
2621  }
2622 
2623  bool is_admin = false;
2624 
2625  for(const auto& player : player_connections_) {
2626  if(issuer_name == player.info().name() && player.info().is_moderator()) {
2627  is_admin = true;
2628  break;
2629  }
2630  }
2631 
2632  if(!is_admin) {
2633  *out << "Your report has been logged and sent to the server administrators. Thanks!";
2634  return;
2635  }
2636 
2637  *out << "Your report has been logged and sent to " << n << " online administrators. Thanks!";
2638 }
2639 
2641  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2642 {
2643  assert(out != nullptr);
2644 
2645  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2646  if(first_space == parameters.end()) {
2647  *out << "You must name a receiver.";
2648  return;
2649  }
2650 
2651  const std::string& sender = issuer_name;
2652  const std::string receiver(parameters.begin(), first_space);
2653 
2654  std::string message(first_space + 1, parameters.end());
2655  boost::trim(message);
2656 
2657  if(message.empty()) {
2658  *out << "You must type a message.";
2659  return;
2660  }
2661 
2663  simple_wml::node& msg = data.root().add_child("whisper");
2664 
2665  // This string is parsed by the client!
2666  msg.set_attr_dup("sender", ("server message from " + sender).c_str());
2667  msg.set_attr_dup("message", message.c_str());
2668 
2669  for(const auto& player : player_connections_) {
2670  if(receiver != player.info().name().c_str()) {
2671  continue;
2672  }
2673 
2675  *out << "Message to " << receiver << " successfully sent.";
2676  return;
2677  }
2678 
2679  *out << "No such nick: " << receiver;
2680 }
2681 
2682 void server::msg_handler(const std::string& /*issuer_name*/,
2683  const std::string& /*query*/,
2684  std::string& parameters,
2685  std::ostringstream* out)
2686 {
2687  assert(out != nullptr);
2688 
2689  if(parameters.empty()) {
2690  *out << "You must type a message.";
2691  return;
2692  }
2693 
2694  send_server_message_to_all(parameters);
2695 
2696  LOG_SERVER << "<server"
2697  << (parameters.find("/me ") == 0
2698  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2699  : "> " + parameters);
2700 
2701  *out << "message '" << parameters << "' relayed to players";
2702 }
2703 
2704 void server::lobbymsg_handler(const std::string& /*issuer_name*/,
2705  const std::string& /*query*/,
2706  std::string& parameters,
2707  std::ostringstream* out)
2708 {
2709  assert(out != nullptr);
2710 
2711  if(parameters.empty()) {
2712  *out << "You must type a message.";
2713  return;
2714  }
2715 
2716  send_server_message_to_lobby(parameters);
2717  LOG_SERVER << "<server"
2718  << (parameters.find("/me ") == 0
2719  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2720  : "> " + parameters);
2721 
2722  *out << "message '" << parameters << "' relayed to players";
2723 }
2724 
2726  const std::string& /*issuer_name*/, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2727 {
2728  assert(out != nullptr);
2729 
2730  if(parameters.empty()) {
2731  *out << "Server version is " << game_config::wesnoth_version.str();
2732  return;
2733  }
2734 
2735  for(const auto& player : player_connections_) {
2736  if(parameters == player.info().name()) {
2737  *out << "Player " << parameters << " is using wesnoth " << player.info().version();
2738  return;
2739  }
2740  }
2741 
2742  *out << "Player '" << parameters << "' not found.";
2743 }
2744 
2746  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2747 {
2748  assert(out != nullptr);
2749 
2750  *out << "STATUS REPORT for '" << parameters << "'";
2751  bool found_something = false;
2752 
2753  // If a simple username is given we'll check for its IP instead.
2754  if(utils::isvalid_username(parameters)) {
2755  for(const auto& player : player_connections_) {
2756  if(parameters == player.name()) {
2757  parameters = player.client_ip();
2758  found_something = true;
2759  break;
2760  }
2761  }
2762 
2763  if(!found_something) {
2764  // out << "\nNo match found. You may want to check with 'searchlog'.";
2765  // return out.str();
2766  *out << process_command("searchlog " + parameters, issuer_name);
2767  return;
2768  }
2769  }
2770 
2771  const bool match_ip = ((std::count(parameters.begin(), parameters.end(), '.') >= 1) || (std::count(parameters.begin(), parameters.end(), ':') >= 1));
2772  for(const auto& player : player_connections_) {
2773  if(parameters.empty() || parameters == "*" ||
2774  (match_ip && utils::wildcard_string_match(player.client_ip(), parameters)) ||
2775  (!match_ip && utils::wildcard_string_match(utf8::lowercase(player.info().name()), utf8::lowercase(parameters)))
2776  ) {
2777  found_something = true;
2778  *out << std::endl << player_status(player);
2779  }
2780  }
2781 
2782  if(!found_something) {
2783  *out << "\nNo match found. You may want to check with 'searchlog'.";
2784  }
2785 }
2786 
2787 void server::clones_handler(const std::string& /*issuer_name*/,
2788  const std::string& /*query*/,
2789  std::string& /*parameters*/,
2790  std::ostringstream* out)
2791 {
2792  assert(out != nullptr);
2793  *out << "CLONES STATUS REPORT";
2794 
2795  std::set<std::string> clones;
2796 
2797  for(auto it = player_connections_.begin(); it != player_connections_.end(); ++it) {
2798  if(clones.find(it->client_ip()) != clones.end()) {
2799  continue;
2800  }
2801 
2802  bool found = false;
2803  for(auto clone = std::next(it); clone != player_connections_.end(); ++clone) {
2804  if(it->client_ip() == clone->client_ip()) {
2805  if(!found) {
2806  found = true;
2807  clones.insert(it->client_ip());
2808  *out << std::endl << player_status(*it);
2809  }
2810 
2811  *out << std::endl << player_status(*clone);
2812  }
2813  }
2814  }
2815 
2816  if(clones.empty()) {
2817  *out << std::endl << "No clones found.";
2818  }
2819 }
2820 
2821 void server::bans_handler(const std::string& /*issuer_name*/,
2822  const std::string& /*query*/,
2823  std::string& parameters,
2824  std::ostringstream* out)
2825 {
2826  assert(out != nullptr);
2827 
2828  try {
2829  if(parameters.empty()) {
2830  ban_manager_.list_bans(*out);
2831  } else if(utf8::lowercase(parameters) == "deleted") {
2833  } else if(utf8::lowercase(parameters).find("deleted") == 0) {
2834  std::string mask = parameters.substr(7);
2835  ban_manager_.list_deleted_bans(*out, boost::trim_copy(mask));
2836  } else {
2837  boost::trim(parameters);
2838  ban_manager_.list_bans(*out, parameters);
2839  }
2840 
2841  } catch(const utf8::invalid_utf8_exception& e) {
2842  ERR_SERVER << "While handling bans, caught an invalid utf8 exception: " << e.what();
2843  }
2844 }
2845 
2847  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2848 {
2849  assert(out != nullptr);
2850 
2851  bool banned = false;
2852  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2853 
2854  if(first_space == parameters.end()) {
2855  *out << ban_manager_.get_ban_help();
2856  return;
2857  }
2858 
2859  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2860  const std::string target(parameters.begin(), first_space);
2861  const std::string duration(first_space + 1, second_space);
2862  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
2863 
2864  if(!success) {
2865  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2866  return;
2867  }
2868 
2869  if(second_space == parameters.end()) {
2870  --second_space;
2871  }
2872 
2873  std::string reason(second_space + 1, parameters.end());
2874  boost::trim(reason);
2875 
2876  if(reason.empty()) {
2877  *out << "You need to give a reason for the ban.";
2878  return;
2879  }
2880 
2881  std::string dummy_group;
2882 
2883  // if we find a '.' consider it an ip mask
2884  /** @todo FIXME: make a proper check for valid IPs. */
2885  if(std::count(target.begin(), target.end(), '.') >= 1) {
2886  banned = true;
2887 
2888  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2889  } else {
2890  for(const auto& player : player_connections_) {
2891  if(utils::wildcard_string_match(player.info().name(), target)) {
2892  if(banned) {
2893  *out << "\n";
2894  } else {
2895  banned = true;
2896  }
2897 
2898  const std::string ip = player.client_ip();
2899  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2900  }
2901  }
2902 
2903  if(!banned) {
2904  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2905  }
2906  }
2907 }
2908 
2910  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2911 {
2912  assert(out != nullptr);
2913 
2914  bool banned = false;
2915  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2916  if(first_space == parameters.end()) {
2917  *out << ban_manager_.get_ban_help();
2918  return;
2919  }
2920 
2921  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2922  const std::string target(parameters.begin(), first_space);
2923  const std::string duration(first_space + 1, second_space);
2924  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
2925 
2926  if(!success) {
2927  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2928  return;
2929  }
2930 
2931  if(second_space == parameters.end()) {
2932  --second_space;
2933  }
2934 
2935  std::string reason(second_space + 1, parameters.end());
2936  boost::trim(reason);
2937 
2938  if(reason.empty()) {
2939  *out << "You need to give a reason for the ban.";
2940  return;
2941  }
2942 
2943  std::string dummy_group;
2944  std::vector<player_iterator> users_to_kick;
2945 
2946  // if we find a '.' consider it an ip mask
2947  /** @todo FIXME: make a proper check for valid IPs. */
2948  if(std::count(target.begin(), target.end(), '.') >= 1) {
2949  banned = true;
2950 
2951  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2952 
2954  if(utils::wildcard_string_match(player->client_ip(), target)) {
2955  users_to_kick.push_back(player);
2956  }
2957  }
2958  } else {
2960  if(utils::wildcard_string_match(player->info().name(), target)) {
2961  if(banned) {
2962  *out << "\n";
2963  } else {
2964  banned = true;
2965  }
2966 
2967  const std::string ip = player->client_ip();
2968  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2969  users_to_kick.push_back(player);
2970  }
2971  }
2972 
2973  if(!banned) {
2974  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2975  }
2976  }
2977 
2978  for(auto user : users_to_kick) {
2979  *out << "\nKicked " << user->info().name() << " (" << user->client_ip() << ").";
2980  utils::visit([this,reason](auto&& socket) { async_send_error(socket, "You have been banned. Reason: " + reason); }, user->socket());
2981  disconnect_player(user);
2982  }
2983 }
2984 
2986  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2987 {
2988  assert(out != nullptr);
2989 
2990  bool banned = false;
2991  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2992  if(first_space == parameters.end()) {
2993  *out << ban_manager_.get_ban_help();
2994  return;
2995  }
2996 
2997  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2998  const std::string target(parameters.begin(), first_space);
2999 
3000  std::string group = std::string(first_space + 1, second_space);
3001  first_space = second_space;
3002  second_space = std::find(first_space + 1, parameters.end(), ' ');
3003 
3004  const std::string duration(first_space + 1, second_space);
3005  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
3006 
3007  if(!success) {
3008  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
3009  return;
3010  }
3011 
3012  if(second_space == parameters.end()) {
3013  --second_space;
3014  }
3015 
3016  std::string reason(second_space + 1, parameters.end());
3017  boost::trim(reason);
3018 
3019  if(reason.empty()) {
3020  *out << "You need to give a reason for the ban.";
3021  return;
3022  }
3023 
3024  // if we find a '.' consider it an ip mask
3025  /** @todo FIXME: make a proper check for valid IPs. */
3026  if(std::count(target.begin(), target.end(), '.') >= 1) {
3027  banned = true;
3028 
3029  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, group);
3030  } else {
3031  for(const auto& player : player_connections_) {
3032  if(utils::wildcard_string_match(player.info().name(), target)) {
3033  if(banned) {
3034  *out << "\n";
3035  } else {
3036  banned = true;
3037  }
3038 
3039  const std::string ip = player.client_ip();
3040  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, group, target);
3041  }
3042  }
3043 
3044  if(!banned) {
3045  *out << "Nickname mask '" << target << "' did not match, no bans set.";
3046  }
3047  }
3048 }
3049 
3050 void server::unban_handler(const std::string& /*issuer_name*/,
3051  const std::string& /*query*/,
3052  std::string& parameters,
3053  std::ostringstream* out)
3054 {
3055  assert(out != nullptr);
3056 
3057  if(parameters.empty()) {
3058  *out << "You must enter an ipmask to unban.";
3059  return;
3060  }
3061 
3062  ban_manager_.unban(*out, parameters);
3063 }
3064 
3065 void server::ungban_handler(const std::string& /*issuer_name*/,
3066  const std::string& /*query*/,
3067  std::string& parameters,
3068  std::ostringstream* out)
3069 {
3070  assert(out != nullptr);
3071 
3072  if(parameters.empty()) {
3073  *out << "You must enter an ipmask to ungban.";
3074  return;
3075  }
3076 
3077  ban_manager_.unban_group(*out, parameters);
3078 }
3079 
3080 void server::kick_handler(const std::string& /*issuer_name*/,
3081  const std::string& /*query*/,
3082  std::string& parameters,
3083  std::ostringstream* out)
3084 {
3085  assert(out != nullptr);
3086 
3087  if(parameters.empty()) {
3088  *out << "You must enter a mask to kick.";
3089  return;
3090  }
3091 
3092  auto i = std::find(parameters.begin(), parameters.end(), ' ');
3093  const std::string kick_mask = std::string(parameters.begin(), i);
3094  const std::string kick_message = (i == parameters.end()
3095  ? "You have been kicked."
3096  : "You have been kicked. Reason: " + std::string(i + 1, parameters.end()));
3097 
3098  bool kicked = false;
3099 
3100  // if we find a '.' consider it an ip mask
3101  const bool match_ip = (std::count(kick_mask.begin(), kick_mask.end(), '.') >= 1);
3102 
3103  std::vector<player_iterator> users_to_kick;
3105  if((match_ip && utils::wildcard_string_match(player->client_ip(), kick_mask)) ||
3106  (!match_ip && utils::wildcard_string_match(player->info().name(), kick_mask))
3107  ) {
3108  users_to_kick.push_back(player);
3109  }
3110  }
3111 
3112  for(const auto& player : users_to_kick) {
3113  if(kicked) {
3114  *out << "\n";
3115  } else {
3116  kicked = true;
3117  }
3118 
3119  *out << "Kicked " << player->name() << " (" << player->client_ip() << "). '"
3120  << kick_message << "'";
3121 
3122  utils::visit([this, &kick_message](auto&& socket) { async_send_error(socket, kick_message); }, player->socket());
3124  }
3125 
3126  if(!kicked) {
3127  *out << "No user matched '" << kick_mask << "'.";
3128  }
3129 }
3130 
3131 void server::motd_handler(const std::string& /*issuer_name*/,
3132  const std::string& /*query*/,
3133  std::string& parameters,
3134  std::ostringstream* out)
3135 {
3136  assert(out != nullptr);
3137 
3138  if(parameters.empty()) {
3139  if(!motd_.empty()) {
3140  *out << "Message of the day:\n" << motd_;
3141  return;
3142  } else {
3143  *out << "No message of the day set.";
3144  return;
3145  }
3146  }
3147 
3148  motd_ = parameters;
3149  *out << "Message of the day set to: " << motd_;
3150 }
3151 
3152 void server::searchlog_handler(const std::string& /*issuer_name*/,
3153  const std::string& /*query*/,
3154  std::string& parameters,
3155  std::ostringstream* out)
3156 {
3157  assert(out != nullptr);
3158 
3159  if(parameters.empty()) {
3160  *out << "You must enter a mask to search for.";
3161  return;
3162  }
3163 
3164  *out << "IP/NICK LOG for '" << parameters << "'";
3165 
3166  // If this looks like an IP look up which nicks have been connected from it
3167  // Otherwise look for the last IP the nick used to connect
3168  const bool match_ip = (std::count(parameters.begin(), parameters.end(), '.') >= 1);
3169 
3170  if(!user_handler_) {
3171  bool found_something = false;
3172 
3173  for(const auto& i : ip_log_) {
3174  const std::string& username = i.nick;
3175  const std::string& ip = i.ip;
3176 
3177  if((match_ip && utils::wildcard_string_match(ip, parameters)) ||
3178  (!match_ip && utils::wildcard_string_match(utf8::lowercase(username), utf8::lowercase(parameters)))
3179  ) {
3180  found_something = true;
3181  auto player = player_connections_.get<name_t>().find(username);
3182 
3183  if(player != player_connections_.get<name_t>().end() && player->client_ip() == ip) {
3184  *out << std::endl << player_status(*player);
3185  } else {
3186  *out << "\n'" << username << "' @ " << ip
3187  << " last seen: " << chrono::format_local_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
3188  }
3189  }
3190  }
3191 
3192  if(!found_something) {
3193  *out << "\nNo match found.";
3194  }
3195  } else {
3196  if(!match_ip) {
3197  utils::to_sql_wildcards(parameters);
3198  user_handler_->get_ips_for_user(parameters, out);
3199  } else {
3200  user_handler_->get_users_for_ip(parameters, out);
3201  }
3202  }
3203 }
3204 
3205 void server::dul_handler(const std::string& /*issuer_name*/,
3206  const std::string& /*query*/,
3207  std::string& parameters,
3208  std::ostringstream* out)
3209 {
3210  assert(out != nullptr);
3211 
3212  try {
3213  if(parameters.empty()) {
3214  *out << "Unregistered login is " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
3215  } else {
3216  deny_unregistered_login_ = (utf8::lowercase(parameters) == "yes");
3217  *out << "Unregistered login is now " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
3218  }
3219 
3220  } catch(const utf8::invalid_utf8_exception& e) {
3221  ERR_SERVER << "While handling dul (deny unregistered logins), caught an invalid utf8 exception: " << e.what();
3222  }
3223 }
3224 
3225 void server::stopgame_handler(const std::string& /*issuer_name*/,
3226  const std::string& /*query*/,
3227  std::string& parameters,
3228  std::ostringstream* out)
3229 {
3230  assert(out != nullptr);
3231 
3232  const std::string nick = parameters.substr(0, parameters.find(' '));
3233  const std::string reason = parameters.length() > nick.length()+1 ? parameters.substr(nick.length()+1) : "";
3234  auto player = player_connections_.get<name_t>().find(nick);
3235 
3236  if(player != player_connections_.get<name_t>().end()){
3237  std::shared_ptr<game> g = player->get_game();
3238  if(g){
3239  *out << "Player '" << nick << "' is in game with id '" << g->id() << ", " << g->db_id() << "' named '" << g->name() << "'. Ending game for reason: '" << reason << "'...";
3240  delete_game(g->id(), reason);
3241  } else {
3242  *out << "Player '" << nick << "' is not currently in a game.";
3243  }
3244  } else {
3245  *out << "Player '" << nick << "' is not currently logged in.";
3246  }
3247 }
3248 
3249 void server::reset_queues_handler(const std::string& /*issuer_name*/,
3250  const std::string& /*query*/,
3251  std::string& /*parameters*/,
3252  std::ostringstream* out)
3253 {
3254  assert(out != nullptr);
3255 
3257  player->info().clear_queues();
3258  }
3259 
3260  for(auto& [id, queue] : queue_info_) {
3261  queue.players_in_queue.clear();
3262  send_queue_update(queue);
3263  }
3264 
3265  *out << "Reset all queues";
3266 }
3267 
3268 void server::delete_game(int gameid, const std::string& reason)
3269 {
3270  // Set the availability status for all quitting users.
3271  auto range_pair = player_connections_.get<game_t>().equal_range(gameid);
3272 
3273  // Make a copy of the iterators so that we can change them while iterating over them.
3274  // We can use pair::first_type since equal_range returns a pair of iterators.
3275  std::vector<decltype(range_pair)::first_type> range_vctor;
3276 
3277  for(auto it = range_pair.first; it != range_pair.second; ++it) {
3278  range_vctor.push_back(it);
3279  it->info().mark_available();
3280 
3281  simple_wml::document udiff;
3282  if(make_change_diff(games_and_users_list_.root(), nullptr, "user", it->info().config_address(), udiff)) {
3283  send_to_lobby(udiff);
3284  } else {
3285  ERR_SERVER << "ERROR: delete_game(): Could not find user in players_.";
3286  }
3287  }
3288 
3289  // Put the remaining users back in the lobby.
3290  // This will call cleanup_game() deleter since there won't
3291  // be any references to that game from player_connections_ anymore
3292  for(const auto& it : range_vctor) {
3293  player_connections_.get<game_t>().modify(it, std::bind(&player_record::enter_lobby, std::placeholders::_1));
3294  }
3295 
3296  // send users in the game a notification to leave the game since it has ended
3297  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
3298 
3299  for(const auto& it : range_vctor) {
3300  player_iterator p { player_connections_.project<0>(it) };
3301  if(reason != "") {
3302  simple_wml::document leave_game_doc_reason("[leave_game]\n[/leave_game]\n", simple_wml::INIT_STATIC);
3303  leave_game_doc_reason.child("leave_game")->set_attr_dup("reason", reason.c_str());
3304  send_to_player(p, leave_game_doc_reason);
3305  } else {
3306  send_to_player(p, leave_game_doc);
3307  }
3309  }
3310 }
3311 
3312 void server::update_game_in_lobby(wesnothd::game& g, utils::optional<player_iterator> exclude)
3313 {
3314  simple_wml::document diff;
3315  if(auto p_desc = g.changed_description()) {
3316  if(make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", p_desc, diff)) {
3317  send_to_lobby(diff, exclude);
3318  }
3319  }
3320 }
3321 
3322 } // namespace wesnothd
3323 
3324 int main(int argc, char** argv)
3325 {
3326  int port = 15000;
3327  bool keep_alive = false;
3328 
3329  srand(static_cast<unsigned>(std::time(nullptr)));
3330 
3331  std::string config_file;
3332 
3333  // setting path to currentworking directory
3335 
3336  // show 'info' by default
3338  lg::timestamps(true);
3339 
3340  for(int arg = 1; arg != argc; ++arg) {
3341  const std::string val(argv[arg]);
3342  if(val.empty()) {
3343  continue;
3344  }
3345 
3346  if((val == "--config" || val == "-c") && arg + 1 != argc) {
3347  config_file = argv[++arg];
3348  } else if(val == "--verbose" || val == "-v") {
3350  } else if(val == "--dump-wml" || val == "-w") {
3351  dump_wml = true;
3352  } else if(val.substr(0, 6) == "--log-") {
3353  std::size_t p = val.find('=');
3354  if(p == std::string::npos) {
3355  PLAIN_LOG << "unknown option: " << val;
3356  return 2;
3357  }
3358 
3359  std::string s = val.substr(6, p - 6);
3361 
3362  if(s == "error") {
3364  } else if(s == "warning") {
3366  } else if(s == "info") {
3368  } else if(s == "debug") {
3370  } else {
3371  PLAIN_LOG << "unknown debug level: " << s;
3372  return 2;
3373  }
3374 
3375  while(p != std::string::npos) {
3376  std::size_t q = val.find(',', p + 1);
3377  s = val.substr(p + 1, q == std::string::npos ? q : q - (p + 1));
3378 
3380  PLAIN_LOG << "unknown debug domain: " << s;
3381  return 2;
3382  }
3383 
3384  p = q;
3385  }
3386  } else if((val == "--port" || val == "-p") && arg + 1 != argc) {
3387  port = utils::from_chars<int>(argv[++arg]).value_or(0);
3388  } else if(val == "--keepalive") {
3389  keep_alive = true;
3390  } else if(val == "--help" || val == "-h") {
3391  std::cout << "usage: " << argv[0]
3392  << " [-dvwV] [-c path] [-p port]\n"
3393  << " -c, --config <path> Tells wesnothd where to find the config file to use.\n"
3394  << " -d, --daemon Runs wesnothd as a daemon.\n"
3395  << " -h, --help Shows this usage message.\n"
3396  << " --log-<level>=<domain1>,<domain2>,...\n"
3397  << " sets the severity level of the debug domains.\n"
3398  << " 'all' can be used to match any debug domain.\n"
3399  << " Available levels: error, warning, info, debug.\n"
3400  << " -p, --port <port> Binds the server to the specified port.\n"
3401  << " --keepalive Enable TCP keepalive.\n"
3402  << " -v --verbose Turns on more verbose logging.\n"
3403  << " -V, --version Returns the server version.\n"
3404  << " -w, --dump-wml Print all WML sent to clients to stdout.\n";
3405  return 0;
3406  } else if(val == "--version" || val == "-V") {
3407  std::cout << "Battle for Wesnoth server " << game_config::wesnoth_version.str() << "\n";
3408  return 0;
3409  } else if(val == "--daemon" || val == "-d") {
3410 #ifdef _WIN32
3411  ERR_SERVER << "Running as a daemon is not supported on this platform";
3412  return -1;
3413 #else
3414  const pid_t pid = fork();
3415  if(pid < 0) {
3416  ERR_SERVER << "Could not fork and run as a daemon";
3417  return -1;
3418  } else if(pid > 0) {
3419  std::cout << "Started wesnothd as a daemon with process id " << pid << "\n";
3420  return 0;
3421  }
3422 
3423  setsid();
3424 #endif
3425  } else if(val == "--request_sample_frequency" && arg + 1 != argc) {
3426  wesnothd::request_sample_frequency = utils::from_chars<int>(argv[++arg]).value_or(0);
3427  } else {
3428  ERR_SERVER << "unknown option: " << val;
3429  return 2;
3430  }
3431  }
3432 
3433  return wesnothd::server(port, keep_alive, config_file).run();
3434 }
double g
Definition: astarsearch.cpp:63
int main(int argc, char **argv)
Definition: server.cpp:2206
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
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.
Definition: config.cpp:380
child_itors child_range(std::string_view key)
Definition: config.cpp:268
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
bool empty() const
Definition: config.cpp:823
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.
Definition: config.cpp:362
A class to handle the non-SQL logic for connecting to the phpbb forum database.
severity get_severity() const
Definition: log.hpp:219
std::ostream & requests(std::ostream &out) const
Definition: metrics.cpp:125
std::ostream & games(std::ostream &out) const
Definition: metrics.cpp:110
void game_terminated(const std::string &reason)
Definition: metrics.cpp:105
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:62
Base class for implementing servers that use gzipped-WML network protocol.
Definition: server_base.hpp:81
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.
void read_from_fifo()
void start_server()
Definition: server_base.cpp:78
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)
Definition: simple_wml.hpp:285
static std::string stats()
node * child(const char *name)
Definition: simple_wml.hpp:269
const string_span & attr(const char *key) const
Definition: simple_wml.hpp:132
void remove_child(const char *name, std::size_t index)
Definition: simple_wml.cpp:596
const child_list & children(const char *name) const
Definition: simple_wml.cpp:636
node & set_attr_int(const char *key, int value)
Definition: simple_wml.cpp:444
node * child(const char *name)
Definition: simple_wml.cpp:601
std::vector< node * > child_list
Definition: simple_wml.hpp:129
node & add_child(const char *name)
Definition: simple_wml.cpp:469
node & set_attr(const char *key, const char *value)
Definition: simple_wml.cpp:412
node & add_child_at(const char *name, std::size_t index)
Definition: simple_wml.cpp:450
void copy_into(node &n) const
Definition: simple_wml.cpp:812
node & set_attr_dup(const char *key, const char *value)
Definition: simple_wml.cpp:427
std::string to_string() const
Definition: simple_wml.cpp:184
const char * begin() const
Definition: simple_wml.hpp:94
const char * end() const
Definition: simple_wml.hpp:95
This class represents a single unit of a specific type.
Definition: unit.hpp:39
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_IP
IP address ban.
@ BAN_USER
User account/name ban.
@ BAN_NONE
Not a 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="")
Definition: ban.cpp:486
void list_bans(std::ostringstream &out, const std::string &mask="*")
Definition: ban.cpp:614
void unban(std::ostringstream &os, const std::string &ip, bool immediate_write=true)
Definition: ban.cpp:523
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 ...
Definition: ban.cpp:331
banned_ptr get_ban_info(const std::string &ip)
Definition: ban.cpp:654
void unban_group(std::ostringstream &os, const std::string &group)
Definition: ban.cpp:550
const std::string & get_ban_help() const
Definition: ban.hpp:188
void list_deleted_bans(std::ostringstream &out, const std::string &mask="*") const
Definition: ban.cpp:591
void load_config(const config &)
Definition: ban.cpp:692
static simple_wml::node * starting_pos(simple_wml::node &data)
The non-const version.
Definition: game.hpp:158
simple_wml::node * description() const
Definition: game.hpp:488
int db_id() const
This ID is not reused between scenarios of MP campaigns.
Definition: game.hpp:67
void emergency_cleanup()
Definition: game.hpp:618
int id() const
This ID is reused between scenarios of MP campaigns.
Definition: game.hpp:55
const std::string & termination_reason() const
Provides the reason the game was ended.
Definition: game.hpp:556
std::string get_replay_filename()
Definition: game.cpp:1793
void set_game(std::shared_ptr< game > new_game)
const std::shared_ptr< game > get_game() const
const std::string & version() const
Definition: player.hpp:56
const simple_wml::node * config_address() const
Definition: player.hpp:58
void set_moderator(bool moderator)
Definition: player.hpp:62
const std::set< int > & get_queues() const
Definition: player.hpp:70
const std::string & name() const
Definition: player.hpp:55
void clear_queues()
Definition: player.hpp:68
bool is_moderator() const
Definition: player.hpp:63
const std::string & source() const
Definition: player.hpp:57
utils::optional< server_base::login_ban_info > is_ip_banned(const std::string &ip)
Definition: server.cpp:655
void update_game_in_lobby(game &g, utils::optional< player_iterator > exclude={})
Definition: server.cpp:3312
void send_to_lobby(simple_wml::document &data, utils::optional< player_iterator > exclude={})
Definition: server.cpp:2310
int failed_login_limit_
Definition: server.hpp:202
void stopgame_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3225
std::unique_ptr< user_handler > user_handler_
Definition: server.hpp:159
void handle_leave_server_queue(player_iterator p, simple_wml::node &data)
Definition: server.cpp:1733
std::string uuid_
Definition: server.hpp:168
const std::string config_file_
Definition: server.hpp:170
boost::asio::steady_timer dummy_player_timer_
Definition: server.hpp:305
void handle_message(player_iterator player, simple_wml::node &message)
Definition: server.cpp:1448
void searchlog_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3152
std::string motd_
Definition: server.hpp:184
bool graceful_restart
Definition: server.hpp:192
std::mt19937 die_
Definition: server.hpp:161
std::vector< std::string > disallowed_names_
Definition: server.hpp:181
void clones_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2787
std::string information_
Definition: server.hpp:188
void setup_handlers()
Definition: server.cpp:384
void ban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2846
void start_dummy_player_updates()
Definition: server.cpp:690
void load_config(bool reload)
Parse the server config into local variables.
Definition: server.cpp:445
std::string input_path_
server socket/fifo.
Definition: server.hpp:165
void unban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3050
std::map< std::string, config > proxy_versions_
Definition: server.hpp:180
void handle_sighup(const boost::system::error_code &error, int signal_number)
Definition: server.cpp:289
void stats_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2506
std::chrono::seconds dummy_player_timer_interval_
Definition: server.hpp:306
std::string server_id_
Definition: server.hpp:186
void delete_game(int, const std::string &reason="")
Definition: server.cpp:3268
void refresh_tournaments(const boost::system::error_code &ec)
Definition: server.cpp:739
std::string recommended_version_
Definition: server.hpp:178
void gban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2985
void pm_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2640
std::function< void(const std::string &, const std::string &, std::string &, std::ostringstream *)> cmd_handler
Definition: server.hpp:260
void adminmsg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2595
bool save_replays_
Definition: server.hpp:197
void start_tournaments_timer()
Definition: server.cpp:733
std::string replay_save_path_
Definition: server.hpp:198
void handle_query(player_iterator player, simple_wml::node &query)
Definition: server.cpp:1327
void handle_player_in_game(player_iterator player, simple_wml::document &doc)
Definition: server.cpp:1755
std::string restart_command
Definition: server.hpp:194
void lobbymsg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2704
bool ip_exceeds_connection_limit(const std::string &ip) const
Definition: server.cpp:639
std::chrono::seconds failed_login_ban_
Definition: server.hpp:203
void handle_graceful_timeout(const boost::system::error_code &error)
Definition: server.cpp:302
void cleanup_game(game *)
Definition: server.cpp:1545
void handle_whisper(player_iterator player, simple_wml::node &whisper)
Definition: server.cpp:1287
metrics metrics_
Definition: server.hpp:216
void start_dump_stats()
Definition: server.cpp:672
bool 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)
Definition: server.cpp:916
void handle_new_client(socket_ptr socket)
Definition: server.cpp:751
void status_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2745
void send_queue_update(const queue_info &queue, utils::optional< player_iterator > exclude={})
Definition: server.cpp:1475
std::string announcements_
Definition: server.hpp:185
std::deque< connection_log > ip_log_
Definition: server.hpp:123
void bans_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2821
void kick_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3080
void dul_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3205
void handle_join_server_queue(player_iterator p, simple_wml::node &data)
Definition: server.cpp:1671
void handle_join_game(player_iterator player, simple_wml::node &join)
Definition: server.cpp:1579
std::deque< login_log >::size_type failed_login_buffer_size_
Definition: server.hpp:204
void wml_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2586
void send_server_message(SocketPtr socket, const std::string &message, const std::string &type)
Definition: server.cpp:2234
void handle_player(boost::asio::yield_context yield, SocketPtr socket, player_iterator player)
Definition: server.cpp:1164
player_connections player_connections_
Definition: server.hpp:218
simple_wml::document games_and_users_list_
Definition: server.hpp:214
void help_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2497
void requests_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2525
void dump_stats(const boost::system::error_code &ec)
Definition: server.cpp:678
std::string tournaments_
Definition: server.hpp:187
void start_new_server()
Definition: server.cpp:2339
void remove_player(player_iterator player)
Definition: server.cpp:2258
boost::asio::steady_timer timer_
Definition: server.hpp:297
randomness::mt_rng rng_
Definition: server.hpp:109
void handle_read_from_fifo(const boost::system::error_code &error, std::size_t bytes_transferred)
Definition: server.cpp:355
simple_wml::document login_response_
Definition: server.hpp:213
boost::asio::steady_timer tournaments_timer_
Definition: server.hpp:241
std::set< std::string > client_sources_
Definition: server.hpp:200
bool player_is_in_game(player_iterator player) const
Definition: server.hpp:103
void abort_lan_server_timer()
Definition: server.cpp:321
void handle_lan_server_shutdown(const boost::system::error_code &error)
Definition: server.cpp:326
bool authenticate(SocketPtr socket, const std::string &username, const std::string &password, bool name_taken, bool &registered)
Definition: server.cpp:1040
void reset_queues_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3249
std::string admin_passwd_
Definition: server.hpp:183
void motd_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3131
bool deny_unregistered_login_
Definition: server.hpp:196
std::vector< std::string > accepted_versions_
Definition: server.hpp:177
std::chrono::seconds lan_server_
Definition: server.hpp:193
void dummy_player_updates(const boost::system::error_code &ec)
Definition: server.cpp:696
void handle_create_game(player_iterator player, simple_wml::node &create_game)
Definition: server.cpp:1486
void send_server_message_to_lobby(const std::string &message, utils::optional< player_iterator > exclude={})
Definition: server.cpp:2320
void kickban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2909
void send_server_message_to_all(const std::string &message, utils::optional< player_iterator > exclude={})
Definition: server.cpp:2330
void handle_nickserv(player_iterator player, simple_wml::node &nickserv)
Definition: server.cpp:1405
std::string process_command(std::string cmd, std::string issuer_name)
Process commands from admins and users.
Definition: server.cpp:2355
std::map< int, queue_info > queue_info_
Definition: server.hpp:182
void login_client(boost::asio::yield_context yield, SocketPtr socket)
Definition: server.cpp:770
void handle_ping(player_iterator player, simple_wml::node &nickserv)
Definition: server.cpp:1430
std::deque< login_log > failed_logins_
Definition: server.hpp:157
std::size_t default_max_messages_
Definition: server.hpp:189
std::size_t max_ip_log_size_
Definition: server.hpp:195
void restart_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2441
void msg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2682
boost::asio::steady_timer lan_server_timer_
Definition: server.hpp:300
void shut_down_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2410
std::map< std::string, config > redirected_versions_
Definition: server.hpp:179
void metrics_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2516
std::map< std::string, cmd_handler > cmd_handlers_
Definition: server.hpp:261
void setup_fifo()
Definition: server.cpp:334
void send_to_player(player_iterator player, simple_wml::document &data)
Definition: server.hpp:85
wesnothd::ban_manager ban_manager_
Definition: server.hpp:108
config read_config() const
Read the server config from file 'config_file_'.
Definition: server.cpp:428
boost::asio::steady_timer dump_stats_timer_
Definition: server.hpp:237
void handle_player_in_lobby(player_iterator player, simple_wml::document &doc)
Definition: server.cpp:1232
std::vector< std::string > tor_ip_list_
Definition: server.hpp:201
void roll_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2534
void games_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2577
std::deque< std::shared_ptr< game > > games() const
Definition: server.hpp:220
simple_wml::document version_query_response_
Definition: server.hpp:212
void version_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2725
std::size_t concurrent_connections_
Definition: server.hpp:191
void send_password_request(SocketPtr socket, const std::string &msg, const char *error_code="", bool force_confirmation=false)
Definition: server.cpp:1146
void sample_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2476
void start_lan_server_timer()
Definition: server.cpp:315
void disconnect_player(player_iterator player)
Definition: server.cpp:2245
bool allow_remote_shutdown_
Definition: server.hpp:199
void ungban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3065
std::chrono::seconds default_time_period_
Definition: server.hpp:190
Definitions for the interface to Wesnoth Markup Language (WML).
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1031
Interfaces for manipulating version numbers of engine, add-ons, etc.
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:296
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_MUST_LOGIN
#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)
Definition: server.cpp:833
constexpr auto days_hours_mins_secs
Definition: chrono.hpp:98
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition: chrono.hpp:57
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
Definition: chrono.hpp:71
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition: chrono.hpp:62
constexpr auto deconstruct_duration(const std::tuple< Ts... > &, const std::chrono::duration< Rep, Period > &span)
Definition: chrono.hpp:85
static void update()
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::string get_cwd()
Definition: filesystem.cpp:973
void set_user_data_dir(std::string newprefdir)
Definition: filesystem.cpp:740
std::string observer
std::string path
Definition: filesystem.cpp:106
const version_info wesnoth_version(VERSION)
void remove()
Removes a tip.
Definition: tooltip.cpp:94
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:855
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:610
logger & err()
Definition: log.cpp:339
severity
Definition: log.hpp:82
logger & debug()
Definition: log.cpp:357
logger & warn()
Definition: log.cpp:345
void timestamps(bool t)
Definition: log.cpp:336
logger & info()
Definition: log.cpp:351
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:379
std::string node_to_string(const node &n)
Definition: simple_wml.cpp:800
std::string lowercase(std::string_view s)
Returns a lowercased version of the string.
Definition: unicode.cpp:50
std::string & insert(std::string &str, const std::size_t pos, const std::string &insert)
Insert a UTF-8 string at the specified position.
Definition: unicode.cpp:100
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
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.
Definition: unicode.cpp:70
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition: general.hpp:31
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:
Definition: charconv.hpp:156
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
Definition: general.hpp:151
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()
Definition: general.hpp:141
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)
Definition: server.cpp:89
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)
Definition: server.cpp:150
int request_sample_frequency
Definition: server.cpp:86
const std::string help_msg
Definition: server.cpp:205
player_connections::const_iterator player_iterator
static std::string player_status(const wesnothd::player_record &player)
Definition: server.cpp:191
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)
Definition: server.cpp:117
version_info secure_version
Definition: server.cpp:87
const std::string denied_msg
Definition: server.cpp:204
std::string to_string(const Range &range, const Func &op)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:188
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map &defines)
Function to use the WML preprocessor on a file.
bool dump_wml
Definition: server_base.cpp:62
std::shared_ptr< boost::asio::ssl::stream< socket_ptr::element_type > > tls_socket_ptr
Definition: server_base.hpp:52
std::string log_address(SocketPtr socket)
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
Definition: server_base.hpp:49
rect src
Non-transparent portion of the surface to compose.
The base template for associating string values with enum values.
Definition: enum_base.hpp:33
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
Ban status description.
BAN_TYPE type
Ban type.
std::chrono::seconds duration
Ban duration (0 if permanent)
std::size_t players_required
Definition: server.hpp:152
std::vector< std::string > players_in_queue
Definition: server.hpp:153
mock_party p
static map_location::direction n
static map_location::direction s
#define FIFODIR
#define DBG_SERVER
Definition: server.cpp:75
#define LOG_SERVER
normal events
Definition: server.cpp:74
#define WRN_SERVER
clients send wrong/unexpected data
Definition: server.cpp:71
#define SETUP_HANDLER(name, function)
#define ERR_CONFIG
Definition: server.cpp:78
#define ERR_SERVER
fatal and directly server related errors/warnings, ie not caused by erroneous client data
Definition: server.cpp:68
static lg::log_domain log_server("server")
static lg::log_domain log_config("config")
#define d
#define e
#define h