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  // this is a game defined in an [mp_queue] in the client
1572  // if there is no mp_queue defined game already existing with empty slots, tell the client to create one
1573  // else update game_id to the game that already exists and have the client join that game
1574  if(game_id < 0) {
1575  for(const auto& game : games()) {
1576  if(game->q_type() == queue_type::type::client_preset &&
1577  !game->started() &&
1578  join["mp_scenario"].to_string() == game->get_scenario_id() &&
1579  game->description()->child("slot_data")->attr("vacant").to_int() != 0) {
1580  game_id = game->id();
1581  }
1582  }
1583 
1584  // if it's still negative, then there's no existing game to join
1585  if(game_id < 0) {
1586  simple_wml::document create_game_doc;
1587  simple_wml::node& create_game_node = create_game_doc.root().add_child("create_game");
1588  create_game_node.set_attr_dup("mp_scenario", join["mp_scenario"].to_string().c_str());
1589  create_game_node.set_attr_int("is_preset", 1);
1590 
1591  // tell the client to create a game since there is no suitable existing game to join
1592  send_to_player(player, create_game_doc);
1593  return;
1594  } else {
1595  simple_wml::document join_game_doc;
1596  simple_wml::node& join_game_node = join_game_doc.root().add_child("join_game");
1597  join_game_node.set_attr_int("id", game_id);
1598 
1599  // tell the client to create a game since there is no suitable existing game to join
1600  send_to_player(player, join_game_doc);
1601  return;
1602  }
1603  }
1604 
1605  const bool observer = join.attr("observe").to_bool();
1606  const std::string& password = join["password"].to_string();
1607 
1608  auto g_iter = player_connections_.get<game_t>().find(game_id);
1609 
1610  std::shared_ptr<game> g;
1611  if(g_iter != player_connections_.get<game_t>().end()) {
1612  g = g_iter->get_game();
1613  }
1614 
1615  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
1616  if(!g) {
1617  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1618  << "\tattempted to join unknown game:\t" << game_id << ".";
1619  send_to_player(player, leave_game_doc);
1620  send_server_message(player, "Attempt to join unknown game.", "error");
1622  return;
1623  } else if(!g->level_init()) {
1624  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1625  << "\tattempted to join uninitialized game:\t\"" << g->name() << "\" (" << game_id << ").";
1626  send_to_player(player, leave_game_doc);
1627  send_server_message(player, "Attempt to join an uninitialized game.", "error");
1629  return;
1630  } else if(player->info().is_moderator()) {
1631  // Admins are always allowed to join.
1632  } else if(g->player_is_banned(player, player->info().name())) {
1633  DBG_SERVER << player->client_ip()
1634  << "\tReject banned player: " << player->info().name()
1635  << "\tfrom game:\t\"" << g->name() << "\" (" << game_id << ").";
1636  send_to_player(player, leave_game_doc);
1637  send_server_message(player, "You are banned from this game.", "error");
1639  return;
1640  } else if(!g->password_matches(password)) {
1641  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1642  << "\tattempted to join game:\t\"" << g->name() << "\" (" << game_id << ") with bad password";
1643  send_to_player(player, leave_game_doc);
1644  send_server_message(player, "Incorrect password.", "error");
1646  return;
1647  }
1648 
1649  bool joined = g->add_player(player, observer);
1650  if(!joined) {
1651  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1652  << "\tattempted to observe game:\t\"" << g->name() << "\" (" << game_id
1653  << ") which doesn't allow observers.";
1654  send_to_player(player, leave_game_doc);
1655 
1657  "Attempt to observe a game that doesn't allow observers. (You probably joined the "
1658  "game shortly after it filled up.)", "error");
1659 
1661  return;
1662  }
1663 
1664  player_connections_.modify(player,
1665  std::bind(&player_record::set_game, std::placeholders::_1, g));
1666 
1667  g->describe_slots();
1668 
1669  // send notification of changes to the game and user
1670  simple_wml::document diff;
1671  bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g->changed_description(), diff);
1672  bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user", player->info().config_address(), diff);
1673 
1674  if(diff1 || diff2) {
1675  send_to_lobby(diff);
1676  }
1677 
1678  // remove from any queues they may have joined
1679  for(int q : player->info().get_queues()) {
1680  queue_info& queue = queue_info_.at(q);
1681  if(!queue.players_in_queue.empty()) {
1682  queue.players_in_queue.erase(std::remove(queue.players_in_queue.begin(), queue.players_in_queue.end(), player->info().name()));
1683  send_queue_update(queue);
1684  }
1685  }
1686 }
1687 
1689 {
1690  int queue_id = data.attr("queue_id").to_int();
1691 
1692  if(queue_info_.count(queue_id) == 0) {
1693  ERR_SERVER << "player " << p->info().name() << " attempted to join non-existing server-side queue " << data.attr("queue_id");
1694  return;
1695  }
1696 
1697  queue_info& queue = queue_info_.at(queue_id);
1698  if(utils::contains(queue.players_in_queue, p->info().name())) {
1699  DBG_SERVER << "player " << p->info().name() << " already in server-side queue " << data.attr("queue_id");
1700  return;
1701  }
1702 
1703  // if they're not already in the queue, add them
1704  queue.players_in_queue.emplace_back(p->info().name());
1705  p->info().add_queue(queue.id);
1706  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);
1707 
1708  send_queue_update(queue);
1709 
1710  // if there are enough players in the queue to start a game, then have the final player who joined the queue host it
1711  // else check if there's an existing game that was created for the queue which needs players (ie: player left or failed to join)
1712  // if yes, tell the player to immediately join that game
1713  // if no, leave them in the queue
1714  if(queue.players_required <= queue.players_in_queue.size()) {
1715  simple_wml::document create_game_doc;
1716  simple_wml::node& create_game_node = create_game_doc.root().add_child("create_game");
1717  create_game_node.set_attr_int("queue_id", queue.id);
1718  simple_wml::node& game = create_game_node.add_child("game");
1719  game.set_attr_dup("scenario", queue.settings["scenario"].str().c_str());
1720  game.set_attr_dup("era", queue.settings["era"].str().c_str());
1721  game.set_attr_int("fog", queue.settings["fog"].to_int());
1722  game.set_attr_int("shroud", queue.settings["shroud"].to_int());
1723  game.set_attr_int("village_gold", queue.settings["village_gold"].to_int());
1724  game.set_attr_int("village_support", queue.settings["village_support"].to_int());
1725  game.set_attr_int("experience_modifier", queue.settings["experience_modifier"].to_int());
1726  game.set_attr_int("countdown", queue.settings["countdown"].to_int());
1727  game.set_attr_int("random_start_time", queue.settings["random_start_time"].to_int());
1728  game.set_attr_int("shuffle_sides", queue.settings["shuffle_sides"].to_int());
1729 
1730  // tell the final player to create and host the game
1731  send_to_player(p, create_game_doc);
1732  } else {
1733  for(const auto& game : games()) {
1734  if(game->is_open_queue_game(queue.id)) {
1735  simple_wml::document join_game_doc;
1736  simple_wml::node& join_game_node = join_game_doc.root().add_child("join_game");
1737  join_game_node.set_attr_int("id", game->id());
1738 
1739  send_to_player(p, join_game_doc);
1740  return;
1741  }
1742  }
1743  }
1744 }
1745 
1747 {
1748  int queue_id = data.attr("queue_id").to_int();
1749 
1750  if(queue_info_.count(queue_id) == 0) {
1751  ERR_SERVER << "player " << p->info().name() << " attempted to leave non-existing server-side queue " << data.attr("queue_id");
1752  return;
1753  }
1754 
1755  queue_info& queue = queue_info_.at(queue_id);
1756 
1757  // if they're in the queue, remove them
1758  if(utils::contains(queue.players_in_queue, p->info().name())) {
1759  queue.players_in_queue.erase(std::remove(queue.players_in_queue.begin(), queue.players_in_queue.end(), p->info().name()));
1760  p->info().clear_queues();
1761 
1762  send_queue_update(queue);
1763  } else {
1764  ERR_SERVER << "player " << p->info().name() << " already not in server-side queue " << data.attr("queue_id");
1765  }
1766 }
1767 
1769 {
1770  DBG_SERVER << "in process_data_game...";
1771 
1772  wesnothd::player& player { p->info() };
1773 
1774  game& g = *(p->get_game());
1775  std::weak_ptr<game> g_ptr{p->get_game()};
1776 
1777  // If this is data describing the level for a game.
1778  if(data.child("snapshot") || data.child("scenario")) {
1779  if(!g.is_owner(p)) {
1780  return;
1781  }
1782 
1783  // If this game is having its level data initialized
1784  // for the first time, and is ready for players to join.
1785  // We should currently have a summary of the game in g.level().
1786  // We want to move this summary to the games_and_users_list_, and
1787  // place a pointer to that summary in the game's description.
1788  // g.level() should then receive the full data for the game.
1789  if(!g.level_init()) {
1790  LOG_SERVER << p->client_ip() << "\t" << player.name() << "\tcreated game:\t\"" << g.name() << "\" ("
1791  << g.id() << ", " << g.db_id() << ").";
1792  // Update our config object which describes the open games,
1793  // and save a pointer to the description in the new game.
1794  simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
1795  assert(gamelist != nullptr);
1796 
1797  simple_wml::node& desc = gamelist->add_child("game");
1798  g.level().root().copy_into(desc);
1799 
1800  if(const simple_wml::node* m = data.child("multiplayer")) {
1801  m->copy_into(desc);
1802  } else {
1803  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1804  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1805  // Set the description so it can be removed in delete_game().
1806  g.set_description(&desc);
1807  delete_game(g.id());
1808 
1810  "The scenario data is missing the [multiplayer] tag which contains the "
1811  "game settings. Game aborted.", "error");
1812  return;
1813  }
1814 
1815  g.set_description(&desc);
1816  desc.set_attr_dup("id", std::to_string(g.id()).c_str());
1817  } else {
1818  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1819  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") although it's already initialized.";
1820  return;
1821  }
1822 
1823  assert(games_and_users_list_.child("gamelist")->children("game").empty() == false);
1824 
1825  simple_wml::node& desc = *g.description_for_writing();
1826 
1827  // Update the game's description.
1828  // If there is no shroud, then tell players in the lobby
1829  // what the map looks like
1831  // fixme: the hanlder of [store_next_scenario] below searches for 'mp_shroud' in [scenario]
1832  // at least of the these cosed is likely wrong.
1833  if(!data["mp_shroud"].to_bool()) {
1834  desc.set_attr_dup("map_data", s["map_data"]);
1835  }
1836 
1837  if(const simple_wml::node* e = data.child("era")) {
1838  if(!e->attr("require_era").to_bool(true)) {
1839  desc.set_attr("require_era", "no");
1840  }
1841  }
1842 
1843  if(s["require_scenario"].to_bool(false)) {
1844  desc.set_attr("require_scenario", "yes");
1845  }
1846 
1847  const simple_wml::node::child_list& mlist = data.children("modification");
1848  for(const simple_wml::node* m : mlist) {
1849  desc.add_child_at("modification", 0);
1850  desc.child("modification")->set_attr_dup("id", m->attr("id"));
1851  desc.child("modification")->set_attr_dup("name", m->attr("name"));
1852  desc.child("modification")->set_attr_dup("addon_id", m->attr("addon_id"));
1853  desc.child("modification")->set_attr_dup("require_modification", m->attr("require_modification"));
1854  }
1855 
1856  // Record the full scenario in g.level()
1857  g.level().swap(data);
1858 
1859  // The host already put himself in the scenario so we just need
1860  // to update_side_data().
1861  // g.take_side(sock);
1862  g.update_side_data();
1863  g.describe_slots();
1864 
1865  // Send the update of the game description to the lobby.
1866  simple_wml::document diff;
1867  make_add_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", diff);
1868  make_change_diff(games_and_users_list_.root(), nullptr, "user", p->info().config_address(), diff);
1869 
1870  send_to_lobby(diff);
1871 
1872  // 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
1873  if(g.q_type() == queue_type::type::server_preset) {
1874  int queue_id = g.queue_id();
1875  int game_id = g.id();
1876 
1877  if(queue_info_.count(queue_id) == 0) {
1878  return;
1879  }
1880 
1881  queue_info& info = queue_info_.at(queue_id);
1882  std::size_t joined_count = 1;
1883  for(const std::string& name : info.players_in_queue) {
1884  auto player_ptr = player_connections_.get<name_t>().find(name);
1885 
1886  // player is still connected and not in a game, tell them to join
1887  if(player_ptr != player_connections_.get<name_t>().end() && !player_ptr->get_game()) {
1888  simple_wml::document join_game_doc;
1889  simple_wml::node& join_game_node = join_game_doc.root().add_child("join_game");
1890  join_game_node.set_attr_int("id", game_id);
1891  send_to_player(player_ptr->socket(), join_game_doc);
1892  }
1893 
1894  // remove them from any queues they joined
1895  for(int queue : player_ptr->info().get_queues()) {
1896  queue_info& other_queue = queue_info_.at(queue);
1897  if(!other_queue.players_in_queue.empty()) {
1898  other_queue.players_in_queue.erase(std::remove(other_queue.players_in_queue.begin(), other_queue.players_in_queue.end(), player_ptr->info().name()));
1899  }
1900  }
1901 
1902  joined_count++;
1903  if(joined_count == info.players_required) {
1904  break;
1905  }
1906  }
1907 
1908  // send all other players the updated player counts for all queues
1909  for(auto& [id, queue] : queue_info_) {
1910  send_queue_update(queue);
1911  }
1912  }
1913 
1914  /** @todo FIXME: Why not save the level data in the history_? */
1915  return;
1916  // Everything below should only be processed if the game is already initialized.
1917  } else if(!g.level_init()) {
1918  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
1919  << " while the scenario wasn't yet initialized."
1920  << data.output();
1921  return;
1922  // If the host is sending the next scenario data.
1923  } else if(const simple_wml::node* scenario = data.child("store_next_scenario")) {
1924  if(!g.is_owner(p)) {
1925  return;
1926  }
1927 
1928  if(!g.level_init()) {
1929  WRN_SERVER << p->client_ip() << "\tWarning: " << player.name()
1930  << "\tsent [store_next_scenario] in game:\t\"" << g.name() << "\" (" << g.id()
1931  << ", " << g.db_id() << ") while the scenario is not yet initialized.";
1932  return;
1933  }
1934 
1935  g.save_replay();
1936  if(user_handler_){
1937  user_handler_->db_update_game_end(uuid_, g.db_id(), g.get_replay_filename());
1938  }
1939 
1940  g.new_scenario(p);
1941  g.reset_last_synced_context_id();
1942 
1943  // Record the full scenario in g.level()
1944  g.level().clear();
1945  scenario->copy_into(g.level().root());
1946  g.next_db_id();
1947 
1948  if(g.description() == nullptr) {
1949  ERR_SERVER << p->client_ip() << "\tERROR: \"" << g.name() << "\" (" << g.id()
1950  << ", " << g.db_id() << ") is initialized but has no description_.";
1951  return;
1952  }
1953 
1954  simple_wml::node& desc = *g.description_for_writing();
1955 
1956  // Update the game's description.
1957  if(const simple_wml::node* m = scenario->child("multiplayer")) {
1958  m->copy_into(desc);
1959  } else {
1960  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1961  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1962 
1963  delete_game(g.id());
1964 
1966  "The scenario data is missing the [multiplayer] tag which contains the game "
1967  "settings. Game aborted.", "error");
1968  return;
1969  }
1970 
1971  // If there is no shroud, then tell players in the lobby
1972  // what the map looks like.
1973  const simple_wml::node& s = *wesnothd::game::starting_pos(g.level().root());
1974  desc.set_attr_dup("map_data", s["mp_shroud"].to_bool() ? "" : s["map_data"]);
1975 
1976  if(const simple_wml::node* e = data.child("era")) {
1977  if(!e->attr("require_era").to_bool(true)) {
1978  desc.set_attr("require_era", "no");
1979  }
1980  }
1981 
1982  if(s["require_scenario"].to_bool(false)) {
1983  desc.set_attr("require_scenario", "yes");
1984  }
1985 
1986  // Tell everyone that the next scenario data is available.
1987  static simple_wml::document notify_next_scenario(
1988  "[notify_next_scenario]\n[/notify_next_scenario]\n", simple_wml::INIT_COMPRESSED);
1989  g.send_data(notify_next_scenario, p);
1990 
1991  // Send the update of the game description to the lobby.
1993  return;
1994  // A mp client sends a request for the next scenario of a mp campaign.
1995  } else if(data.child("load_next_scenario")) {
1996  g.load_next_scenario(p);
1997  return;
1998  } else if(data.child("start_game")) {
1999  if(!g.is_owner(p)) {
2000  return;
2001  }
2002 
2003  // perform controller tweaks, assigning sides as human for their owners etc.
2004  g.perform_controller_tweaks();
2005 
2006  // Send notification of the game starting immediately.
2007  // g.start_game() will send data that assumes
2008  // the [start_game] message has been sent
2009  g.send_data(data, p);
2010  g.start_game(p);
2011 
2012  if(user_handler_) {
2013  const simple_wml::node& m = *g.level().root().child("multiplayer");
2015  // [addon] info handling
2016  std::set<std::string> primary_keys;
2017  for(const auto& addon : m.children("addon")) {
2018  for(const auto& content : addon->children("content")) {
2019  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();
2020  if(primary_keys.count(key) == 0) {
2021  primary_keys.emplace(key);
2022  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());
2023  if(rows_inserted == 0) {
2024  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() << "'";
2025  }
2026  }
2027  }
2028  }
2029  if(m.children("addon").size() == 0) {
2030  WRN_SERVER << "Game content info missing for game with uuid '" << uuid_ << "', game ID '" << g.db_id() << "', named '" << g.name() << "'";
2031  }
2032 
2033  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());
2034 
2035  const simple_wml::node::child_list& sides = g.get_sides_list();
2036  for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
2037  const simple_wml::node& side = *sides[side_index];
2038  const auto player = player_connections_.get<name_t>().find(side["player_id"].to_string());
2039  std::string version;
2040  std::string source;
2041 
2042  // if "Nobody" is chosen for a side, for example
2043  if(player == player_connections_.get<name_t>().end()){
2044  version = "";
2045  source = "";
2046  } else {
2047  version = player->info().version();
2048  source = player->info().source();
2049 
2050  if(client_sources_.count(source) == 0) {
2051  source = "Default";
2052  }
2053  }
2054 
2055  // approximately determine leader(s) for the side like the client does
2056  // useful generally to know how often leaders are used vs other leaders
2057  // 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
2058  std::vector<std::string> leaders;
2059  // if a type= attribute is specified for the side, add it
2060  if(side.attr("type") != "") {
2061  leaders.emplace_back(side.attr("type").to_string());
2062  }
2063  // add each [unit] in the side that has canrecruit=yes
2064  for(const auto unit : side.children("unit")) {
2065  if(unit->attr("canrecruit") == "yes") {
2066  leaders.emplace_back(unit->attr("type").to_string());
2067  }
2068  }
2069  // add any [leader] specified for the side
2070  for(const auto leader : side.children("leader")) {
2071  leaders.emplace_back(leader->attr("type").to_string());
2072  }
2073 
2074  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));
2075  }
2076  }
2077 
2078  // update the game having changed in the lobby
2080  return;
2081  } else if(data.child("leave_game")) {
2082  if(g.remove_player(p)) {
2083  delete_game(g.id());
2084  } else {
2085  bool has_diff = false;
2086  simple_wml::document diff;
2087 
2088  // After this line, the game object may be destroyed. Don't use `g`!
2089  player_connections_.modify(p, std::bind(&player_record::enter_lobby, std::placeholders::_1));
2090 
2091  // Only run this if the game object is still valid
2092  if(auto gStrong = g_ptr.lock()) {
2093  gStrong->describe_slots();
2094  //Don't update the game if it no longer exists.
2095  has_diff |= make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", gStrong->description(), diff);
2096  }
2097 
2098  // Send all other players in the lobby the update to the gamelist.
2099  has_diff |= make_change_diff(games_and_users_list_.root(), nullptr, "user", player.config_address(), diff);
2100 
2101  if(has_diff) {
2102  send_to_lobby(diff, p);
2103  }
2104 
2105  // Send the player who has quit the gamelist.
2107  }
2108 
2109  // send the current queue counts
2110  for(const auto& [id, info] : queue_info_) {
2111  simple_wml::document queue_update;
2112  simple_wml::node& update = queue_update.root().add_child("queue_update");
2113  update.set_attr_int("queue_id", info.id);
2114  update.set_attr_dup("action", "update");
2115  update.set_attr_dup("current_players", utils::join(info.players_in_queue).c_str());
2116 
2117  send_to_player(p, queue_update);
2118  }
2119 
2120  return;
2121  // If this is data describing side changes by the host.
2122  } else if(const simple_wml::node* scenario_diff = data.child("scenario_diff")) {
2123  if(!g.is_owner(p)) {
2124  return;
2125  }
2126 
2127  g.level().root().apply_diff(*scenario_diff);
2128  const simple_wml::node* cfg_change = scenario_diff->child("change_child");
2129 
2130  if(cfg_change) {
2131  g.update_side_data();
2132  }
2133 
2134  g.describe_slots();
2136 
2137  g.send_data(data, p);
2138  return;
2139  // If a player changes his faction.
2140  } else if(data.child("change_faction")) {
2141  g.send_data(data, p);
2142  return;
2143  // If the owner of a side is changing the controller.
2144  } else if(const simple_wml::node* change = data.child("change_controller")) {
2145  g.transfer_side_control(p, *change);
2146  g.describe_slots();
2148 
2149  return;
2150  // If all observers should be muted. (toggles)
2151  } else if(data.child("muteall")) {
2152  if(!g.is_owner(p)) {
2153  g.send_server_message("You cannot mute: not the game host.", p);
2154  return;
2155  }
2156 
2157  g.mute_all_observers();
2158  return;
2159  // If an observer should be muted.
2160  } else if(const simple_wml::node* mute = data.child("mute")) {
2161  g.mute_observer(*mute, p);
2162  return;
2163  // If an observer should be unmuted.
2164  } else if(const simple_wml::node* unmute = data.child("unmute")) {
2165  g.unmute_observer(*unmute, p);
2166  return;
2167  // The owner is kicking/banning someone from the game.
2168  } else if(data.child("kick") || data.child("ban")) {
2169  bool ban = (data.child("ban") != nullptr);
2170  auto user { ban
2171  ? g.ban_user(*data.child("ban"), p)
2172  : g.kick_member(*data.child("kick"), p)};
2173 
2174  if(user) {
2175  player_connections_.modify(*user, std::bind(&player_record::enter_lobby, std::placeholders::_1));
2176  g.describe_slots();
2177 
2178  update_game_in_lobby(g, user);
2179 
2180  // Send all other players in the lobby the update to the gamelist.
2181  simple_wml::document gamelist_diff;
2182  make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), gamelist_diff);
2183  make_change_diff(games_and_users_list_.root(), nullptr, "user", (*user)->info().config_address(), gamelist_diff);
2184 
2185  send_to_lobby(gamelist_diff, p);
2186 
2187  // Send the removed user the lobby game list.
2189  }
2190 
2191  return;
2192  } else if(const simple_wml::node* unban = data.child("unban")) {
2193  g.unban_user(*unban, p);
2194  return;
2195  // If info is being provided about the game state.
2196  } else if(const simple_wml::node* info = data.child("info")) {
2197  if(!g.is_player(p)) {
2198  return;
2199  }
2200 
2201  if((*info)["type"] == "termination") {
2202  g.set_termination_reason((*info)["condition"].to_string());
2203  if((*info)["condition"].to_string() == "out of sync") {
2204  g.send_and_record_server_message(player.name() + " reports out of sync errors.");
2205  if(user_handler_){
2206  user_handler_->db_set_oos_flag(uuid_, g.db_id());
2207  }
2208  }
2209  }
2210 
2211  return;
2212  } else if(data.child("turn")) {
2213  // Notify the game of the commands, and if it changes
2214  // the description, then sync the new description
2215  // to players in the lobby.
2216  g.process_turn(data, p);
2218 
2219  return;
2220  } else if(data.child("whiteboard")) {
2221  g.process_whiteboard(data, p);
2222  return;
2223  } else if(data.child("change_turns_wml")) {
2224  g.process_change_turns_wml(data, p);
2226  return;
2227  } else if(simple_wml::node* sch = data.child("request_choice")) {
2228  g.handle_choice(*sch, p);
2229  return;
2230  } else if(data.child("message")) {
2231  g.process_message(data, p);
2232  return;
2233  } else if(data.child("stop_updates")) {
2234  g.send_data(data, p);
2235  return;
2236  // Data to ignore.
2237  } else if(
2238  data.child("error") ||
2239  data.child("side_secured") ||
2240  data.root().has_attr("failed") ||
2241  data.root().has_attr("side")
2242  ) {
2243  return;
2244  }
2245 
2246  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
2247  << " in game: \"" << g.name() << "\" (" << g.id() << ", " << g.db_id() << ")\n"
2248  << data.output();
2249 }
2250 
2251 template<class SocketPtr> void server::send_server_message(SocketPtr socket, const std::string& message, const std::string& type)
2252 {
2253  simple_wml::document server_message;
2254  simple_wml::node& msg = server_message.root().add_child("message");
2255  msg.set_attr("sender", "server");
2256  msg.set_attr_esc("message", message);
2257  msg.set_attr_dup("type", type.c_str());
2258 
2259  async_send_doc_queued(socket, server_message);
2260 }
2261 
2263 {
2264  utils::visit([](auto&& socket) {
2265  if constexpr (utils::decayed_is_same<tls_socket_ptr, decltype(socket)>) {
2266  socket->async_shutdown([socket](...) {});
2267  const char buffer[] = "";
2268  async_write(*socket, boost::asio::buffer(buffer), [socket](...) { socket->lowest_layer().close(); });
2269  } else {
2270  socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
2271  }
2272  }, player->socket());
2273 }
2274 
2276 {
2277  std::string ip = iter->client_ip();
2278 
2279  const std::shared_ptr<game> g = iter->get_game();
2280  bool game_ended = false;
2281  if(g) {
2282  game_ended = g->remove_player(iter, true, false);
2283  }
2284 
2286  const std::size_t index =
2287  std::distance(users.begin(), std::find(users.begin(), users.end(), iter->info().config_address()));
2288 
2289  // Notify other players in lobby
2290  simple_wml::document diff;
2291  if(make_delete_diff(games_and_users_list_.root(), nullptr, "user", iter->info().config_address(), diff)) {
2292  send_to_lobby(diff, iter);
2293  }
2294 
2296 
2297  LOG_SERVER << ip << "\t" << iter->info().name() << "\thas logged off";
2298 
2299  // Find the matching nick-ip pair in the log and update the sign off time
2300  if(user_handler_) {
2301  user_handler_->db_update_logout(iter->info().get_login_id());
2302  } else {
2303  connection_log ip_name { iter->info().name(), ip, {} };
2304 
2305  auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name);
2306  if(i != ip_log_.end()) {
2307  i->log_off = std::chrono::system_clock::now();
2308  }
2309  }
2310 
2311  for(auto& [id, queue] : queue_info_) {
2312  if(!queue.players_in_queue.empty()) {
2313  queue.players_in_queue.erase(std::remove(queue.players_in_queue.begin(), queue.players_in_queue.end(), iter->info().name()));
2314  }
2315  send_queue_update(queue, iter);
2316  }
2317 
2318  player_connections_.erase(iter);
2319 
2320  if(lan_server_ > 0s && player_connections_.size() == 0)
2322 
2323  if(game_ended) delete_game(g->id());
2324 }
2325 
2326 void server::send_to_lobby(simple_wml::document& data, utils::optional<player_iterator> exclude)
2327 {
2328  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
2329  auto player { player_connections_.iterator_to(p) };
2330  if(player != exclude) {
2332  }
2333  }
2334 }
2335 
2336 void server::send_server_message_to_lobby(const std::string& message, utils::optional<player_iterator> exclude)
2337 {
2338  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
2339  auto player { player_connections_.iterator_to(p) };
2340  if(player != exclude) {
2341  send_server_message(player, message, "alert");
2342  }
2343  }
2344 }
2345 
2346 void server::send_server_message_to_all(const std::string& message, utils::optional<player_iterator> exclude)
2347 {
2348  for(auto player = player_connections_.begin(); player != player_connections_.end(); ++player) {
2349  if(player != exclude) {
2350  send_server_message(player, message, "alert");
2351  }
2352  }
2353 }
2354 
2356 {
2357  if(restart_command.empty()) {
2358  return;
2359  }
2360 
2361  // Example config line:
2362  // restart_command="./wesnothd-debug -d -c ~/.wesnoth1.5/server.cfg"
2363  // remember to make new one as a daemon or it will block old one
2364  if(std::system(restart_command.c_str())) {
2365  ERR_SERVER << "Failed to start new server with command: " << restart_command;
2366  } else {
2367  LOG_SERVER << "New server started with command: " << restart_command;
2368  }
2369 }
2370 
2371 std::string server::process_command(std::string query, std::string issuer_name)
2372 {
2373  boost::trim(query);
2374 
2375  if(issuer_name == "*socket*" && !query.empty() && query.at(0) == '+') {
2376  // The first argument might be "+<issuer>: ".
2377  // In that case we use +<issuer>+ as the issuer_name.
2378  // (Mostly used for communication with IRC.)
2379  auto issuer_end = std::find(query.begin(), query.end(), ':');
2380 
2381  std::string issuer(query.begin() + 1, issuer_end);
2382  if(!issuer.empty()) {
2383  issuer_name = "+" + issuer + "+";
2384  query = std::string(issuer_end + 1, query.end());
2385  boost::trim(query);
2386  }
2387  }
2388 
2389  const auto i = std::find(query.begin(), query.end(), ' ');
2390 
2391  try {
2392  const std::string command = utf8::lowercase(std::string(query.begin(), i));
2393 
2394  std::string parameters = (i == query.end() ? "" : std::string(i + 1, query.end()));
2395  boost::trim(parameters);
2396 
2397  std::ostringstream out;
2398  auto handler_itor = cmd_handlers_.find(command);
2399 
2400  if(handler_itor == cmd_handlers_.end()) {
2401  out << "Command '" << command << "' is not recognized.\n" << help_msg;
2402  } else {
2403  const cmd_handler& handler = handler_itor->second;
2404  try {
2405  handler(issuer_name, query, parameters, &out);
2406  } catch(const std::bad_function_call& ex) {
2407  ERR_SERVER << "While handling a command '" << command
2408  << "', caught a std::bad_function_call exception.";
2409  ERR_SERVER << ex.what();
2410  out << "An internal server error occurred (std::bad_function_call) while executing '" << command
2411  << "'\n";
2412  }
2413  }
2414 
2415  return out.str();
2416 
2417  } catch(const utf8::invalid_utf8_exception& e) {
2418  std::string msg = "While handling a command, caught an invalid utf8 exception: ";
2419  msg += e.what();
2420  ERR_SERVER << msg;
2421  return (msg + '\n');
2422  }
2423 }
2424 
2425 // Shutdown, restart and sample commands can only be issued via the socket.
2427  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2428 {
2429  assert(out != nullptr);
2430 
2431  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2432  *out << denied_msg;
2433  return;
2434  }
2435 
2436  if(parameters == "now") {
2437  BOOST_THROW_EXCEPTION(server_shutdown("shut down by admin command"));
2438  } else {
2439  // Graceful shut down.
2440  graceful_restart = true;
2441  acceptor_v6_.close();
2442  acceptor_v4_.close();
2443 
2444  timer_.expires_after(10s);
2445  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2446 
2448  "msg The server is shutting down. You may finish your games but can't start new ones. Once all "
2449  "games have ended the server will exit.",
2450  issuer_name
2451  );
2452 
2453  *out << "Server is doing graceful shut down.";
2454  }
2455 }
2456 
2457 void server::restart_handler(const std::string& issuer_name,
2458  const std::string& /*query*/,
2459  std::string& /*parameters*/,
2460  std::ostringstream* out)
2461 {
2462  assert(out != nullptr);
2463 
2464  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2465  *out << denied_msg;
2466  return;
2467  }
2468 
2469  if(restart_command.empty()) {
2470  *out << "No restart_command configured! Not restarting.";
2471  } else {
2472  graceful_restart = true;
2473  acceptor_v6_.close();
2474  acceptor_v4_.close();
2475  timer_.expires_after(10s);
2476  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2477 
2478  start_new_server();
2479 
2481  "msg The server has been restarted. You may finish current games but can't start new ones and "
2482  "new players can't join this (old) server instance. (So if a player of your game disconnects "
2483  "you have to save, reconnect and reload the game on the new server instance. It is actually "
2484  "recommended to do that right away.)",
2485  issuer_name
2486  );
2487 
2488  *out << "New server started.";
2489  }
2490 }
2491 
2493  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2494 {
2495  assert(out != nullptr);
2496 
2497  if(parameters.empty()) {
2498  *out << "Current sample frequency: " << request_sample_frequency;
2499  return;
2500  } else if(issuer_name != "*socket*") {
2501  *out << denied_msg;
2502  return;
2503  }
2504 
2505  request_sample_frequency = atoi(parameters.c_str());
2506  if(request_sample_frequency <= 0) {
2507  *out << "Sampling turned off.";
2508  } else {
2509  *out << "Sampling every " << request_sample_frequency << " requests.";
2510  }
2511 }
2512 
2513 void server::help_handler(const std::string& /*issuer_name*/,
2514  const std::string& /*query*/,
2515  std::string& /*parameters*/,
2516  std::ostringstream* out)
2517 {
2518  assert(out != nullptr);
2519  *out << help_msg;
2520 }
2521 
2522 void server::stats_handler(const std::string& /*issuer_name*/,
2523  const std::string& /*query*/,
2524  std::string& /*parameters*/,
2525  std::ostringstream* out)
2526 {
2527  assert(out != nullptr);
2528 
2529  *out << "Number of games = " << games().size() << "\nTotal number of users = " << player_connections_.size();
2530 }
2531 
2532 void server::metrics_handler(const std::string& /*issuer_name*/,
2533  const std::string& /*query*/,
2534  std::string& /*parameters*/,
2535  std::ostringstream* out)
2536 {
2537  assert(out != nullptr);
2538  *out << metrics_;
2539 }
2540 
2541 void server::requests_handler(const std::string& /*issuer_name*/,
2542  const std::string& /*query*/,
2543  std::string& /*parameters*/,
2544  std::ostringstream* out)
2545 {
2546  assert(out != nullptr);
2547  metrics_.requests(*out);
2548 }
2549 
2550 void server::roll_handler(const std::string& issuer_name,
2551  const std::string& /*query*/,
2552  std::string& parameters,
2553  std::ostringstream* out)
2554 {
2555  assert(out != nullptr);
2556  if(parameters.empty()) {
2557  return;
2558  }
2559 
2560  int N;
2561  try {
2562  N = std::stoi(parameters);
2563  } catch(const std::invalid_argument&) {
2564  *out << "The number of die sides must be a number!";
2565  return;
2566  } catch(const std::out_of_range&) {
2567  *out << "The number of sides is too big for the die!";
2568  return;
2569  }
2570 
2571  if(N < 1) {
2572  *out << "The die cannot have less than 1 side!";
2573  return;
2574  }
2575  std::uniform_int_distribution<int> dice_distro(1, N);
2576  std::string value = std::to_string(dice_distro(die_));
2577 
2578  *out << "You rolled a die [1 - " + parameters + "] and got a " + value + ".";
2579 
2580  auto player_ptr = player_connections_.get<name_t>().find(issuer_name);
2581  if(player_ptr == player_connections_.get<name_t>().end()) {
2582  return;
2583  }
2584 
2585  auto g_ptr = player_ptr->get_game();
2586  if(g_ptr) {
2587  g_ptr->send_server_message_to_all(issuer_name + " rolled a die [1 - " + parameters + "] and got a " + value + ".", player_connections_.project<0>(player_ptr));
2588  } else {
2589  *out << " (The result is shown to others only in a game.)";
2590  }
2591 }
2592 
2593 void server::games_handler(const std::string& /*issuer_name*/,
2594  const std::string& /*query*/,
2595  std::string& /*parameters*/,
2596  std::ostringstream* out)
2597 {
2598  assert(out != nullptr);
2599  metrics_.games(*out);
2600 }
2601 
2602 void server::wml_handler(const std::string& /*issuer_name*/,
2603  const std::string& /*query*/,
2604  std::string& /*parameters*/,
2605  std::ostringstream* out)
2606 {
2607  assert(out != nullptr);
2608  *out << simple_wml::document::stats();
2609 }
2610 
2612  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2613 {
2614  assert(out != nullptr);
2615 
2616  if(parameters.empty()) {
2617  *out << "You must type a message.";
2618  return;
2619  }
2620 
2621  const std::string& sender = issuer_name;
2622  const std::string& message = parameters;
2623  LOG_SERVER << "Admin message: <" << sender
2624  << (message.find("/me ") == 0 ? std::string(message.begin() + 3, message.end()) + ">" : "> " + message);
2625 
2627  simple_wml::node& msg = data.root().add_child("whisper");
2628  msg.set_attr_dup("sender", ("admin message from " + sender).c_str());
2629  msg.set_attr_dup("message", message.c_str());
2630 
2631  int n = 0;
2632  for(const auto& player : player_connections_) {
2633  if(player.info().is_moderator()) {
2634  ++n;
2636  }
2637  }
2638 
2639  bool is_admin = false;
2640 
2641  for(const auto& player : player_connections_) {
2642  if(issuer_name == player.info().name() && player.info().is_moderator()) {
2643  is_admin = true;
2644  break;
2645  }
2646  }
2647 
2648  if(!is_admin) {
2649  *out << "Your report has been logged and sent to the server administrators. Thanks!";
2650  return;
2651  }
2652 
2653  *out << "Your report has been logged and sent to " << n << " online administrators. Thanks!";
2654 }
2655 
2657  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2658 {
2659  assert(out != nullptr);
2660 
2661  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2662  if(first_space == parameters.end()) {
2663  *out << "You must name a receiver.";
2664  return;
2665  }
2666 
2667  const std::string& sender = issuer_name;
2668  const std::string receiver(parameters.begin(), first_space);
2669 
2670  std::string message(first_space + 1, parameters.end());
2671  boost::trim(message);
2672 
2673  if(message.empty()) {
2674  *out << "You must type a message.";
2675  return;
2676  }
2677 
2679  simple_wml::node& msg = data.root().add_child("whisper");
2680 
2681  // This string is parsed by the client!
2682  msg.set_attr_dup("sender", ("server message from " + sender).c_str());
2683  msg.set_attr_dup("message", message.c_str());
2684 
2685  for(const auto& player : player_connections_) {
2686  if(receiver != player.info().name().c_str()) {
2687  continue;
2688  }
2689 
2691  *out << "Message to " << receiver << " successfully sent.";
2692  return;
2693  }
2694 
2695  *out << "No such nick: " << receiver;
2696 }
2697 
2698 void server::msg_handler(const std::string& /*issuer_name*/,
2699  const std::string& /*query*/,
2700  std::string& parameters,
2701  std::ostringstream* out)
2702 {
2703  assert(out != nullptr);
2704 
2705  if(parameters.empty()) {
2706  *out << "You must type a message.";
2707  return;
2708  }
2709 
2710  send_server_message_to_all(parameters);
2711 
2712  LOG_SERVER << "<server"
2713  << (parameters.find("/me ") == 0
2714  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2715  : "> " + parameters);
2716 
2717  *out << "message '" << parameters << "' relayed to players";
2718 }
2719 
2720 void server::lobbymsg_handler(const std::string& /*issuer_name*/,
2721  const std::string& /*query*/,
2722  std::string& parameters,
2723  std::ostringstream* out)
2724 {
2725  assert(out != nullptr);
2726 
2727  if(parameters.empty()) {
2728  *out << "You must type a message.";
2729  return;
2730  }
2731 
2732  send_server_message_to_lobby(parameters);
2733  LOG_SERVER << "<server"
2734  << (parameters.find("/me ") == 0
2735  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2736  : "> " + parameters);
2737 
2738  *out << "message '" << parameters << "' relayed to players";
2739 }
2740 
2742  const std::string& /*issuer_name*/, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2743 {
2744  assert(out != nullptr);
2745 
2746  if(parameters.empty()) {
2747  *out << "Server version is " << game_config::wesnoth_version.str();
2748  return;
2749  }
2750 
2751  for(const auto& player : player_connections_) {
2752  if(parameters == player.info().name()) {
2753  *out << "Player " << parameters << " is using wesnoth " << player.info().version();
2754  return;
2755  }
2756  }
2757 
2758  *out << "Player '" << parameters << "' not found.";
2759 }
2760 
2762  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2763 {
2764  assert(out != nullptr);
2765 
2766  *out << "STATUS REPORT for '" << parameters << "'";
2767  bool found_something = false;
2768 
2769  // If a simple username is given we'll check for its IP instead.
2770  if(utils::isvalid_username(parameters)) {
2771  for(const auto& player : player_connections_) {
2772  if(parameters == player.name()) {
2773  parameters = player.client_ip();
2774  found_something = true;
2775  break;
2776  }
2777  }
2778 
2779  if(!found_something) {
2780  // out << "\nNo match found. You may want to check with 'searchlog'.";
2781  // return out.str();
2782  *out << process_command("searchlog " + parameters, issuer_name);
2783  return;
2784  }
2785  }
2786 
2787  const bool match_ip = ((std::count(parameters.begin(), parameters.end(), '.') >= 1) || (std::count(parameters.begin(), parameters.end(), ':') >= 1));
2788  for(const auto& player : player_connections_) {
2789  if(parameters.empty() || parameters == "*" ||
2790  (match_ip && utils::wildcard_string_match(player.client_ip(), parameters)) ||
2791  (!match_ip && utils::wildcard_string_match(utf8::lowercase(player.info().name()), utf8::lowercase(parameters)))
2792  ) {
2793  found_something = true;
2794  *out << std::endl << player_status(player);
2795  }
2796  }
2797 
2798  if(!found_something) {
2799  *out << "\nNo match found. You may want to check with 'searchlog'.";
2800  }
2801 }
2802 
2803 void server::clones_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  *out << "CLONES STATUS REPORT";
2810 
2811  std::set<std::string> clones;
2812 
2813  for(auto it = player_connections_.begin(); it != player_connections_.end(); ++it) {
2814  if(clones.find(it->client_ip()) != clones.end()) {
2815  continue;
2816  }
2817 
2818  bool found = false;
2819  for(auto clone = std::next(it); clone != player_connections_.end(); ++clone) {
2820  if(it->client_ip() == clone->client_ip()) {
2821  if(!found) {
2822  found = true;
2823  clones.insert(it->client_ip());
2824  *out << std::endl << player_status(*it);
2825  }
2826 
2827  *out << std::endl << player_status(*clone);
2828  }
2829  }
2830  }
2831 
2832  if(clones.empty()) {
2833  *out << std::endl << "No clones found.";
2834  }
2835 }
2836 
2837 void server::bans_handler(const std::string& /*issuer_name*/,
2838  const std::string& /*query*/,
2839  std::string& parameters,
2840  std::ostringstream* out)
2841 {
2842  assert(out != nullptr);
2843 
2844  try {
2845  if(parameters.empty()) {
2846  ban_manager_.list_bans(*out);
2847  } else if(utf8::lowercase(parameters) == "deleted") {
2849  } else if(utf8::lowercase(parameters).find("deleted") == 0) {
2850  std::string mask = parameters.substr(7);
2851  ban_manager_.list_deleted_bans(*out, boost::trim_copy(mask));
2852  } else {
2853  boost::trim(parameters);
2854  ban_manager_.list_bans(*out, parameters);
2855  }
2856 
2857  } catch(const utf8::invalid_utf8_exception& e) {
2858  ERR_SERVER << "While handling bans, caught an invalid utf8 exception: " << e.what();
2859  }
2860 }
2861 
2863  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2864 {
2865  assert(out != nullptr);
2866 
2867  bool banned = false;
2868  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2869 
2870  if(first_space == parameters.end()) {
2871  *out << ban_manager_.get_ban_help();
2872  return;
2873  }
2874 
2875  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2876  const std::string target(parameters.begin(), first_space);
2877  const std::string duration(first_space + 1, second_space);
2878  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
2879 
2880  if(!success) {
2881  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2882  return;
2883  }
2884 
2885  if(second_space == parameters.end()) {
2886  --second_space;
2887  }
2888 
2889  std::string reason(second_space + 1, parameters.end());
2890  boost::trim(reason);
2891 
2892  if(reason.empty()) {
2893  *out << "You need to give a reason for the ban.";
2894  return;
2895  }
2896 
2897  std::string dummy_group;
2898 
2899  // if we find a '.' consider it an ip mask
2900  /** @todo FIXME: make a proper check for valid IPs. */
2901  if(std::count(target.begin(), target.end(), '.') >= 1) {
2902  banned = true;
2903 
2904  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2905  } else {
2906  for(const auto& player : player_connections_) {
2907  if(utils::wildcard_string_match(player.info().name(), target)) {
2908  if(banned) {
2909  *out << "\n";
2910  } else {
2911  banned = true;
2912  }
2913 
2914  const std::string ip = player.client_ip();
2915  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2916  }
2917  }
2918 
2919  if(!banned) {
2920  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2921  }
2922  }
2923 }
2924 
2926  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2927 {
2928  assert(out != nullptr);
2929 
2930  bool banned = false;
2931  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2932  if(first_space == parameters.end()) {
2933  *out << ban_manager_.get_ban_help();
2934  return;
2935  }
2936 
2937  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2938  const std::string target(parameters.begin(), first_space);
2939  const std::string duration(first_space + 1, second_space);
2940  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
2941 
2942  if(!success) {
2943  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2944  return;
2945  }
2946 
2947  if(second_space == parameters.end()) {
2948  --second_space;
2949  }
2950 
2951  std::string reason(second_space + 1, parameters.end());
2952  boost::trim(reason);
2953 
2954  if(reason.empty()) {
2955  *out << "You need to give a reason for the ban.";
2956  return;
2957  }
2958 
2959  std::string dummy_group;
2960  std::vector<player_iterator> users_to_kick;
2961 
2962  // if we find a '.' consider it an ip mask
2963  /** @todo FIXME: make a proper check for valid IPs. */
2964  if(std::count(target.begin(), target.end(), '.') >= 1) {
2965  banned = true;
2966 
2967  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2968 
2970  if(utils::wildcard_string_match(player->client_ip(), target)) {
2971  users_to_kick.push_back(player);
2972  }
2973  }
2974  } else {
2976  if(utils::wildcard_string_match(player->info().name(), target)) {
2977  if(banned) {
2978  *out << "\n";
2979  } else {
2980  banned = true;
2981  }
2982 
2983  const std::string ip = player->client_ip();
2984  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2985  users_to_kick.push_back(player);
2986  }
2987  }
2988 
2989  if(!banned) {
2990  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2991  }
2992  }
2993 
2994  for(auto user : users_to_kick) {
2995  *out << "\nKicked " << user->info().name() << " (" << user->client_ip() << ").";
2996  utils::visit([this,reason](auto&& socket) { async_send_error(socket, "You have been banned. Reason: " + reason); }, user->socket());
2997  disconnect_player(user);
2998  }
2999 }
3000 
3002  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
3003 {
3004  assert(out != nullptr);
3005 
3006  bool banned = false;
3007  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
3008  if(first_space == parameters.end()) {
3009  *out << ban_manager_.get_ban_help();
3010  return;
3011  }
3012 
3013  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
3014  const std::string target(parameters.begin(), first_space);
3015 
3016  std::string group = std::string(first_space + 1, second_space);
3017  first_space = second_space;
3018  second_space = std::find(first_space + 1, parameters.end(), ' ');
3019 
3020  const std::string duration(first_space + 1, second_space);
3021  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
3022 
3023  if(!success) {
3024  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
3025  return;
3026  }
3027 
3028  if(second_space == parameters.end()) {
3029  --second_space;
3030  }
3031 
3032  std::string reason(second_space + 1, parameters.end());
3033  boost::trim(reason);
3034 
3035  if(reason.empty()) {
3036  *out << "You need to give a reason for the ban.";
3037  return;
3038  }
3039 
3040  // if we find a '.' consider it an ip mask
3041  /** @todo FIXME: make a proper check for valid IPs. */
3042  if(std::count(target.begin(), target.end(), '.') >= 1) {
3043  banned = true;
3044 
3045  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, group);
3046  } else {
3047  for(const auto& player : player_connections_) {
3048  if(utils::wildcard_string_match(player.info().name(), target)) {
3049  if(banned) {
3050  *out << "\n";
3051  } else {
3052  banned = true;
3053  }
3054 
3055  const std::string ip = player.client_ip();
3056  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, group, target);
3057  }
3058  }
3059 
3060  if(!banned) {
3061  *out << "Nickname mask '" << target << "' did not match, no bans set.";
3062  }
3063  }
3064 }
3065 
3066 void server::unban_handler(const std::string& /*issuer_name*/,
3067  const std::string& /*query*/,
3068  std::string& parameters,
3069  std::ostringstream* out)
3070 {
3071  assert(out != nullptr);
3072 
3073  if(parameters.empty()) {
3074  *out << "You must enter an ipmask to unban.";
3075  return;
3076  }
3077 
3078  ban_manager_.unban(*out, parameters);
3079 }
3080 
3081 void server::ungban_handler(const std::string& /*issuer_name*/,
3082  const std::string& /*query*/,
3083  std::string& parameters,
3084  std::ostringstream* out)
3085 {
3086  assert(out != nullptr);
3087 
3088  if(parameters.empty()) {
3089  *out << "You must enter an ipmask to ungban.";
3090  return;
3091  }
3092 
3093  ban_manager_.unban_group(*out, parameters);
3094 }
3095 
3096 void server::kick_handler(const std::string& /*issuer_name*/,
3097  const std::string& /*query*/,
3098  std::string& parameters,
3099  std::ostringstream* out)
3100 {
3101  assert(out != nullptr);
3102 
3103  if(parameters.empty()) {
3104  *out << "You must enter a mask to kick.";
3105  return;
3106  }
3107 
3108  auto i = std::find(parameters.begin(), parameters.end(), ' ');
3109  const std::string kick_mask = std::string(parameters.begin(), i);
3110  const std::string kick_message = (i == parameters.end()
3111  ? "You have been kicked."
3112  : "You have been kicked. Reason: " + std::string(i + 1, parameters.end()));
3113 
3114  bool kicked = false;
3115 
3116  // if we find a '.' consider it an ip mask
3117  const bool match_ip = (std::count(kick_mask.begin(), kick_mask.end(), '.') >= 1);
3118 
3119  std::vector<player_iterator> users_to_kick;
3121  if((match_ip && utils::wildcard_string_match(player->client_ip(), kick_mask)) ||
3122  (!match_ip && utils::wildcard_string_match(player->info().name(), kick_mask))
3123  ) {
3124  users_to_kick.push_back(player);
3125  }
3126  }
3127 
3128  for(const auto& player : users_to_kick) {
3129  if(kicked) {
3130  *out << "\n";
3131  } else {
3132  kicked = true;
3133  }
3134 
3135  *out << "Kicked " << player->name() << " (" << player->client_ip() << "). '"
3136  << kick_message << "'";
3137 
3138  utils::visit([this, &kick_message](auto&& socket) { async_send_error(socket, kick_message); }, player->socket());
3140  }
3141 
3142  if(!kicked) {
3143  *out << "No user matched '" << kick_mask << "'.";
3144  }
3145 }
3146 
3147 void server::motd_handler(const std::string& /*issuer_name*/,
3148  const std::string& /*query*/,
3149  std::string& parameters,
3150  std::ostringstream* out)
3151 {
3152  assert(out != nullptr);
3153 
3154  if(parameters.empty()) {
3155  if(!motd_.empty()) {
3156  *out << "Message of the day:\n" << motd_;
3157  return;
3158  } else {
3159  *out << "No message of the day set.";
3160  return;
3161  }
3162  }
3163 
3164  motd_ = parameters;
3165  *out << "Message of the day set to: " << motd_;
3166 }
3167 
3168 void server::searchlog_handler(const std::string& /*issuer_name*/,
3169  const std::string& /*query*/,
3170  std::string& parameters,
3171  std::ostringstream* out)
3172 {
3173  assert(out != nullptr);
3174 
3175  if(parameters.empty()) {
3176  *out << "You must enter a mask to search for.";
3177  return;
3178  }
3179 
3180  *out << "IP/NICK LOG for '" << parameters << "'";
3181 
3182  // If this looks like an IP look up which nicks have been connected from it
3183  // Otherwise look for the last IP the nick used to connect
3184  const bool match_ip = (std::count(parameters.begin(), parameters.end(), '.') >= 1);
3185 
3186  if(!user_handler_) {
3187  bool found_something = false;
3188 
3189  for(const auto& i : ip_log_) {
3190  const std::string& username = i.nick;
3191  const std::string& ip = i.ip;
3192 
3193  if((match_ip && utils::wildcard_string_match(ip, parameters)) ||
3194  (!match_ip && utils::wildcard_string_match(utf8::lowercase(username), utf8::lowercase(parameters)))
3195  ) {
3196  found_something = true;
3197  auto player = player_connections_.get<name_t>().find(username);
3198 
3199  if(player != player_connections_.get<name_t>().end() && player->client_ip() == ip) {
3200  *out << std::endl << player_status(*player);
3201  } else {
3202  *out << "\n'" << username << "' @ " << ip
3203  << " last seen: " << chrono::format_local_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
3204  }
3205  }
3206  }
3207 
3208  if(!found_something) {
3209  *out << "\nNo match found.";
3210  }
3211  } else {
3212  if(!match_ip) {
3213  utils::to_sql_wildcards(parameters);
3214  user_handler_->get_ips_for_user(parameters, out);
3215  } else {
3216  user_handler_->get_users_for_ip(parameters, out);
3217  }
3218  }
3219 }
3220 
3221 void server::dul_handler(const std::string& /*issuer_name*/,
3222  const std::string& /*query*/,
3223  std::string& parameters,
3224  std::ostringstream* out)
3225 {
3226  assert(out != nullptr);
3227 
3228  try {
3229  if(parameters.empty()) {
3230  *out << "Unregistered login is " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
3231  } else {
3232  deny_unregistered_login_ = (utf8::lowercase(parameters) == "yes");
3233  *out << "Unregistered login is now " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
3234  }
3235 
3236  } catch(const utf8::invalid_utf8_exception& e) {
3237  ERR_SERVER << "While handling dul (deny unregistered logins), caught an invalid utf8 exception: " << e.what();
3238  }
3239 }
3240 
3241 void server::stopgame_handler(const std::string& /*issuer_name*/,
3242  const std::string& /*query*/,
3243  std::string& parameters,
3244  std::ostringstream* out)
3245 {
3246  assert(out != nullptr);
3247 
3248  const std::string nick = parameters.substr(0, parameters.find(' '));
3249  const std::string reason = parameters.length() > nick.length()+1 ? parameters.substr(nick.length()+1) : "";
3250  auto player = player_connections_.get<name_t>().find(nick);
3251 
3252  if(player != player_connections_.get<name_t>().end()){
3253  std::shared_ptr<game> g = player->get_game();
3254  if(g){
3255  *out << "Player '" << nick << "' is in game with id '" << g->id() << ", " << g->db_id() << "' named '" << g->name() << "'. Ending game for reason: '" << reason << "'...";
3256  delete_game(g->id(), reason);
3257  } else {
3258  *out << "Player '" << nick << "' is not currently in a game.";
3259  }
3260  } else {
3261  *out << "Player '" << nick << "' is not currently logged in.";
3262  }
3263 }
3264 
3265 void server::reset_queues_handler(const std::string& /*issuer_name*/,
3266  const std::string& /*query*/,
3267  std::string& /*parameters*/,
3268  std::ostringstream* out)
3269 {
3270  assert(out != nullptr);
3271 
3273  player->info().clear_queues();
3274  }
3275 
3276  for(auto& [id, queue] : queue_info_) {
3277  queue.players_in_queue.clear();
3278  send_queue_update(queue);
3279  }
3280 
3281  *out << "Reset all queues";
3282 }
3283 
3284 void server::delete_game(int gameid, const std::string& reason)
3285 {
3286  // Set the availability status for all quitting users.
3287  auto range_pair = player_connections_.get<game_t>().equal_range(gameid);
3288 
3289  // Make a copy of the iterators so that we can change them while iterating over them.
3290  // We can use pair::first_type since equal_range returns a pair of iterators.
3291  std::vector<decltype(range_pair)::first_type> range_vctor;
3292 
3293  for(auto it = range_pair.first; it != range_pair.second; ++it) {
3294  range_vctor.push_back(it);
3295  it->info().mark_available();
3296 
3297  simple_wml::document udiff;
3298  if(make_change_diff(games_and_users_list_.root(), nullptr, "user", it->info().config_address(), udiff)) {
3299  send_to_lobby(udiff);
3300  } else {
3301  ERR_SERVER << "ERROR: delete_game(): Could not find user in players_.";
3302  }
3303  }
3304 
3305  // Put the remaining users back in the lobby.
3306  // This will call cleanup_game() deleter since there won't
3307  // be any references to that game from player_connections_ anymore
3308  for(const auto& it : range_vctor) {
3309  player_connections_.get<game_t>().modify(it, std::bind(&player_record::enter_lobby, std::placeholders::_1));
3310  }
3311 
3312  // send users in the game a notification to leave the game since it has ended
3313  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
3314 
3315  for(const auto& it : range_vctor) {
3316  player_iterator p { player_connections_.project<0>(it) };
3317  if(reason != "") {
3318  simple_wml::document leave_game_doc_reason("[leave_game]\n[/leave_game]\n", simple_wml::INIT_STATIC);
3319  leave_game_doc_reason.child("leave_game")->set_attr_dup("reason", reason.c_str());
3320  send_to_player(p, leave_game_doc_reason);
3321  } else {
3322  send_to_player(p, leave_game_doc);
3323  }
3325  }
3326 }
3327 
3328 void server::update_game_in_lobby(wesnothd::game& g, utils::optional<player_iterator> exclude)
3329 {
3330  simple_wml::document diff;
3331  if(auto p_desc = g.changed_description()) {
3332  if(make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", p_desc, diff)) {
3333  send_to_lobby(diff, exclude);
3334  }
3335  }
3336 }
3337 
3338 } // namespace wesnothd
3339 
3340 int main(int argc, char** argv)
3341 {
3342  int port = 15000;
3343  bool keep_alive = false;
3344 
3345  srand(static_cast<unsigned>(std::time(nullptr)));
3346 
3347  std::string config_file;
3348 
3349  // setting path to currentworking directory
3351 
3352  // show 'info' by default
3354  lg::timestamps(true);
3355 
3356  for(int arg = 1; arg != argc; ++arg) {
3357  const std::string val(argv[arg]);
3358  if(val.empty()) {
3359  continue;
3360  }
3361 
3362  if((val == "--config" || val == "-c") && arg + 1 != argc) {
3363  config_file = argv[++arg];
3364  } else if(val == "--verbose" || val == "-v") {
3366  } else if(val == "--dump-wml" || val == "-w") {
3367  dump_wml = true;
3368  } else if(val.substr(0, 6) == "--log-") {
3369  std::size_t p = val.find('=');
3370  if(p == std::string::npos) {
3371  PLAIN_LOG << "unknown option: " << val;
3372  return 2;
3373  }
3374 
3375  std::string s = val.substr(6, p - 6);
3377 
3378  if(s == "error") {
3380  } else if(s == "warning") {
3382  } else if(s == "info") {
3384  } else if(s == "debug") {
3386  } else {
3387  PLAIN_LOG << "unknown debug level: " << s;
3388  return 2;
3389  }
3390 
3391  while(p != std::string::npos) {
3392  std::size_t q = val.find(',', p + 1);
3393  s = val.substr(p + 1, q == std::string::npos ? q : q - (p + 1));
3394 
3396  PLAIN_LOG << "unknown debug domain: " << s;
3397  return 2;
3398  }
3399 
3400  p = q;
3401  }
3402  } else if((val == "--port" || val == "-p") && arg + 1 != argc) {
3403  port = atoi(argv[++arg]);
3404  } else if(val == "--keepalive") {
3405  keep_alive = true;
3406  } else if(val == "--help" || val == "-h") {
3407  std::cout << "usage: " << argv[0]
3408  << " [-dvwV] [-c path] [-p port]\n"
3409  << " -c, --config <path> Tells wesnothd where to find the config file to use.\n"
3410  << " -d, --daemon Runs wesnothd as a daemon.\n"
3411  << " -h, --help Shows this usage message.\n"
3412  << " --log-<level>=<domain1>,<domain2>,...\n"
3413  << " sets the severity level of the debug domains.\n"
3414  << " 'all' can be used to match any debug domain.\n"
3415  << " Available levels: error, warning, info, debug.\n"
3416  << " -p, --port <port> Binds the server to the specified port.\n"
3417  << " --keepalive Enable TCP keepalive.\n"
3418  << " -v --verbose Turns on more verbose logging.\n"
3419  << " -V, --version Returns the server version.\n"
3420  << " -w, --dump-wml Print all WML sent to clients to stdout.\n";
3421  return 0;
3422  } else if(val == "--version" || val == "-V") {
3423  std::cout << "Battle for Wesnoth server " << game_config::wesnoth_version.str() << "\n";
3424  return 0;
3425  } else if(val == "--daemon" || val == "-d") {
3426 #ifdef _WIN32
3427  ERR_SERVER << "Running as a daemon is not supported on this platform";
3428  return -1;
3429 #else
3430  const pid_t pid = fork();
3431  if(pid < 0) {
3432  ERR_SERVER << "Could not fork and run as a daemon";
3433  return -1;
3434  } else if(pid > 0) {
3435  std::cout << "Started wesnothd as a daemon with process id " << pid << "\n";
3436  return 0;
3437  }
3438 
3439  setsid();
3440 #endif
3441  } else if(val == "--request_sample_frequency" && arg + 1 != argc) {
3442  wesnothd::request_sample_frequency = atoi(argv[++arg]);
3443  } else {
3444  ERR_SERVER << "unknown option: " << val;
3445  return 2;
3446  }
3447  }
3448 
3449  return wesnothd::server(port, keep_alive, config_file).run();
3450 }
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:3328
void send_to_lobby(simple_wml::document &data, utils::optional< player_iterator > exclude={})
Definition: server.cpp:2326
int failed_login_limit_
Definition: server.hpp:202
void stopgame_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3241
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:1746
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:3168
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:2803
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:2862
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:3066
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:2522
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:3284
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:3001
void pm_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2656
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:2611
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:1768
std::string restart_command
Definition: server.hpp:194
void lobbymsg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2720
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:2761
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:2837
void kick_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3096
void dul_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3221
void handle_join_server_queue(player_iterator p, simple_wml::node &data)
Definition: server.cpp:1688
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:2602
void send_server_message(SocketPtr socket, const std::string &message, const std::string &type)
Definition: server.cpp:2251
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:2513
void requests_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2541
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:2355
void remove_player(player_iterator player)
Definition: server.cpp:2275
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:3265
std::string admin_passwd_
Definition: server.hpp:183
void motd_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3147
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:2336
void kickban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2925
void send_server_message_to_all(const std::string &message, utils::optional< player_iterator > exclude={})
Definition: server.cpp:2346
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:2371
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:2457
void msg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2698
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:2426
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:2532
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:2550
void games_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2593
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:2741
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:2492
void start_lan_server_timer()
Definition: server.cpp:305
void disconnect_player(player_iterator player)
Definition: server.cpp:2262
bool allow_remote_shutdown_
Definition: server.hpp:199
void ungban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:3081
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