Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 9 additions & 20 deletions src/confighttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")" }
Expand All @@ -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 }
Expand All @@ -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) {
Expand Down Expand Up @@ -636,11 +636,8 @@ void start() {

auto port_https = map_port(PORT_HTTPS);

auto ctx = std::make_shared<boost::asio::ssl::context>(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;
Expand All @@ -666,27 +663,19 @@ 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
if(shutdown_event->peek()) {
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;
}
Expand Down
138 changes: 95 additions & 43 deletions src/nvhttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<SimpleWeb::HTTPS>;
class SunshineHttpsServer : public SimpleWeb::Server<SimpleWeb::HTTPS> {
public:
SunshineHttpsServer(const std::string &certification_file, const std::string &private_key_file)
: SimpleWeb::Server<SimpleWeb::HTTPS>::Server(certification_file, private_key_file) {}

std::function<int(SSL *)> verify;
std::function<void(std::shared_ptr<Response>, std::shared_ptr<Request>)> on_verify_failed;

protected:
void after_bind() override {
SimpleWeb::Server<SimpleWeb::HTTPS>::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<HTTPS>::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<Session>(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<SimpleWeb::HTTP>;

struct conf_intern_t {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<crypto::aes_t>(args.at("rikey"s), true);
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
launch_session.gcm_key = util::from_hex<crypto::aes_t>(get_arg(args, "rikey"), true);
uint32_t prepend_iv = util::endian::big<uint32_t>(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));
Expand Down Expand Up @@ -217,7 +287,7 @@ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin
tree.put("root.<xmlattr>.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<uint8_t> decrypted;
crypto::cipher::ecb_t cipher(*sess.cipher_key, false);
Expand All @@ -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);

Expand Down Expand Up @@ -274,7 +344,7 @@ void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args)
void clientpairingsecret(std::shared_ptr<safe::queue_t<crypto::x509_t>> &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 };
Expand Down Expand Up @@ -391,7 +461,7 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &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;
Expand All @@ -400,12 +470,12 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &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;
Expand Down Expand Up @@ -477,7 +547,7 @@ void pin(std::shared_ptr<typename SimpleWeb::ServerBase<T>::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;
Expand Down Expand Up @@ -513,6 +583,8 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
}
}

auto local_endpoint = request->local_endpoint();

pt::ptree tree;

tree.put("root.<xmlattr>.status_code", 200);
Expand All @@ -523,9 +595,9 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::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");
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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.<xmlattr>.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);
}

Expand Down Expand Up @@ -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.<xmlattr>.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);
}

Expand Down Expand Up @@ -764,7 +836,7 @@ void appasset(resp_https_t response, req_https_t request) {
print_req<SimpleWeb::HTTPS>(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;
Expand All @@ -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>(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) {
Expand All @@ -801,16 +869,11 @@ void start() {

auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>(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
Expand Down Expand Up @@ -870,7 +933,7 @@ void start() {
tree.put("root.<xmlattr>.status_message"s, "The client is not authorized. Certificate verification failed."s);
};

https_server.default_resource = not_found<SimpleWeb::HTTPS>;
https_server.default_resource["GET"] = not_found<SimpleWeb::HTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTPS>;
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTPS>(add_cert, resp, req); };
https_server.resource["^/applist$"]["GET"] = applist;
Expand All @@ -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<SimpleWeb::HTTP>;
http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); };
http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTP>;
Expand All @@ -893,28 +956,17 @@ 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
if(shutdown_event->peek()) {
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;
}
Expand Down
2 changes: 1 addition & 1 deletion third-party/Simple-Web-Server
Submodule Simple-Web-Server updated from 3ae451 to 2f2992