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