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