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