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