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