From 0896b5c34ae438cc255b960caee7f7ef0d029d99 Mon Sep 17 00:00:00 2001 From: loonycyborg Date: Tue, 22 Dec 2020 15:11:13 +0300 Subject: [PATCH] Add TLS codepath to server_base --- SConstruct | 2 +- src/server/campaignd/server.cpp | 2 + src/server/common/server_base.cpp | 80 ++++++++++++++++++++++++------- src/server/common/server_base.hpp | 12 +++++ src/server/wesnothd/server.cpp | 8 ++++ src/server/wesnothd/server.hpp | 1 + 6 files changed, 87 insertions(+), 18 deletions(-) diff --git a/SConstruct b/SConstruct index 79782fe85b6a..f3098d2e4b7b 100755 --- a/SConstruct +++ b/SConstruct @@ -393,7 +393,7 @@ if env["prereqs"]: if(env["PLATFORM"] != 'darwin'): # Otherwise, use Security.framework - have_server_prereqs = have_server_prereqs & conf.CheckLib("libcrypto") + have_server_prereqs = have_server_prereqs & conf.CheckLib("libcrypto") & conf.CheckLib("ssl") env = conf.Finish() diff --git a/src/server/campaignd/server.cpp b/src/server/campaignd/server.cpp index aa5d598be0fc..699aea38f8e8 100644 --- a/src/server/campaignd/server.cpp +++ b/src/server/campaignd/server.cpp @@ -449,6 +449,8 @@ void server::load_config() } LOG_CS << "Loaded addons metadata. " << addons_.size() << " addons found.\n"; + + load_tls_config(cfg_); } std::ostream& operator<<(std::ostream& o, const server::request& r) diff --git a/src/server/common/server_base.cpp b/src/server/common/server_base.cpp index d71274042ce4..dc319b5086c8 100644 --- a/src/server/common/server_base.cpp +++ b/src/server/common/server_base.cpp @@ -15,6 +15,7 @@ #include "server/common/server_base.hpp" #include "log.hpp" +#include "serialization/parser.hpp" #include "filesystem.hpp" #ifdef HAVE_CONFIG_H @@ -98,6 +99,7 @@ void server_base::serve(boost::asio::yield_context yield, boost::asio::ip::tcp:: } socket_ptr socket = std::make_shared(io_service_); + bool use_tls { false }; boost::system::error_code error; acceptor.async_accept(socket->lowest_layer(), yield[error]); @@ -128,30 +130,57 @@ void server_base::serve(boost::asio::yield_context yield, boost::asio::ip::tcp:: DBG_SERVER << client_address(socket) << "\tnew connection tentatively accepted\n"; - boost::shared_array handshake(new char[4]); - async_read(*socket, boost::asio::buffer(handshake.get(), 4), yield[error]); + union { + uint32_t number; + char buf[4]; + } protocol_version; + + async_read(*socket, boost::asio::buffer(protocol_version.buf), yield[error]); if(check_error(error, socket)) return; - if(memcmp(handshake.get(), "\0\0\0\0", 4) != 0) { - ERR_SERVER << client_address(socket) << "\tincorrect handshake\n"; - return; - } + switch(ntohl(protocol_version.number)) { + case 0: + async_write(*socket, boost::asio::buffer(handshake_response_.buf, 4), yield[error]); + if(check_error(error, socket)) return; + break; + case 1: + if(!tls_enabled_) { + ERR_SERVER << client_address(socket) << "\tTLS requested by client but not enabled on server\n"; + async_send_error(socket, "TLS support disabled on server."); + return; + } + use_tls = true; - async_write(*socket, boost::asio::buffer(handshake_response_.buf, 4), yield[error]); + break; + default: + ERR_SERVER << client_address(socket) << "\tincorrect handshake\n"; + return; + } - if(!check_error(error, socket)) { - const std::string ip = client_address(socket); + const std::string ip = client_address(socket); - const std::string reason = is_ip_banned(ip); - if (!reason.empty()) { - LOG_SERVER << ip << "\trejected banned user. Reason: " << reason << "\n"; - async_send_error(socket, "You are banned. Reason: " + reason); - return; - } else if (ip_exceeds_connection_limit(ip)) { - LOG_SERVER << ip << "\trejected ip due to excessive connections\n"; - async_send_error(socket, "Too many connections from your IP."); + const std::string reason = is_ip_banned(ip); + if (!reason.empty()) { + LOG_SERVER << ip << "\trejected banned user. Reason: " << reason << "\n"; + async_send_error(socket, "You are banned. Reason: " + reason); return; + } else if (ip_exceeds_connection_limit(ip)) { + LOG_SERVER << ip << "\trejected ip due to excessive connections\n"; + async_send_error(socket, "Too many connections from your IP."); + return; + } else { + if(use_tls) { + async_send_warning(socket, "Go TLS."); + tls_socket_ptr tls_socket { new tls_socket_ptr::element_type(std::move(*socket), tls_context_) }; + tls_socket->async_handshake(boost::asio::ssl::stream_base::server, yield[error]); + if(error) { + ERR_SERVER << "TLS handshake failed: " << error.message() << "\n"; + return; + } + + DBG_SERVER << ip << "\tnew encrypted connection fully accepted\n"; + this->handle_new_client(tls_socket); } else { DBG_SERVER << ip << "\tnew connection fully accepted\n"; this->handle_new_client(socket); @@ -473,6 +502,23 @@ void server_base::async_send_warning(socket_ptr socket, const std::string& msg, async_send_doc_queued(socket, doc); } +void server_base::load_tls_config(const config& cfg) +{ + tls_enabled_ = cfg["tls_enabled"].to_bool(false); + if(!tls_enabled_) return; + + tls_context_.set_options( + boost::asio::ssl::context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::no_sslv3 + | boost::asio::ssl::context::single_dh_use + ); + + tls_context_.use_certificate_chain_file(cfg["tls_fullchain"].str()); + tls_context_.use_private_key_file(cfg["tls_private_key"].str(), boost::asio::ssl::context::pem); + if(!cfg["tls_dh"].str().empty()) tls_context_.use_tmp_dh_file(cfg["tls_dh"].str()); +} + // This is just here to get it to build without the deprecation_message function #include "game_version.hpp" #include "deprecation.hpp" diff --git a/src/server/common/server_base.hpp b/src/server/common/server_base.hpp index 3cea4b238ac8..2be065f15835 100644 --- a/src/server/common/server_base.hpp +++ b/src/server/common/server_base.hpp @@ -33,14 +33,20 @@ #endif #include #include +#include #include #include +#include #include extern bool dump_wml; +class config; + typedef std::shared_ptr socket_ptr; +typedef std::shared_ptr> tls_socket_ptr; +typedef utils::variant any_socket_ptr; struct server_shutdown : public game::error { @@ -93,8 +99,13 @@ class server_base unsigned short port_; bool keep_alive_; boost::asio::io_service io_service_; + boost::asio::ssl::context tls_context_ { boost::asio::ssl::context::sslv23 }; + bool tls_enabled_ { false }; boost::asio::ip::tcp::acceptor acceptor_v6_; boost::asio::ip::tcp::acceptor acceptor_v4_; + + void load_tls_config(const config& cfg); + void start_server(); void serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint); @@ -104,6 +115,7 @@ class server_base } handshake_response_; virtual void handle_new_client(socket_ptr socket) = 0; + virtual void handle_new_client(tls_socket_ptr socket) = 0; virtual bool accepting_connections() const { return true; } virtual std::string is_ip_banned(const std::string&) { return std::string(); } diff --git a/src/server/wesnothd/server.cpp b/src/server/wesnothd/server.cpp index 9f504135d3a8..3534a5818a66 100644 --- a/src/server/wesnothd/server.cpp +++ b/src/server/wesnothd/server.cpp @@ -529,6 +529,8 @@ void server::load_config() tournaments_ = user_handler_->get_tournaments(); } #endif + + load_tls_config(cfg_); } bool server::ip_exceeds_connection_limit(const std::string& ip) const @@ -607,6 +609,12 @@ void server::handle_new_client(socket_ptr socket) boost::asio::spawn(io_service_, [socket, this](boost::asio::yield_context yield) { login_client(yield, socket); }); } +void server::handle_new_client(tls_socket_ptr /*socket*/) +{ + //boost::asio::spawn(io_service_, [socket, this](boost::asio::yield_context yield) { login_client(yield, socket); }); + throw std::runtime_error("Not implemented"); +} + void server::login_client(boost::asio::yield_context yield, socket_ptr socket) { boost::system::error_code ec; diff --git a/src/server/wesnothd/server.hpp b/src/server/wesnothd/server.hpp index 1ff02683ebb4..68e1db2f0ba8 100644 --- a/src/server/wesnothd/server.hpp +++ b/src/server/wesnothd/server.hpp @@ -38,6 +38,7 @@ class server : public server_base private: void handle_new_client(socket_ptr socket); + void handle_new_client(tls_socket_ptr socket); void login_client(boost::asio::yield_context yield, socket_ptr socket); bool is_login_allowed(socket_ptr socket, const simple_wml::node* const login, const std::string& username, bool& registered, bool& is_moderator);