#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "server.h" #include "http.h" #include "privileges.h" #include "statistics.h" namespace beast = boost::beast; // from namespace http = beast::http; // from namespace net = boost::asio; // from namespace ssl = boost::asio::ssl; // from using tcp = boost::asio::ip::tcp; // from namespace fs = std::filesystem; using namespace Reichwein; const std::string Server::VersionString{ "Reichwein.IT Webserver "s + std::string{VERSION} }; namespace { const int32_t stats_timer_seconds { 24 * 60 * 60 }; // save stats once a day std::string unbracketed(const std::string& s) { if (s.size() >= 2 && s.front() == '[' && s.back() == ']') { return s.substr(1, s.size() - 2); } else { return s; } } } // anonymous namespace Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics) : m_config(config) , m_ioc(ioc) , m_socket(socket) , m_plugins(plugins) , m_statistics(statistics) { } Server::~Server() { } Config& Server::GetConfig() { return m_config; } const Socket& Server::GetSocket() { return m_socket; } plugin_type Server::GetPlugin(const std::string& name) { try { return m_plugins.at(name); } catch (const std::out_of_range& ex) { std::cout << "Out of range at Server::GetPlugin(): " << name << std::endl; std::rethrow_exception(std::current_exception()); } catch (...) { std::cout << "Unknown exception at Server::GetPlugin(): " << name << std::endl; std::rethrow_exception(std::current_exception()); } } Statistics& Server::GetStatistics() { return m_statistics; } namespace HTTP { class Server: public ::Server { public: Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics) : ::Server(config, ioc, socket, plugins, statistics) { } ~Server() override { } int start() override { auto const address = net::ip::make_address(m_socket.address); auto const port = static_cast(std::atoi(m_socket.port.data())); // Create and launch a listening port make_listener(m_ioc, address, port, *this); return EXIT_SUCCESS; } }; } // namespace HTTP namespace { /* Load a signed certificate into the ssl context, and configure the context for use with a server. */ void load_server_certificate(boost::asio::ssl::context& ctx, const fs::path& cert_path, const fs::path& key_path) { /* The certificate was generated from CMD.EXE on Windows 10 using: winpty openssl dhparam -out dh.pem 2048 winpty openssl req -newkey rsa:4096 -sha256 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=DE\ST=BY\L=Munich\O=Reichwein\CN=reichwein.it" */ std::string const dh = "-----BEGIN DH PARAMETERS-----\n" "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n" "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n" "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n" "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n" "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n" "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n" "-----END DH PARAMETERS-----\n"; ctx.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); std::string cert; if (cert_path.empty()) { // use dummy self signed certificate. Will be replaced by real // certificate if configured upon respective session cert = "-----BEGIN CERTIFICATE-----\n" "MIIDnTCCAoWgAwIBAgIULkYtO+2Ddeg+qLZ+aDQpmA5b4L0wDQYJKoZIhvcNAQEL\n" "BQAwXjELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0JhdmFyaWExDzANBgNVBAcMBk11\n" "bmljaDEVMBMGA1UECgwMUmVpY2h3ZWluIElUMRUwEwYDVQQDDAxyZWljaHdlaW4u\n" "aXQwHhcNMjAwNDA1MDgwNzIyWhcNNDcwODIyMDgwNzIyWjBeMQswCQYDVQQGEwJE\n" "RTEQMA4GA1UECAwHQmF2YXJpYTEPMA0GA1UEBwwGTXVuaWNoMRUwEwYDVQQKDAxS\n" "ZWljaHdlaW4gSVQxFTATBgNVBAMMDHJlaWNod2Vpbi5pdDCCASIwDQYJKoZIhvcN\n" "AQEBBQADggEPADCCAQoCggEBALJNb0WLbz+xP+YITMMk+eeK/SIOCRFs/9aZIAyK\n" "ParGauxa+8d25mlfJTAo6/G0h3sA240JHyNpOzVOogPU+v4dRWyGO0w5vHVD0caB\n" "rDb1eEfmLtqfKLLUL9iPDReUh6WAE7qoNDtfoT551uSMIae1cpPUduVTnSkEgw8k\n" "NjJSHYT800jSB2R+e7tJG3ErXDM63R3B8RbitZPoWACjpBxDT+Qrj0fBFS4AWw6b\n" "z09uitv0RrgI6CW7xRh3UAdRwEBGHiU6HTIthX6LNgez1UL0sfu1iZ22wNmYZP/S\n" "sL3b20WtSH9LN2PRJ4q3AGt6RMbmSGr65ljha9xkTFna0Y8CAwEAAaNTMFEwHQYD\n" "VR0OBBYEFKd5/MGFZUAUV502vJ/Kcswax8WVMB8GA1UdIwQYMBaAFKd5/MGFZUAU\n" "V502vJ/Kcswax8WVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB\n" "AIBS4AfM7wiunQ2UZQQ5A0Un99+BLax9e+h11h/jGeJ+/9maY/E9MK6UG9LXoOv2\n" "z32Q7Ta2xKeRu6GC/qupwYJ0Xt3LENOfogsaNCAgxKlAN48LGlRyCTvzWsEMh28j\n" "RaelWonh2qQoiryKLVnRwrg8g1Bu4v+V437cIBmeZPxf0spEL9EVqlN+iS8plmel\n" "7/F4ULdybKGq39tgicuS7JhnY21ZzOFoq0bWnKBbAeTndmuROdb3pEppxW6pwu0q\n" "TFdMrSJE38kiQh2O9IchPQbTZ+Rdj0HE9NxStlrNr5bu6rjikRm50/G3JoXpzYdp\n" "AN4ZI2QZ6R6Y+TzDixKecNk=\n" "-----END CERTIFICATE-----\n" ; } else { cert = File::getFile(cert_path); } ctx.use_certificate_chain( boost::asio::buffer(cert.data(), cert.size())); std::string key; if (key_path == "") { // use dummy self signed key. Will be replaced by real // certificate if configured upon respective session key = "-----BEGIN PRIVATE KEY-----\n" "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyTW9Fi28/sT/m\n" "CEzDJPnniv0iDgkRbP/WmSAMij2qxmrsWvvHduZpXyUwKOvxtId7ANuNCR8jaTs1\n" "TqID1Pr+HUVshjtMObx1Q9HGgaw29XhH5i7anyiy1C/Yjw0XlIelgBO6qDQ7X6E+\n" "edbkjCGntXKT1HblU50pBIMPJDYyUh2E/NNI0gdkfnu7SRtxK1wzOt0dwfEW4rWT\n" "6FgAo6QcQ0/kK49HwRUuAFsOm89Pborb9Ea4COglu8UYd1AHUcBARh4lOh0yLYV+\n" "izYHs9VC9LH7tYmdtsDZmGT/0rC929tFrUh/Szdj0SeKtwBrekTG5khq+uZY4Wvc\n" "ZExZ2tGPAgMBAAECggEBAK9bJKIa3dCgPB257/TEOtsTgJyrfROcRYkCk9iBZOC9\n" "v46wdIrZTwY2wtY4iMPwLoY0c7ijTfJ/nfFxYjmujyK4Gvz+jvcKmWQizP8TrRFo\n" "HWFo6o+slFQ8BspO9itIspd7/OtIXgY+qNBO959Sig7sjsEA5eXoc9pRS6vqizq0\n" "j4G/UO5Amr/l3ciEJiqMJgZsIVLDKaGlqFTymydSqkB8UHQYWK1kunQxhK4Ldycu\n" "hTooQE7tXM0zvoFVV6v1fldV5OFsZk2kPMNtvMO6ZEpOM4rNMlg+vJy8kB1fb3Gs\n" "iFE/DCUpZsMSserQMU9/hfrYlndgsFD5Sr1EVGEebhECgYEA1gc9qx+ugdhYTY5j\n" "tJDXjOsnw8KY/l/1y+mQ8XNJ9MVdBGy1WB+uWB4teiyJchV49gn2XlKUK2rcCBvZ\n" "vC5CwPmFi2t70JezQgnXtDlbR0bARPlRd741i4rBpD7hEiZNCTOd2HFBpUg/CGWN\n" "E4n1ksazBm6jvv3Jo6WAa07Z390CgYEA1USrFqmc/fKGQpTCdH0qYZv3hQtrb1TQ\n" "9YnrbhtaC0haPpintZKjvhU3tCd1fPuIDXtMAgaaKSyoGiE2aMvLxt1/eV08BkMi\n" "kGIss9poYNi5+6ZD9QAHmHJhzZtVGj8U5L8379XmwxAByiBRVVE8CW1X/e6+iJpz\n" "+CLgN+zEVlsCgYEAsuOAdtxXJm4meERwL8b0cvNF3Eh1Sf/42MPTAwzCntSrh3w5\n" "InvwY/RtPHWnN/ScksEG7BWHhLafTCPDHJdp8hNcvIhNB68UBDln0loyYePP5pag\n" "sj4IUSbb7SUlR989elhrMTKQlM5K6QDAJrmjyVdM4S5urL9A3wgAyzAvyP0CgYAO\n" "paGuc8WxdzebWQYl4/bGL2UHgSpGwid7xZYiwMQlZDm2dNuHz+NpCaICwHcEN243\n" "ptEojnWGAGgnK0LGXcDIDqxTlICr2W6FRgjV7Vkf1aKoUtn1+KOM58YpzdJBdDWm\n" "JC/eS+2GVhIZZLDRUDv0VcsmSIBTd3AhiZumm588YwKBgBZfNqfmHAwIP2pM1wml\n" "Ck3vaLLvonghj3iQW9CFJ/SqLOnfT4KJkFObR6oGbxY0RtXsCrmSqidIKgDd0Kkq\n" "L6QbHp2j3+16GBdmLNUJlfjBTNPJp69IDKztjeCX7/8JZs79p/LAv+I9Sh4lVw4O\n" "IrDprlB0yzP5zigcsAZeViYJ\n" "-----END PRIVATE KEY-----\n" ; } else { key = File::getFile(key_path); } ctx.use_private_key( boost::asio::buffer(key.data(), key.size()), boost::asio::ssl::context::file_format::pem); ctx.use_tmp_dh( boost::asio::buffer(dh.data(), dh.size())); } } // namespace namespace HTTPS { typedef std::unordered_map> ctx_type; static const ssl::context_base::method tls_method {ssl::context::tlsv13}; int ServerNameError(SSL *s, ctx_type& ctx_map) { std::shared_ptr ctx{ctx_map.at("")}; SSL_set_SSL_CTX(s, ctx->native_handle()); return SSL_CLIENT_HELLO_SUCCESS; // OK for now } int servername_callback(SSL *s, int *al, void *arg) { ctx_type& ctx_map = *(ctx_type*)arg; if (0) { // not really necessary int* numbers; size_t numbers_size; if (SSL_client_hello_get1_extensions_present(s, &numbers, &numbers_size) != 1) { std::cout << "Error on SSL_client_hello_get1_extensions_present" << std::endl; return ServerNameError(s, ctx_map); } bool server_name_available {false}; for (size_t i = 0; i < numbers_size; i++) if (numbers[i] == 0) server_name_available = true; OPENSSL_free(numbers); if (!server_name_available) { std::cout << "Error: No server_name available at SSL_client_hello_get1_extensions_present" << std::endl; return ServerNameError(s, ctx_map); } } const unsigned char* data; size_t data_size; // 0 is server_name if (SSL_client_hello_get0_ext(s, 0, &data, &data_size) != 1) { std::cout << "Warning: Error on SSL_client_hello_get0_ext: servername not available. Using dummy ctx." << std::endl; return ServerNameError(s, ctx_map); } // SNI Server Name, See https://tools.ietf.org/html/rfc6066 (TODO: why are there 5 bytes header?) std::string server_name {std::string((const char*)data, (size_t)data_size)}; if (server_name.size() >= 5 && server_name[0] == '\0') server_name = server_name.substr(5); server_name = unbracketed(server_name); auto it {ctx_map.find(server_name)}; std::shared_ptr ctx{}; if (it != ctx_map.end()) { ctx = it->second; } else { std::cout << "Warning: server_name " << server_name << " (" << server_name.size() << ") not found in list of prepared contexts. Using dummy ctx." << std::endl; return ServerNameError(s, ctx_map); } SSL_set_SSL_CTX(s, ctx->native_handle()); return SSL_CLIENT_HELLO_SUCCESS; } class Server: public ::Server { private: ctx_type m_ctx; public: Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics): ::Server(config, ioc, socket, plugins, statistics) { load_certificates(); // load initially } ~Server() override { } void load_certificates() { // initial dummy, before we can add specific ctx w/ certificate std::shared_ptr ctx_dummy{std::make_shared(tls_method)}; load_server_certificate(*ctx_dummy, "", ""); SSL_CTX_set_client_hello_cb(ctx_dummy->native_handle(), servername_callback, &m_ctx); m_ctx.emplace("", ctx_dummy); // import the real certificates for (const auto& serve_site: m_socket.serve_sites) { for (const auto& site: m_config.Sites()) { if (site.first == serve_site) { std::shared_ptr ctx {std::make_shared(tls_method)}; std::cout << "Creating SSL context/cert for site " << serve_site << " on port " << m_socket.port << std::endl; load_server_certificate(*ctx, site.second.cert_path, site.second.key_path); SSL_CTX_set_client_hello_cb(ctx->native_handle(), servername_callback, &m_ctx); for (const auto& host: site.second.hosts) { std::cout << " Adding Host " << host << std::endl; m_ctx.emplace(unbracketed(host), ctx); } } } } } int start() override { auto const address = net::ip::make_address(m_socket.address); auto const port = static_cast(std::atoi(m_socket.port.data())); // Create and launch a listening port make_listener(m_ioc, *m_ctx[""], address, port, *this); return EXIT_SUCCESS; } }; } // namespace HTTPS int run_server(Config& config, plugins_container_type& plugins) { Statistics stats(config.statistics_path()); auto const threads = std::max(1, config.Threads()); boost::asio::io_context ioc{threads}; // for now, just terminate on SIGINT, SIGHUP and SIGTERM boost::asio::signal_set signals(ioc, SIGINT, SIGTERM, SIGHUP); signals.async_wait([&](const boost::system::error_code& error, int signal_number){ std::cout << "Terminating via signal " << signal_number << std::endl; ioc.stop(); }); // Save stats once a day boost::asio::steady_timer stats_save_timer(ioc, boost::asio::chrono::seconds(stats_timer_seconds)); std::function stats_callback = [&](const boost::system::error_code& error){ stats.save(); stats_save_timer.expires_at(stats_save_timer.expires_at() + boost::asio::chrono::seconds(stats_timer_seconds)); stats_save_timer.async_wait(stats_callback); }; stats_save_timer.async_wait(stats_callback); std::vector> servers; const auto& sockets {config.Sockets()}; for (const auto& socket: sockets) { if (socket.protocol == SocketProtocol::HTTP) { servers.push_back(std::make_shared(config, ioc, socket, plugins, stats)); } else { servers.push_back(std::make_shared(config, ioc, socket, plugins, stats)); } servers.back()->start(); } // set UID, GID drop_privileges(config); // Run the I/O service on the requested number of threads std::vector v; v.reserve(threads - 1); for (auto i = threads - 1; i > 0; --i) { v.emplace_back( [&ioc] { ioc.run(); }); } ioc.run(); for (auto& t: v) { t.join(); } return EXIT_SUCCESS; }