The Battle for Wesnoth  1.19.7+dev
server.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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  input_.assign(fifo);
335  LOG_SERVER << "opened fifo at '" << input_path_ << "'. Server commands may be written to this file.";
336  read_from_fifo();
337 #endif
338 }
339 
340 #ifndef _WIN32
341 
342 void server::handle_read_from_fifo(const boost::system::error_code& error, std::size_t)
343 {
344  if(error) {
345  std::cout << error.message() << std::endl;
346  return;
347  }
348 
349  std::istream is(&admin_cmd_);
350  std::string cmd;
351  std::getline(is, cmd);
352 
353  LOG_SERVER << "Admin Command: type: " << cmd;
354 
355  const std::string res = process_command(cmd, "*socket*");
356 
357  // Only mark the response if we fake the issuer (i.e. command comes from IRC or so)
358  if(!cmd.empty() && cmd.at(0) == '+') {
359  LOG_SERVER << "[admin_command_response]\n"
360  << res << "\n"
361  << "[/admin_command_response]";
362  } else {
363  LOG_SERVER << res;
364  }
365 
366  read_from_fifo();
367 }
368 
369 #endif
370 
372 {
373 #define SETUP_HANDLER(name, function) \
374  cmd_handlers_[name] = std::bind(function, this, \
375  std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
376 
391  SETUP_HANDLER("privatemsg", &server::pm_handler);
393  SETUP_HANDLER("lobbymsg", &server::msg_handler);
408  SETUP_HANDLER("deny_unregistered_login", &server::dul_handler);
409  SETUP_HANDLER("stopgame", &server::stopgame);
410 
411 #undef SETUP_HANDLER
412 }
413 
415 {
416  config configuration;
417 
418  if(config_file_.empty()) {
419  return configuration;
420  }
421 
422  try {
423  // necessary to avoid assert since preprocess_file() goes through filesystem::get_short_wml_path()
424  filesystem::set_user_data_dir(std::string());
426  read(configuration, *stream);
427  LOG_SERVER << "Server configuration from file: '" << config_file_ << "' read.";
428  } catch(const config::error& e) {
429  ERR_CONFIG << "ERROR: Could not read configuration file: '" << config_file_ << "': '" << e.message << "'.";
430  }
431 
432  return configuration;
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 
1380  DBG_SERVER << player->client_ip() << "\t" << player->info().name()
1381  << "\tcreates a new game: \"" << game_name << "\".";
1382 
1383  // Create the new game, remove the player from the lobby
1384  // and set the player as the host/owner.
1385  player_connections_.modify(player, [this, player, &game_name](player_record& host_record) {
1386  host_record.get_game().reset(
1388  std::bind(&server::cleanup_game, this, std::placeholders::_1)
1389  );
1390  });
1391 
1392  wesnothd::game& g = *player->get_game();
1393 
1394  DBG_SERVER << "initial bans: " << initial_bans;
1395  if(initial_bans != "") {
1396  g.set_name_bans(utils::split(initial_bans,','));
1397  }
1398 
1399  if(game_password.empty() == false) {
1400  g.set_password(game_password);
1401  }
1402 
1403  create_game.copy_into(g.level().root());
1404 }
1405 
1407 {
1409 
1410  if(user_handler_){
1411  user_handler_->db_update_game_end(uuid_, game_ptr->db_id(), game_ptr->get_replay_filename());
1412  }
1413 
1414  simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
1415  assert(gamelist != nullptr);
1416 
1417  // Send a diff of the gamelist with the game deleted to players in the lobby
1418  simple_wml::document diff;
1419  if(!destructed && make_delete_diff(*gamelist, "gamelist", "game", game_ptr->description(), diff)) {
1420  send_to_lobby(diff);
1421  }
1422 
1423  // Delete the game from the games_and_users_list_.
1424  const simple_wml::node::child_list& games = gamelist->children("game");
1425  const auto g = std::find(games.begin(), games.end(), game_ptr->description());
1426 
1427  if(g != games.end()) {
1428  const std::size_t index = std::distance(games.begin(), g);
1429  gamelist->remove_child("game", index);
1430  } else {
1431  // Can happen when the game ends before the scenario was transferred.
1432  LOG_SERVER << "Could not find game (" << game_ptr->id() << ", " << game_ptr->db_id() << ") to delete in games_and_users_list_.";
1433  }
1434 
1435  if(destructed) game_ptr->emergency_cleanup();
1436 
1437  delete game_ptr;
1438 }
1439 
1441 {
1442  const bool observer = join.attr("observe").to_bool();
1443  const std::string& password = join["password"].to_string();
1444  int game_id = join["id"].to_int();
1445 
1446  auto g_iter = player_connections_.get<game_t>().find(game_id);
1447 
1448  std::shared_ptr<game> g;
1449  if(g_iter != player_connections_.get<game_t>().end()) {
1450  g = g_iter->get_game();
1451  }
1452 
1453  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
1454  if(!g) {
1455  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1456  << "\tattempted to join unknown game:\t" << game_id << ".";
1457  send_to_player(player, leave_game_doc);
1458  send_server_message(player, "Attempt to join unknown game.", "error");
1460  return;
1461  } else if(!g->level_init()) {
1462  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1463  << "\tattempted to join uninitialized game:\t\"" << g->name() << "\" (" << game_id << ").";
1464  send_to_player(player, leave_game_doc);
1465  send_server_message(player, "Attempt to join an uninitialized game.", "error");
1467  return;
1468  } else if(player->info().is_moderator()) {
1469  // Admins are always allowed to join.
1470  } else if(g->player_is_banned(player, player->info().name())) {
1471  DBG_SERVER << player->client_ip()
1472  << "\tReject banned player: " << player->info().name()
1473  << "\tfrom game:\t\"" << g->name() << "\" (" << game_id << ").";
1474  send_to_player(player, leave_game_doc);
1475  send_server_message(player, "You are banned from this game.", "error");
1477  return;
1478  } else if(!g->password_matches(password)) {
1479  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1480  << "\tattempted to join game:\t\"" << g->name() << "\" (" << game_id << ") with bad password";
1481  send_to_player(player, leave_game_doc);
1482  send_server_message(player, "Incorrect password.", "error");
1484  return;
1485  }
1486 
1487  bool joined = g->add_player(player, observer);
1488  if(!joined) {
1489  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1490  << "\tattempted to observe game:\t\"" << g->name() << "\" (" << game_id
1491  << ") which doesn't allow observers.";
1492  send_to_player(player, leave_game_doc);
1493 
1495  "Attempt to observe a game that doesn't allow observers. (You probably joined the "
1496  "game shortly after it filled up.)", "error");
1497 
1499  return;
1500  }
1501 
1502  player_connections_.modify(player,
1503  std::bind(&player_record::set_game, std::placeholders::_1, g));
1504 
1505  g->describe_slots();
1506 
1507  // send notification of changes to the game and user
1508  simple_wml::document diff;
1509  bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g->changed_description(), diff);
1510  bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user",
1511  player->info().config_address(), diff);
1512 
1513  if(diff1 || diff2) {
1514  send_to_lobby(diff);
1515  }
1516 }
1517 
1519 {
1520  DBG_SERVER << "in process_data_game...";
1521 
1522  wesnothd::player& player { p->info() };
1523 
1524  game& g = *(p->get_game());
1525  std::weak_ptr<game> g_ptr{p->get_game()};
1526 
1527  // If this is data describing the level for a game.
1528  if(data.child("snapshot") || data.child("scenario")) {
1529  if(!g.is_owner(p)) {
1530  return;
1531  }
1532 
1533  // If this game is having its level data initialized
1534  // for the first time, and is ready for players to join.
1535  // We should currently have a summary of the game in g.level().
1536  // We want to move this summary to the games_and_users_list_, and
1537  // place a pointer to that summary in the game's description.
1538  // g.level() should then receive the full data for the game.
1539  if(!g.level_init()) {
1540  LOG_SERVER << p->client_ip() << "\t" << player.name() << "\tcreated game:\t\"" << g.name() << "\" ("
1541  << g.id() << ", " << g.db_id() << ").";
1542  // Update our config object which describes the open games,
1543  // and save a pointer to the description in the new game.
1544  simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
1545  assert(gamelist != nullptr);
1546 
1547  simple_wml::node& desc = gamelist->add_child("game");
1548  g.level().root().copy_into(desc);
1549 
1550  if(const simple_wml::node* m = data.child("multiplayer")) {
1551  m->copy_into(desc);
1552  } else {
1553  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1554  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1555  // Set the description so it can be removed in delete_game().
1556  g.set_description(&desc);
1557  delete_game(g.id());
1558 
1560  "The scenario data is missing the [multiplayer] tag which contains the "
1561  "game settings. Game aborted.", "error");
1562  return;
1563  }
1564 
1565  g.set_description(&desc);
1566  desc.set_attr_dup("id", std::to_string(g.id()).c_str());
1567  } else {
1568  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1569  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") although it's already initialized.";
1570  return;
1571  }
1572 
1573  assert(games_and_users_list_.child("gamelist")->children("game").empty() == false);
1574 
1575  simple_wml::node& desc = *g.description_for_writing();
1576 
1577  // Update the game's description.
1578  // If there is no shroud, then tell players in the lobby
1579  // what the map looks like
1581  // fixme: the hanlder of [store_next_scenario] below searches for 'mp_shroud' in [scenario]
1582  // at least of the these cosed is likely wrong.
1583  if(!data["mp_shroud"].to_bool()) {
1584  desc.set_attr_dup("map_data", s["map_data"]);
1585  }
1586 
1587  if(const simple_wml::node* e = data.child("era")) {
1588  if(!e->attr("require_era").to_bool(true)) {
1589  desc.set_attr("require_era", "no");
1590  }
1591  }
1592 
1593  if(s["require_scenario"].to_bool(false)) {
1594  desc.set_attr("require_scenario", "yes");
1595  }
1596 
1597  const simple_wml::node::child_list& mlist = data.children("modification");
1598  for(const simple_wml::node* m : mlist) {
1599  desc.add_child_at("modification", 0);
1600  desc.child("modification")->set_attr_dup("id", m->attr("id"));
1601  desc.child("modification")->set_attr_dup("name", m->attr("name"));
1602  desc.child("modification")->set_attr_dup("addon_id", m->attr("addon_id"));
1603  desc.child("modification")->set_attr_dup("require_modification", m->attr("require_modification"));
1604  }
1605 
1606  // Record the full scenario in g.level()
1607  g.level().swap(data);
1608 
1609  // The host already put himself in the scenario so we just need
1610  // to update_side_data().
1611  // g.take_side(sock);
1612  g.update_side_data();
1613  g.describe_slots();
1614 
1615  // Send the update of the game description to the lobby.
1616  simple_wml::document diff;
1617  make_add_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", diff);
1618  make_change_diff(games_and_users_list_.root(), nullptr, "user", p->info().config_address(), diff);
1619 
1620  send_to_lobby(diff);
1621 
1622  /** @todo FIXME: Why not save the level data in the history_? */
1623  return;
1624  // Everything below should only be processed if the game is already initialized.
1625  } else if(!g.level_init()) {
1626  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
1627  << " while the scenario wasn't yet initialized."
1628  << data.output();
1629  return;
1630  // If the host is sending the next scenario data.
1631  } else if(const simple_wml::node* scenario = data.child("store_next_scenario")) {
1632  if(!g.is_owner(p)) {
1633  return;
1634  }
1635 
1636  if(!g.level_init()) {
1637  WRN_SERVER << p->client_ip() << "\tWarning: " << player.name()
1638  << "\tsent [store_next_scenario] in game:\t\"" << g.name() << "\" (" << g.id()
1639  << ", " << g.db_id() << ") while the scenario is not yet initialized.";
1640  return;
1641  }
1642 
1643  g.save_replay();
1644  if(user_handler_){
1645  user_handler_->db_update_game_end(uuid_, g.db_id(), g.get_replay_filename());
1646  }
1647 
1648  g.new_scenario(p);
1649  g.reset_last_synced_context_id();
1650 
1651  // Record the full scenario in g.level()
1652  g.level().clear();
1653  scenario->copy_into(g.level().root());
1654  g.next_db_id();
1655 
1656  if(g.description() == nullptr) {
1657  ERR_SERVER << p->client_ip() << "\tERROR: \"" << g.name() << "\" (" << g.id()
1658  << ", " << g.db_id() << ") is initialized but has no description_.";
1659  return;
1660  }
1661 
1662  simple_wml::node& desc = *g.description_for_writing();
1663 
1664  // Update the game's description.
1665  if(const simple_wml::node* m = scenario->child("multiplayer")) {
1666  m->copy_into(desc);
1667  } else {
1668  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1669  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1670 
1671  delete_game(g.id());
1672 
1674  "The scenario data is missing the [multiplayer] tag which contains the game "
1675  "settings. Game aborted.", "error");
1676  return;
1677  }
1678 
1679  // If there is no shroud, then tell players in the lobby
1680  // what the map looks like.
1681  const simple_wml::node& s = *wesnothd::game::starting_pos(g.level().root());
1682  desc.set_attr_dup("map_data", s["mp_shroud"].to_bool() ? "" : s["map_data"]);
1683 
1684  if(const simple_wml::node* e = data.child("era")) {
1685  if(!e->attr("require_era").to_bool(true)) {
1686  desc.set_attr("require_era", "no");
1687  }
1688  }
1689 
1690  if(s["require_scenario"].to_bool(false)) {
1691  desc.set_attr("require_scenario", "yes");
1692  }
1693 
1694  // Tell everyone that the next scenario data is available.
1695  static simple_wml::document notify_next_scenario(
1696  "[notify_next_scenario]\n[/notify_next_scenario]\n", simple_wml::INIT_COMPRESSED);
1697  g.send_data(notify_next_scenario, p);
1698 
1699  // Send the update of the game description to the lobby.
1701  return;
1702  // A mp client sends a request for the next scenario of a mp campaign.
1703  } else if(data.child("load_next_scenario")) {
1704  g.load_next_scenario(p);
1705  return;
1706  } else if(data.child("start_game")) {
1707  if(!g.is_owner(p)) {
1708  return;
1709  }
1710 
1711  // perform controller tweaks, assigning sides as human for their owners etc.
1712  g.perform_controller_tweaks();
1713 
1714  // Send notification of the game starting immediately.
1715  // g.start_game() will send data that assumes
1716  // the [start_game] message has been sent
1717  g.send_data(data, p);
1718  g.start_game(p);
1719 
1720  if(user_handler_) {
1721  const simple_wml::node& m = *g.level().root().child("multiplayer");
1723  // [addon] info handling
1724  std::set<std::string> primary_keys;
1725  for(const auto& addon : m.children("addon")) {
1726  for(const auto& content : addon->children("content")) {
1727  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();
1728  if(primary_keys.count(key) == 0) {
1729  primary_keys.emplace(key);
1730  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());
1731  if(rows_inserted == 0) {
1732  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() << "'";
1733  }
1734  }
1735  }
1736  }
1737  if(m.children("addon").size() == 0) {
1738  WRN_SERVER << "Game content info missing for game with uuid '" << uuid_ << "', game ID '" << g.db_id() << "', named '" << g.name() << "'";
1739  }
1740 
1741  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());
1742 
1743  const simple_wml::node::child_list& sides = g.get_sides_list();
1744  for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
1745  const simple_wml::node& side = *sides[side_index];
1746  const auto player = player_connections_.get<name_t>().find(side["player_id"].to_string());
1747  std::string version;
1748  std::string source;
1749 
1750  // if "Nobody" is chosen for a side, for example
1751  if(player == player_connections_.get<name_t>().end()){
1752  version = "";
1753  source = "";
1754  } else {
1755  version = player->info().version();
1756  source = player->info().source();
1757 
1758  if(client_sources_.count(source) == 0) {
1759  source = "Default";
1760  }
1761  }
1762 
1763  // approximately determine leader(s) for the side like the client does
1764  // useful generally to know how often leaders are used vs other leaders
1765  // 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
1766  std::vector<std::string> leaders;
1767  // if a type= attribute is specified for the side, add it
1768  if(side.attr("type") != "") {
1769  leaders.emplace_back(side.attr("type").to_string());
1770  }
1771  // add each [unit] in the side that has canrecruit=yes
1772  for(const auto unit : side.children("unit")) {
1773  if(unit->attr("canrecruit") == "yes") {
1774  leaders.emplace_back(unit->attr("type").to_string());
1775  }
1776  }
1777  // add any [leader] specified for the side
1778  for(const auto leader : side.children("leader")) {
1779  leaders.emplace_back(leader->attr("type").to_string());
1780  }
1781 
1782  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));
1783  }
1784  }
1785 
1786  // update the game having changed in the lobby
1788  return;
1789  } else if(data.child("leave_game")) {
1790  if(g.remove_player(p)) {
1791  delete_game(g.id());
1792  } else {
1793  bool has_diff = false;
1794  simple_wml::document diff;
1795 
1796  // After this line, the game object may be destroyed. Don't use `g`!
1797  player_connections_.modify(p, std::bind(&player_record::enter_lobby, std::placeholders::_1));
1798 
1799  // Only run this if the game object is still valid
1800  if(auto gStrong = g_ptr.lock()) {
1801  gStrong->describe_slots();
1802  //Don't update the game if it no longer exists.
1803  has_diff |= make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", gStrong->description(), diff);
1804  }
1805 
1806  // Send all other players in the lobby the update to the gamelist.
1807  has_diff |= make_change_diff(games_and_users_list_.root(), nullptr, "user", player.config_address(), diff);
1808 
1809  if(has_diff) {
1810  send_to_lobby(diff, p);
1811  }
1812 
1813  // Send the player who has quit the gamelist.
1815  }
1816 
1817  return;
1818  // If this is data describing side changes by the host.
1819  } else if(const simple_wml::node* scenario_diff = data.child("scenario_diff")) {
1820  if(!g.is_owner(p)) {
1821  return;
1822  }
1823 
1824  g.level().root().apply_diff(*scenario_diff);
1825  const simple_wml::node* cfg_change = scenario_diff->child("change_child");
1826 
1827  if(cfg_change) {
1828  g.update_side_data();
1829  }
1830 
1831  g.describe_slots();
1833 
1834  g.send_data(data, p);
1835  return;
1836  // If a player changes his faction.
1837  } else if(data.child("change_faction")) {
1838  g.send_data(data, p);
1839  return;
1840  // If the owner of a side is changing the controller.
1841  } else if(const simple_wml::node* change = data.child("change_controller")) {
1842  g.transfer_side_control(p, *change);
1843  g.describe_slots();
1845 
1846  return;
1847  // If all observers should be muted. (toggles)
1848  } else if(data.child("muteall")) {
1849  if(!g.is_owner(p)) {
1850  g.send_server_message("You cannot mute: not the game host.", p);
1851  return;
1852  }
1853 
1854  g.mute_all_observers();
1855  return;
1856  // If an observer should be muted.
1857  } else if(const simple_wml::node* mute = data.child("mute")) {
1858  g.mute_observer(*mute, p);
1859  return;
1860  // If an observer should be unmuted.
1861  } else if(const simple_wml::node* unmute = data.child("unmute")) {
1862  g.unmute_observer(*unmute, p);
1863  return;
1864  // The owner is kicking/banning someone from the game.
1865  } else if(data.child("kick") || data.child("ban")) {
1866  bool ban = (data.child("ban") != nullptr);
1867  auto user { ban
1868  ? g.ban_user(*data.child("ban"), p)
1869  : g.kick_member(*data.child("kick"), p)};
1870 
1871  if(user) {
1872  player_connections_.modify(*user, std::bind(&player_record::enter_lobby, std::placeholders::_1));
1873  g.describe_slots();
1874 
1875  update_game_in_lobby(g, user);
1876 
1877  // Send all other players in the lobby the update to the gamelist.
1878  simple_wml::document gamelist_diff;
1879  make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), gamelist_diff);
1880  make_change_diff(games_and_users_list_.root(), nullptr, "user", (*user)->info().config_address(), gamelist_diff);
1881 
1882  send_to_lobby(gamelist_diff, p);
1883 
1884  // Send the removed user the lobby game list.
1886  }
1887 
1888  return;
1889  } else if(const simple_wml::node* unban = data.child("unban")) {
1890  g.unban_user(*unban, p);
1891  return;
1892  // If info is being provided about the game state.
1893  } else if(const simple_wml::node* info = data.child("info")) {
1894  if(!g.is_player(p)) {
1895  return;
1896  }
1897 
1898  if((*info)["type"] == "termination") {
1899  g.set_termination_reason((*info)["condition"].to_string());
1900  if((*info)["condition"].to_string() == "out of sync") {
1901  g.send_and_record_server_message(player.name() + " reports out of sync errors.");
1902  if(user_handler_){
1903  user_handler_->db_set_oos_flag(uuid_, g.db_id());
1904  }
1905  }
1906  }
1907 
1908  return;
1909  } else if(data.child("turn")) {
1910  // Notify the game of the commands, and if it changes
1911  // the description, then sync the new description
1912  // to players in the lobby.
1913  g.process_turn(data, p);
1915 
1916  return;
1917  } else if(data.child("whiteboard")) {
1918  g.process_whiteboard(data, p);
1919  return;
1920  } else if(data.child("change_turns_wml")) {
1921  g.process_change_turns_wml(data, p);
1923  return;
1924  } else if(simple_wml::node* sch = data.child("request_choice")) {
1925  g.handle_choice(*sch, p);
1926  return;
1927  } else if(data.child("message")) {
1928  g.process_message(data, p);
1929  return;
1930  } else if(data.child("stop_updates")) {
1931  g.send_data(data, p);
1932  return;
1933  // Data to ignore.
1934  } else if(
1935  data.child("error") ||
1936  data.child("side_secured") ||
1937  data.root().has_attr("failed") ||
1938  data.root().has_attr("side")
1939  ) {
1940  return;
1941  }
1942 
1943  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
1944  << " in game: \"" << g.name() << "\" (" << g.id() << ", " << g.db_id() << ")\n"
1945  << data.output();
1946 }
1947 
1948 template<class SocketPtr> void server::send_server_message(SocketPtr socket, const std::string& message, const std::string& type)
1949 {
1950  simple_wml::document server_message;
1951  simple_wml::node& msg = server_message.root().add_child("message");
1952  msg.set_attr("sender", "server");
1953  msg.set_attr_dup("message", message.c_str());
1954  msg.set_attr_dup("type", type.c_str());
1955 
1956  async_send_doc_queued(socket, server_message);
1957 }
1958 
1960 {
1961  utils::visit([](auto&& socket) {
1962  if constexpr (utils::decayed_is_same<tls_socket_ptr, decltype(socket)>) {
1963  socket->async_shutdown([socket](...) {});
1964  const char buffer[] = "";
1965  async_write(*socket, boost::asio::buffer(buffer), [socket](...) { socket->lowest_layer().close(); });
1966  } else {
1967  socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
1968  }
1969  }, player->socket());
1970 }
1971 
1973 {
1974  std::string ip = iter->client_ip();
1975 
1976  const std::shared_ptr<game> g = iter->get_game();
1977  bool game_ended = false;
1978  if(g) {
1979  game_ended = g->remove_player(iter, true, false);
1980  }
1981 
1983  const std::size_t index =
1984  std::distance(users.begin(), std::find(users.begin(), users.end(), iter->info().config_address()));
1985 
1986  // Notify other players in lobby
1987  simple_wml::document diff;
1988  if(make_delete_diff(games_and_users_list_.root(), nullptr, "user", iter->info().config_address(), diff)) {
1989  send_to_lobby(diff, iter);
1990  }
1991 
1993 
1994  LOG_SERVER << ip << "\t" << iter->info().name() << "\thas logged off";
1995 
1996  // Find the matching nick-ip pair in the log and update the sign off time
1997  if(user_handler_) {
1998  user_handler_->db_update_logout(iter->info().get_login_id());
1999  } else {
2000  connection_log ip_name { iter->info().name(), ip, {} };
2001 
2002  auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name);
2003  if(i != ip_log_.end()) {
2004  i->log_off = std::chrono::system_clock::now();
2005  }
2006  }
2007 
2008  player_connections_.erase(iter);
2009 
2010  if(lan_server_ > 0s && player_connections_.size() == 0)
2012 
2013  if(game_ended) delete_game(g->id());
2014 }
2015 
2016 void server::send_to_lobby(simple_wml::document& data, utils::optional<player_iterator> exclude)
2017 {
2018  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
2019  auto player { player_connections_.iterator_to(p) };
2020  if(player != exclude) {
2022  }
2023  }
2024 }
2025 
2026 void server::send_server_message_to_lobby(const std::string& message, utils::optional<player_iterator> exclude)
2027 {
2028  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
2029  auto player { player_connections_.iterator_to(p) };
2030  if(player != exclude) {
2031  send_server_message(player, message, "alert");
2032  }
2033  }
2034 }
2035 
2036 void server::send_server_message_to_all(const std::string& message, utils::optional<player_iterator> exclude)
2037 {
2038  for(auto player = player_connections_.begin(); player != player_connections_.end(); ++player) {
2039  if(player != exclude) {
2040  send_server_message(player, message, "alert");
2041  }
2042  }
2043 }
2044 
2046 {
2047  if(restart_command.empty()) {
2048  return;
2049  }
2050 
2051  // Example config line:
2052  // restart_command="./wesnothd-debug -d -c ~/.wesnoth1.5/server.cfg"
2053  // remember to make new one as a daemon or it will block old one
2054  if(std::system(restart_command.c_str())) {
2055  ERR_SERVER << "Failed to start new server with command: " << restart_command;
2056  } else {
2057  LOG_SERVER << "New server started with command: " << restart_command;
2058  }
2059 }
2060 
2061 std::string server::process_command(std::string query, std::string issuer_name)
2062 {
2063  boost::trim(query);
2064 
2065  if(issuer_name == "*socket*" && !query.empty() && query.at(0) == '+') {
2066  // The first argument might be "+<issuer>: ".
2067  // In that case we use +<issuer>+ as the issuer_name.
2068  // (Mostly used for communication with IRC.)
2069  auto issuer_end = std::find(query.begin(), query.end(), ':');
2070 
2071  std::string issuer(query.begin() + 1, issuer_end);
2072  if(!issuer.empty()) {
2073  issuer_name = "+" + issuer + "+";
2074  query = std::string(issuer_end + 1, query.end());
2075  boost::trim(query);
2076  }
2077  }
2078 
2079  const auto i = std::find(query.begin(), query.end(), ' ');
2080 
2081  try {
2082  const std::string command = utf8::lowercase(std::string(query.begin(), i));
2083 
2084  std::string parameters = (i == query.end() ? "" : std::string(i + 1, query.end()));
2085  boost::trim(parameters);
2086 
2087  std::ostringstream out;
2088  auto handler_itor = cmd_handlers_.find(command);
2089 
2090  if(handler_itor == cmd_handlers_.end()) {
2091  out << "Command '" << command << "' is not recognized.\n" << help_msg;
2092  } else {
2093  const cmd_handler& handler = handler_itor->second;
2094  try {
2095  handler(issuer_name, query, parameters, &out);
2096  } catch(const std::bad_function_call& ex) {
2097  ERR_SERVER << "While handling a command '" << command
2098  << "', caught a std::bad_function_call exception.";
2099  ERR_SERVER << ex.what();
2100  out << "An internal server error occurred (std::bad_function_call) while executing '" << command
2101  << "'\n";
2102  }
2103  }
2104 
2105  return out.str();
2106 
2107  } catch(const utf8::invalid_utf8_exception& e) {
2108  std::string msg = "While handling a command, caught an invalid utf8 exception: ";
2109  msg += e.what();
2110  ERR_SERVER << msg;
2111  return (msg + '\n');
2112  }
2113 }
2114 
2115 // Shutdown, restart and sample commands can only be issued via the socket.
2117  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2118 {
2119  assert(out != nullptr);
2120 
2121  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2122  *out << denied_msg;
2123  return;
2124  }
2125 
2126  if(parameters == "now") {
2127  BOOST_THROW_EXCEPTION(server_shutdown("shut down by admin command"));
2128  } else {
2129  // Graceful shut down.
2130  graceful_restart = true;
2131  acceptor_v6_.close();
2132  acceptor_v4_.close();
2133 
2134  timer_.expires_after(10s);
2135  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2136 
2138  "msg The server is shutting down. You may finish your games but can't start new ones. Once all "
2139  "games have ended the server will exit.",
2140  issuer_name
2141  );
2142 
2143  *out << "Server is doing graceful shut down.";
2144  }
2145 }
2146 
2147 void server::restart_handler(const std::string& issuer_name,
2148  const std::string& /*query*/,
2149  std::string& /*parameters*/,
2150  std::ostringstream* out)
2151 {
2152  assert(out != nullptr);
2153 
2154  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2155  *out << denied_msg;
2156  return;
2157  }
2158 
2159  if(restart_command.empty()) {
2160  *out << "No restart_command configured! Not restarting.";
2161  } else {
2162  graceful_restart = true;
2163  acceptor_v6_.close();
2164  acceptor_v4_.close();
2165  timer_.expires_after(10s);
2166  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2167 
2168  start_new_server();
2169 
2171  "msg The server has been restarted. You may finish current games but can't start new ones and "
2172  "new players can't join this (old) server instance. (So if a player of your game disconnects "
2173  "you have to save, reconnect and reload the game on the new server instance. It is actually "
2174  "recommended to do that right away.)",
2175  issuer_name
2176  );
2177 
2178  *out << "New server started.";
2179  }
2180 }
2181 
2183  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2184 {
2185  assert(out != nullptr);
2186 
2187  if(parameters.empty()) {
2188  *out << "Current sample frequency: " << request_sample_frequency;
2189  return;
2190  } else if(issuer_name != "*socket*") {
2191  *out << denied_msg;
2192  return;
2193  }
2194 
2195  request_sample_frequency = atoi(parameters.c_str());
2196  if(request_sample_frequency <= 0) {
2197  *out << "Sampling turned off.";
2198  } else {
2199  *out << "Sampling every " << request_sample_frequency << " requests.";
2200  }
2201 }
2202 
2203 void server::help_handler(const std::string& /*issuer_name*/,
2204  const std::string& /*query*/,
2205  std::string& /*parameters*/,
2206  std::ostringstream* out)
2207 {
2208  assert(out != nullptr);
2209  *out << help_msg;
2210 }
2211 
2212 void server::stats_handler(const std::string& /*issuer_name*/,
2213  const std::string& /*query*/,
2214  std::string& /*parameters*/,
2215  std::ostringstream* out)
2216 {
2217  assert(out != nullptr);
2218 
2219  *out << "Number of games = " << games().size() << "\nTotal number of users = " << player_connections_.size();
2220 }
2221 
2222 void server::metrics_handler(const std::string& /*issuer_name*/,
2223  const std::string& /*query*/,
2224  std::string& /*parameters*/,
2225  std::ostringstream* out)
2226 {
2227  assert(out != nullptr);
2228  *out << metrics_;
2229 }
2230 
2231 void server::requests_handler(const std::string& /*issuer_name*/,
2232  const std::string& /*query*/,
2233  std::string& /*parameters*/,
2234  std::ostringstream* out)
2235 {
2236  assert(out != nullptr);
2237  metrics_.requests(*out);
2238 }
2239 
2240 void server::roll_handler(const std::string& issuer_name,
2241  const std::string& /*query*/,
2242  std::string& parameters,
2243  std::ostringstream* out)
2244 {
2245  assert(out != nullptr);
2246  if(parameters.empty()) {
2247  return;
2248  }
2249 
2250  int N;
2251  try {
2252  N = std::stoi(parameters);
2253  } catch(const std::invalid_argument&) {
2254  *out << "The number of die sides must be a number!";
2255  return;
2256  } catch(const std::out_of_range&) {
2257  *out << "The number of sides is too big for the die!";
2258  return;
2259  }
2260 
2261  if(N < 1) {
2262  *out << "The die cannot have less than 1 side!";
2263  return;
2264  }
2265  std::uniform_int_distribution<int> dice_distro(1, N);
2266  std::string value = std::to_string(dice_distro(die_));
2267 
2268  *out << "You rolled a die [1 - " + parameters + "] and got a " + value + ".";
2269 
2270  auto player_ptr = player_connections_.get<name_t>().find(issuer_name);
2271  if(player_ptr == player_connections_.get<name_t>().end()) {
2272  return;
2273  }
2274 
2275  auto g_ptr = player_ptr->get_game();
2276  if(g_ptr) {
2277  g_ptr->send_server_message_to_all(issuer_name + " rolled a die [1 - " + parameters + "] and got a " + value + ".", player_connections_.project<0>(player_ptr));
2278  } else {
2279  *out << " (The result is shown to others only in a game.)";
2280  }
2281 }
2282 
2283 void server::games_handler(const std::string& /*issuer_name*/,
2284  const std::string& /*query*/,
2285  std::string& /*parameters*/,
2286  std::ostringstream* out)
2287 {
2288  assert(out != nullptr);
2289  metrics_.games(*out);
2290 }
2291 
2292 void server::wml_handler(const std::string& /*issuer_name*/,
2293  const std::string& /*query*/,
2294  std::string& /*parameters*/,
2295  std::ostringstream* out)
2296 {
2297  assert(out != nullptr);
2298  *out << simple_wml::document::stats();
2299 }
2300 
2302  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2303 {
2304  assert(out != nullptr);
2305 
2306  if(parameters.empty()) {
2307  *out << "You must type a message.";
2308  return;
2309  }
2310 
2311  const std::string& sender = issuer_name;
2312  const std::string& message = parameters;
2313  LOG_SERVER << "Admin message: <" << sender
2314  << (message.find("/me ") == 0 ? std::string(message.begin() + 3, message.end()) + ">" : "> " + message);
2315 
2317  simple_wml::node& msg = data.root().add_child("whisper");
2318  msg.set_attr_dup("sender", ("admin message from " + sender).c_str());
2319  msg.set_attr_dup("message", message.c_str());
2320 
2321  int n = 0;
2322  for(const auto& player : player_connections_) {
2323  if(player.info().is_moderator()) {
2324  ++n;
2326  }
2327  }
2328 
2329  bool is_admin = false;
2330 
2331  for(const auto& player : player_connections_) {
2332  if(issuer_name == player.info().name() && player.info().is_moderator()) {
2333  is_admin = true;
2334  break;
2335  }
2336  }
2337 
2338  if(!is_admin) {
2339  *out << "Your report has been logged and sent to the server administrators. Thanks!";
2340  return;
2341  }
2342 
2343  *out << "Your report has been logged and sent to " << n << " online administrators. Thanks!";
2344 }
2345 
2347  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2348 {
2349  assert(out != nullptr);
2350 
2351  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2352  if(first_space == parameters.end()) {
2353  *out << "You must name a receiver.";
2354  return;
2355  }
2356 
2357  const std::string& sender = issuer_name;
2358  const std::string receiver(parameters.begin(), first_space);
2359 
2360  std::string message(first_space + 1, parameters.end());
2361  boost::trim(message);
2362 
2363  if(message.empty()) {
2364  *out << "You must type a message.";
2365  return;
2366  }
2367 
2369  simple_wml::node& msg = data.root().add_child("whisper");
2370 
2371  // This string is parsed by the client!
2372  msg.set_attr_dup("sender", ("server message from " + sender).c_str());
2373  msg.set_attr_dup("message", message.c_str());
2374 
2375  for(const auto& player : player_connections_) {
2376  if(receiver != player.info().name().c_str()) {
2377  continue;
2378  }
2379 
2381  *out << "Message to " << receiver << " successfully sent.";
2382  return;
2383  }
2384 
2385  *out << "No such nick: " << receiver;
2386 }
2387 
2388 void server::msg_handler(const std::string& /*issuer_name*/,
2389  const std::string& /*query*/,
2390  std::string& parameters,
2391  std::ostringstream* out)
2392 {
2393  assert(out != nullptr);
2394 
2395  if(parameters.empty()) {
2396  *out << "You must type a message.";
2397  return;
2398  }
2399 
2400  send_server_message_to_all(parameters);
2401 
2402  LOG_SERVER << "<server"
2403  << (parameters.find("/me ") == 0
2404  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2405  : "> " + parameters);
2406 
2407  *out << "message '" << parameters << "' relayed to players";
2408 }
2409 
2410 void server::lobbymsg_handler(const std::string& /*issuer_name*/,
2411  const std::string& /*query*/,
2412  std::string& parameters,
2413  std::ostringstream* out)
2414 {
2415  assert(out != nullptr);
2416 
2417  if(parameters.empty()) {
2418  *out << "You must type a message.";
2419  return;
2420  }
2421 
2422  send_server_message_to_lobby(parameters);
2423  LOG_SERVER << "<server"
2424  << (parameters.find("/me ") == 0
2425  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2426  : "> " + parameters);
2427 
2428  *out << "message '" << parameters << "' relayed to players";
2429 }
2430 
2432  const std::string& /*issuer_name*/, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2433 {
2434  assert(out != nullptr);
2435 
2436  if(parameters.empty()) {
2437  *out << "Server version is " << game_config::wesnoth_version.str();
2438  return;
2439  }
2440 
2441  for(const auto& player : player_connections_) {
2442  if(parameters == player.info().name()) {
2443  *out << "Player " << parameters << " is using wesnoth " << player.info().version();
2444  return;
2445  }
2446  }
2447 
2448  *out << "Player '" << parameters << "' not found.";
2449 }
2450 
2452  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2453 {
2454  assert(out != nullptr);
2455 
2456  *out << "STATUS REPORT for '" << parameters << "'";
2457  bool found_something = false;
2458 
2459  // If a simple username is given we'll check for its IP instead.
2460  if(utils::isvalid_username(parameters)) {
2461  for(const auto& player : player_connections_) {
2462  if(parameters == player.name()) {
2463  parameters = player.client_ip();
2464  found_something = true;
2465  break;
2466  }
2467  }
2468 
2469  if(!found_something) {
2470  // out << "\nNo match found. You may want to check with 'searchlog'.";
2471  // return out.str();
2472  *out << process_command("searchlog " + parameters, issuer_name);
2473  return;
2474  }
2475  }
2476 
2477  const bool match_ip = ((std::count(parameters.begin(), parameters.end(), '.') >= 1) || (std::count(parameters.begin(), parameters.end(), ':') >= 1));
2478  for(const auto& player : player_connections_) {
2479  if(parameters.empty() || parameters == "*" ||
2480  (match_ip && utils::wildcard_string_match(player.client_ip(), parameters)) ||
2481  (!match_ip && utils::wildcard_string_match(utf8::lowercase(player.info().name()), utf8::lowercase(parameters)))
2482  ) {
2483  found_something = true;
2484  *out << std::endl << player_status(player);
2485  }
2486  }
2487 
2488  if(!found_something) {
2489  *out << "\nNo match found. You may want to check with 'searchlog'.";
2490  }
2491 }
2492 
2493 void server::clones_handler(const std::string& /*issuer_name*/,
2494  const std::string& /*query*/,
2495  std::string& /*parameters*/,
2496  std::ostringstream* out)
2497 {
2498  assert(out != nullptr);
2499  *out << "CLONES STATUS REPORT";
2500 
2501  std::set<std::string> clones;
2502 
2503  for(auto it = player_connections_.begin(); it != player_connections_.end(); ++it) {
2504  if(clones.find(it->client_ip()) != clones.end()) {
2505  continue;
2506  }
2507 
2508  bool found = false;
2509  for(auto clone = std::next(it); clone != player_connections_.end(); ++clone) {
2510  if(it->client_ip() == clone->client_ip()) {
2511  if(!found) {
2512  found = true;
2513  clones.insert(it->client_ip());
2514  *out << std::endl << player_status(*it);
2515  }
2516 
2517  *out << std::endl << player_status(*clone);
2518  }
2519  }
2520  }
2521 
2522  if(clones.empty()) {
2523  *out << std::endl << "No clones found.";
2524  }
2525 }
2526 
2527 void server::bans_handler(const std::string& /*issuer_name*/,
2528  const std::string& /*query*/,
2529  std::string& parameters,
2530  std::ostringstream* out)
2531 {
2532  assert(out != nullptr);
2533 
2534  try {
2535  if(parameters.empty()) {
2536  ban_manager_.list_bans(*out);
2537  } else if(utf8::lowercase(parameters) == "deleted") {
2539  } else if(utf8::lowercase(parameters).find("deleted") == 0) {
2540  std::string mask = parameters.substr(7);
2541  ban_manager_.list_deleted_bans(*out, boost::trim_copy(mask));
2542  } else {
2543  boost::trim(parameters);
2544  ban_manager_.list_bans(*out, parameters);
2545  }
2546 
2547  } catch(const utf8::invalid_utf8_exception& e) {
2548  ERR_SERVER << "While handling bans, caught an invalid utf8 exception: " << e.what();
2549  }
2550 }
2551 
2553  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2554 {
2555  assert(out != nullptr);
2556 
2557  bool banned = false;
2558  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2559 
2560  if(first_space == parameters.end()) {
2561  *out << ban_manager_.get_ban_help();
2562  return;
2563  }
2564 
2565  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2566  const std::string target(parameters.begin(), first_space);
2567  const std::string duration(first_space + 1, second_space);
2568  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
2569 
2570  if(!success) {
2571  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2572  return;
2573  }
2574 
2575  if(second_space == parameters.end()) {
2576  --second_space;
2577  }
2578 
2579  std::string reason(second_space + 1, parameters.end());
2580  boost::trim(reason);
2581 
2582  if(reason.empty()) {
2583  *out << "You need to give a reason for the ban.";
2584  return;
2585  }
2586 
2587  std::string dummy_group;
2588 
2589  // if we find a '.' consider it an ip mask
2590  /** @todo FIXME: make a proper check for valid IPs. */
2591  if(std::count(target.begin(), target.end(), '.') >= 1) {
2592  banned = true;
2593 
2594  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2595  } else {
2596  for(const auto& player : player_connections_) {
2597  if(utils::wildcard_string_match(player.info().name(), target)) {
2598  if(banned) {
2599  *out << "\n";
2600  } else {
2601  banned = true;
2602  }
2603 
2604  const std::string ip = player.client_ip();
2605  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2606  }
2607  }
2608 
2609  if(!banned) {
2610  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2611  }
2612  }
2613 }
2614 
2616  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2617 {
2618  assert(out != nullptr);
2619 
2620  bool banned = false;
2621  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2622  if(first_space == parameters.end()) {
2623  *out << ban_manager_.get_ban_help();
2624  return;
2625  }
2626 
2627  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2628  const std::string target(parameters.begin(), first_space);
2629  const std::string duration(first_space + 1, second_space);
2630  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
2631 
2632  if(!success) {
2633  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2634  return;
2635  }
2636 
2637  if(second_space == parameters.end()) {
2638  --second_space;
2639  }
2640 
2641  std::string reason(second_space + 1, parameters.end());
2642  boost::trim(reason);
2643 
2644  if(reason.empty()) {
2645  *out << "You need to give a reason for the ban.";
2646  return;
2647  }
2648 
2649  std::string dummy_group;
2650  std::vector<player_iterator> users_to_kick;
2651 
2652  // if we find a '.' consider it an ip mask
2653  /** @todo FIXME: make a proper check for valid IPs. */
2654  if(std::count(target.begin(), target.end(), '.') >= 1) {
2655  banned = true;
2656 
2657  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2658 
2660  if(utils::wildcard_string_match(player->client_ip(), target)) {
2661  users_to_kick.push_back(player);
2662  }
2663  }
2664  } else {
2666  if(utils::wildcard_string_match(player->info().name(), target)) {
2667  if(banned) {
2668  *out << "\n";
2669  } else {
2670  banned = true;
2671  }
2672 
2673  const std::string ip = player->client_ip();
2674  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2675  users_to_kick.push_back(player);
2676  }
2677  }
2678 
2679  if(!banned) {
2680  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2681  }
2682  }
2683 
2684  for(auto user : users_to_kick) {
2685  *out << "\nKicked " << user->info().name() << " (" << user->client_ip() << ").";
2686  utils::visit([this,reason](auto&& socket) { async_send_error(socket, "You have been banned. Reason: " + reason); }, user->socket());
2687  disconnect_player(user);
2688  }
2689 }
2690 
2692  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2693 {
2694  assert(out != nullptr);
2695 
2696  bool banned = false;
2697  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2698  if(first_space == parameters.end()) {
2699  *out << ban_manager_.get_ban_help();
2700  return;
2701  }
2702 
2703  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2704  const std::string target(parameters.begin(), first_space);
2705 
2706  std::string group = std::string(first_space + 1, second_space);
2707  first_space = second_space;
2708  second_space = std::find(first_space + 1, parameters.end(), ' ');
2709 
2710  const std::string duration(first_space + 1, second_space);
2711  auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
2712 
2713  if(!success) {
2714  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2715  return;
2716  }
2717 
2718  if(second_space == parameters.end()) {
2719  --second_space;
2720  }
2721 
2722  std::string reason(second_space + 1, parameters.end());
2723  boost::trim(reason);
2724 
2725  if(reason.empty()) {
2726  *out << "You need to give a reason for the ban.";
2727  return;
2728  }
2729 
2730  // if we find a '.' consider it an ip mask
2731  /** @todo FIXME: make a proper check for valid IPs. */
2732  if(std::count(target.begin(), target.end(), '.') >= 1) {
2733  banned = true;
2734 
2735  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, group);
2736  } else {
2737  for(const auto& player : player_connections_) {
2738  if(utils::wildcard_string_match(player.info().name(), target)) {
2739  if(banned) {
2740  *out << "\n";
2741  } else {
2742  banned = true;
2743  }
2744 
2745  const std::string ip = player.client_ip();
2746  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, group, target);
2747  }
2748  }
2749 
2750  if(!banned) {
2751  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2752  }
2753  }
2754 }
2755 
2756 void server::unban_handler(const std::string& /*issuer_name*/,
2757  const std::string& /*query*/,
2758  std::string& parameters,
2759  std::ostringstream* out)
2760 {
2761  assert(out != nullptr);
2762 
2763  if(parameters.empty()) {
2764  *out << "You must enter an ipmask to unban.";
2765  return;
2766  }
2767 
2768  ban_manager_.unban(*out, parameters);
2769 }
2770 
2771 void server::ungban_handler(const std::string& /*issuer_name*/,
2772  const std::string& /*query*/,
2773  std::string& parameters,
2774  std::ostringstream* out)
2775 {
2776  assert(out != nullptr);
2777 
2778  if(parameters.empty()) {
2779  *out << "You must enter an ipmask to ungban.";
2780  return;
2781  }
2782 
2783  ban_manager_.unban_group(*out, parameters);
2784 }
2785 
2786 void server::kick_handler(const std::string& /*issuer_name*/,
2787  const std::string& /*query*/,
2788  std::string& parameters,
2789  std::ostringstream* out)
2790 {
2791  assert(out != nullptr);
2792 
2793  if(parameters.empty()) {
2794  *out << "You must enter a mask to kick.";
2795  return;
2796  }
2797 
2798  auto i = std::find(parameters.begin(), parameters.end(), ' ');
2799  const std::string kick_mask = std::string(parameters.begin(), i);
2800  const std::string kick_message = (i == parameters.end()
2801  ? "You have been kicked."
2802  : "You have been kicked. Reason: " + std::string(i + 1, parameters.end()));
2803 
2804  bool kicked = false;
2805 
2806  // if we find a '.' consider it an ip mask
2807  const bool match_ip = (std::count(kick_mask.begin(), kick_mask.end(), '.') >= 1);
2808 
2809  std::vector<player_iterator> users_to_kick;
2811  if((match_ip && utils::wildcard_string_match(player->client_ip(), kick_mask)) ||
2812  (!match_ip && utils::wildcard_string_match(player->info().name(), kick_mask))
2813  ) {
2814  users_to_kick.push_back(player);
2815  }
2816  }
2817 
2818  for(const auto& player : users_to_kick) {
2819  if(kicked) {
2820  *out << "\n";
2821  } else {
2822  kicked = true;
2823  }
2824 
2825  *out << "Kicked " << player->name() << " (" << player->client_ip() << "). '"
2826  << kick_message << "'";
2827 
2828  utils::visit([this, &kick_message](auto&& socket) { async_send_error(socket, kick_message); }, player->socket());
2830  }
2831 
2832  if(!kicked) {
2833  *out << "No user matched '" << kick_mask << "'.";
2834  }
2835 }
2836 
2837 void server::motd_handler(const std::string& /*issuer_name*/,
2838  const std::string& /*query*/,
2839  std::string& parameters,
2840  std::ostringstream* out)
2841 {
2842  assert(out != nullptr);
2843 
2844  if(parameters.empty()) {
2845  if(!motd_.empty()) {
2846  *out << "Message of the day:\n" << motd_;
2847  return;
2848  } else {
2849  *out << "No message of the day set.";
2850  return;
2851  }
2852  }
2853 
2854  motd_ = parameters;
2855  *out << "Message of the day set to: " << motd_;
2856 }
2857 
2858 void server::searchlog_handler(const std::string& /*issuer_name*/,
2859  const std::string& /*query*/,
2860  std::string& parameters,
2861  std::ostringstream* out)
2862 {
2863  assert(out != nullptr);
2864 
2865  if(parameters.empty()) {
2866  *out << "You must enter a mask to search for.";
2867  return;
2868  }
2869 
2870  *out << "IP/NICK LOG for '" << parameters << "'";
2871 
2872  // If this looks like an IP look up which nicks have been connected from it
2873  // Otherwise look for the last IP the nick used to connect
2874  const bool match_ip = (std::count(parameters.begin(), parameters.end(), '.') >= 1);
2875 
2876  if(!user_handler_) {
2877  bool found_something = false;
2878 
2879  for(const auto& i : ip_log_) {
2880  const std::string& username = i.nick;
2881  const std::string& ip = i.ip;
2882 
2883  if((match_ip && utils::wildcard_string_match(ip, parameters)) ||
2884  (!match_ip && utils::wildcard_string_match(utf8::lowercase(username), utf8::lowercase(parameters)))
2885  ) {
2886  found_something = true;
2887  auto player = player_connections_.get<name_t>().find(username);
2888 
2889  if(player != player_connections_.get<name_t>().end() && player->client_ip() == ip) {
2890  *out << std::endl << player_status(*player);
2891  } else {
2892  *out << "\n'" << username << "' @ " << ip
2893  << " last seen: " << chrono::format_local_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
2894  }
2895  }
2896  }
2897 
2898  if(!found_something) {
2899  *out << "\nNo match found.";
2900  }
2901  } else {
2902  if(!match_ip) {
2903  utils::to_sql_wildcards(parameters);
2904  user_handler_->get_ips_for_user(parameters, out);
2905  } else {
2906  user_handler_->get_users_for_ip(parameters, out);
2907  }
2908  }
2909 }
2910 
2911 void server::dul_handler(const std::string& /*issuer_name*/,
2912  const std::string& /*query*/,
2913  std::string& parameters,
2914  std::ostringstream* out)
2915 {
2916  assert(out != nullptr);
2917 
2918  try {
2919  if(parameters.empty()) {
2920  *out << "Unregistered login is " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
2921  } else {
2922  deny_unregistered_login_ = (utf8::lowercase(parameters) == "yes");
2923  *out << "Unregistered login is now " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
2924  }
2925 
2926  } catch(const utf8::invalid_utf8_exception& e) {
2927  ERR_SERVER << "While handling dul (deny unregistered logins), caught an invalid utf8 exception: " << e.what();
2928  }
2929 }
2930 
2931 void server::stopgame(const std::string& /*issuer_name*/,
2932  const std::string& /*query*/,
2933  std::string& parameters,
2934  std::ostringstream* out)
2935 {
2936  const std::string nick = parameters.substr(0, parameters.find(' '));
2937  const std::string reason = parameters.length() > nick.length()+1 ? parameters.substr(nick.length()+1) : "";
2938  auto player = player_connections_.get<name_t>().find(nick);
2939 
2940  if(player != player_connections_.get<name_t>().end()){
2941  std::shared_ptr<game> g = player->get_game();
2942  if(g){
2943  *out << "Player '" << nick << "' is in game with id '" << g->id() << ", " << g->db_id() << "' named '" << g->name() << "'. Ending game for reason: '" << reason << "'...";
2944  delete_game(g->id(), reason);
2945  } else {
2946  *out << "Player '" << nick << "' is not currently in a game.";
2947  }
2948  } else {
2949  *out << "Player '" << nick << "' is not currently logged in.";
2950  }
2951 }
2952 
2953 void server::delete_game(int gameid, const std::string& reason)
2954 {
2955  // Set the availability status for all quitting users.
2956  auto range_pair = player_connections_.get<game_t>().equal_range(gameid);
2957 
2958  // Make a copy of the iterators so that we can change them while iterating over them.
2959  // We can use pair::first_type since equal_range returns a pair of iterators.
2960  std::vector<decltype(range_pair)::first_type> range_vctor;
2961 
2962  for(auto it = range_pair.first; it != range_pair.second; ++it) {
2963  range_vctor.push_back(it);
2964  it->info().mark_available();
2965 
2966  simple_wml::document udiff;
2967  if(make_change_diff(games_and_users_list_.root(), nullptr, "user", it->info().config_address(), udiff)) {
2968  send_to_lobby(udiff);
2969  } else {
2970  ERR_SERVER << "ERROR: delete_game(): Could not find user in players_.";
2971  }
2972  }
2973 
2974  // Put the remaining users back in the lobby.
2975  // This will call cleanup_game() deleter since there won't
2976  // be any references to that game from player_connections_ anymore
2977  for(const auto& it : range_vctor) {
2978  player_connections_.get<game_t>().modify(it, std::bind(&player_record::enter_lobby, std::placeholders::_1));
2979  }
2980 
2981  // send users in the game a notification to leave the game since it has ended
2982  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
2983 
2984  for(const auto& it : range_vctor) {
2985  player_iterator p { player_connections_.project<0>(it) };
2986  if(reason != "") {
2987  simple_wml::document leave_game_doc_reason("[leave_game]\n[/leave_game]\n", simple_wml::INIT_STATIC);
2988  leave_game_doc_reason.child("leave_game")->set_attr_dup("reason", reason.c_str());
2989  send_to_player(p, leave_game_doc_reason);
2990  } else {
2991  send_to_player(p, leave_game_doc);
2992  }
2994  }
2995 }
2996 
2997 void server::update_game_in_lobby(wesnothd::game& g, utils::optional<player_iterator> exclude)
2998 {
2999  simple_wml::document diff;
3000  if(auto p_desc = g.changed_description()) {
3001  if(make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", p_desc, diff)) {
3002  send_to_lobby(diff, exclude);
3003  }
3004  }
3005 }
3006 
3007 } // namespace wesnothd
3008 
3009 int main(int argc, char** argv)
3010 {
3011  int port = 15000;
3012  bool keep_alive = false;
3013 
3014  srand(static_cast<unsigned>(std::time(nullptr)));
3015 
3016  std::string config_file;
3017 
3018  // setting path to currentworking directory
3020 
3021  // show 'info' by default
3023  lg::timestamps(true);
3024 
3025  for(int arg = 1; arg != argc; ++arg) {
3026  const std::string val(argv[arg]);
3027  if(val.empty()) {
3028  continue;
3029  }
3030 
3031  if((val == "--config" || val == "-c") && arg + 1 != argc) {
3032  config_file = argv[++arg];
3033  } else if(val == "--verbose" || val == "-v") {
3035  } else if(val == "--dump-wml" || val == "-w") {
3036  dump_wml = true;
3037  } else if(val.substr(0, 6) == "--log-") {
3038  std::size_t p = val.find('=');
3039  if(p == std::string::npos) {
3040  PLAIN_LOG << "unknown option: " << val;
3041  return 2;
3042  }
3043 
3044  std::string s = val.substr(6, p - 6);
3046 
3047  if(s == "error") {
3049  } else if(s == "warning") {
3051  } else if(s == "info") {
3053  } else if(s == "debug") {
3055  } else {
3056  PLAIN_LOG << "unknown debug level: " << s;
3057  return 2;
3058  }
3059 
3060  while(p != std::string::npos) {
3061  std::size_t q = val.find(',', p + 1);
3062  s = val.substr(p + 1, q == std::string::npos ? q : q - (p + 1));
3063 
3065  PLAIN_LOG << "unknown debug domain: " << s;
3066  return 2;
3067  }
3068 
3069  p = q;
3070  }
3071  } else if((val == "--port" || val == "-p") && arg + 1 != argc) {
3072  port = atoi(argv[++arg]);
3073  } else if(val == "--keepalive") {
3074  keep_alive = true;
3075  } else if(val == "--help" || val == "-h") {
3076  std::cout << "usage: " << argv[0]
3077  << " [-dvwV] [-c path] [-p port]\n"
3078  << " -c, --config <path> Tells wesnothd where to find the config file to use.\n"
3079  << " -d, --daemon Runs wesnothd as a daemon.\n"
3080  << " -h, --help Shows this usage message.\n"
3081  << " --log-<level>=<domain1>,<domain2>,...\n"
3082  << " sets the severity level of the debug domains.\n"
3083  << " 'all' can be used to match any debug domain.\n"
3084  << " Available levels: error, warning, info, debug.\n"
3085  << " -p, --port <port> Binds the server to the specified port.\n"
3086  << " --keepalive Enable TCP keepalive.\n"
3087  << " -v --verbose Turns on more verbose logging.\n"
3088  << " -V, --version Returns the server version.\n"
3089  << " -w, --dump-wml Print all WML sent to clients to stdout.\n";
3090  return 0;
3091  } else if(val == "--version" || val == "-V") {
3092  std::cout << "Battle for Wesnoth server " << game_config::wesnoth_version.str() << "\n";
3093  return 0;
3094  } else if(val == "--daemon" || val == "-d") {
3095 #ifdef _WIN32
3096  ERR_SERVER << "Running as a daemon is not supported on this platform";
3097  return -1;
3098 #else
3099  const pid_t pid = fork();
3100  if(pid < 0) {
3101  ERR_SERVER << "Could not fork and run as a daemon";
3102  return -1;
3103  } else if(pid > 0) {
3104  std::cout << "Started wesnothd as a daemon with process id " << pid << "\n";
3105  return 0;
3106  }
3107 
3108  setsid();
3109 #endif
3110  } else if(val == "--request_sample_frequency" && arg + 1 != argc) {
3111  wesnothd::request_sample_frequency = atoi(argv[++arg]);
3112  } else {
3113  ERR_SERVER << "unknown option: " << val;
3114  return 2;
3115  }
3116  }
3117 
3118  return wesnothd::server(port, keep_alive, config_file).run();
3119 }
double g
Definition: astarsearch.cpp:63
int main(int argc, char **argv)
Definition: server.cpp:2077
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:272
bool empty() const
Definition: config.cpp:849
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:384
A class to handle the non-SQL logic for connecting to the phpbb forum database.
severity get_severity() const
Definition: log.hpp:220
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:279
static std::string stats()
node * child(const char *name)
Definition: simple_wml.hpp:263
const string_span & attr(const char *key) const
Definition: simple_wml.hpp:128
void remove_child(const char *name, std::size_t index)
Definition: simple_wml.cpp:603
const child_list & children(const char *name) const
Definition: simple_wml.cpp:643
node & set_attr_int(const char *key, int value)
Definition: simple_wml.cpp:441
node * child(const char *name)
Definition: simple_wml.cpp:608
std::vector< node * > child_list
Definition: simple_wml.hpp:125
node & add_child(const char *name)
Definition: simple_wml.cpp:466
node & set_attr(const char *key, const char *value)
Definition: simple_wml.cpp:413
node & add_child_at(const char *name, std::size_t index)
Definition: simple_wml.cpp:447
void copy_into(node &n) const
Definition: simple_wml.cpp:821
node & set_attr_dup(const char *key, const char *value)
Definition: simple_wml.cpp:429
std::string to_string() const
Definition: simple_wml.cpp:183
const char * begin() const
Definition: simple_wml.hpp:90
const char * end() const
Definition: simple_wml.hpp:91
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:490
void list_bans(std::ostringstream &out, const std::string &mask="*")
Definition: ban.cpp:618
void unban(std::ostringstream &os, const std::string &ip, bool immediate_write=true)
Definition: ban.cpp:527
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:332
banned_ptr get_ban_info(const std::string &ip)
Definition: ban.cpp:658
void unban_group(std::ostringstream &os, const std::string &group)
Definition: ban.cpp:554
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:595
void load_config(const config &)
Definition: ban.cpp:696
static simple_wml::node * starting_pos(simple_wml::node &data)
The non-const version.
Definition: game.hpp:148
simple_wml::node * description() const
Definition: game.hpp:478
int db_id() const
This ID is not reused between scenarios of MP campaigns.
Definition: game.hpp:64
void emergency_cleanup()
Definition: game.hpp:608
int id() const
This ID is reused between scenarios of MP campaigns.
Definition: game.hpp:52
const std::string & termination_reason() const
Provides the reason the game was ended.
Definition: game.hpp:546
std::string get_replay_filename()
Definition: game.cpp:1786
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:2997
void send_to_lobby(simple_wml::document &data, utils::optional< player_iterator > exclude={})
Definition: server.cpp:2016
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:2858
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:2493
std::string information_
Definition: server.hpp:163
void setup_handlers()
Definition: server.cpp:371
void ban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2552
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:2756
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:2212
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:2953
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:2691
void pm_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2346
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:2301
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:1518
std::string restart_command
Definition: server.hpp:169
void lobbymsg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2410
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:1406
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:2451
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:2527
void kick_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2786
void dul_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2911
void handle_join_game(player_iterator player, simple_wml::node &join)
Definition: server.cpp:1440
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:2292
void send_server_message(SocketPtr socket, const std::string &message, const std::string &type)
Definition: server.cpp:1948
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:2203
void requests_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2231
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:2045
void remove_player(player_iterator player)
Definition: server.cpp:1972
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:342
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:2837
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:2026
void kickban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2615
void send_server_message_to_all(const std::string &message, utils::optional< player_iterator > exclude={})
Definition: server.cpp:2036
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:2061
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:2931
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:2147
void msg_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2388
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:2116
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:2222
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:414
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:2240
void games_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2283
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:2431
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:2182
void start_lan_server_timer()
Definition: server.cpp:306
void disconnect_player(player_iterator player)
Definition: server.cpp:1959
bool allow_remote_shutdown_
Definition: server.hpp:174
void ungban_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2771
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:1029
Interfaces for manipulating version numbers of engine, add-ons, etc.
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:297
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:857
constexpr auto days_hours_mins_secs
Definition: chrono.hpp:89
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
Definition: chrono.hpp:70
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition: chrono.hpp:61
constexpr auto deconstruct_duration(const std::tuple< Ts... > &, const std::chrono::duration< Rep, Period > &span)
Definition: chrono.hpp:76
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:53
std::string get_cwd()
Definition: filesystem.cpp:947
void set_user_data_dir(std::string newprefdir)
Definition: filesystem.cpp:722
std::string observer
std::string path
Definition: filesystem.cpp:92
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:818
logger & err()
Definition: log.cpp:307
severity
Definition: log.hpp:83
logger & debug()
Definition: log.cpp:325
logger & warn()
Definition: log.cpp:313
void timestamps(bool t)
Definition: log.cpp:304
logger & info()
Definition: log.cpp:319
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:347
std::string node_to_string(const node &n)
Definition: simple_wml.cpp:809
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.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:629
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