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