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