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