The Battle for Wesnoth  1.19.0-dev
server_base.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2024
3  by Sergey Popov <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Base class for servers using Wesnoth's WML over TCP protocol.
19  */
20 
21 #pragma once
22 
23 #include "exceptions.hpp"
25 
26 #include "utils/variant.hpp"
27 #include "utils/general.hpp"
28 
29 #ifdef _WIN32
31 #endif
32 
33 #include <boost/asio/io_service.hpp>
34 #include <boost/asio/ip/tcp.hpp>
35 #ifndef _WIN32
36 #include <boost/asio/posix/stream_descriptor.hpp>
37 #endif
38 #include <boost/asio/signal_set.hpp>
39 #include <boost/asio/streambuf.hpp>
40 #include <boost/asio/ssl.hpp>
41 #include <boost/asio/spawn.hpp>
42 #include <boost/shared_array.hpp>
43 
44 #include <map>
45 
46 extern bool dump_wml;
47 
48 class config;
49 
50 typedef std::shared_ptr<boost::asio::ip::tcp::socket> socket_ptr;
51 typedef std::shared_ptr<boost::asio::ssl::stream<socket_ptr::element_type>> tls_socket_ptr;
52 typedef utils::variant<socket_ptr, tls_socket_ptr> any_socket_ptr;
53 
55 {
56  boost::system::error_code ec;
57  server_shutdown(const std::string& msg, boost::system::error_code ec = {}) : game::error(msg), ec(ec) {}
58 };
59 
60 /**
61  * Base class for implementing servers that use gzipped-WML network protocol
62  *
63  * The protocol is based on TCP connection between client and server.
64  * Before WML payloads can be sent a handshake is required. Handshake process is as follows:
65  * - client establishes a TCP connection to server.
66  * - client sends 32-bit integer(network byte order) representing protocol version requested.
67  * - server receives 32-bit integer. Depending on number received server does the following:
68  * 0: unencrypted protocol, send unspecified 32-bit integer for compatibility(current implementation sends 42)
69  * 1: depending on whether TLS is enabled on server
70  * if TLS enabled: send 32-bit integer 0 and immediately start TLS, client is expected to start TLS on receiving this 0
71  * if TLS disabled: send 32-bit integer 0xFFFFFFFF, on receiving this client should proceed as with unencrypted connection or immediately close
72  * any other number: server closes connection immediately
73  * - at this point handshake is completed and client and server can exchange WML messages
74  *
75  * Message format is as follows:
76  * - 32-bit unsigned integer(network byte order), this is size of the following payload
77  * - payload: gzipped WML data, which is WML text fed through gzip utility or the equivalent library function.
78  */
80 {
81  template<class SocketPtr> void send_doc_queued(SocketPtr socket, std::unique_ptr<simple_wml::document>& doc_ptr, boost::asio::yield_context yield);
82 
83 public:
84  server_base(unsigned short port, bool keep_alive);
85  virtual ~server_base() {}
86  int run();
87 
88  /**
89  * Send a WML document from within a coroutine
90  * @param socket
91  * @param doc
92  * @param yield The function will suspend on write operation using this yield context
93  */
94  template<class SocketPtr> void coro_send_doc(SocketPtr socket, simple_wml::document& doc, boost::asio::yield_context yield);
95  /**
96  * Send contents of entire file directly to socket from within a coroutine
97  * @param socket
98  * @param filename
99  * @param yield The function will suspend on write operations using this yield context
100  */
101  void coro_send_file(socket_ptr socket, const std::string& filename, boost::asio::yield_context yield);
102  void coro_send_file(tls_socket_ptr socket, const std::string& filename, boost::asio::yield_context yield);
103  /**
104  * Receive WML document from a coroutine
105  * @param socket
106  * @param yield The function will suspend on read operation using this yield context
107  * @return unique_ptr with doc deceived. In case of error empty unique_ptr
108  */
109  template<class SocketPtr> std::unique_ptr<simple_wml::document> coro_receive_doc(SocketPtr socket, boost::asio::yield_context yield);
110 
111  /**
112  * High level wrapper for sending a WML document
113  *
114  * This function returns before send is finished. This function can be called again on same socket before previous send was finished.
115  * WML documents are kept in internal queue and sent in FIFO order.
116  * @param socket
117  * @param doc Document to send. A copy of it will be made so there is no need to keep the reference live after the function returns.
118  */
119  template<class SocketPtr> void async_send_doc_queued(SocketPtr socket, simple_wml::document& doc);
120 
121  typedef std::map<std::string, std::string> info_table;
122  template<class SocketPtr> void async_send_error(SocketPtr socket, const std::string& msg, const char* error_code = "", const info_table& info = {});
123  template<class SocketPtr> void async_send_warning(SocketPtr socket, const std::string& msg, const char* warning_code = "", const info_table& info = {});
124 
125  /**
126  * Create the poor security nonce for use with passwords still hashed with MD5.
127  * Uses 8 random integer digits, 29.8 bits entropy.
128  *
129  * @param length How many random numbers to generate.
130  * @return The nonce to use.
131  */
132  std::string create_unsecure_nonce(int length = 8);
133  /**
134  * Create a good security nonce for use with bcrypt/crypt_blowfish hashing.
135  * Uses 32 random Base64 characters, cryptographic-strength, 192 bits entropy
136  *
137  * @return The nonce to use.
138  */
139  std::string create_secure_nonce();
140  /**
141  * Handles hashing the password provided by the player before comparing it to the hashed password in the forum database.
142  *
143  * @param pw The plaintext password.
144  * @param salt The salt as retrieved from the forum database.
145  * @param username The player attempting to log in.
146  * @return The hashed password, or empty if the password couldn't be hashed.
147  */
148  std::string hash_password(const std::string& pw, const std::string& salt, const std::string& username);
149 
150 protected:
151  unsigned short port_;
153  boost::asio::io_service io_service_;
154  boost::asio::ssl::context tls_context_ { boost::asio::ssl::context::sslv23 };
155  bool tls_enabled_ { false };
156  boost::asio::ip::tcp::acceptor acceptor_v6_;
157  boost::asio::ip::tcp::acceptor acceptor_v4_;
158 
159  void load_tls_config(const config& cfg);
160 
161  void start_server();
162  void serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint);
163 
165 
166  virtual void handle_new_client(socket_ptr socket) = 0;
167  virtual void handle_new_client(tls_socket_ptr socket) = 0;
168 
169  virtual bool accepting_connections() const { return true; }
170  virtual std::string is_ip_banned(const std::string&) { return std::string(); }
171  virtual bool ip_exceeds_connection_limit(const std::string&) const { return false; }
172 
173 #ifndef _WIN32
174  boost::asio::posix::stream_descriptor input_;
175  std::string fifo_path_;
176  void read_from_fifo();
177  virtual void handle_read_from_fifo(const boost::system::error_code& error, std::size_t bytes_transferred) = 0;
178  boost::asio::streambuf admin_cmd_;
179 
180  boost::asio::signal_set sighup_;
181  virtual void handle_sighup(const boost::system::error_code& error, int signal_number) = 0;
182 #endif
183 };
184 
185 template<class SocketPtr> std::string client_address(SocketPtr socket);
186 template<class SocketPtr> std::string log_address(SocketPtr socket) { return (utils::decayed_is_same<tls_socket_ptr, decltype(socket)> ? "+" : "") + client_address(socket); }
187 template<class SocketPtr> bool check_error(const boost::system::error_code& error, SocketPtr socket);
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
Base class for implementing servers that use gzipped-WML network protocol.
Definition: server_base.hpp:80
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
virtual ~server_base()
Definition: server_base.hpp:85
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
std::string create_secure_nonce()
Create a good security nonce for use with bcrypt/crypt_blowfish hashing.
void send_doc_queued(SocketPtr socket, std::unique_ptr< simple_wml::document > &doc_ptr, boost::asio::yield_context yield)
virtual void handle_new_client(tls_socket_ptr socket)=0
void coro_send_doc(SocketPtr socket, simple_wml::document &doc, boost::asio::yield_context yield)
Send a WML document from within a coroutine.
unsigned short port_
void coro_send_file(tls_socket_ptr socket, const std::string &filename, boost::asio::yield_context yield)
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 read_from_fifo()
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 &)
std::string create_unsecure_nonce(int length=8)
Create the poor security nonce for use with passwords still hashed with MD5.
server_base(unsigned short port, bool keep_alive)
Definition: server_base.cpp:60
void start_server()
Definition: server_base.cpp:74
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)
Definition: server_base.cpp:91
void load_tls_config(const config &cfg)
boost::asio::ssl::context tls_context_
boost::asio::ip::tcp::acceptor acceptor_v6_
std::string fifo_path_
logger & info()
Definition: log.cpp:314
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition: general.hpp:33
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::shared_ptr< boost::asio::ssl::stream< socket_ptr::element_type > > tls_socket_ptr
Definition: server_base.hpp:51
bool check_error(const boost::system::error_code &error, SocketPtr socket)
std::string client_address(SocketPtr socket)
bool dump_wml
Definition: server_base.cpp:58
std::string log_address(SocketPtr socket)
utils::variant< socket_ptr, tls_socket_ptr > any_socket_ptr
Definition: server_base.hpp:52
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
Definition: server_base.hpp:48
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
boost::system::error_code ec
Definition: server_base.hpp:56
server_shutdown(const std::string &msg, boost::system::error_code ec={})
Definition: server_base.hpp:57
MacOS doesn't support std::visit when targing MacOS < 10.14 (currently we target 10....