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