
merged the samples command back into metrics actually allowed advertised commands for ordinary users
2333 lines
79 KiB
C++
2333 lines
79 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2
|
|
or at your option any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
/**
|
|
* @file server/server.cpp
|
|
* Wesnoth-Server, for multiplayer-games.
|
|
*/
|
|
|
|
#include "../global.hpp"
|
|
|
|
#include "../config.hpp"
|
|
#include "../game_config.hpp"
|
|
#include "../log.hpp"
|
|
#include "../map.hpp" // gamemap::MAX_PLAYERS
|
|
#include "../network.hpp"
|
|
#include "../filesystem.hpp"
|
|
#include "../time.hpp"
|
|
#include "../serialization/parser.hpp"
|
|
#include "../serialization/preprocessor.hpp"
|
|
#include "../serialization/string_utils.hpp"
|
|
|
|
#include "game.hpp"
|
|
#include "input_stream.hpp"
|
|
#include "metrics.hpp"
|
|
#include "player.hpp"
|
|
#include "proxy.hpp"
|
|
#include "simple_wml.hpp"
|
|
#include "ban.hpp"
|
|
|
|
#include "user_handler.hpp"
|
|
// For now sample_user_handler is broken due
|
|
// to the hardcoding of the phpbb hashing algorithms
|
|
// #include "sample_user_handler.hpp"
|
|
|
|
#ifdef HAVE_MYSQLPP
|
|
#include "forum_user_handler.hpp"
|
|
#endif
|
|
|
|
#include <boost/scoped_ptr.hpp>
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <queue>
|
|
|
|
#include <csignal>
|
|
|
|
#ifndef _WIN32
|
|
#include <sys/times.h>
|
|
|
|
|
|
namespace {
|
|
|
|
clock_t get_cpu_time(bool active) {
|
|
if(!active) {
|
|
return 0;
|
|
}
|
|
struct tms buf;
|
|
times(&buf);
|
|
return buf.tms_utime + buf.tms_stime;
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
// on Windows we don't calculate CPU time
|
|
clock_t get_cpu_time(bool active) {
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* fatal and directly server related errors/warnings,
|
|
* ie not caused by erroneous client data
|
|
*/
|
|
#define ERR_SERVER LOG_STREAM(err, mp_server)
|
|
|
|
/** clients send wrong/unexpected data */
|
|
#define WRN_SERVER LOG_STREAM(warn, mp_server)
|
|
|
|
/** normal events */
|
|
#define LOG_SERVER LOG_STREAM(info, mp_server)
|
|
#define DBG_SERVER LOG_STREAM(debug, mp_server)
|
|
#define ERR_CONFIG LOG_STREAM(err, config)
|
|
#define WRN_CONFIG LOG_STREAM(warn, config)
|
|
|
|
//compatibility code for MS compilers
|
|
#ifndef SIGHUP
|
|
#define SIGHUP 20
|
|
#endif
|
|
/** @todo FIXME: should define SIGINT here too, but to what? */
|
|
|
|
sig_atomic_t config_reload = 0;
|
|
|
|
static void reload_config(int signal) {
|
|
assert(signal == SIGHUP);
|
|
config_reload = 1;
|
|
}
|
|
|
|
static void exit_sigint(int signal) {
|
|
assert(signal == SIGINT);
|
|
LOG_SERVER << "SIGINT caught, exiting without cleanup immediately.\n";
|
|
exit(1);
|
|
}
|
|
|
|
static void exit_sigterm(int signal) {
|
|
assert(signal == SIGTERM);
|
|
LOG_SERVER << "SIGTERM caught, exiting without cleanup immediately.\n";
|
|
exit(1);
|
|
}
|
|
|
|
namespace {
|
|
|
|
// we take profiling info on every n requests
|
|
int request_sample_frequency = 1;
|
|
|
|
void send_doc(simple_wml::document& doc, network::connection connection, std::string type = "")
|
|
{
|
|
if (type.empty())
|
|
type = doc.root().first_child().to_string();
|
|
simple_wml::string_span s = doc.output_compressed();
|
|
network::send_raw_data(s.begin(), s.size(), connection, type);
|
|
}
|
|
|
|
void make_add_diff(const simple_wml::node& src, const char* gamelist,
|
|
const char* type,
|
|
simple_wml::document& out, int index=-1)
|
|
{
|
|
if(out.root().child("gamelist_diff") == NULL) {
|
|
out.root().add_child("gamelist_diff");
|
|
}
|
|
|
|
simple_wml::node* top = out.root().child("gamelist_diff");
|
|
if(gamelist) {
|
|
top = &top->add_child("change_child");
|
|
top->set_attr_int("index", 0);
|
|
top = &top->add_child("gamelist");
|
|
}
|
|
|
|
simple_wml::node& insert = top->add_child("insert_child");
|
|
const simple_wml::node::child_list& children = src.children(type);
|
|
assert(!children.empty());
|
|
if(index < 0) {
|
|
index = children.size() - 1;
|
|
}
|
|
|
|
assert(index < static_cast<int>(children.size()));
|
|
insert.set_attr_int("index", index);
|
|
children[index]->copy_into(insert.add_child(type));
|
|
}
|
|
|
|
bool make_delete_diff(const simple_wml::node& src,
|
|
const char* gamelist,
|
|
const char* type,
|
|
const simple_wml::node* remove,
|
|
simple_wml::document& out)
|
|
{
|
|
if(out.root().child("gamelist_diff") == NULL) {
|
|
out.root().add_child("gamelist_diff");
|
|
}
|
|
|
|
simple_wml::node* top = out.root().child("gamelist_diff");
|
|
if(gamelist) {
|
|
top = &top->add_child("change_child");
|
|
top->set_attr_int("index", 0);
|
|
top = &top->add_child("gamelist");
|
|
}
|
|
|
|
const simple_wml::node::child_list& children = src.children(type);
|
|
const simple_wml::node::child_list::const_iterator itor =
|
|
std::find(children.begin(), children.end(), remove);
|
|
if(itor == children.end()) {
|
|
return false;
|
|
}
|
|
const int index = itor - children.begin();
|
|
simple_wml::node& del = top->add_child("delete_child");
|
|
del.set_attr_int("index", index);
|
|
del.add_child(type);
|
|
return true;
|
|
}
|
|
|
|
bool make_change_diff(const simple_wml::node& src,
|
|
const char* gamelist,
|
|
const char* type,
|
|
const simple_wml::node* item,
|
|
simple_wml::document& out)
|
|
{
|
|
if(out.root().child("gamelist_diff") == NULL) {
|
|
out.root().add_child("gamelist_diff");
|
|
}
|
|
|
|
simple_wml::node* top = out.root().child("gamelist_diff");
|
|
if(gamelist) {
|
|
top = &top->add_child("change_child");
|
|
top->set_attr_int("index", 0);
|
|
top = &top->add_child("gamelist");
|
|
}
|
|
const simple_wml::node::child_list& children = src.children(type);
|
|
const simple_wml::node::child_list::const_iterator itor =
|
|
std::find(children.begin(), children.end(), item);
|
|
if(itor == children.end()) {
|
|
return false;
|
|
}
|
|
|
|
simple_wml::node& diff = *top;
|
|
simple_wml::node& del = diff.add_child("delete_child");
|
|
const int index = itor - children.begin();
|
|
del.set_attr_int("index", index);
|
|
del.add_child(type);
|
|
|
|
//inserts will be processed first by the client, so insert at index+1,
|
|
//and then when the delete is processed we'll slide into the right position
|
|
simple_wml::node& insert = diff.add_child("insert_child");
|
|
insert.set_attr_int("index", index+1);
|
|
children[index]->copy_into(insert.add_child(type));
|
|
return true;
|
|
}
|
|
|
|
}
|
|
class fps_limiter {
|
|
size_t start_ticks_;
|
|
size_t ms_per_frame_;
|
|
public:
|
|
fps_limiter(size_t ms_per_frame = 20) : start_ticks_(0),ms_per_frame_(ms_per_frame)
|
|
{}
|
|
|
|
void limit() {
|
|
size_t current_ticks = SDL_GetTicks();
|
|
if (current_ticks - start_ticks_ < ms_per_frame_) {
|
|
SDL_Delay(ms_per_frame_ - (current_ticks - start_ticks_));
|
|
start_ticks_ += ms_per_frame_;
|
|
} else {
|
|
start_ticks_ = current_ticks;
|
|
}
|
|
}
|
|
|
|
void set_ms_per_frame(size_t ms_per_frame)
|
|
{
|
|
ms_per_frame_ = ms_per_frame;
|
|
}
|
|
|
|
void set_fps(size_t fps)
|
|
{
|
|
ms_per_frame_ = 1000 / fps;
|
|
}
|
|
};
|
|
|
|
|
|
class server
|
|
{
|
|
public:
|
|
server(int port, const std::string& config_file, size_t min_threads,size_t max_threads);
|
|
void run();
|
|
private:
|
|
void send_error(network::connection sock, const char* msg) const;
|
|
void send_error_dup(network::connection sock, const std::string& msg) const;
|
|
|
|
// The same as send_error(), we just add an extra child to the response
|
|
// telling the client the chosen username requires a password.
|
|
void send_password_request(network::connection sock, const char* msg, const std::string& user);
|
|
|
|
const network::manager net_manager_;
|
|
network::server_manager server_;
|
|
wesnothd::ban_manager ban_manager_;
|
|
|
|
std::map<std::string, std::string> ip_log_;
|
|
|
|
boost::scoped_ptr<user_handler> user_handler_;
|
|
std::map<network::connection,std::string> seeds_;
|
|
|
|
/** std::map<network::connection,player>. */
|
|
wesnothd::player_map players_;
|
|
wesnothd::player_map ghost_players_ ;
|
|
|
|
std::vector<wesnothd::game*> games_;
|
|
wesnothd::game not_logged_in_;
|
|
|
|
/** The lobby is implemented as a game. */
|
|
wesnothd::game lobby_;
|
|
|
|
/** server socket/fifo. */
|
|
boost::scoped_ptr<input_stream> input_;
|
|
|
|
const std::string config_file_;
|
|
config cfg_;
|
|
|
|
/** Read the server config from file 'config_file_'. */
|
|
config read_config() const;
|
|
|
|
// settings from the server config
|
|
std::set<std::string> accepted_versions_;
|
|
std::map<std::string,config> redirected_versions_;
|
|
std::map<std::string,config> proxy_versions_;
|
|
std::vector<std::string> disallowed_names_;
|
|
std::string admin_passwd_;
|
|
std::set<network::connection> admins_;
|
|
std::string motd_;
|
|
size_t default_max_messages_;
|
|
size_t default_time_period_;
|
|
size_t concurrent_connections_;
|
|
bool graceful_restart;
|
|
time_t lan_server_;
|
|
time_t last_user_seen_time_;
|
|
std::string restart_command;
|
|
size_t max_ip_log_size_;
|
|
|
|
/** Parse the server config into local variables. */
|
|
void load_config();
|
|
|
|
bool ip_exceeds_connection_limit(const std::string& ip) const;
|
|
bool is_ip_banned(const std::string& ip) const;
|
|
|
|
simple_wml::document version_query_response_;
|
|
simple_wml::document login_response_;
|
|
simple_wml::document join_lobby_response_;
|
|
simple_wml::document games_and_users_list_;
|
|
|
|
metrics metrics_;
|
|
|
|
time_t last_ping_;
|
|
time_t last_stats_;
|
|
void dump_stats(const time_t& now);
|
|
|
|
time_t last_uh_clean_;
|
|
void clean_user_handler(const time_t& now);
|
|
|
|
void process_data(const network::connection sock,
|
|
simple_wml::document& data);
|
|
void process_login(const network::connection sock,
|
|
simple_wml::document& data);
|
|
|
|
/** Handle queries from clients. */
|
|
void process_query(const network::connection sock,
|
|
simple_wml::node& query);
|
|
|
|
/** Process commands from admins and users. */
|
|
std::string process_command(const std::string& cmd, const std::string& issuer_name);
|
|
|
|
/** Handle private messages between players. */
|
|
void process_whisper(const network::connection sock,
|
|
simple_wml::node& whisper) const;
|
|
|
|
/** Handle nickname registration related requests from clients. */
|
|
void process_nickserv(const network::connection sock, simple_wml::node& data);
|
|
void process_data_lobby(const network::connection sock,
|
|
simple_wml::document& data);
|
|
void process_data_game(const network::connection sock,
|
|
simple_wml::document& data);
|
|
void delete_game(std::vector<wesnothd::game*>::iterator game_it);
|
|
|
|
void update_game_in_lobby(const wesnothd::game* g, network::connection exclude=0);
|
|
|
|
void start_new_server();
|
|
};
|
|
|
|
server::server(int port, const std::string& config_file, size_t min_threads,
|
|
size_t max_threads) :
|
|
net_manager_(min_threads,max_threads),
|
|
server_(port),
|
|
ban_manager_(),
|
|
user_handler_(NULL),
|
|
seeds_(),
|
|
players_(),
|
|
ghost_players_(),
|
|
games_(),
|
|
not_logged_in_(players_),
|
|
lobby_(players_),
|
|
input_(),
|
|
config_file_(config_file),
|
|
cfg_(read_config()),
|
|
accepted_versions_(),
|
|
redirected_versions_(),
|
|
proxy_versions_(),
|
|
disallowed_names_(),
|
|
admin_passwd_(),
|
|
admins_(),
|
|
motd_(),
|
|
default_max_messages_(0),
|
|
default_time_period_(0),
|
|
concurrent_connections_(0),
|
|
graceful_restart(false),
|
|
lan_server_(time(NULL)),
|
|
last_user_seen_time_(time(NULL)),
|
|
restart_command(),
|
|
max_ip_log_size_(0),
|
|
version_query_response_("[version]\n[/version]\n", simple_wml::INIT_COMPRESSED),
|
|
login_response_("[mustlogin]\n[/mustlogin]\n", simple_wml::INIT_COMPRESSED),
|
|
join_lobby_response_("[join_lobby]\n[/join_lobby]\n", simple_wml::INIT_COMPRESSED),
|
|
games_and_users_list_("[gamelist]\n[/gamelist]\n", simple_wml::INIT_STATIC),
|
|
metrics_(),
|
|
last_ping_(time(NULL)),
|
|
last_stats_(last_ping_),
|
|
last_uh_clean_(last_ping_)
|
|
{
|
|
load_config();
|
|
ban_manager_.read();
|
|
#ifndef _MSC_VER
|
|
signal(SIGHUP, reload_config);
|
|
#endif
|
|
signal(SIGINT, exit_sigint);
|
|
signal(SIGTERM, exit_sigterm);
|
|
}
|
|
|
|
void server::send_error(network::connection sock, const char* msg) const
|
|
{
|
|
simple_wml::document doc;
|
|
doc.root().add_child("error").set_attr("message", msg);
|
|
simple_wml::string_span output = doc.output_compressed();
|
|
network::send_raw_data(output.begin(), output.size(), sock, "error");
|
|
}
|
|
|
|
void server::send_error_dup(network::connection sock, const std::string& msg) const
|
|
{
|
|
simple_wml::document doc;
|
|
doc.root().add_child("error").set_attr_dup("message", msg.c_str());
|
|
simple_wml::string_span output = doc.output_compressed();
|
|
network::send_raw_data(output.begin(), output.size(), sock, "error");
|
|
}
|
|
|
|
void server::send_password_request(network::connection sock, const char* msg, const std::string& user)
|
|
{
|
|
|
|
std::string salt1 = user_handler_->create_salt();
|
|
std::string salt2 = user_handler_->create_pepper(user, 0);
|
|
std::string salt3 = user_handler_->create_pepper(user, 1);
|
|
|
|
seeds_.insert(std::pair<network::connection,std::string>(sock, salt1));
|
|
|
|
simple_wml::document doc;
|
|
doc.root().add_child("error").set_attr("message", msg);
|
|
(*(doc.root().child("error"))).set_attr("password_request", "yes");
|
|
(*(doc.root().child("error"))).set_attr("random_salt", salt1.c_str());
|
|
(*(doc.root().child("error"))).set_attr("hash_seed", salt2.c_str());
|
|
(*(doc.root().child("error"))).set_attr("salt", salt3.c_str());
|
|
|
|
simple_wml::string_span output = doc.output_compressed();
|
|
network::send_raw_data(output.begin(), output.size(), sock);
|
|
}
|
|
|
|
config server::read_config() const {
|
|
config configuration;
|
|
if (config_file_ == "") return configuration;
|
|
scoped_istream stream = preprocess_file(config_file_);
|
|
std::string errors;
|
|
try {
|
|
read(configuration, *stream, &errors);
|
|
if (errors.empty()) {
|
|
LOG_SERVER << "Server configuration from file: '" << config_file_
|
|
<< "' read.\n";
|
|
} else {
|
|
ERR_CONFIG << "ERROR: Errors reading configuration file: '"
|
|
<< errors << "'.\n";
|
|
}
|
|
} catch(config::error& e) {
|
|
ERR_CONFIG << "ERROR: Could not read configuration file: '"
|
|
<< config_file_ << "': '" << e.message << "'.\n";
|
|
}
|
|
return configuration;
|
|
}
|
|
|
|
void server::load_config() {
|
|
#ifndef FIFODIR
|
|
# warning "No FIFODIR set"
|
|
# define FIFODIR "/var/run/wesnothd"
|
|
#endif
|
|
const std::string fifo_path = (cfg_["fifo_path"].empty() ? std::string(FIFODIR) + "/socket" : std::string(cfg_["fifo_path"]));
|
|
input_.reset();
|
|
input_.reset(new input_stream(fifo_path));
|
|
|
|
admin_passwd_ = cfg_["passwd"];
|
|
motd_ = cfg_["motd"];
|
|
lan_server_ = lexical_cast_default<time_t>(cfg_["lan_server"], 0);
|
|
|
|
disallowed_names_.clear();
|
|
if (cfg_["disallow_names"] == "") {
|
|
disallowed_names_.push_back("*admin*");
|
|
disallowed_names_.push_back("*admln*");
|
|
disallowed_names_.push_back("*server*");
|
|
disallowed_names_.push_back("player");
|
|
disallowed_names_.push_back("network");
|
|
disallowed_names_.push_back("human");
|
|
disallowed_names_.push_back("computer");
|
|
disallowed_names_.push_back("ai");
|
|
disallowed_names_.push_back("ai?");
|
|
} else {
|
|
disallowed_names_ = utils::split(cfg_["disallow_names"]);
|
|
}
|
|
default_max_messages_ =
|
|
lexical_cast_default<size_t>(cfg_["max_messages"],4);
|
|
default_time_period_ =
|
|
lexical_cast_default<size_t>(cfg_["messages_time_period"],10);
|
|
concurrent_connections_ =
|
|
lexical_cast_default<size_t>(cfg_["connections_allowed"],5);
|
|
max_ip_log_size_ =
|
|
lexical_cast_default<size_t>(cfg_["max_ip_log_size"],500);
|
|
// Example config line:
|
|
// restart_command="./wesnothd-debug -d -c ~/.wesnoth1.5/server.cfg"
|
|
// remember to make new one as a daemon or it will block old one
|
|
restart_command = cfg_["restart_command"];
|
|
|
|
ntime::source::get_source().set_frame_time(lexical_cast_default<size_t>(cfg_["ms_per_frame"],20));
|
|
|
|
accepted_versions_.clear();
|
|
const std::string& versions = cfg_["versions_accepted"];
|
|
if (versions.empty() == false) {
|
|
const std::vector<std::string> accepted(utils::split(versions));
|
|
for (std::vector<std::string>::const_iterator i = accepted.begin(); i != accepted.end(); ++i) {
|
|
accepted_versions_.insert(*i);
|
|
}
|
|
} else {
|
|
accepted_versions_.insert(game_config::version);
|
|
accepted_versions_.insert("test");
|
|
}
|
|
|
|
redirected_versions_.clear();
|
|
const config::child_list& redirects = cfg_.get_children("redirect");
|
|
for (config::child_list::const_iterator i = redirects.begin(); i != redirects.end(); ++i) {
|
|
const std::vector<std::string> versions(utils::split((**i)["version"]));
|
|
for (std::vector<std::string>::const_iterator j = versions.begin(); j != versions.end(); ++j) {
|
|
redirected_versions_[*j] = **i;
|
|
}
|
|
}
|
|
|
|
proxy_versions_.clear();
|
|
const config::child_list& proxies = cfg_.get_children("proxy");
|
|
for (config::child_list::const_iterator p = proxies.begin(); p != proxies.end(); ++p) {
|
|
const std::vector<std::string> versions(utils::split((**p)["version"]));
|
|
for (std::vector<std::string>::const_iterator j = versions.begin(); j != versions.end(); ++j) {
|
|
proxy_versions_[*j] = **p;
|
|
}
|
|
}
|
|
ban_manager_.load_config(cfg_);
|
|
|
|
// If there is a [user_handler] tag in the config file
|
|
// allow nick registration, otherwise we set user_handler_
|
|
// to NULL. Thus we must check user_handler_ for not being
|
|
// NULL everytime we want to use it.
|
|
user_handler_.reset();
|
|
|
|
#ifdef HAVE_MYSQLPP
|
|
if (const config* user_handler = cfg_.child("user_handler")) {
|
|
user_handler_.reset(new fuh(*user_handler));
|
|
// Initiate the mailer class with the [mail] tag
|
|
// from the config file
|
|
if (user_handler_) user_handler_->init_mailer(cfg_.child("mail"));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool server::ip_exceeds_connection_limit(const std::string& ip) const {
|
|
if (concurrent_connections_ == 0) return false;
|
|
size_t connections = 0;
|
|
for (wesnothd::player_map::const_iterator i = players_.begin(); i != players_.end(); ++i) {
|
|
if (network::ip_address(i->first) == ip) {
|
|
++connections;
|
|
}
|
|
}
|
|
|
|
return connections >= concurrent_connections_;
|
|
}
|
|
|
|
bool server::is_ip_banned(const std::string& ip) const {
|
|
return ban_manager_.is_ip_banned(ip);
|
|
}
|
|
|
|
void server::dump_stats(const time_t& now) {
|
|
last_stats_ = now;
|
|
LOG_SERVER << "Statistics:"
|
|
<< "\tnumber_of_games = " << games_.size()
|
|
<< "\tnumber_of_users = " << players_.size()
|
|
<< "\tnumber_of_ghost_users = " << ghost_players_.size()
|
|
<< "\tlobby_users = " << lobby_.nobservers() << "\n";
|
|
}
|
|
|
|
void server::clean_user_handler(const time_t& now) {
|
|
if(!user_handler_) {
|
|
return;
|
|
}
|
|
last_uh_clean_ = now;
|
|
user_handler_->clean_up();
|
|
}
|
|
|
|
void server::run() {
|
|
int graceful_counter = 0;
|
|
|
|
for (int loop = 0;; ++loop) {
|
|
// Try to run with 50 FPS all the time
|
|
// Server will respond a bit faster under heavy load
|
|
ntime::source::get_source().start_frame();
|
|
try {
|
|
// We are going to waith 10 seconds before shutting down so users can get out of game.
|
|
if (graceful_restart && games_.size() == 0 && ++graceful_counter > 500 )
|
|
{
|
|
// TODO: We should implement client side autoreconnect.
|
|
// Idea:
|
|
// server should send [reconnect]host=host,port=number[/reconnect]
|
|
// Then client would reconnect to new server automaticaly.
|
|
// This would also allow server to move to new port or address if there is need
|
|
|
|
process_command("msg All games ended. Shutting down now. Reconnect to the new server instance.", "system");
|
|
throw network::error("shut down");
|
|
}
|
|
|
|
if (config_reload == 1) {
|
|
cfg_ = read_config();
|
|
load_config();
|
|
config_reload = 0;
|
|
}
|
|
|
|
// Process commands from the server socket/fifo
|
|
std::string admin_cmd;
|
|
if (input_ && input_->read_line(admin_cmd)) {
|
|
process_command(admin_cmd, "*socket*");
|
|
}
|
|
|
|
time_t now = time(NULL);
|
|
if (last_ping_ + 30 <= now) {
|
|
if (lan_server_ && players_.empty() && last_user_seen_time_ + lan_server_ < now)
|
|
{
|
|
LOG_SERVER << "Lan server has been empty for " << (now - last_user_seen_time_) << " seconds. Shutting down!\n";
|
|
// We have to shutdown
|
|
graceful_restart = true;
|
|
}
|
|
// and check if bans have expired
|
|
ban_manager_.check_ban_times(now);
|
|
// Make sure we log stats every 5 minutes
|
|
if (last_stats_ + 5*60 <= now) {
|
|
dump_stats(now);
|
|
}
|
|
|
|
// Cleaning the user_handler once a day should be more than enough
|
|
if (last_uh_clean_ + 60 * 60 * 24 <= now) {
|
|
clean_user_handler(now);
|
|
}
|
|
|
|
// Send network stats every hour
|
|
static int prev_hour = localtime(&now)->tm_hour;
|
|
if (prev_hour != localtime(&now)->tm_hour)
|
|
{
|
|
prev_hour = localtime(&now)->tm_hour;
|
|
LOG_SERVER << network::get_bandwidth_stats();
|
|
|
|
}
|
|
|
|
// send a 'ping' to all players to detect ghosts
|
|
DBG_SERVER << "Pinging inactive players.\n" ;
|
|
std::ostringstream strstr ;
|
|
strstr << "ping=\"" << lexical_cast<std::string>(now) << "\"" ;
|
|
simple_wml::document ping( strstr.str().c_str(),
|
|
simple_wml::INIT_COMPRESSED ) ;
|
|
simple_wml::string_span s = ping.output_compressed() ;
|
|
for (wesnothd::player_map::const_iterator i = ghost_players_.begin();
|
|
i != ghost_players_.end(); ++i)
|
|
{
|
|
DBG_SERVER << "Pinging " << i->second.name() << "(" << i->first << ").\n" ;
|
|
network::send_raw_data( s.begin(),
|
|
s.size(),
|
|
i->first, "ping" ) ;
|
|
}
|
|
|
|
// Copy new player list on top of ghost_players_ list.
|
|
// Only a single thread should be accessing this
|
|
{
|
|
// Erase before we copy - speeds inserts
|
|
ghost_players_.clear() ;
|
|
std::copy( players_.begin(), players_.end(),
|
|
std::inserter( ghost_players_, ghost_players_.begin() ) ) ;
|
|
}
|
|
last_ping_ = now;
|
|
}
|
|
|
|
network::process_send_queue();
|
|
|
|
network::connection sock = network::accept_connection();
|
|
if (sock) {
|
|
const std::string& ip = network::ip_address(sock);
|
|
if (is_ip_banned(ip)) {
|
|
LOG_SERVER << ip << "\trejected banned user.\n";
|
|
send_error(sock, "You are banned.");
|
|
network::disconnect(sock);
|
|
} else if (ip_exceeds_connection_limit(ip)) {
|
|
LOG_SERVER << ip << "\trejected ip due to excessive connections\n";
|
|
send_error(sock, "Too many connections from your IP.");
|
|
network::disconnect(sock);
|
|
} else {
|
|
DBG_SERVER << ip << "\tnew connection accepted. (socket: "
|
|
<< sock << ")\n";
|
|
send_doc(version_query_response_, sock);
|
|
not_logged_in_.add_player(sock, true);
|
|
}
|
|
}
|
|
|
|
static int sample_counter = 0;
|
|
|
|
std::vector<char> buf;
|
|
network::bandwidth_in_ptr bandwidth_type;
|
|
while ((sock = network::receive_data(buf, &bandwidth_type)) != network::null_connection) {
|
|
metrics_.service_request();
|
|
|
|
if(buf.empty()) {
|
|
std::cerr << "received empty packet\n";
|
|
continue;
|
|
}
|
|
|
|
//TODO: this is a HUGE HACK. There was a bug in Wesnoth 1.4
|
|
//that caused it to still use binary WML for the leave game
|
|
//message. (ugh). We will see if this looks like binary WML
|
|
//and if it does, assume it's a leave_game message
|
|
if(buf.front() < 5) {
|
|
std::cerr << "hit wesnoth bug...forcing disconnection incorrectly." << buf.front() << std::endl ;
|
|
static simple_wml::document leave_game_doc(
|
|
"[leave_game]\n[/leave_game]\n",
|
|
simple_wml::INIT_COMPRESSED);
|
|
process_data(sock, leave_game_doc);
|
|
continue;
|
|
}
|
|
|
|
const bool sample = request_sample_frequency >= 1 && (sample_counter++ % request_sample_frequency) == 0;
|
|
|
|
const clock_t before_parsing = get_cpu_time(sample);
|
|
|
|
char* buf_ptr = new char [buf.size()];
|
|
memcpy(buf_ptr, &buf[0], buf.size());
|
|
simple_wml::string_span compressed_buf(buf_ptr, buf.size());
|
|
simple_wml::document data(compressed_buf);
|
|
data.take_ownership_of_buffer(buf_ptr);
|
|
std::vector<char>().swap(buf);
|
|
|
|
const clock_t after_parsing = get_cpu_time(sample);
|
|
|
|
process_data(sock, data);
|
|
|
|
bandwidth_type->set_type(data.root().first_child().to_string());
|
|
if(sample) {
|
|
const clock_t after_processing = get_cpu_time(sample);
|
|
metrics_.record_sample(data.root().first_child(),
|
|
after_parsing - before_parsing,
|
|
after_processing - after_parsing);
|
|
}
|
|
}
|
|
|
|
metrics_.no_requests();
|
|
|
|
} catch(config::error& e) {
|
|
WRN_CONFIG << "Warning: error in received data: " << e.message << "\n";
|
|
} catch(simple_wml::error& e) {
|
|
WRN_CONFIG << "Warning: error in received data\n";
|
|
} catch(network::error& e) {
|
|
if (e.message == "shut down") {
|
|
LOG_SERVER << "Try to disconnect all users...\n";
|
|
for (wesnothd::player_map::const_iterator pl = players_.begin();
|
|
pl != players_.end(); ++pl)
|
|
{
|
|
network::disconnect(pl->first);
|
|
}
|
|
LOG_SERVER << "Shutting server down.\n";
|
|
break;
|
|
}
|
|
if (!e.socket) {
|
|
ERR_SERVER << "network error: " << e.message << "\n";
|
|
e.disconnect();
|
|
continue;
|
|
}
|
|
DBG_SERVER << "socket closed: " << e.message << "\n";
|
|
const std::string ip = network::ip_address(e.socket);
|
|
if (proxy::is_proxy(e.socket)) {
|
|
LOG_SERVER << ip << "\tProxy user disconnected.\n";
|
|
proxy::disconnect(e.socket);
|
|
e.disconnect();
|
|
DBG_SERVER << "done closing socket...\n";
|
|
continue;
|
|
}
|
|
// Was the user already logged in?
|
|
const wesnothd::player_map::iterator pl_it = players_.find(e.socket);
|
|
if (pl_it == players_.end()) {
|
|
if (not_logged_in_.is_observer(e.socket)) {
|
|
DBG_SERVER << ip << "\tNot logged in user disconnected.\n";
|
|
not_logged_in_.remove_player(e.socket);
|
|
} else {
|
|
WRN_SERVER << ip << "\tWarning: User disconnected right after the connection was accepted.\n";
|
|
}
|
|
e.disconnect();
|
|
DBG_SERVER << "done closing socket...\n";
|
|
continue;
|
|
}
|
|
const simple_wml::node::child_list& users = games_and_users_list_.root().children("user");
|
|
const size_t index = std::find(users.begin(), users.end(), pl_it->second.config_address()) - users.begin();
|
|
if (index < users.size()) {
|
|
simple_wml::document diff;
|
|
if(make_delete_diff(games_and_users_list_.root(), NULL, "user",
|
|
pl_it->second.config_address(), diff)) {
|
|
lobby_.send_data(diff, e.socket);
|
|
}
|
|
|
|
games_and_users_list_.root().remove_child("user",index);
|
|
} else {
|
|
ERR_SERVER << ip << "ERROR: Could not find user to remove: "
|
|
<< pl_it->second.name() << " in games_and_users_list_.\n";
|
|
}
|
|
// Was the player in the lobby or a game?
|
|
if (lobby_.is_member(e.socket)) {
|
|
lobby_.remove_player(e.socket);
|
|
LOG_SERVER << ip << "\t" << pl_it->second.name()
|
|
<< "\thas logged off. (socket: " << e.socket << ")\n";
|
|
|
|
} else {
|
|
for (std::vector<wesnothd::game*>::iterator g = games_.begin();
|
|
g != games_.end(); ++g)
|
|
{
|
|
if (!(*g)->is_member(e.socket)) {
|
|
continue;
|
|
}
|
|
// Did the last player leave?
|
|
if ((*g)->remove_player(e.socket, true)) {
|
|
delete_game(g);
|
|
break;
|
|
} else {
|
|
(*g)->describe_slots();
|
|
|
|
update_game_in_lobby(*g, e.socket);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
players_.erase(pl_it);
|
|
if (lan_server_)
|
|
{
|
|
last_user_seen_time_ = time(0);
|
|
}
|
|
e.disconnect();
|
|
DBG_SERVER << "done closing socket...\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void server::process_data(const network::connection sock,
|
|
simple_wml::document& data) {
|
|
if (proxy::is_proxy(sock)) {
|
|
proxy::received_data(sock, data);
|
|
return;
|
|
}
|
|
|
|
// We know the client is alive for this interval
|
|
// Remove player from ghost_players map if selective_ping
|
|
// is enabled for the player.
|
|
const wesnothd::player_map::const_iterator pl = ghost_players_.find( sock ) ;
|
|
if( pl != ghost_players_.end() && pl->second.selective_ping() ) {
|
|
ghost_players_.erase( sock ) ;
|
|
}
|
|
|
|
// Process the message
|
|
simple_wml::node& root = data.root();
|
|
if(root.has_attr("ping")) {
|
|
// Ignore client side pings for now.
|
|
return;
|
|
} else if(not_logged_in_.is_observer(sock)) {
|
|
// Someone who is not yet logged in is sending login details.
|
|
process_login(sock, data);
|
|
} else if (simple_wml::node* query = root.child("query")) {
|
|
process_query(sock, *query);
|
|
} else if (simple_wml::node* nickserv = root.child("nickserv")) {
|
|
process_nickserv(sock, *nickserv);
|
|
} else if (simple_wml::node* whisper = root.child("whisper")) {
|
|
process_whisper(sock, *whisper);
|
|
} else if (lobby_.is_observer(sock)) {
|
|
process_data_lobby(sock, data);
|
|
} else {
|
|
process_data_game(sock, data);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void server::process_login(const network::connection sock,
|
|
simple_wml::document& data) {
|
|
|
|
// See if the client is sending their version number.
|
|
if (const simple_wml::node* const version = data.child("version")) {
|
|
const simple_wml::string_span& version_str_span = (*version)["version"];
|
|
const std::string version_str(version_str_span.begin(),
|
|
version_str_span.end());
|
|
std::set<std::string>::const_iterator accepted_it;
|
|
// Check if it is an accepted version.
|
|
for (accepted_it = accepted_versions_.begin();
|
|
accepted_it != accepted_versions_.end(); ++accepted_it) {
|
|
if (utils::wildcard_string_match(version_str,*accepted_it)) break;
|
|
}
|
|
if (accepted_it != accepted_versions_.end()) {
|
|
LOG_SERVER << network::ip_address(sock)
|
|
<< "\tplayer joined using accepted version " << version_str
|
|
<< ":\ttelling them to log in.\n";
|
|
send_doc(login_response_, sock);
|
|
return;
|
|
}
|
|
std::map<std::string,config>::const_iterator config_it;
|
|
// Check if it is a redirected version
|
|
for (config_it = redirected_versions_.begin();
|
|
config_it != redirected_versions_.end(); ++config_it)
|
|
{
|
|
if (utils::wildcard_string_match(version_str,config_it->first))
|
|
break;
|
|
}
|
|
if (config_it != redirected_versions_.end()) {
|
|
LOG_SERVER << network::ip_address(sock)
|
|
<< "\tplayer joined using version " << version_str
|
|
<< ":\tredirecting them to " << config_it->second["host"]
|
|
<< ":" << config_it->second["port"] << "\n";
|
|
config response;
|
|
response.add_child("redirect",config_it->second);
|
|
network::send_data(response, sock, true, "redirect");
|
|
return;
|
|
}
|
|
// Check if it's a version we should start a proxy for.
|
|
for (config_it = proxy_versions_.begin();
|
|
config_it != proxy_versions_.end(); ++config_it)
|
|
{
|
|
if (utils::wildcard_string_match(version_str,config_it->first))
|
|
break;
|
|
}
|
|
if (config_it != proxy_versions_.end()) {
|
|
LOG_SERVER << network::ip_address(sock)
|
|
<< "\tplayer joined using version " << version_str
|
|
<< ":\tconnecting them by proxy to " << config_it->second["host"]
|
|
<< ":" << config_it->second["port"] << "\n";
|
|
proxy::create_proxy(sock,config_it->second["host"],
|
|
lexical_cast_default<int>(config_it->second["port"],15000));
|
|
return;
|
|
}
|
|
// No match, send a response and reject them.
|
|
LOG_SERVER << network::ip_address(sock)
|
|
<< "\tplayer joined using unknown version " << version_str
|
|
<< ":\trejecting them\n";
|
|
config response;
|
|
if (!accepted_versions_.empty()) {
|
|
response["version"] = *accepted_versions_.begin();
|
|
} else if (redirected_versions_.empty() == false) {
|
|
response["version"] = redirected_versions_.begin()->first;
|
|
} else {
|
|
ERR_SERVER << "ERROR: This server doesn't accept any versions at all.\n";
|
|
response["version"] = "null";
|
|
}
|
|
network::send_data(response, sock, true, "error");
|
|
return;
|
|
}
|
|
|
|
const simple_wml::node* const login = data.child("login");
|
|
// Client must send a login first.
|
|
if (login == NULL) {
|
|
send_error(sock, "You must login first.");
|
|
return;
|
|
}
|
|
|
|
// Check if the username is valid (all alpha-numeric plus underscore and hyphen)
|
|
std::string username = (*login)["username"].to_string();
|
|
if (!utils::isvalid_username(username)) {
|
|
send_error(sock, "This username contains invalid "
|
|
"characters. Only alpha-numeric characters, underscores and hyphens"
|
|
"are allowed.");
|
|
return;
|
|
}
|
|
if (username.size() > 18) {
|
|
send_error(sock, "This username is too long. Usernames must be 18 characers or less.");
|
|
return;
|
|
}
|
|
// Check if the username is allowed.
|
|
for (std::vector<std::string>::const_iterator d_it = disallowed_names_.begin();
|
|
d_it != disallowed_names_.end(); ++d_it)
|
|
{
|
|
if (utils::wildcard_string_match(utils::lowercase(username),
|
|
utils::lowercase(*d_it)))
|
|
{
|
|
send_error(sock, "The nick you chose is reserved and cannot be used by players");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If this is a request for password reminder
|
|
if(user_handler_) {
|
|
std::string password_reminder = (*login)["password_reminder"].to_string();
|
|
if(password_reminder == "yes") {
|
|
try {
|
|
user_handler_->password_reminder(username);
|
|
send_error(sock, "Your password reminder email has been sent.");
|
|
} catch (user_handler::error e) {
|
|
send_error(sock, ("There was an error sending your password reminder email. The error message was: " +
|
|
e.message).c_str());
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check the username isn't already taken
|
|
wesnothd::player_map::const_iterator p;
|
|
for (p = players_.begin(); p != players_.end(); ++p) {
|
|
if (p->second.name() == username) {
|
|
send_error(sock, "The username you chose is already taken.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for password
|
|
|
|
// Current login procedure for registered nicks is:
|
|
// - Client asks to log in with a particular nick
|
|
// - Server sends client random salt plus some info
|
|
// generated from the original hash that is required to
|
|
// regenerate the hash
|
|
// - Client generates hash for the user provided password
|
|
// and mixes it with the received random salt
|
|
// - Server received salted hash, salts the valid hash with
|
|
// the same salt it sent to the client and compares the results
|
|
|
|
bool registered = false;
|
|
if(user_handler_) {
|
|
std::string password = (*login)["password"].to_string();
|
|
if(user_handler_->user_exists(username)) {
|
|
// This name is registered and no password provided
|
|
if(password.empty()) {
|
|
send_password_request(sock, ("The username '" + username + "' is registered on this server.").c_str(), username);
|
|
return;
|
|
}
|
|
// A password (or hashed password) was provided, however
|
|
// there is no seed
|
|
if(seeds_[sock].empty()) {
|
|
send_password_request(sock, "Please try again.", username);
|
|
}
|
|
// This name is registered and an incorrect password provided
|
|
else if(!(user_handler_->login(username, password, seeds_[sock]))) {
|
|
// Reset the random seed
|
|
seeds_.erase(sock);
|
|
send_password_request(sock, ("The password you provided for the username '" + username +
|
|
"' was incorrect.").c_str(), username);
|
|
|
|
LOG_SERVER << network::ip_address(sock) << "\t"
|
|
<< "Login attempt with incorrect password for username '" << username << "'.\n";
|
|
return;
|
|
}
|
|
// This name exists and the password was neither empty nor incorrect
|
|
registered = true;
|
|
// Reset the random seed
|
|
seeds_.erase(sock);
|
|
user_handler_->user_logged_in(username);
|
|
}
|
|
}
|
|
|
|
// Check if the version is now avaliable. If it is not, this player must
|
|
// always be pinged.
|
|
bool selective_ping = false ;
|
|
if( (*login)["selective_ping"].to_bool() ) {
|
|
selective_ping = true ;
|
|
DBG_SERVER << "selective ping is ENABLED for " << sock << "\n" ;
|
|
} else {
|
|
DBG_SERVER << "selective ping is DISABLED for " << sock << "\n" ;
|
|
}
|
|
|
|
send_doc(join_lobby_response_, sock);
|
|
|
|
simple_wml::node& player_cfg = games_and_users_list_.root().add_child("user");
|
|
const player new_player(username, player_cfg, registered, default_max_messages_,
|
|
default_time_period_, selective_ping );
|
|
|
|
// If the new player does not have selective ping enabled, immediately
|
|
// add the player to the ghost player's list. This ensures a client won't
|
|
// have to wait as long as x2 the current ping delay; which could cause
|
|
// a client-side disconnection.
|
|
players_.insert(std::pair<network::connection,player>(sock, new_player));
|
|
if( !selective_ping )
|
|
ghost_players_.insert( std::pair<network::connection,player>(sock, new_player) ) ;
|
|
|
|
not_logged_in_.remove_player(sock);
|
|
lobby_.add_player(sock, true);
|
|
|
|
// Send the new player the entire list of games and players
|
|
send_doc(games_and_users_list_, sock);
|
|
|
|
if (motd_ != "") {
|
|
lobby_.send_server_message(motd_.c_str(), sock);
|
|
}
|
|
|
|
// Send other players in the lobby the update that the player has joined
|
|
simple_wml::document diff;
|
|
make_add_diff(games_and_users_list_.root(), NULL, "user", diff);
|
|
lobby_.send_data(diff, sock);
|
|
|
|
LOG_SERVER << network::ip_address(sock) << "\t" << username
|
|
<< "\thas logged on. (socket: " << sock << ")\n";
|
|
|
|
for (std::vector<wesnothd::game*>::const_iterator g = games_.begin(); g != games_.end(); ++g) {
|
|
// Note: This string is parsed by the client to identify lobby join messages!
|
|
(*g)->send_server_message_to_all((username + " has logged into the lobby").c_str());
|
|
}
|
|
|
|
// Log the IP
|
|
ip_log_[username] = network::ip_address(sock);
|
|
// Remove the oldes entry in the size of the IP log exceeds the maximum size
|
|
if(ip_log_.size() > max_ip_log_size_) ip_log_.erase(ip_log_.begin());
|
|
}
|
|
|
|
void server::process_query(const network::connection sock,
|
|
simple_wml::node& query) {
|
|
const wesnothd::player_map::const_iterator pl = players_.find(sock);
|
|
if (pl == players_.end()) {
|
|
DBG_SERVER << "ERROR: process_query(): Could not find player with socket: " << sock << "\n";
|
|
return;
|
|
}
|
|
const simple_wml::string_span& command(query["type"]);
|
|
std::ostringstream response;
|
|
const std::string& help_msg = "Available commands are: help, games, metrics,"
|
|
" motd, netstats [all], stats, status, wml.";
|
|
if (admins_.count(sock) != 0) {
|
|
LOG_SERVER << "Admin Command:" << "\ttype: " << command
|
|
<< "\tIP: "<< network::ip_address(sock)
|
|
<< "\tnick: "<< pl->second.name() << std::endl;
|
|
response << process_command(command.to_string(), pl->second.name());
|
|
// Commands a player may issue.
|
|
} else if (command == "help") {
|
|
response << help_msg;
|
|
} else if (command == "status") {
|
|
response << process_command(command.to_string() + " " + pl->second.name(), pl->second.name());
|
|
} else if (command == "games"
|
|
|| command == "metrics"
|
|
|| command == "motd"
|
|
|| command == "netstats"
|
|
|| command == "netstats all"
|
|
|| command == "sample"
|
|
|| command == "stats"
|
|
|| command == "status " + pl->second.name()
|
|
|| command == "wml")
|
|
{
|
|
response << process_command(command.to_string(), pl->second.name());
|
|
} else if (command == admin_passwd_) {
|
|
LOG_SERVER << "New Admin recognized:" << "\tIP: "
|
|
<< network::ip_address(sock) << "\tnick: "
|
|
<< pl->second.name() << std::endl;
|
|
admins_.insert(sock);
|
|
response << "You are now recognized as an administrator.";
|
|
} else if (admin_passwd_.empty() == false) {
|
|
WRN_SERVER << "FAILED Admin attempt: '" << command << "'\tIP: "
|
|
<< network::ip_address(sock) << "\tnick: "
|
|
<< pl->second.name() << std::endl;
|
|
response << "Error: unrecognized query: '" << command << "'\n" << help_msg;
|
|
} else {
|
|
response << "Error: unrecognized query: '" << command << "'\n" << help_msg;
|
|
}
|
|
lobby_.send_server_message(response.str().c_str(), sock);
|
|
}
|
|
|
|
void server::start_new_server() {
|
|
if (restart_command.empty())
|
|
return;
|
|
|
|
// Example config line:
|
|
// restart_command="./wesnothd-debug -d -c ~/.wesnoth1.5/server.cfg"
|
|
// remember to make new one as a daemon or it will block old one
|
|
std::system(restart_command.c_str());
|
|
|
|
LOG_SERVER << "New server started with command: " << restart_command << "\n";
|
|
}
|
|
|
|
std::string server::process_command(const std::string& query, const std::string& issuer_name) {
|
|
std::ostringstream out;
|
|
const std::string::const_iterator i = std::find(query.begin(),query.end(),' ');
|
|
const std::string command(query.begin(),i);
|
|
std::string parameters = (i == query.end() ? "" : std::string(i+1,query.end()));
|
|
utils::strip(parameters);
|
|
const std::string& help_msg = "Available commands are: ban <mask> [<time>] <reason>,"
|
|
" bans [deleted], kick <mask>, k[ick]ban <mask> [<time>] <reason>,"
|
|
" help, games, metrics, netstats [all], [lobby]msg <message>, motd [<message>],"
|
|
" requestes, stats, status [<mask>], searchlog [<mask>], unban <ipmask>";
|
|
// Shutdown, restart and sample commands can only be issued via the socket.
|
|
if (command == "shut_down") {
|
|
if (issuer_name != "*socket*") return "";
|
|
if (parameters == "now") {
|
|
throw network::error("shut down");
|
|
} else {
|
|
// Graceful shut down.
|
|
server_.stop();
|
|
input_.reset();
|
|
graceful_restart = true;
|
|
process_command("msg The server is shutting down. You may finish your games but can't start new ones. Once all games have ended the server will exit.", issuer_name);
|
|
out << "Server is doing graceful shut down.";
|
|
}
|
|
|
|
#ifndef _WIN32 // Not sure if this works on windows
|
|
// TODO: check if this works in windows.
|
|
} else if (command == "restart") {
|
|
if (issuer_name != "*socket*") return "";
|
|
if (restart_command.empty()) {
|
|
out << "No restart_command configured! Not restarting.";
|
|
} else {
|
|
LOG_SERVER << "Graceful restart requested.";
|
|
graceful_restart = true;
|
|
// stop listening socket
|
|
server_.stop();
|
|
input_.reset();
|
|
// start new server
|
|
start_new_server();
|
|
process_command("msg The server has been restarted. You may finish current games but can't start new ones and new players can't join this (old) server instance. (So if a player of your game disconnects you have to save, reconnect and reload the game on the new server instance. It is actually recommended to do that right away.)", issuer_name);
|
|
out << "New server started.";
|
|
}
|
|
#endif
|
|
} else if (command == "sample") {
|
|
if (parameters.empty()) {
|
|
out << "Current sample frequency: " << request_sample_frequency;
|
|
return out.str();
|
|
} else if (issuer_name != "*socket*") {
|
|
return "";
|
|
}
|
|
request_sample_frequency = atoi(parameters.c_str());
|
|
if (request_sample_frequency <= 0) {
|
|
out << "Sampling turned off.";
|
|
} else {
|
|
out << "Sampling every " << request_sample_frequency << " requests.";
|
|
}
|
|
} else if (command == "help") {
|
|
return help_msg;
|
|
} else if (command == "stats") {
|
|
out << "Number of games = " << games_.size()
|
|
<< "\nTotal number of users = " << players_.size()
|
|
<< "\nNumber of ghost users = " << ghost_players_.size()
|
|
<< "\nNumber of users in the lobby = " << lobby_.nobservers();
|
|
return out.str();
|
|
} else if (command == "metrics") {
|
|
out << metrics_;
|
|
} else if (command == "games") {
|
|
metrics_.games(out);
|
|
} else if (command == "wml") {
|
|
out << simple_wml::document::stats();
|
|
} else if (command == "netstats") {
|
|
network::pending_statistics stats = network::get_pending_stats();
|
|
out << "Network stats:\nPending send buffers: "
|
|
<< stats.npending_sends << "\nBytes in buffers: "
|
|
<< stats.nbytes_pending_sends << "\n";
|
|
if (parameters == "all")
|
|
out << network::get_bandwidth_stats_all();
|
|
else
|
|
out << network::get_bandwidth_stats(); // stats from previuos hour
|
|
} else if (command == "msg" || command == "lobbymsg") {
|
|
if (parameters == "") {
|
|
return "You must type a message.";
|
|
}
|
|
lobby_.send_server_message_to_all(parameters.c_str());
|
|
if (command == "msg") {
|
|
for (std::vector<wesnothd::game*>::const_iterator g = games_.begin(); g != games_.end(); ++g) {
|
|
(*g)->send_server_message_to_all(parameters.c_str());
|
|
}
|
|
}
|
|
LOG_SERVER << "<server> " + parameters + "\n";
|
|
out << "message '" << parameters << "' relayed to players";
|
|
} else if (command == "status") {
|
|
out << "STATUS REPORT";
|
|
// If a simple username is given we'll check for its IP instead.
|
|
if (utils::isvalid_username(parameters)) {
|
|
bool found = false;
|
|
for (wesnothd::player_map::const_iterator pl = players_.begin(); pl != players_.end(); ++pl) {
|
|
if (parameters == pl->second.name().c_str()) {
|
|
parameters = network::ip_address(pl->first);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) return out.str();
|
|
}
|
|
for (wesnothd::player_map::const_iterator pl = players_.begin(); pl != players_.end(); ++pl) {
|
|
if (parameters == ""
|
|
|| utils::wildcard_string_match(network::ip_address(pl->first), parameters)
|
|
|| utils::wildcard_string_match(pl->second.name(), parameters)) {
|
|
const network::connection_stats& stats = network::get_connection_stats(pl->first);
|
|
const int time_connected = stats.time_connected/1000;
|
|
const int seconds = time_connected%60;
|
|
const int minutes = (time_connected/60)%60;
|
|
const int hours = time_connected/(60*60);
|
|
out << "\n'" << pl->second.name() << "' @ " << network::ip_address(pl->first)
|
|
<< " connected for " << hours << ":" << minutes << ":" << seconds
|
|
<< " sent " << stats.bytes_sent << " bytes, received "
|
|
<< stats.bytes_received << " bytes";
|
|
}
|
|
}
|
|
} else if (command == "bans") {
|
|
if (parameters == "deleted") {
|
|
ban_manager_.list_deleted_bans(out);
|
|
} else {
|
|
ban_manager_.list_bans(out);
|
|
}
|
|
} else if (command == "ban" || command == "kban" || command == "kickban" || command == "gban") {
|
|
bool banned = false;
|
|
const bool kick = (command == "kban" || command == "kickban");
|
|
const bool group_ban = command == "gban";
|
|
std::string::iterator first_space = std::find(parameters.begin(), parameters.end(), ' ');
|
|
if (first_space == parameters.end()) {
|
|
return ban_manager_.get_ban_help();
|
|
}
|
|
std::string::iterator second_space = std::find(first_space + 1, parameters.end(), ' ');
|
|
const std::string target(parameters.begin(), first_space);
|
|
std::string group;
|
|
if (group_ban) {
|
|
group = std::string(first_space + 1, second_space);
|
|
first_space = second_space;
|
|
second_space = std::find(first_space + 1, parameters.end(), ' ');
|
|
}
|
|
const std::string time(first_space + 1, second_space);
|
|
time_t parsed_time = ban_manager_.parse_time(time);
|
|
if (parsed_time == 0) {
|
|
second_space = first_space;
|
|
}
|
|
|
|
if (second_space == parameters.end()) {
|
|
--second_space;
|
|
}
|
|
std::string reason(second_space + 1, parameters.end());
|
|
utils::strip(reason);
|
|
if (reason.empty()) return ban_manager_.get_ban_help();
|
|
|
|
// if we find a '.' consider it an ip mask
|
|
/** @todo FIXME: make a proper check for valid IPs. */
|
|
if (std::count(target.begin(), target.end(), '.') >= 1) {
|
|
banned = true;
|
|
|
|
std::string err = ban_manager_.ban(target, parsed_time, reason, issuer_name, group);
|
|
out << err;
|
|
|
|
if (kick) {
|
|
for (wesnothd::player_map::const_iterator pl = players_.begin();
|
|
pl != players_.end(); ++pl)
|
|
{
|
|
if (utils::wildcard_string_match(network::ip_address(pl->first), target)) {
|
|
out << "\nKicked " << pl->second.name() << ".";
|
|
send_error(pl->first, ("You have been banned. Reason: " + reason).c_str());
|
|
network::queue_disconnect(pl->first);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
bool kicked = false;
|
|
for (wesnothd::player_map::const_iterator pl = players_.begin();
|
|
pl != players_.end(); ++pl)
|
|
{
|
|
if (utils::wildcard_string_match(pl->second.name(), target)) {
|
|
banned = true;
|
|
const std::string& ip = network::ip_address(pl->first);
|
|
if (!is_ip_banned(ip)) {
|
|
std::string err = ban_manager_.ban(ip, parsed_time, reason, issuer_name, group, target);
|
|
out << err;
|
|
}
|
|
if (kick) {
|
|
if (kicked) out << "\n";
|
|
else kicked = true;
|
|
out << "\nKicked " << pl->second.name() << ".";
|
|
send_error(pl->first, ("You have been banned. Reason: " + reason).c_str());
|
|
network::queue_disconnect(pl->first);
|
|
}
|
|
}
|
|
}
|
|
if (!banned) {
|
|
// If nobody was banned yet look in the logs
|
|
for (std::map<std::string, std::string>::const_iterator i = ip_log_.begin();
|
|
i != ip_log_.end(); i++) {
|
|
if (utils::wildcard_string_match(i->first, target)) {
|
|
banned = true;
|
|
if (!is_ip_banned(i->second)) {
|
|
std::string err = ban_manager_.ban(i->second,parsed_time, reason, issuer_name, group);
|
|
out << err;
|
|
}
|
|
}
|
|
}
|
|
if(!banned) {
|
|
out << "Nickmask '" << target << "' did not match, no bans set.";
|
|
}
|
|
}
|
|
}
|
|
} else if (command == "unban") {
|
|
if (parameters == "") {
|
|
return "You must enter an ipmask to unban.";
|
|
}
|
|
ban_manager_.unban(out, parameters);
|
|
} else if (command == "ungban") {
|
|
if (parameters == "") {
|
|
return "You must enter an ipmask to ungban.";
|
|
}
|
|
ban_manager_.unban_group(out, parameters);
|
|
} else if (command == "kick") {
|
|
if (parameters == "") {
|
|
return "You must enter a mask to kick.";
|
|
}
|
|
bool kicked = false;
|
|
// if we find a '.' consider it an ip mask
|
|
if (std::count(parameters.begin(), parameters.end(), '.') >= 1) {
|
|
for (wesnothd::player_map::const_iterator pl = players_.begin();
|
|
pl != players_.end(); ++pl)
|
|
{
|
|
if (utils::wildcard_string_match(network::ip_address(pl->first), parameters)) {
|
|
if (kicked) out << "\n";
|
|
else kicked = true;
|
|
out << "Kicked " << pl->second.name() << " ("
|
|
<< network::ip_address(pl->first) << ").";
|
|
send_error(pl->first, "You have been kicked.");
|
|
network::queue_disconnect(pl->first);
|
|
}
|
|
}
|
|
} else {
|
|
for (wesnothd::player_map::const_iterator pl = players_.begin();
|
|
pl != players_.end(); ++pl)
|
|
{
|
|
if (utils::wildcard_string_match(pl->second.name(), parameters)) {
|
|
if (kicked) out << "\n";
|
|
else kicked = true;
|
|
out << "Kicked " << pl->second.name() << " ("
|
|
<< network::ip_address(pl->first) << ").";
|
|
send_error(pl->first, "You have been kicked.");
|
|
network::queue_disconnect(pl->first);
|
|
}
|
|
}
|
|
}
|
|
if (!kicked) out << "No user matched '" << parameters << "'.";
|
|
} else if (command == "motd") {
|
|
if (parameters == "") {
|
|
if (motd_ != "") {
|
|
out << "Message of the day: " << motd_;
|
|
return out.str();
|
|
} else {
|
|
return "No message of the day set.";
|
|
}
|
|
}
|
|
motd_ = parameters;
|
|
out << "Message of the day set to: " << motd_;
|
|
} else if (command == "searchlog") {
|
|
if (parameters.empty()) {
|
|
return "You must enter a mask to search for.";
|
|
}
|
|
out << "IP/NICK LOG";
|
|
|
|
bool found_something = false;
|
|
|
|
// If this looks like an IP look up which nicks have been connected from it
|
|
// Otherwise look for the last IP the nick used to connect
|
|
if (std::count(parameters.begin(), parameters.end(), '.') >= 1) {
|
|
for (std::map<std::string, std::string>::const_iterator i = ip_log_.begin();
|
|
i != ip_log_.end(); i++) {
|
|
if (utils::wildcard_string_match(i->second, parameters)) {
|
|
found_something = true;
|
|
out << "\n'" << i->first << "' @ " << i->second;
|
|
}
|
|
}
|
|
} else {
|
|
for (std::map<std::string, std::string>::const_iterator i = ip_log_.begin();
|
|
i != ip_log_.end(); i++) {
|
|
if (utils::wildcard_string_match(i->first, parameters)) {
|
|
found_something = true;
|
|
out << "\n'" << i->first << "' @ " << i->second;
|
|
}
|
|
}
|
|
}
|
|
if (!found_something) out << "\nNo results found for '" << parameters << "'.";
|
|
} else {
|
|
out << "Command '" << command << "' is not recognized.\n" << help_msg;
|
|
}
|
|
|
|
LOG_SERVER << out.str() << "\n";
|
|
return out.str();
|
|
}
|
|
|
|
void server::process_nickserv(const network::connection sock, simple_wml::node& data) {
|
|
const wesnothd::player_map::iterator pl = players_.find(sock);
|
|
if (pl == players_.end()) {
|
|
DBG_SERVER << "ERROR: Could not find player with socket: " << sock << "\n";
|
|
return;
|
|
}
|
|
|
|
// Check if this server allows nick registration at all
|
|
if(!user_handler_) {
|
|
lobby_.send_server_message("This server does not allow username registration.", sock);
|
|
return;
|
|
}
|
|
|
|
if(data.child("register")) {
|
|
try {
|
|
(user_handler_->add_user(pl->second.name(), (*data.child("register"))["mail"].to_string(),
|
|
(*data.child("register"))["password"].to_string()));
|
|
|
|
std::stringstream msg;
|
|
msg << "Your username has been registered." <<
|
|
// Warn that providing an email address might be a good idea
|
|
((*data.child("register"))["mail"].empty() ?
|
|
" It is recommended that you provide an email address for password recovery." : "");
|
|
lobby_.send_server_message(msg.str().c_str(), sock);
|
|
|
|
// Mark the player as registered and send the other clients
|
|
// an update to dislpay this change
|
|
pl->second.mark_registered();
|
|
|
|
simple_wml::document diff;
|
|
make_change_diff(games_and_users_list_.root(), NULL,
|
|
"user", pl->second.config_address(), diff);
|
|
lobby_.send_data(diff);
|
|
|
|
} catch (user_handler::error e) {
|
|
lobby_.send_server_message(("There was an error registering your username. The error message was: "
|
|
+ e.message).c_str(), sock);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// A user requested to update his password or mail
|
|
if(data.child("set")) {
|
|
if(!(user_handler_->user_exists(pl->second.name()))) {
|
|
lobby_.send_server_message("You are not registered. Please register first.",
|
|
sock);
|
|
return;
|
|
}
|
|
|
|
const simple_wml::node& set = *(data.child("set"));
|
|
|
|
try {
|
|
user_handler_->set_user_detail(pl->second.name(), set["detail"].to_string(), set["value"].to_string());
|
|
|
|
lobby_.send_server_message("Your details have been updated.", sock);
|
|
|
|
} catch (user_handler::error e) {
|
|
lobby_.send_server_message(("There was an error updating your details. The error message was: "
|
|
+ e.message).c_str(), sock);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// A user requested information about another user
|
|
if(data.child("details")) {
|
|
lobby_.send_server_message(("Valid details for this server are: " +
|
|
user_handler_->get_valid_details()).c_str(), sock);
|
|
return;
|
|
}
|
|
|
|
// A user requested a list of which details can be set
|
|
if(data.child("info")) {
|
|
try {
|
|
std::string res = user_handler_->user_info((*data.child("info"))["name"].to_string());
|
|
lobby_.send_server_message(res.c_str(), sock);
|
|
} catch (user_handler::error e) {
|
|
lobby_.send_server_message(("There was an error looking up the details of the user '" +
|
|
(*data.child("info"))["name"].to_string() + "'. " +" The error message was: "
|
|
+ e.message).c_str(), sock);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// A user requested to delete his nick
|
|
if(data.child("drop")) {
|
|
if(!(user_handler_->user_exists(pl->second.name()))) {
|
|
lobby_.send_server_message("You are not registered.",
|
|
sock);
|
|
return;
|
|
}
|
|
|
|
// With the current policy of dissallowing to log in with a
|
|
// registerd username without the password we should never get
|
|
// to call this
|
|
if(!(pl->second.registered())) {
|
|
lobby_.send_server_message("You are not logged in.",
|
|
sock);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
user_handler_->remove_user(pl->second.name());
|
|
lobby_.send_server_message("Your username has been dropped.", sock);
|
|
|
|
// Mark the player as not registered and send the other clients
|
|
// an update to dislpay this change
|
|
pl->second.mark_registered(false);
|
|
|
|
simple_wml::document diff;
|
|
make_change_diff(games_and_users_list_.root(), NULL,
|
|
"user", pl->second.config_address(), diff);
|
|
lobby_.send_data(diff);
|
|
} catch (user_handler::error e) {
|
|
lobby_.send_server_message(("There was an error dropping your username. The error message was: "
|
|
+ e.message).c_str(), sock);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void server::process_whisper(const network::connection sock,
|
|
simple_wml::node& whisper) const {
|
|
if ((whisper["receiver"] == "") || (whisper["message"] == "")) {
|
|
static simple_wml::document data(
|
|
"[message]\n"
|
|
"message=\"Invalid number of arguments\"\n"
|
|
"sender=\"server\"\n"
|
|
"[/message]\n", simple_wml::INIT_COMPRESSED);
|
|
send_doc(data, sock);
|
|
return;
|
|
}
|
|
const wesnothd::player_map::const_iterator pl = players_.find(sock);
|
|
if (pl == players_.end()) {
|
|
ERR_SERVER << "ERROR: Could not find whispering player. (socket: "
|
|
<< sock << ")\n";
|
|
return;
|
|
}
|
|
whisper.set_attr_dup("sender", pl->second.name().c_str());
|
|
bool dont_send = false;
|
|
const simple_wml::string_span& whisper_receiver = whisper["receiver"];
|
|
for (wesnothd::player_map::const_iterator i = players_.begin(); i != players_.end(); ++i) {
|
|
if (whisper_receiver != i->second.name().c_str()) {
|
|
continue;
|
|
}
|
|
|
|
std::vector<wesnothd::game*>::const_iterator g;
|
|
for (g = games_.begin(); g != games_.end(); ++g) {
|
|
if (!(*g)->is_member(i->first)) continue;
|
|
// Don't send to players in a running game the sender is part of.
|
|
dont_send = ((*g)->started() && (*g)->is_player(i->first) && (*g)->is_member(sock));
|
|
break;
|
|
}
|
|
if (dont_send) {
|
|
break;
|
|
}
|
|
|
|
simple_wml::document cwhisper;
|
|
whisper.copy_into(cwhisper.root().add_child("whisper"));
|
|
send_doc(cwhisper, i->first);
|
|
return;
|
|
}
|
|
|
|
simple_wml::document data;
|
|
simple_wml::node& msg = data.root().add_child("message");
|
|
|
|
if (dont_send) {
|
|
msg.set_attr("message", "You cannot send private messages to players in a running game you observe.");
|
|
} else {
|
|
msg.set_attr_dup("message", ("Can't find '" + whisper["receiver"].to_string() + "'.").c_str());
|
|
}
|
|
|
|
msg.set_attr("sender", "server");
|
|
send_doc(data, sock);
|
|
}
|
|
|
|
void server::process_data_lobby(const network::connection sock,
|
|
simple_wml::document& data) {
|
|
DBG_SERVER << "in process_data_lobby...\n";
|
|
|
|
const wesnothd::player_map::iterator pl = players_.find(sock);
|
|
if (pl == players_.end()) {
|
|
ERR_SERVER << "ERROR: Could not find player in players_. (socket: "
|
|
<< sock << ")\n";
|
|
return;
|
|
}
|
|
|
|
if (data.root().child("create_game")) {
|
|
if (graceful_restart) {
|
|
static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
|
|
send_doc(leave_game_doc, sock);
|
|
lobby_.send_server_message("This server is shutting down. You aren't allowed to make new games. Please reconnect to the new server.", sock);
|
|
send_doc(games_and_users_list_, sock);
|
|
return;
|
|
}
|
|
const std::string game_name = (*data.root().child("create_game"))["name"].to_string();
|
|
const std::string game_password = (*data.root().child("create_game"))["password"].to_string();
|
|
DBG_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tcreates a new game: \"" << game_name << "\".\n";
|
|
// Create the new game, remove the player from the lobby
|
|
// and set the player as the host/owner.
|
|
games_.push_back(new wesnothd::game(players_, sock, game_name));
|
|
wesnothd::game& g = *games_.back();
|
|
if(game_password.empty() == false) {
|
|
g.set_password(game_password);
|
|
}
|
|
|
|
data.root().child("create_game")->copy_into(g.level().root());
|
|
lobby_.remove_player(sock);
|
|
simple_wml::document diff;
|
|
if(make_change_diff(games_and_users_list_.root(), NULL,
|
|
"user", pl->second.config_address(), diff)) {
|
|
lobby_.send_data(diff);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// See if the player is joining a game
|
|
if (data.root().child("join")) {
|
|
const bool observer = data.root().child("join")->attr("observe").to_bool();
|
|
const std::string& password = (*data.root().child("join"))["password"].to_string();
|
|
int game_id = (*data.root().child("join"))["id"].to_int();
|
|
|
|
const std::vector<wesnothd::game*>::iterator g =
|
|
std::find_if(games_.begin(),games_.end(), wesnothd::game_id_matches(game_id));
|
|
|
|
static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
|
|
if (g == games_.end()) {
|
|
WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tattempted to join unknown game:\t" << game_id << ".\n";
|
|
send_doc(leave_game_doc, sock);
|
|
lobby_.send_server_message("Attempt to join unknown game.", sock);
|
|
send_doc(games_and_users_list_, sock);
|
|
return;
|
|
} else if ((*g)->player_is_banned(sock)) {
|
|
DBG_SERVER << network::ip_address(sock) << "\tReject banned player: "
|
|
<< pl->second.name() << "\tfrom game:\t\"" << (*g)->name()
|
|
<< "\" (" << game_id << ").\n";
|
|
send_doc(leave_game_doc, sock);
|
|
lobby_.send_server_message("You are banned from this game.", sock);
|
|
send_doc(games_and_users_list_, sock);
|
|
return;
|
|
} else if(!observer && !(*g)->password_matches(password)) {
|
|
WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tattempted to join game:\t\"" << (*g)->name() << "\" ("
|
|
<< game_id << ") with bad password\n";
|
|
send_doc(leave_game_doc, sock);
|
|
lobby_.send_server_message("Incorrect password.", sock);
|
|
send_doc(games_and_users_list_, sock);
|
|
return;
|
|
} else if (observer && !(*g)->allow_observers()) {
|
|
WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tattempted to observe game:\t\"" << (*g)->name() << "\" ("
|
|
<< game_id << ") which doesn't allow observers.\n";
|
|
send_doc(leave_game_doc, sock);
|
|
lobby_.send_server_message("Attempt to observe a game that doesn't allow observers.", sock);
|
|
send_doc(games_and_users_list_, sock);
|
|
return;
|
|
} else if (!(*g)->level_init()) {
|
|
WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tattempted to join uninitialized game:\t\"" << (*g)->name()
|
|
<< "\" (" << game_id << ").\n";
|
|
send_doc(leave_game_doc, sock);
|
|
lobby_.send_server_message("Attempt to observe a game that doesn't allow observers.", sock);
|
|
send_doc(games_and_users_list_, sock);
|
|
return;
|
|
}
|
|
LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tjoined game:\t\"" << (*g)->name()
|
|
<< "\" (" << game_id << (observer ? ") as an observer.\n" : ").\n");
|
|
lobby_.remove_player(sock);
|
|
(*g)->add_player(sock, observer);
|
|
(*g)->describe_slots();
|
|
|
|
//send notification of changes to the game and user
|
|
simple_wml::document diff;
|
|
bool diff1 = make_change_diff(*games_and_users_list_.root().child("gamelist"),
|
|
"gamelist", "game", (*g)->description(), diff);
|
|
bool diff2 = make_change_diff(games_and_users_list_.root(), NULL,
|
|
"user", pl->second.config_address(), diff);
|
|
if (diff1 || diff2) {
|
|
lobby_.send_data(diff);
|
|
}
|
|
}
|
|
|
|
// See if it's a message, in which case we add the name of the sender,
|
|
// and forward it to all players in the lobby.
|
|
if (data.child("message")) {
|
|
lobby_.process_message(data, pl);
|
|
}
|
|
|
|
// Player requests update of lobby content,
|
|
// for example when cancelling the create game dialog
|
|
if (data.child("refresh_lobby")) {
|
|
send_doc(games_and_users_list_, sock);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process data sent by a player in a game. Note that 'data' by default gets
|
|
* broadcasted and saved in the replay.
|
|
*/
|
|
void server::process_data_game(const network::connection sock,
|
|
simple_wml::document& data) {
|
|
DBG_SERVER << "in process_data_game...\n";
|
|
|
|
const wesnothd::player_map::iterator pl = players_.find(sock);
|
|
if (pl == players_.end()) {
|
|
ERR_SERVER << "ERROR: Could not find player in players_. (socket: "
|
|
<< sock << ")\n";
|
|
return;
|
|
}
|
|
|
|
std::vector<wesnothd::game*>::iterator itor;
|
|
for (itor = games_.begin(); itor != games_.end(); ++itor) {
|
|
if ((*itor)->is_owner(sock) || (*itor)->is_member(sock))
|
|
break;
|
|
}
|
|
if (itor == games_.end()) {
|
|
ERR_SERVER << "ERROR: Could not find game for player: "
|
|
<< pl->second.name() << ". (socket: " << sock << ")\n";
|
|
return;
|
|
}
|
|
|
|
wesnothd::game* g = *itor;
|
|
|
|
// If this is data describing the level for a game.
|
|
if (data.root().child("side")) {
|
|
if (!g->is_owner(sock)) {
|
|
return;
|
|
}
|
|
size_t nsides = 0;
|
|
const simple_wml::node::child_list& sides = data.root().children("side");
|
|
for (simple_wml::node::child_list::const_iterator s = sides.begin(); s != sides.end(); ++s) {
|
|
++nsides;
|
|
}
|
|
if (nsides > gamemap::MAX_PLAYERS) {
|
|
delete_game(itor);
|
|
std::stringstream msg;
|
|
msg << "This server does not support games with more than "
|
|
<< gamemap::MAX_PLAYERS << " sides.";
|
|
lobby_.send_server_message(msg.str().c_str(), sock);
|
|
return;
|
|
}
|
|
|
|
// If this game is having its level data initialized
|
|
// for the first time, and is ready for players to join.
|
|
// We should currently have a summary of the game in g->level().
|
|
// We want to move this summary to the games_and_users_list_, and
|
|
// place a pointer to that summary in the game's description.
|
|
// g->level() should then receive the full data for the game.
|
|
if (!g->level_init()) {
|
|
LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tcreated game:\t\"" << g->name() << "\" ("
|
|
<< g->id() << ").\n";
|
|
// Update our config object which describes the open games,
|
|
// and save a pointer to the description in the new game.
|
|
simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
|
|
assert(gamelist != NULL);
|
|
simple_wml::node& desc = gamelist->add_child("game");
|
|
g->level().root().copy_into(desc);
|
|
g->set_description(&desc);
|
|
desc.set_attr_dup("id", lexical_cast<std::string>(g->id()).c_str());
|
|
} else {
|
|
WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< "\tsent scenario data in game:\t\"" << g->name() << "\" ("
|
|
<< g->id() << ") although it's already initialized.\n";
|
|
return;
|
|
}
|
|
|
|
assert(games_and_users_list_.child("gamelist")->children("game").empty() == false);
|
|
|
|
simple_wml::node& desc = *g->description();
|
|
// Update the game's description.
|
|
// If there is no shroud, then tell players in the lobby
|
|
// what the map looks like
|
|
if (!data["mp_shroud"].to_bool()) {
|
|
desc.set_attr_dup("map_data", data["map_data"]);
|
|
}
|
|
if(data.child("era")) {
|
|
desc.set_attr_dup("mp_era", data.child("era")->attr("id"));
|
|
if(!(utils::string_bool(data.child("era")->attr("require_era").to_string(),true))) {
|
|
desc.set_attr("require_era", "no");
|
|
}
|
|
} else {
|
|
desc.set_attr("mp_era", "");
|
|
}
|
|
|
|
// map id
|
|
desc.set_attr_dup("mp_scenario", data["id"]);
|
|
desc.set_attr_dup("observer", data["observer"]);
|
|
desc.set_attr_dup("mp_village_gold", data["mp_village_gold"]);
|
|
desc.set_attr_dup("experience_modifier", data["experience_modifier"]);
|
|
desc.set_attr_dup("mp_fog", data["mp_fog"]);
|
|
desc.set_attr_dup("mp_shroud", data["mp_shroud"]);
|
|
desc.set_attr_dup("mp_use_map_settings", data["mp_use_map_settings"]);
|
|
desc.set_attr_dup("mp_countdown", data["mp_countdown"]);
|
|
desc.set_attr_dup("mp_countdown_init_time", data["mp_countdown_init_time"]);
|
|
desc.set_attr_dup("mp_countdown_turn_bonus", data["mp_countdown_turn_bonus"]);
|
|
desc.set_attr_dup("mp_countdown_reservoir_time", data["mp_countdown_reservoir_time"]);
|
|
desc.set_attr_dup("mp_countdown_action_bonus", data["mp_countdown_action_bonus"]);
|
|
desc.set_attr_dup("savegame", data["savegame"]);
|
|
desc.set_attr_dup("hash", data["hash"]);
|
|
//desc["map_name"] = data["name"];
|
|
//desc["map_description"] = data["description"];
|
|
//desc[""] = data["objectives"];
|
|
//desc[""] = data["random_start_time"];
|
|
//desc[""] = data["turns"];
|
|
//desc["client_version"] = data["version"];
|
|
|
|
// Record the full scenario in g->level()
|
|
|
|
g->level().swap(data);
|
|
// The host already put himself in the scenario so we just need
|
|
// to update_side_data().
|
|
//g->take_side(sock);
|
|
g->update_side_data();
|
|
g->describe_slots();
|
|
|
|
assert(games_and_users_list_.child("gamelist")->children("game").empty() == false);
|
|
|
|
// Send the update of the game description to the lobby.
|
|
simple_wml::document diff;
|
|
make_add_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", diff);
|
|
lobby_.send_data(diff);
|
|
|
|
/** @todo FIXME: Why not save the level data in the history_? */
|
|
return;
|
|
// Everything below should only be processed if the game is already intialized.
|
|
} else if (!g->level_init()) {
|
|
WRN_SERVER << "Received unknown data from: " << pl->second.name()
|
|
<< " (socket:" << sock
|
|
<< ") while the scenario wasn't yet initialized.\n" << data.output();
|
|
return;
|
|
// If the host is sending the next scenario data.
|
|
} else if (data.child("store_next_scenario")) {
|
|
if (!g->is_owner(sock)) return;
|
|
if (!g->level_init()) {
|
|
WRN_SERVER << network::ip_address(sock) << "\tWarning: "
|
|
<< pl->second.name() << "\tsent [store_next_scenario] in game:\t\""
|
|
<< g->name() << "\" (" << g->id()
|
|
<< ") while the scenario is not yet initialized.";
|
|
return;
|
|
}
|
|
size_t nsides = 0;
|
|
const simple_wml::node::child_list& sides = data.root().children("side");
|
|
for (simple_wml::node::child_list::const_iterator s = sides.begin(); s != sides.end(); ++s) {
|
|
++nsides;
|
|
}
|
|
if (nsides > gamemap::MAX_PLAYERS) {
|
|
delete_game(itor);
|
|
std::stringstream msg;
|
|
msg << "This server does not support games with more than "
|
|
<< gamemap::MAX_PLAYERS << " sides.";
|
|
lobby_.send_server_message(msg.str().c_str(), sock);
|
|
return;
|
|
}
|
|
const simple_wml::node& s = *data.child("store_next_scenario");
|
|
// Record the full scenario in g->level()
|
|
g->level().clear();
|
|
s.copy_into(g->level().root());
|
|
g->start_game(pl);
|
|
if (g->description() == NULL) {
|
|
ERR_SERVER << network::ip_address(sock) << "\tERROR: \""
|
|
<< g->name() << "\" (" << g->id()
|
|
<< ") is initialized but has no description_.\n";
|
|
return;
|
|
}
|
|
simple_wml::node& desc = *g->description();
|
|
// Update the game's description.
|
|
// If there is no shroud, then tell players in the lobby
|
|
// what the map looks like.
|
|
if (s["mp_shroud"].to_bool()) {
|
|
desc.set_attr_dup("map_data", s["map_data"]);
|
|
} else {
|
|
desc.set_attr("map_data", "");
|
|
}
|
|
|
|
if(s.child("era")) {
|
|
desc.set_attr_dup("mp_era", s.child("era")->attr("id"));
|
|
} else {
|
|
desc.set_attr("mp_era", "");
|
|
}
|
|
|
|
// map id
|
|
desc.set_attr_dup("mp_scenario", s["id"]);
|
|
desc.set_attr_dup("observer", s["observer"]);
|
|
desc.set_attr_dup("mp_village_gold", s["mp_village_gold"]);
|
|
desc.set_attr_dup("experience_modifier", s["experience_modifier"]);
|
|
desc.set_attr_dup("mp_fog", s["mp_fog"]);
|
|
desc.set_attr_dup("mp_shroud", s["mp_shroud"]);
|
|
desc.set_attr_dup("mp_use_map_settings", s["mp_use_map_settings"]);
|
|
desc.set_attr_dup("mp_countdown", s["mp_countdown"]);
|
|
desc.set_attr_dup("mp_countdown_init_time", s["mp_countdown_init_time"]);
|
|
desc.set_attr_dup("mp_countdown_turn_bonus", s["mp_countdown_turn_bonus"]);
|
|
desc.set_attr_dup("mp_countdown_reservoir_time", s["mp_countdown_reservoir_time"]);
|
|
desc.set_attr_dup("mp_countdown_action_bonus", s["mp_countdown_action_bonus"]);
|
|
desc.set_attr_dup("hash", s["hash"]);
|
|
//desc["map_name"] = s["name"];
|
|
//desc["map_description"] = s["description"];
|
|
//desc[""] = s["objectives"];
|
|
//desc[""] = s["random_start_time"];
|
|
//desc[""] = s["turns"];
|
|
//desc["client_version"] = s["version"];
|
|
// Send the update of the game description to the lobby.
|
|
update_game_in_lobby(g);
|
|
return;
|
|
// If a player advances to the next scenario of a mp campaign. (deprecated)
|
|
} else if(data.child("notify_next_scenario")) {
|
|
//g->send_data(g->construct_server_message(pl->second.name()
|
|
// + " advanced to the next scenario."), sock);
|
|
return;
|
|
// A mp client sends a request for the next scenario of a mp campaign.
|
|
} else if (data.child("load_next_scenario")) {
|
|
g->load_next_scenario(pl);
|
|
return;
|
|
} else if (data.child("start_game")) {
|
|
if (!g->is_owner(sock)) return;
|
|
// Send notification of the game starting immediately.
|
|
// g->start_game() will send data that assumes
|
|
// the [start_game] message has been sent
|
|
g->send_data(data, sock);
|
|
g->start_game(pl);
|
|
|
|
//update the game having changed in the lobby
|
|
update_game_in_lobby(g);
|
|
return;
|
|
} else if (data.child("leave_game")) {
|
|
if ((g->is_player(sock) && g->nplayers() == 1)
|
|
|| (g->is_owner(sock) && !g->started())) {
|
|
LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name()
|
|
<< (g->started() ? "\tended game:\t\"" : "\taborted game:\t\"")
|
|
<< g->name() << "\" (" << g->id() << ")"
|
|
<< (g->started() ? " at turn: "
|
|
+ lexical_cast_default<std::string,size_t>(g->current_turn())
|
|
+ " with reason: '" + g->termination_reason() + "'" : "")
|
|
<< ".\n";
|
|
g->send_server_message_to_all((pl->second.name() + " ended the game.").c_str(), pl->first);
|
|
// Remove the player in delete_game() with all other remaining
|
|
// ones so he gets the updated gamelist.
|
|
delete_game(itor);
|
|
} else {
|
|
g->remove_player(sock);
|
|
lobby_.add_player(sock, true);
|
|
g->describe_slots();
|
|
|
|
// Send all other players in the lobby the update to the gamelist.
|
|
simple_wml::document diff;
|
|
bool diff1 = make_change_diff(*games_and_users_list_.root().child("gamelist"),
|
|
"gamelist", "game", g->description(), diff);
|
|
bool diff2 = make_change_diff(games_and_users_list_.root(), NULL,
|
|
"user", pl->second.config_address(), diff);
|
|
if (diff1 || diff2) {
|
|
lobby_.send_data(diff, sock);
|
|
}
|
|
|
|
// Send the player who has quit the gamelist.
|
|
send_doc(games_and_users_list_, sock);
|
|
}
|
|
return;
|
|
// If this is data describing side changes by the host.
|
|
} else if (data.child("scenario_diff")) {
|
|
if (!g->is_owner(sock)) return;
|
|
g->level().root().apply_diff(*data.child("scenario_diff"));
|
|
const simple_wml::node* cfg_change = data.child("scenario_diff")->child("change_child");
|
|
if ((cfg_change != NULL) && (cfg_change->child("side") != NULL)) {
|
|
g->update_side_data();
|
|
}
|
|
if (g->describe_slots()) {
|
|
update_game_in_lobby(g);
|
|
}
|
|
g->send_data(data, sock);
|
|
return;
|
|
// If a player changes his faction.
|
|
} else if (data.child("change_faction")) {
|
|
g->send_data(data, sock);
|
|
return;
|
|
// If the owner of a side is changing the controller.
|
|
} else if (data.child("change_controller")) {
|
|
const simple_wml::node& change = *data.child("change_controller");
|
|
g->transfer_side_control(sock, change);
|
|
if (g->describe_slots()) {
|
|
update_game_in_lobby(g);
|
|
}
|
|
// FIXME: Why not save it in the history_? (if successful)
|
|
return;
|
|
// If all observers should be muted. (toggles)
|
|
} else if (data.child("muteall")) {
|
|
if (!g->is_owner(sock)) {
|
|
g->send_server_message("You cannot mute: not the game host.", sock);
|
|
return;
|
|
}
|
|
g->mute_all_observers();
|
|
return;
|
|
// If an observer should be muted.
|
|
} else if (data.child("mute")) {
|
|
g->mute_observer(*data.child("mute"), pl);
|
|
return;
|
|
// The owner is kicking/banning someone from the game.
|
|
} else if (data.child("kick") || data.child("ban")) {
|
|
bool ban = (data.child("ban") != NULL);
|
|
const network::connection user =
|
|
(ban ? g->ban_user(*data.child("ban"), pl)
|
|
: g->kick_member(*data.child("kick"), pl));
|
|
if (user) {
|
|
lobby_.add_player(user, true);
|
|
if (g->describe_slots()) {
|
|
update_game_in_lobby(g, user);
|
|
}
|
|
// Send the removed user the lobby game list.
|
|
send_doc(games_and_users_list_, user);
|
|
// FIXME: should also send a user diff to the lobby
|
|
// to mark this player as available for others
|
|
}
|
|
return;
|
|
// If info is being provided about the game state.
|
|
} else if (data.child("info")) {
|
|
if (!g->is_player(sock)) return;
|
|
const simple_wml::node& info = *data.child("info");
|
|
if (info["type"] == "termination") {
|
|
g->set_termination_reason(info["condition"].to_string());
|
|
}
|
|
return;
|
|
} else if (data.child("turn")) {
|
|
// Notify the game of the commands, and if it changes
|
|
// the description, then sync the new description
|
|
// to players in the lobby.
|
|
if (g->process_turn(data, pl)) {
|
|
update_game_in_lobby(g);
|
|
}
|
|
return;
|
|
} else if (data.child("message")) {
|
|
g->process_message(data, pl);
|
|
return;
|
|
// Data to store and broadcast.
|
|
} else if (data.child("stop_updates")) {
|
|
// if (g->started()) g->record_data(data);
|
|
g->send_data(data, sock);
|
|
return;
|
|
// Data to ignore.
|
|
} else if (data.child("error")
|
|
|| data.child("side_secured")
|
|
|| data.root().has_attr("failed")
|
|
|| data.root().has_attr("side_drop")
|
|
|| data.root().has_attr("side")) {
|
|
return;
|
|
}
|
|
|
|
WRN_SERVER << "Received unknown data from: " << pl->second.name()
|
|
<< ". (socket:" << sock << ")\n" << data.output();
|
|
}
|
|
|
|
void server::delete_game(std::vector<wesnothd::game*>::iterator game_it) {
|
|
metrics_.game_terminated((*game_it)->termination_reason());
|
|
|
|
simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
|
|
assert(gamelist != NULL);
|
|
|
|
// Send a diff of the gamelist with the game deleted to players in the lobby
|
|
simple_wml::document diff;
|
|
bool send_diff = false;
|
|
if(make_delete_diff(*gamelist, "gamelist", "game",
|
|
(*game_it)->description(), diff)) {
|
|
send_diff = true;
|
|
}
|
|
|
|
// Delete the game from the games_and_users_list_.
|
|
const simple_wml::node::child_list& games = gamelist->children("game");
|
|
const simple_wml::node::child_list::const_iterator g =
|
|
std::find(games.begin(), games.end(), (*game_it)->description());
|
|
if (g != games.end()) {
|
|
const size_t index = g - games.begin();
|
|
gamelist->remove_child("game", index);
|
|
} else {
|
|
// Can happen when the game ends before the scenario was transfered.
|
|
LOG_SERVER << "Could not find game (" << (*game_it)->id()
|
|
<< ") to delete in games_and_users_list_.\n";
|
|
}
|
|
const wesnothd::user_vector& users = (*game_it)->all_game_users();
|
|
// Set the availability status for all quitting users.
|
|
for (wesnothd::user_vector::const_iterator user = users.begin();
|
|
user != users.end(); user++)
|
|
{
|
|
const wesnothd::player_map::iterator pl = players_.find(*user);
|
|
if (pl != players_.end()) {
|
|
pl->second.mark_available();
|
|
if (make_change_diff(games_and_users_list_.root(), NULL,
|
|
"user", pl->second.config_address(), diff)) {
|
|
send_diff = true;
|
|
}
|
|
} else {
|
|
ERR_SERVER << "ERROR: delete_game(): Could not find user in players_. (socket: "
|
|
<< *user << ")\n";
|
|
}
|
|
}
|
|
if (send_diff) {
|
|
lobby_.send_data(diff);
|
|
}
|
|
|
|
//send users in the game a notification to leave the game since it has ended
|
|
static simple_wml::document leave_game_doc("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
|
|
(*game_it)->send_data(leave_game_doc);
|
|
// Put the remaining users back in the lobby.
|
|
lobby_.add_players(**game_it, true);
|
|
|
|
(*game_it)->send_data(games_and_users_list_);
|
|
|
|
delete *game_it;
|
|
games_.erase(game_it);
|
|
}
|
|
|
|
void server::update_game_in_lobby(const wesnothd::game* g, network::connection exclude)
|
|
{
|
|
simple_wml::document diff;
|
|
if(make_change_diff(*games_and_users_list_.root().child("gamelist"), "gamelist", "game", g->description(), diff)) {
|
|
lobby_.send_data(diff, exclude);
|
|
}
|
|
}
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#ifndef _MSC_VER
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
int main(int argc, char** argv) {
|
|
int port = 15000;
|
|
size_t min_threads = 5;
|
|
size_t max_threads = 0;
|
|
|
|
std::string config_file;
|
|
|
|
// setting path to currentworking directory
|
|
game_config::path = get_cwd();
|
|
|
|
// show 'info' by default
|
|
lg::set_log_domain_severity("server", 2);
|
|
lg::timestamps(true);
|
|
|
|
for (int arg = 1; arg != argc; ++arg) {
|
|
const std::string val(argv[arg]);
|
|
if (val.empty()) {
|
|
continue;
|
|
}
|
|
|
|
if ((val == "--config" || val == "-c") && arg+1 != argc) {
|
|
config_file = argv[++arg];
|
|
} else if (val == "--verbose" || val == "-v") {
|
|
lg::set_log_domain_severity("all",3);
|
|
} else if (val.substr(0, 6) == "--log-") {
|
|
size_t p = val.find('=');
|
|
if (p == std::string::npos) {
|
|
std::cerr << "unknown option: " << val << '\n';
|
|
return 0;
|
|
}
|
|
std::string s = val.substr(6, p - 6);
|
|
int severity;
|
|
if (s == "error") severity = 0;
|
|
else if (s == "warning") severity = 1;
|
|
else if (s == "info") severity = 2;
|
|
else if (s == "debug") severity = 3;
|
|
else {
|
|
std::cerr << "unknown debug level: " << s << '\n';
|
|
return 0;
|
|
}
|
|
while (p != std::string::npos) {
|
|
size_t q = val.find(',', p + 1);
|
|
s = val.substr(p + 1, q == std::string::npos ? q : q - (p + 1));
|
|
if (!lg::set_log_domain_severity(s, severity)) {
|
|
std::cerr << "unknown debug domain: " << s << '\n';
|
|
return 0;
|
|
}
|
|
p = q;
|
|
}
|
|
} else if ((val == "--port" || val == "-p") && arg+1 != argc) {
|
|
port = atoi(argv[++arg]);
|
|
} else if (val == "--help" || val == "-h") {
|
|
std::cout << "usage: " << argv[0]
|
|
<< " [-dvV] [-c path] [-m n] [-p port] [-t n]\n"
|
|
<< " -c, --config <path> Tells wesnothd where to find the config file to use.\n"
|
|
<< " -d, --daemon Runs wesnothd as a daemon.\n"
|
|
<< " -h, --help Shows this usage message.\n"
|
|
<< " --log-<level>=<domain1>,<domain2>,...\n"
|
|
<< " sets the severity level of the debug domains.\n"
|
|
<< " 'all' can be used to match any debug domain.\n"
|
|
<< " Available levels: error, warning, info, debug.\n"
|
|
<< " -p, --port <port> Binds the server to the specified port.\n"
|
|
<< " -t, --threads <n> Uses n worker threads for network I/O (default: 5).\n"
|
|
<< " -v --verbose Turns on more verbose logging.\n"
|
|
<< " -V, --version Returns the server version.\n";
|
|
return 0;
|
|
} else if (val == "--version" || val == "-V") {
|
|
std::cout << "Battle for Wesnoth server " << game_config::version
|
|
<< "\n";
|
|
return 0;
|
|
} else if (val == "--daemon" || val == "-d") {
|
|
#ifdef _WIN32
|
|
ERR_SERVER << "Running as a daemon is not supported on this platform\n";
|
|
return -1;
|
|
#else
|
|
const pid_t pid = fork();
|
|
if (pid < 0) {
|
|
ERR_SERVER << "Could not fork and run as a daemon\n";
|
|
return -1;
|
|
} else if (pid > 0) {
|
|
std::cout << "Started wesnothd as a daemon with process id "
|
|
<< pid << "\n";
|
|
return 0;
|
|
}
|
|
|
|
setsid();
|
|
#endif
|
|
} else if ((val == "--threads" || val == "-t") && arg+1 != argc) {
|
|
min_threads = atoi(argv[++arg]);
|
|
if (min_threads > 30) {
|
|
min_threads = 30;
|
|
}
|
|
} else if ((val == "--max-threads" || val == "-T") && arg+1 != argc) {
|
|
max_threads = atoi(argv[++arg]);
|
|
} else if(val == "--request_sample_frequency" && arg+1 != argc) {
|
|
request_sample_frequency = atoi(argv[++arg]);
|
|
} else if (val[0] == '-') {
|
|
ERR_SERVER << "unknown option: " << val << "\n";
|
|
return 0;
|
|
} else {
|
|
port = atoi(argv[arg]);
|
|
}
|
|
}
|
|
|
|
network::set_raw_data_only();
|
|
|
|
try {
|
|
server(port, config_file, min_threads, max_threads).run();
|
|
} catch(network::error& e) {
|
|
ERR_SERVER << "Caught network error while server was running. Aborting.: "
|
|
<< e.message << "\n";
|
|
|
|
/**
|
|
* @todo errno should be passed here with the error or it might not be
|
|
* the true errno anymore. Seems to work good enough for now though.
|
|
*/
|
|
return errno;
|
|
} catch(std::bad_alloc&) {
|
|
ERR_SERVER << "Ran out of memory. Aborting.\n";
|
|
return ENOMEM;
|
|
} catch(...) {
|
|
ERR_SERVER << "Caught unknown error while server was running. Aborting.\n";
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|