From 2e7bde8958e715aea181f80f38c2f22a9940eb3b Mon Sep 17 00:00:00 2001 From: Yukino Song Date: Sun, 15 Sep 2024 22:40:38 +0800 Subject: [PATCH] Implement permission for stream --- src/crypto.h | 44 +++++++++++++++++++++++++++++++------- src/input.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++++- src/input.h | 3 ++- src/nvhttp.cpp | 57 +++++++++++++++++++++++++------------------------- src/rtsp.h | 4 +++- src/stream.cpp | 27 ++++++++++++++++++++++-- 6 files changed, 150 insertions(+), 41 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 98e6d3a2..fe642bd5 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -38,16 +38,44 @@ namespace crypto { * @brief The permissions of a client. */ enum class PERM: uint32_t { - list = 1 << 0, // Allow list apps - view = 1 << 3, // Allow view streams - input = 1 << 7, // Allow any input: kbd/mouse, controller - server_cmd = 1 << 10, // Allow execute server cmd - launch = 1 << 15, // Allow launch apps - _default = view | list, // Default permissions for new clients - _all = launch | server_cmd | input | view | list, // All current permissions - _no = 0, // No permissions are granted + _reserved = 1, + + _input = _reserved << 8, // Input permission group + input_controller = _input << 0, // Allow controller input + input_touch = _input << 1, // Allow touch input + input_pen = _input << 2, // Allow pen input + input_kbdm = _input << 3, // Allow kbd/mouse input + _all_inputs = input_controller | input_touch | input_pen | input_kbdm, + + _operation = _input << 8, // Operation permission group + clipboard_set = _operation << 0, // Allow set clipboard from client + clipboard_read = _operation << 1, // Allow read clipboard from host + file_upload = _operation << 2, // Allow upload files to host + file_dwnload = _operation << 3, // Allow download files from host + server_cmd = _operation << 4, // Allow execute server cmd + _all_opeiations = clipboard_set | clipboard_read | file_upload | file_dwnload | server_cmd, + + _action = _operation << 8, // Action permission group + list = _action << 0, // Allow list apps + view = _action << 1, // Allow view streams + launch = _action << 2, // Allow launch apps + _all_actions = list | view | launch, + + _default = view | list, // Default permissions for new clients + _no = 0, // No permissions are granted + _all = _all_inputs | _all_opeiations | _all_actions, // All current permissions }; + inline constexpr PERM + operator&(PERM x, PERM y) { + return static_cast(static_cast(x) & static_cast(y)); + } + + inline constexpr bool + operator!(PERM p) { + return static_cast(p) == 0; + } + struct named_cert_t { std::string name; std::string uuid; diff --git a/src/input.cpp b/src/input.cpp index bf275467..aafad577 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -1628,7 +1628,61 @@ namespace input { * @param input_data The input message. */ void - passthrough(std::shared_ptr &input, std::vector &&input_data) { + passthrough(std::shared_ptr &input, std::vector &&input_data, const crypto::PERM& permission) { + // No input permissions at all + if (!(permission & crypto::PERM::_all_inputs)) { + return; + } + + // Have some input permission + // Otherwise have all input permission + if ((permission & crypto::PERM::_all_inputs) != crypto::PERM::_all_inputs) { + PNV_INPUT_HEADER payload = (PNV_INPUT_HEADER)input_data.data(); + + // Check permission + switch (util::endian::little(payload->magic)) { + case MULTI_CONTROLLER_MAGIC_GEN5: + case SS_CONTROLLER_ARRIVAL_MAGIC: + case SS_CONTROLLER_TOUCH_MAGIC: + case SS_CONTROLLER_MOTION_MAGIC: + case SS_CONTROLLER_BATTERY_MAGIC: + if (!(permission & crypto::PERM::input_controller)) { + return; + } else { + break; + } + case MOUSE_MOVE_REL_MAGIC_GEN5: + case MOUSE_MOVE_ABS_MAGIC: + case MOUSE_BUTTON_DOWN_EVENT_MAGIC_GEN5: + case MOUSE_BUTTON_UP_EVENT_MAGIC_GEN5: + case SCROLL_MAGIC_GEN5: + case SS_HSCROLL_MAGIC: + case KEY_DOWN_EVENT_MAGIC: + case KEY_UP_EVENT_MAGIC: + case UTF8_TEXT_EVENT_MAGIC: + if (!(permission & crypto::PERM::input_kbdm)) { + return; + } else { + break; + } + case SS_TOUCH_MAGIC: + if (!(permission & crypto::PERM::input_touch)) { + return; + } else { + break; + } + case SS_PEN_MAGIC: + if (!(permission & crypto::PERM::input_pen)) { + return; + } else { + break; + } + default: + // Unknown input event + return; + } + } + { std::lock_guard lg(input->input_queue_lock); input->input_queue.push_back(std::move(input_data)); diff --git a/src/input.h b/src/input.h index a20c9c30..c1225482 100644 --- a/src/input.h +++ b/src/input.h @@ -8,6 +8,7 @@ #include "platform/common.h" #include "thread_safe.h" +#include "crypto.h" namespace input { struct input_t; @@ -17,7 +18,7 @@ namespace input { void reset(std::shared_ptr &input); void - passthrough(std::shared_ptr &input, std::vector &&input_data); + passthrough(std::shared_ptr &input, std::vector &&input_data, const crypto::PERM& permission); [[nodiscard]] std::unique_ptr init(); diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index c9d369c0..4a9e2baf 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -314,7 +314,7 @@ namespace nvhttp { named_cert_p->name = el.get("name"); named_cert_p->cert = el.get("cert"); named_cert_p->uuid = el.get("uuid"); - named_cert_p->perm = (PERM)el.get("perm", (uint32_t)PERM::_all); + named_cert_p->perm = (PERM)el.get("perm", (uint32_t)PERM::_all) & PERM::_all; client.named_devices.emplace_back(named_cert_p); } } @@ -340,7 +340,7 @@ namespace nvhttp { } std::shared_ptr - make_launch_session(bool host_audio, const args_t &args, const std::string& device_name, const std::string& uuid) { + make_launch_session(bool host_audio, const args_t &args, const crypto::named_cert_t* named_cert_p) { auto launch_session = std::make_shared(); launch_session->id = ++session_id_counter; @@ -360,8 +360,9 @@ namespace nvhttp { x++; } - launch_session->device_name = device_name.empty() ? "ApolloDisplay"s : device_name; - launch_session->unique_id = uuid; + launch_session->device_name = named_cert_p->name.empty() ? "ApolloDisplay"s : named_cert_p->name; + launch_session->unique_id = named_cert_p->uuid; + launch_session->perm = named_cert_p->perm; launch_session->appid = util::from_view(get_arg(args, "appid", "unknown")); launch_session->enable_sops = util::from_view(get_arg(args, "sops", "0")); launch_session->surround_info = util::from_view(get_arg(args, "surroundAudioInfo", "196610")); @@ -797,15 +798,13 @@ namespace nvhttp { tree.put("root.ExternalPort", net::map_port(PORT_HTTP)); tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0"); - crypto::named_cert_t* named_cert_p; - // Only include the MAC address for requests sent from paired clients over HTTPS. // For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore. if constexpr (std::is_same_v) { tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address()))); - named_cert_p = get_verified_cert(request); - if (named_cert_p->perm >= PERM::server_cmd) { + auto named_cert_p = get_verified_cert(request); + if (!!(named_cert_p->perm & PERM::server_cmd)) { pt::ptree& root_node = tree.get_child("root"); if (config::sunshine.server_cmds.size() > 0) { @@ -820,12 +819,14 @@ namespace nvhttp { BOOST_LOG(debug) << "Permission Get ServerCommand denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")"; } + tree.put("root.Permission", std::to_string((uint32_t)named_cert_p->perm)); + #ifdef _WIN32 tree.put("root.VirtualDisplayCapable", true); - if (named_cert_p->perm < PERM::list) { - tree.put("root.VirtualDisplayDriverReady", true); - } else { + if (!!(named_cert_p->perm & PERM::list)) { tree.put("root.VirtualDisplayDriverReady", proc::vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK); + } else { + tree.put("root.VirtualDisplayDriverReady", true); } #endif } @@ -931,7 +932,17 @@ namespace nvhttp { apps.put(".status_code", 200); auto named_cert_p = get_verified_cert(request); - if (named_cert_p->perm < PERM::list) { + if (!!(named_cert_p->perm & PERM::list)) { + for (auto &proc : proc::proc.get_apps()) { + pt::ptree app; + + app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0); + app.put("AppTitle"s, proc.name); + app.put("ID", proc.id); + + apps.push_back(std::make_pair("App", std::move(app))); + } + } else { BOOST_LOG(debug) << "Permission ListApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")"; pt::ptree app; @@ -943,16 +954,6 @@ namespace nvhttp { apps.push_back(std::make_pair("App", std::move(app))); return; - } else { - for (auto &proc : proc::proc.get_apps()) { - pt::ptree app; - - app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0); - app.put("AppTitle"s, proc.name); - app.put("ID", proc.id); - - apps.push_back(std::make_pair("App", std::move(app))); - } } } @@ -971,7 +972,7 @@ namespace nvhttp { }); auto named_cert_p = get_verified_cert(request); - if (named_cert_p->perm < PERM::launch) { + if (!(named_cert_p->perm & PERM::launch)) { BOOST_LOG(debug) << "Permission LaunchApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")"; tree.put("root.resume", 0); @@ -1012,7 +1013,7 @@ namespace nvhttp { } host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); - auto launch_session = make_launch_session(host_audio, args, named_cert_p->name, named_cert_p->uuid); + auto launch_session = make_launch_session(host_audio, args, named_cert_p); auto encryption_mode = net::encryption_mode_for_address(request->remote_endpoint().address()); if (!launch_session->rtsp_cipher && encryption_mode == config::ENCRYPTION_MODE_MANDATORY) { @@ -1079,7 +1080,7 @@ namespace nvhttp { }); auto named_cert_p = get_verified_cert(request); - if (named_cert_p->perm < PERM::view) { + if (!(named_cert_p->perm & PERM::view)) { BOOST_LOG(debug) << "Permission ViewApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")"; tree.put("root.resume", 0); @@ -1140,7 +1141,7 @@ namespace nvhttp { } } - auto launch_session = make_launch_session(host_audio, args, named_cert_p->name, named_cert_p->uuid); + auto launch_session = make_launch_session(host_audio, args, named_cert_p); auto encryption_mode = net::encryption_mode_for_address(request->remote_endpoint().address()); if (!launch_session->rtsp_cipher && encryption_mode == config::ENCRYPTION_MODE_MANDATORY) { @@ -1176,7 +1177,7 @@ namespace nvhttp { }); auto named_cert_p = get_verified_cert(request); - if (named_cert_p->perm < PERM::launch) { + if (!(named_cert_p->perm & PERM::launch)) { BOOST_LOG(debug) << "Permission CancelApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")"; tree.put("root.resume", 0); @@ -1215,7 +1216,7 @@ namespace nvhttp { auto named_cert_p = get_verified_cert(request); - if (named_cert_p->perm < PERM::list) { + if (!(named_cert_p->perm & PERM::list)) { BOOST_LOG(debug) << "Permission Get AppAsset denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")"; fg.disable(); diff --git a/src/rtsp.h b/src/rtsp.h index 928ee67c..598d842b 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -21,9 +21,11 @@ namespace rtsp_stream { std::string av_ping_payload; uint32_t control_connect_data; - bool host_audio; std::string device_name; std::string unique_id; + crypto::PERM perm; + + bool host_audio; int width; int height; int fps; diff --git a/src/stream.cpp b/src/stream.cpp index 47925653..7519e754 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -20,6 +20,7 @@ extern "C" { } #include "config.h" +#include "crypto.h" #include "globals.h" #include "input.h" #include "logging.h" @@ -403,6 +404,9 @@ namespace stream { } control; std::uint32_t launch_session_id; + std::string device_name; + std::string device_uuid; + crypto::PERM permission; safe::mail_raw_t::event_t shutdown_event; safe::signal_t controlEnd; @@ -992,11 +996,17 @@ namespace stream { std::copy(payload.end() - 16, payload.end(), std::begin(iv)); } - input::passthrough(session->input, std::move(plaintext)); + input::passthrough(session->input, std::move(plaintext), session->permission); }); server->map(packetTypes[IDX_EXEC_SERVER_CMD], [server](session_t *session, const std::string_view &payload) { BOOST_LOG(debug) << "type [IDX_EXEC_SERVER_CMD]"sv; + + if (!(session->permission & crypto::PERM::server_cmd)) { + BOOST_LOG(debug) << "Permission Exec Server Cmd deined for [" << session->device_name << "]"; + return; + } + uint8_t cmdIndex = *(uint8_t*)payload.data(); if (cmdIndex < config::sunshine.server_cmds.size()) { @@ -1024,10 +1034,20 @@ namespace stream { server->map(packetTypes[IDX_SET_CLIPBOARD], [server](session_t *session, const std::string_view &payload) { BOOST_LOG(info) << "type [IDX_SET_CLIPBOARD]: "sv << payload << " size: " << payload.size(); + + if (!(session->permission & crypto::PERM::clipboard_set)) { + BOOST_LOG(debug) << "Permission Clipboard Set deined for [" << session->device_name << "]"; + return; + } }); server->map(packetTypes[IDX_FILE_TRANSFER_NONCE_REQUEST], [server](session_t *session, const std::string_view &payload) { BOOST_LOG(info) << "type [IDX_FILE_TRANSFER_NONCE_REQUEST]: "sv << payload << " size: " << payload.size(); + + if (!(session->permission & crypto::PERM::file_upload)) { + BOOST_LOG(debug) << "Permission File Upload deined for [" << session->device_name << "]"; + return; + } }); server->map(packetTypes[IDX_ENCRYPTED], [server](session_t *session, const std::string_view &payload) { @@ -1091,7 +1111,7 @@ namespace stream { // IDX_INPUT_DATA callback will attempt to decrypt unencrypted data, therefore we need pass it directly if (type == packetTypes[IDX_INPUT_DATA]) { plaintext.erase(std::begin(plaintext), std::begin(plaintext) + 4); - input::passthrough(session->input, std::move(plaintext)); + input::passthrough(session->input, std::move(plaintext), session->permission); } else { server->call(type, session, next_payload, true); @@ -2051,6 +2071,9 @@ namespace stream { session->shutdown_event = mail->event(mail::shutdown); session->launch_session_id = launch_session.id; + session->device_name = launch_session.device_name; + session->device_uuid = launch_session.unique_id; + session->permission = launch_session.perm; session->config = config;