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