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