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