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