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