The Battle for Wesnoth  1.17.0-dev
server_base.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2021
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  server_shutdown(const std::string& msg) : game::error(msg) {}
57 };
58 
59 /**
60  * Base class for implementing servers that use gzipped-WML network protocol
61  *
62  * The protocol is based on TCP connection between client and server.
63  * Before WML payloads can be sent a handshake is required. Handshake process is as follows:
64  * - client establishes a TCP connection to server.
65  * - client sends 32-bit integer(network byte order) representing protocol version requested.
66  * - server receives 32-bit integer. Depending on number received server does the following:
67  * 0: unencrypted protocol, send unspecified 32-bit integer for compatibility(current implementation sends 42)
68  * 1: depending on whether TLS is enabled on server
69  * if TLS enabled: send 32-bit integer 0 and immediately start TLS, client is expected to start TLS on receiving this 0
70  * if TLS disabled: send 32-bit integer 0xFFFFFFFF, on receiving this client should proceed as with unencrypted connection or immediately close
71  * any other number: server closes connection immediately
72  * - at this point handshake is completed and client and server can exchange WML messages
73  *
74  * Message format is as follows:
75  * - 32-bit unsigned integer(network byte order), this is size of the following payload
76  * - payload: gzipped WML data, which is WML text fed through gzip utility or the equivalent library function.
77  */
79 {
80 public:
81  server_base(unsigned short port, bool keep_alive);
82  virtual ~server_base() {}
83  void run();
84 
85  /**
86  * Send a WML document from within a coroutine
87  * @param socket
88  * @param doc
89  * @param yield The function will suspend on write operation using this yield context
90  */
91  template<class SocketPtr> void coro_send_doc(SocketPtr socket, simple_wml::document& doc, boost::asio::yield_context yield);
92  /**
93  * Send contents of entire file directly to socket from within a coroutine
94  * @param socket
95  * @param filename
96  * @param yield The function will suspend on write operations using this yield context
97  */
98  void coro_send_file(socket_ptr socket, const std::string& filename, boost::asio::yield_context yield);
99  void coro_send_file(tls_socket_ptr socket, const std::string& filename, boost::asio::yield_context yield);
100  /**
101  * Receive WML document from a coroutine
102  * @param socket
103  * @param yield The function will suspend on read operation using this yield context
104  */
105  template<class SocketPtr> std::unique_ptr<simple_wml::document> coro_receive_doc(SocketPtr socket, boost::asio::yield_context yield);
106 
107  /**
108  * High level wrapper for sending a WML document
109  *
110  * This function returns before send is finished. This function can be called again on same socket before previous send was finished.
111  * WML documents are kept in internal queue and sent in FIFO order.
112  * @param socket
113  * @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.
114  */
115  template<class SocketPtr> void async_send_doc_queued(SocketPtr socket, simple_wml::document& doc);
116 
117  typedef std::map<std::string, std::string> info_table;
118  template<class SocketPtr> void async_send_error(SocketPtr socket, const std::string& msg, const char* error_code = "", const info_table& info = {});
119  template<class SocketPtr> void async_send_warning(SocketPtr socket, const std::string& msg, const char* warning_code = "", const info_table& info = {});
120 
121  /**
122  * Create the poor security nonce for use with passwords still hashed with MD5.
123  * Uses 8 random integer digits, 29.8 bits entropy.
124  *
125  * @param length How many random numbers to generate.
126  * @return The nonce to use.
127  */
128  std::string create_unsecure_nonce(int length = 8);
129  /**
130  * Create a good security nonce for use with bcrypt/crypt_blowfish hashing.
131  * Uses 32 random Base64 characters, cryptographic-strength, 192 bits entropy
132  *
133  * @return The nonce to use.
134  */
135  std::string create_secure_nonce();
136  /**
137  * Handles hashing the password provided by the player before comparing it to the hashed password in the forum database.
138  *
139  * @param pw The plaintext password.
140  * @param salt The salt as retrieved from the forum database.
141  * @param username The player attempting to log in.
142  * @return The hashed password, or empty if the password couldn't be hashed.
143  */
144  std::string hash_password(const std::string& pw, const std::string& salt, const std::string& username);
145 
146 protected:
147  unsigned short port_;
149  boost::asio::io_service io_service_;
150  boost::asio::ssl::context tls_context_ { boost::asio::ssl::context::sslv23 };
151  bool tls_enabled_ { false };
152  boost::asio::ip::tcp::acceptor acceptor_v6_;
153  boost::asio::ip::tcp::acceptor acceptor_v4_;
154 
155  void load_tls_config(const config& cfg);
156 
157  void start_server();
158  void serve(boost::asio::yield_context yield, boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint);
159 
161 
162  virtual void handle_new_client(socket_ptr socket) = 0;
163  virtual void handle_new_client(tls_socket_ptr socket) = 0;
164 
165  virtual bool accepting_connections() const { return true; }
166  virtual std::string is_ip_banned(const std::string&) { return std::string(); }
167  virtual bool ip_exceeds_connection_limit(const std::string&) const { return false; }
168 
169 #ifndef _WIN32
170  boost::asio::posix::stream_descriptor input_;
171  std::string fifo_path_;
172  void read_from_fifo();
173  virtual void handle_read_from_fifo(const boost::system::error_code& error, std::size_t bytes_transferred) = 0;
174  boost::asio::streambuf admin_cmd_;
175 
176  boost::asio::signal_set sighup_;
177  virtual void handle_sighup(const boost::system::error_code& error, int signal_number) = 0;
178 #endif
179  boost::asio::signal_set sigs_;
180  void handle_termination(const boost::system::error_code& error, int signal_number);
181 };
182 
183 template<class SocketPtr> std::string client_address(SocketPtr socket);
184 template<class SocketPtr> std::string log_address(SocketPtr socket) { return (utils::decayed_is_same<tls_socket_ptr, decltype(socket)> ? "+" : "") + client_address(socket); }
185 template<class SocketPtr> bool check_error(const boost::system::error_code& error, SocketPtr socket);
boost::asio::ip::tcp::acceptor acceptor_v4_
server_shutdown(const std::string &msg)
Definition: server_base.hpp:56
boost::asio::signal_set sighup_
logger & info()
Definition: log.cpp:89
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
Base class for implementing servers that use gzipped-WML network protocol.
Definition: server_base.hpp:78
std::string client_address(SocketPtr socket)
unsigned short port_
utils::variant< socket_ptr, tls_socket_ptr > any_socket_ptr
Definition: server_base.hpp:52
boost::asio::streambuf admin_cmd_
std::string log_address(SocketPtr socket)
boost::asio::io_service io_service_
bool dump_wml
Definition: server_base.cpp:63
boost::asio::ip::tcp::acceptor acceptor_v6_
virtual bool accepting_connections() const
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed throgh std::decay first.
Definition: general.hpp:33
virtual bool ip_exceeds_connection_limit(const std::string &) const
virtual ~server_base()
Definition: server_base.hpp:82
uint32_t handshake_response_
bool check_error(const boost::system::error_code &error, SocketPtr socket)
std::map< std::string, std::string > info_table
virtual std::string is_ip_banned(const std::string &)
boost::asio::posix::stream_descriptor input_
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:28
std::string fifo_path_
std::shared_ptr< boost::asio::ssl::stream< socket_ptr::element_type > > tls_socket_ptr
Definition: server_base.hpp:51
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
Definition: server_base.hpp:48
MacOS doesn&#39;t support std::visit when targing MacOS < 10.14 (currently we target 10.11).
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
boost::asio::signal_set sigs_