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