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