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