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>
42 #ifndef BOOST_NO_EXCEPTIONS
43 #include <boost/exception/diagnostic_information.hpp>
53 #define ERR_SERVER LOG_STREAM(err, log_server)
54 #define WRN_SERVER LOG_STREAM(warn, log_server)
55 #define LOG_SERVER LOG_STREAM(info, log_server)
56 #define DBG_SERVER LOG_STREAM(debug, log_server)
59 #define ERR_CONFIG LOG_STREAM(err, log_config)
60 #define WRN_CONFIG LOG_STREAM(warn, log_config)
66 , keep_alive_(keep_alive)
68 , acceptor_v6_(io_service_)
69 , acceptor_v4_(io_service_)
70 , handshake_response_()
73 , sighup_(io_service_, SIGHUP)
80 boost::asio::ip::tcp::endpoint endpoint_v6(boost::asio::ip::tcp::v6(),
port_);
81 boost::asio::spawn(
io_service_, [
this, endpoint_v6](
const boost::asio::yield_context& yield) {
serve(yield,
acceptor_v6_, endpoint_v6); }
82 #if BOOST_VERSION >= 108000
83 , [](
const std::exception_ptr&
e) {
if (
e) std::rethrow_exception(
e); }
87 boost::asio::ip::tcp::endpoint endpoint_v4(boost::asio::ip::tcp::v4(),
port_);
88 boost::asio::spawn(
io_service_, [
this, endpoint_v4](
const boost::asio::yield_context& yield) {
serve(yield,
acceptor_v4_, endpoint_v4); }
89 #if BOOST_VERSION >= 108000
90 , [](
const std::exception_ptr&
e) {
if (
e) std::rethrow_exception(
e); }
98 [
this](
const boost::system::error_code& error,
int sig)
103 void server_base::serve(
const boost::asio::yield_context& yield, boost::asio::ip::tcp::acceptor& acceptor,
const boost::asio::ip::tcp::endpoint& endpoint)
106 if(!acceptor.is_open()) {
107 acceptor.open(endpoint.protocol());
108 acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(
true));
109 acceptor.set_option(boost::asio::ip::tcp::acceptor::keep_alive(
keep_alive_));
110 if(endpoint.protocol() == boost::asio::ip::tcp::v6())
111 acceptor.set_option(boost::asio::ip::v6_only(
true));
112 acceptor.bind(endpoint);
115 }
catch(
const boost::system::system_error&
e) {
116 ERR_SERVER <<
"Exception when trying to bind port: " <<
e.code().message();
122 boost::system::error_code error;
123 acceptor.async_accept(socket->lowest_layer(), yield[error]);
125 ERR_SERVER <<
"Accept failed: " << error.message();
130 boost::asio::spawn(
io_service_, [
this, &acceptor, endpoint](
const boost::asio::yield_context& yield) {
serve(yield, acceptor, endpoint); }
131 #if BOOST_VERSION >= 108000
132 , [](
const std::exception_ptr&
e) {
if (
e) std::rethrow_exception(
e); }
144 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPIDLE, &timeout,
sizeof(timeout));
145 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPCNT, &cnt,
sizeof(cnt));
146 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPINTVL, &interval,
sizeof(interval));
147 #elif defined(__APPLE__) && defined(__MACH__)
148 setsockopt(socket->native_handle(), IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
sizeof(timeout));
149 #elif defined(_WIN32)
151 DWORD timeout_ms = timeout * 1000;
152 setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char*
>(&timeout_ms),
sizeof(timeout_ms));
153 setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO,
reinterpret_cast<const char*
>(&timeout_ms),
sizeof(timeout_ms));
158 fcntl(socket->native_handle(), F_SETFD, FD_CLOEXEC);
163 uint32_t protocol_version;
164 uint32_t handshake_response;
168 async_read(*socket, boost::asio::buffer(
reinterpret_cast<std::byte*
>(&protocol_version), 4), yield[error]);
172 switch(ntohl(protocol_version)) {
174 async_write(*socket, boost::asio::buffer(
reinterpret_cast<std::byte*
>(&
handshake_response_), 4), yield[error]);
176 final_socket = socket;
181 handshake_response = 0xFFFFFFFFU;
183 handshake_response = 0x00000000;
186 async_write(*socket, boost::asio::buffer(
reinterpret_cast<const std::byte*
>(&handshake_response), 4), yield[error]);
189 final_socket = socket;
194 utils::get<tls_socket_ptr>(final_socket)->async_handshake(boost::asio::ssl::stream_base::server, yield[error]);
196 ERR_SERVER <<
"TLS handshake failed: " << error.message();
207 [
this](
auto&& socket) {
211 const auto& [error_code, reason, time_remaining] = *ban_info;
216 async_send_error(socket,
"You are banned from this server: " + reason, error_code,
217 {{
"duration", std::to_string(time_remaining->count()) }});
220 async_send_error(socket,
"You are banned from this server: " + reason, error_code);
233 DBG_SERVER << ip <<
"\tnew encrypted connection fully accepted";
235 DBG_SERVER << ip <<
"\tnew connection fully accepted";
246 [
this](
const boost::system::error_code& error, std::size_t bytes_transferred)
256 LOG_SERVER <<
"Server has shut down because event loop is out of work";
259 LOG_SERVER <<
"Server has been shut down: " <<
e.what();
261 }
catch(
const boost::system::system_error&
e) {
262 ERR_SERVER <<
"Caught system error exception from handler: " <<
e.code().message();
263 }
catch(
const std::exception&
e) {
264 ERR_SERVER <<
"Caught exception from handler: " <<
e.what() <<
"\n" << boost::current_exception_diagnostic_information();
271 boost::system::error_code error;
272 std::string result = socket->lowest_layer().remote_endpoint(error).address().to_string();
274 return "<unknown address>";
279 template<
class SocketPtr>
bool check_error(
const boost::system::error_code& error, SocketPtr socket)
282 if(error == boost::asio::error::eof)
301 for(
const auto& kv :
info) {
317 std::cout <<
"Sending WML to " <<
log_address(socket) <<
": \n" << doc.
output() << std::endl;
328 data_size.size = htonl(
s.size());
330 std::vector<boost::asio::const_buffer> buffers {
331 { data_size.buf, 4 },
332 {
s.begin(), std::size_t(
s.size()) }
335 boost::system::error_code ec;
336 async_write(*socket, buffers, yield[ec]);
338 socket->lowest_layer().close();
342 WRN_CONFIG << __func__ <<
": simple_wml error: " <<
e.message;
357 data_size.size = htonl(filesize);
359 boost::system::error_code ec;
360 async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
362 socket->lowest_layer().close();
370 ifs->read(buf,
sizeof(buf));
371 async_write(*socket, boost::asio::buffer(buf, ifs->gcount()), yield[ec]);
373 socket->lowest_layer().close();
391 int in_file { open(
filename.c_str(), O_RDONLY) };
401 data_size.size = htonl(filesize);
403 boost::system::error_code ec;
404 async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
408 if(!socket->native_non_blocking()) {
409 socket->native_non_blocking(
true, ec);
417 int n = ::sendfile(socket->native_handle(), in_file, &offset, 65536);
418 ec = boost::system::error_code(
n < 0 ? errno : 0,
419 boost::asio::error::get_system_category());
423 if(ec == boost::asio::error::interrupted)
427 if (ec == boost::asio::error::would_block
428 || ec == boost::asio::error::try_again)
431 socket->async_write_some(boost::asio::null_buffers(), yield[ec]);
447 #elif defined(_WIN32)
457 std::vector<boost::asio::const_buffer> buffers;
459 SetLastError(ERROR_SUCCESS);
462 std::wstring filename_ucs2 = unicode_cast<std::wstring>(
filename);
463 HANDLE in_file = CreateFileW(filename_ucs2.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING,
464 FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
465 if (GetLastError() != ERROR_SUCCESS)
467 throw std::runtime_error(
"Failed to open the file");
469 BOOST_SCOPE_EXIT_ALL(in_file) {
470 CloseHandle(&in_file);
473 HANDLE event = CreateEvent(
nullptr, TRUE, TRUE,
nullptr);
474 if (GetLastError() != ERROR_SUCCESS)
476 throw std::runtime_error(
"Failed to create an event");
478 BOOST_SCOPE_EXIT_ALL(&overlap) {
479 CloseHandle(overlap.hEvent);
482 overlap.hEvent = event;
489 data_size.size = htonl(filesize);
491 async_write(*socket, boost::asio::buffer(data_size.buf, 4), yield);
493 BOOL
success = TransmitFile(socket->native_handle(), in_file, 0, 0, &overlap,
nullptr, 0);
495 if(WSAGetLastError() == WSA_IO_PENDING) {
498 socket->async_write_some(boost::asio::null_buffers(), yield);
500 DWORD win_ec = GetLastError();
501 if (win_ec != ERROR_IO_PENDING && win_ec != ERROR_SUCCESS)
502 throw std::runtime_error(
"TransmitFile failed");
504 if(HasOverlappedIoCompleted(&overlap))
break;
507 throw std::runtime_error(
"TransmitFile failed");
533 boost::system::error_code ec;
534 async_read(*socket, boost::asio::buffer(data_size.buf, 4), yield[ec]);
536 uint32_t
size = ntohl(data_size.size);
541 "\treceived invalid packet with payload size 0";
547 "\treceived packet with payload size over size limit";
551 boost::shared_array<char> buffer{
new char[
size] };
552 async_read(*socket, boost::asio::buffer(buffer.get(),
size), yield[ec]);
557 return std::make_unique<simple_wml::document>(compressed_buf);
561 "\tsimple_wml error in received data: " <<
e.message;
566 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<socket_ptr>(
socket_ptr socket,
const boost::asio::yield_context& yield);
567 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<tls_socket_ptr>(
tls_socket_ptr socket,
const boost::asio::yield_context& yield);
569 template<
class SocketPtr>
void server_base::send_doc_queued(SocketPtr socket, std::unique_ptr<simple_wml::document>& doc_ptr, boost::asio::yield_context yield)
571 static std::map<SocketPtr, std::queue<std::unique_ptr<simple_wml::document>>> queues;
573 queues[socket].push(std::move(doc_ptr));
574 if(queues[socket].
size() > 1) {
580 while(queues[socket].
size() > 0) {
589 io_service_, [
this, doc_ptr = doc.
clone(), socket](boost::asio::yield_context yield)
mutable {
590 send_doc_queued(socket, doc_ptr, yield);
592 #
if BOOST_VERSION >= 108000
593 , [](
const std::exception_ptr&
e) { if (e) std::rethrow_exception(e); }
602 if(*error_code !=
'\0') {
605 info_table_into_simple_wml(doc,
"error",
info);
609 template void server_base::async_send_error<socket_ptr>(
socket_ptr socket,
const std::string&
msg,
const char* error_code,
const info_table&
info);
610 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);
616 if(*warning_code !=
'\0') {
617 doc.
child(
"warning")->
set_attr(
"warning_code", warning_code);
619 info_table_into_simple_wml(doc,
"warning",
info);
623 template void server_base::async_send_warning<socket_ptr>(
socket_ptr socket,
const std::string&
msg,
const char* warning_code,
const info_table&
info);
624 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);
632 boost::asio::ssl::context::default_workarounds
633 | boost::asio::ssl::context::no_sslv2
634 | boost::asio::ssl::context::no_sslv3
635 | boost::asio::ssl::context::single_dh_use
638 tls_context_.use_certificate_chain_file(cfg[
"tls_fullchain"].str());
639 tls_context_.use_private_key_file(cfg[
"tls_private_key"].str(), boost::asio::ssl::context::pem);
640 if(!cfg[
"tls_dh"].str().empty())
tls_context_.use_tmp_dh_file(cfg[
"tls_dh"].str());
645 if(salt.length() < 12) {
646 ERR_SERVER <<
"Bad salt found for user: " << username;
650 std::string password = pw;
656 for(std::string::size_type pos = 0; (pos = password.find(
'&', pos)) != std::string::npos; ++pos) {
657 password.replace(pos, 1,
"&");
659 for(std::string::size_type pos = 0; (pos = password.find(
'\"', pos)) != std::string::npos; ++pos) {
660 password.replace(pos, 1,
""");
662 for(std::string::size_type pos = 0; (pos = password.find(
'<', pos)) != std::string::npos; ++pos) {
663 password.replace(pos, 1,
"<");
665 for(std::string::size_type pos = 0; (pos = password.find(
'>', pos)) != std::string::npos; ++pos) {
666 password.replace(pos, 1,
">");
676 return hash.base64_digest();
678 ERR_SERVER <<
"bcrypt hash failed for user " << username <<
": " <<
err.what();
682 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...
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
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
virtual utils::optional< login_ban_info > is_ip_banned(const std::string &)
void coro_send_doc(SocketPtr socket, simple_wml::document &doc, const boost::asio::yield_context &yield)
Send a WML document from within a coroutine.
void send_doc_queued(SocketPtr socket, std::unique_ptr< simple_wml::document > &doc_ptr, boost::asio::yield_context yield)
boost::asio::io_context io_service_
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={})
void serve(const boost::asio::yield_context &yield, boost::asio::ip::tcp::acceptor &acceptor, const boost::asio::ip::tcp::endpoint &endpoint)
virtual bool ip_exceeds_connection_limit(const std::string &) const
virtual bool accepting_connections() const
server_base(unsigned short port, bool keep_alive)
std::unique_ptr< simple_wml::document > coro_receive_doc(SocketPtr socket, const boost::asio::yield_context &yield)
Receive WML document from a coroutine.
boost::asio::posix::stream_descriptor input_
void coro_send_file(const socket_ptr &socket, const std::string &filename, const boost::asio::yield_context &yield)
Send contents of entire file directly to socket from within a coroutine.
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(std::string_view 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, const 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