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