diff --git a/.gitmodules b/.gitmodules index 75dd21c9..80f505eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/moonlight-stream/moonlight-common-c.git [submodule "Simple-Web-Server"] path = third-party/Simple-Web-Server - url = https://github.com/loki-47-6F-64/Simple-Web-Server.git + url = https://gitlab.com/eidheim/Simple-Web-Server.git [submodule "ViGEmClient"] path = third-party/ViGEmClient url = https://github.com/ViGEm/ViGEmClient diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 6cf6b123..d6b60c0f 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -68,7 +68,7 @@ void print_req(const req_https_t &request) { } void send_unauthorized(resp_https_t response, req_https_t request) { - auto address = request->remote_endpoint_address(); + auto address = request->remote_endpoint().address().to_string(); BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv; const SimpleWeb::CaseInsensitiveMultimap headers { { "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" } @@ -77,7 +77,7 @@ void send_unauthorized(resp_https_t response, req_https_t request) { } void send_redirect(resp_https_t response, req_https_t request, const char *path) { - auto address = request->remote_endpoint_address(); + auto address = request->remote_endpoint().address().to_string(); BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv; const SimpleWeb::CaseInsensitiveMultimap headers { { "Location", path } @@ -86,7 +86,7 @@ void send_redirect(resp_https_t response, req_https_t request, const char *path) } bool authenticate(resp_https_t response, req_https_t request) { - auto address = request->remote_endpoint_address(); + auto address = request->remote_endpoint().address().to_string(); auto ip_type = net::from_address(address); if(ip_type > http::origin_web_ui_allowed) { @@ -636,11 +636,8 @@ void start() { auto port_https = map_port(PORT_HTTPS); - auto ctx = std::make_shared(boost::asio::ssl::context::tls); - ctx->use_certificate_chain_file(config::nvhttp.cert); - ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); - https_server_t server { ctx, 0 }; - server.default_resource = not_found; + https_server_t server { config::nvhttp.cert, config::nvhttp.pkey }; + server.default_resource["GET"] = not_found; server.resource["^/$"]["GET"] = getIndexPage; server.resource["^/pin$"]["GET"] = getPinPage; server.resource["^/apps$"]["GET"] = getAppsPage; @@ -666,19 +663,11 @@ void start() { server.config.address = "0.0.0.0"s; server.config.port = port_https; - try { - server.bind(); - BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << port_https << "]"; - } - catch(boost::system::system_error &err) { - BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << port_https << "]: "sv << err.what(); - - shutdown_event->raise(true); - return; - } auto accept_and_run = [&](auto *server) { try { - server->accept_and_run(); + server->start([](unsigned short port) { + BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << port << "]"; + }); } catch(boost::system::system_error &err) { // It's possible the exception gets thrown after calling server->stop() from a different thread @@ -686,7 +675,7 @@ void start() { return; } - BOOST_LOG(fatal) << "Couldn't start Configuration HTTPS server to port ["sv << port_https << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't start Configuration HTTPS server on port ["sv << port_https << "]: "sv << err.what(); shutdown_event->raise(true); return; } diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 47067d26..9ffda1b4 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -37,7 +37,69 @@ constexpr auto GFE_VERSION = "3.23.0.74"; namespace fs = std::filesystem; namespace pt = boost::property_tree; -using https_server_t = SimpleWeb::Server; +class SunshineHttpsServer : public SimpleWeb::Server { +public: + SunshineHttpsServer(const std::string &certification_file, const std::string &private_key_file) + : SimpleWeb::Server::Server(certification_file, private_key_file) {} + + std::function verify; + std::function, std::shared_ptr)> on_verify_failed; + +protected: + void after_bind() override { + SimpleWeb::Server::after_bind(); + + if(verify) { + context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once); + context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) { + // To respond with an error message, a connection must be established + return 1; + }); + } + } + + // This is Server::accept() with SSL validation support added + void accept() override { + auto connection = create_connection(*io_service, context); + + acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) { + auto lock = connection->handler_runner->continue_lock(); + if(!lock) + return; + + if(ec != SimpleWeb::error::operation_aborted) + this->accept(); + + auto session = std::make_shared(config.max_request_streambuf_size, connection); + + if(!ec) { + boost::asio::ip::tcp::no_delay option(true); + SimpleWeb::error_code ec; + session->connection->socket->lowest_layer().set_option(option, ec); + + session->connection->set_timeout(config.timeout_request); + session->connection->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) { + session->connection->cancel_timeout(); + auto lock = session->connection->handler_runner->continue_lock(); + if(!lock) + return; + if(!ec) { + if(verify && !verify(session->connection->socket->native_handle())) + this->write(session, on_verify_failed); + else + this->read(session); + } + else if(this->on_error) + this->on_error(session->request, ec); + }); + } + else if(this->on_error) + this->on_error(session->request, ec); + }); + } +}; + +using https_server_t = SunshineHttpsServer; using http_server_t = SimpleWeb::Server; struct conf_intern_t { @@ -86,6 +148,14 @@ enum class op_e { REMOVE }; +std::string get_arg(const args_t &args, const char *name) { + auto it = args.find(name); + if(it == std::end(args)) { + throw std::out_of_range(name); + } + return it->second; +} + void save_state() { pt::ptree root; @@ -188,8 +258,8 @@ stream::launch_session_t make_launch_session(bool host_audio, const args_t &args stream::launch_session_t launch_session; launch_session.host_audio = host_audio; - launch_session.gcm_key = util::from_hex(args.at("rikey"s), true); - uint32_t prepend_iv = util::endian::big(util::from_view(args.at("rikeyid"s))); + launch_session.gcm_key = util::from_hex(get_arg(args, "rikey"), true); + uint32_t prepend_iv = util::endian::big(util::from_view(get_arg(args, "rikeyid"))); auto prepend_iv_p = (uint8_t *)&prepend_iv; auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv)); @@ -217,7 +287,7 @@ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin tree.put("root..status_code", 200); } void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &args) { - auto encrypted_response = util::from_hex_vec(args.at("serverchallengeresp"s), true); + auto encrypted_response = util::from_hex_vec(get_arg(args, "serverchallengeresp"), true); std::vector decrypted; crypto::cipher::ecb_t cipher(*sess.cipher_key, false); @@ -237,7 +307,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar } void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) { - auto challenge = util::from_hex_vec(args.at("clientchallenge"s), true); + auto challenge = util::from_hex_vec(get_arg(args, "clientchallenge"), true); crypto::cipher::ecb_t cipher(*sess.cipher_key, false); @@ -274,7 +344,7 @@ void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) void clientpairingsecret(std::shared_ptr> &add_cert, pair_session_t &sess, pt::ptree &tree, const args_t &args) { auto &client = sess.client; - auto pairingsecret = util::from_hex_vec(args.at("clientpairingsecret"), true); + auto pairingsecret = util::from_hex_vec(get_arg(args, "clientpairingsecret"), true); std::string_view secret { pairingsecret.data(), 16 }; std::string_view sign { pairingsecret.data() + secret.size(), crypto::digest_size }; @@ -391,7 +461,7 @@ void pair(std::shared_ptr> &add_cert, std::shared_ return; } - auto uniqID { std::move(args.at("uniqueid"s)) }; + auto uniqID { std::move(get_arg(args, "uniqueid")) }; auto sess_it = map_id_sess.find(uniqID); args_t::const_iterator it; @@ -400,12 +470,12 @@ void pair(std::shared_ptr> &add_cert, std::shared_ pair_session_t sess; sess.client.uniqueID = std::move(uniqID); - sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true); + sess.client.cert = util::from_hex_vec(get_arg(args, "clientcert"), true); BOOST_LOG(debug) << sess.client.cert; auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first; - ptr->second.async_insert_pin.salt = std::move(args.at("salt"s)); + ptr->second.async_insert_pin.salt = std::move(get_arg(args, "salt")); if(config::sunshine.flags[config::flag::PIN_STDIN]) { std::string pin; @@ -477,7 +547,7 @@ void pin(std::shared_ptr::Response> response, response->close_connection_after_response = true; - auto address = request->remote_endpoint_address(); + auto address = request->remote_endpoint().address().to_string(); auto ip_type = net::from_address(address); if(ip_type > http::origin_pin_allowed) { BOOST_LOG(info) << "/pin: ["sv << address << "] -- denied"sv; @@ -513,6 +583,8 @@ void serverinfo(std::shared_ptr::Response> res } } + auto local_endpoint = request->local_endpoint(); + pt::ptree tree; tree.put("root..status_code", 200); @@ -523,9 +595,9 @@ void serverinfo(std::shared_ptr::Response> res tree.put("root.uniqueid", http::unique_id); tree.put("root.HttpsPort", map_port(PORT_HTTPS)); tree.put("root.ExternalPort", map_port(PORT_HTTP)); - tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address())); + tree.put("root.mac", platf::get_mac_address(local_endpoint.address().to_string())); tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0"); - tree.put("root.LocalIP", request->local_endpoint_address()); + tree.put("root.LocalIP", local_endpoint.address().to_string()); if(config::video.hevc_mode == 3) { tree.put("root.ServerCodecModeSupport", "3843"); @@ -598,7 +670,7 @@ void applist(resp_https_t response, req_https_t request) { return; } - auto clientID = args.at("uniqueid"s); + auto clientID = get_arg(args, "uniqueid"); auto client = map_id_client.find(clientID); if(client == std::end(map_id_client)) { @@ -655,7 +727,7 @@ void launch(bool &host_audio, resp_https_t response, req_https_t request) { return; } - auto appid = util::from_view(args.at("appid")) - 1; + auto appid = util::from_view(get_arg(args, "appid")) - 1; auto current_appid = proc::proc.running(); if(current_appid != -1) { @@ -675,11 +747,11 @@ void launch(bool &host_audio, resp_https_t response, req_https_t request) { } } - host_audio = util::from_view(args.at("localAudioPlayMode")); + host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); stream::launch_session_raise(make_launch_session(host_audio, args)); tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint_address() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT))); + tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint().address().to_string() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT))); tree.put("root.gamesession", 1); } @@ -726,7 +798,7 @@ void resume(bool &host_audio, resp_https_t response, req_https_t request) { stream::launch_session_raise(make_launch_session(host_audio, args)); tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint_address() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT))); + tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint().address().to_string() + ':' + std::to_string(map_port(stream::RTSP_SETUP_PORT))); tree.put("root.resume", 1); } @@ -764,7 +836,7 @@ void appasset(resp_https_t response, req_https_t request) { print_req(request); auto args = request->parse_query_string(); - auto app_image = proc::proc.get_app_image(util::from_view(args.at("appid"))); + auto app_image = proc::proc.get_app_image(util::from_view(get_arg(args, "appid"))); std::ifstream in(app_image, std::ios::binary); SimpleWeb::CaseInsensitiveMultimap headers; @@ -788,10 +860,6 @@ void start() { conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); conf_intern.servercert = read_file(config::nvhttp.cert.c_str()); - auto ctx = std::make_shared(boost::asio::ssl::context::tls); - ctx->use_certificate_chain_file(config::nvhttp.cert); - ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); - crypto::cert_chain_t cert_chain; for(auto &[_, client] : map_id_client) { for(auto &cert : client.certs) { @@ -801,16 +869,11 @@ void start() { auto add_cert = std::make_shared>(30); - ctx->set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) { - // To respond with an error message, a connection must be established - return 1; - }); - // /resume doesn't get the parameter "localAudioPlayMode" // /launch will store it in host_audio bool host_audio {}; - https_server_t https_server { ctx, boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once }; + https_server_t https_server { config::nvhttp.cert, config::nvhttp.pkey }; http_server_t http_server; // Verify certificates after establishing connection @@ -870,7 +933,7 @@ void start() { tree.put("root..status_message"s, "The client is not authorized. Certificate verification failed."s); }; - https_server.default_resource = not_found; + https_server.default_resource["GET"] = not_found; https_server.resource["^/serverinfo$"]["GET"] = serverinfo; https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; https_server.resource["^/applist$"]["GET"] = applist; @@ -884,7 +947,7 @@ void start() { https_server.config.address = "0.0.0.0"s; https_server.config.port = port_https; - http_server.default_resource = not_found; + http_server.default_resource["GET"] = not_found; http_server.resource["^/serverinfo$"]["GET"] = serverinfo; http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair(add_cert, resp, req); }; http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin; @@ -893,20 +956,9 @@ void start() { http_server.config.address = "0.0.0.0"s; http_server.config.port = port_http; - try { - https_server.bind(); - http_server.bind(); - } - catch(boost::system::system_error &err) { - BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << port_http << ", "sv << port_http << "]: "sv << err.what(); - - shutdown_event->raise(true); - return; - } - auto accept_and_run = [&](auto *http_server) { try { - http_server->accept_and_run(); + http_server->start(); } catch(boost::system::system_error &err) { // It's possible the exception gets thrown after calling http_server->stop() from a different thread @@ -914,7 +966,7 @@ void start() { return; } - BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << port_https << ", "sv << port_https << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't start http server on ports ["sv << port_https << ", "sv << port_https << "]: "sv << err.what(); shutdown_event->raise(true); return; } diff --git a/third-party/Simple-Web-Server b/third-party/Simple-Web-Server index 3ae45103..2f29926d 160000 --- a/third-party/Simple-Web-Server +++ b/third-party/Simple-Web-Server @@ -1 +1 @@ -Subproject commit 3ae451038ff151b9abeb0a945c8c9241653420c7 +Subproject commit 2f29926dbbcd8a0425064d98c24f37ac50bd0b5b