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