From 4fe90dcbd66a61157f75e1d6f6847a03039658bf Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 9 May 2021 18:55:34 +0200 Subject: [PATCH 01/15] Started Work on Web UI --- CMakeLists.txt | 2 + assets/web/apps.html | 124 +++++++++++++++++++ assets/web/clients.html | 3 + assets/web/config.html | 4 + assets/web/header.html | 45 +++++++ assets/web/index.html | 4 + assets/web/pin.html | 22 ++++ sunshine/confighttp.cpp | 262 ++++++++++++++++++++++++++++++++++++++++ sunshine/confighttp.h | 20 +++ sunshine/main.cpp | 17 +-- sunshine/nvhttp.h | 4 +- sunshine/process.cpp | 5 + 12 files changed, 498 insertions(+), 14 deletions(-) create mode 100644 assets/web/apps.html create mode 100644 assets/web/clients.html create mode 100644 assets/web/config.html create mode 100644 assets/web/header.html create mode 100644 assets/web/index.html create mode 100644 assets/web/pin.html create mode 100644 sunshine/confighttp.cpp create mode 100644 sunshine/confighttp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1347b7d7..33e09423 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,8 @@ set(SUNSHINE_TARGET_FILES sunshine/crypto.h sunshine/nvhttp.cpp sunshine/nvhttp.h + sunshine/confighttp.cpp + sunshine/confighttp.h sunshine/rtsp.cpp sunshine/rtsp.h sunshine/stream.cpp diff --git a/assets/web/apps.html b/assets/web/apps.html new file mode 100644 index 00000000..d63f8260 --- /dev/null +++ b/assets/web/apps.html @@ -0,0 +1,124 @@ +
+

Applications

+
Applications are refreshed only when Client is restarted
+ + + + + + + + + + + + + +
NameActions
{{app.name}} +
+
+
+ + +
Application Name, as shown on Moonlight
+
+
+ + +
The file where the output of the command is stored, if it is not specified, the output is ignored
+
+
+ +
A list of commands to be run before/after the application.
If any of the prep-commands fail, starting the application is aborted
+ + + + + + + + + + + + + +
DoUndo
+ +
+
+ + +
The main application, if it is not specified, a processs is started that sleeps indefinitely
+
+
+ + +
+
+ +
+ + + + \ No newline at end of file diff --git a/assets/web/clients.html b/assets/web/clients.html new file mode 100644 index 00000000..c2d03025 --- /dev/null +++ b/assets/web/clients.html @@ -0,0 +1,3 @@ +
+

Clients

+
\ No newline at end of file diff --git a/assets/web/config.html b/assets/web/config.html new file mode 100644 index 00000000..c63ec39d --- /dev/null +++ b/assets/web/config.html @@ -0,0 +1,4 @@ +
+

Hello, Sunshine!

+

Placeholer for config page

+
\ No newline at end of file diff --git a/assets/web/header.html b/assets/web/header.html new file mode 100644 index 00000000..59c11c2f --- /dev/null +++ b/assets/web/header.html @@ -0,0 +1,45 @@ + + + + + + + + Sunshine + + + + + + + \ No newline at end of file diff --git a/assets/web/index.html b/assets/web/index.html new file mode 100644 index 00000000..376f8613 --- /dev/null +++ b/assets/web/index.html @@ -0,0 +1,4 @@ +
+

Hello, Sunshine!

+

Sunshine is a Gamestream host for Moonlight

+
\ No newline at end of file diff --git a/assets/web/pin.html b/assets/web/pin.html new file mode 100644 index 00000000..811a1796 --- /dev/null +++ b/assets/web/pin.html @@ -0,0 +1,22 @@ +
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp new file mode 100644 index 00000000..aa923f11 --- /dev/null +++ b/sunshine/confighttp.cpp @@ -0,0 +1,262 @@ +// +// Created by TheElixZammuto on 2021-05-09. +// TODO: Authentication, better handling of routes common to nvhttp, cleanup + +#include "process.h" + +#include + +#include +#include +#include + +#include + +#include +#include + +#include "config.h" +#include "utility.h" +#include "rtsp.h" +#include "crypto.h" +#include "confighttp.h" +#include "platform/common.h" +#include "network.h" +#include "nvhttp.h" +#include "uuid.h" +#include "main.h" + +std::string read_file(std::string path); + +namespace confighttp +{ + using namespace std::literals; + constexpr auto PORT_HTTP = 47990; + + namespace fs = std::filesystem; + namespace pt = boost::property_tree; + + using https_server_t = SimpleWeb::Server; + + using args_t = SimpleWeb::CaseInsensitiveMultimap; + using resp_https_t = std::shared_ptr::Response>; + using req_https_t = std::shared_ptr::Request>; + + enum class op_e + { + ADD, + REMOVE + }; + + template + void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + pt::ptree tree; + tree.put("root..status_code", 404); + + std::ostringstream data; + + pt::write_xml(data, tree); + response->write(data.str()); + + *response << "HTTP/1.1 404 NOT FOUND\r\n" + << data.str(); + } + + void getIndexPage(resp_https_t response, req_https_t request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "index.html"); + response->write(header + content); + } + + template + void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "pin.html"); + response->write(header + content); + } + + template + void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "apps.html"); + response->write(header + content); + } + + template + void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "clients.html"); + response->write(header + content); + } + + template + void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) + { + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "config.html"); + response->write(header + content); + } + + void getApps(resp_https_t response, req_https_t request) + { + std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + response->write(content); + } + + void saveApp(resp_https_t response, req_https_t request) + { + std::stringstream ss; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree inputTree,fileTree; + try { + //TODO: Input Validation + pt::read_json(ss, inputTree); + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = inputTree.get("index"); + BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); + if(inputTree.get_child("prep-cmd").empty())inputTree.erase("prep-cmd"); + inputTree.erase("index"); + if(index == -1){ + apps_node.push_back(std::make_pair("",inputTree)); + } else { + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto& kv : apps_node) { + if(i == index){ + newApps.push_back(std::make_pair("",inputTree)); + } else { + newApps.push_back(std::make_pair("",kv.second)); + } + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps",newApps)); + } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status","true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } catch (std::exception &e) { + BOOST_LOG(warning) << e.what(); + outputTree.put("status","false"); + outputTree.put("error","Invalid Input JSON"); + return; + } + } + + void deleteApp(resp_https_t response, req_https_t request) + { + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree fileTree; + try { + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = stoi(request->path_match[1]); + BOOST_LOG(info) << index; + if(index <= 0){ + outputTree.put("status","false"); + outputTree.put("error","Invalid Index"); + return; + } else { + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto& kv : apps_node) { + if(i != index){ + newApps.push_back(std::make_pair("",kv.second)); + } + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps",newApps)); + } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status","true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } catch (std::exception &e) { + BOOST_LOG(warning) << e.what(); + outputTree.put("status","false"); + outputTree.put("error","Invalid File JSON"); + return; + } + } + + void start(std::shared_ptr shutdown_event) + { + 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 http_server { ctx, 0 }; + http_server.default_resource = not_found; + http_server.resource["^/$"]["GET"] = getIndexPage; + http_server.resource["^/pin$"]["GET"] = getPinPage; + http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/api/apps$"]["GET"] = getApps; + http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + http_server.config.reuse_address = true; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; + + try + { + http_server.bind(); + BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + } + catch (boost::system::system_error &err) + { + BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); + + shutdown_event->raise(true); + return; + } + + std::thread tcp{&https_server_t::accept_and_run, &http_server}; + + // Wait for any event + shutdown_event->view(); + + http_server.stop(); + + tcp.join(); + } +} + +std::string read_file(std::string path) +{ + std::ifstream in(path); + + std::string input; + std::string base64_cert; + + //FIXME: Being unable to read file could result in infinite loop + while (!in.eof()) + { + std::getline(in, input); + base64_cert += input + '\n'; + } + + return base64_cert; +} \ No newline at end of file diff --git a/sunshine/confighttp.h b/sunshine/confighttp.h new file mode 100644 index 00000000..d3b9e85e --- /dev/null +++ b/sunshine/confighttp.h @@ -0,0 +1,20 @@ +// +// Created by loki on 6/3/19. +// + +#ifndef SUNSHINE_CONFIGHTTP_H +#define SUNSHINE_CONFIGHTTP_H + +#include +#include + +#include "thread_safe.h" + +#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/" + + +namespace confighttp { + void start(std::shared_ptr shutdown_event); +} + +#endif //SUNSHINE_CONFIGHTTP_H diff --git a/sunshine/main.cpp b/sunshine/main.cpp index c21f81fc..2938e69f 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -17,6 +17,7 @@ #include "video.h" #include "input.h" #include "nvhttp.h" +#include "confighttp.h" #include "rtsp.h" #include "config.h" #include "thread_pool.h" @@ -124,19 +125,8 @@ int main(int argc, char *argv[]) { shutdown_event->raise(true); }); - auto proc_opt = proc::parse(config::stream.file_apps); - if(!proc_opt) { - return 7; - } - - { - proc::ctx_t ctx; - ctx.name = "Desktop"s; - proc_opt->get_apps().emplace(std::begin(proc_opt->get_apps()), std::move(ctx)); - } - - proc::proc = std::move(*proc_opt); - + proc::refresh(config::stream.file_apps); + auto deinit_guard = platf::init(); input::init(); reed_solomon_init(); @@ -147,6 +137,7 @@ int main(int argc, char *argv[]) { task_pool.start(1); std::thread httpThread { nvhttp::start, shutdown_event }; + std::thread configThread { confighttp::start, shutdown_event }; stream::rtpThread(shutdown_event); httpThread.join(); diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index eb90540a..9958f193 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -7,7 +7,8 @@ #include #include - +#include +#include #include "thread_safe.h" #define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA" @@ -16,6 +17,7 @@ namespace nvhttp { void start(std::shared_ptr shutdown_event); +template void pin(std::shared_ptr::Response> response, std::shared_ptr::Request> request); } #endif //SUNSHINE_NVHTTP_H diff --git a/sunshine/process.cpp b/sunshine/process.cpp index ec2aa2fe..c61b2481 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -302,6 +302,11 @@ void refresh(const std::string &file_name) { auto proc_opt = proc::parse(file_name); if(proc_opt) { + { + proc::ctx_t ctx; + ctx.name = "Desktop"s; + proc_opt->get_apps().emplace(std::begin(proc_opt->get_apps()), std::move(ctx)); + } proc = std::move(*proc_opt); } } From 04421d84a34c99598c0c10747103cd17807fbe9b Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Tue, 11 May 2021 22:19:29 +0200 Subject: [PATCH 02/15] Fix Indentations and Shutdown Handling --- sunshine/confighttp.cpp | 456 ++++++++++++++++++++++------------------ 1 file changed, 246 insertions(+), 210 deletions(-) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index aa923f11..8241c9fe 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -30,233 +30,269 @@ std::string read_file(std::string path); namespace confighttp { - using namespace std::literals; - constexpr auto PORT_HTTP = 47990; +using namespace std::literals; +constexpr auto PORT_HTTP = 47990; - namespace fs = std::filesystem; - namespace pt = boost::property_tree; +namespace fs = std::filesystem; +namespace pt = boost::property_tree; - using https_server_t = SimpleWeb::Server; +using https_server_t = SimpleWeb::Server; - using args_t = SimpleWeb::CaseInsensitiveMultimap; - using resp_https_t = std::shared_ptr::Response>; - using req_https_t = std::shared_ptr::Request>; +using args_t = SimpleWeb::CaseInsensitiveMultimap; +using resp_https_t = std::shared_ptr::Response>; +using req_https_t = std::shared_ptr::Request>; - enum class op_e +enum class op_e +{ + ADD, + REMOVE +}; + +template +void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + pt::ptree tree; + tree.put("root..status_code", 404); + + std::ostringstream data; + + pt::write_xml(data, tree); + response->write(data.str()); + + *response << "HTTP/1.1 404 NOT FOUND\r\n" + << data.str(); +} + +void getIndexPage(resp_https_t response, req_https_t request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "index.html"); + response->write(header + content); +} + +template +void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "pin.html"); + response->write(header + content); +} + +template +void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "apps.html"); + response->write(header + content); +} + +template +void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "clients.html"); + response->write(header + content); +} + +template +void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "config.html"); + response->write(header + content); +} + +void getApps(resp_https_t response, req_https_t request) +{ + std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + response->write(content); +} + +void saveApp(resp_https_t response, req_https_t request) +{ + std::stringstream ss; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree inputTree, fileTree; + try + { + //TODO: Input Validation + pt::read_json(ss, inputTree); + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = inputTree.get("index"); + BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); + if (inputTree.get_child("prep-cmd").empty()) + inputTree.erase("prep-cmd"); + inputTree.erase("index"); + if (index == -1) { - ADD, - REMOVE - }; - - template - void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - pt::ptree tree; - tree.put("root..status_code", 404); - - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - - *response << "HTTP/1.1 404 NOT FOUND\r\n" - << data.str(); + apps_node.push_back(std::make_pair("", inputTree)); } - - void getIndexPage(resp_https_t response, req_https_t request) + else { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "index.html"); - response->write(header + content); - } - - template - void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "pin.html"); - response->write(header + content); - } - - template - void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "apps.html"); - response->write(header + content); - } - - template - void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "clients.html"); - response->write(header + content); - } - - template - void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "config.html"); - response->write(header + content); - } - - void getApps(resp_https_t response, req_https_t request) - { - std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - response->write(content); - } - - void saveApp(resp_https_t response, req_https_t request) - { - std::stringstream ss; - ss << request->content.rdbuf(); - pt::ptree outputTree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_json(data, outputTree); - response->write(data.str()); - }); - pt::ptree inputTree,fileTree; - try { - //TODO: Input Validation - pt::read_json(ss, inputTree); - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - auto &apps_node = fileTree.get_child("apps"s); - int index = inputTree.get("index"); - BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); - if(inputTree.get_child("prep-cmd").empty())inputTree.erase("prep-cmd"); - inputTree.erase("index"); - if(index == -1){ - apps_node.push_back(std::make_pair("",inputTree)); - } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick - pt::ptree newApps; - int i = 0; - for (const auto& kv : apps_node) { - if(i == index){ - newApps.push_back(std::make_pair("",inputTree)); - } else { - newApps.push_back(std::make_pair("",kv.second)); - } - i++; - } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps",newApps)); - } - pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - outputTree.put("status","true"); - proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - } catch (std::exception &e) { - BOOST_LOG(warning) << e.what(); - outputTree.put("status","false"); - outputTree.put("error","Invalid Input JSON"); - return; - } - } - - void deleteApp(resp_https_t response, req_https_t request) - { - pt::ptree outputTree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_json(data, outputTree); - response->write(data.str()); - }); - pt::ptree fileTree; - try { - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - auto &apps_node = fileTree.get_child("apps"s); - int index = stoi(request->path_match[1]); - BOOST_LOG(info) << index; - if(index <= 0){ - outputTree.put("status","false"); - outputTree.put("error","Invalid Index"); - return; - } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick - pt::ptree newApps; - int i = 0; - for (const auto& kv : apps_node) { - if(i != index){ - newApps.push_back(std::make_pair("",kv.second)); - } - i++; - } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps",newApps)); - } - pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - outputTree.put("status","true"); - proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - } catch (std::exception &e) { - BOOST_LOG(warning) << e.what(); - outputTree.put("status","false"); - outputTree.put("error","Invalid File JSON"); - return; - } - } - - void start(std::shared_ptr shutdown_event) - { - 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 http_server { ctx, 0 }; - http_server.default_resource = not_found; - http_server.resource["^/$"]["GET"] = getIndexPage; - http_server.resource["^/pin$"]["GET"] = getPinPage; - http_server.resource["^/apps$"]["GET"] = getAppsPage; - http_server.resource["^/api/apps$"]["GET"] = getApps; - http_server.resource["^/api/apps$"]["POST"] = saveApp; - http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; - http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; - - try + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto &kv : apps_node) + { + if (i == index) { - http_server.bind(); - BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + newApps.push_back(std::make_pair("", inputTree)); } - catch (boost::system::system_error &err) + else { - BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); - - shutdown_event->raise(true); - return; + newApps.push_back(std::make_pair("", kv.second)); } - - std::thread tcp{&https_server_t::accept_and_run, &http_server}; - - // Wait for any event - shutdown_event->view(); - - http_server.stop(); - - tcp.join(); + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps", newApps)); } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status", "true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid Input JSON"); + return; + } +} + +void deleteApp(resp_https_t response, req_https_t request) +{ + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree fileTree; + try + { + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = stoi(request->path_match[1]); + BOOST_LOG(info) << index; + if (index <= 0) + { + outputTree.put("status", "false"); + outputTree.put("error", "Invalid Index"); + return; + } + else + { + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto &kv : apps_node) + { + if (i != index) + { + newApps.push_back(std::make_pair("", kv.second)); + } + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps", newApps)); + } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status", "true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid File JSON"); + return; + } +} + +void start(std::shared_ptr shutdown_event) +{ + 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 http_server{ctx, 0}; + http_server.default_resource = not_found; + http_server.resource["^/$"]["GET"] = getIndexPage; + http_server.resource["^/pin$"]["GET"] = getPinPage; + http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/api/apps$"]["GET"] = getApps; + http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + http_server.config.reuse_address = true; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; + + try + { + http_server.bind(); + BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + } + catch (boost::system::system_error &err) + { + BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); + + shutdown_event->raise(true); + return; + } + auto accept_and_run = [&](auto *http_server) { + try + { + http_server->accept_and_run(); + } + 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 Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + shutdown_event->raise(true); + return; + } + }; + std::thread tcp{accept_and_run, &http_server}; + + // Wait for any event + shutdown_event->view(); + + http_server.stop(); + + tcp.join(); +} } std::string read_file(std::string path) { - std::ifstream in(path); + std::ifstream in(path); - std::string input; - std::string base64_cert; + std::string input; + std::string base64_cert; - //FIXME: Being unable to read file could result in infinite loop - while (!in.eof()) - { - std::getline(in, input); - base64_cert += input + '\n'; - } + //FIXME: Being unable to read file could result in infinite loop + while (!in.eof()) + { + std::getline(in, input); + base64_cert += input + '\n'; + } - return base64_cert; + return base64_cert; } \ No newline at end of file From 27a1144217f16a4821fb698a880b6d2c4c40b4b2 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Tue, 11 May 2021 23:38:45 +0200 Subject: [PATCH 03/15] Moved Common HTTPS Initialization Logic in a common file --- CMakeLists.txt | 2 + sunshine/confighttp.cpp | 2 +- sunshine/httpcommon.cpp | 148 ++++++++++++++++++++++++++++++++++++++++ sunshine/httpcommon.h | 7 ++ sunshine/main.cpp | 4 +- sunshine/nvhttp.cpp | 117 +++---------------------------- 6 files changed, 168 insertions(+), 112 deletions(-) create mode 100644 sunshine/httpcommon.cpp create mode 100644 sunshine/httpcommon.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a85d880d..1dc30920 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,8 @@ set(SUNSHINE_TARGET_FILES sunshine/crypto.h sunshine/nvhttp.cpp sunshine/nvhttp.h + sunshine/httpcommon.cpp + sunshine/httpcommon.h sunshine/confighttp.cpp sunshine/confighttp.h sunshine/rtsp.cpp diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index 8241c9fe..c71fc0f7 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -264,7 +264,7 @@ void start(std::shared_ptr shutdown_event) return; } - BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTP << ", "sv << PORT_HTTP << "]: "sv << err.what(); shutdown_event->raise(true); return; } diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp new file mode 100644 index 00000000..6c05dba9 --- /dev/null +++ b/sunshine/httpcommon.cpp @@ -0,0 +1,148 @@ +#include "process.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include "config.h" +#include "utility.h" +#include "rtsp.h" +#include "crypto.h" +#include "nvhttp.h" +#include "platform/common.h" +#include "network.h" +#include "uuid.h" +#include "main.h" +#include "httpcommon.h" + +namespace http +{ + using namespace std::literals; + namespace fs = std::filesystem; + + int create_creds(const std::string &pkey, const std::string &cert); + std::string read_file(const char *path); + int write_file(const char *path, const std::string_view &contents); + std::string unique_id; + + void init(std::shared_ptr shutdown_event) + { + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; + if (clean_slate) + { + unique_id = util::uuid_t::generate().string(); + auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; + config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); + config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); + } + + if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) + { + if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) + { + shutdown_event->raise(true); + return; + } + } + } + + int create_creds(const std::string &pkey, const std::string &cert) + { + fs::path pkey_path = pkey; + fs::path cert_path = cert; + + auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); + + auto pkey_dir = pkey_path; + auto cert_dir = cert_path; + pkey_dir.remove_filename(); + cert_dir.remove_filename(); + + std::error_code err_code{}; + fs::create_directories(pkey_dir, err_code); + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); + return -1; + } + + fs::create_directories(cert_dir, err_code); + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); + return -1; + } + + if (write_file(pkey.c_str(), creds.pkey)) + { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; + return -1; + } + + if (write_file(cert.c_str(), creds.x509)) + { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; + return -1; + } + + fs::permissions(pkey_path, + fs::perms::owner_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); + return -1; + } + + fs::permissions(cert_path, + fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if (err_code) + { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); + return -1; + } + + return 0; + } + int write_file(const char *path, const std::string_view &contents) + { + std::ofstream out(path); + + if (!out.is_open()) + { + return -1; + } + + out << contents; + + return 0; + } + + std::string read_file(const char *path) + { + std::ifstream in(path); + + std::string input; + std::string base64_cert; + + //FIXME: Being unable to read file could result in infinite loop + while (!in.eof()) + { + std::getline(in, input); + base64_cert += input + '\n'; + } + + return base64_cert; + } +} \ No newline at end of file diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h new file mode 100644 index 00000000..5320025a --- /dev/null +++ b/sunshine/httpcommon.h @@ -0,0 +1,7 @@ +namespace http{ + void init(std::shared_ptr shutdown_event); + int create_creds(const std::string &pkey, const std::string &cert); + std::string read_file(const char *path); + int write_file(const char *path, const std::string_view &contents); + extern std::string unique_id; +} \ No newline at end of file diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 15248deb..defaa348 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -17,6 +17,7 @@ #include "video.h" #include "input.h" #include "nvhttp.h" +#include "httpcommon.h" #include "confighttp.h" #include "rtsp.h" #include "config.h" @@ -137,13 +138,12 @@ int main(int argc, char *argv[]) { if(video::init()) { return 2; } - + http::init(shutdown_event); task_pool.start(1); std::thread httpThread { nvhttp::start, shutdown_event }; std::thread configThread { confighttp::start, shutdown_event }; stream::rtpThread(shutdown_event); - httpThread.join(); task_pool.stop(); task_pool.join(); diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 98f5a79d..8799dc1f 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -25,6 +25,7 @@ #include "network.h" #include "uuid.h" #include "main.h" +#include "httpcommon.h" namespace nvhttp { @@ -38,9 +39,6 @@ constexpr auto GFE_VERSION = "3.12.0.1"; namespace fs = std::filesystem; namespace pt = boost::property_tree; -std::string read_file(const char *path); -int write_file(const char *path, const std::string_view &contents); - using https_server_t = SimpleWeb::Server; using http_server_t = SimpleWeb::Server; @@ -78,7 +76,6 @@ struct pair_session_t { // uniqueID, session std::unordered_map map_id_sess; std::unordered_map map_id_client; -std::string unique_id; net::net_e origin_pin_allowed; using args_t = SimpleWeb::CaseInsensitiveMultimap; @@ -95,7 +92,7 @@ enum class op_e { void save_state() { pt::ptree root; - root.put("root.uniqueid", unique_id); + root.put("root.uniqueid", http::unique_id); auto &nodes = root.add_child("root.devices", pt::ptree {}); for(auto &[_,client] : map_id_client) { pt::ptree node; @@ -120,7 +117,7 @@ void load_state() { auto file_state = fs::current_path() / config::nvhttp.file_state; if(!fs::exists(file_state)) { - unique_id = util::uuid_t::generate().string(); + http::unique_id = util::uuid_t::generate().string(); return; } @@ -133,7 +130,7 @@ void load_state() { return; } - unique_id = root.get("root.uniqueid"); + http::unique_id = root.get("root.uniqueid"); auto device_nodes = root.get_child("root.devices"); for(auto &[_,device_node] : device_nodes) { @@ -474,7 +471,7 @@ void serverinfo(std::shared_ptr::Response> res tree.put("root.appversion", VERSION); tree.put("root.GfeVersion", GFE_VERSION); - tree.put("root.uniqueid", unique_id); + tree.put("root.uniqueid", http::unique_id); tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address())); tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0"); tree.put("root.LocalIP", request->local_endpoint_address()); @@ -677,88 +674,17 @@ void appasset(resp_https_t response, req_https_t request) { response->write(SimpleWeb::StatusCode::success_ok, in); } -int create_creds(const std::string &pkey, const std::string &cert) { - fs::path pkey_path = pkey; - fs::path cert_path = cert; - - auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); - - auto pkey_dir = pkey_path; - auto cert_dir = cert_path; - pkey_dir.remove_filename(); - cert_dir.remove_filename(); - - std::error_code err_code{}; - fs::create_directories(pkey_dir, err_code); - if (err_code) { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); - return -1; - } - - fs::create_directories(cert_dir, err_code); - if (err_code) { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); - return -1; - } - - if (write_file(pkey.c_str(), creds.pkey)) { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; - return -1; - } - - if (write_file(cert.c_str(), creds.x509)) { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; - return -1; - } - - fs::permissions(pkey_path, - fs::perms::owner_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); - return -1; - } - - fs::permissions(cert_path, - fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); - return -1; - } - - return 0; -} - void start(std::shared_ptr shutdown_event) { + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - if(clean_slate) { - unique_id = util::uuid_t::generate().string(); - - auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; - - config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); - config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); - } - - - if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) { - if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) { - shutdown_event->raise(true); - return; - } - } - origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); if(!clean_slate) { load_state(); } - conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); - conf_intern.servercert = read_file(config::nvhttp.cert.c_str()); + conf_intern.pkey = http::read_file(config::nvhttp.pkey.c_str()); + conf_intern.servercert = http::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); @@ -872,31 +798,4 @@ void start(std::shared_ptr shutdown_event) { ssl.join(); tcp.join(); } - -int write_file(const char *path, const std::string_view &contents) { - std::ofstream out(path); - - if(!out.is_open()) { - return -1; - } - - out << contents; - - return 0; -} - -std::string read_file(const char *path) { - std::ifstream in(path); - - std::string input; - std::string base64_cert; - - //FIXME: Being unable to read file could result in infinite loop - while(!in.eof()) { - std::getline(in, input); - base64_cert += input + '\n'; - } - - return base64_cert; -} } From 03236a50e555fefd0417a35ff6f13159871b79ea Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 16 May 2021 20:06:06 +0200 Subject: [PATCH 04/15] UI for Application Config --- assets/web/config.html | 414 +++++++++++++++++++++++++++++++++++++++- assets/web/header.html | 5 +- sunshine/config.cpp | 1 - sunshine/config.h | 2 + sunshine/confighttp.cpp | 79 ++++++++ 5 files changed, 492 insertions(+), 9 deletions(-) diff --git a/assets/web/config.html b/assets/web/config.html index c63ec39d..eb15bfe2 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -1,4 +1,410 @@ -
-

Hello, Sunshine!

-

Placeholer for config page

-
\ No newline at end of file +
+

Configuration

+
+ + + +
+ +
+ + +
The name displayed by Moonlight. If not specified, the PC's hostname is used +
+
+ +
+ + +
The minimum log level printed to standard out
+
+ +
+ + +
The origin of the remote endpoint address that is not denied for HTTP method /pin +
+
+ +
+ + +
If no external IP address is given, the local IP address is used
+
+ +
+ + +
How long to wait in milliseconds for data from moonlight before shutting down the + stream
+
+
+ +
+ +
+ + +
The private key must be 2048 bits
+
+ +
+ + +
The certificate must be signed with a 2048 bit key
+
+ + +
+ + +
The file where current state of Sunshine is stored
+
+ +
+ + +
The file where current apps of Sunshine are stored
+
+
+
+ +
+ + +
+ The back/select button on the controller.
+ On the Shield, the home and powerbutton are not passed to Moonlight.
+ If, after the timeout, the back button is still pressed down, Home/Guide button press is + emulated.
+ If back_button_timeout < 0, then the Home/Guide button will not be emulated
+
+
+ +
+ + +
+ Control how fast keys will repeat themselves
+ The initial delay in milliseconds before repeating keys +
+
+ +
+ + +
+ How often keys repeat every second
+ This configurable option supports decimals +
+
+
+ +
+ +
+ + +
+ The name of the audio sink used for Audio Loopback
+ You can find the name of the audio sink using the following command:
+ tools\audio-info.exe +
+
+ The name of the audio sink used for Audio Loopback
+ If you do not specify this variable, pulseaudio will select the default monitor device.
+
+ You can find the name of the audio sink using the following command:
+ pacmd list-sources | grep "name:"
+
+
+ +
+ + +
+ You can select the video card you want to stream:
+ The appropriate values can be found using the following command:
+ tools\dxgi-info.exe +
+
+ +
+ + +
+ You can select the video card you want to stream:
+ The appropriate values can be found using the following command:
+ tools\dxgi-info.exe
+ !! Linux only !!
+ Set the display number to stream. I have no idea how they are numbered. They start from 1, + usually.
+ output_name = 1
+
+
+
+
+ +
+ + +
+ Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still + image, + resulting in constant perceived quality
+ Higher value means more compression, but less quality
+ If crf == 0, then use QP directly instead +
+
+ +
+ + +
+ Quantitization Parameter
+ Higher value means more compression, but less quality
+ If crf != 0, then this parameter is ignored +
+
+ +
+ + +
+ Minimum number of threads used by ffmpeg to encode the video.
+ Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
+ worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
+ value that can reliably encode at your desired streaming settings on your hardware. +
+
+ +
+ + +
+ Allows the client to request HEVC Main or HEVC Main10 video streams.
+ HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software + encoding. +
+
+ +
+ + +
+ Force a specific encoder, otherwise Sunshine will use the first encoder that is available +
+
+ +
+ + +
+ How much error correcting packets must be send for every video.
+ This is just some random number, don't know the optimal value.
+ The higher fec_percentage, the lower space for the actual data to send per frame there is +
+
+ +
+ + +
+ When multicasting, it could be useful to have different configurations for each connected Client. + For example: +
    +
  • Clients connected through WAN and LAN have different bitrate contstraints.
  • +
  • Decoders may require different settings for color
  • +
+ Unlike simply broadcasting to multiple Client, this will generate distinct video streams.
+ Note, CPU usage increases for each distinct video stream generated +
+
+
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/assets/web/header.html b/assets/web/header.html index 59c11c2f..9c621101 100644 --- a/assets/web/header.html +++ b/assets/web/header.html @@ -11,7 +11,7 @@ - + @@ -34,9 +34,6 @@ - diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 9408578c..d2a97d2e 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include diff --git a/sunshine/config.h b/sunshine/config.h index f67ac12b..1dbcabbc 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace config { struct video_t { @@ -96,6 +97,7 @@ extern input_t input; extern sunshine_t sunshine; int parse(int argc, char *argv[]); +std::unordered_map parse_config(std::string_view file_content); } #endif diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index c71fc0f7..670b9204 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -21,6 +21,7 @@ #include "crypto.h" #include "confighttp.h" #include "platform/common.h" +#include "httpcommon.h" #include "network.h" #include "nvhttp.h" #include "uuid.h" @@ -219,6 +220,82 @@ void deleteApp(resp_https_t response, req_https_t request) } } +void getConfig(resp_https_t response, req_https_t request) +{ + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + try + { + outputTree.put("status","true"); + #ifdef _WIN32 + outputTree.put("platform","windows"); + #elif + outputTree.put("platform","linux"); + #endif + const char *config_file = SUNSHINE_ASSETS_DIR "/sunshine.conf"; + std::ifstream in { config_file }; + + if(!in.is_open()) { + std::cout << "Error: Couldn't open "sv << config_file << std::endl; + } + + auto vars = config::parse_config(std::string { + // Quick and dirty + std::istreambuf_iterator(in), + std::istreambuf_iterator() + }); + + for(auto &[name,value] : vars) { + outputTree.put(std::move(name), std::move(value)); + } + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid File JSON"); + return; + } +} + +void saveConfig(resp_https_t response, req_https_t request){ + std::stringstream ss; + std::stringstream configStream; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree inputTree; + try + { + //TODO: Input Validation + pt::read_json(ss, inputTree); + for (const auto &kv : inputTree) + { + std::string value = inputTree.get(kv.first); + if(value.length() == 0 || value.compare("null") == 0)continue; + configStream << kv.first << " = " << value << std::endl; + } + http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf",configStream.str()); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", e.what()); + return; + } +} + void start(std::shared_ptr shutdown_event) { auto ctx = std::make_shared(boost::asio::ssl::context::tls); @@ -231,6 +308,8 @@ void start(std::shared_ptr shutdown_event) http_server.resource["^/apps$"]["GET"] = getAppsPage; http_server.resource["^/api/apps$"]["GET"] = getApps; http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/config$"]["GET"] = getConfig; + http_server.resource["^/api/config$"]["POST"] = saveConfig; http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; http_server.resource["^/clients$"]["GET"] = getClientsPage; http_server.resource["^/config$"]["GET"] = getConfigPage; From fe08c241ece3855bd22f7a0d11bc827ace7d7e4c Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Mon, 17 May 2021 21:30:03 +0200 Subject: [PATCH 05/15] Fixes --- CMakeLists.txt | 2 ++ sunshine/confighttp.cpp | 13 +++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1dc30920..61426cb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ if(WIN32) set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") endif() + add_compile_definitions(SUNSHINE_PLATFORM="windows") add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json") @@ -82,6 +83,7 @@ if(WIN32) set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650") set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") else() + add_compile_definitions(SUNSHINE_PLATFORM="linux") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") find_package(X11 REQUIRED) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index 670b9204..2b859c29 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -181,7 +181,7 @@ void deleteApp(resp_https_t response, req_https_t request) pt::ptree fileTree; try { - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + pt::read_json(config::stream.file_apps, fileTree); auto &apps_node = fileTree.get_child("apps"s); int index = stoi(request->path_match[1]); BOOST_LOG(info) << index; @@ -193,7 +193,7 @@ void deleteApp(resp_https_t response, req_https_t request) } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; for (const auto &kv : apps_node) @@ -232,11 +232,7 @@ void getConfig(resp_https_t response, req_https_t request) try { outputTree.put("status","true"); - #ifdef _WIN32 - outputTree.put("platform","windows"); - #elif - outputTree.put("platform","linux"); - #endif + outputTree.put("platform",SUNSHINE_PLATFORM); const char *config_file = SUNSHINE_ASSETS_DIR "/sunshine.conf"; std::ifstream in { config_file }; @@ -263,7 +259,8 @@ void getConfig(resp_https_t response, req_https_t request) } } -void saveConfig(resp_https_t response, req_https_t request){ +void saveConfig(resp_https_t response, req_https_t request) +{ std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); From 57f444357d64e4536a89c0a33a5951aa02d5965a Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Mon, 17 May 2021 21:56:55 +0200 Subject: [PATCH 06/15] Web UI IP Based Authentication --- assets/web/config.html | 6 +++--- sunshine/confighttp.cpp | 22 ++++++++++++++++++++++ sunshine/httpcommon.cpp | 2 ++ sunshine/httpcommon.h | 2 ++ sunshine/nvhttp.cpp | 4 +--- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/assets/web/config.html b/assets/web/config.html index eb15bfe2..fa9612d9 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -35,9 +35,9 @@
The origin of the remote endpoint address that is not denied for HTTP method /pin
diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index 2b859c29..e8fe4184 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -49,6 +49,18 @@ enum class op_e REMOVE }; +bool authenticate(resp_https_t response, req_https_t request) +{ +auto address = request->remote_endpoint_address(); + auto ip_type = net::from_address(address); + if(ip_type > http::origin_pin_allowed) { + BOOST_LOG(info) << '[' << address << "] -- denied"sv; + response->write(SimpleWeb::StatusCode::client_error_forbidden); + return false; + } + return true; +} + template void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { @@ -66,6 +78,7 @@ void not_found(std::shared_ptr::Response> resp void getIndexPage(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "index.html"); response->write(header + content); @@ -74,6 +87,7 @@ void getIndexPage(resp_https_t response, req_https_t request) template void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "pin.html"); response->write(header + content); @@ -82,6 +96,7 @@ void getPinPage(std::shared_ptr::Response> res template void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "apps.html"); response->write(header + content); @@ -90,6 +105,7 @@ void getAppsPage(std::shared_ptr::Response> re template void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "clients.html"); response->write(header + content); @@ -98,6 +114,7 @@ void getClientsPage(std::shared_ptr::Response> template void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response,request))return; std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "config.html"); response->write(header + content); @@ -105,12 +122,14 @@ void getConfigPage(std::shared_ptr::Response> void getApps(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); response->write(content); } void saveApp(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::stringstream ss; ss << request->content.rdbuf(); pt::ptree outputTree; @@ -171,6 +190,7 @@ void saveApp(resp_https_t response, req_https_t request) void deleteApp(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -222,6 +242,7 @@ void deleteApp(resp_https_t response, req_https_t request) void getConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -261,6 +282,7 @@ void getConfig(resp_https_t response, req_https_t request) void saveConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response,request))return; std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 6c05dba9..53da503a 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -32,10 +32,12 @@ namespace http std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); std::string unique_id; + net::net_e origin_pin_allowed; void init(std::shared_ptr shutdown_event) { bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; + origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); if (clean_slate) { unique_id = util::uuid_t::generate().string(); diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h index 5320025a..40cc0a66 100644 --- a/sunshine/httpcommon.h +++ b/sunshine/httpcommon.h @@ -1,7 +1,9 @@ +#include "network.h" namespace http{ void init(std::shared_ptr shutdown_event); int create_creds(const std::string &pkey, const std::string &cert); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); extern std::string unique_id; + extern net::net_e origin_pin_allowed; } \ No newline at end of file diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 8799dc1f..9261ff17 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -76,7 +76,6 @@ struct pair_session_t { // uniqueID, session std::unordered_map map_id_sess; std::unordered_map map_id_client; -net::net_e origin_pin_allowed; using args_t = SimpleWeb::CaseInsensitiveMultimap; using resp_https_t = std::shared_ptr::Response>; @@ -405,7 +404,7 @@ void pin(std::shared_ptr::Response> response, auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); - if(ip_type > origin_pin_allowed) { + if(ip_type > http::origin_pin_allowed) { BOOST_LOG(info) << '[' << address << "] -- denied"sv; response->write(SimpleWeb::StatusCode::client_error_forbidden); @@ -677,7 +676,6 @@ void appasset(resp_https_t response, req_https_t request) { void start(std::shared_ptr shutdown_event) { bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); if(!clean_slate) { load_state(); From 0ea6363172dce933f43b5f3058875f6e6d7fe4d9 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Fri, 28 May 2021 22:49:27 +0200 Subject: [PATCH 07/15] Username/Password Authentication for UI --- sunshine/config.cpp | 6 ++++- sunshine/config.h | 5 ++++- sunshine/confighttp.cpp | 29 ++++++++++++++++++++++-- sunshine/crypto.cpp | 21 ++++++++++++++++++ sunshine/crypto.h | 3 +++ sunshine/httpcommon.cpp | 49 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 4 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index d2a97d2e..4e08cf1d 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -191,7 +191,11 @@ input_t input { sunshine_t sunshine { 2, // min_log_level - 0 // flags + 0, // flags + "user_credentials.json"s,//User file + ""s,//Username + ""s,//Password + ""s//Password Salt }; bool whitespace(char ch) { diff --git a/sunshine/config.h b/sunshine/config.h index 1dbcabbc..0daf0146 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -85,8 +85,11 @@ enum flag_e : std::size_t { struct sunshine_t { int min_log_level; - std::bitset flags; + std::string credentials_file; + std::string username; + std::string password; + std::string salt; }; extern video_t video; diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index e8fe4184..a707d8de 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include "config.h" @@ -49,16 +50,40 @@ enum class op_e REMOVE }; +void send_unauthorized(resp_https_t response, req_https_t request) +{ + auto address = request->remote_endpoint_address(); + BOOST_LOG(info) << '[' << address << "] -- denied"sv; + const SimpleWeb::CaseInsensitiveMultimap headers { + {"WWW-Authenticate",R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"} + }; + response->write(SimpleWeb::StatusCode::client_error_unauthorized,headers); +} + bool authenticate(resp_https_t response, req_https_t request) { -auto address = request->remote_endpoint_address(); + auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); if(ip_type > http::origin_pin_allowed) { BOOST_LOG(info) << '[' << address << "] -- denied"sv; response->write(SimpleWeb::StatusCode::client_error_forbidden); return false; } - return true; + auto auth = request->header.find("authorization"); + if(auth == request->header.end() ){ + send_unauthorized(response,request); + return false; + } + std::string rawAuth = auth->second; + std::string authData = rawAuth.substr("Basic "sv.length()); + authData = SimpleWeb::Crypto::Base64::decode(authData); + int index = authData.find(':'); + std::string username = authData.substr(0,index); + std::string password = authData.substr(index + 1); + std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + if(username == config::sunshine.username && hash == config::sunshine.password) return true; + send_unauthorized(response,request); + return false; } template diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index 398c8e09..b4a5ce80 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -4,6 +4,7 @@ #include #include "crypto.h" +#include namespace crypto { using big_num_t = util::safe_ptr; //using rsa_t = util::safe_ptr; @@ -338,4 +339,24 @@ bool verify256(const x509_t &x509, const std::string_view &data, const std::stri void md_ctx_destroy(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } + +std::string rand_string(std::size_t bytes) +{ + std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"; + std::string value = rand(bytes); + for (std::size_t i = 0; i != value.size(); ++i) { + value[i] = alphabet[value[i] % alphabet.length()]; + } + return value; +} + +std::string hash_hexstr(const std::string_view &plaintext) +{ + sha256_t hashBytes = crypto::hash(plaintext); + std::ostringstream hashStream; + hashStream << std::hex << std::setfill( '0' ); + std::for_each( hashBytes.cbegin(), hashBytes.cend(), [&]( int c ) { hashStream << std::setw( 2 ) << c; } ); + std::string hashString = hashStream.str(); + return hashString; +} } diff --git a/sunshine/crypto.h b/sunshine/crypto.h index faf1fc1f..f0781597 100644 --- a/sunshine/crypto.h +++ b/sunshine/crypto.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "utility.h" @@ -35,6 +36,7 @@ using bio_t = util::safe_ptr; using pkey_t = util::safe_ptr; sha256_t hash(const std::string_view &plaintext); +std::string hash_hexstr(const std::string_view &plaintext); aes_t gen_aes_key(const std::array &salt, const std::string_view &pin); x509_t x509(const std::string_view &x); @@ -50,6 +52,7 @@ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits); std::string_view signature(const x509_t &x); std::string rand(std::size_t bytes); +std::string rand_string(std::size_t bytes); class cert_chain_t { public: diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 53da503a..b8763ffe 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -27,8 +27,11 @@ namespace http { using namespace std::literals; namespace fs = std::filesystem; + namespace pt = boost::property_tree; int create_creds(const std::string &pkey, const std::string &cert); + int generate_user_creds(const std::string &file); + int reload_user_creds(const std::string &file); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); std::string unique_id; @@ -54,8 +57,54 @@ namespace http return; } } + if(!fs::exists(config::sunshine.credentials_file)){ + if(generate_user_creds(config::sunshine.credentials_file)){ + shutdown_event->raise(true); + return; + } + } + if(reload_user_creds(config::sunshine.credentials_file)){ + shutdown_event->raise(true); + return; + } } + int generate_user_creds(const std::string &file) + { + pt::ptree outputTree; + try { + std::string username = "sunshine"; + std::string plainPassword = crypto::rand_string(16); + std::string salt = crypto::rand_string(16); + outputTree.put("username","sunshine"); + outputTree.put("salt",salt); + outputTree.put("password",crypto::hash_hexstr(plainPassword + salt)); + BOOST_LOG(info) << "New credentials has been created"; + BOOST_LOG(info) << "Username: " << username; + BOOST_LOG(info) << "Password: " << plainPassword; + pt::write_json(file,outputTree); + } catch (std::exception &e){ + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; + } + + int reload_user_creds(const std::string &file) + { + pt::ptree inputTree; + try { + pt::read_json(file, inputTree); + config::sunshine.username = inputTree.get("username"); + config::sunshine.password = inputTree.get("password"); + config::sunshine.salt = inputTree.get("salt"); + } catch(std::exception &e){ + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; + } + int create_creds(const std::string &pkey, const std::string &cert) { fs::path pkey_path = pkey; From 4835366a0ca3e6376131e10819e4cf90b26c471c Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 22:26:17 +0200 Subject: [PATCH 08/15] Fixed Build --- sunshine/config.cpp | 1 + sunshine/config.h | 1 + sunshine/nvhttp.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index f28e5f76..a990ccf6 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include diff --git a/sunshine/config.h b/sunshine/config.h index 957544c9..55b9296d 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -105,4 +105,5 @@ extern sunshine_t sunshine; int parse(int argc, char *argv[]); std::unordered_map parse_config(std::string_view file_content); +} #endif diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 4601a93a..b7d90132 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -857,4 +857,5 @@ void start(std::shared_ptr shutdown_event) { ssl.join(); tcp.join(); +} } \ No newline at end of file From ffb80c5fc3b1cc52c41c24ae38f4f16dc992ba35 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 22:29:10 +0200 Subject: [PATCH 09/15] Fix Format --- sunshine/config.cpp | 12 +- sunshine/config.h | 4 +- sunshine/confighttp.cpp | 254 +++++++++++++------------------ sunshine/confighttp.h | 2 +- sunshine/crypto.cpp | 18 +-- sunshine/crypto.h | 3 +- sunshine/httpcommon.cpp | 320 +++++++++++++++++++--------------------- sunshine/httpcommon.h | 16 +- sunshine/main.cpp | 6 +- sunshine/nvhttp.cpp | 12 +- sunshine/nvhttp.h | 11 +- 11 files changed, 298 insertions(+), 360 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index a990ccf6..f2cf391e 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -209,12 +209,12 @@ input_t input { }; sunshine_t sunshine { - 2, // min_log_level - 0, // flags - "user_credentials.json"s,//User file - ""s,//Username - ""s,//Password - ""s//Password Salt + 2, // min_log_level + 0, // flags + "user_credentials.json"s, //User file + ""s, //Username + ""s, //Password + ""s //Password Salt }; bool endline(char ch) { diff --git a/sunshine/config.h b/sunshine/config.h index 55b9296d..99f50408 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include #include namespace config { @@ -105,5 +105,5 @@ extern sunshine_t sunshine; int parse(int argc, char *argv[]); std::unordered_map parse_config(std::string_view file_content); -} +} // namespace config #endif diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index a707d8de..fb20a8dd 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -6,32 +6,31 @@ #include +#include #include #include -#include #include -#include #include +#include #include #include "config.h" -#include "utility.h" -#include "rtsp.h" -#include "crypto.h" #include "confighttp.h" -#include "platform/common.h" +#include "crypto.h" #include "httpcommon.h" +#include "main.h" #include "network.h" #include "nvhttp.h" +#include "platform/common.h" +#include "rtsp.h" +#include "utility.h" #include "uuid.h" -#include "main.h" std::string read_file(std::string path); -namespace confighttp -{ +namespace confighttp { using namespace std::literals; constexpr auto PORT_HTTP = 47990; @@ -40,28 +39,25 @@ namespace pt = boost::property_tree; using https_server_t = SimpleWeb::Server; -using args_t = SimpleWeb::CaseInsensitiveMultimap; +using args_t = SimpleWeb::CaseInsensitiveMultimap; using resp_https_t = std::shared_ptr::Response>; -using req_https_t = std::shared_ptr::Request>; +using req_https_t = std::shared_ptr::Request>; -enum class op_e -{ +enum class op_e { ADD, REMOVE }; -void send_unauthorized(resp_https_t response, req_https_t request) -{ +void send_unauthorized(resp_https_t response, req_https_t request) { auto address = request->remote_endpoint_address(); BOOST_LOG(info) << '[' << address << "] -- denied"sv; const SimpleWeb::CaseInsensitiveMultimap headers { - {"WWW-Authenticate",R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"} + { "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" } }; - response->write(SimpleWeb::StatusCode::client_error_unauthorized,headers); + response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers); } -bool authenticate(resp_https_t response, req_https_t request) -{ +bool authenticate(resp_https_t response, req_https_t request) { auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); if(ip_type > http::origin_pin_allowed) { @@ -70,25 +66,24 @@ bool authenticate(resp_https_t response, req_https_t request) return false; } auto auth = request->header.find("authorization"); - if(auth == request->header.end() ){ - send_unauthorized(response,request); + if(auth == request->header.end()) { + send_unauthorized(response, request); return false; } - std::string rawAuth = auth->second; + std::string rawAuth = auth->second; std::string authData = rawAuth.substr("Basic "sv.length()); - authData = SimpleWeb::Crypto::Base64::decode(authData); - int index = authData.find(':'); - std::string username = authData.substr(0,index); + authData = SimpleWeb::Crypto::Base64::decode(authData); + int index = authData.find(':'); + std::string username = authData.substr(0, index); std::string password = authData.substr(index + 1); - std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); if(username == config::sunshine.username && hash == config::sunshine.password) return true; - send_unauthorized(response,request); + send_unauthorized(response, request); return false; } -template -void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ +template +void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { pt::ptree tree; tree.put("root..status_code", 404); @@ -101,60 +96,53 @@ void not_found(std::shared_ptr::Response> resp << data.str(); } -void getIndexPage(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +void getIndexPage(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "index.html"); response->write(header + content); } -template -void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "pin.html"); response->write(header + content); } -template -void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "apps.html"); response->write(header + content); } -template -void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "clients.html"); response->write(header + content); } -template -void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) -{ - if(!authenticate(response,request))return; - std::string header = read_file(WEB_DIR "header.html"); +template +void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "config.html"); response->write(header + content); } -void getApps(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void getApps(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); response->write(content); } -void saveApp(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void saveApp(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; std::stringstream ss; ss << request->content.rdbuf(); pt::ptree outputTree; @@ -165,34 +153,28 @@ void saveApp(resp_https_t response, req_https_t request) response->write(data.str()); }); pt::ptree inputTree, fileTree; - try - { + try { //TODO: Input Validation pt::read_json(ss, inputTree); pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); auto &apps_node = fileTree.get_child("apps"s); - int index = inputTree.get("index"); + int index = inputTree.get("index"); BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); - if (inputTree.get_child("prep-cmd").empty()) + if(inputTree.get_child("prep-cmd").empty()) inputTree.erase("prep-cmd"); inputTree.erase("index"); - if (index == -1) - { + if(index == -1) { apps_node.push_back(std::make_pair("", inputTree)); } - else - { + else { //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick pt::ptree newApps; int i = 0; - for (const auto &kv : apps_node) - { - if (i == index) - { + for(const auto &kv : apps_node) { + if(i == index) { newApps.push_back(std::make_pair("", inputTree)); } - else - { + else { newApps.push_back(std::make_pair("", kv.second)); } i++; @@ -204,8 +186,7 @@ void saveApp(resp_https_t response, req_https_t request) outputTree.put("status", "true"); proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", "Invalid Input JSON"); @@ -213,9 +194,8 @@ void saveApp(resp_https_t response, req_https_t request) } } -void deleteApp(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void deleteApp(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -224,27 +204,22 @@ void deleteApp(resp_https_t response, req_https_t request) response->write(data.str()); }); pt::ptree fileTree; - try - { + try { pt::read_json(config::stream.file_apps, fileTree); auto &apps_node = fileTree.get_child("apps"s); - int index = stoi(request->path_match[1]); + int index = stoi(request->path_match[1]); BOOST_LOG(info) << index; - if (index <= 0) - { + if(index <= 0) { outputTree.put("status", "false"); outputTree.put("error", "Invalid Index"); return; } - else - { + else { //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; - for (const auto &kv : apps_node) - { - if (i != index) - { + for(const auto &kv : apps_node) { + if(i != index) { newApps.push_back(std::make_pair("", kv.second)); } i++; @@ -256,8 +231,7 @@ void deleteApp(resp_https_t response, req_https_t request) outputTree.put("status", "true"); proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", "Invalid File JSON"); @@ -265,9 +239,8 @@ void deleteApp(resp_https_t response, req_https_t request) } } -void getConfig(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void getConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -275,10 +248,9 @@ void getConfig(resp_https_t response, req_https_t request) pt::write_json(data, outputTree); response->write(data.str()); }); - try - { - outputTree.put("status","true"); - outputTree.put("platform",SUNSHINE_PLATFORM); + try { + outputTree.put("status", "true"); + outputTree.put("platform", SUNSHINE_PLATFORM); const char *config_file = SUNSHINE_ASSETS_DIR "/sunshine.conf"; std::ifstream in { config_file }; @@ -289,15 +261,13 @@ void getConfig(resp_https_t response, req_https_t request) auto vars = config::parse_config(std::string { // Quick and dirty std::istreambuf_iterator(in), - std::istreambuf_iterator() - }); + std::istreambuf_iterator() }); - for(auto &[name,value] : vars) { + for(auto &[name, value] : vars) { outputTree.put(std::move(name), std::move(value)); } } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", "Invalid File JSON"); @@ -305,9 +275,8 @@ void getConfig(resp_https_t response, req_https_t request) } } -void saveConfig(resp_https_t response, req_https_t request) -{ - if(!authenticate(response,request))return; +void saveConfig(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); @@ -319,20 +288,17 @@ void saveConfig(resp_https_t response, req_https_t request) response->write(data.str()); }); pt::ptree inputTree; - try - { + try { //TODO: Input Validation pt::read_json(ss, inputTree); - for (const auto &kv : inputTree) - { + for(const auto &kv : inputTree) { std::string value = inputTree.get(kv.first); - if(value.length() == 0 || value.compare("null") == 0)continue; + if(value.length() == 0 || value.compare("null") == 0) continue; configStream << kv.first << " = " << value << std::endl; } - http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf",configStream.str()); + http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf", configStream.str()); } - catch (std::exception &e) - { + catch(std::exception &e) { BOOST_LOG(warning) << e.what(); outputTree.put("status", "false"); outputTree.put("error", e.what()); @@ -340,50 +306,44 @@ void saveConfig(resp_https_t response, req_https_t request) } } -void start(std::shared_ptr shutdown_event) -{ +void start(std::shared_ptr shutdown_event) { 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 http_server{ctx, 0}; - http_server.default_resource = not_found; - http_server.resource["^/$"]["GET"] = getIndexPage; - http_server.resource["^/pin$"]["GET"] = getPinPage; - http_server.resource["^/apps$"]["GET"] = getAppsPage; - http_server.resource["^/api/apps$"]["GET"] = getApps; - http_server.resource["^/api/apps$"]["POST"] = saveApp; - http_server.resource["^/api/config$"]["GET"] = getConfig; - http_server.resource["^/api/config$"]["POST"] = saveConfig; + https_server_t http_server { ctx, 0 }; + http_server.default_resource = not_found; + http_server.resource["^/$"]["GET"] = getIndexPage; + http_server.resource["^/pin$"]["GET"] = getPinPage; + http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/api/apps$"]["GET"] = getApps; + http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/config$"]["GET"] = getConfig; + http_server.resource["^/api/config$"]["POST"] = saveConfig; http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; - http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + http_server.config.reuse_address = true; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; - try - { + try { http_server.bind(); BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; } - catch (boost::system::system_error &err) - { + catch(boost::system::system_error &err) { BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); shutdown_event->raise(true); return; } auto accept_and_run = [&](auto *http_server) { - try - { + try { http_server->accept_and_run(); } - catch (boost::system::system_error &err) - { + 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()) - { + if(shutdown_event->peek()) { return; } @@ -392,7 +352,7 @@ void start(std::shared_ptr shutdown_event) return; } }; - std::thread tcp{accept_and_run, &http_server}; + std::thread tcp { accept_and_run, &http_server }; // Wait for any event shutdown_event->view(); @@ -401,18 +361,16 @@ void start(std::shared_ptr shutdown_event) tcp.join(); } -} +} // namespace confighttp -std::string read_file(std::string path) -{ +std::string read_file(std::string path) { std::ifstream in(path); std::string input; std::string base64_cert; //FIXME: Being unable to read file could result in infinite loop - while (!in.eof()) - { + while(!in.eof()) { std::getline(in, input); base64_cert += input + '\n'; } diff --git a/sunshine/confighttp.h b/sunshine/confighttp.h index d3b9e85e..edd7181e 100644 --- a/sunshine/confighttp.h +++ b/sunshine/confighttp.h @@ -14,7 +14,7 @@ namespace confighttp { - void start(std::shared_ptr shutdown_event); +void start(std::shared_ptr shutdown_event); } #endif //SUNSHINE_CONFIGHTTP_H diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index a49af9da..58053a03 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -6,7 +6,7 @@ #include #include namespace crypto { -using big_num_t = util::safe_ptr; +using big_num_t = util::safe_ptr; //using rsa_t = util::safe_ptr; using asn1_string_t = util::safe_ptr; @@ -340,23 +340,21 @@ void md_ctx_destroy(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } -std::string rand_string(std::size_t bytes) -{ +std::string rand_string(std::size_t bytes) { std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"; - std::string value = rand(bytes); - for (std::size_t i = 0; i != value.size(); ++i) { + std::string value = rand(bytes); + for(std::size_t i = 0; i != value.size(); ++i) { value[i] = alphabet[value[i] % alphabet.length()]; } return value; } -std::string hash_hexstr(const std::string_view &plaintext) -{ +std::string hash_hexstr(const std::string_view &plaintext) { sha256_t hashBytes = crypto::hash(plaintext); std::ostringstream hashStream; - hashStream << std::hex << std::setfill( '0' ); - std::for_each( hashBytes.cbegin(), hashBytes.cend(), [&]( int c ) { hashStream << std::setw( 2 ) << c; } ); + hashStream << std::hex << std::setfill('0'); + std::for_each(hashBytes.cbegin(), hashBytes.cend(), [&](int c) { hashStream << std::setw(2) << c; }); std::string hashString = hashStream.str(); return hashString; } -} \ No newline at end of file +} // namespace crypto \ No newline at end of file diff --git a/sunshine/crypto.h b/sunshine/crypto.h index ed6373a9..369a417c 100644 --- a/sunshine/crypto.h +++ b/sunshine/crypto.h @@ -7,12 +7,11 @@ #include #include +#include #include #include #include #include -#include -#include #include "utility.h" diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index b8763ffe..9dda7a00 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -2,9 +2,9 @@ #include +#include #include #include -#include #include @@ -13,187 +13,171 @@ #include #include "config.h" -#include "utility.h" -#include "rtsp.h" #include "crypto.h" +#include "httpcommon.h" +#include "main.h" +#include "network.h" #include "nvhttp.h" #include "platform/common.h" -#include "network.h" +#include "rtsp.h" +#include "utility.h" #include "uuid.h" -#include "main.h" -#include "httpcommon.h" -namespace http -{ - using namespace std::literals; - namespace fs = std::filesystem; - namespace pt = boost::property_tree; +namespace http { +using namespace std::literals; +namespace fs = std::filesystem; +namespace pt = boost::property_tree; - int create_creds(const std::string &pkey, const std::string &cert); - int generate_user_creds(const std::string &file); - int reload_user_creds(const std::string &file); - std::string read_file(const char *path); - int write_file(const char *path, const std::string_view &contents); - std::string unique_id; - net::net_e origin_pin_allowed; - - void init(std::shared_ptr shutdown_event) - { - bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); - if (clean_slate) - { - unique_id = util::uuid_t::generate().string(); - auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; - config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); - config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); - } +int create_creds(const std::string &pkey, const std::string &cert); +int generate_user_creds(const std::string &file); +int reload_user_creds(const std::string &file); +std::string read_file(const char *path); +int write_file(const char *path, const std::string_view &contents); +std::string unique_id; +net::net_e origin_pin_allowed; - if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) - { - if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) - { - shutdown_event->raise(true); - return; - } - } - if(!fs::exists(config::sunshine.credentials_file)){ - if(generate_user_creds(config::sunshine.credentials_file)){ - shutdown_event->raise(true); - return; - } - } - if(reload_user_creds(config::sunshine.credentials_file)){ +void init(std::shared_ptr shutdown_event) { + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; + origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); + if(clean_slate) { + unique_id = util::uuid_t::generate().string(); + auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; + config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); + config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); + } + + if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) { + if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) { shutdown_event->raise(true); return; } } - - int generate_user_creds(const std::string &file) - { - pt::ptree outputTree; - try { - std::string username = "sunshine"; - std::string plainPassword = crypto::rand_string(16); - std::string salt = crypto::rand_string(16); - outputTree.put("username","sunshine"); - outputTree.put("salt",salt); - outputTree.put("password",crypto::hash_hexstr(plainPassword + salt)); - BOOST_LOG(info) << "New credentials has been created"; - BOOST_LOG(info) << "Username: " << username; - BOOST_LOG(info) << "Password: " << plainPassword; - pt::write_json(file,outputTree); - } catch (std::exception &e){ - BOOST_LOG(fatal) << e.what(); - return 1; + if(!fs::exists(config::sunshine.credentials_file)) { + if(generate_user_creds(config::sunshine.credentials_file)) { + shutdown_event->raise(true); + return; } - return 0; + } + if(reload_user_creds(config::sunshine.credentials_file)) { + shutdown_event->raise(true); + return; + } +} + +int generate_user_creds(const std::string &file) { + pt::ptree outputTree; + try { + std::string username = "sunshine"; + std::string plainPassword = crypto::rand_string(16); + std::string salt = crypto::rand_string(16); + outputTree.put("username", "sunshine"); + outputTree.put("salt", salt); + outputTree.put("password", crypto::hash_hexstr(plainPassword + salt)); + BOOST_LOG(info) << "New credentials has been created"; + BOOST_LOG(info) << "Username: " << username; + BOOST_LOG(info) << "Password: " << plainPassword; + pt::write_json(file, outputTree); + } + catch(std::exception &e) { + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; +} + +int reload_user_creds(const std::string &file) { + pt::ptree inputTree; + try { + pt::read_json(file, inputTree); + config::sunshine.username = inputTree.get("username"); + config::sunshine.password = inputTree.get("password"); + config::sunshine.salt = inputTree.get("salt"); + } + catch(std::exception &e) { + BOOST_LOG(fatal) << e.what(); + return 1; + } + return 0; +} + +int create_creds(const std::string &pkey, const std::string &cert) { + fs::path pkey_path = pkey; + fs::path cert_path = cert; + + auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); + + auto pkey_dir = pkey_path; + auto cert_dir = cert_path; + pkey_dir.remove_filename(); + cert_dir.remove_filename(); + + std::error_code err_code {}; + fs::create_directories(pkey_dir, err_code); + if(err_code) { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); + return -1; } - int reload_user_creds(const std::string &file) - { - pt::ptree inputTree; - try { - pt::read_json(file, inputTree); - config::sunshine.username = inputTree.get("username"); - config::sunshine.password = inputTree.get("password"); - config::sunshine.salt = inputTree.get("salt"); - } catch(std::exception &e){ - BOOST_LOG(fatal) << e.what(); - return 1; - } - return 0; - } - - int create_creds(const std::string &pkey, const std::string &cert) - { - fs::path pkey_path = pkey; - fs::path cert_path = cert; - - auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); - - auto pkey_dir = pkey_path; - auto cert_dir = cert_path; - pkey_dir.remove_filename(); - cert_dir.remove_filename(); - - std::error_code err_code{}; - fs::create_directories(pkey_dir, err_code); - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); - return -1; - } - - fs::create_directories(cert_dir, err_code); - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); - return -1; - } - - if (write_file(pkey.c_str(), creds.pkey)) - { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; - return -1; - } - - if (write_file(cert.c_str(), creds.x509)) - { - BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; - return -1; - } - - fs::permissions(pkey_path, - fs::perms::owner_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); - return -1; - } - - fs::permissions(cert_path, - fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) - { - BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); - return -1; - } - - return 0; - } - int write_file(const char *path, const std::string_view &contents) - { - std::ofstream out(path); - - if (!out.is_open()) - { - return -1; - } - - out << contents; - - return 0; + fs::create_directories(cert_dir, err_code); + if(err_code) { + BOOST_LOG(fatal) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); + return -1; } - std::string read_file(const char *path) - { - std::ifstream in(path); - - std::string input; - std::string base64_cert; - - //FIXME: Being unable to read file could result in infinite loop - while (!in.eof()) - { - std::getline(in, input); - base64_cert += input + '\n'; - } - - return base64_cert; + if(write_file(pkey.c_str(), creds.pkey)) { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; + return -1; } -} \ No newline at end of file + + if(write_file(cert.c_str(), creds.x509)) { + BOOST_LOG(fatal) << "Couldn't open ["sv << config::nvhttp.cert << ']'; + return -1; + } + + fs::permissions(pkey_path, + fs::perms::owner_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if(err_code) { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); + return -1; + } + + fs::permissions(cert_path, + fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, + fs::perm_options::replace, err_code); + + if(err_code) { + BOOST_LOG(fatal) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); + return -1; + } + + return 0; +} +int write_file(const char *path, const std::string_view &contents) { + std::ofstream out(path); + + if(!out.is_open()) { + return -1; + } + + out << contents; + + return 0; +} + +std::string read_file(const char *path) { + std::ifstream in(path); + + std::string input; + std::string base64_cert; + + //FIXME: Being unable to read file could result in infinite loop + while(!in.eof()) { + std::getline(in, input); + base64_cert += input + '\n'; + } + + return base64_cert; +} +} // namespace http \ No newline at end of file diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h index 40cc0a66..4997b2b1 100644 --- a/sunshine/httpcommon.h +++ b/sunshine/httpcommon.h @@ -1,9 +1,9 @@ #include "network.h" -namespace http{ - void init(std::shared_ptr shutdown_event); - int create_creds(const std::string &pkey, const std::string &cert); - std::string read_file(const char *path); - int write_file(const char *path, const std::string_view &contents); - extern std::string unique_id; - extern net::net_e origin_pin_allowed; -} \ No newline at end of file +namespace http { +void init(std::shared_ptr shutdown_event); +int create_creds(const std::string &pkey, const std::string &cert); +std::string read_file(const char *path); +int write_file(const char *path, const std::string_view &contents); +extern std::string unique_id; +extern net::net_e origin_pin_allowed; +} // namespace http \ No newline at end of file diff --git a/sunshine/main.cpp b/sunshine/main.cpp index fd029a72..1d1693ea 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -15,9 +15,9 @@ #include #include "config.h" -#include "nvhttp.h" -#include "httpcommon.h" #include "confighttp.h" +#include "httpcommon.h" +#include "nvhttp.h" #include "rtsp.h" #include "thread_pool.h" #include "video.h" @@ -126,7 +126,7 @@ int main(int argc, char *argv[]) { }); proc::refresh(config::stream.file_apps); - + auto deinit_guard = platf::init(); if(!deinit_guard) { return 4; diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index b7d90132..e1dc5c0c 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -20,6 +20,7 @@ #include "config.h" #include "crypto.h" +#include "httpcommon.h" #include "main.h" #include "network.h" #include "nvhttp.h" @@ -27,9 +28,6 @@ #include "rtsp.h" #include "utility.h" #include "uuid.h" -#include "main.h" -#include "httpcommon.h" - namespace nvhttp { @@ -133,7 +131,7 @@ void load_state() { return; } - http::unique_id = root.get("root.uniqueid"); + http::unique_id = root.get("root.uniqueid"); auto device_nodes = root.get_child("root.devices"); for(auto &[_, device_node] : device_nodes) { @@ -731,14 +729,14 @@ void appasset(resp_https_t response, req_https_t request) { } void start(std::shared_ptr shutdown_event) { - + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; if(!clean_slate) { load_state(); } - conf_intern.pkey = http::read_file(config::nvhttp.pkey.c_str()); + conf_intern.pkey = http::read_file(config::nvhttp.pkey.c_str()); conf_intern.servercert = http::read_file(config::nvhttp.cert.c_str()); auto ctx = std::make_shared(boost::asio::ssl::context::tls); @@ -858,4 +856,4 @@ void start(std::shared_ptr shutdown_event) { ssl.join(); tcp.join(); } -} \ No newline at end of file +} // namespace nvhttp \ No newline at end of file diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index bfa3e119..18aaa969 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -5,11 +5,11 @@ #ifndef SUNSHINE_NVHTTP_H #define SUNSHINE_NVHTTP_H -#include -#include +#include "thread_safe.h" #include #include -#include "thread_safe.h" +#include +#include #define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA" #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" @@ -17,7 +17,8 @@ namespace nvhttp { void start(std::shared_ptr shutdown_event); -template void pin(std::shared_ptr::Response> response, std::shared_ptr::Request> request); -} +template +void pin(std::shared_ptr::Response> response, std::shared_ptr::Request> request); +} // namespace nvhttp #endif //SUNSHINE_NVHTTP_H From 8961b0462ed7cd6e97d4dca120e7f6782e890ef0 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 23:05:37 +0200 Subject: [PATCH 10/15] Fix Incldue Order --- sunshine/httpcommon.cpp | 12 ++++++------ sunshine/main.cpp | 4 ++-- sunshine/nvhttp.cpp | 5 +++-- sunshine/nvhttp.h | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 9dda7a00..b6f7ac0a 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -2,9 +2,9 @@ #include -#include #include #include +#include #include @@ -13,15 +13,15 @@ #include #include "config.h" +#include "utility.h" +#include "rtsp.h" #include "crypto.h" -#include "httpcommon.h" -#include "main.h" -#include "network.h" #include "nvhttp.h" #include "platform/common.h" -#include "rtsp.h" -#include "utility.h" +#include "network.h" #include "uuid.h" +#include "main.h" +#include "httpcommon.h" namespace http { using namespace std::literals; diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 1d1693ea..70301297 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -15,9 +15,9 @@ #include #include "config.h" -#include "confighttp.h" -#include "httpcommon.h" #include "nvhttp.h" +#include "httpcommon.h" +#include "confighttp.h" #include "rtsp.h" #include "thread_pool.h" #include "video.h" diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index e1dc5c0c..1793b060 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -18,9 +18,9 @@ #include #include + #include "config.h" #include "crypto.h" -#include "httpcommon.h" #include "main.h" #include "network.h" #include "nvhttp.h" @@ -28,7 +28,8 @@ #include "rtsp.h" #include "utility.h" #include "uuid.h" - +#include "main.h" +#include "httpcommon.h" namespace nvhttp { using namespace std::literals; diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index 18aaa969..db995fc8 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -5,11 +5,11 @@ #ifndef SUNSHINE_NVHTTP_H #define SUNSHINE_NVHTTP_H -#include "thread_safe.h" -#include -#include #include #include +#include +#include +#include "thread_safe.h" #define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA" #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" From ec9481392a7614a322f8f5e3b751e709c3728b0e Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sat, 29 May 2021 23:35:44 +0200 Subject: [PATCH 11/15] Password Change UI --- assets/web/config.html | 22 ++++----- assets/web/header.html | 3 ++ assets/web/password.html | 97 ++++++++++++++++++++++++++++++++++++++++ sunshine/confighttp.cpp | 64 ++++++++++++++++++++++++-- sunshine/httpcommon.h | 1 + 5 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 assets/web/password.html diff --git a/assets/web/config.html b/assets/web/config.html index fa9612d9..b2b642cb 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -1,5 +1,5 @@
-

Configuration

+

Configuration

\ No newline at end of file diff --git a/assets/web/password.html b/assets/web/password.html new file mode 100644 index 00000000..3e3f2140 --- /dev/null +++ b/assets/web/password.html @@ -0,0 +1,97 @@ +
+

Password Change

+
+
+
+

Current Credentials

+
+ + +
 
+
+
+ + +
+
+
+

New Credentials

+
+ + +
If not specified, the username will not change +
+
+
+ + +
+
+ + +
+
+
+
Error: {{error}}
+
Success! This page will reload soon, your browser will ask you for the new credentials
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index fb20a8dd..e76b8ca4 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -135,6 +135,14 @@ void getConfigPage(std::shared_ptr::Response> response->write(header + content); } +template +void getPasswordPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { + if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "password.html"); + response->write(header + content); +} + void getApps(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); @@ -306,6 +314,54 @@ void saveConfig(resp_https_t response, req_https_t request) { } } +void savePassword(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; + std::stringstream ss; + std::stringstream configStream; + ss << request->content.rdbuf(); + + pt::ptree inputTree,outputTree,fileTree; + + auto g = util::fail_guard([&]() { + std::ostringstream data; + pt::write_json(data, outputTree); + response->write(data.str()); + }); + + try { + //TODO: Input Validation + pt::read_json(ss, inputTree); + std::string username = inputTree.get("currentUsername"); + std::string newUsername = inputTree.get("newUsername"); + std::string password = inputTree.get("currentPassword"); + std::string newPassword = inputTree.get("newPassword"); + std::string confirmPassword = inputTree.get("confirmNewPassword"); + if(newUsername.length() == 0) newUsername = username; + std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + if(username == config::sunshine.username && hash == config::sunshine.password){ + if(newPassword != confirmPassword){ + outputTree.put("status",false); + outputTree.put("error","Password Mismatch"); + } + fileTree.put("username",newUsername); + fileTree.put("password",crypto::hash_hexstr(newPassword + config::sunshine.salt)); + fileTree.put("salt",config::sunshine.salt); + pt::write_json(config::sunshine.credentials_file,fileTree); + http::reload_user_creds(config::sunshine.credentials_file); + outputTree.put("status",true); + } else { + outputTree.put("status",false); + outputTree.put("error","Invalid Current Credentials"); + } + } + catch(std::exception &e) { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", false); + outputTree.put("error", e.what()); + return; + } +} + void start(std::shared_ptr shutdown_event) { auto ctx = std::make_shared(boost::asio::ssl::context::tls); ctx->use_certificate_chain_file(config::nvhttp.cert); @@ -315,14 +371,16 @@ void start(std::shared_ptr shutdown_event) { http_server.resource["^/$"]["GET"] = getIndexPage; http_server.resource["^/pin$"]["GET"] = getPinPage; http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/password$"]["GET"] = getPasswordPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; http_server.resource["^/api/apps$"]["GET"] = getApps; http_server.resource["^/api/apps$"]["POST"] = saveApp; http_server.resource["^/api/config$"]["GET"] = getConfig; http_server.resource["^/api/config$"]["POST"] = saveConfig; + http_server.resource["^/api/password$"]["POST"] = savePassword; http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; http_server.config.reuse_address = true; http_server.config.address = "0.0.0.0"s; http_server.config.port = PORT_HTTP; diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h index 4997b2b1..7c441a7c 100644 --- a/sunshine/httpcommon.h +++ b/sunshine/httpcommon.h @@ -4,6 +4,7 @@ void init(std::shared_ptr shutdown_event); int create_creds(const std::string &pkey, const std::string &cert); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); +int reload_user_creds(const std::string &file); extern std::string unique_id; extern net::net_e origin_pin_allowed; } // namespace http \ No newline at end of file From bb251d5046ac47764b06366324102dcd0f8fa2c7 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 30 May 2021 14:58:38 +0200 Subject: [PATCH 12/15] Code Cleanup --- sunshine/confighttp.cpp | 90 +++++++++++++++++++++++------------------ sunshine/httpcommon.cpp | 2 +- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index e76b8ca4..6546b31d 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -76,14 +76,14 @@ bool authenticate(resp_https_t response, req_https_t request) { int index = authData.find(':'); std::string username = authData.substr(0, index); std::string password = authData.substr(index + 1); - std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + std::string hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); if(username == config::sunshine.username && hash == config::sunshine.password) return true; + send_unauthorized(response, request); return false; } -template -void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void not_found(resp_https_t response, req_https_t request) { pt::ptree tree; tree.put("root..status_code", 404); @@ -98,46 +98,47 @@ void not_found(std::shared_ptr::Response> resp void getIndexPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "index.html"); response->write(header + content); } -template -void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getPinPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "pin.html"); response->write(header + content); } -template -void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getAppsPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "apps.html"); response->write(header + content); } -template -void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getClientsPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "clients.html"); response->write(header + content); } -template -void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getConfigPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "config.html"); response->write(header + content); } -template -void getPasswordPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { +void getPasswordPage(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "password.html"); response->write(header + content); @@ -145,12 +146,14 @@ void getPasswordPage(std::shared_ptr::Response void getApps(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); response->write(content); } void saveApp(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::stringstream ss; ss << request->content.rdbuf(); pt::ptree outputTree; @@ -170,12 +173,13 @@ void saveApp(resp_https_t response, req_https_t request) { BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); if(inputTree.get_child("prep-cmd").empty()) inputTree.erase("prep-cmd"); + inputTree.erase("index"); if(index == -1) { apps_node.push_back(std::make_pair("", inputTree)); } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; for(const auto &kv : apps_node) { @@ -204,6 +208,7 @@ void saveApp(resp_https_t response, req_https_t request) { void deleteApp(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -249,6 +254,7 @@ void deleteApp(resp_https_t response, req_https_t request) { void getConfig(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + pt::ptree outputTree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -285,6 +291,7 @@ void getConfig(resp_https_t response, req_https_t request) { void saveConfig(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); @@ -302,6 +309,7 @@ void saveConfig(resp_https_t response, req_https_t request) { for(const auto &kv : inputTree) { std::string value = inputTree.get(kv.first); if(value.length() == 0 || value.compare("null") == 0) continue; + configStream << kv.first << " = " << value << std::endl; } http::write_file(SUNSHINE_ASSETS_DIR "/sunshine.conf", configStream.str()); @@ -316,6 +324,7 @@ void saveConfig(resp_https_t response, req_https_t request) { void savePassword(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; + std::stringstream ss; std::stringstream configStream; ss << request->content.rdbuf(); @@ -337,14 +346,15 @@ void savePassword(resp_https_t response, req_https_t request) { std::string newPassword = inputTree.get("newPassword"); std::string confirmPassword = inputTree.get("confirmNewPassword"); if(newUsername.length() == 0) newUsername = username; - std::string hash = crypto::hash_hexstr(password + config::sunshine.salt); + + std::string hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); if(username == config::sunshine.username && hash == config::sunshine.password){ if(newPassword != confirmPassword){ outputTree.put("status",false); outputTree.put("error","Password Mismatch"); } fileTree.put("username",newUsername); - fileTree.put("password",crypto::hash_hexstr(newPassword + config::sunshine.salt)); + fileTree.put("password",util::hex(crypto::hash(newPassword + config::sunshine.salt)).to_string()); fileTree.put("salt",config::sunshine.salt); pt::write_json(config::sunshine.credentials_file,fileTree); http::reload_user_creds(config::sunshine.credentials_file); @@ -366,27 +376,27 @@ void start(std::shared_ptr shutdown_event) { 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 http_server { ctx, 0 }; - http_server.default_resource = not_found; - http_server.resource["^/$"]["GET"] = getIndexPage; - http_server.resource["^/pin$"]["GET"] = getPinPage; - http_server.resource["^/apps$"]["GET"] = getAppsPage; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/password$"]["GET"] = getPasswordPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; - http_server.resource["^/api/apps$"]["GET"] = getApps; - http_server.resource["^/api/apps$"]["POST"] = saveApp; - http_server.resource["^/api/config$"]["GET"] = getConfig; - http_server.resource["^/api/config$"]["POST"] = saveConfig; - http_server.resource["^/api/password$"]["POST"] = savePassword; - http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; + https_server_t server { ctx, 0 }; + server.default_resource = not_found; + server.resource["^/$"]["GET"] = getIndexPage; + server.resource["^/pin$"]["GET"] = getPinPage; + server.resource["^/apps$"]["GET"] = getAppsPage; + server.resource["^/clients$"]["GET"] = getClientsPage; + server.resource["^/config$"]["GET"] = getConfigPage; + server.resource["^/password$"]["GET"] = getPasswordPage; + server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + server.resource["^/api/apps$"]["GET"] = getApps; + server.resource["^/api/apps$"]["POST"] = saveApp; + server.resource["^/api/config$"]["GET"] = getConfig; + server.resource["^/api/config$"]["POST"] = saveConfig; + server.resource["^/api/password$"]["POST"] = savePassword; + server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + server.config.reuse_address = true; + server.config.address = "0.0.0.0"s; + server.config.port = PORT_HTTP; try { - http_server.bind(); + server.bind(); BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; } catch(boost::system::system_error &err) { @@ -395,12 +405,12 @@ void start(std::shared_ptr shutdown_event) { shutdown_event->raise(true); return; } - auto accept_and_run = [&](auto *http_server) { + auto accept_and_run = [&](auto *server) { try { - http_server->accept_and_run(); + server->accept_and_run(); } catch(boost::system::system_error &err) { - // It's possible the exception gets thrown after calling http_server->stop() from a different thread + // It's possible the exception gets thrown after calling server->stop() from a different thread if(shutdown_event->peek()) { return; } @@ -410,12 +420,12 @@ void start(std::shared_ptr shutdown_event) { return; } }; - std::thread tcp { accept_and_run, &http_server }; + std::thread tcp { accept_and_run, &server }; // Wait for any event shutdown_event->view(); - http_server.stop(); + server.stop(); tcp.join(); } diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index b6f7ac0a..825ae3d6 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -72,7 +72,7 @@ int generate_user_creds(const std::string &file) { std::string salt = crypto::rand_string(16); outputTree.put("username", "sunshine"); outputTree.put("salt", salt); - outputTree.put("password", crypto::hash_hexstr(plainPassword + salt)); + outputTree.put("password", util::hex(crypto::hash(plainPassword + salt)).to_string()); BOOST_LOG(info) << "New credentials has been created"; BOOST_LOG(info) << "Username: " << username; BOOST_LOG(info) << "Password: " << plainPassword; From bc261fddf23705c00a17194475565b03df89b8fd Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 30 May 2021 15:56:13 +0200 Subject: [PATCH 13/15] Updated Config Page --- assets/web/apps.html | 6 ++- assets/web/config.html | 120 ++++++++++++++++++++++++++++++++++------- assets/web/index.html | 9 +++- assets/web/pin.html | 14 +++-- 4 files changed, 123 insertions(+), 26 deletions(-) diff --git a/assets/web/apps.html b/assets/web/apps.html index d63f8260..4f49f7da 100644 --- a/assets/web/apps.html +++ b/assets/web/apps.html @@ -1,6 +1,8 @@
-

Applications

-
Applications are refreshed only when Client is restarted
+
+

Applications

+
Applications are refreshed only when Client is restarted
+
diff --git a/assets/web/config.html b/assets/web/config.html index b2b642cb..356e4d45 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -55,6 +55,43 @@
How long to wait in milliseconds for data from moonlight before shutting down the stream
+ +
+ +
+ +
+
+ {{r}} + × +
+
+ + + +
+
+
+ +
+
+ {{f}} + × +
+
+ + + +
+
+
+ The display modes advertised by Sunshine
+ Some versions of Moonlight, such as Moonlight-nx (Switch), + rely on this list to ensure that the requested resolutions and fps + are supported. +
+
@@ -98,7 +135,7 @@
-
+
@@ -107,7 +144,7 @@
-
+
@@ -122,27 +159,36 @@
-
+
The name of the audio sink used for Audio Loopback
You can find the name of the audio sink using the following command:
- tools\audio-info.exe +
tools\audio-info.exe
-
+
The name of the audio sink used for Audio Loopback
If you do not specify this variable, pulseaudio will select the default monitor device.

You can find the name of the audio sink using the following command:
- pacmd list-sources | grep "name:"
+
pacmd list-sinks | grep "name:"

+
+
+ +
+ + +
+ The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine + to stream audio, while muting the speakers.
-
+
You can select the video card you want to stream:
The appropriate values can be found using the following command:
- tools\dxgi-info.exe +
tools\dxgi-info.exe
@@ -154,9 +200,7 @@ The appropriate values can be found using the following command:
tools\dxgi-info.exe
!! Linux only !!
- Set the display number to stream. I have no idea how they are numbered. They start from 1, - usually.
- output_name = 1
+ Set the display number to stream. I have no idea how they are numbered. They start from 0, usually.
@@ -214,7 +258,7 @@
+ +
- - + +
@@ -293,7 +337,7 @@
- + - +

PIN Pairing

+
+
+ + +
+
+ Warning! Make sure you have access to the client you are pairing with.
+ This software can give total control to your computer, so be careful! +
From b848db8f2be3d44d7298cb6db5fda39cb1160735 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 30 May 2021 16:42:40 +0200 Subject: [PATCH 14/15] Config Update and PIN POST Method --- assets/web/apps.html | 170 ++++++++++++++++++++++++---------------- assets/web/pin.html | 7 +- sunshine/confighttp.cpp | 34 +++++++- sunshine/nvhttp.cpp | 64 ++++++++------- sunshine/nvhttp.h | 3 +- 5 files changed, 173 insertions(+), 105 deletions(-) diff --git a/assets/web/apps.html b/assets/web/apps.html index 4f49f7da..d1784608 100644 --- a/assets/web/apps.html +++ b/assets/web/apps.html @@ -3,62 +3,90 @@

Applications

Applications are refreshed only when Client is restarted
-
- - - - - - - - - - - - -
NameActions
{{app.name}} -
-
-
- - -
Application Name, as shown on Moonlight
-
-
- - -
The file where the output of the command is stored, if it is not specified, the output is ignored
-
-
- -
A list of commands to be run before/after the application.
If any of the prep-commands fail, starting the application is aborted
- - - - - - - - - - - - - -
DoUndo
- -
-
- - -
The main application, if it is not specified, a processs is started that sleeps indefinitely
-
-
- - +
+ + + + + + + + + + + + + +
NameActions
{{app.name}} +
+
+
+
+ +
+ + +
Application Name, as shown on Moonlight
+
+ +
+ + +
The file where the output of the command is stored, if it is not + specified, the output is ignored
+
+ +
+ +
A list of commands to be run before/after the application.
If any of the + prep-commands fail, starting the application is aborted
+ + + + + + + + + + + + + +
DoUndo
+ +
+ +
+ +
+
{{c}}
+ +
+
+ + +
+
A list of commands to be run and forgotten about
+
+ +
+ + +
The main application, if it is not specified, a processs is started that + sleeps indefinitely
+
+ +
+ + +
- +
+ +