Feature: Add exec_command MCP tool
Add a one-shot exec_command MCP tool that runs a command on a remote Windows host and returns stdout plus exit code, reusing the Web terminal link (main-connection COMMAND_SHELL, a shell sub-connection, and a sentinel command line located via rfind to tolerate ConPTY echo). The sentinel marker is embedded in the command line ConPTY echoes back, so it is only treated as hit when it starts a line (preceded by a newline or the buffer start); this keeps the echoed marker from being mistaken for the real sentinel when the echo packet arrives before the output. Execution is gated at Web remote-desktop sensitivity: a read-only mode (McpReadonly, default on, hides the tool) and a command whitelist (McpCmdWhitelist) with built-in read-only prefixes. Shell metacharacters (& | < > ^) are rejected before whitelist matching, and each execution is recorded in the server audit log. Extend the MCP settings dialog with the read-only checkbox and a multi-line whitelist box (commas and newlines both accepted, normalized to a comma-separated list on save), and add English and Traditional Chinese mappings for the new UI and audit-log strings. Co-Authored-By: deepseek-v4-pro
This commit is contained in:
@@ -98,6 +98,47 @@ public:
|
||||
bool WaitPendingRegistry(uint64_t device_id, std::vector<BYTE>& subkeys,
|
||||
std::vector<BYTE>& values, int timeoutMs);
|
||||
|
||||
// ===== P3:exec_command(Windows 一次性远程命令,复刻 Web 终端链路)=====
|
||||
// 流程:工具线程 BeginTermPending → 主连接下发 COMMAND_SHELL → 客户端 shell 子连接回
|
||||
// TOKEN_TERMINAL_START / TOKEN_SHELL_START → RegisterTerminalContext 接管 → 发
|
||||
// COMMAND_NEXT(+PTY resize) 启动输出回流 → 工具线程发哨兵命令行 → OnTerminalData 收集
|
||||
// 并做哨兵检测 → WaitTerminalDone 取 stdout + exit_code → CancelIO 关子链接。
|
||||
// 单设备单终端(MVP):BeginTermPending 命中已有会话即拒绝,避免同设备并发归属歧义。
|
||||
|
||||
// 该 host 是否已有待接管的终端会话(MessageHandle TOKEN_*_START 分支调用)。
|
||||
bool IsTermPending(uint64_t device_id);
|
||||
|
||||
// 终端子连接就绪:接管(登记 subCtx/isPty、清 pending、发 COMMAND_NEXT 唤醒回流)。
|
||||
void RegisterTerminalContext(uint64_t device_id, context* subCtx, bool isPty);
|
||||
|
||||
// 终端子连接是否已被 MCP 接管(MessageHandle 顶部据此把 shell 输出路由到 OnTerminalData)。
|
||||
bool IsTerminalContext(context* subCtx);
|
||||
|
||||
// 追加 shell 输出并做哨兵检测(命中置 done 并唤醒等待者)。
|
||||
void OnTerminalData(context* subCtx, const BYTE* data, ULONG len);
|
||||
|
||||
// shell 进程退出(TOKEN_TERMINAL_CLOSE)。
|
||||
void OnTerminalClosed(context* subCtx);
|
||||
|
||||
// 工具线程:登记终端挂起(false = 该 host 已有终端会话)。
|
||||
bool BeginTermPending(uint64_t device_id, const std::string& command, const std::string& nonce);
|
||||
|
||||
// 工具线程:等待子连接回 START 并接管(true = 已接管,输出 subCtx/isPty)。
|
||||
bool WaitTerminalReady(uint64_t device_id, context*& subCtx, bool& isPty, int timeoutMs);
|
||||
|
||||
// 工具线程:等待输出收集完成(哨兵命中 / 进程退出 / 超时)。
|
||||
// 成功 true:out=截断到哨兵前的原始字节、exitCode=0/1(进程退出无哨兵时 -1)、closed=是否进程退出。
|
||||
bool WaitTerminalDone(uint64_t device_id, std::vector<BYTE>& out, int& exitCode, bool& closed, int timeoutMs);
|
||||
|
||||
// 工具线程:清理终端挂起(发送失败等提前退出路径)。
|
||||
void ClearTermPending(uint64_t device_id);
|
||||
|
||||
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
|
||||
void SetReadonly(bool readonly) { m_readonly = readonly; }
|
||||
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
|
||||
bool IsReadonly() const { return m_readonly; }
|
||||
const std::string& GetCmdWhitelist() const { return m_cmdWhitelist; }
|
||||
|
||||
private:
|
||||
CMcpServer();
|
||||
~CMcpServer();
|
||||
@@ -131,6 +172,27 @@ private:
|
||||
|
||||
// get_screenshot 的 reqId 发生器(16 位,跳过 0)。与 MFC 预览的 m_PreviewReqId 各自独立计数。
|
||||
std::atomic<uint16_t> m_PreviewReqId{1};
|
||||
|
||||
// ===== P3:exec_command 终端会话(受 m_TermMutex 保护)=====
|
||||
struct TermSession {
|
||||
bool started = false; // false=已发 COMMAND_SHELL 待 START;true=已接管
|
||||
context* subCtx = nullptr; // shell 子连接上下文
|
||||
bool isPty = false; // true=ConPTY(UTF-8);false=老 cmd 管道(GBK)
|
||||
std::string command; // 原始 UTF-8 命令(审计用)
|
||||
std::string nonce; // 哨兵随机串
|
||||
std::vector<BYTE> data; // 收集的原始 shell 输出
|
||||
size_t sentPos = 0; // 哨兵在 data 中的位置
|
||||
int exitCode = -1;
|
||||
bool done = false; // 哨兵命中
|
||||
bool closed = false; // TOKEN_TERMINAL_CLOSE(进程退出)
|
||||
};
|
||||
std::mutex m_TermMutex;
|
||||
std::condition_variable m_TermCv;
|
||||
std::map<uint64_t, TermSession> m_TermSessions; // device_id → 会话
|
||||
std::map<context*, uint64_t> m_TermContextToDevice; // subCtx → device_id(顶部路由)
|
||||
|
||||
bool m_readonly = true;
|
||||
std::string m_cmdWhitelist;
|
||||
};
|
||||
|
||||
// 全局访问器(仿 WebService(),见 WebService.h 末尾)
|
||||
|
||||
Reference in New Issue
Block a user