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