31 #include <sys/sendfile.h> 36 #include <boost/scope_exit.hpp> 39 #include <boost/asio/ip/v6_only.hpp> 40 #include <boost/asio/read.hpp> 42 #include <boost/asio/read_until.hpp> 44 #include <boost/asio/write.hpp> 56 #define ERR_SERVER LOG_STREAM(err, log_server) 57 #define WRN_SERVER LOG_STREAM(warn, log_server) 58 #define LOG_SERVER LOG_STREAM(info, log_server) 59 #define DBG_SERVER LOG_STREAM(debug, log_server) 62 #define ERR_CONFIG LOG_STREAM(err, log_config) 63 #define WRN_CONFIG LOG_STREAM(warn, log_config) 69 , keep_alive_(keep_alive)
71 , acceptor_v6_(io_service_)
72 , acceptor_v4_(io_service_)
73 , handshake_response_()
76 , sighup_(io_service_, SIGHUP)
78 , sigs_(io_service_, SIGINT, SIGTERM)
84 boost::asio::ip::tcp::endpoint endpoint_v6(boost::asio::ip::tcp::v6(),
port_);
87 boost::asio::ip::tcp::endpoint endpoint_v4(boost::asio::ip::tcp::v4(),
port_);
94 [=](
const boost::system::error_code& error,
int sig)
100 void server_base::serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint)
103 if(!acceptor.is_open()) {
104 acceptor.open(endpoint.protocol());
105 acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(
true));
106 acceptor.set_option(boost::asio::ip::tcp::acceptor::keep_alive(
keep_alive_));
107 if(endpoint.protocol() == boost::asio::ip::tcp::v6())
108 acceptor.set_option(boost::asio::ip::v6_only(
true));
109 acceptor.bind(endpoint);
112 }
catch(
const boost::system::system_error&
e) {
113 ERR_SERVER <<
"Exception when trying to bind port: " << e.code().message();
114 BOOST_THROW_EXCEPTION(
server_shutdown(
"Port binding failed", e.code()));
119 boost::system::error_code error;
120 acceptor.async_accept(socket->lowest_layer(), yield[error]);
122 ERR_SERVER <<
"Accept failed: " << error.message();
127 boost::asio::spawn(
io_service_, [
this, &acceptor, endpoint](boost::asio::yield_context yield) {
serve(yield, acceptor, endpoint); });
137 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPIDLE, &timeout,
sizeof(timeout));
138 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPCNT, &cnt,
sizeof(cnt));
139 setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPINTVL, &interval,
sizeof(interval));
140 #elif defined(__APPLE__) && defined(__MACH__) 141 setsockopt(socket->native_handle(), IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
sizeof(timeout));
142 #elif defined(_WIN32) 144 DWORD timeout_ms = timeout * 1000;
145 setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char*
>(&timeout_ms),
sizeof(timeout_ms));
146 setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO,
reinterpret_cast<const char*
>(&timeout_ms),
sizeof(timeout_ms));
151 fcntl(socket->native_handle(), F_SETFD, FD_CLOEXEC);
156 uint32_t protocol_version;
157 uint32_t handshake_response;
161 async_read(*socket, boost::asio::buffer(reinterpret_cast<std::byte*>(&protocol_version), 4), yield[error]);
165 switch(ntohl(protocol_version)) {
167 async_write(*socket, boost::asio::buffer(reinterpret_cast<std::byte*>(&
handshake_response_), 4), yield[error]);
169 final_socket = socket;
174 handshake_response = 0xFFFFFFFFU;
176 handshake_response = 0x00000000;
179 async_write(*socket, boost::asio::buffer(reinterpret_cast<const std::byte*>(&handshake_response), 4), yield[error]);
182 final_socket = socket;
187 utils::get<tls_socket_ptr>(final_socket)->async_handshake(boost::asio::ssl::stream_base::server, yield[error]);
189 ERR_SERVER <<
"TLS handshake failed: " << error.message();
199 utils::visit([
this](
auto&& socket) {
203 if (!reason.empty()) {
204 LOG_SERVER << ip <<
"\trejected banned user. Reason: " << reason;
208 LOG_SERVER << ip <<
"\trejected ip due to excessive connections";
213 DBG_SERVER << ip <<
"\tnew encrypted connection fully accepted";
215 DBG_SERVER << ip <<
"\tnew connection fully accepted";
226 [=](
const boost::system::error_code& error, std::size_t bytes_transferred)
237 if(signal_number == SIGINT) signame =
"SIGINT";
238 else if(signal_number == SIGTERM) signame =
"SIGTERM";
239 else signame = std::to_string(signal_number);
240 LOG_SERVER << signame <<
" caught, exiting without cleanup immediately.";
241 exit(128 + signal_number);
248 LOG_SERVER <<
"Server has shut down because event loop is out of work";
253 }
catch(
const boost::system::system_error& e) {
254 ERR_SERVER <<
"Caught system error exception from handler: " << e.code().message();
255 }
catch(
const std::exception& e) {
256 ERR_SERVER <<
"Caught exception from handler: " << e.what() <<
"\n" << boost::current_exception_diagnostic_information();
263 boost::system::error_code error;
264 std::string result = socket->lowest_layer().remote_endpoint(error).address().to_string();
266 return "<unknown address>";
271 template<
class SocketPtr>
bool check_error(
const boost::system::error_code& error, SocketPtr socket)
274 if(error == boost::asio::error::eof)
292 auto& node = doc.
child(parent_name.c_str())->add_child(
"data");
293 for(
const auto& kv : info) {
309 std::cout <<
"Sending WML to " <<
log_address(socket) <<
": \n" << doc.
output() << std::endl;
320 data_size.size = htonl(s.
size());
322 std::vector<boost::asio::const_buffer> buffers {
323 { data_size.buf, 4 },
327 boost::system::error_code ec;
328 async_write(*socket, buffers, yield[ec]);
330 socket->lowest_layer().close();
341 template<
class SocketPtr>
void coro_send_file_userspace(SocketPtr socket,
const std::string& filename, boost::asio::yield_context yield)
349 data_size.size = htonl(filesize);
351 boost::system::error_code ec;
352 async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
354 socket->lowest_layer().close();
362 ifs->read(buf,
sizeof(buf));
363 async_write(*socket, boost::asio::buffer(buf, ifs->gcount()), yield[ec]);
365 socket->lowest_layer().close();
383 int in_file { open(filename.c_str(), O_RDONLY) };
393 data_size.size = htonl(filesize);
395 boost::system::error_code ec;
396 async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
400 if(!socket->native_non_blocking()) {
401 socket->native_non_blocking(
true, ec);
409 int n = ::sendfile(socket->native_handle(), in_file, &offset, 65536);
410 ec = boost::system::error_code(n < 0 ? errno : 0,
411 boost::asio::error::get_system_category());
415 if(ec == boost::asio::error::interrupted)
419 if (ec == boost::asio::error::would_block
420 || ec == boost::asio::error::try_again)
423 socket->async_write_some(boost::asio::null_buffers(), yield[ec]);
439 #elif defined(_WIN32) 449 std::vector<boost::asio::const_buffer> buffers;
451 SetLastError(ERROR_SUCCESS);
454 std::wstring filename_ucs2 =
unicode_cast<std::wstring>(filename);
455 HANDLE in_file = CreateFileW(filename_ucs2.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING,
456 FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
457 if (GetLastError() != ERROR_SUCCESS)
459 throw std::runtime_error(
"Failed to open the file");
461 BOOST_SCOPE_EXIT_ALL(in_file) {
462 CloseHandle(&in_file);
465 HANDLE
event = CreateEvent(
nullptr, TRUE, TRUE,
nullptr);
466 if (GetLastError() != ERROR_SUCCESS)
468 throw std::runtime_error(
"Failed to create an event");
470 BOOST_SCOPE_EXIT_ALL(&overlap) {
471 CloseHandle(overlap.hEvent);
474 overlap.hEvent = event;
481 data_size.size = htonl(filesize);
483 async_write(*socket, boost::asio::buffer(data_size.buf, 4), yield);
485 BOOL success = TransmitFile(socket->native_handle(), in_file, 0, 0, &overlap,
nullptr, 0);
487 if(WSAGetLastError() == WSA_IO_PENDING) {
490 socket->async_write_some(boost::asio::null_buffers(), yield);
492 DWORD win_ec = GetLastError();
493 if (win_ec != ERROR_IO_PENDING && win_ec != ERROR_SUCCESS)
494 throw std::runtime_error(
"TransmitFile failed");
496 if(HasOverlappedIoCompleted(&overlap))
break;
499 throw std::runtime_error(
"TransmitFile failed");
525 boost::system::error_code ec;
526 async_read(*socket, boost::asio::buffer(data_size.buf, 4), yield[ec]);
528 uint32_t
size = ntohl(data_size.size);
533 "\treceived invalid packet with payload size 0";
539 "\treceived packet with payload size over size limit";
543 boost::shared_array<char> buffer{
new char[
size] };
544 async_read(*socket, boost::asio::buffer(buffer.get(),
size), yield[ec]);
549 return std::make_unique<simple_wml::document>(compressed_buf);
553 "\tsimple_wml error in received data: " << e.
message;
558 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<socket_ptr>(
socket_ptr socket, boost::asio::yield_context yield);
559 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<tls_socket_ptr>(
tls_socket_ptr socket, boost::asio::yield_context yield);
561 template<
class SocketPtr>
void server_base::send_doc_queued(SocketPtr socket, std::unique_ptr<simple_wml::document>& doc_ptr, boost::asio::yield_context yield)
563 static std::map<SocketPtr, std::queue<std::unique_ptr<simple_wml::document>>> queues;
565 queues[socket].push(std::move(doc_ptr));
566 if(queues[socket].
size() > 1) {
572 while(queues[socket].
size() > 0) {
581 io_service_, [
this, doc_ptr = doc.
clone(), socket](boost::asio::yield_context yield)
mutable {
591 if(*error_code !=
'\0') {
594 info_table_into_simple_wml(doc,
"error", info);
598 template void server_base::async_send_error<socket_ptr>(
socket_ptr socket,
const std::string&
msg,
const char* error_code,
const info_table&
info);
599 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);
605 if(*warning_code !=
'\0') {
606 doc.
child(
"warning")->
set_attr(
"warning_code", warning_code);
608 info_table_into_simple_wml(doc,
"warning", info);
612 template void server_base::async_send_warning<socket_ptr>(
socket_ptr socket,
const std::string&
msg,
const char* warning_code,
const info_table&
info);
613 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);
621 boost::asio::ssl::context::default_workarounds
622 | boost::asio::ssl::context::no_sslv2
623 | boost::asio::ssl::context::no_sslv3
624 | boost::asio::ssl::context::single_dh_use
627 tls_context_.use_certificate_chain_file(cfg[
"tls_fullchain"].str());
628 tls_context_.use_private_key_file(cfg[
"tls_private_key"].str(), boost::asio::ssl::context::pem);
629 if(!cfg[
"tls_dh"].str().empty())
tls_context_.use_tmp_dh_file(cfg[
"tls_dh"].str());
634 if(salt.length() < 12) {
635 ERR_SERVER <<
"Bad salt found for user: " << username;
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,
""");
651 for(std::string::size_type pos = 0; (pos = password.find(
'<', pos)) != std::string::npos; ++pos) {
652 password.replace(pos, 1,
"<");
654 for(std::string::size_type pos = 0; (pos = password.find(
'>', pos)) != std::string::npos; ++pos) {
655 password.replace(pos, 1,
">");
665 return hash.base64_digest();
667 ERR_SERVER <<
"bcrypt hash failed for user " << username <<
": " << err.
what();
671 ERR_SERVER <<
"Unable to determine how to hash the password for user: " << username;
boost::asio::ip::tcp::acceptor acceptor_v4_
node & add_child(const char *name)
virtual void handle_sighup(const boost::system::error_code &error, int signal_number)=0
template bool check_error< tls_socket_ptr >(const boost::system::error_code &error, tls_socket_ptr socket)
std::unique_ptr< simple_wml::document > coro_receive_doc(SocketPtr socket, boost::asio::yield_context yield)
Receive WML document from a coroutine.
string_span output_compressed(bool bzip2=false)
Interfaces for manipulating version numbers of engine, add-ons, etc.
static bool is_valid_prefix(const std::string &hash)
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 std::string base64_digest() const override
virtual void handle_new_client(socket_ptr socket)=0
boost::asio::signal_set sighup_
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
void async_send_error(SocketPtr socket, const std::string &msg, const char *error_code="", const info_table &info={})
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
node & set_attr(const char *key, const char *value)
boost::system::error_code ec
void handle_termination(const boost::system::error_code &error, int signal_number)
static bool is_valid_prefix(const std::string &hash)
static int get_iteration_count(const std::string &hash)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
void load_tls_config(const config &cfg)
Definitions for the interface to Wesnoth Markup Language (WML).
utils::variant< socket_ptr, tls_socket_ptr > any_socket_ptr
std::unique_ptr< document > clone()
node * child(const char *name)
const char * begin() const
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
boost::asio::streambuf admin_cmd_
void coro_send_file_userspace(SocketPtr socket, const std::string &filename, boost::asio::yield_context yield)
std::string log_address(SocketPtr socket)
static bcrypt from_salted_salt(const std::string &input)
boost::asio::io_service io_service_
void async_send_doc_queued(SocketPtr socket, simple_wml::document &doc)
High level wrapper for sending a WML document.
std::string client_address(SocketPtr socket)
boost::asio::ip::tcp::acceptor acceptor_v6_
virtual bool accepting_connections() const
const char * what() const noexcept
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
static std::size_t document_size_limit
virtual bool ip_exceeds_connection_limit(const std::string &) const
virtual void handle_read_from_fifo(const boost::system::error_code &error, std::size_t bytes_transferred)=0
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...
uint32_t handshake_response_
static lg::log_domain log_server("server")
node & set_attr_dup(const char *key, const char *value)
server_base(unsigned short port, bool keep_alive)
std::map< std::string, std::string > info_table
static map_location::DIRECTION s
void coro_send_doc(SocketPtr socket, simple_wml::document &doc, boost::asio::yield_context yield)
Send a WML document from within a coroutine.
std::string password(const std::string &server, const std::string &login)
static lg::log_domain log_config("config")
Declarations for File-IO.
virtual std::string is_ip_banned(const std::string &)
boost::asio::posix::stream_descriptor input_
Represents version numbers.
std::string deprecated_message(const std::string &, DEP_LEVEL, const version_info &, const std::string &)
int file_size(const std::string &fname)
Returns the size of a file, or -1 if the file doesn't exist.
#define ON_SCOPE_EXIT(...)
Run some arbitrary code (a lambda) when the current scope exits The lambda body follows this header...
std::shared_ptr< boost::asio::ssl::stream< socket_ptr::element_type > > tls_socket_ptr
bool check_error(const boost::system::error_code &error, SocketPtr socket)
void async_send_warning(SocketPtr socket, const std::string &msg, const char *warning_code="", const info_table &info={})
Standard logging facilities (interface).
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
static std::string get_salt(const std::string &hash)
static bcrypt hash_pw(const std::string &password, bcrypt &salt)
A config object defines a single node in a WML file, with access to child nodes.
static map_location::DIRECTION n
void send_doc_queued(SocketPtr socket, std::unique_ptr< simple_wml::document > &doc_ptr, boost::asio::yield_context yield)
boost::asio::ssl::context tls_context_
void serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor &acceptor, boost::asio::ip::tcp::endpoint endpoint)
Base class for servers using Wesnoth's WML over TCP protocol.
boost::asio::signal_set sigs_