The Battle for Wesnoth  1.19.13+dev
server_base.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2025
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 
17 
18 #include "config.hpp"
19 #include "hash.hpp"
20 #include "log.hpp"
21 #include "filesystem.hpp"
22 #include "utils/scope_exit.hpp"
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #ifdef HAVE_SENDFILE
29 #include <sys/sendfile.h>
30 #endif
31 
32 #ifdef _WIN32
33 #include <windows.h>
34 #include <boost/scope_exit.hpp>
35 #endif
36 
37 #include <boost/asio/ip/v6_only.hpp>
38 #include <boost/asio/read.hpp>
39 #ifndef _WIN32
40 #include <boost/asio/read_until.hpp>
41 #endif
42 #ifndef BOOST_NO_EXCEPTIONS
43 #include <boost/exception/diagnostic_information.hpp>
44 #endif
45 
46 #include <iostream>
47 #include <queue>
48 #include <string>
49 #include <utility>
50 
51 
52 static lg::log_domain log_server("server");
53 #define ERR_SERVER LOG_STREAM(err, log_server)
54 #define WRN_SERVER LOG_STREAM(warn, log_server)
55 #define LOG_SERVER LOG_STREAM(info, log_server)
56 #define DBG_SERVER LOG_STREAM(debug, log_server)
57 
58 static lg::log_domain log_config("config");
59 #define ERR_CONFIG LOG_STREAM(err, log_config)
60 #define WRN_CONFIG LOG_STREAM(warn, log_config)
61 
62 bool dump_wml = false;
63 
64 server_base::server_base(unsigned short port, bool keep_alive)
65  : port_(port)
66  , keep_alive_(keep_alive)
67  , io_service_()
68  , acceptor_v6_(io_service_)
69  , acceptor_v4_(io_service_)
70  , handshake_response_()
71 #ifndef _WIN32
72  , input_(io_service_)
73  , sighup_(io_service_, SIGHUP)
74 #endif
75 {
76 }
77 
79 {
80  boost::asio::ip::tcp::endpoint endpoint_v6(boost::asio::ip::tcp::v6(), port_);
81  boost::asio::spawn(io_service_, [this, endpoint_v6](const boost::asio::yield_context& yield) { serve(yield, acceptor_v6_, endpoint_v6); }
82 #if BOOST_VERSION >= 108000
83  , [](const std::exception_ptr& e) { if (e) std::rethrow_exception(e); }
84 #endif
85  );
86 
87  boost::asio::ip::tcp::endpoint endpoint_v4(boost::asio::ip::tcp::v4(), port_);
88  boost::asio::spawn(io_service_, [this, endpoint_v4](const boost::asio::yield_context& yield) { serve(yield, acceptor_v4_, endpoint_v4); }
89 #if BOOST_VERSION >= 108000
90  , [](const std::exception_ptr& e) { if (e) std::rethrow_exception(e); }
91 #endif
92  );
93 
94  handshake_response_ = htonl(42);
95 
96 #ifndef _WIN32
97  sighup_.async_wait(
98  [this](const boost::system::error_code& error, int sig)
99  { this->handle_sighup(error, sig); });
100 #endif
101 }
102 
103 void server_base::serve(const boost::asio::yield_context& yield, boost::asio::ip::tcp::acceptor& acceptor, const boost::asio::ip::tcp::endpoint& endpoint)
104 {
105  try {
106  if(!acceptor.is_open()) {
107  acceptor.open(endpoint.protocol());
108  acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
109  acceptor.set_option(boost::asio::ip::tcp::acceptor::keep_alive(keep_alive_));
110  if(endpoint.protocol() == boost::asio::ip::tcp::v6())
111  acceptor.set_option(boost::asio::ip::v6_only(true));
112  acceptor.bind(endpoint);
113  acceptor.listen();
114  }
115  } catch(const boost::system::system_error& e) {
116  if(endpoint.protocol() == boost::asio::ip::tcp::v6() && e.code() == boost::asio::error::address_family_not_supported) {
117  WRN_SERVER << "Failed to bind port with IPv6 because IPv6 is not supported. Will try with IPv4 too.";
118  return;
119  } else {
120  ERR_SERVER << "Exception when trying to bind port: " << e.code().message();
121  BOOST_THROW_EXCEPTION(server_shutdown("Port binding failed", e.code()));
122  }
123  }
124 
125  socket_ptr socket = std::make_shared<socket_ptr::element_type>(io_service_);
126 
127  boost::system::error_code error;
128  acceptor.async_accept(socket->lowest_layer(), yield[error]);
129  if(error && accepting_connections()) {
130  ERR_SERVER << "Accept failed: " << error.message();
131  BOOST_THROW_EXCEPTION(server_shutdown("Accept failed", error));
132  }
133 
134  if(accepting_connections()) {
135  boost::asio::spawn(io_service_, [this, &acceptor, endpoint](const boost::asio::yield_context& yield) { serve(yield, acceptor, endpoint); }
136 #if BOOST_VERSION >= 108000
137  , [](const std::exception_ptr& e) { if (e) std::rethrow_exception(e); }
138 #endif
139  );
140  } else {
141  return;
142  }
143 
144  if(keep_alive_) {
145  int timeout = 30;
146 #ifdef __linux__
147  int cnt = 10;
148  int interval = 30;
149  setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPIDLE, &timeout, sizeof(timeout));
150  setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
151  setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
152 #elif defined(__APPLE__) && defined(__MACH__)
153  setsockopt(socket->native_handle(), IPPROTO_TCP, TCP_KEEPALIVE, &timeout, sizeof(timeout));
154 #elif defined(_WIN32)
155  // these are in milliseconds for windows
156  DWORD timeout_ms = timeout * 1000;
157  setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout_ms), sizeof(timeout_ms));
158  setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&timeout_ms), sizeof(timeout_ms));
159 #endif
160  }
161 
162 #ifdef __linux__
163  fcntl(socket->native_handle(), F_SETFD, FD_CLOEXEC);
164 #endif
165 
166  DBG_SERVER << client_address(socket) << "\tnew connection tentatively accepted";
167 
168  uint32_t protocol_version;
169  uint32_t handshake_response;
170 
171  any_socket_ptr final_socket;
172 
173  async_read(*socket, boost::asio::buffer(reinterpret_cast<std::byte*>(&protocol_version), 4), yield[error]);
174  if(check_error(error, socket))
175  return;
176 
177  switch(ntohl(protocol_version)) {
178  case 0:
179  async_write(*socket, boost::asio::buffer(reinterpret_cast<std::byte*>(&handshake_response_), 4), yield[error]);
180  if(check_error(error, socket)) return;
181  final_socket = socket;
182  break;
183  case 1:
184  if(!tls_enabled_) {
185  ERR_SERVER << client_address(socket) << "\tTLS requested by client but not enabled on server";
186  handshake_response = 0xFFFFFFFFU;
187  } else {
188  handshake_response = 0x00000000;
189  }
190 
191  async_write(*socket, boost::asio::buffer(reinterpret_cast<const std::byte*>(&handshake_response), 4), yield[error]);
192  if(check_error(error, socket)) return;
193  if(!tls_enabled_) { // continue with unencrypted connection if TLS disabled
194  final_socket = socket;
195  break;
196  }
197 
198  final_socket = tls_socket_ptr { new tls_socket_ptr::element_type(std::move(*socket), tls_context_) };
199  utils::get<tls_socket_ptr>(final_socket)->async_handshake(boost::asio::ssl::stream_base::server, yield[error]);
200  if(error) {
201  ERR_SERVER << "TLS handshake failed: " << error.message();
202  return;
203  }
204 
205  break;
206  default:
207  ERR_SERVER << client_address(socket) << "\tincorrect handshake";
208  return;
209  }
210 
211  utils::visit(
212  [this](auto&& socket) {
213  const std::string ip = client_address(socket);
214 
215  if(const auto ban_info = is_ip_banned(ip)) {
216  const auto& [error_code, reason, time_remaining] = *ban_info;
217  LOG_SERVER << log_address(socket) << "\trejected banned user. Reason: " << reason;
218 
219  if(time_remaining) {
220  // Temporary ban
221  async_send_error(socket, "You are banned from this server: " + reason, error_code,
222  {{ "duration", std::to_string(time_remaining->count()) }});
223  } else {
224  // Permanent ban
225  async_send_error(socket, "You are banned from this server: " + reason, error_code);
226  }
227 
228  return;
229  }
230 
232  LOG_SERVER << log_address(socket) << "\trejected ip due to excessive connections";
233  async_send_error(socket, "Too many connections from your IP.");
234  return;
235  }
236 
237  if constexpr(utils::decayed_is_same<tls_socket_ptr, decltype(socket)>) {
238  DBG_SERVER << ip << "\tnew encrypted connection fully accepted";
239  } else {
240  DBG_SERVER << ip << "\tnew connection fully accepted";
241  }
242  this->handle_new_client(socket);
243  },
244  final_socket);
245 }
246 
247 #ifndef _WIN32
249  async_read_until(input_,
250  admin_cmd_, '\n',
251  [this](const boost::system::error_code& error, std::size_t bytes_transferred)
252  { this->handle_read_from_fifo(error, bytes_transferred); }
253  );
254 }
255 #endif
256 
258  for(;;) {
259  try {
260  io_service_.run();
261  LOG_SERVER << "Server has shut down because event loop is out of work";
262  return 1;
263  } catch(const server_shutdown& e) {
264  LOG_SERVER << "Server has been shut down: " << e.what();
265  return e.ec.value();
266  } catch(const boost::system::system_error& e) {
267  ERR_SERVER << "Caught system error exception from handler: " << e.code().message();
268  } catch(const std::exception& e) {
269  ERR_SERVER << "Caught exception from handler: " << e.what() << "\n" << boost::current_exception_diagnostic_information();
270  }
271  }
272 }
273 
274 template<class SocketPtr> std::string client_address(SocketPtr socket)
275 {
276  boost::system::error_code error;
277  std::string result = socket->lowest_layer().remote_endpoint(error).address().to_string();
278  if(error)
279  return "<unknown address>";
280  else
281  return result;
282 }
283 
284 template<class SocketPtr> bool check_error(const boost::system::error_code& error, const SocketPtr& socket)
285 {
286  if(error) {
287  if(error == boost::asio::error::eof)
288  LOG_SERVER << log_address(socket) << "\tconnection closed";
289  else
290  ERR_SERVER << log_address(socket) << "\t" << error.message();
291  return true;
292  }
293  return false;
294 }
295 template bool check_error<tls_socket_ptr>(const boost::system::error_code& error, const tls_socket_ptr& socket);
296 
297 namespace {
298 
299 void info_table_into_simple_wml(simple_wml::document& doc, const std::string& parent_name, const server_base::info_table& info)
300 {
301  if(info.empty()) {
302  return;
303  }
304 
305  auto& node = doc.child(parent_name.c_str())->add_child("data");
306  for(const auto& kv : info) {
307  node.set_attr_dup(kv.first.c_str(), kv.second.c_str());
308  }
309 }
310 
311 }
312 
313 /**
314  * Send a WML document from within a coroutine
315  * @param socket
316  * @param doc
317  * @param yield The function will suspend on write operation using this yield context
318  */
319 template<class SocketPtr> void server_base::coro_send_doc(const SocketPtr& socket, simple_wml::document& doc, const boost::asio::yield_context& yield)
320 {
321  if(dump_wml) {
322  std::cout << "Sending WML to " << log_address(socket) << ": \n" << doc.output() << std::endl;
323  }
324 
325  try {
327 
328  union DataSize
329  {
330  uint32_t size;
331  char buf[4];
332  } data_size {};
333  data_size.size = htonl(s.size());
334 
335  std::vector<boost::asio::const_buffer> buffers {
336  { data_size.buf, 4 },
337  { s.begin(), std::size_t(s.size()) }
338  };
339 
340  boost::system::error_code ec;
341  async_write(*socket, buffers, yield[ec]);
342  if(check_error(ec, socket)) {
343  socket->lowest_layer().close();
344  return;
345  }
346  } catch (simple_wml::error& e) {
347  WRN_CONFIG << __func__ << ": simple_wml error: " << e.message;
348  throw;
349  }
350 }
351 template void server_base::coro_send_doc<socket_ptr>(const socket_ptr& socket, simple_wml::document& doc, const boost::asio::yield_context& yield);
352 template void server_base::coro_send_doc<tls_socket_ptr>(const tls_socket_ptr& socket, simple_wml::document& doc, const boost::asio::yield_context& yield);
353 
354 template<class SocketPtr> void coro_send_file_userspace(const SocketPtr& socket, const std::string& filename, const boost::asio::yield_context& yield)
355 {
356  std::size_t filesize { std::size_t(filesystem::file_size(filename)) };
357  union DataSize
358  {
359  uint32_t size;
360  char buf[4];
361  } data_size {};
362  data_size.size = htonl(filesize);
363 
364  boost::system::error_code ec;
365  async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
366  if(check_error(ec, socket)) {
367  socket->lowest_layer().close();
368  return;
369  }
370 
371  auto ifs { filesystem::istream_file(filename) };
372  ifs->seekg(0);
373  while(ifs->good()) {
374  char buf[16384];
375  ifs->read(buf, sizeof(buf));
376  async_write(*socket, boost::asio::buffer(buf, ifs->gcount()), yield[ec]);
377  if(check_error(ec, socket)) {
378  socket->lowest_layer().close();
379  return;
380  }
381  }
382 }
383 
384 #ifdef HAVE_SENDFILE
385 
386 void server_base::coro_send_file(const tls_socket_ptr& socket, const std::string& filename, const boost::asio::yield_context& yield)
387 {
388  // We fallback to userspace if using TLS socket because sendfile is not aware of TLS state
389  // TODO: keep in mind possibility of using KTLS instead. This seem to be available only in openssl3 branch for now
390  coro_send_file_userspace(socket, filename, yield);
391 }
392 
393 void server_base::coro_send_file(const socket_ptr& socket, const std::string& filename, const boost::asio::yield_context& yield)
394 {
395  std::size_t filesize { std::size_t(filesystem::file_size(filename)) };
396  int in_file { open(filename.c_str(), O_RDONLY) };
397  ON_SCOPE_EXIT(in_file) { close(in_file); };
398  off_t offset { 0 };
399  //std::size_t total_bytes_transferred { 0 };
400 
401  union DataSize
402  {
403  uint32_t size;
404  char buf[4];
405  } data_size {};
406  data_size.size = htonl(filesize);
407 
408  boost::system::error_code ec;
409  async_write(*socket, boost::asio::buffer(data_size.buf), yield[ec]);
410  if(check_error(ec, socket)) return;
411 
412  // Put the underlying socket into non-blocking mode.
413  if(!socket->native_non_blocking()) {
414  socket->native_non_blocking(true, ec);
415  if(check_error(ec, socket)) return;
416  }
417 
418  for(;;)
419  {
420  // Try the system call.
421  errno = 0;
422  int n = ::sendfile(socket->native_handle(), in_file, &offset, 65536);
423  ec = boost::system::error_code(n < 0 ? errno : 0,
424  boost::asio::error::get_system_category());
425  //total_bytes_transferred += *(yield.ec_) ? 0 : n;
426 
427  // Retry operation immediately if interrupted by signal.
428  if(ec == boost::asio::error::interrupted)
429  continue;
430 
431  // Check if we need to run the operation again.
432  if (ec == boost::asio::error::would_block
433  || ec == boost::asio::error::try_again)
434  {
435  // We have to wait for the socket to become ready again.
436  socket->async_write_some(boost::asio::null_buffers(), yield[ec]);
437  if(check_error(ec, socket)) return;
438  continue;
439  }
440 
441  if (ec || n == 0)
442  {
443  // An error occurred, or we have reached the end of the file.
444  // Either way we must exit the loop.
445  break;
446  }
447 
448  // Loop around to try calling sendfile again.
449  }
450 }
451 
452 #elif defined(_WIN32)
453 
454 void server_base::coro_send_file(const tls_socket_ptr& socket, const std::string& filename, const boost::asio::yield_context& yield)
455 {
456  coro_send_file_userspace(socket, filename, yield);
457 }
458 
459 void server_base::coro_send_file(const socket_ptr& socket, const std::string& filename, const boost::asio::yield_context& yield)
460 {
461  OVERLAPPED overlap;
462  std::vector<boost::asio::const_buffer> buffers;
463 
464  SetLastError(ERROR_SUCCESS);
465 
466  std::size_t filesize = filesystem::file_size(filename);
467  std::wstring filename_ucs2 = unicode_cast<std::wstring>(filename);
468  HANDLE in_file = CreateFileW(filename_ucs2.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
469  FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
470  if (GetLastError() != ERROR_SUCCESS)
471  {
472  throw std::runtime_error("Failed to open the file");
473  }
474  BOOST_SCOPE_EXIT_ALL(in_file) {
475  CloseHandle(&in_file);
476  };
477 
478  HANDLE event = CreateEvent(nullptr, TRUE, TRUE, nullptr);
479  if (GetLastError() != ERROR_SUCCESS)
480  {
481  throw std::runtime_error("Failed to create an event");
482  }
483  BOOST_SCOPE_EXIT_ALL(&overlap) {
484  CloseHandle(overlap.hEvent);
485  };
486 
487  overlap.hEvent = event;
488 
489  union DataSize
490  {
491  uint32_t size;
492  char buf[4];
493  } data_size {};
494  data_size.size = htonl(filesize);
495 
496  async_write(*socket, boost::asio::buffer(data_size.buf, 4), yield);
497 
498  BOOL success = TransmitFile(socket->native_handle(), in_file, 0, 0, &overlap, nullptr, 0);
499  if(!success) {
500  if(WSAGetLastError() == WSA_IO_PENDING) {
501  while(true) {
502  // The request is pending. Wait until it completes.
503  socket->async_write_some(boost::asio::null_buffers(), yield);
504 
505  DWORD win_ec = GetLastError();
506  if (win_ec != ERROR_IO_PENDING && win_ec != ERROR_SUCCESS)
507  throw std::runtime_error("TransmitFile failed");
508 
509  if(HasOverlappedIoCompleted(&overlap)) break;
510  }
511  } else {
512  throw std::runtime_error("TransmitFile failed");
513  }
514  }
515 }
516 
517 #else
518 
519 void server_base::coro_send_file(const tls_socket_ptr& socket, const std::string& filename, const boost::asio::yield_context& yield)
520 {
521  coro_send_file_userspace(socket, filename, yield);
522 }
523 
524 void server_base::coro_send_file(const socket_ptr& socket, const std::string& filename, const boost::asio::yield_context& yield)
525 {
526  coro_send_file_userspace(socket, filename, yield);
527 }
528 
529 #endif
530 
531 template<class SocketPtr> std::unique_ptr<simple_wml::document> server_base::coro_receive_doc(const SocketPtr& socket, const boost::asio::yield_context& yield)
532 {
533  union DataSize
534  {
535  uint32_t size;
536  char buf[4];
537  } data_size {};
538  boost::system::error_code ec;
539  async_read(*socket, boost::asio::buffer(data_size.buf, 4), yield[ec]);
540  if(check_error(ec, socket)) return {};
541  uint32_t size = ntohl(data_size.size);
542 
543  if(size == 0) {
544  ERR_SERVER <<
545  log_address(socket) <<
546  "\treceived invalid packet with payload size 0";
547  return {};
548  }
550  ERR_SERVER <<
551  log_address(socket) <<
552  "\treceived packet with payload size over size limit";
553  return {};
554  }
555 
556  boost::shared_array<char> buffer{ new char[size] };
557  async_read(*socket, boost::asio::buffer(buffer.get(), size), yield[ec]);
558  if(check_error(ec, socket)) return {};
559 
560  try {
561  simple_wml::string_span compressed_buf(buffer.get(), size);
562  return std::make_unique<simple_wml::document>(compressed_buf);
563  } catch (simple_wml::error& e) {
564  ERR_SERVER <<
565  log_address(socket) <<
566  "\tsimple_wml error in received data: " << e.message;
567  async_send_error(socket, "Invalid WML received: " + e.message);
568  return {};
569  }
570 }
571 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<socket_ptr>(const socket_ptr& socket, const boost::asio::yield_context& yield);
572 template std::unique_ptr<simple_wml::document> server_base::coro_receive_doc<tls_socket_ptr>(const tls_socket_ptr& socket, const boost::asio::yield_context& yield);
573 
574 template<class SocketPtr> void server_base::send_doc_queued(const SocketPtr& socket, std::unique_ptr<simple_wml::document>& doc_ptr, const boost::asio::yield_context& yield)
575 {
576  static std::map<SocketPtr, std::queue<std::unique_ptr<simple_wml::document>>> queues;
577 
578  queues[socket].push(std::move(doc_ptr));
579  if(queues[socket].size() > 1) {
580  return;
581  }
582 
583  ON_SCOPE_EXIT(socket) { queues.erase(socket); };
584 
585  while(queues[socket].size() > 0) {
586  coro_send_doc(socket, *(queues[socket].front()), yield);
587  ON_SCOPE_EXIT(socket) { queues[socket].pop(); };
588  }
589 }
590 
591 template<class SocketPtr> void server_base::async_send_doc_queued(const SocketPtr& socket, simple_wml::document& doc)
592 {
593  boost::asio::spawn(
594  io_service_, [this, doc_ptr = doc.clone(), socket](const boost::asio::yield_context& yield) mutable {
595  send_doc_queued(socket, doc_ptr, yield);
596  }
597 #if BOOST_VERSION >= 108000
598  , [](const std::exception_ptr& e) { if (e) std::rethrow_exception(e); }
599 #endif
600  );
601 }
602 
603 template<class SocketPtr> void server_base::async_send_error(SocketPtr socket, const std::string& msg, const char* error_code, const info_table& info)
604 {
606  doc.root().add_child("error").set_attr_dup("message", msg.c_str());
607  if(*error_code != '\0') {
608  doc.child("error")->set_attr("error_code", error_code);
609  }
610  info_table_into_simple_wml(doc, "error", info);
611 
612  async_send_doc_queued(socket, doc);
613 }
614 template void server_base::async_send_error<socket_ptr>(socket_ptr socket, const std::string& msg, const char* error_code, const info_table& info);
615 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);
616 
617 template<class SocketPtr> void server_base::async_send_warning(const SocketPtr& socket, const std::string& msg, const char* warning_code, const info_table& info)
618 {
620  doc.root().add_child("warning").set_attr_dup("message", msg.c_str());
621  if(*warning_code != '\0') {
622  doc.child("warning")->set_attr("warning_code", warning_code);
623  }
624  info_table_into_simple_wml(doc, "warning", info);
625 
626  async_send_doc_queued(socket, doc);
627 }
628 template void server_base::async_send_warning<socket_ptr>(const socket_ptr& socket, const std::string& msg, const char* warning_code, const info_table& info);
629 template void server_base::async_send_warning<tls_socket_ptr>(const tls_socket_ptr& socket, const std::string& msg, const char* warning_code, const info_table& info);
630 
632 {
633  tls_enabled_ = cfg["tls_enabled"].to_bool(false);
634  if(!tls_enabled_) return;
635 
636  tls_context_.set_options(
637  boost::asio::ssl::context::default_workarounds
638  | boost::asio::ssl::context::no_sslv2
639  | boost::asio::ssl::context::no_sslv3
640  | boost::asio::ssl::context::single_dh_use
641  );
642 
643  tls_context_.use_certificate_chain_file(cfg["tls_fullchain"].str());
644  tls_context_.use_private_key_file(cfg["tls_private_key"].str(), boost::asio::ssl::context::pem);
645  if(!cfg["tls_dh"].str().empty()) tls_context_.use_tmp_dh_file(cfg["tls_dh"].str());
646 }
647 
648 std::string server_base::hash_password(const std::string& pw, const std::string& salt, const std::string& username)
649 {
650  if(salt.length() < 12) {
651  ERR_SERVER << "Bad salt found for user: " << username;
652  return "";
653  }
654 
655  std::string password = pw;
656 
657  // Apparently HTML key-characters are passed to the hashing functions of phpbb in this escaped form.
658  // I will do closer investigations on this, for now let's just hope these are all of them.
659 
660  // Note: we must obviously replace '&' first, I wasted some time before I figured that out... :)
661  for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos) {
662  password.replace(pos, 1, "&amp;");
663  }
664  for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos) {
665  password.replace(pos, 1, "&quot;");
666  }
667  for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos) {
668  password.replace(pos, 1, "&lt;");
669  }
670  for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos) {
671  password.replace(pos, 1, "&gt;");
672  }
673 
674  if(utils::md5::is_valid_prefix(salt)) {
675  std::string hash = utils::md5(password, utils::md5::get_salt(salt), utils::md5::get_iteration_count(salt)).base64_digest();
676  return salt+hash;
677  } else if(utils::bcrypt::is_valid_prefix(salt)) {
678  try {
679  auto bcrypt_salt = utils::bcrypt::from_salted_salt(salt);
680  auto hash = utils::bcrypt::hash_pw(password, bcrypt_salt);
681  return hash.base64_digest();
682  } catch(const utils::hash_error& err) {
683  ERR_SERVER << "bcrypt hash failed for user " << username << ": " << err.what();
684  return "";
685  }
686  } else {
687  ERR_SERVER << "Unable to determine how to hash the password for user: " << username;
688  return "";
689  }
690 }
691 
692 // This is just here to get it to build without the deprecation_message function
693 #include "deprecation.hpp"
694 
695 std::string deprecated_message(const std::string&, DEP_LEVEL, const version_info&, const std::string&) {return "";}
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
void async_send_warning(const SocketPtr &socket, const std::string &msg, const char *warning_code="", const info_table &info={})
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
boost::asio::ip::tcp::acceptor acceptor_v4_
std::unique_ptr< simple_wml::document > coro_receive_doc(const SocketPtr &socket, const boost::asio::yield_context &yield)
Receive WML document from a coroutine.
std::map< std::string, std::string > info_table
virtual utils::optional< login_ban_info > is_ip_banned(const std::string &)
boost::asio::io_context io_service_
unsigned short port_
void async_send_doc_queued(const SocketPtr &socket, simple_wml::document &doc)
High level wrapper for sending a WML document.
virtual void handle_sighup(const boost::system::error_code &error, int signal_number)=0
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
void send_doc_queued(const SocketPtr &socket, std::unique_ptr< simple_wml::document > &doc_ptr, const boost::asio::yield_context &yield)
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_
void coro_send_doc(const SocketPtr &socket, simple_wml::document &doc, const boost::asio::yield_context &yield)
Send a WML document from within a coroutine.
boost::asio::ip::tcp::acceptor acceptor_v6_
const char * output()
std::unique_ptr< document > clone()
static std::size_t document_size_limit
Definition: simple_wml.hpp:298
node * child(const char *name)
Definition: simple_wml.hpp:269
string_span output_compressed(bool bzip2=false)
node & add_child(const char *name)
Definition: simple_wml.cpp:469
node & set_attr(const char *key, const char *value)
Definition: simple_wml.cpp:412
node & set_attr_dup(const char *key, const char *value)
Definition: simple_wml.cpp:427
static bool is_valid_prefix(const std::string &hash)
Definition: hash.cpp:168
static bcrypt from_salted_salt(const std::string &input)
Definition: hash.cpp:138
static bcrypt hash_pw(const std::string &password, bcrypt &salt)
Definition: hash.cpp:159
static bool is_valid_prefix(const std::string &hash)
Definition: hash.cpp:94
virtual std::string base64_digest() const override
Definition: hash.cpp:124
static std::string get_salt(const std::string &hash)
Definition: hash.cpp:90
static int get_iteration_count(const std::string &hash)
Definition: hash.cpp:86
Represents version numbers.
Definitions for the interface to Wesnoth Markup Language (WML).
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
Definition: deprecation.hpp:21
Declarations for File-IO.
Standard logging facilities (interface).
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
int file_size(const std::string &fname)
Returns the size of a file, or -1 if the file doesn't exist.
logger & err()
Definition: log.cpp:339
logger & info()
Definition: log.cpp:351
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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
#define ON_SCOPE_EXIT(...)
Run some arbitrary code (a lambda) when the current scope exits The lambda body follows this header,...
Definition: scope_exit.hpp:43
#define DBG_SERVER
Definition: server_base.cpp:56
#define LOG_SERVER
Definition: server_base.cpp:55
#define WRN_SERVER
Definition: server_base.cpp:54
std::string client_address(SocketPtr socket)
bool dump_wml
Definition: server_base.cpp:62
std::string deprecated_message(const std::string &, DEP_LEVEL, const version_info &, const std::string &)
template bool check_error< tls_socket_ptr >(const boost::system::error_code &error, const tls_socket_ptr &socket)
#define ERR_SERVER
Definition: server_base.cpp:53
void coro_send_file_userspace(const SocketPtr &socket, const std::string &filename, const boost::asio::yield_context &yield)
#define WRN_CONFIG
Definition: server_base.cpp:60
bool check_error(const boost::system::error_code &error, const SocketPtr &socket)
static lg::log_domain log_server("server")
static lg::log_domain log_config("config")
Base class for servers using Wesnoth's WML over TCP protocol.
std::shared_ptr< boost::asio::ssl::stream< socket_ptr::element_type > > tls_socket_ptr
Definition: server_base.hpp:52
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.
static map_location::direction n
static map_location::direction s
#define e