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