29 #include <sys/sendfile.h>
34 #include <boost/scope_exit.hpp>
37 #include <boost/asio/ip/v6_only.hpp>
38 #include <boost/asio/read.hpp>
40 #include <boost/asio/read_until.hpp>
49 #define ERR_SERVER LOG_STREAM(err, log_server)
50 #define WRN_SERVER LOG_STREAM(warn, log_server)
51 #define LOG_SERVER LOG_STREAM(info, log_server)
52 #define DBG_SERVER LOG_STREAM(debug, log_server)
55 #define ERR_CONFIG LOG_STREAM(err, log_config)
56 #define WRN_CONFIG LOG_STREAM(warn, log_config)
62 , keep_alive_(keep_alive)
64 , acceptor_v6_(io_service_)
65 , acceptor_v4_(io_service_)
66 , handshake_response_()
69 , sighup_(io_service_, SIGHUP)
76 boost::asio::ip::tcp::endpoint endpoint_v6(boost::asio::ip::tcp::v6(),
port_);
78 #if BOOST_VERSION >= 108000
79 , [](std::exception_ptr
e) {
if (
e) std::rethrow_exception(
e); }
83 boost::asio::ip::tcp::endpoint endpoint_v4(boost::asio::ip::tcp::v4(),
port_);
85 #if BOOST_VERSION >= 108000
86 , [](std::exception_ptr
e) {
if (
e) std::rethrow_exception(
e); }
94 [
this](
const boost::system::error_code& error,
int sig)
99 void server_base::serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint)
102 if(!acceptor.is_open()) {
103 acceptor.open(endpoint.protocol());
104 acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(
true));
105 acceptor.set_option(boost::asio::ip::tcp::acceptor::keep_alive(
keep_alive_));
106 if(endpoint.protocol() == boost::asio::ip::tcp::v6())
107 acceptor.set_option(boost::asio::ip::v6_only(
true));
108 acceptor.bind(endpoint);
111 }
catch(
const boost::system::system_error&
e) {
112 ERR_SERVER <<
"Exception when trying to bind port: " <<
e.code().message();
118 boost::system::error_code error;
119 acceptor.async_accept(socket->lowest_layer(), yield[error]);
121 ERR_SERVER <<
"Accept failed: " << error.message();
126 boost::asio::spawn(
io_service_, [
this, &acceptor, endpoint](boost::asio::yield_context yield) {
serve(yield, acceptor, endpoint); }
127 #if BOOST_VERSION >= 108000
128 , [](std::exception_ptr
e) {
if (
e) std::rethrow_exception(
e); }
140 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPIDLE, &timeout,
sizeof(timeout));
141 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPCNT, &cnt,
sizeof(cnt));
142 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPINTVL, &interval,
sizeof(interval));
143 #elif defined(__APPLE__) && defined(__MACH__)
144 setsockopt(socket->native_handle(), IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
sizeof(timeout));
145 #elif defined(_WIN32)
147 DWORD timeout_ms = timeout * 1000;
148 setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char*
>(&timeout_ms),
sizeof(timeout_ms));
149 setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO,
reinterpret_cast<const char*
>(&timeout_ms),
sizeof(timeout_ms));
154 fcntl(socket->native_handle(), F_SETFD, FD_CLOEXEC);
159 uint32_t protocol_version;
160 uint32_t handshake_response;
164 async_read(*socket, boost::asio::buffer(
reinterpret_cast<std::byte*
>(&protocol_version), 4), yield[error]);
168 switch(ntohl(protocol_version)) {
170 async_write(*socket, boost::asio::buffer(
reinterpret_cast<std::byte*
>(&
handshake_response_), 4), yield[error]);
172 final_socket = socket;
177 handshake_response = 0xFFFFFFFFU;
179 handshake_response = 0x00000000;
182 async_write(*socket, boost::asio::buffer(
reinterpret_cast<const std::byte*
>(&handshake_response), 4), yield[error]);
185 final_socket = socket;
190 utils::get<tls_socket_ptr>(final_socket)->async_handshake(boost::asio::ssl::stream_base::server, yield[error]);
192 ERR_SERVER <<
"TLS handshake failed: " << error.message();
202 utils::visit([
this](
auto&& socket) {
206 if (!reason.empty()) {
207 LOG_SERVER << ip <<
"\trejected banned user. Reason: " << reason;
211 LOG_SERVER << ip <<
"\trejected ip due to excessive connections";
216 DBG_SERVER << ip <<
"\tnew encrypted connection fully accepted";
218 DBG_SERVER << ip <<
"\tnew connection fully accepted";
229 [
this](
const boost::system::error_code& error, std::size_t bytes_transferred)
239 LOG_SERVER <<
"Server has shut down because event loop is out of work";
242 LOG_SERVER <<
"Server has been shut down: " <<
e.what();
244 }
catch(
const boost::system::system_error&
e) {
245 ERR_SERVER <<
"Caught system error exception from handler: " <<
e.code().message();
246 }
catch(
const std::exception&
e) {
247 ERR_SERVER <<
"Caught exception from handler: " <<
e.what() <<
"\n" << boost::current_exception_diagnostic_information();
254 boost::system::error_code error;
255 std::string result = socket->lowest_layer().remote_endpoint(error).address().to_string();
257 return "<unknown address>";
262 template<
class SocketPtr>
bool check_error(
const boost::system::error_code& error, SocketPtr socket)
265 if(error == boost::asio::error::eof)
284 for(
const auto& kv :
info) {
300 std::cout <<
"Sending WML to " <<
log_address(socket) <<
": \n" << doc.
output() << std::endl;
311 data_size.size = htonl(
s.size());
313 std::vector<boost::asio::const_buffer> buffers {
314 { data_size.buf, 4 },
315 {
s.begin(), std::size_t(
s.size()) }
318 boost::system::error_code ec;
319 async_write(*socket, buffers, yield[ec]);
321 socket->lowest_layer().close();
325 WRN_CONFIG << __func__ <<
": simple_wml error: " <<
e.message;
340 data_size.size = htonl(filesize);
342 boost::system::error_code ec;
343 async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
345 socket->lowest_layer().close();
353 ifs->read(buf,
sizeof(buf));
354 async_write(*socket, boost::asio::buffer(buf, ifs->gcount()), yield[ec]);
356 socket->lowest_layer().close();
374 int in_file { open(
filename.c_str(), O_RDONLY) };
384 data_size.size = htonl(filesize);
386 boost::system::error_code ec;
387 async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
391 if(!socket->native_non_blocking()) {
392 socket->native_non_blocking(
true, ec);
400 int n = ::sendfile(socket->native_handle(), in_file, &offset, 65536);
401 ec = boost::system::error_code(
n < 0 ? errno : 0,
402 boost::asio::error::get_system_category());
406 if(ec == boost::asio::error::interrupted)
410 if (ec == boost::asio::error::would_block
411 || ec == boost::asio::error::try_again)
414 socket->async_write_some(boost::asio::null_buffers(), yield[ec]);
430 #elif defined(_WIN32)
440 std::vector<boost::asio::const_buffer> buffers;
442 SetLastError(ERROR_SUCCESS);
445 std::wstring filename_ucs2 = unicode_cast<std::wstring>(
filename);
446 HANDLE in_file = CreateFileW(filename_ucs2.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING,
447 FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
448 if (GetLastError() != ERROR_SUCCESS)
450 throw std::runtime_error(
"Failed to open the file");
452 BOOST_SCOPE_EXIT_ALL(in_file) {
453 CloseHandle(&in_file);
456 HANDLE event = CreateEvent(
nullptr, TRUE, TRUE,
nullptr);
457 if (GetLastError() != ERROR_SUCCESS)
459 throw std::runtime_error(
"Failed to create an event");
461 BOOST_SCOPE_EXIT_ALL(&overlap) {
462 CloseHandle(overlap.hEvent);
465 overlap.hEvent = event;
472 data_size.size = htonl(filesize);
474 async_write(*socket, boost::asio::buffer(data_size.buf, 4), yield);
476 BOOL
success = TransmitFile(socket->native_handle(), in_file, 0, 0, &overlap,
nullptr, 0);
478 if(WSAGetLastError() == WSA_IO_PENDING) {
481 socket->async_write_some(boost::asio::null_buffers(), yield);
483 DWORD win_ec = GetLastError();
484 if (win_ec != ERROR_IO_PENDING && win_ec != ERROR_SUCCESS)
485 throw std::runtime_error(
"TransmitFile failed");
487 if(HasOverlappedIoCompleted(&overlap))
break;
490 throw std::runtime_error(
"TransmitFile failed");
516 boost::system::error_code ec;
517 async_read(*socket, boost::asio::buffer(data_size.buf, 4), yield[ec]);
519 uint32_t
size = ntohl(data_size.size);
524 "\treceived invalid packet with payload size 0";
530 "\treceived packet with payload size over size limit";
534 boost::shared_array<char> buffer{
new char[
size] };
535 async_read(*socket, boost::asio::buffer(buffer.get(),
size), yield[ec]);
540 return std::make_unique<simple_wml::document>(compressed_buf);
544 "\tsimple_wml error in received data: " <<
e.message;
549 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<socket_ptr>(
socket_ptr socket, boost::asio::yield_context yield);
550 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<tls_socket_ptr>(
tls_socket_ptr socket, boost::asio::yield_context yield);
552 template<
class SocketPtr>
void server_base::send_doc_queued(SocketPtr socket, std::unique_ptr<simple_wml::document>& doc_ptr, boost::asio::yield_context yield)
554 static std::map<SocketPtr, std::queue<std::unique_ptr<simple_wml::document>>> queues;
556 queues[socket].push(std::move(doc_ptr));
557 if(queues[socket].
size() > 1) {
563 while(queues[socket].
size() > 0) {
572 io_service_, [
this, doc_ptr = doc.
clone(), socket](boost::asio::yield_context yield)
mutable {
573 send_doc_queued(socket, doc_ptr, yield);
575 #
if BOOST_VERSION >= 108000
576 , [](std::exception_ptr
e) { if (e) std::rethrow_exception(e); }
585 if(*error_code !=
'\0') {
588 info_table_into_simple_wml(doc,
"error",
info);
592 template void server_base::async_send_error<socket_ptr>(
socket_ptr socket,
const std::string&
msg,
const char* error_code,
const info_table&
info);
593 template void server_base::async_send_error<tls_socket_ptr>(
tls_socket_ptr socket,
const std::string&
msg,
const char* error_code,
const info_table&
info);
599 if(*warning_code !=
'\0') {
600 doc.
child(
"warning")->
set_attr(
"warning_code", warning_code);
602 info_table_into_simple_wml(doc,
"warning",
info);
606 template void server_base::async_send_warning<socket_ptr>(
socket_ptr socket,
const std::string&
msg,
const char* warning_code,
const info_table&
info);
607 template void server_base::async_send_warning<tls_socket_ptr>(
tls_socket_ptr socket,
const std::string&
msg,
const char* warning_code,
const info_table&
info);
615 boost::asio::ssl::context::default_workarounds
616 | boost::asio::ssl::context::no_sslv2
617 | boost::asio::ssl::context::no_sslv3
618 | boost::asio::ssl::context::single_dh_use
621 tls_context_.use_certificate_chain_file(cfg[
"tls_fullchain"].str());
622 tls_context_.use_private_key_file(cfg[
"tls_private_key"].str(), boost::asio::ssl::context::pem);
623 if(!cfg[
"tls_dh"].str().empty())
tls_context_.use_tmp_dh_file(cfg[
"tls_dh"].str());
628 if(salt.length() < 12) {
629 ERR_SERVER <<
"Bad salt found for user: " << username;
633 std::string password = pw;
639 for(std::string::size_type pos = 0; (pos = password.find(
'&', pos)) != std::string::npos; ++pos) {
640 password.replace(pos, 1,
"&");
642 for(std::string::size_type pos = 0; (pos = password.find(
'\"', pos)) != std::string::npos; ++pos) {
643 password.replace(pos, 1,
""");
645 for(std::string::size_type pos = 0; (pos = password.find(
'<', pos)) != std::string::npos; ++pos) {
646 password.replace(pos, 1,
"<");
648 for(std::string::size_type pos = 0; (pos = password.find(
'>', pos)) != std::string::npos; ++pos) {
649 password.replace(pos, 1,
">");
659 return hash.base64_digest();
661 ERR_SERVER <<
"bcrypt hash failed for user " << username <<
": " <<
err.what();
665 ERR_SERVER <<
"Unable to determine how to hash the password for user: " << username;
A config object defines a single node in a WML file, with access to child nodes.
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...
void coro_send_file(socket_ptr socket, const std::string &filename, boost::asio::yield_context yield)
Send contents of entire file directly to socket from within a coroutine.
virtual void handle_read_from_fifo(const boost::system::error_code &error, std::size_t bytes_transferred)=0
boost::asio::signal_set sighup_
uint32_t handshake_response_
boost::asio::streambuf admin_cmd_
void async_send_error(SocketPtr socket, const std::string &msg, const char *error_code="", const info_table &info={})
virtual void handle_new_client(socket_ptr socket)=0
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.
std::map< std::string, std::string > info_table
void send_doc_queued(SocketPtr socket, std::unique_ptr< simple_wml::document > &doc_ptr, boost::asio::yield_context yield)
void coro_send_doc(SocketPtr socket, simple_wml::document &doc, boost::asio::yield_context yield)
Send a WML document from within a coroutine.
virtual void handle_sighup(const boost::system::error_code &error, int signal_number)=0
void async_send_warning(SocketPtr socket, const std::string &msg, const char *warning_code="", const info_table &info={})
virtual bool ip_exceeds_connection_limit(const std::string &) const
boost::asio::io_service io_service_
virtual bool accepting_connections() const
virtual std::string is_ip_banned(const std::string &)
server_base(unsigned short port, bool keep_alive)
boost::asio::posix::stream_descriptor input_
void serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor &acceptor, boost::asio::ip::tcp::endpoint endpoint)
void load_tls_config(const config &cfg)
boost::asio::ssl::context tls_context_
boost::asio::ip::tcp::acceptor acceptor_v6_
std::unique_ptr< document > clone()
static std::size_t document_size_limit
node * child(const char *name)
string_span output_compressed(bool bzip2=false)
node & add_child(const char *name)
node & set_attr(const char *key, const char *value)
node & set_attr_dup(const char *key, const char *value)
static bool is_valid_prefix(const std::string &hash)
static bcrypt from_salted_salt(const std::string &input)
static bcrypt hash_pw(const std::string &password, bcrypt &salt)
static bool is_valid_prefix(const std::string &hash)
virtual std::string base64_digest() const override
static std::string get_salt(const std::string &hash)
static int get_iteration_count(const std::string &hash)
Represents version numbers.
Definitions for the interface to Wesnoth Markup Language (WML).
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
Declarations for File-IO.
Standard logging facilities (interface).
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
int file_size(const std::string &fname)
Returns the size of a file, or -1 if the file doesn't exist.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
#define ON_SCOPE_EXIT(...)
Run some arbitrary code (a lambda) when the current scope exits The lambda body follows this header,...
template bool check_error< tls_socket_ptr >(const boost::system::error_code &error, tls_socket_ptr socket)
void coro_send_file_userspace(SocketPtr socket, const std::string &filename, boost::asio::yield_context yield)
bool check_error(const boost::system::error_code &error, SocketPtr socket)
std::string client_address(SocketPtr socket)
std::string deprecated_message(const std::string &, DEP_LEVEL, const version_info &, const std::string &)
static lg::log_domain log_server("server")
static lg::log_domain log_config("config")
Base class for servers using Wesnoth's WML over TCP protocol.
std::shared_ptr< boost::asio::ssl::stream< socket_ptr::element_type > > tls_socket_ptr
std::string log_address(SocketPtr socket)
utils::variant< socket_ptr, tls_socket_ptr > any_socket_ptr
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
std::string filename
Filename.
static map_location::direction n
static map_location::direction s