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