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