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