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