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