The Battle for Wesnoth  1.17.14+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  if(simple_wml::node* request = data.child("game_history_request")) {
1144  if(user_handler_) {
1145  int offset = request->attr("offset").to_int();
1146  int player_id = 0;
1147 
1148  // if no search_for attribute -> get the requestor's forum id
1149  // if search_for attribute for offline player -> query the forum database for the forum id
1150  // if search_for attribute for online player -> get the forum id from wesnothd's player info
1151  if(!request->has_attr("search_for")) {
1152  player_id = player->info().config_address()->attr("forum_id").to_int();
1153  } else {
1154  std::string player_name = request->attr("search_for").to_string();
1155  auto player_ptr = player_connections_.get<name_t>().find(player_name);
1156  if(player_ptr == player_connections_.get<name_t>().end()) {
1157  player_id = user_handler_->get_forum_id(player_name);
1158  } else {
1159  player_id = player_ptr->info().config_address()->attr("forum_id").to_int();
1160  }
1161  }
1162 
1163  if(player_id != 0) {
1164  LOG_SERVER << "Querying game history requested by player `" << player->info().name() << "` for player id `" << player_id << "`.";
1165  user_handler_->async_get_and_send_game_history(io_service_, *this, player, player_id, offset);
1166  }
1167  }
1168  return;
1169  }
1170 }
1171 
1173 {
1174  if((whisper["receiver"].empty()) || (whisper["message"].empty())) {
1175  static simple_wml::document data(
1176  "[message]\n"
1177  "message=\"Invalid number of arguments\"\n"
1178  "sender=\"server\"\n"
1179  "[/message]\n",
1181  );
1182 
1183  send_to_player(player, data);
1184  return;
1185  }
1186 
1187  whisper.set_attr_dup("sender", player->name().c_str());
1188 
1189  auto receiver_iter = player_connections_.get<name_t>().find(whisper["receiver"].to_string());
1190  if(receiver_iter == player_connections_.get<name_t>().end()) {
1191  send_server_message(player, "Can't find '" + whisper["receiver"].to_string() + "'.", "error");
1192  return;
1193  }
1194 
1195  auto g = player->get_game();
1196  if(g && g->started() && g->is_player(player_connections_.project<0>(receiver_iter))) {
1197  send_server_message(player, "You cannot send private messages to players in a running game you observe.", "error");
1198  return;
1199  }
1200 
1201  simple_wml::document cwhisper;
1202 
1203  simple_wml::node& trunc_whisper = cwhisper.root().add_child("whisper");
1204  whisper.copy_into(trunc_whisper);
1205 
1206  const simple_wml::string_span& msg = trunc_whisper["message"];
1207  chat_message::truncate_message(msg, trunc_whisper);
1208 
1209  send_to_player(player_connections_.project<0>(receiver_iter), cwhisper);
1210 }
1211 
1213 {
1214  wesnothd::player& player = iter->info();
1215 
1216  const std::string command(query["type"].to_string());
1217  std::ostringstream response;
1218 
1219  const std::string& query_help_msg =
1220  "Available commands are: adminmsg <msg>, help, games, metrics,"
1221  " motd, requests, roll <sides>, sample, stats, status, version, wml.";
1222 
1223  // Commands a player may issue.
1224  if(command == "status") {
1225  response << process_command(command + " " + player.name(), player.name());
1226  } else if(
1227  command.compare(0, 8, "adminmsg") == 0 ||
1228  command.compare(0, 6, "report") == 0 ||
1229  command == "games" ||
1230  command == "metrics" ||
1231  command == "motd" ||
1232  command.compare(0, 7, "version") == 0 ||
1233  command == "requests" ||
1234  command.compare(0, 4, "roll") == 0 ||
1235  command == "sample" ||
1236  command == "stats" ||
1237  command == "status " + player.name() ||
1238  command == "wml"
1239  ) {
1240  response << process_command(command, player.name());
1241  } else if(player.is_moderator()) {
1242  if(command == "signout") {
1243  LOG_SERVER << "Admin signed out: IP: " << iter->client_ip() << "\tnick: " << player.name();
1244  player.set_moderator(false);
1245  // This string is parsed by the client!
1246  response << "You are no longer recognized as an administrator.";
1247  if(user_handler_) {
1248  user_handler_->set_is_moderator(player.name(), false);
1249  }
1250  } else {
1251  LOG_SERVER << "Admin Command: type: " << command << "\tIP: " << iter->client_ip()
1252  << "\tnick: " << player.name();
1253  response << process_command(command, player.name());
1254  LOG_SERVER << response.str();
1255  }
1256  } else if(command == "help" || command.empty()) {
1257  response << query_help_msg;
1258  } else if(command == "admin" || command.compare(0, 6, "admin ") == 0) {
1259  if(admin_passwd_.empty()) {
1260  send_server_message(iter, "No password set.", "error");
1261  return;
1262  }
1263 
1264  std::string passwd;
1265  if(command.size() >= 6) {
1266  passwd = command.substr(6);
1267  }
1268 
1269  if(passwd == admin_passwd_) {
1270  LOG_SERVER << "New Admin recognized: IP: " << iter->client_ip() << "\tnick: " << player.name();
1271  player.set_moderator(true);
1272  // This string is parsed by the client!
1273  response << "You are now recognized as an administrator.";
1274 
1275  if(user_handler_) {
1276  user_handler_->set_is_moderator(player.name(), true);
1277  }
1278  } else {
1279  WRN_SERVER << "FAILED Admin attempt with password: '" << passwd << "'\tIP: " << iter->client_ip()
1280  << "\tnick: " << player.name();
1281  response << "Error: wrong password";
1282  }
1283  } else {
1284  response << "Error: unrecognized query: '" << command << "'\n" << query_help_msg;
1285  }
1286 
1287  send_server_message(iter, response.str(), "info");
1288 }
1289 
1291 {
1292  // Check if this server allows nick registration at all
1293  if(!user_handler_) {
1294  send_server_message(player, "This server does not allow username registration.", "error");
1295  return;
1296  }
1297 
1298  // A user requested a list of which details can be set
1299  if(nickserv.child("info")) {
1300  try {
1301  std::string res = user_handler_->user_info((*nickserv.child("info"))["name"].to_string());
1302  send_server_message(player, res, "info");
1303  } catch(const user_handler::error& e) {
1304  send_server_message(player,
1305  "There was an error looking up the details of the user '"
1306  + (*nickserv.child("info"))["name"].to_string() + "'. "
1307  + " The error message was: " + e.message, "error"
1308  );
1309  }
1310 
1311  return;
1312  }
1313 }
1314 
1316 {
1317  if(user->info().is_message_flooding()) {
1318  send_server_message(user,
1319  "Warning: you are sending too many messages too fast. Your message has not been relayed.", "error");
1320  return;
1321  }
1322 
1323  simple_wml::document relay_message;
1324  message.set_attr_dup("sender", user->name().c_str());
1325 
1326  simple_wml::node& trunc_message = relay_message.root().add_child("message");
1327  message.copy_into(trunc_message);
1328 
1329  const simple_wml::string_span& msg = trunc_message["message"];
1330  chat_message::truncate_message(msg, trunc_message);
1331 
1332  if(msg.size() >= 3 && simple_wml::string_span(msg.begin(), 4) == "/me ") {
1333  LOG_SERVER << user->client_ip() << "\t<" << user->name()
1334  << simple_wml::string_span(msg.begin() + 3, msg.size() - 3) << ">";
1335  } else {
1336  LOG_SERVER << user->client_ip() << "\t<" << user->name() << "> " << msg;
1337  }
1338 
1339  send_to_lobby(relay_message, user);
1340 }
1341 
1343 {
1344  if(graceful_restart) {
1345  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
1346  send_to_player(player, leave_game_doc);
1347 
1348  send_server_message(player,
1349  "This server is shutting down. You aren't allowed to make new games. Please "
1350  "reconnect to the new server.", "error");
1351 
1353  return;
1354  }
1355 
1356  const std::string game_name = create_game["name"].to_string();
1357  const std::string game_password = create_game["password"].to_string();
1358  const std::string initial_bans = create_game["ignored"].to_string();
1359 
1360  DBG_SERVER << player->client_ip() << "\t" << player->info().name()
1361  << "\tcreates a new game: \"" << game_name << "\".";
1362 
1363  // Create the new game, remove the player from the lobby
1364  // and set the player as the host/owner.
1365  player_connections_.modify(player, [this, player, &game_name](player_record& host_record) {
1366  host_record.get_game().reset(
1367  new wesnothd::game(*this, player_connections_, player, game_name, save_replays_, replay_save_path_),
1368  std::bind(&server::cleanup_game, this, std::placeholders::_1)
1369  );
1370  });
1371 
1372  wesnothd::game& g = *player->get_game();
1373 
1374  DBG_SERVER << "initial bans: " << initial_bans;
1375  if(initial_bans != "") {
1376  g.set_name_bans(utils::split(initial_bans,','));
1377  }
1378 
1379  if(game_password.empty() == false) {
1380  g.set_password(game_password);
1381  }
1382 
1383  create_game.copy_into(g.level().root());
1384 }
1385 
1387 {
1389 
1390  if(user_handler_){
1391  user_handler_->db_update_game_end(uuid_, game_ptr->db_id(), game_ptr->get_replay_filename());
1392  }
1393 
1394  simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
1395  assert(gamelist != nullptr);
1396 
1397  // Send a diff of the gamelist with the game deleted to players in the lobby
1398  simple_wml::document diff;
1399  if(!destructed && make_delete_diff(*gamelist, "gamelist", "game", game_ptr->description(), diff)) {
1400  send_to_lobby(diff);
1401  }
1402 
1403  // Delete the game from the games_and_users_list_.
1404  const simple_wml::node::child_list& games = gamelist->children("game");
1405  const auto g = std::find(games.begin(), games.end(), game_ptr->description());
1406 
1407  if(g != games.end()) {
1408  const std::size_t index = std::distance(games.begin(), g);
1409  gamelist->remove_child("game", index);
1410  } else {
1411  // Can happen when the game ends before the scenario was transferred.
1412  LOG_SERVER << "Could not find game (" << game_ptr->id() << ", " << game_ptr->db_id() << ") to delete in games_and_users_list_.";
1413  }
1414 
1415  if(destructed) game_ptr->emergency_cleanup();
1416 
1417  delete game_ptr;
1418 }
1419 
1421 {
1422  const bool observer = join.attr("observe").to_bool();
1423  const std::string& password = join["password"].to_string();
1424  int game_id = join["id"].to_int();
1425 
1426  auto g_iter = player_connections_.get<game_t>().find(game_id);
1427 
1428  std::shared_ptr<game> g;
1429  if(g_iter != player_connections_.get<game_t>().end()) {
1430  g = g_iter->get_game();
1431  }
1432 
1433  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
1434  if(!g) {
1435  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1436  << "\tattempted to join unknown game:\t" << game_id << ".";
1437  send_to_player(player, leave_game_doc);
1438  send_server_message(player, "Attempt to join unknown game.", "error");
1440  return;
1441  } else if(!g->level_init()) {
1442  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1443  << "\tattempted to join uninitialized game:\t\"" << g->name() << "\" (" << game_id << ").";
1444  send_to_player(player, leave_game_doc);
1445  send_server_message(player, "Attempt to join an uninitialized game.", "error");
1447  return;
1448  } else if(player->info().is_moderator()) {
1449  // Admins are always allowed to join.
1450  } else if(g->player_is_banned(player, player->info().name())) {
1451  DBG_SERVER << player->client_ip()
1452  << "\tReject banned player: " << player->info().name()
1453  << "\tfrom game:\t\"" << g->name() << "\" (" << game_id << ").";
1454  send_to_player(player, leave_game_doc);
1455  send_server_message(player, "You are banned from this game.", "error");
1457  return;
1458  } else if(!g->password_matches(password)) {
1459  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1460  << "\tattempted to join game:\t\"" << g->name() << "\" (" << game_id << ") with bad password";
1461  send_to_player(player, leave_game_doc);
1462  send_server_message(player, "Incorrect password.", "error");
1464  return;
1465  }
1466 
1467  bool joined = g->add_player(player, observer);
1468  if(!joined) {
1469  WRN_SERVER << player->client_ip() << "\t" << player->info().name()
1470  << "\tattempted to observe game:\t\"" << g->name() << "\" (" << game_id
1471  << ") which doesn't allow observers.";
1472  send_to_player(player, leave_game_doc);
1473 
1474  send_server_message(player,
1475  "Attempt to observe a game that doesn't allow observers. (You probably joined the "
1476  "game shortly after it filled up.)", "error");
1477 
1479  return;
1480  }
1481 
1482  player_connections_.modify(player,
1483  std::bind(&player_record::set_game, std::placeholders::_1, g));
1484 
1485  g->describe_slots();
1486 
1487  // send notification of changes to the game and user
1488  simple_wml::document diff;
1489  bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g->description(), diff);
1490  bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user",
1491  player->info().config_address(), diff);
1492 
1493  if(diff1 || diff2) {
1494  send_to_lobby(diff);
1495  }
1496 }
1497 
1499 {
1500  DBG_SERVER << "in process_data_game...";
1501 
1502  wesnothd::player& player { p->info() };
1503 
1504  game& g = *(p->get_game());
1505  std::weak_ptr<game> g_ptr{p->get_game()};
1506 
1507  // If this is data describing the level for a game.
1508  if(data.child("snapshot") || data.child("scenario")) {
1509  if(!g.is_owner(p)) {
1510  return;
1511  }
1512 
1513  // If this game is having its level data initialized
1514  // for the first time, and is ready for players to join.
1515  // We should currently have a summary of the game in g.level().
1516  // We want to move this summary to the games_and_users_list_, and
1517  // place a pointer to that summary in the game's description.
1518  // g.level() should then receive the full data for the game.
1519  if(!g.level_init()) {
1520  LOG_SERVER << p->client_ip() << "\t" << player.name() << "\tcreated game:\t\"" << g.name() << "\" ("
1521  << g.id() << ", " << g.db_id() << ").";
1522  // Update our config object which describes the open games,
1523  // and save a pointer to the description in the new game.
1524  simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
1525  assert(gamelist != nullptr);
1526 
1527  simple_wml::node& desc = gamelist->add_child("game");
1528  g.level().root().copy_into(desc);
1529 
1530  if(const simple_wml::node* m = data.child("multiplayer")) {
1531  m->copy_into(desc);
1532  } else {
1533  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1534  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1535  // Set the description so it can be removed in delete_game().
1536  g.set_description(&desc);
1537  delete_game(g.id());
1538 
1540  "The scenario data is missing the [multiplayer] tag which contains the "
1541  "game settings. Game aborted.", "error");
1542  return;
1543  }
1544 
1545  g.set_description(&desc);
1546  desc.set_attr_dup("id", std::to_string(g.id()).c_str());
1547  } else {
1548  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1549  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") although it's already initialized.";
1550  return;
1551  }
1552 
1553  assert(games_and_users_list_.child("gamelist")->children("game").empty() == false);
1554 
1555  simple_wml::node& desc = *g.description();
1556 
1557  // Update the game's description.
1558  // If there is no shroud, then tell players in the lobby
1559  // what the map looks like
1561  // fixme: the hanlder of [store_next_scenario] below searches for 'mp_shroud' in [scenario]
1562  // at least of the these cosed is likely wrong.
1563  if(!data["mp_shroud"].to_bool()) {
1564  desc.set_attr_dup("map_data", s["map_data"]);
1565  }
1566 
1567  if(const simple_wml::node* e = data.child("era")) {
1568  if(!e->attr("require_era").to_bool(true)) {
1569  desc.set_attr("require_era", "no");
1570  }
1571  }
1572 
1573  if(s["require_scenario"].to_bool(false)) {
1574  desc.set_attr("require_scenario", "yes");
1575  }
1576 
1577  const simple_wml::node::child_list& mlist = data.children("modification");
1578  for(const simple_wml::node* m : mlist) {
1579  desc.add_child_at("modification", 0);
1580  desc.child("modification")->set_attr_dup("id", m->attr("id"));
1581  desc.child("modification")->set_attr_dup("name", m->attr("name"));
1582  desc.child("modification")->set_attr_dup("addon_id", m->attr("addon_id"));
1583 
1584  if(m->attr("require_modification").to_bool(false)) {
1585  desc.child("modification")->set_attr("require_modification", "yes");
1586  }
1587  }
1588 
1589  // Record the full scenario in g.level()
1590  g.level().swap(data);
1591 
1592  // The host already put himself in the scenario so we just need
1593  // to update_side_data().
1594  // g.take_side(sock);
1595  g.update_side_data();
1596  g.describe_slots();
1597 
1598  // Send the update of the game description to the lobby.
1599  simple_wml::document diff;
1600  make_add_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", diff);
1601  make_change_diff(games_and_users_list_.root(), nullptr, "user", p->info().config_address(), diff);
1602 
1603  send_to_lobby(diff);
1604 
1605  /** @todo FIXME: Why not save the level data in the history_? */
1606  return;
1607  // Everything below should only be processed if the game is already initialized.
1608  } else if(!g.level_init()) {
1609  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
1610  << " while the scenario wasn't yet initialized."
1611  << data.output();
1612  return;
1613  // If the host is sending the next scenario data.
1614  } else if(const simple_wml::node* scenario = data.child("store_next_scenario")) {
1615  if(!g.is_owner(p)) {
1616  return;
1617  }
1618 
1619  if(!g.level_init()) {
1620  WRN_SERVER << p->client_ip() << "\tWarning: " << player.name()
1621  << "\tsent [store_next_scenario] in game:\t\"" << g.name() << "\" (" << g.id()
1622  << ", " << g.db_id() << ") while the scenario is not yet initialized.";
1623  return;
1624  }
1625 
1626  g.save_replay();
1627  if(user_handler_){
1628  user_handler_->db_update_game_end(uuid_, g.db_id(), g.get_replay_filename());
1629  }
1630 
1631  g.new_scenario(p);
1633 
1634  // Record the full scenario in g.level()
1635  g.level().clear();
1636  scenario->copy_into(g.level().root());
1637  g.next_db_id();
1638 
1639  if(g.description() == nullptr) {
1640  ERR_SERVER << p->client_ip() << "\tERROR: \"" << g.name() << "\" (" << g.id()
1641  << ", " << g.db_id() << ") is initialized but has no description_.";
1642  return;
1643  }
1644 
1645  simple_wml::node& desc = *g.description();
1646 
1647  // Update the game's description.
1648  if(const simple_wml::node* m = scenario->child("multiplayer")) {
1649  m->copy_into(desc);
1650  } else {
1651  WRN_SERVER << p->client_ip() << "\t" << player.name() << "\tsent scenario data in game:\t\""
1652  << g.name() << "\" (" << g.id() << ", " << g.db_id() << ") without a 'multiplayer' child.";
1653 
1654  delete_game(g.id());
1655 
1657  "The scenario data is missing the [multiplayer] tag which contains the game "
1658  "settings. Game aborted.", "error");
1659  return;
1660  }
1661 
1662  // If there is no shroud, then tell players in the lobby
1663  // what the map looks like.
1665  desc.set_attr_dup("map_data", s["mp_shroud"].to_bool() ? "" : s["map_data"]);
1666 
1667  if(const simple_wml::node* e = data.child("era")) {
1668  if(!e->attr("require_era").to_bool(true)) {
1669  desc.set_attr("require_era", "no");
1670  }
1671  }
1672 
1673  if(s["require_scenario"].to_bool(false)) {
1674  desc.set_attr("require_scenario", "yes");
1675  }
1676 
1677  // Tell everyone that the next scenario data is available.
1678  static simple_wml::document notify_next_scenario(
1679  "[notify_next_scenario]\n[/notify_next_scenario]\n", simple_wml::INIT_COMPRESSED);
1680  g.send_data(notify_next_scenario, p);
1681 
1682  // Send the update of the game description to the lobby.
1684  return;
1685  // A mp client sends a request for the next scenario of a mp campaign.
1686  } else if(data.child("load_next_scenario")) {
1687  g.load_next_scenario(p);
1688  return;
1689  } else if(data.child("start_game")) {
1690  if(!g.is_owner(p)) {
1691  return;
1692  }
1693 
1694  // perform controller tweaks, assigning sides as human for their owners etc.
1696 
1697  // Send notification of the game starting immediately.
1698  // g.start_game() will send data that assumes
1699  // the [start_game] message has been sent
1700  g.send_data(data, p);
1701  g.start_game(p);
1702 
1703  if(user_handler_) {
1704  const simple_wml::node& m = *g.level().root().child("multiplayer");
1706  // [addon] info handling
1707  std::set<std::string> primary_keys;
1708  for(const auto& addon : m.children("addon")) {
1709  for(const auto& content : addon->children("content")) {
1710  std::string key = uuid_+"-"+std::to_string(g.db_id())+"-"+content->attr("type").to_string()+"-"+content->attr("id").to_string()+"-"+addon->attr("id").to_string();
1711  if(primary_keys.count(key) == 0) {
1712  primary_keys.emplace(key);
1713  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());
1714  if(rows_inserted == 0) {
1715  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() << "'";
1716  }
1717  }
1718  }
1719  }
1720  if(m.children("addon").size() == 0) {
1721  WRN_SERVER << "Game content info missing for game with uuid '" << uuid_ << "', game ID '" << g.db_id() << "', named '" << g.name() << "'";
1722  }
1723 
1724  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());
1725 
1726  const simple_wml::node::child_list& sides = g.get_sides_list();
1727  for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
1728  const simple_wml::node& side = *sides[side_index];
1729  const auto player = player_connections_.get<name_t>().find(side["player_id"].to_string());
1730  std::string version;
1731  std::string source;
1732 
1733  // if "Nobody" is chosen for a side, for example
1734  if(player == player_connections_.get<name_t>().end()){
1735  version = "";
1736  source = "";
1737  } else {
1738  version = player->info().version();
1739  source = player->info().source();
1740 
1741  if(client_sources_.count(source) == 0) {
1742  source = "Default";
1743  }
1744  }
1745  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());
1746  }
1747  }
1748 
1749  // update the game having changed in the lobby
1751  return;
1752  } else if(data.child("leave_game")) {
1753  if(g.remove_player(p)) {
1754  delete_game(g.id());
1755  } else {
1756  auto description = g.description();
1757 
1758  // After this line, the game object may be destroyed. Don't use `g`!
1759  player_connections_.modify(p, std::bind(&player_record::enter_lobby, std::placeholders::_1));
1760 
1761  // Only run this if the game object is still valid
1762  if(auto gStrong = g_ptr.lock()) {
1763  gStrong->describe_slots();
1764  }
1765 
1766  // Send all other players in the lobby the update to the gamelist.
1767  simple_wml::document diff;
1768  bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", description, diff);
1769  bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user", player.config_address(), diff);
1770 
1771  if(diff1 || diff2) {
1772  send_to_lobby(diff, p);
1773  }
1774 
1775  // Send the player who has quit the gamelist.
1777  }
1778 
1779  return;
1780  // If this is data describing side changes by the host.
1781  } else if(const simple_wml::node* scenario_diff = data.child("scenario_diff")) {
1782  if(!g.is_owner(p)) {
1783  return;
1784  }
1785 
1786  g.level().root().apply_diff(*scenario_diff);
1787  const simple_wml::node* cfg_change = scenario_diff->child("change_child");
1788 
1789  if(cfg_change) {
1790  g.update_side_data();
1791  }
1792 
1793  if(g.describe_slots()) {
1795  }
1796 
1797  g.send_data(data, p);
1798  return;
1799  // If a player changes his faction.
1800  } else if(data.child("change_faction")) {
1801  g.send_data(data, p);
1802  return;
1803  // If the owner of a side is changing the controller.
1804  } else if(const simple_wml::node* change = data.child("change_controller")) {
1805  g.transfer_side_control(p, *change);
1806  if(g.describe_slots()) {
1808  }
1809 
1810  return;
1811  // If all observers should be muted. (toggles)
1812  } else if(data.child("muteall")) {
1813  if(!g.is_owner(p)) {
1814  g.send_server_message("You cannot mute: not the game host.", p);
1815  return;
1816  }
1817 
1818  g.mute_all_observers();
1819  return;
1820  // If an observer should be muted.
1821  } else if(const simple_wml::node* mute = data.child("mute")) {
1822  g.mute_observer(*mute, p);
1823  return;
1824  // If an observer should be unmuted.
1825  } else if(const simple_wml::node* unmute = data.child("unmute")) {
1826  g.unmute_observer(*unmute, p);
1827  return;
1828  // The owner is kicking/banning someone from the game.
1829  } else if(data.child("kick") || data.child("ban")) {
1830  bool ban = (data.child("ban") != nullptr);
1831  auto user { ban
1832  ? g.ban_user(*data.child("ban"), p)
1833  : g.kick_member(*data.child("kick"), p)};
1834 
1835  if(user) {
1836  player_connections_.modify(*user, std::bind(&player_record::enter_lobby, std::placeholders::_1));
1837  if(g.describe_slots()) {
1838  update_game_in_lobby(g, user);
1839  }
1840 
1841  // Send all other players in the lobby the update to the gamelist.
1842  simple_wml::document gamelist_diff;
1843  make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), gamelist_diff);
1844  make_change_diff(games_and_users_list_.root(), nullptr, "user", (*user)->info().config_address(), gamelist_diff);
1845 
1846  send_to_lobby(gamelist_diff, p);
1847 
1848  // Send the removed user the lobby game list.
1850  }
1851 
1852  return;
1853  } else if(const simple_wml::node* unban = data.child("unban")) {
1854  g.unban_user(*unban, p);
1855  return;
1856  // If info is being provided about the game state.
1857  } else if(const simple_wml::node* info = data.child("info")) {
1858  if(!g.is_player(p)) {
1859  return;
1860  }
1861 
1862  if((*info)["type"] == "termination") {
1863  g.set_termination_reason((*info)["condition"].to_string());
1864  if((*info)["condition"].to_string() == "out of sync") {
1865  g.send_and_record_server_message(player.name() + " reports out of sync errors.");
1866  if(user_handler_){
1867  user_handler_->db_set_oos_flag(uuid_, g.db_id());
1868  }
1869  }
1870  }
1871 
1872  return;
1873  } else if(data.child("turn")) {
1874  // Notify the game of the commands, and if it changes
1875  // the description, then sync the new description
1876  // to players in the lobby.
1877  if(g.process_turn(data, p)) {
1879  }
1880 
1881  return;
1882  } else if(data.child("whiteboard")) {
1883  g.process_whiteboard(data, p);
1884  return;
1885  } else if(data.child("change_turns_wml")) {
1886  g.process_change_turns_wml(data, p);
1888  return;
1889  } else if(simple_wml::node* sch = data.child("request_choice")) {
1890  g.handle_choice(*sch, p);
1891  return;
1892  } else if(data.child("message")) {
1893  g.process_message(data, p);
1894  return;
1895  } else if(data.child("stop_updates")) {
1896  g.send_data(data, p);
1897  return;
1898  // Data to ignore.
1899  } else if(
1900  data.child("error") ||
1901  data.child("side_secured") ||
1902  data.root().has_attr("failed") ||
1903  data.root().has_attr("side")
1904  ) {
1905  return;
1906  }
1907 
1908  WRN_SERVER << p->client_ip() << "\tReceived unknown data from: " << player.name()
1909  << " in game: \"" << g.name() << "\" (" << g.id() << ", " << g.db_id() << ")\n"
1910  << data.output();
1911 }
1912 
1913 template<class SocketPtr> void server::send_server_message(SocketPtr socket, const std::string& message, const std::string& type)
1914 {
1915  simple_wml::document server_message;
1916  simple_wml::node& msg = server_message.root().add_child("message");
1917  msg.set_attr("sender", "server");
1918  msg.set_attr_dup("message", message.c_str());
1919  msg.set_attr_dup("type", type.c_str());
1920 
1921  async_send_doc_queued(socket, server_message);
1922 }
1923 
1925 {
1926  utils::visit([](auto&& socket) {
1927  if constexpr (utils::decayed_is_same<tls_socket_ptr, decltype(socket)>) {
1928  socket->async_shutdown([socket](...) {});
1929  const char buffer[] = "";
1930  async_write(*socket, boost::asio::buffer(buffer), [socket](...) { socket->lowest_layer().close(); });
1931  } else {
1932  socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
1933  }
1934  }, player->socket());
1935 }
1936 
1938 {
1939  std::string ip = iter->client_ip();
1940 
1941  const std::shared_ptr<game> g = iter->get_game();
1942  bool game_ended = false;
1943  if(g) {
1944  game_ended = g->remove_player(iter, true, false);
1945  }
1946 
1948  const std::size_t index =
1949  std::distance(users.begin(), std::find(users.begin(), users.end(), iter->info().config_address()));
1950 
1951  // Notify other players in lobby
1952  simple_wml::document diff;
1953  if(make_delete_diff(games_and_users_list_.root(), nullptr, "user", iter->info().config_address(), diff)) {
1954  send_to_lobby(diff, iter);
1955  }
1956 
1957  games_and_users_list_.root().remove_child("user", index);
1958 
1959  LOG_SERVER << ip << "\t" << iter->info().name() << "\thas logged off";
1960 
1961  // Find the matching nick-ip pair in the log and update the sign off time
1962  if(user_handler_) {
1963  user_handler_->db_update_logout(iter->info().get_login_id());
1964  } else {
1965  connection_log ip_name { iter->info().name(), ip, 0 };
1966 
1967  auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name);
1968  if(i != ip_log_.end()) {
1969  i->log_off = std::time(nullptr);
1970  }
1971  }
1972 
1973  player_connections_.erase(iter);
1974 
1975  if(lan_server_ && player_connections_.size() == 0)
1977 
1978  if(game_ended) delete_game(g->id());
1979 }
1980 
1981 void server::send_to_lobby(simple_wml::document& data, std::optional<player_iterator> exclude)
1982 {
1983  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
1984  auto player { player_connections_.iterator_to(p) };
1985  if(player != exclude) {
1986  send_to_player(player, data);
1987  }
1988  }
1989 }
1990 
1991 void server::send_server_message_to_lobby(const std::string& message, std::optional<player_iterator> exclude)
1992 {
1993  for(const auto& p : player_connections_.get<game_t>().equal_range(0)) {
1994  auto player { player_connections_.iterator_to(p) };
1995  if(player != exclude) {
1996  send_server_message(player, message, "alert");
1997  }
1998  }
1999 }
2000 
2001 void server::send_server_message_to_all(const std::string& message, std::optional<player_iterator> exclude)
2002 {
2003  for(auto player = player_connections_.begin(); player != player_connections_.end(); ++player) {
2004  if(player != exclude) {
2005  send_server_message(player, message, "alert");
2006  }
2007  }
2008 }
2009 
2011 {
2012  if(restart_command.empty()) {
2013  return;
2014  }
2015 
2016  // Example config line:
2017  // restart_command="./wesnothd-debug -d -c ~/.wesnoth1.5/server.cfg"
2018  // remember to make new one as a daemon or it will block old one
2019  if(std::system(restart_command.c_str())) {
2020  ERR_SERVER << "Failed to start new server with command: " << restart_command;
2021  } else {
2022  LOG_SERVER << "New server started with command: " << restart_command;
2023  }
2024 }
2025 
2026 std::string server::process_command(std::string query, std::string issuer_name)
2027 {
2028  boost::trim(query);
2029 
2030  if(issuer_name == "*socket*" && !query.empty() && query.at(0) == '+') {
2031  // The first argument might be "+<issuer>: ".
2032  // In that case we use +<issuer>+ as the issuer_name.
2033  // (Mostly used for communication with IRC.)
2034  auto issuer_end = std::find(query.begin(), query.end(), ':');
2035 
2036  std::string issuer(query.begin() + 1, issuer_end);
2037  if(!issuer.empty()) {
2038  issuer_name = "+" + issuer + "+";
2039  query = std::string(issuer_end + 1, query.end());
2040  boost::trim(query);
2041  }
2042  }
2043 
2044  const auto i = std::find(query.begin(), query.end(), ' ');
2045 
2046  try {
2047  const std::string command = utf8::lowercase(std::string(query.begin(), i));
2048 
2049  std::string parameters = (i == query.end() ? "" : std::string(i + 1, query.end()));
2050  boost::trim(parameters);
2051 
2052  std::ostringstream out;
2053  auto handler_itor = cmd_handlers_.find(command);
2054 
2055  if(handler_itor == cmd_handlers_.end()) {
2056  out << "Command '" << command << "' is not recognized.\n" << help_msg;
2057  } else {
2058  const cmd_handler& handler = handler_itor->second;
2059  try {
2060  handler(issuer_name, query, parameters, &out);
2061  } catch(const std::bad_function_call& ex) {
2062  ERR_SERVER << "While handling a command '" << command
2063  << "', caught a std::bad_function_call exception.";
2064  ERR_SERVER << ex.what();
2065  out << "An internal server error occurred (std::bad_function_call) while executing '" << command
2066  << "'\n";
2067  }
2068  }
2069 
2070  return out.str();
2071 
2072  } catch(const utf8::invalid_utf8_exception& e) {
2073  std::string msg = "While handling a command, caught an invalid utf8 exception: ";
2074  msg += e.what();
2075  ERR_SERVER << msg;
2076  return (msg + '\n');
2077  }
2078 }
2079 
2080 // Shutdown, restart and sample commands can only be issued via the socket.
2082  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2083 {
2084  assert(out != nullptr);
2085 
2086  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2087  *out << denied_msg;
2088  return;
2089  }
2090 
2091  if(parameters == "now") {
2092  BOOST_THROW_EXCEPTION(server_shutdown("shut down by admin command"));
2093  } else {
2094  // Graceful shut down.
2095  graceful_restart = true;
2096  acceptor_v6_.close();
2097  acceptor_v4_.close();
2098 
2099  timer_.expires_from_now(std::chrono::seconds(10));
2100  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2101 
2103  "msg The server is shutting down. You may finish your games but can't start new ones. Once all "
2104  "games have ended the server will exit.",
2105  issuer_name
2106  );
2107 
2108  *out << "Server is doing graceful shut down.";
2109  }
2110 }
2111 
2112 void server::restart_handler(const std::string& issuer_name,
2113  const std::string& /*query*/,
2114  std::string& /*parameters*/,
2115  std::ostringstream* out)
2116 {
2117  assert(out != nullptr);
2118 
2119  if(issuer_name != "*socket*" && !allow_remote_shutdown_) {
2120  *out << denied_msg;
2121  return;
2122  }
2123 
2124  if(restart_command.empty()) {
2125  *out << "No restart_command configured! Not restarting.";
2126  } else {
2127  graceful_restart = true;
2128  acceptor_v6_.close();
2129  acceptor_v4_.close();
2130  timer_.expires_from_now(std::chrono::seconds(10));
2131  timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
2132 
2133  start_new_server();
2134 
2136  "msg The server has been restarted. You may finish current games but can't start new ones and "
2137  "new players can't join this (old) server instance. (So if a player of your game disconnects "
2138  "you have to save, reconnect and reload the game on the new server instance. It is actually "
2139  "recommended to do that right away.)",
2140  issuer_name
2141  );
2142 
2143  *out << "New server started.";
2144  }
2145 }
2146 
2148  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2149 {
2150  assert(out != nullptr);
2151 
2152  if(parameters.empty()) {
2153  *out << "Current sample frequency: " << request_sample_frequency;
2154  return;
2155  } else if(issuer_name != "*socket*") {
2156  *out << denied_msg;
2157  return;
2158  }
2159 
2160  request_sample_frequency = atoi(parameters.c_str());
2161  if(request_sample_frequency <= 0) {
2162  *out << "Sampling turned off.";
2163  } else {
2164  *out << "Sampling every " << request_sample_frequency << " requests.";
2165  }
2166 }
2167 
2168 void server::help_handler(const std::string& /*issuer_name*/,
2169  const std::string& /*query*/,
2170  std::string& /*parameters*/,
2171  std::ostringstream* out)
2172 {
2173  assert(out != nullptr);
2174  *out << help_msg;
2175 }
2176 
2177 void server::stats_handler(const std::string& /*issuer_name*/,
2178  const std::string& /*query*/,
2179  std::string& /*parameters*/,
2180  std::ostringstream* out)
2181 {
2182  assert(out != nullptr);
2183 
2184  *out << "Number of games = " << games().size() << "\nTotal number of users = " << player_connections_.size();
2185 }
2186 
2187 void server::metrics_handler(const std::string& /*issuer_name*/,
2188  const std::string& /*query*/,
2189  std::string& /*parameters*/,
2190  std::ostringstream* out)
2191 {
2192  assert(out != nullptr);
2193  *out << metrics_;
2194 }
2195 
2196 void server::requests_handler(const std::string& /*issuer_name*/,
2197  const std::string& /*query*/,
2198  std::string& /*parameters*/,
2199  std::ostringstream* out)
2200 {
2201  assert(out != nullptr);
2202  metrics_.requests(*out);
2203 }
2204 
2205 void server::roll_handler(const std::string& issuer_name,
2206  const std::string& /*query*/,
2207  std::string& parameters,
2208  std::ostringstream* out)
2209 {
2210  assert(out != nullptr);
2211  if(parameters.empty()) {
2212  return;
2213  }
2214 
2215  int N;
2216  try {
2217  N = std::stoi(parameters);
2218  } catch(const std::invalid_argument&) {
2219  *out << "The number of die sides must be a number!";
2220  return;
2221  } catch(const std::out_of_range&) {
2222  *out << "The number of sides is too big for the die!";
2223  return;
2224  }
2225 
2226  if(N < 1) {
2227  *out << "The die cannot have less than 1 side!";
2228  return;
2229  }
2230  std::uniform_int_distribution<int> dice_distro(1, N);
2231  std::string value = std::to_string(dice_distro(die_));
2232 
2233  *out << "You rolled a die [1 - " + parameters + "] and got a " + value + ".";
2234 
2235  auto player_ptr = player_connections_.get<name_t>().find(issuer_name);
2236  if(player_ptr == player_connections_.get<name_t>().end()) {
2237  return;
2238  }
2239 
2240  auto g_ptr = player_ptr->get_game();
2241  if(g_ptr) {
2242  g_ptr->send_server_message_to_all(issuer_name + " rolled a die [1 - " + parameters + "] and got a " + value + ".", player_connections_.project<0>(player_ptr));
2243  } else {
2244  *out << " (The result is shown to others only in a game.)";
2245  }
2246 }
2247 
2248 void server::games_handler(const std::string& /*issuer_name*/,
2249  const std::string& /*query*/,
2250  std::string& /*parameters*/,
2251  std::ostringstream* out)
2252 {
2253  assert(out != nullptr);
2254  metrics_.games(*out);
2255 }
2256 
2257 void server::wml_handler(const std::string& /*issuer_name*/,
2258  const std::string& /*query*/,
2259  std::string& /*parameters*/,
2260  std::ostringstream* out)
2261 {
2262  assert(out != nullptr);
2263  *out << simple_wml::document::stats();
2264 }
2265 
2267  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2268 {
2269  assert(out != nullptr);
2270 
2271  if(parameters.empty()) {
2272  *out << "You must type a message.";
2273  return;
2274  }
2275 
2276  const std::string& sender = issuer_name;
2277  const std::string& message = parameters;
2278  LOG_SERVER << "Admin message: <" << sender
2279  << (message.find("/me ") == 0 ? std::string(message.begin() + 3, message.end()) + ">" : "> " + message);
2280 
2282  simple_wml::node& msg = data.root().add_child("whisper");
2283  msg.set_attr_dup("sender", ("admin message from " + sender).c_str());
2284  msg.set_attr_dup("message", message.c_str());
2285 
2286  int n = 0;
2287  for(const auto& player : player_connections_) {
2288  if(player.info().is_moderator()) {
2289  ++n;
2290  send_to_player(player_connections_.iterator_to(player), data);
2291  }
2292  }
2293 
2294  bool is_admin = false;
2295 
2296  for(const auto& player : player_connections_) {
2297  if(issuer_name == player.info().name() && player.info().is_moderator()) {
2298  is_admin = true;
2299  break;
2300  }
2301  }
2302 
2303  if(!is_admin) {
2304  *out << "Your report has been logged and sent to the server administrators. Thanks!";
2305  return;
2306  }
2307 
2308  *out << "Your report has been logged and sent to " << n << " online administrators. Thanks!";
2309 }
2310 
2312  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2313 {
2314  assert(out != nullptr);
2315 
2316  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2317  if(first_space == parameters.end()) {
2318  *out << "You must name a receiver.";
2319  return;
2320  }
2321 
2322  const std::string& sender = issuer_name;
2323  const std::string receiver(parameters.begin(), first_space);
2324 
2325  std::string message(first_space + 1, parameters.end());
2326  boost::trim(message);
2327 
2328  if(message.empty()) {
2329  *out << "You must type a message.";
2330  return;
2331  }
2332 
2334  simple_wml::node& msg = data.root().add_child("whisper");
2335 
2336  // This string is parsed by the client!
2337  msg.set_attr_dup("sender", ("server message from " + sender).c_str());
2338  msg.set_attr_dup("message", message.c_str());
2339 
2340  for(const auto& player : player_connections_) {
2341  if(receiver != player.info().name().c_str()) {
2342  continue;
2343  }
2344 
2345  send_to_player(player_connections_.iterator_to(player), data);
2346  *out << "Message to " << receiver << " successfully sent.";
2347  return;
2348  }
2349 
2350  *out << "No such nick: " << receiver;
2351 }
2352 
2353 void server::msg_handler(const std::string& /*issuer_name*/,
2354  const std::string& /*query*/,
2355  std::string& parameters,
2356  std::ostringstream* out)
2357 {
2358  assert(out != nullptr);
2359 
2360  if(parameters.empty()) {
2361  *out << "You must type a message.";
2362  return;
2363  }
2364 
2365  send_server_message_to_all(parameters);
2366 
2367  LOG_SERVER << "<server"
2368  << (parameters.find("/me ") == 0
2369  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2370  : "> " + parameters);
2371 
2372  *out << "message '" << parameters << "' relayed to players";
2373 }
2374 
2375 void server::lobbymsg_handler(const std::string& /*issuer_name*/,
2376  const std::string& /*query*/,
2377  std::string& parameters,
2378  std::ostringstream* out)
2379 {
2380  assert(out != nullptr);
2381 
2382  if(parameters.empty()) {
2383  *out << "You must type a message.";
2384  return;
2385  }
2386 
2387  send_server_message_to_lobby(parameters);
2388  LOG_SERVER << "<server"
2389  << (parameters.find("/me ") == 0
2390  ? std::string(parameters.begin() + 3, parameters.end()) + ">"
2391  : "> " + parameters);
2392 
2393  *out << "message '" << parameters << "' relayed to players";
2394 }
2395 
2397  const std::string& /*issuer_name*/, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2398 {
2399  assert(out != nullptr);
2400 
2401  if(parameters.empty()) {
2402  *out << "Server version is " << game_config::wesnoth_version.str();
2403  return;
2404  }
2405 
2406  for(const auto& player : player_connections_) {
2407  if(parameters == player.info().name()) {
2408  *out << "Player " << parameters << " is using wesnoth " << player.info().version();
2409  return;
2410  }
2411  }
2412 
2413  *out << "Player '" << parameters << "' not found.";
2414 }
2415 
2417  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2418 {
2419  assert(out != nullptr);
2420 
2421  *out << "STATUS REPORT for '" << parameters << "'";
2422  bool found_something = false;
2423 
2424  // If a simple username is given we'll check for its IP instead.
2425  if(utils::isvalid_username(parameters)) {
2426  for(const auto& player : player_connections_) {
2427  if(utf8::lowercase(parameters) == utf8::lowercase(player.info().name())) {
2428  parameters = player.client_ip();
2429  found_something = true;
2430  break;
2431  }
2432  }
2433 
2434  if(!found_something) {
2435  // out << "\nNo match found. You may want to check with 'searchlog'.";
2436  // return out.str();
2437  *out << process_command("searchlog " + parameters, issuer_name);
2438  return;
2439  }
2440  }
2441 
2442  const bool match_ip = ((std::count(parameters.begin(), parameters.end(), '.') >= 1) || (std::count(parameters.begin(), parameters.end(), ':') >= 1));
2443  for(const auto& player : player_connections_) {
2444  if(parameters.empty() || parameters == "*" ||
2445  (match_ip && utils::wildcard_string_match(player.client_ip(), parameters)) ||
2446  (!match_ip && utils::wildcard_string_match(utf8::lowercase(player.info().name()), utf8::lowercase(parameters)))
2447  ) {
2448  found_something = true;
2449  *out << std::endl << player_status(player);
2450  }
2451  }
2452 
2453  if(!found_something) {
2454  *out << "\nNo match found. You may want to check with 'searchlog'.";
2455  }
2456 }
2457 
2458 void server::clones_handler(const std::string& /*issuer_name*/,
2459  const std::string& /*query*/,
2460  std::string& /*parameters*/,
2461  std::ostringstream* out)
2462 {
2463  assert(out != nullptr);
2464  *out << "CLONES STATUS REPORT";
2465 
2466  std::set<std::string> clones;
2467 
2468  for(auto it = player_connections_.begin(); it != player_connections_.end(); ++it) {
2469  if(clones.find(it->client_ip()) != clones.end()) {
2470  continue;
2471  }
2472 
2473  bool found = false;
2474  for(auto clone = std::next(it); clone != player_connections_.end(); ++clone) {
2475  if(it->client_ip() == clone->client_ip()) {
2476  if(!found) {
2477  found = true;
2478  clones.insert(it->client_ip());
2479  *out << std::endl << player_status(*it);
2480  }
2481 
2482  *out << std::endl << player_status(*clone);
2483  }
2484  }
2485  }
2486 
2487  if(clones.empty()) {
2488  *out << std::endl << "No clones found.";
2489  }
2490 }
2491 
2492 void server::bans_handler(const std::string& /*issuer_name*/,
2493  const std::string& /*query*/,
2494  std::string& parameters,
2495  std::ostringstream* out)
2496 {
2497  assert(out != nullptr);
2498 
2499  try {
2500  if(parameters.empty()) {
2501  ban_manager_.list_bans(*out);
2502  } else if(utf8::lowercase(parameters) == "deleted") {
2504  } else if(utf8::lowercase(parameters).find("deleted") == 0) {
2505  std::string mask = parameters.substr(7);
2506  ban_manager_.list_deleted_bans(*out, boost::trim_copy(mask));
2507  } else {
2508  boost::trim(parameters);
2509  ban_manager_.list_bans(*out, parameters);
2510  }
2511 
2512  } catch(const utf8::invalid_utf8_exception& e) {
2513  ERR_SERVER << "While handling bans, caught an invalid utf8 exception: " << e.what();
2514  }
2515 }
2516 
2518  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2519 {
2520  assert(out != nullptr);
2521 
2522  bool banned = false;
2523  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2524 
2525  if(first_space == parameters.end()) {
2526  *out << ban_manager_.get_ban_help();
2527  return;
2528  }
2529 
2530  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2531  const std::string target(parameters.begin(), first_space);
2532  const std::string duration(first_space + 1, second_space);
2533  std::time_t parsed_time = std::time(nullptr);
2534 
2535  if(ban_manager_.parse_time(duration, &parsed_time) == false) {
2536  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2537  return;
2538  }
2539 
2540  if(second_space == parameters.end()) {
2541  --second_space;
2542  }
2543 
2544  std::string reason(second_space + 1, parameters.end());
2545  boost::trim(reason);
2546 
2547  if(reason.empty()) {
2548  *out << "You need to give a reason for the ban.";
2549  return;
2550  }
2551 
2552  std::string dummy_group;
2553 
2554  // if we find a '.' consider it an ip mask
2555  /** @todo FIXME: make a proper check for valid IPs. */
2556  if(std::count(target.begin(), target.end(), '.') >= 1) {
2557  banned = true;
2558 
2559  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2560  } else {
2561  for(const auto& player : player_connections_) {
2562  if(utils::wildcard_string_match(player.info().name(), target)) {
2563  if(banned) {
2564  *out << "\n";
2565  } else {
2566  banned = true;
2567  }
2568 
2569  const std::string ip = player.client_ip();
2570  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2571  }
2572  }
2573 
2574  if(!banned) {
2575  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2576  }
2577  }
2578 }
2579 
2581  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2582 {
2583  assert(out != nullptr);
2584 
2585  bool banned = false;
2586  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2587  if(first_space == parameters.end()) {
2588  *out << ban_manager_.get_ban_help();
2589  return;
2590  }
2591 
2592  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2593  const std::string target(parameters.begin(), first_space);
2594  const std::string duration(first_space + 1, second_space);
2595  std::time_t parsed_time = std::time(nullptr);
2596 
2597  if(ban_manager_.parse_time(duration, &parsed_time) == false) {
2598  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2599  return;
2600  }
2601 
2602  if(second_space == parameters.end()) {
2603  --second_space;
2604  }
2605 
2606  std::string reason(second_space + 1, parameters.end());
2607  boost::trim(reason);
2608 
2609  if(reason.empty()) {
2610  *out << "You need to give a reason for the ban.";
2611  return;
2612  }
2613 
2614  std::string dummy_group;
2615  std::vector<player_iterator> users_to_kick;
2616 
2617  // if we find a '.' consider it an ip mask
2618  /** @todo FIXME: make a proper check for valid IPs. */
2619  if(std::count(target.begin(), target.end(), '.') >= 1) {
2620  banned = true;
2621 
2622  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, dummy_group);
2623 
2625  if(utils::wildcard_string_match(player->client_ip(), target)) {
2626  users_to_kick.push_back(player);
2627  }
2628  }
2629  } else {
2631  if(utils::wildcard_string_match(player->info().name(), target)) {
2632  if(banned) {
2633  *out << "\n";
2634  } else {
2635  banned = true;
2636  }
2637 
2638  const std::string ip = player->client_ip();
2639  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, dummy_group, target);
2640  users_to_kick.push_back(player);
2641  }
2642  }
2643 
2644  if(!banned) {
2645  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2646  }
2647  }
2648 
2649  for(auto user : users_to_kick) {
2650  *out << "\nKicked " << user->info().name() << " (" << user->client_ip() << ").";
2651  utils::visit([this,reason](auto&& socket) { async_send_error(socket, "You have been banned. Reason: " + reason); }, user->socket());
2652  disconnect_player(user);
2653  }
2654 }
2655 
2657  const std::string& issuer_name, const std::string& /*query*/, std::string& parameters, std::ostringstream* out)
2658 {
2659  assert(out != nullptr);
2660 
2661  bool banned = false;
2662  auto first_space = std::find(parameters.begin(), parameters.end(), ' ');
2663  if(first_space == parameters.end()) {
2664  *out << ban_manager_.get_ban_help();
2665  return;
2666  }
2667 
2668  auto second_space = std::find(first_space + 1, parameters.end(), ' ');
2669  const std::string target(parameters.begin(), first_space);
2670 
2671  std::string group = std::string(first_space + 1, second_space);
2672  first_space = second_space;
2673  second_space = std::find(first_space + 1, parameters.end(), ' ');
2674 
2675  const std::string duration(first_space + 1, second_space);
2676  std::time_t parsed_time = std::time(nullptr);
2677 
2678  if(ban_manager_.parse_time(duration, &parsed_time) == false) {
2679  *out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
2680  return;
2681  }
2682 
2683  if(second_space == parameters.end()) {
2684  --second_space;
2685  }
2686 
2687  std::string reason(second_space + 1, parameters.end());
2688  boost::trim(reason);
2689 
2690  if(reason.empty()) {
2691  *out << "You need to give a reason for the ban.";
2692  return;
2693  }
2694 
2695  // if we find a '.' consider it an ip mask
2696  /** @todo FIXME: make a proper check for valid IPs. */
2697  if(std::count(target.begin(), target.end(), '.') >= 1) {
2698  banned = true;
2699 
2700  *out << ban_manager_.ban(target, parsed_time, reason, issuer_name, group);
2701  } else {
2702  for(const auto& player : player_connections_) {
2703  if(utils::wildcard_string_match(player.info().name(), target)) {
2704  if(banned) {
2705  *out << "\n";
2706  } else {
2707  banned = true;
2708  }
2709 
2710  const std::string ip = player.client_ip();
2711  *out << ban_manager_.ban(ip, parsed_time, reason, issuer_name, group, target);
2712  }
2713  }
2714 
2715  if(!banned) {
2716  *out << "Nickname mask '" << target << "' did not match, no bans set.";
2717  }
2718  }
2719 }
2720 
2721 void server::unban_handler(const std::string& /*issuer_name*/,
2722  const std::string& /*query*/,
2723  std::string& parameters,
2724  std::ostringstream* out)
2725 {
2726  assert(out != nullptr);
2727 
2728  if(parameters.empty()) {
2729  *out << "You must enter an ipmask to unban.";
2730  return;
2731  }
2732 
2733  ban_manager_.unban(*out, parameters);
2734 }
2735 
2736 void server::ungban_handler(const std::string& /*issuer_name*/,
2737  const std::string& /*query*/,
2738  std::string& parameters,
2739  std::ostringstream* out)
2740 {
2741  assert(out != nullptr);
2742 
2743  if(parameters.empty()) {
2744  *out << "You must enter an ipmask to ungban.";
2745  return;
2746  }
2747 
2748  ban_manager_.unban_group(*out, parameters);
2749 }
2750 
2751 void server::kick_handler(const std::string& /*issuer_name*/,
2752  const std::string& /*query*/,
2753  std::string& parameters,
2754  std::ostringstream* out)
2755 {
2756  assert(out != nullptr);
2757 
2758  if(parameters.empty()) {
2759  *out << "You must enter a mask to kick.";
2760  return;
2761  }
2762 
2763  auto i = std::find(parameters.begin(), parameters.end(), ' ');
2764  const std::string kick_mask = std::string(parameters.begin(), i);
2765  const std::string kick_message = (i == parameters.end()
2766  ? "You have been kicked."
2767  : "You have been kicked. Reason: " + std::string(i + 1, parameters.end()));
2768 
2769  bool kicked = false;
2770 
2771  // if we find a '.' consider it an ip mask
2772  const bool match_ip = (std::count(kick_mask.begin(), kick_mask.end(), '.') >= 1);
2773 
2774  std::vector<player_iterator> users_to_kick;
2776  if((match_ip && utils::wildcard_string_match(player->client_ip(), kick_mask)) ||
2777  (!match_ip && utils::wildcard_string_match(player->info().name(), kick_mask))
2778  ) {
2779  users_to_kick.push_back(player);
2780  }
2781  }
2782 
2783  for(const auto& player : users_to_kick) {
2784  if(kicked) {
2785  *out << "\n";
2786  } else {
2787  kicked = true;
2788  }
2789 
2790  *out << "Kicked " << player->name() << " (" << player->client_ip() << "). '"
2791  << kick_message << "'";
2792 
2793  utils::visit([this, &kick_message](auto&& socket) { async_send_error(socket, kick_message); }, player->socket());
2795  }
2796 
2797  if(!kicked) {
2798  *out << "No user matched '" << kick_mask << "'.";
2799  }
2800 }
2801 
2802 void server::motd_handler(const std::string& /*issuer_name*/,
2803  const std::string& /*query*/,
2804  std::string& parameters,
2805  std::ostringstream* out)
2806 {
2807  assert(out != nullptr);
2808 
2809  if(parameters.empty()) {
2810  if(!motd_.empty()) {
2811  *out << "Message of the day:\n" << motd_;
2812  return;
2813  } else {
2814  *out << "No message of the day set.";
2815  return;
2816  }
2817  }
2818 
2819  motd_ = parameters;
2820  *out << "Message of the day set to: " << motd_;
2821 }
2822 
2823 void server::searchlog_handler(const std::string& /*issuer_name*/,
2824  const std::string& /*query*/,
2825  std::string& parameters,
2826  std::ostringstream* out)
2827 {
2828  assert(out != nullptr);
2829 
2830  if(parameters.empty()) {
2831  *out << "You must enter a mask to search for.";
2832  return;
2833  }
2834 
2835  *out << "IP/NICK LOG for '" << parameters << "'";
2836 
2837  // If this looks like an IP look up which nicks have been connected from it
2838  // Otherwise look for the last IP the nick used to connect
2839  const bool match_ip = (std::count(parameters.begin(), parameters.end(), '.') >= 1);
2840 
2841  if(!user_handler_) {
2842  bool found_something = false;
2843 
2844  for(const auto& i : ip_log_) {
2845  const std::string& username = i.nick;
2846  const std::string& ip = i.ip;
2847 
2848  if((match_ip && utils::wildcard_string_match(ip, parameters)) ||
2849  (!match_ip && utils::wildcard_string_match(utf8::lowercase(username), utf8::lowercase(parameters)))
2850  ) {
2851  found_something = true;
2852  auto player = player_connections_.get<name_t>().find(username);
2853 
2854  if(player != player_connections_.get<name_t>().end() && player->client_ip() == ip) {
2855  *out << std::endl << player_status(*player);
2856  } else {
2857  *out << "\n'" << username << "' @ " << ip
2858  << " last seen: " << lg::get_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
2859  }
2860  }
2861  }
2862 
2863  if(!found_something) {
2864  *out << "\nNo match found.";
2865  }
2866  } else {
2867  if(!match_ip) {
2868  utils::to_sql_wildcards(parameters);
2869  user_handler_->get_ips_for_user(parameters, out);
2870  } else {
2871  user_handler_->get_users_for_ip(parameters, out);
2872  }
2873  }
2874 }
2875 
2876 void server::dul_handler(const std::string& /*issuer_name*/,
2877  const std::string& /*query*/,
2878  std::string& parameters,
2879  std::ostringstream* out)
2880 {
2881  assert(out != nullptr);
2882 
2883  try {
2884  if(parameters.empty()) {
2885  *out << "Unregistered login is " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
2886  } else {
2887  deny_unregistered_login_ = (utf8::lowercase(parameters) == "yes");
2888  *out << "Unregistered login is now " << (deny_unregistered_login_ ? "disallowed" : "allowed") << ".";
2889  }
2890 
2891  } catch(const utf8::invalid_utf8_exception& e) {
2892  ERR_SERVER << "While handling dul (deny unregistered logins), caught an invalid utf8 exception: " << e.what();
2893  }
2894 }
2895 
2896 void server::stopgame(const std::string& /*issuer_name*/,
2897  const std::string& /*query*/,
2898  std::string& parameters,
2899  std::ostringstream* out)
2900 {
2901  const std::string nick = parameters.substr(0, parameters.find(' '));
2902  const std::string reason = parameters.length() > nick.length()+1 ? parameters.substr(nick.length()+1) : "";
2903  auto player = player_connections_.get<name_t>().find(nick);
2904 
2905  if(player != player_connections_.get<name_t>().end()){
2906  std::shared_ptr<game> g = player->get_game();
2907  if(g){
2908  *out << "Player '" << nick << "' is in game with id '" << g->id() << ", " << g->db_id() << "' named '" << g->name() << "'. Ending game for reason: '" << reason << "'...";
2909  delete_game(g->id(), reason);
2910  } else {
2911  *out << "Player '" << nick << "' is not currently in a game.";
2912  }
2913  } else {
2914  *out << "Player '" << nick << "' is not currently logged in.";
2915  }
2916 }
2917 
2918 void server::delete_game(int gameid, const std::string& reason)
2919 {
2920  // Set the availability status for all quitting users.
2921  auto range_pair = player_connections_.get<game_t>().equal_range(gameid);
2922 
2923  // Make a copy of the iterators so that we can change them while iterating over them.
2924  // We can use pair::first_type since equal_range returns a pair of iterators.
2925  std::vector<decltype(range_pair)::first_type> range_vctor;
2926 
2927  for(auto it = range_pair.first; it != range_pair.second; ++it) {
2928  range_vctor.push_back(it);
2929  it->info().mark_available();
2930 
2931  simple_wml::document udiff;
2932  if(make_change_diff(games_and_users_list_.root(), nullptr, "user", it->info().config_address(), udiff)) {
2933  send_to_lobby(udiff);
2934  } else {
2935  ERR_SERVER << "ERROR: delete_game(): Could not find user in players_.";
2936  }
2937  }
2938 
2939  // Put the remaining users back in the lobby.
2940  // This will call cleanup_game() deleter since there won't
2941  // be any references to that game from player_connections_ anymore
2942  for(const auto& it : range_vctor) {
2943  player_connections_.get<game_t>().modify(it, std::bind(&player_record::enter_lobby, std::placeholders::_1));
2944  }
2945 
2946  // send users in the game a notification to leave the game since it has ended
2947  static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
2948 
2949  for(const auto& it : range_vctor) {
2950  player_iterator p { player_connections_.project<0>(it) };
2951  if(reason != "") {
2952  simple_wml::document leave_game_doc_reason("[leave_game]\n[/leave_game]\n", simple_wml::INIT_STATIC);
2953  leave_game_doc_reason.child("leave_game")->set_attr_dup("reason", reason.c_str());
2954  send_to_player(p, leave_game_doc_reason);
2955  } else {
2956  send_to_player(p, leave_game_doc);
2957  }
2959  }
2960 }
2961 
2962 void server::update_game_in_lobby(const wesnothd::game& g, std::optional<player_iterator> exclude)
2963 {
2964  simple_wml::document diff;
2965  if(make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), diff)) {
2966  send_to_lobby(diff, exclude);
2967  }
2968 }
2969 
2970 } // namespace wesnothd
2971 
2972 int main(int argc, char** argv)
2973 {
2974  int port = 15000;
2975  bool keep_alive = false;
2976  std::size_t min_threads = 5;
2977  std::size_t max_threads = 0;
2978 
2979  srand(static_cast<unsigned>(std::time(nullptr)));
2980 
2981  std::string config_file;
2982 
2983  // setting path to currentworking directory
2985 
2986  // show 'info' by default
2988  lg::timestamps(true);
2989 
2990  for(int arg = 1; arg != argc; ++arg) {
2991  const std::string val(argv[arg]);
2992  if(val.empty()) {
2993  continue;
2994  }
2995 
2996  if((val == "--config" || val == "-c") && arg + 1 != argc) {
2997  config_file = argv[++arg];
2998  } else if(val == "--verbose" || val == "-v") {
3000  } else if(val == "--dump-wml" || val == "-w") {
3001  dump_wml = true;
3002  } else if(val.substr(0, 6) == "--log-") {
3003  std::size_t p = val.find('=');
3004  if(p == std::string::npos) {
3005  PLAIN_LOG << "unknown option: " << val;
3006  return 2;
3007  }
3008 
3009  std::string s = val.substr(6, p - 6);
3010  int severity;
3011 
3012  if(s == "error") {
3013  severity = lg::err().get_severity();
3014  } else if(s == "warning") {
3015  severity = lg::warn().get_severity();
3016  } else if(s == "info") {
3017  severity = lg::info().get_severity();
3018  } else if(s == "debug") {
3019  severity = lg::debug().get_severity();
3020  } else {
3021  PLAIN_LOG << "unknown debug level: " << s;
3022  return 2;
3023  }
3024 
3025  while(p != std::string::npos) {
3026  std::size_t q = val.find(',', p + 1);
3027  s = val.substr(p + 1, q == std::string::npos ? q : q - (p + 1));
3028 
3029  if(!lg::set_log_domain_severity(s, severity)) {
3030  PLAIN_LOG << "unknown debug domain: " << s;
3031  return 2;
3032  }
3033 
3034  p = q;
3035  }
3036  } else if((val == "--port" || val == "-p") && arg + 1 != argc) {
3037  port = atoi(argv[++arg]);
3038  } else if(val == "--keepalive") {
3039  keep_alive = true;
3040  } else if(val == "--help" || val == "-h") {
3041  std::cout << "usage: " << argv[0]
3042  << " [-dvV] [-c path] [-m n] [-p port] [-t n]\n"
3043  << " -c, --config <path> Tells wesnothd where to find the config file to use.\n"
3044  << " -d, --daemon Runs wesnothd as a daemon.\n"
3045  << " -h, --help Shows this usage message.\n"
3046  << " --log-<level>=<domain1>,<domain2>,...\n"
3047  << " sets the severity level of the debug domains.\n"
3048  << " 'all' can be used to match any debug domain.\n"
3049  << " Available levels: error, warning, info, debug.\n"
3050  << " -p, --port <port> Binds the server to the specified port.\n"
3051  << " --keepalive Enable TCP keepalive.\n"
3052  << " -t, --threads <n> Uses n worker threads for network I/O (default: 5).\n"
3053  << " -v --verbose Turns on more verbose logging.\n"
3054  << " -V, --version Returns the server version.\n"
3055  << " -w, --dump-wml Print all WML sent to clients to stdout.\n";
3056  return 0;
3057  } else if(val == "--version" || val == "-V") {
3058  std::cout << "Battle for Wesnoth server " << game_config::wesnoth_version.str() << "\n";
3059  return 0;
3060  } else if(val == "--daemon" || val == "-d") {
3061 #ifdef _WIN32
3062  ERR_SERVER << "Running as a daemon is not supported on this platform";
3063  return -1;
3064 #else
3065  const pid_t pid = fork();
3066  if(pid < 0) {
3067  ERR_SERVER << "Could not fork and run as a daemon";
3068  return -1;
3069  } else if(pid > 0) {
3070  std::cout << "Started wesnothd as a daemon with process id " << pid << "\n";
3071  return 0;
3072  }
3073 
3074  setsid();
3075 #endif
3076  } else if((val == "--threads" || val == "-t") && arg + 1 != argc) {
3077  min_threads = atoi(argv[++arg]);
3078  if(min_threads > 30) {
3079  min_threads = 30;
3080  }
3081  } else if((val == "--max-threads" || val == "-T") && arg + 1 != argc) {
3082  max_threads = atoi(argv[++arg]);
3083  } else if(val == "--request_sample_frequency" && arg + 1 != argc) {
3084  wesnothd::request_sample_frequency = atoi(argv[++arg]);
3085  } else {
3086  ERR_SERVER << "unknown option: " << val;
3087  return 2;
3088  }
3089  }
3090 
3091  return wesnothd::server(port, keep_alive, config_file, min_threads, max_threads).run();
3092 }
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:1172
std::string get_timestamp(const std::time_t &t, const std::string &format)
Definition: log.cpp:313
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:260
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:2081
std::string get_timespan(const std::time_t &t)
Definition: log.cpp:320
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:1981
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:2416
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:2248
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:1937
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:228
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:2580
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:213
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:2168
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:2257
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:2177
#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:1315
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:1420
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:2196
#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:2517
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:2656
std::string get_cwd()
Definition: filesystem.cpp:927
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:1017
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:2047
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:2353
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:1498
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:2458
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:2001
void motd_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2802
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:2492
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:2823
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:234
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:2751
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:1386
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:2147
std::size_t i
Definition: function.cpp:968
logger & err()
Definition: log.cpp:216
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:2010
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:2721
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:2205
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:2918
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:2266
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:2896
#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:1212
std::string observer
bool set_log_domain_severity(const std::string &name, int severity)
Definition: log.cpp:256
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:2112
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:2396
#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:222
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:1342
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:2876
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:2962
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:2736
std::string process_command(std::string cmd, std::string issuer_name)
Process commands from admins and users.
Definition: server.cpp:2026
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:2187
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:1913
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:2375
#define MP_NAME_AUTH_BAN_EMAIL_ERROR
void pm_handler(const std::string &, const std::string &, std::string &, std::ostringstream *)
Definition: server.cpp:2311
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:180
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:1924
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:1290
void send_server_message_to_lobby(const std::string &message, std::optional< player_iterator > exclude={})
Definition: server.cpp:1991
#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