The Battle for Wesnoth  1.19.7+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 #include "utils/optional_fwd.hpp"
29 
30 #ifdef _WIN32
32 #endif
33 
34 #include <boost/asio/io_context.hpp>
35 #include <boost/asio/ip/tcp.hpp>
36 #ifndef _WIN32
37 #include <boost/asio/posix/stream_descriptor.hpp>
38 #endif
39 #include <boost/asio/signal_set.hpp>
40 #include <boost/asio/streambuf.hpp>
41 #include <boost/asio/ssl.hpp>
42 #include <boost/asio/spawn.hpp>
43 #include <boost/shared_array.hpp>
44 
45 #include <map>
46 
47 extern bool dump_wml;
48 
49 class config;
50 
51 typedef std::shared_ptr<boost::asio::ip::tcp::socket> socket_ptr;
52 typedef std::shared_ptr<boost::asio::ssl::stream<socket_ptr::element_type>> tls_socket_ptr;
53 typedef utils::variant<socket_ptr, tls_socket_ptr> any_socket_ptr;
54 
56 {
57  boost::system::error_code ec;
58  server_shutdown(const std::string& msg, boost::system::error_code ec = {}) : game::error(msg), ec(ec) {}
59 };
60 
61 /**
62  * Base class for implementing servers that use gzipped-WML network protocol
63  *
64  * The protocol is based on TCP connection between client and server.
65  * Before WML payloads can be sent a handshake is required. Handshake process is as follows:
66  * - client establishes a TCP connection to server.
67  * - client sends 32-bit integer(network byte order) representing protocol version requested.
68  * - server receives 32-bit integer. Depending on number received server does the following:
69  * 0: unencrypted protocol, send unspecified 32-bit integer for compatibility(current implementation sends 42)
70  * 1: depending on whether TLS is enabled on server
71  * if TLS enabled: send 32-bit integer 0 and immediately start TLS, client is expected to start TLS on receiving this 0
72  * if TLS disabled: send 32-bit integer 0xFFFFFFFF, on receiving this client should proceed as with unencrypted connection or immediately close
73  * any other number: server closes connection immediately
74  * - at this point handshake is completed and client and server can exchange WML messages
75  *
76  * Message format is as follows:
77  * - 32-bit unsigned integer(network byte order), this is size of the following payload
78  * - payload: gzipped WML data, which is WML text fed through gzip utility or the equivalent library function.
79  */
81 {
82  template<class SocketPtr> void send_doc_queued(SocketPtr socket, std::unique_ptr<simple_wml::document>& doc_ptr, boost::asio::yield_context yield);
83 
84 public:
85  server_base(unsigned short port, bool keep_alive);
86  virtual ~server_base() {}
87  int run();
88 
89  /**
90  * Send a WML document from within a coroutine
91  * @param socket
92  * @param doc
93  * @param yield The function will suspend on write operation using this yield context
94  */
95  template<class SocketPtr> void coro_send_doc(SocketPtr socket, simple_wml::document& doc, const boost::asio::yield_context& yield);
96  /**
97  * Send contents of entire file directly to socket from within a coroutine
98  * @param socket
99  * @param filename
100  * @param yield The function will suspend on write operations using this yield context
101  */
102  void coro_send_file(const socket_ptr& socket, const std::string& filename, const boost::asio::yield_context& yield);
103  void coro_send_file(tls_socket_ptr socket, const std::string& filename, const boost::asio::yield_context& yield);
104  /**
105  * Receive WML document from a coroutine
106  * @param socket
107  * @param yield The function will suspend on read operation using this yield context
108  * @return unique_ptr with doc deceived. In case of error empty unique_ptr
109  */
110  template<class SocketPtr> std::unique_ptr<simple_wml::document> coro_receive_doc(SocketPtr socket, const boost::asio::yield_context& yield);
111 
112  /**
113  * High level wrapper for sending a WML document
114  *
115  * This function returns before send is finished. This function can be called again on same socket before previous send was finished.
116  * WML documents are kept in internal queue and sent in FIFO order.
117  * @param socket
118  * @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.
119  */
120  template<class SocketPtr> void async_send_doc_queued(SocketPtr socket, simple_wml::document& doc);
121 
122  typedef std::map<std::string, std::string> info_table;
123  template<class SocketPtr> void async_send_error(SocketPtr socket, const std::string& msg, const char* error_code = "", const info_table& info = {});
124  template<class SocketPtr> void async_send_warning(SocketPtr socket, const std::string& msg, const char* warning_code = "", const info_table& info = {});
125 
126  /**
127  * Handles hashing the password provided by the player before comparing it to the hashed password in the forum database.
128  *
129  * @param pw The plaintext password.
130  * @param salt The salt as retrieved from the forum database.
131  * @param username The player attempting to log in.
132  * @return The hashed password, or empty if the password couldn't be hashed.
133  */
134  std::string hash_password(const std::string& pw, const std::string& salt, const std::string& username);
135 
136 protected:
137  unsigned short port_;
139  boost::asio::io_context io_service_;
140  boost::asio::ssl::context tls_context_ { boost::asio::ssl::context::sslv23 };
141  bool tls_enabled_ { false };
142  boost::asio::ip::tcp::acceptor acceptor_v6_;
143  boost::asio::ip::tcp::acceptor acceptor_v4_;
144 
145  void load_tls_config(const config& cfg);
146 
147  void start_server();
148  void serve(const boost::asio::yield_context& yield, boost::asio::ip::tcp::acceptor& acceptor, const boost::asio::ip::tcp::endpoint& endpoint);
149 
151 
152  virtual void handle_new_client(socket_ptr socket) = 0;
153  virtual void handle_new_client(tls_socket_ptr socket) = 0;
154 
155  virtual bool accepting_connections() const { return true; }
156  virtual bool ip_exceeds_connection_limit(const std::string&) const { return false; }
157 
159  {
160  const char* error_code;
161  std::string reason;
162  utils::optional<std::chrono::seconds> time_remaining;
163  };
164 
165  virtual utils::optional<login_ban_info> is_ip_banned(const std::string&) { return {}; }
166 
167 #ifndef _WIN32
168  boost::asio::posix::stream_descriptor input_;
169  std::string fifo_path_;
170  void read_from_fifo();
171  virtual void handle_read_from_fifo(const boost::system::error_code& error, std::size_t bytes_transferred) = 0;
172  boost::asio::streambuf admin_cmd_;
173 
174  boost::asio::signal_set sighup_;
175  virtual void handle_sighup(const boost::system::error_code& error, int signal_number) = 0;
176 #endif
177 };
178 
179 template<class SocketPtr> std::string client_address(SocketPtr socket);
180 template<class SocketPtr> std::string log_address(SocketPtr socket) { return (utils::decayed_is_same<tls_socket_ptr, decltype(socket)> ? "+" : "") + client_address(socket); }
181 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:172
Base class for implementing servers that use gzipped-WML network protocol.
Definition: server_base.hpp:81
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
virtual ~server_base()
Definition: server_base.hpp:86
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)
virtual void handle_new_client(tls_socket_ptr socket)=0
boost::asio::io_context io_service_
unsigned short port_
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()
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)
Definition: server_base.cpp:64
void start_server()
Definition: server_base.cpp:78
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::string fifo_path_
logger & info()
Definition: log.cpp:319
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition: general.hpp:30
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:52
bool check_error(const boost::system::error_code &error, SocketPtr socket)
std::string client_address(SocketPtr socket)
bool dump_wml
Definition: server_base.cpp:62
std::string log_address(SocketPtr socket)
utils::variant< socket_ptr, tls_socket_ptr > any_socket_ptr
Definition: server_base.hpp:53
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
Definition: server_base.hpp:49
std::string filename
Filename.
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
utils::optional< std::chrono::seconds > time_remaining
boost::system::error_code ec
Definition: server_base.hpp:57
server_shutdown(const std::string &msg, boost::system::error_code ec={})
Definition: server_base.hpp:58
MacOS doesn't support std::visit when targing MacOS < 10.14 (currently we target 10....