diff --git a/src/confighttp.cpp b/src/confighttp.cpp index b2b9992a..e1781ff2 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -1062,8 +1062,10 @@ namespace confighttp { pt::read_json(ss, inputTree); std::string uuid = inputTree.get("uuid"); std::string name = inputTree.get("name"); + auto do_cmds = nvhttp::extract_command_entries(inputTree, "do"); + auto undo_cmds = nvhttp::extract_command_entries(inputTree, "undo"); auto perm = (crypto::PERM)inputTree.get("perm") & crypto::PERM::_all; - outputTree.put("status", nvhttp::update_device_info(uuid, name, perm)); + outputTree.put("status", nvhttp::update_device_info(uuid, name, do_cmds, undo_cmds, perm)); send_response(response, outputTree); } catch (std::exception &e) { diff --git a/src/crypto.h b/src/crypto.h index 69595a9f..a2a7fc6e 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -5,11 +5,14 @@ #pragma once #include +#include #include #include #include #include +#include + #include "utility.h" namespace crypto { @@ -78,10 +81,25 @@ namespace crypto { return static_cast(p) == 0; } + struct command_entry_t { + std::string cmd; + bool elevated; + + // Serialize method + static inline boost::property_tree::ptree serialize(const command_entry_t& entry) { + boost::property_tree::ptree node; + node.put("cmd", entry.cmd); + node.put("elevated", entry.elevated); + return node; + } + }; + struct named_cert_t { std::string name; std::string uuid; std::string cert; + std::list do_cmds; + std::list undo_cmds; PERM perm; }; diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 6249efb1..2bd19ffe 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -168,6 +168,34 @@ namespace nvhttp { return it->second; } + // Helper function to extract command entries + cmd_list_t + extract_command_entries(const pt::ptree& pt, const std::string& key) { + cmd_list_t commands; + + // Check if the specified key exists + auto it = pt.find(key); + if (it != pt.not_found()) { + // Traverse the array and extract entries + for (const auto& item : pt.get_child(key)) { + try { + // Extract "cmd" and "elevated" fields + std::string cmd = item.second.get("cmd"); + bool elevated = item.second.get("elevated"); + + // Add to the list of commands + commands.push_back({cmd, elevated}); + } catch (const std::exception& e) { + BOOST_LOG(warning) << "Error parsing entry: " << e.what(); + } + } + } else { + BOOST_LOG(debug) << "Key \"" << key << "\" not found in the JSON."; + } + + return commands; + } + void save_state() { pt::ptree root; @@ -209,6 +237,25 @@ namespace nvhttp { named_cert_node.put("cert"s, named_cert_p->cert); named_cert_node.put("uuid"s, named_cert_p->uuid); named_cert_node.put("perm"s, (uint32_t)named_cert_p->perm); + + // Add "do" commands + if (!named_cert_p->do_cmds.empty()) { + pt::ptree do_cmds_node; + for (const auto& cmd : named_cert_p->do_cmds) { + do_cmds_node.push_back(std::make_pair(""s, crypto::command_entry_t::serialize(cmd))); + } + named_cert_node.add_child("do", do_cmds_node); + } + + // Add "undo" commands + if (!named_cert_p->undo_cmds.empty()) { + pt::ptree undo_cmds_node; + for (const auto& cmd : named_cert_p->undo_cmds) { + undo_cmds_node.push_back(std::make_pair(""s, crypto::command_entry_t::serialize(cmd))); + } + named_cert_node.add_child("undo", undo_cmds_node); + } + named_cert_nodes.push_back(std::make_pair(""s, named_cert_node)); } } @@ -235,9 +282,8 @@ namespace nvhttp { try { pt::read_json(config::nvhttp.file_state, tree); } - catch (std::exception &e) { + catch (std::exception& e) { BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what(); - return; } @@ -257,11 +303,11 @@ namespace nvhttp { // Import from old format if (root.get_child_optional("devices")) { auto device_nodes = root.get_child("devices"); - for (auto &[_, device_node] : device_nodes) { + for (auto& [_, device_node] : device_nodes) { auto uniqID = device_node.get("uniqueid"); if (device_node.count("certs")) { - for (auto &[_, el] : device_node.get_child("certs")) { + for (auto& [_, el] : device_node.get_child("certs")) { auto named_cert_p = std::make_shared(); named_cert_p->name = ""s; named_cert_p->cert = el.get_value(); @@ -274,19 +320,24 @@ namespace nvhttp { } if (root.count("named_devices")) { - for (auto &[_, el] : root.get_child("named_devices")) { + for (auto& [_, el] : root.get_child("named_devices")) { auto named_cert_p = std::make_shared(); 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) & PERM::_all; + + // Load commands + named_cert_p->do_cmds = extract_command_entries(el, "do"); + named_cert_p->undo_cmds = extract_command_entries(el, "undo"); + client.named_devices.emplace_back(named_cert_p); } } // Empty certificate chain and import certs from file cert_chain.clear(); - for (auto &named_cert : client.named_devices) { + for (auto& named_cert : client.named_devices) { cert_chain.add(named_cert); } @@ -366,6 +417,9 @@ namespace nvhttp { launch_session->virtual_display = util::from_view(get_arg(args, "virtualDisplay", "0")); launch_session->scale_factor = util::from_view(get_arg(args, "scaleFactor", "100")); + launch_session->client_do_cmds = named_cert_p->do_cmds; + launch_session->client_undo_cmds = named_cert_p->undo_cmds; + return launch_session; } @@ -932,6 +986,24 @@ namespace nvhttp { named_cert_node.put("uuid"s, named_cert_p->uuid); named_cert_node.put("perm", (uint32_t)named_cert_p->perm); + // Add "do" commands + if (!named_cert_p->do_cmds.empty()) { + pt::ptree do_cmds_node; + for (const auto& cmd : named_cert_p->do_cmds) { + do_cmds_node.push_back(std::make_pair(""s, crypto::command_entry_t::serialize(cmd))); + } + named_cert_node.add_child("do", do_cmds_node); + } + + // Add "undo" commands + if (!named_cert_p->undo_cmds.empty()) { + pt::ptree undo_cmds_node; + for (const auto& cmd : named_cert_p->undo_cmds) { + undo_cmds_node.push_back(std::make_pair(""s, crypto::command_entry_t::serialize(cmd))); + } + named_cert_node.add_child("undo", undo_cmds_node); + } + if (connected_uuids.empty()) { named_cert_node.put("connected"s, false); } else { @@ -1563,7 +1635,13 @@ namespace nvhttp { return false; } - bool update_device_info(const std::string& uuid, const std::string& name, const crypto::PERM newPerm) { + bool update_device_info( + const std::string& uuid, + const std::string& name, + const cmd_list_t& do_cmds, + const cmd_list_t& undo_cmds, + const crypto::PERM newPerm + ) { find_and_udpate_session_info(uuid, name, newPerm); client_t &client = client_root; @@ -1573,6 +1651,8 @@ namespace nvhttp { if (named_cert_p->uuid == uuid) { named_cert_p->name = name; named_cert_p->perm = newPerm; + named_cert_p->do_cmds = do_cmds; + named_cert_p->undo_cmds = undo_cmds; save_state(); return true; } diff --git a/src/nvhttp.h b/src/nvhttp.h index 7eed892c..1ecc8e62 100644 --- a/src/nvhttp.h +++ b/src/nvhttp.h @@ -8,6 +8,7 @@ // standard includes #include #include +#include // lib includes #include @@ -26,6 +27,7 @@ using namespace std::chrono_literals; namespace nvhttp { using args_t = SimpleWeb::CaseInsensitiveMultimap; + using cmd_list_t = std::list; /** * @brief The protocol version. @@ -63,6 +65,10 @@ namespace nvhttp { std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr); + // Helper function to extract command entries + cmd_list_t + extract_command_entries(const boost::property_tree::ptree& pt, const std::string& key); + std::shared_ptr make_launch_session(bool host_audio, int appid, const args_t &args, const crypto::named_cert_t* named_cert_p); @@ -262,9 +268,19 @@ namespace nvhttp { /** * @brief Update device info * - * @param[in] uuid The uuid string - * @param[in] name New name - * @param[in] newPerm New permission + * @param[in] uuid The uuid string + * @param[in] name New name + * @param[in] do_cmds The do commands + * @param[in] undo_cmds The undo commands + * @param[in] newPerm New permission + * + * @return Whether the update is successful */ - bool update_device_info(const std::string& uuid, const std::string& name, const crypto::PERM newPerm); + bool update_device_info( + const std::string& uuid, + const std::string& name, + const cmd_list_t& do_cmds, + const cmd_list_t& undo_cmds, + const crypto::PERM newPerm + ); } // namespace nvhttp diff --git a/src/rtsp.h b/src/rtsp.h index 90d59a89..2c1b40a4 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -53,6 +53,9 @@ namespace rtsp_stream { std::string rtsp_url_scheme; uint32_t rtsp_iv_counter; + std::list client_do_cmds; + std::list client_undo_cmds; + #ifdef _WIN32 GUID display_guid{}; #endif diff --git a/src/stream.cpp b/src/stream.cpp index 9ec5fc31..6e60e345 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -409,6 +409,9 @@ namespace stream { std::string device_uuid; crypto::PERM permission; + std::list do_cmds; + std::list undo_cmds; + safe::mail_raw_t::event_t shutdown_event; safe::signal_t controlEnd; @@ -2076,6 +2079,25 @@ namespace stream { BOOST_LOG(debug) << "Resetting Input..."sv; input::reset(session.input); + if (!session.undo_cmds.empty()) { + auto exec_thread = std::thread([cmd_list = session.undo_cmds]{ + for (auto &cmd : cmd_list) { + std::error_code ec; + auto env = proc::proc.get_env(); + boost::filesystem::path working_dir = proc::find_working_directory(cmd.cmd, env); + auto child = platf::run_command(cmd.elevated, true, cmd.cmd, working_dir, env, nullptr, ec, nullptr); + BOOST_LOG(info) << "Spawning client undo command ["sv << cmd.cmd << "] in ["sv << working_dir << ']'; + if (ec) { + BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd.cmd << "]: System: "sv << ec.message(); + } else { + child.detach(); + } + } + }); + + exec_thread.detach(); + } + // If this is the last session, invoke the platform callbacks if (--running_sessions == 0) { if (proc::proc.running()) { @@ -2130,6 +2152,25 @@ namespace stream { platf::streaming_will_start(); } + if (!session.do_cmds.empty()) { + auto exec_thread = std::thread([cmd_list = session.do_cmds]{ + for (auto &cmd : cmd_list) { + std::error_code ec; + auto env = proc::proc.get_env(); + boost::filesystem::path working_dir = proc::find_working_directory(cmd.cmd, env); + auto child = platf::run_command(cmd.elevated, true, cmd.cmd, working_dir, env, nullptr, ec, nullptr); + BOOST_LOG(info) << "Spawning client do command ["sv << cmd.cmd << "] in ["sv << working_dir << ']'; + if (ec) { + BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd.cmd << "]: System: "sv << ec.message(); + } else { + child.detach(); + } + } + }); + + exec_thread.detach(); + } + return 0; } @@ -2145,6 +2186,9 @@ namespace stream { session->device_uuid = launch_session.unique_id; session->permission = launch_session.perm; + session->do_cmds = std::move(launch_session.client_do_cmds); + session->undo_cmds = std::move(launch_session.client_undo_cmds); + session->config = config; session->control.connect_data = launch_session.control_connect_data;