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