Add terminal_open / terminal_exec / terminal_close so an AI can hold one
shell session per Windows host and run a sequence of commands with cwd and
environment preserved, instead of the one-shot exec_command.
The persistent terminal is a separate full-command write capability gated by
McpTerminal (default off) plus McpReadonly=0, with no whitelist and full
audit. One device maps to one terminal session via the shared m_TermSessions
map; idle sessions are swept after 300s. terminal_exec rejects commands that
contain & or | (they corrupt the sentinel control-operator chain) as well as
control characters.
Also harden exec_command and terminal_exec against newline/CR injection, fix a
dangling subCtx after an abrupt shell disconnect, and refresh lastActiveAt on
command completion. Add en/zh-TW translations for the new UI strings and a
design document covering both exec_command and the persistent terminal.
Co-Authored-By: deepseek-v4-pro
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
Add a read-only list_registry MCP tool for querying a remote Windows
host's registry. An empty path returns the five root keys; a path such as
HKEY_LOCAL_MACHINE plus subkeys returns that key's immediate subkeys and
values (name, type, and formatted data).
Client side: enumerate with KEY_READ instead of KEY_ALL_ACCESS and map the
full value-type set (REG_SZ, REG_DWORD, REG_BINARY, REG_EXPAND_SZ,
REG_MULTI_SZ, REG_QWORD, REG_NONE) so unknown types are no longer
mis-reported as REG_SZ. The registry manager now always sends both the
TOKEN_REG_PATH and TOKEN_REG_KEY packets (an empty packet when a part is
empty), making the two-packet reply deterministic for the server.
Server side: add the three-phase flow (COMMAND_REGEDIT, COMMAND_REG_FIND,
then PATH plus KEY) with per-host pending state, parse the fixed-width
wire format with bounds checks, format value data for JSON, and guard
registry paths against exceeding MAX_PATH to protect the client stack.
Co-Authored-By: deepseek-v4-pro
Expose the server main-window audit log (host online/offline, operation
results, alerts) as a read-only, no-arg, in-memory MCP tool. The log lives
in m_CList_Message, a CListCtrl that is only safe to touch on the UI
thread, so add a thread-safe mirror m_MessageLog (guarded by m_cs) and keep
it in sync at the four item-mutation sites: ShowMessage, OnShowErrMessage,
OnMsglogDelete and OnMsglogClear. The tool reads the mirror directly under
m_cs and returns up to MAX_MESSAGE_COUNT (1000) entries, newest first.
Co-Authored-By: deepseek-v4-pro
Replacing the old connection on a duplicate login made the client
reconnect in a tight loop. Instead, park the new connection in a standby
list keyed by clientID and promote it into the main host list only after
the old connection is removed by heartbeat timeout or disconnect.
Fix heartbeat attribution so a parked standby refreshes its own
last-heartbeat time rather than the main connection's, otherwise the main
never times out and the standby can never take over. Gate the offline
notification on an actual removal with no promoted standby to avoid a
spurious "host offline" when takeover happens. Skip promoting a standby
already marked removed, and reuse the normal login sign/settings path when
a standby is promoted.
Co-Authored-By: deepseek-v4-pro
The Win32 EDIT control caps text at ~32K/64K chars by default, so the
client log dialog silently stopped accepting appends long before the
512KB truncation logic could run. Call SetLimitText(0) in both
CClientLog and CActivityDialog to lift the cap.
Also unify newline handling in Logger: each entry now ends with exactly
one '\n' at the source, so the in-memory ring-buffer dump (used by the
TCP log query) splits into lines correctly instead of relying on every
Mprintf caller ending its format with '\n'. writeToFile drops the extra
std::endl accordingly.
Additionally fix a CFont leak in CClientLog::OnInitDialog by moving the
font to a member.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When an online host has a remark set in the client list, use it in place of
the IP in the remote desktop ("远程桌面控制") and virtual screen ("远程虚拟屏幕")
dialog titles, falling back to the IP when no remark is present.
Add a GetTitleHostName() helper to both dialogs. CScreenSpyDlg resolves the
remark through its parent dialog's client map, while CHideScreenSpyDlg uses
the global g_2015RemoteDlg pointer; each falls back to m_IPAddress when the
host has no remark.
Co-Authored-By: deepseek-v4-pro
The view-mode "forbidden" cursor was applied with SetClassLongPtr(GCLP_HCURSOR),
which mutates the cursor for the whole window class instead of the single dialog
instance. Opening a second remote-desktop dialog without remote control overwrote
that shared class cursor with IDC_NO, so the already-controlled dialog also began
showing the forbidden icon even though it remained operable.
Move the view-mode cursor decision into OnSetCursor, which Windows routes per
window, and remove the now-unused class-cursor assignments. Remote-control mode
and the remote cursor-shape path are left unchanged.
Also drop the dead m_hRemoteCursor member and the leftover GetIconInfo block
(which leaked hbmMask/hbmColor in ScreenSpyDlg), plus the now-unreferenced
m_bMouseTracking flag.
Co-Authored-By: deepseek-v4-pro
Add the two P2d read-only MCP tools, both one-shot sub-links (mode A') on
the existing single-flight pending registry.
list_services sends COMMAND_SERVICES; the client's CServicesManager emits
TOKEN_SERVERLIST on sub-link creation, parsed as 5 null-terminated fields per
record (display_name/service_name/binary_path/status/start_type) with
zero-padding termination. Services are Windows-only, so LNX/MAC hosts get
-32005 up front instead of a 20s timeout.
get_client_log sends COMMAND_QUERY_LOG; the client's CClientLogManager dumps
its full in-memory Logger ring buffer once, then pushes deltas every 3s. The
MCP path takes the first (full) TOKEN_REPORT_LOG and cancels the sub-link so
later deltas stop, while the MFC log dialog keeps receiving deltas on the
non-pending branch. Log text is client ANSI on Windows, decoded by clientType
like process/file names. MessageHandle intercepts both with
IsPending -> TakeMainResponse + CancelIO. Sync the design doc (P2d section +
verification notes; Linux get_client_log timeout is a client-version gap).
Co-Authored-By: deepseek-v4-pro
The get_screenshot tool inherited the MFC thumbnail-preview profile, capping
the frame at 1024px wide (1280 on 4K source), which is too small for AI
vision/OCR of dense UI. Add an optional max_width argument that overrides the
width (clamped to the client's 64..1920 limit) while keeping the RTT-adaptive
jpegQuality. Omitted or 0 keeps the existing thumbnail profile; 1920 yields
near-native resolution (full 1080p on a 1080p source, 1920 wide on 4K). Sync
the design doc.
Co-Authored-By: deepseek-v4-pro
Add the two P2c MCP tools on top of the P2b protocol. list_files lists
drives (COMMAND_LIST_DRIVE -> TOKEN_DRIVE_LIST) or a directory (follow-up
COMMAND_LIST_FILES -> TOKEN_FILE_LIST on the same one-shot sub-link);
get_screenshot reuses the screen-preview RPC with a per-host reqId
correlation so stale or MFC-preview responses fall through to the MFC
path untouched. Both share the P2b single-flight pending registry.
Fix file/process name encoding: the client reads process names, paths and
file names via the ANSI (A) APIs, so they are GBK on Windows regardless of
the CLIENT_CAP_UTF8 capability bit (which governs window titles only).
Decode by clientType (LNX/MAC = UTF-8, Windows = 936) rather than
GetClientEncoding, and convert the list_files path to the client ANSI code
page before sending. Sync Mcp_Phase2_Design.md with the P2c design and the
encoding correction.
Co-Authored-By: deepseek-v4-pro
Window list (WSLIST) sub-connections carry no capability bits, so
GetClientEncoding fell back to FindHostByIP(peer IP) to locate the main
connection. Under FRP/NAT reverse proxy the sub-connection's socket peer
is 127.0.0.1 or an internal relay address, which does not match the main
connection's recorded public IP; the lookup returned NULL and the client's
UTF-8 window titles were decoded as CP936 (GBK), producing mojibake on
hosts with Chinese window titles.
Resolve the main connection by clientID first (pinned on the sub-connection
after TOKEN_CONN_AUTH via SetID), falling back to the peer-IP lookup only
for legacy clients that do not send TOKEN_CONN_AUTH.
Co-Authored-By: deepseek-v4-pro
Add three read-only P2b MCP tools over the existing protocol. list_processes and list_windows trigger the client via COMMAND_SYSTEM / COMMAND_WSLIST on the main connection and receive TOKEN_PSLIST / TOKEN_WSLIST on a one-shot sub-link; get_activity_history uses the main-connection RPC COMMAND_QUERY_ACTIVITY -> TOKEN_REPORT_ACTIVITY. A per-host single-flight pending registry (m_Pending) with a 20s timeout correlates each response to its request and rejects a concurrent request for the same host with -32003. Parsers stop on the first empty record to ignore the client's LocalSize trailing zero padding, and window titles are decoded per the client UTF-8 capability bit. MessageHandle only adds if-guarded branches, so the MFC dialogs are untouched. Sync Mcp_Phase2_Design.md with the mode A'/A architecture, the verification notes, and the registry-backed config location.
Co-Authored-By: deepseek-v4-pro
Add two pure in-memory MCP tools over the online host list. search_hosts filters by name/remark, IP, group and OS (all optional, AND-combined, ASCII case-insensitive substring match); get_host_detail returns one online host by id and answers -32602 for a missing/non-numeric id and -32002 for an unknown or offline id. Sync Mcp_Phase2_Design.md with the second-review corrections and the P2a implementation.
Co-Authored-By: deepseek-v4-pro
Add an optional MCP server (JSON-RPC 2.0 over Streamable HTTP) exposing
an online-host listing tool, protected by a Bearer token. Disabled by
default; configured via a new "Extensions > MCP Settings" dialog.
- McpServer: httplib + JSON-RPC 2.0 dispatch (initialize/ping/tools/list/tools/call)
- McpSettingsDlg: runtime-created dialog for enable/port/bind/token
- HostJson: extract single-host JSON serialization shared with WebService
- FRP: expose MCP port (union with listening/Web ports) when bound to 0.0.0.0
- i18n: en_US / zh_TW translations
Co-Authored-By: deepseek-v4-pro
Add 16x16 icons for the three context-menu items that previously had
none: "Activity History" (Client Management), "Browse Window" (Remote
Control), and "Proxy Port - Auto Start" (Client Proxy). Register three
new bitmap resources and extend the main dialog's bitmap array from 63
to 66 entries so each menu item renders an intuitive glyph.
Co-Authored-By: deepseek-v4-pro
Record the client's active-window history (start time, window title, and
dwell duration), newest first, skipping dwellings under 5 seconds and
breaking the timing on idle, capped at 500 entries. Add an "Activity
History" item inside the host's Client Management submenu that requests
the snapshot over the main connection and shows it in a new dialog. The
dialog rebuilds its edit control as a Unicode window so UTF-8 titles
render correctly instead of degrading to "?" through the MBCS ANSI
boundary. The menu item and dialog title go through _TR and are
localized in en_US and zh_TW.
Co-Authored-By: deepseek-v4-pro
Persist the sort column and direction chosen via the online host list
header (list\OnlineListSort registry key) so the next launch defaults to
the same sort. Re-apply the sort when hosts come online/offline so newly
connected hosts land in their sorted position instead of appending at the
end.
Co-Authored-By: deepseek-v4-pro
Persist the sort column and direction chosen via the list header so the
next time the process or window management dialog opens it defaults to
the same sort. Process and window lists are remembered separately
(list\ProcessListSort and list\WindowListSort registry keys) since their
columns have different meanings.
Co-Authored-By: deepseek-v4-pro
The process list stored the architecture string only in ItemData::Arch,
leaving Data[3] (the architecture column) empty. Sorting by the
architecture column therefore compared empty strings and did nothing.
Populate Data[3] with the architecture value so the sort works.
Co-Authored-By: deepseek-v4-pro
- Add "Unban IP" menu item to the log message list (m_CList_Message)
right-click context menu, performing two actions on the extracted IP:
1. Remove the IP from the IPBlacklist singleton and persist to config
2. Scan the entire history host list (m_ClientMap->GetAll()) for all
clients matching the IP with AUTH_FORBIDDEN status, and restore
them to UNAUTHORIZED (0) — requiring re-authorization for safety
- Reuses ExtractFirstIPFromMessage() for dependency-free IPv4 parsing
- Handles the case where an IP is not in the blacklist and has no
matching forbidden clients with a clear "no action needed" message
- Menu item is grayed out when no log entry is selected
- Sync en_US / zh_TW language files with new translation strings
Co-Authored-By: DeepSeek V4 Pro
- Add "Ban IP" menu item to the log message list (m_CList_Message)
right-click context menu, allowing users to extract IPv4 addresses from
selected log entries and add them directly to the IP blacklist
- Reuses ExtractFirstIPFromMessage() from the previous commit for
dependency-free IPv4 parsing with per-segment 0-255 validation
- Checks against whitelist before banning (whitelisted IPs cannot be
banned) and skips IPs already in blacklist with clear feedback
- Immediately persists via THIS_CFG.SetStr to the config ini file;
IPBlacklist singleton takes effect instantly — subsequent connections
from banned IPs are rejected in IOCPServer::OnAccept() which already
calls IsIPBlacklisted() on every new connection
- Menu item is grayed out when no log entry is selected
- Sync en_US / zh_TW language files with new translation strings
Co-Authored-By: DeepSeek V4 Pro
- Add "Online Notify" menu item to the log message list (m_CList_Message)
right-click context menu, allowing users to extract IPv4 addresses from
selected log entries and add them to notification keywords
- Implement ExtractFirstIPFromMessage() for dependency-free IPv4 parsing
with per-segment 0-255 validation, handling diverse log message formats
- Extend NotifyManager::ShouldNotify with IP column (ONLINELIST_IP)
fallback check, ensuring IP keywords match regardless of the user's
configured ColumnIndex setting
- OnMsglogLoginNotify only appends keywords and enables the rule without
overwriting the user's existing ColumnIndex or TriggerType settings
- Menu item is automatically grayed out when PowerShell is unavailable
or no log entry is selected
- Sync en_US / zh_TW language files with new translation strings
Co-Authored-By: DeepSeek V4 Pro
Commit 6d0b1bb (Feat: Add privilege column) inserted ONLINELIST_PRIVILEGE
at enum value 9, shifting INSTALLTIME from 9→10 and PATH from 12→13.
However, FileUpload_Lib (SimplePlugins) is compiled against a separate
copy of context.h that still uses the old enum values. Since
HostInfo.cpp's SaveClientMapData uses OLD enum values to read
sClientInfo[], it was reading privilegeStr for InstallTime and client
type for ProgramPath — corrupting the history host database.
Fix: move ONLINELIST_PRIVILEGE to value 16 (just before MAX), restoring
all original enum values (INSTALLTIME=9, PATH=12, etc.) so the
pre-built library can continue reading correct data without recompilation.
The 'permission' column now appears as the rightmost column in the
online host list instead of between 'version' and 'install time'.
Co-Authored-By: DeepSeek V4 Pro
- Add LicenseLimit field to LicenseInfo struct (0 = not set, unlimited)
- Add GetLicenseLimit/SetLicenseLimit: read/write LicenseLimit key in licenses.ini
- Append |lic:N to reserved field in TOKEN_AUTH response only when
LicenseLimit > 0; absent |lic: means no limit (client defaults to 9999),
so super admin authenticating to its own server is never falsely terminated
- Add "Sub-license limit" item in CLicenseDlg right-click menu (1-9999,
empty = clear limit); menu label shows current value in real time
- Limit change takes effect when sub-client re-authenticates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TOKEN_AUTH: when the server has a V2 private key, signs "SN|valid(0/1)"
with ECDSA P-256 and places "sig:<base64>" in the response reserved field.
Clients can verify server identity without changing the request format.
TOKEN_SERVER_VERIFY (251): added constant to commands.h; handler already
present in 2015RemoteDlg.cpp for the challenge-response server identity check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two changes to reduce unnecessary CScreenSpy restarts when connecting:
1. Client (ScreenManager.cpp): Initialize CScreenSpy with bitrate from
the locally-saved quality profile, so CMD_QUALITY_LEVEL arriving from
the server (same bitrate as default) hits SetBitRate(3000)==3000 and
skips the restart instead of comparing against the hard-coded 0.
Also fixes QualityLevel init to use the already-computed `quality`
variable (which honours the QUALITY_DISABLED override when algo!=NUL)
rather than re-reading the cfg key a second time.
2. Server (ScreenSpyDlg.cpp): Only send CMD_SCREEN_SIZE strategy=2 when
the session is in QUALITY_ADAPTIVE mode and a cached maxWidth exists.
Fixed quality levels already carry resolution via CMD_QUALITY_PROFILES,
so unconditionally sending CMD_SCREEN_SIZE caused a second restart when
the screen spy was still rebuilding from the first one.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>