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