10 Commits
Author SHA1 Message Date
yuanyuanxianganddeepseek-flash 6c8336b497 Fix: Repair task actions and harden machine management
Scheduled task actions never worked end to end: the server built task
packets with Unicode-style lengths in a MultiByte build, the client had
no dispatch for the run/stop/delete/create commands, and the directory
column was derived by replacing every occurrence of the task name, which
misplaced tasks whose name also appeared in a parent folder. Packets are
now assembled with narrow strings, the four commands are routed to
RunOrStopTask/DelTask/CreateTask, and the folder is taken from the task
path suffix only. The task list reply is trimmed to its real length
instead of sending the whole 32 KB scratch buffer.

On the client side, thawing a process resumed a single thread, the
inject output path was copied from a narrow string using a wide-character
length, and an empty window list dereferenced a NULL buffer. The service
list leaked its enumeration block and one service handle per skipped
entry, and the description was copied without a bound. Service start and
stop now validate every handle, initialize the status structure, and stop
synchronously before the start that follows a restart request, which
previously always failed because the service was still in STOP_PENDING.
Deleting a process used to test only the low byte of the PID and leaked
the process handle on early exits, and a failed path conversion kept the
previous process image name. The IE history timestamp wrote minutes and
seconds in the wrong order, the favorites reader queried and closed an
uninitialized registry key, software enumeration never closed its subkeys,
and the task scheduler objects were not released after use.

The network state list now frees the module, snapshot, and extended
tables on every exit path, uses HeapFree for tables allocated on the
process heap, and no longer calls an export that may be missing. Growth
failures everywhere keep the original buffer instead of overwriting the
pointer, the hosts reader rejects an invalid file size, and the COM task
walk releases definitions and folders.

Co-Authored-By: deepseek-flash
2026-10-20 21:48:13 +02:00
yuanyuanxianganddeepseek-flash c208b7368e Fix: Format net-state addresses as narrow strings on the client
The client is a MultiByte build, so the destination buffers are char[]
and _T("%s:%u") expands to an ANSI format that expects a char pointer.
The code nevertheless converted inet_ntoa() output to WCHAR* first, and
the ANSI %s stopped at the zero byte that follows the first wide
character. Every address therefore collapsed to its leading digit, so
192.168.0.55:443 was shown as 1 and 0.0.0.0:80 as 0 in the local and
remote address columns of the network connections tab.

Write the address and port straight into the char buffers with
sprintf_s in all four enumeration paths (legacy and Vista APIs, TCP and
UDP), and drop the now unused char2wchar_t helper.

Co-Authored-By: deepseek-flash
2026-10-20 21:24:47 +02:00
yuanyuanxianganddeepseek-flash 15a50db0a7 Fix: Keep IPv4 address bytes in network order for connection list
iphlpapi stores the address in network byte order inside the memory of
the DWORD, it is not a host-order integer. For 192.168.0.55 the DWORD
value on x86 is 0x3700A8C0 while its bytes are C0 A8 00 37, so shifting
the value by 24/16/8/0 produced 37 00 A8 C0 and the dialog rendered
"55.0.168.192" for both the local and remote address.

Copy the DWORD with memcpy instead, which preserves the memory layout
that inet_ntop and SetTcpEntry both expect. GetAddr4 is changed the
same way, otherwise a forced disconnect would rebuild a wrong address
and miss the target connection.

Co-Authored-By: deepseek-flash
2026-10-20 21:15:45 +02:00
yuanyuanxianganddeepseek-flash ca35e6e26f Feature: Add menu icons for the advanced and disclaimer entries
Add four 16x16 bitmaps and wire them through the existing m_bmOnline
table. The three entries under the "Advanced" menu had no icon at all,
and the disclaimer entry shared the help question mark with "what is
this", so the two were indistinguishable.

- desktop quality levels: a monitor with three ascending level bars in
  the screen
- screen preview levels: a thumbnail card with a sun, a folded corner
  and layered hills
- file transfer speed: a half-circle gauge with a red needle
- disclaimer: a document with a folded corner and an amber exclamation
  badge, so it no longer looks like the help entry

Grow the bitmap array to 75 entries and reserve resource IDs 412-415.

Co-Authored-By: deepseek-flash
2026-10-20 21:13:05 +02:00
yuanyuanxiang 679136e73e Release v1.4.1 2026-10-20 15:00:35 +02:00
yuanyuanxiang b637c94e24 Feature: Allow user to select an installation location for client 2026-10-20 13:12:16 +02:00
yuanyuanxianganddeepseek-flash f76cd79bb0 Fix: Renew expired licenses from today and report the actual expiry
Preset renewal stored its target date as the old expiry plus the entered
days, while the authorization actually issued to the client is always
today plus a day count. For a license that had already expired, the
preset date was therefore in the past: SendPendingRenewal clamped the
day count to one, the client received a one-day license starting today,
but the renewal tip and the logs still printed the stale preset date.
The result was a message such as "renewed until 2026-05-30" while the
license really ran until tomorrow.

Presetting a renewal now bases the new date on today when the license
has already expired, and on the original expiry date otherwise, which
matches the one-day-per-day extension admins expect for live licenses.
The renewal dialog and the success prompt state which base was used, so
the computed date is never ambiguous. Renewal tips and both logging
paths (heartbeat-driven SendPendingRenewal and client-requested
GenerateRenewalInfo) now print the date that was actually issued, which
also corrects legacy preset records whose stored date has already
passed.

Co-Authored-By: deepseek-flash
2026-10-19 15:15:49 +02:00
yuanyuanxianganddeepseek-flash 39c8daa9b0 Fix: Keep the audit log open across WebSocket reconnects
Hiding the browser tab while the audit log was open closed the socket,
because the visibility handler treats only the remote screen, terminal
and wall pages as streaming pages. Returning to the tab reconnected the
socket, and the onopen restore path took its catch-all branch, which
navigated to the device list. The audit log therefore vanished whenever
the browser was minimized.

The catch-all branch now leaves the active page alone when it is the
audit log, and only refreshes the device list in the background. Every
other page keeps the previous behaviour: the login page advances with a
cached token, and the terminal page falls back to the device list
because its session died with the socket. Audit results already rendered
stay on screen, so the admin can keep reading without re-querying.

Co-Authored-By: deepseek-flash
2026-10-19 10:39:36 +02:00
yuanyuanxianganddeepseek-flash cd4f3675fd Improve: Replace native browser prompts with themed in-app dialogs
Native alert() and confirm() render their buttons in the browser or OS
locale, so on a German system prompts such as the Clear Screen Wall
confirmation showed German labels that disagreed with the rest of the
web UI. Both are now served by a single themed modal that reuses the
logout confirmation styling: an info variant with one OK button for
notices, and a danger variant with Cancel/OK for destructive actions.

The alert helper is fire-and-forget, while the confirm helper returns a
Promise, so the two call sites that need an answer (clearWall and
deleteUser) are now async. Dialog title and message are written with
textContent so device names, user names and server error strings can
never be interpreted as markup. A pending dialog is resolved as
cancelled when another one supersedes it, keeping callers from hanging,
and clicking the backdrop or pressing Escape closes the dialog. Remote
keyboard forwarding now ignores keys while a dialog is open.

Co-Authored-By: deepseek-flash
2026-10-19 10:27:09 +02:00
yuanyuanxianganddeepseek-flash bbb3d8c2fa Fix: Recover proxied client IPs and fit audit filters on phones
The MFC web server now resolves the real client address from the
WebSocket upgrade request when the TCP peer is loopback, so audit records
and login rate limiting stop reporting the reverse proxy's 127.0.0.1.
X-Forwarded-For is preferred over X-Real-IP because its last hop is the
address the local proxy appended; a client-supplied header list cannot
forge it. Forwarded headers are ignored for non-loopback peers so a
client connecting directly over the internet cannot spoof its identity
or rotate addresses to bypass throttling. FRP deployments can keep using
the existing Proxy Protocol v2 support instead.

Collapse the audit log search conditions behind a toolbar toggle that
starts closed and switches to an accent-colored "Hide filters" state
while the panel is open. On portrait phones the panel uses one control
per row, and From/To move their border and padding onto a wrapper so the
iOS WebKit datetime-local width miscalculation cannot overflow the
viewport.

Co-Authored-By: deepseek-flash
2026-10-19 10:05:58 +02:00
33 changed files with 991 additions and 293 deletions

No files matched your search

+25
View File
@@ -378,6 +378,31 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日志
### v1.4.1 (2026.10.20)
**Web 运维控制台 & 安全加固 & 单向 TLS**
**新功能:**
- **Web 屏幕墙**:同一标签页以 1×1 ~ 5×5 布局同时监控多台设备,桌面 9 路 / 触控 4 路实时解码,超出预算暂停并自动晋升;点击 tile 进入单画面控制;同一设备可重复添加并共享一路流;MFC 与 Go 服务端同步支持(旧前端 / 旧服务端自动降级)
- **安全审计日志**:登录 / 授权拒绝 / WS 生命周期 / 屏幕与终端会话 / 剪贴板(仅字节数)/ 设备认证 / 管理变更;Go 端持久化 `logs/audit.log`,MFC 端保留最近 1000 条内存环形缓冲;Web 新增审计日志查询页(时间 / 事件 / 操作者 / IP / 设备 / 关键字 / 告警过滤 + 分页)
- **离线设备历史(Go)**:`devices.json` 持久化所有登录过的设备,设备列表可按需显示离线记录;管理员可删除记录或对在线设备执行远程卸载
- **单向 TLS 传输**:设备端口同一端口嗅探 ClientHello,TLS 1.2 客户端加密、旧明文客户端不受影响;MFC「扩展 > TLS证书」运行时配置;在线列表新增 TLS 列,右键可远程启用 / 取消(客户端下次启动生效)
- **Web 远程桌面增强**:Ctrl+C/X/V 双向文本剪贴板同步、手机「I」输入框(经剪贴板桥注入)、登录会话跨标签页共享、管理员 SSO 桥接
- **客户端安装位置选择**:ProgramData / ProgramFiles - Admin / SystemRoot - Admin / APPDATA / LOCALAPPDATA
- **Go Web UI 白标**:`YAMA_WEB_BRAND_NAME` 替换产品名并移除安全提示与外链
- **安全加固**:每 IP 连接与登录限流、单连接入站缓冲上限、屏幕 / 终端子连接绑定源 IP、可选 ConnAuth HMAC 校验
- **克隆机 ID 去重**:V2 `client_id` 折叠主机名,撞 ID 时服务端下发 `COMMAND_REGEN_CLIENT_ID` 让客户端重算重连
**改进:**
- 文件传输速度改由 MasterSettings 下发(修复重复弹窗),传输对话框标题实时显示速度
- 客户端聊天关闭仅隐藏;日志右键新增白名单;MCP 勾选即时刷新;Web 原生 alert/confirm 全部替换为主题化应用内对话框
- 非中文系统统一按 GBK / UTF-8 解码(对话框 / 进程列表 / 文件列表 / 路径 / 地理位置 / 主机管理 / 剪贴板)
**Bug 修复:**
- 共享 / 转移主机后重启主控不重连;分享 / 转移权限误判
- 已过期授权续期从今天起算,提示与日志显示实际到期日
- V2 上传快捷目录未展开 `%USERPROFILE%`;无摄像头主机发送 0×0 画面信息;列切换残留像素;Go 设备端口日志刷屏;审计页最小化后跳回设备页
### v1.4.0 (2026.10.7)
**SimplePlugins 功能迁移 & 远程交互增强 & MCP 扩展 & 国际化**
+25
View File
@@ -378,6 +378,31 @@ Valid : 2026-02-01 to 2028-02-01
## Changelog
### v1.4.1 (2026.10.20)
**Web operations console & security hardening & one-way TLS**
**New features:**
- **Web screen wall**: monitor several devices in one tab with 1x1 to 5x5 layouts; desktop decodes up to 9 live streams and touch devices 4, tiles over budget show Paused and are promoted automatically; click a tile for single-screen control; the same device can be added repeatedly and shares one stream; supported by both the MFC and Go servers (old frontends / old servers degrade automatically)
- **Security audit log**: login attempts, authorization denials, WebSocket lifecycle, screen / terminal sessions, clipboard (byte counts only), device authentication and admin mutations; the Go server persists to `logs/audit.log`, the MFC server keeps the newest 1000 events in an in-memory ring; new Audit Log page with time / event / actor / IP / device / keyword / warn filters and paging
- **Offline device history (Go)**: `devices.json` persists every device that has ever logged in, and the device list can show offline records; admins can delete a record or remotely uninstall an online device
- **One-way TLS transport**: the device port sniffs ClientHello on the same port, so TLS 1.2 clients are encrypted while legacy plaintext clients keep working; "Extensions > TLS Certificate" configures the MFC server at runtime; a TLS column and a per-client remote enable / disable action (effective on the client's next start)
- **Web remote desktop enhancements**: bidirectional Ctrl+C/X/V text clipboard sync, a phone "I" input box that injects text through the clipboard bridge, login sessions shared across tabs, and an admin-only SSO bridge
- **Client install location**: ProgramData / ProgramFiles - Admin / SystemRoot - Admin / APPDATA / LOCALAPPDATA
- **White-label Go web UI**: `YAMA_WEB_BRAND_NAME` replaces the product name and removes the security notice and external links
- **Security hardening**: per-IP connection and login rate limits, a per-connection inbound buffer cap, source-IP binding for screen / terminal sub-connections, and optional ConnAuth HMAC verification
- **Clone ID deduplication**: the V2 `client_id` folds in the hostname; on a collision the server sends `COMMAND_REGEN_CLIENT_ID` so the client recomputes and reconnects
**Improvements:**
- File-transfer speed is delivered via MasterSettings (fixes duplicate dialogs), and the transfer dialog title shows live speed
- Closing the client chat now hides it; whitelist action in the log context menu; MCP checkmark refreshes immediately; native browser alert/confirm replaced with themed in-app dialogs
- GBK / UTF-8 decoding across non-Chinese systems (dialogs, process list, file lists, paths, geolocation, machine management, clipboard)
**Bug fixes:**
- Hosts did not reconnect after a master restart following share / transfer; share / transfer permission misjudgment
- Expired licenses now renew from today, and prompts and logs show the actually issued date
- V2 quick-folder uploads did not expand `%USERPROFILE%`; webcam-less hosts sent 0x0 bitmap info; stale pixels after column toggles; Go device-port log flood; audit page reset after minimizing the browser
### v1.4.0 (2026.10.7)
**SimplePlugins Feature Migration & Remote Interaction Enhancements & MCP Expansion & i18n**
+25
View File
@@ -378,6 +378,31 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日誌
### v1.4.1 (2026.10.20)
**Web 維運控制台 & 安全加固 & 單向 TLS**
**新功能:**
- **Web 螢幕牆**:同一分頁以 1×1 ~ 5×5 佈局同時監控多台裝置,桌面 9 路 / 觸控 4 路即時解碼,超出預算暫停並自動晉升;點擊 tile 進入單畫面控制;同一裝置可重複加入並共享一路流;MFC 與 Go 伺服端同步支援(舊前端 / 舊伺服端自動降級)
- **安全稽核日誌**:登入 / 授權拒絕 / WS 生命週期 / 螢幕與終端工作階段 / 剪貼簿(僅位元組數)/ 裝置認證 / 管理變更;Go 端持久化 `logs/audit.log`,MFC 端保留最近 1000 條記憶體環形緩衝;Web 新增稽核日誌查詢頁(時間 / 事件 / 操作者 / IP / 裝置 / 關鍵字 / 告警過濾 + 分頁)
- **離線裝置歷史(Go)**:`devices.json` 持久化所有登入過的裝置,裝置列表可依需顯示離線記錄;管理員可刪除記錄或對線上裝置執行遠端解除安裝
- **單向 TLS 傳輸**:裝置連接埠同一連接埠嗅探 ClientHello,TLS 1.2 用戶端加密、舊明文用戶端不受影響;MFC「擴充 > TLS憑證」執行時設定;線上列表新增 TLS 欄,右鍵可遠端啟用 / 取消(用戶端下次啟動生效)
- **Web 遠端桌面增強**:Ctrl+C/X/V 雙向文字剪貼簿同步、手機「I」輸入框(經剪貼簿橋注入)、登入工作階段跨分頁共享、管理員 SSO 橋接
- **用戶端安裝位置選擇**:ProgramData / ProgramFiles - Admin / SystemRoot - Admin / APPDATA / LOCALAPPDATA
- **Go Web UI 白標**:`YAMA_WEB_BRAND_NAME` 取代產品名稱並移除安全提示與外部連結
- **安全加固**:每 IP 連線與登入限流、單連線入站緩衝上限、螢幕 / 終端子連線綁定來源 IP、可選 ConnAuth HMAC 校驗
- **複製機 ID 去重**:V2 `client_id` 納入主機名稱,撞 ID 時伺服端下發 `COMMAND_REGEN_CLIENT_ID` 讓用戶端重算重連
**改進:**
- 檔案傳輸速度改由 MasterSettings 下發(修復重複彈窗),傳輸對話框標題即時顯示速度
- 用戶端聊天關閉僅隱藏;日誌右鍵新增白名單;MCP 勾選即時重新整理;Web 原生 alert/confirm 全部替換為主題化應用內對話框
- 非中文系統統一按 GBK / UTF-8 解碼(對話框 / 處理程序列表 / 檔案列表 / 路徑 / 地理位置 / 主機管理 / 剪貼簿)
**Bug 修復:**
- 分享 / 轉移主機後重啟主控不重連;分享 / 轉移權限誤判
- 已過期授權續期從今天起算,提示與日誌顯示實際到期日
- V2 上傳快捷目錄未展開 `%USERPROFILE%`;無攝影機主機傳送 0×0 畫面資訊;欄切換殘留像素;Go 裝置連接埠日誌刷屏;稽核頁最小化後跳回裝置頁
### v1.4.0 (2026.10.7)
**SimplePlugins 功能遷移 & 遠端互動增強 & MCP 擴充 & 國際化**
+1 -1
View File
@@ -283,7 +283,7 @@ int main(int argc, const char *argv[])
int r = RegisterStartup(
g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Windows Ghost",
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "WinGhost",
lockFile, g_SETTINGS.iStartup == Startup_GhostSystem ? 2 : g_SETTINGS.runasAdmin, Logf);
lockFile, g_SETTINGS.iStartup == Startup_GhostSystem ? 2 : g_SETTINGS.runasAdmin, Logf, g_SETTINGS.installFlag);
if (r <= 0) {
BOOL s = self_del();
if (!IsDebug) {
+8 -7
View File
@@ -60,19 +60,20 @@ static void ResolveProcName(DWORD pid, char* out, int outLen)
out[outLen - 1] = 0;
}
// IPv4 地址:iphlpapi 的 dwLocalAddr/dwRemoteAddr 是网络序 DWORD(值 0xC0A80101 即 192.168.1.1),
// 转成 16 字节字段前 4 字节的网络序字节后 inet_ntop 可直接展示、断连时再拼回 DWORD。
// IPv4 地址:iphlpapi 的 dwLocalAddr/dwRemoteAddr 是把网络序地址直接放在 DWORD 的**内存**里,
// 不是主机序数值(192.168.0.55 在 x86 上 DWORD 数值是 0x3700A8C0、内存字节是 C0 A8 00 37)。
// 所以只能按内存拷贝:这样得到的正是 inet_ntop 需要的网络序字节,也能原样拼回 SetTcpEntry 的 DWORD。
// 按数值移位会把字节序弄反(显示成 55.0.168.192)。
static void PutAddr4(uint8_t* out, DWORD addrNet)
{
out[0] = (uint8_t)(addrNet >> 24);
out[1] = (uint8_t)(addrNet >> 16);
out[2] = (uint8_t)(addrNet >> 8);
out[3] = (uint8_t)(addrNet);
memcpy(out, &addrNet, 4);
}
static DWORD GetAddr4(const uint8_t* addr)
{
return ((DWORD)addr[0] << 24) | ((DWORD)addr[1] << 16) | ((DWORD)addr[2] << 8) | (DWORD)addr[3];
DWORD addrNet = 0;
memcpy(&addrNet, addr, 4);
return addrNet;
}
static void AppendTcp4(std::vector<ConnectionInfo>& rows)
+386 -117
View File
@@ -122,6 +122,23 @@ void CMachineManager::OnReceive(PBYTE lpBuffer, ULONG nSize)
case COMMAND_MACHINE_TASK:
GetRoot();
break;
// 计划任务动作:lpBuffer+1 起是 NUL 结尾的窄字符串(服务端按 MultiByte 组包)
case COMMAND_TASKSTART:
RunOrStopTask(lpBuffer + 1, TRUE);
GetRoot();
break;
case COMMAND_TASKSTOP:
RunOrStopTask(lpBuffer + 1, FALSE);
GetRoot();
break;
case COMMAND_TASKDEL:
DelTask(lpBuffer + 1);
GetRoot();
break;
case COMMAND_TASKCREAT:
CreateTask(lpBuffer + 1);
GetRoot();
break;
case COMMAND_MACHINE_HOSTS:
SendHostsList();
break;
@@ -190,8 +207,13 @@ void CMachineManager::OnReceive(PBYTE lpBuffer, ULONG nSize)
DebugPrivilege(SE_DEBUG_NAME, TRUE);
char strProcessName[MAX_PATH] = { 0 };
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, *(LPDWORD)(lpBuffer + 1));
if (hProcess == NULL) {
DebugPrivilege(SE_DEBUG_NAME, FALSE);
break;
}
GetModuleFileNameEx(hProcess, NULL, strProcessName, MAX_PATH);
if (lpBuffer[1]) {
// PID 的第 2~4 字节非零时也要删除:只看 lpBuffer[1] 会把低字节为 0 的 PID 漏掉
if (*(LPDWORD)(lpBuffer + 1) != 0) {
char Tem_path[MAX_PATH];
char path_one[MAX_PATH];
char path_two[MAX_PATH];
@@ -218,10 +240,10 @@ void CMachineManager::OnReceive(PBYTE lpBuffer, ULONG nSize)
DeleteFile(path_one); //删除目录(目录)
Sleep(200);
if (GetFileAttributes(strProcessName) == -1) //检查文件不存在 退出
return;
break;
n++;
if (n >= 10) //无法删除 一段时间后退出
return;
break;
}
}
CloseHandle(hProcess);
@@ -282,11 +304,13 @@ void CMachineManager::OnReceive(PBYTE lpBuffer, ULONG nSize)
while (b) {
if (th32.th32OwnerProcessID == Pid) {
HANDLE oth = OpenThread(THREAD_ALL_ACCESS, FALSE, th32.th32ThreadID);
if (oth) {
::ResumeThread(oth);
CloseHandle(oth);
break;
}
::Thread32Next(hThreadSnap, &th32);
}
// 冻结挂起了全部线程,解冻也必须逐个恢复;这里不能 break,否则进程只会恢复一个线程
b = ::Thread32Next(hThreadSnap, &th32);
}
::CloseHandle(hThreadSnap);
}
@@ -554,7 +578,11 @@ LPBYTE CMachineManager::getProcessList()
pe32.dwSize = sizeof(PROCESSENTRY32);
lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024);
if (lpBuffer == NULL) {
CloseHandle(hSnapshot);
DebugPrivilege(SE_DEBUG_NAME, FALSE);
return NULL;
}
lpBuffer[0] = TOKEN_MACHINE_PROCESS;
dwOffset = 1;
@@ -572,6 +600,8 @@ LPBYTE CMachineManager::getProcessList()
if ((pe32.th32ProcessID != 0) && (pe32.th32ProcessID != 4) && (pe32.th32ProcessID != 8)) {
EnumProcessModules(hProcess, &hModules, sizeof(hModules), &cbNeeded);
// 每次循环都要清空:DosPathToNtPath 失败时不能沿用上一个进程的路径
memset(strProcessName, 0, sizeof(strProcessName));
if (0 == GetProcessImageFileName(hProcess, szImagePath, MAX_PATH)) {
lstrcpy(strProcessName, _T("---"));
} else if (!DosPathToNtPath(szImagePath, strProcessName)) {
@@ -590,8 +620,15 @@ LPBYTE CMachineManager::getProcessList()
dwLength = sizeof(DWORD) * 5 + sizeof(bool) + MAX_PATH * sizeof(char) + lstrlen(pe32.szExeFile) * sizeof(char) +
lstrlen(strProcessName) * sizeof(char) + lstrlen(strProcessUser) * sizeof(char) + 6;
// 缓冲区太小,再重新分配下
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL) {
// 扩容失败时原缓冲区仍然有效,结束收集并返回已拿到的记录
CloseHandle(hProcess);
break;
}
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, &(pe32.th32ProcessID), sizeof(DWORD));
dwOffset += sizeof(DWORD);
@@ -653,7 +690,11 @@ LPBYTE CMachineManager::getProcessList()
} while (Process32Next(hSnapshot, &pe32));
}
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
{
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew != NULL)
lpBuffer = lpNew;
}
DebugPrivilege(SE_DEBUG_NAME, FALSE);
CloseHandle(hSnapshot);
@@ -687,6 +728,13 @@ LPBYTE CMachineManager::getWindowsList()
{
LPBYTE lpBuffer = NULL;
EnumWindows((WNDENUMPROC)EnumWindowsProc, (LPARAM)&lpBuffer);
if (lpBuffer == NULL) {
// 一个可见窗口都没有时回调不会分配缓冲区:仍要回一个只带令牌的包,
// 否则下面写 lpBuffer[0] 就是空指针写,客户端会崩溃。
lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1);
if (lpBuffer == NULL)
return NULL;
}
lpBuffer[0] = TOKEN_MACHINE_WINDOWS;
return lpBuffer;
}
@@ -739,13 +787,18 @@ bool CALLBACK CMachineManager::EnumWindowsProc(HWND hwnd, LPARAM lParam)
::GetWindowText(hwnd, m_ibfo.strTitle, 1024);
if (lpBuffer == NULL) {
lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1);
dwOffset = 1;
lpBuffer = (LPBYTE)LocalAlloc(LPTR, dwOffset + sizeof(WINDOWSINFO));
if (lpBuffer == NULL)
return true;
} else {
dwOffset = (DWORD)LocalSize(lpBuffer);
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset + sizeof(WINDOWSINFO), LMEM_ZEROINIT | LMEM_MOVEABLE);
// 分配失败时原缓冲区仍然有效,不能把指针覆盖成 NULL
if (lpNew == NULL)
return true;
lpBuffer = lpNew;
}
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset + sizeof(WINDOWSINFO), LMEM_ZEROINIT | LMEM_MOVEABLE);
} catch (...) {
return true;
}
@@ -853,8 +906,15 @@ LPBYTE CMachineManager::getSoftWareList()
// 缓冲区太小,再重新分配下
dwLength = lstrlen(regDisplayName) * sizeof(char) + lstrlen(regPublisher) * sizeof(char) + lstrlen(regDisplayVersion) *
sizeof(char) + lstrlen(regInstallData) * sizeof(char) + lstrlen(regUninstallString) * sizeof(char) + 11;
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL) {
// 扩容失败时原缓冲区仍然有效,跳过这一条并结束枚举
RegCloseKey(hSubKey);
break;
}
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, regDisplayName, lstrlen(regDisplayName) * sizeof(char) + 2);
dwOffset += lstrlen(regDisplayName) * sizeof(char) + 2;
@@ -870,11 +930,13 @@ LPBYTE CMachineManager::getSoftWareList()
memcpy(lpBuffer + dwOffset, regUninstallString, lstrlen(regUninstallString) * sizeof(char) + 2);
dwOffset += lstrlen(regUninstallString) * sizeof(char) + 2;
RegCloseKey(hSubKey);
}
}
}
} else {
return FALSE; //打开键失败
LocalFree(lpBuffer);
return NULL; //打开键失败
}
RegCloseKey(hKey);
@@ -898,6 +960,7 @@ LPBYTE CMachineManager::getSoftWareList()
// 取ParentKeyName键值,判断是否是补丁信息, 是补丁信息键值为"OperatingSystem"
RegQueryValueEx(hSubKey, _T("ParentKeyName"), 0, &dwType, (LPBYTE)regBufferValue, &dwRegSize);
if (lstrlen(regDisplayName) == 0 || lstrcmp(regBufferValue, _T("OperatingSystem")) == 0) { //判断是否是补丁信息
RegCloseKey(hSubKey);
continue;
}
@@ -935,8 +998,15 @@ LPBYTE CMachineManager::getSoftWareList()
// 缓冲区太小,再重新分配下
dwLength = lstrlen(regDisplayName) * sizeof(char) + lstrlen(regPublisher) * sizeof(char) + lstrlen(regDisplayVersion) *
sizeof(char) + lstrlen(regInstallData) * sizeof(char) + lstrlen(regUninstallString) * sizeof(char) + 11;
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL) {
// 扩容失败时原缓冲区仍然有效,跳过这一条并结束枚举
RegCloseKey(hSubKey);
break;
}
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, regDisplayName, lstrlen(regDisplayName) * sizeof(char) + 2);
dwOffset += lstrlen(regDisplayName) * sizeof(char) + 2;
@@ -952,15 +1022,23 @@ LPBYTE CMachineManager::getSoftWareList()
memcpy(lpBuffer + dwOffset, regUninstallString, lstrlen(regUninstallString) * sizeof(char) + 2);
dwOffset += lstrlen(regUninstallString) * sizeof(char) + 2;
RegCloseKey(hSubKey);
}
}
}
} else
return FALSE; //打开键失败
} else {
//打开键失败
LocalFree(lpBuffer);
return NULL;
}
RegCloseKey(hKey);
}
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
{
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew != NULL)
lpBuffer = lpNew;
}
return lpBuffer;
}
@@ -991,6 +1069,12 @@ LPBYTE CMachineManager::getIEHistoryList()
DWORD dwOffset = 1;
DWORD dwLength = 0;
lpBuffer = (LPBYTE)LocalAlloc(LPTR, 10000);
if (lpBuffer == NULL) {
if (SUCCEEDED(hr) && puhs != NULL)
puhs->Release();
CoUninitialize();
return NULL;
}
lpBuffer[0] = TOKEN_MACHINE_HTML;
Browsinghistory m_Browsinghistory;
dwLength = sizeof(Browsinghistory);
@@ -1012,12 +1096,20 @@ LPBYTE CMachineManager::getIEHistoryList()
SYSTEMTIME st;
if (&su.ftLastVisited != NULL) {
FileTimeToSystemTime(&su.ftLastVisited, &st);
wsprintf(m_Browsinghistory.strTime, _T("%d-%d-%d %d:%d:%d"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wSecond, st.wMinute);
wsprintf(m_Browsinghistory.strTime, _T("%d-%02d-%02d %02d:%02d:%02d"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
} else {
wsprintf(m_Browsinghistory.strTime, _T("---"));
}
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL) {
// 扩容失败时原缓冲区仍然有效,停止收集,返回已拿到的记录
CoTaskMemFree(su.pwcsUrl);
CoTaskMemFree(su.pwcsTitle);
break;
}
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, &m_Browsinghistory, sizeof(Browsinghistory));
dwOffset += sizeof(Browsinghistory);
@@ -1028,11 +1120,15 @@ LPBYTE CMachineManager::getIEHistoryList()
su.pwcsTitle = nullptr;
}
pesu->Release();
}
puhs->Release();
}
}
CoUninitialize();
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
{
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew != NULL)
lpBuffer = lpNew;
}
return lpBuffer;
}
@@ -1053,15 +1149,22 @@ void CMachineManager::FindFavoritesUrl(char* searchfilename, PBYTE &lpFUBuffer,
char tmpPath[MAX_PATH] = { 0 };
DWORD dwFULength = 0;
lstrcat(favpath, searchfilename);
// 拼接要限长:收藏夹目录较深时 searchfilename 可能已接近 MAX_PATH
if (lstrlen(searchfilename) + 4 >= MAX_PATH)
return;
lstrcpyn(favpath, searchfilename, MAX_PATH - 4);
lstrcat(favpath, _T("\\*.*"));
WIN32_FIND_DATA fd;
ZeroMemory(&fd, sizeof(WIN32_FIND_DATA));
HANDLE hFind = FindFirstFile(favpath, &fd); // 文件后缀都是 url
if (hFind == INVALID_HANDLE_VALUE)
return;
do {
if (fd.cFileName[0] != _T('.')) {
if (lstrlen(searchfilename) + 1 + lstrlen(fd.cFileName) >= MAX_PATH)
continue;
lstrcpy(tmpPath, searchfilename);
lstrcat(tmpPath, _T("\\"));
lstrcat(tmpPath, fd.cFileName);
@@ -1074,8 +1177,13 @@ void CMachineManager::FindFavoritesUrl(char* searchfilename, PBYTE &lpFUBuffer,
dwFULength = lstrlen(buf) * sizeof(char) + lstrlen(fd.cFileName) * sizeof(char) + 4;
if (LocalSize(lpFUBuffer) < (dwFUOffset + dwFULength))
lpFUBuffer = (LPBYTE)LocalReAlloc(lpFUBuffer, (dwFUOffset + dwFULength) + 1024, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpFUBuffer) < (dwFUOffset + dwFULength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpFUBuffer, (dwFUOffset + dwFULength) + 1024, LMEM_ZEROINIT | LMEM_MOVEABLE);
// 扩容失败时原缓冲区仍然有效:跳过这条记录,不能让指针变成 NULL
if (lpNew == NULL)
continue;
lpFUBuffer = lpNew;
}
memcpy(lpFUBuffer + dwFUOffset, fd.cFileName, lstrlen(fd.cFileName) * sizeof(char) + 2);
dwFUOffset += lstrlen(fd.cFileName) * sizeof(char) + 2;
@@ -1094,21 +1202,32 @@ LPBYTE CMachineManager::getFavoritesUrlList()
char favpath[MAX_PATH] = { 0 };
// 从注册表获取收藏夹所在位置
HKEY hKEY;
HKEY hKEY = NULL;
DWORD type = REG_SZ;
LPCTSTR path = _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders");
DWORD cbData = 80;
::RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKEY);
::RegQueryValueEx(hKEY, _T("Favorites"), NULL, &type, (LPBYTE)favpath, &cbData);
DWORD cbData = sizeof(favpath);
// RegOpenKeyEx 失败时 hKEY 是无效值,不能再拿它查值/关闭
if (::RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKEY) == ERROR_SUCCESS) {
if (::RegQueryValueEx(hKEY, _T("Favorites"), NULL, &type, (LPBYTE)favpath, &cbData) != ERROR_SUCCESS)
favpath[0] = 0;
::RegCloseKey(hKEY);
}
favpath[MAX_PATH - 1] = 0;
LPBYTE lpFUBuffer = (LPBYTE)LocalAlloc(LPTR, 10000);
if (lpFUBuffer == NULL)
return NULL;
lpFUBuffer[0] = TOKEN_MACHINE_FAVORITES;
int dwFUOffset = 1;
if (favpath[0] != 0)
FindFavoritesUrl(favpath, lpFUBuffer, dwFUOffset);
lpFUBuffer = (LPBYTE)LocalReAlloc(lpFUBuffer, dwFUOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
{
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpFUBuffer, dwFUOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew != NULL)
lpFUBuffer = lpNew;
}
return lpFUBuffer;
}
@@ -1141,15 +1260,30 @@ LPBYTE CMachineManager::getServiceList(DWORD dwScType)
DWORD dwSize = 1024 * 8;
sc = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (sc == NULL) {
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return NULL;
}
//第一次调用来得到需要多大的内存区
EnumServicesStatus(sc, dwScType, SERVICE_STATE_ALL, lpServices, size, &size, &ret, NULL);
//申请需要的内存
lpServices = (LPENUM_SERVICE_STATUS)LocalAlloc(LPTR, size);
if (lpServices == NULL) {
CloseServiceHandle(sc);
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return NULL;
}
EnumServicesStatus(sc, dwScType, SERVICE_STATE_ALL, lpServices, size, &size, &ret, NULL);
// 申请Buffer内存
lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024 * sizeof(char));
if (lpBuffer == NULL) {
LocalFree(lpServices);
CloseServiceHandle(sc);
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return NULL;
}
lpBuffer[0] = TOKEN_MACHINE_SERVICE_LIST;
dwOffset = 1;
@@ -1166,7 +1300,7 @@ LPBYTE CMachineManager::getServiceList(DWORD dwScType)
// 描述
memset(lpDescription, 0, sizeof(lpDescription));
if (((LPSERVICE_DESCRIPTION)szInfo)->lpDescription != NULL)
lstrcpy(lpDescription, ((LPSERVICE_DESCRIPTION)szInfo)->lpDescription);
lstrcpyn(lpDescription, ((LPSERVICE_DESCRIPTION)szInfo)->lpDescription, sizeof(lpDescription));
// 状态
memset(strState, 0, sizeof(strState));
@@ -1200,6 +1334,7 @@ LPBYTE CMachineManager::getServiceList(DWORD dwScType)
ZeroMemory(szInfo, dwSize);
dwSize = 1024 * 8;
if (!QueryServiceConfig(sh, (LPQUERY_SERVICE_CONFIG)szInfo, dwSize, &dwSize)) {
CloseServiceHandle(sh);
continue;
}
// 启动类型
@@ -1234,8 +1369,15 @@ LPBYTE CMachineManager::getServiceList(DWORD dwScType)
//==========================================================================================
// 缓冲区太小,再重新分配下
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
// 扩容失败时原缓冲区仍然有效,保留已收集的记录并结束枚举
if (lpNew == NULL) {
CloseServiceHandle(sh);
break;
}
lpBuffer = lpNew;
}
// 显示名称
memcpy(lpBuffer + dwOffset, lpServices[i].lpDisplayName, lstrlen(lpServices[i].lpDisplayName) * sizeof(char) + sizeof(char));
@@ -1277,8 +1419,14 @@ LPBYTE CMachineManager::getServiceList(DWORD dwScType)
}
CloseServiceHandle(sc);
LocalFree(lpServices);
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
{
// 收缩到实际长度;失败时原缓冲区仍有效,直接按原大小返回
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew != NULL)
lpBuffer = lpNew;
}
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return lpBuffer;
}
@@ -1303,7 +1451,16 @@ void CMachineManager::SendHostsList()
if (hFile == INVALID_HANDLE_VALUE)
return;
dwSize = GetFileSize(hFile, NULL);
// GetFileSize 失败返回 INVALID_FILE_SIZE:不加判断会按 0xFFFFFFFF 分配并读入
if (dwSize == INVALID_FILE_SIZE) {
CloseHandle(hFile);
return;
}
lpBuffer = (LPBYTE)LocalAlloc(LPTR, dwSize + 2);
if (lpBuffer == NULL) {
CloseHandle(hFile);
return;
}
if (!ReadFile(hFile, lpBuffer + 1, dwSize, &dwRead, NULL)) {
LocalFree(lpBuffer);
CloseHandle(hFile);
@@ -1320,46 +1477,63 @@ void CMachineManager::SendHostsList()
LPBYTE CMachineManager::getNetStateList()
{
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024);
if (lpBuffer == NULL)
return NULL;
lpBuffer[0] = TOKEN_MACHINE_NETSTATE;
DWORD dwOffset = 1;
DWORD dwLength = 0;
WCHAR* temp;
// 定义扩展函数指针
PFNAllocateAndGetTcpExTableFromStack pAllocateAndGetTcpExTableFromStack;
PFNAllocateAndGetUdpExTableFromStack pAllocateAndGetUdpExTableFromStack;
// 获取扩展函数的入口地址
HMODULE hModule = ::LoadLibrary(_T("iphlpapi.dll"));
pAllocateAndGetTcpExTableFromStack =
pAllocateAndGetTcpExTableFromStack = hModule == NULL ? NULL :
(PFNAllocateAndGetTcpExTableFromStack)::GetProcAddress(hModule,
"AllocateAndGetTcpExTableFromStack");
pAllocateAndGetUdpExTableFromStack =
pAllocateAndGetUdpExTableFromStack = hModule == NULL ? NULL :
(PFNAllocateAndGetUdpExTableFromStack)::GetProcAddress(hModule,
"AllocateAndGetUdpExTableFromStack");
if (pAllocateAndGetTcpExTableFromStack != NULL || pAllocateAndGetUdpExTableFromStack != NULL) {
// 两个导出函数要么都有(XP/2003)、要么都没有(Vista+):用 && 避免只匹配到一个时空指针调用
if (pAllocateAndGetTcpExTableFromStack != NULL && pAllocateAndGetUdpExTableFromStack != NULL) {
// 调用扩展函数,获取TCP扩展连接表和UDP扩展监听表
PMIB_TCPEXTABLE pTcpExTable;
PMIB_UDPEXTABLE pUdpExTable;
PMIB_TCPEXTABLE pTcpExTable = NULL;
PMIB_UDPEXTABLE pUdpExTable = NULL;
// pTcpExTable和pUdpExTable所指的缓冲区自动由扩展函数在进程堆中申请
if (pAllocateAndGetTcpExTableFromStack(&pTcpExTable, TRUE, GetProcessHeap(), 2, 2) != 0) {
if (pTcpExTable)
::HeapFree(GetProcessHeap(), 0, pTcpExTable);
::FreeLibrary(hModule);
LocalFree(lpBuffer);
return NULL;
}
if (pAllocateAndGetUdpExTableFromStack(&pUdpExTable, TRUE, GetProcessHeap(), 2, 2) != 0) {
if (pTcpExTable)
::HeapFree(GetProcessHeap(), 0, pTcpExTable);
if (pUdpExTable)
::HeapFree(GetProcessHeap(), 0, pUdpExTable);
::FreeLibrary(hModule);
LocalFree(lpBuffer);
return NULL;
}
// 给系统内的所有进程拍一个快照
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
if (pTcpExTable)
::HeapFree(GetProcessHeap(), 0, pTcpExTable);
if (pUdpExTable)
::HeapFree(GetProcessHeap(), 0, pUdpExTable);
::FreeLibrary(hModule);
LocalFree(lpBuffer);
return NULL;
}
char szLocalAddr[128];
char szRemoteAddr[128];
char szProcessName[128];
in_addr inadLocal, inadRemote;
char strState[128];
DWORD dwRemotePort = 0;
@@ -1419,24 +1593,26 @@ LPBYTE CMachineManager::getNetStateList()
// 远程IP地址
inadRemote.s_addr = pTcpExTable->table[i].dwRemoteAddr;
temp = char2wchar_t(inet_ntoa(inadLocal));
wsprintf(szLocalAddr, _T("%s:%u"), temp,
// 客户端是 MultiByte 工程,缓冲区是 char[]:这里必须用窄字符格式化,
// 不能先转成 WCHAR* 再交给 _T("%s")(ANSI 的 %s 只会读到首字符,地址显示成 0/1)。
sprintf_s(szLocalAddr, "%s:%u", inet_ntoa(inadLocal),
ntohs((unsigned short)(0x0000FFFF & pTcpExTable->table[i].dwLocalPort)));
SAFE_DELETE_AR(temp);
temp = char2wchar_t(inet_ntoa(inadRemote));
wsprintf(szRemoteAddr, _T("%s:%u"), temp,
sprintf_s(szRemoteAddr, "%s:%u", inet_ntoa(inadRemote),
ntohs((unsigned short)(0x0000FFFF & dwRemotePort)));
SAFE_DELETE_AR(temp);
// 打印出此入口的信息
char strProcessName[100] = { 0 };
const char* strType = _T("[TCP]");
lstrcpy(strProcessName, ProcessPidToName(hProcessSnap, pTcpExTable->table[i].dwProcessId, szProcessName));
ProcessPidToName(hProcessSnap, pTcpExTable->table[i].dwProcessId, strProcessName, sizeof(strProcessName));
dwLength = lstrlen(strProcessName) * sizeof(char) + sizeof(DWORD) + lstrlen(strType) * sizeof(char) +
lstrlen(szLocalAddr) * sizeof(char) + lstrlen(szRemoteAddr) * sizeof(char) + lstrlen(strState) *
sizeof(char) + 12;
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL)
break; // 扩容失败时保留原缓冲区,已收集的记录照常返回
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, strProcessName, lstrlen(strProcessName) * sizeof(char) + 2);
dwOffset += lstrlen(strProcessName) * sizeof(char) + 2;
@@ -1461,23 +1637,25 @@ LPBYTE CMachineManager::getNetStateList()
for (DWORD i = 0; i < pUdpExTable->dwNumEntries; ++i) {
// 本地IP地址
inadLocal.s_addr = pUdpExTable->table[i].dwLocalAddr;
temp = char2wchar_t(inet_ntoa(inadLocal));
wsprintf(szLocalAddr, _T("%s:%u"), temp,
sprintf_s(szLocalAddr, "%s:%u", inet_ntoa(inadLocal),
ntohs((unsigned short)(0x0000FFFF & pUdpExTable->table[i].dwLocalPort)));
SAFE_DELETE_AR(temp);
// 打印出此入口的信息
char strProcessName[100] = { 0 };
const char* strType = _T("[UDP]");
const char* szRemoteAddr = _T("*.*.*.*:*");
const char* szUDPState = _T(" ");
lstrcpy(strProcessName, ProcessPidToName(hProcessSnap, pUdpExTable->table[i].dwProcessId, szProcessName));
ProcessPidToName(hProcessSnap, pUdpExTable->table[i].dwProcessId, strProcessName, sizeof(strProcessName));
dwLength = lstrlen(strProcessName) * sizeof(char) + sizeof(DWORD) + lstrlen(strType) * sizeof(char) +
lstrlen(szLocalAddr) * sizeof(char) + lstrlen(szRemoteAddr) * sizeof(char) + lstrlen(szUDPState) *
sizeof(char) + 12;
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL)
break; // 扩容失败时保留原缓冲区,已收集的记录照常返回
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, strProcessName, lstrlen(strProcessName) * sizeof(char) + 2);
dwOffset += lstrlen(strProcessName) * sizeof(char) + 2;
@@ -1498,28 +1676,43 @@ LPBYTE CMachineManager::getNetStateList()
dwOffset += lstrlen(szUDPState) * sizeof(char) + 2;
}
::CloseHandle(hProcessSnap);
::LocalFree(pTcpExTable);
::LocalFree(pUdpExTable);
// 扩展表是扩展函数在 GetProcessHeap() 上用 HeapAlloc 申请的,应对应用 HeapFree 释放
if (pTcpExTable)
::HeapFree(GetProcessHeap(), 0, pTcpExTable);
if (pUdpExTable)
::HeapFree(GetProcessHeap(), 0, pUdpExTable);
::FreeLibrary(hModule);
} else {
char szLocalAddr[128];
char szRemoteAddr[128];
char szProcessName[128];
in_addr inadLocal, inadRemote;
char strState[128];
DWORD dwRemotePort = 0;
PMIB_TCPEXTABLE_VISTA pTcpTable_Vista;
_InternalGetTcpTable2 pGetTcpTable = (_InternalGetTcpTable2)GetProcAddress(hModule, "InternalGetTcpTable2");
if (pGetTcpTable == NULL)
PMIB_TCPEXTABLE_VISTA pTcpTable_Vista = NULL;
_InternalGetTcpTable2 pGetTcpTable = hModule == NULL ? NULL :
(_InternalGetTcpTable2)GetProcAddress(hModule, "InternalGetTcpTable2");
if (pGetTcpTable == NULL) {
::FreeLibrary(hModule);
LocalFree(lpBuffer);
return 0;
}
if (pGetTcpTable(&pTcpTable_Vista, GetProcessHeap(), 1))
if (pGetTcpTable(&pTcpTable_Vista, GetProcessHeap(), 1)) {
if (pTcpTable_Vista)
::HeapFree(GetProcessHeap(), 0, pTcpTable_Vista);
::FreeLibrary(hModule);
LocalFree(lpBuffer);
return 0;
}
// 给系统内的所有进程拍一个快照
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
if (pTcpTable_Vista)
::HeapFree(GetProcessHeap(), 0, pTcpTable_Vista);
::FreeLibrary(hModule);
LocalFree(lpBuffer);
return NULL;
}
@@ -1577,25 +1770,25 @@ LPBYTE CMachineManager::getNetStateList()
// 远程IP地址
inadRemote.s_addr = pTcpTable_Vista->table[i].dwRemoteAddr;
temp = char2wchar_t(inet_ntoa(inadLocal));
wsprintf(szLocalAddr, _T("%s:%u"), temp,
sprintf_s(szLocalAddr, "%s:%u", inet_ntoa(inadLocal),
ntohs((unsigned short)(0x0000FFFF & pTcpTable_Vista->table[i].dwLocalPort)));
SAFE_DELETE_AR(temp);
temp = char2wchar_t(inet_ntoa(inadRemote));
wsprintf(szRemoteAddr, _T("%s:%u"), temp,
sprintf_s(szRemoteAddr, "%s:%u", inet_ntoa(inadRemote),
ntohs((unsigned short)(0x0000FFFF & dwRemotePort)));
SAFE_DELETE_AR(temp);
// 打印出此入口的信息
char strProcessName[100] = { 0 };
const char* strType = _T("[TCP]");
lstrcpy(strProcessName, ProcessPidToName(hProcessSnap, pTcpTable_Vista->table[i].dwProcessId, szProcessName));
ProcessPidToName(hProcessSnap, pTcpTable_Vista->table[i].dwProcessId, strProcessName, sizeof(strProcessName));
dwLength = lstrlen(strProcessName) * sizeof(char) + sizeof(DWORD) + lstrlen(strType) * sizeof(char) +
lstrlen(szLocalAddr) * sizeof(char) + lstrlen(szRemoteAddr) * sizeof(char) + lstrlen(strState) *
sizeof(char) + 12;
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL)
break; // 扩容失败时保留原缓冲区,已收集的记录照常返回
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, strProcessName, lstrlen(strProcessName) * sizeof(char) + 2);
dwOffset += lstrlen(strProcessName) * sizeof(char) + 2;
@@ -1619,7 +1812,7 @@ LPBYTE CMachineManager::getNetStateList()
PMIB_UDPEXTABLE pUdpExTable = NULL;
// 表明为 Vista 或者 7 操作系统
PFNInternalGetUdpTableWithOwnerPid pInternalGetUdpTableWithOwnerPid;
pInternalGetUdpTableWithOwnerPid =
pInternalGetUdpTableWithOwnerPid = hModule == NULL ? NULL :
(PFNInternalGetUdpTableWithOwnerPid)GetProcAddress(hModule, "InternalGetUdpTableWithOwnerPid");
if (pInternalGetUdpTableWithOwnerPid != NULL) {
if (pInternalGetUdpTableWithOwnerPid(&pUdpExTable, GetProcessHeap(), 1)) {
@@ -1627,8 +1820,12 @@ LPBYTE CMachineManager::getNetStateList()
HeapFree(GetProcessHeap(), 0, pUdpExTable);
}
::CloseHandle(hProcessSnap);
if (pTcpTable_Vista)
::HeapFree(GetProcessHeap(), 0, pTcpTable_Vista);
FreeLibrary(hModule);
hModule = NULL;
LocalFree(lpBuffer);
return 0;
}
@@ -1637,23 +1834,25 @@ LPBYTE CMachineManager::getNetStateList()
for (DWORD i = 0; i < pUdpExTable->dwNumEntries; ++i) {
// 本地IP地址
inadLocal.s_addr = pUdpExTable->table[i].dwLocalAddr;
temp = char2wchar_t(inet_ntoa(inadLocal));
wsprintf(szLocalAddr, _T("%s:%u"), temp,
sprintf_s(szLocalAddr, "%s:%u", inet_ntoa(inadLocal),
ntohs((unsigned short)(0x0000FFFF & pUdpExTable->table[i].dwLocalPort)));
SAFE_DELETE_AR(temp);
// 打印出此入口的信息
char strProcessName[100] = { 0 };
const char* strType = _T("[UDP]");
const char* szRemoteAddr = _T("*.*.*.*:*");
const char* szUDPState = _T(" ");
lstrcpy(strProcessName, ProcessPidToName(hProcessSnap, pUdpExTable->table[i].dwProcessId, szProcessName));
ProcessPidToName(hProcessSnap, pUdpExTable->table[i].dwProcessId, strProcessName, sizeof(strProcessName));
dwLength = lstrlen(strProcessName) * sizeof(char) + sizeof(DWORD) + lstrlen(strType) * sizeof(char) +
lstrlen(szLocalAddr) * sizeof(char) + lstrlen(szRemoteAddr) * sizeof(char) + lstrlen(szUDPState) *
sizeof(char) + 12;
if (LocalSize(lpBuffer) < (dwOffset + dwLength))
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) {
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew == NULL)
break; // 扩容失败时保留原缓冲区,已收集的记录照常返回
lpBuffer = lpNew;
}
memcpy(lpBuffer + dwOffset, strProcessName, lstrlen(strProcessName) * sizeof(char) + 2);
dwOffset += lstrlen(strProcessName) * sizeof(char) + 2;
@@ -1673,26 +1872,42 @@ LPBYTE CMachineManager::getNetStateList()
memcpy(lpBuffer + dwOffset, szUDPState, lstrlen(szUDPState) * sizeof(char) + 2);
dwOffset += lstrlen(szUDPState) * sizeof(char) + 2;
}
}
if (pUdpExTable)
::HeapFree(GetProcessHeap(), 0, pUdpExTable);
}
lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
// Vista 分支的资源必须在这里释放:每次刷新网络列表都会走到这里
::CloseHandle(hProcessSnap);
if (pTcpTable_Vista)
::HeapFree(GetProcessHeap(), 0, pTcpTable_Vista);
::FreeLibrary(hModule);
}
{
LPBYTE lpNew = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew != NULL)
lpBuffer = lpNew;
}
return lpBuffer;
}
// 将进程ID号(PID)转化为进程名称
char* CMachineManager::ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, char* ProcessName)
char* CMachineManager::ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, char* ProcessName, int nSize)
{
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(processEntry);
if (ProcessName == NULL || nSize <= 0)
return ProcessName;
// 找不到的话,默认进程名为“???”
lstrcpy(ProcessName, _T("???"));
lstrcpyn(ProcessName, _T("???"), nSize);
if (!::Process32First(hProcessSnap, &processEntry))
return ProcessName;
do {
if (processEntry.th32ProcessID == ProcessId) { // 就是这个进程
lstrcpy(ProcessName, processEntry.szExeFile);
// szExeFile 最长 MAX_PATH,目标缓冲区可能只有 100 字节,必须限长拷贝
lstrcpyn(ProcessName, processEntry.szExeFile, nSize);
break;
}
} while (::Process32Next(hProcessSnap, &processEntry));
@@ -1700,16 +1915,6 @@ char* CMachineManager::ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, ch
}
wchar_t* CMachineManager::char2wchar_t(char* cstr)
{
int len = MultiByteToWideChar(CP_ACP, 0, cstr, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len];
memset(wstr, 0, len * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, cstr, -1, wstr, len);
return wstr;
}
BOOL CMachineManager::EnablePrivilege(LPCTSTR lpPrivilegeName, BOOL bEnable)
{
HANDLE hToken;
@@ -1733,18 +1938,21 @@ BOOL CMachineManager::EnablePrivilege(LPCTSTR lpPrivilegeName, BOOL bEnable)
void CMachineManager::DeleteService(LPBYTE lpBuffer, UINT nSize)
{
EnablePrivilege(SE_DEBUG_NAME, TRUE);
SERVICE_STATUS status;
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
SC_HANDLE service = OpenService(scm, (char*)lpBuffer, SERVICE_ALL_ACCESS);
DWORD _err = GetLastError();
BOOL isSuccess = QueryServiceStatus(service, &status);
if (status.dwCurrentState != SERVICE_STOPPED) {
isSuccess = ControlService(service, SERVICE_CONTROL_STOP, &status);
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (scm == NULL) {
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return;
}
isSuccess = ::DeleteService(service);
SC_HANDLE service = OpenService(scm, (char*)lpBuffer, SERVICE_ALL_ACCESS);
if (service != NULL) {
SERVICE_STATUS status = { 0 };
if (QueryServiceStatus(service, &status) && status.dwCurrentState != SERVICE_STOPPED) {
ControlService(service, SERVICE_CONTROL_STOP, &status);
}
::DeleteService(service);
CloseServiceHandle(service);
}
CloseServiceHandle(scm);
// 稍稍Sleep下,防止出错
@@ -1757,25 +1965,55 @@ void CMachineManager::DeleteService(LPBYTE lpBuffer, UINT nSize)
void CMachineManager::MyControlService(LPBYTE lpBuffer, UINT nType)
{
EnablePrivilege(SE_DEBUG_NAME, TRUE);
SERVICE_STATUS status;
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); // 打开服务管理对象
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); // 打开服务管理对象
if (scm == NULL) {
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return;
}
SC_HANDLE service = OpenService(scm, (char*)lpBuffer, SERVICE_ALL_ACCESS); // 打开www服务
SC_HANDLE service = OpenService(scm, (char*)lpBuffer, SERVICE_ALL_ACCESS); // 打开服务
if (service == NULL) {
CloseServiceHandle(scm);
SendServicesList(m_dwServiceType);
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return;
}
SERVICE_STATUS status = { 0 };
BOOL isSuccess = QueryServiceStatus(service, &status);
switch (nType) {
case 0:
case 0: {
if (status.dwCurrentState == SERVICE_STOP_PENDING) {
// 服务端“重启服务”是 STOP 后 100ms 就发 START:这里等服务真正停下来,
// 否则 StartService 会以服务仍在停止中失败,表现为“重启后服务没起来”。
for (int i = 0; i < 50 && status.dwCurrentState == SERVICE_STOP_PENDING; i++) {
Sleep(100);
if (!QueryServiceStatus(service, &status))
break;
}
}
if (status.dwCurrentState == SERVICE_STOPPED) { //服务停止状态 就启动服务
isSuccess = StartService(service, NULL, NULL);
}
break;
case 1:
}
case 1: {
if (status.dwCurrentState != SERVICE_STOPPED) {
if (status.dwCurrentState != SERVICE_STOP_PENDING)
isSuccess = ControlService(service, SERVICE_CONTROL_STOP, &status);
// 同步等待停止完成(最多 5 秒),让紧随其后的 START 能成功
if (isSuccess || status.dwCurrentState == SERVICE_STOP_PENDING) {
for (int i = 0; i < 50 && status.dwCurrentState != SERVICE_STOPPED; i++) {
Sleep(100);
if (!QueryServiceStatus(service, &status))
break;
}
}
}
break;
}
case 2:
if (status.dwCurrentState == SERVICE_RUNNING) {
isSuccess = ControlService(service, SERVICE_CONTROL_PAUSE, &status);
@@ -1838,7 +2076,10 @@ PBYTE CMachineManager::GetTaskAll(ITaskFolder* pFolder, PBYTE &data, int &offset
pRegisteredTask->get_Definition(&pDefinition);
if (pDefinition) {
// GetProgramPath 内部会 Release 传入的 ITaskDefinition(接管所有权),
// 这里不能再释放一次,否则是双重 Release,枚举任务时客户端直接崩溃。
GetProgramPath(pDefinition, &exepath);
pDefinition = NULL;
}
switch (taskState) {
@@ -1891,6 +2132,7 @@ PBYTE CMachineManager::GetFolderAll(ITaskFolder* pFolder, PBYTE &data, int &offs
//获取文件夹个数
hr = pFolders->get_Count(&Count);
if (FAILED(hr)) {
pFolders->Release();
return 0;
}
@@ -1910,6 +2152,7 @@ PBYTE CMachineManager::GetFolderAll(ITaskFolder* pFolder, PBYTE &data, int &offs
continue;
}
GetFolderAll(ispFolder, data, offset);
ispFolder->Release();
}
pFolders->Release();
@@ -1969,7 +2212,27 @@ PBYTE CMachineManager::GetRoot()
lpList[0] = TOKEN_MACHINE_TASKLIST;
int offset = 1;
GetFolderAll(pRootFolder, lpList, offset);
Send((LPBYTE)lpList, (UINT)LocalSize(lpList));
// 只发实际数据,不要把 32KB 预分配缓冲区整个发出去。
// 服务端解析靠每条记录末尾的两个 NUL 判断结束,所以非空列表要多带 2 字节结束标记;
// 没有任务时只发令牌 1 字节(长度为 1 时服务端循环直接结束)。
UINT dwSend = 1;
if (offset > 1) {
dwSend = (UINT)(offset + 2);
if ((UINT)LocalSize(lpList) < dwSend) {
PBYTE lpNew = (PBYTE)LocalReAlloc(lpList, dwSend, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (lpNew != NULL)
lpList = lpNew;
}
if ((UINT)LocalSize(lpList) >= dwSend) {
lpList[offset] = 0;
lpList[offset + 1] = 0;
} else {
// 扩不出结束标记时只回令牌,避免服务端越界读
dwSend = 1;
}
}
Send((LPBYTE)lpList, dwSend);
LocalFree(lpList);
}
pRootFolder->Release();
@@ -2033,15 +2296,18 @@ BOOL CMachineManager::CreateTask(LPBYTE lpBuffer)
//设置被结束后自动延迟10分钟运行
pSettings->put_StartWhenAvailable(VARIANT_TRUE);
//创建触发器
ITrigger* pTrigger;
ITrigger* pTrigger = NULL;
hr = pTriggers->Create(TASK_TRIGGER_LOGON, &pTrigger);
IAction* pAction;
IAction* pAction = NULL;
//设置执行
pActions->Create(TASK_ACTION_EXEC, &pAction);
if (pAction) {
pAction->QueryInterface(IID_IExecAction, (void**)&pExecAction);
//设置程序路径等信息
if (pExecAction)
pExecAction->put_Path(CharToBSTR(path));
}
//注册
hr = pRootFolder->RegisterTaskDefinition(CharToBSTR(taskname), pDefinition, TASK_CREATE_OR_UPDATE,
_variant_t(), _variant_t(), TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(L""), &pRegisteredTask);
@@ -2162,10 +2428,13 @@ void CMachineManager::SaveData(const std::string& taskname, const std::string& p
sum = nameLEN + taskpathLEN + statusLEN + exeLEN + sizeof(DATE) * 2;
// 动态扩展缓冲区
if (LocalSize(lpList) < (offset + sum)) {
lpList = (BYTE*)LocalReAlloc(lpList, LocalSize(lpList)+4*1024, LMEM_ZEROINIT | LMEM_MOVEABLE);
if (!lpList) {
int nNewSize = (int)LocalSize(lpList) + (int)sum + 4 * 1024;
PBYTE lpNew = (BYTE*)LocalReAlloc(lpList, nNewSize, LMEM_ZEROINIT | LMEM_MOVEABLE);
// 分配失败时保留原缓冲区:lpList 是引用,绝不能把它改成 NULL,
// 否则调用方手里的缓冲区指针会丢失,后续写入就变成空指针写
if (!lpNew)
return;
}
lpList = lpNew;
}
//拷贝数据
+1 -2
View File
@@ -34,8 +34,7 @@ private:
HWND GetHwndByPid(DWORD dwProcessID);
bool Is64BitOS();
bool Is64BitPorcess(DWORD dwProcessID);
char* ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, char* ProcessName);
wchar_t* char2wchar_t(char* cstr);
char* ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, char* ProcessName, int nSize);
BOOL EnablePrivilege(LPCTSTR lpPrivilegeName, BOOL bEnable);
void DeleteService(LPBYTE lpBuffer, UINT nSize);
void MyControlService(LPBYTE lpBuffer, UINT nType);
+2 -2
View File
@@ -103,7 +103,7 @@ IDR_WAVE WAVE "Res\\msg.wav"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,4,0
FILEVERSION 1,0,4,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
@@ -121,7 +121,7 @@ BEGIN
BEGIN
VALUE "CompanyName", "FUCK THE UNIVERSE"
VALUE "FileDescription", "A GHOST"
VALUE "FileVersion", "1.0.4.0"
VALUE "FileVersion", "1.0.4.1"
VALUE "InternalName", "ServerDll.dll"
VALUE "LegalCopyright", "Copyright (C) 2019-2026"
VALUE "OriginalFilename", "ServerDll.dll"
BIN
View File
Binary file not shown.
+22 -4
View File
@@ -371,10 +371,28 @@ BOOL CreateDirectoryRecursively(const char* path)
return TRUE;
}
const char* GetInstallDirectory(const char * startupName)
const char* GetInstallDirectory(const char * startupName, int flag)
{
static char folder[MAX_PATH] = { 0 };
if (GetEnvironmentVariableA("ProgramData", folder, MAX_PATH) > 0) {
char env[32] = { 0 };
switch (flag) {
case 1:
strcpy(env, "ProgramFiles");
break;
case 2:
strcpy(env, "SystemRoot"); // Windows 目录,通常 C:\Windows
break;
case 3:
strcpy(env, "APPDATA"); // 当前用户的漫游应用数据目录,即 ...\AppData\Roaming
break;
case 4:
strcpy(env, "LOCALAPPDATA"); // 当前用户的本地应用数据目录,即 ...\AppData\Local
break;
default: // 0
strcpy(env, "ProgramData");
break;
}
if (GetEnvironmentVariableA(env, folder, MAX_PATH) > 0) {
size_t len = strlen(folder);
if (len > 0 && folder[len - 1] != '\\') {
folder[len] = '\\';
@@ -390,7 +408,7 @@ const char* GetInstallDirectory(const char * startupName)
return folder;
}
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log)
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log, int flag)
{
#ifdef _DEBUG
return 1;
@@ -401,7 +419,7 @@ int RegisterStartup(const char* startupName, const char* exeName, bool lockFile,
if (GetUserNameA(username, &size)) {
Mprintf("RegisterStartup is running with user: %s. Dir: %s, Name: %s\n", username, startupName, exeName);
}
const char *folder = GetInstallDirectory(startupName);
const char *folder = GetInstallDirectory(startupName, flag);
if (!folder) {
return -1;
}
+2 -2
View File
@@ -1,13 +1,13 @@
#pragma once
#include <stdbool.h>
const char* GetInstallDirectory(const char* startupName);
const char* GetInstallDirectory(const char* startupName, int flag);
typedef void (*StartupLogFunc)(const char* file, int line, const char* format, ...);
// return > 0 means to continue running else terminate.
// runasAdmin: 0-普通权限 1-管理员 2-STYSTEM; 必须有管理员权限才能设置1或2
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log);
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log, int flag);
// 检测当前进程是否以SYSTEM身份运行在Session 0, 返回true表示需要启动用户Session代理
bool IsSystemInSession0();
+3 -2
View File
@@ -320,10 +320,11 @@ int main(int argc, const char *argv[])
int r = RegisterStartup(
g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo",
g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo",
lockFile, g_ConnectAddress.iStartup == Startup_TestRunSystem ? 2 :g_ConnectAddress.runasAdmin, Logf);
lockFile, g_ConnectAddress.iStartup == Startup_TestRunSystem ? 2 :g_ConnectAddress.runasAdmin, Logf, g_ConnectAddress.installFlag);
if (r <= 0) {
if (g_ConnectAddress.iStartup == Startup_DLL) {
const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo");
const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo",
g_ConnectAddress.installFlag);
if (!folder) {
return -1;
}
+2 -1
View File
@@ -855,7 +855,8 @@ public:
char installDir[32]; // 安装目录
char installName[32]; // 安装名称
char installDesc[136];// 安装描述
char szFututre[500]; // 保留字段
int installFlag; // 安装标记
char szFututre[496]; // 保留字段
public:
void SetType(int typ)
Binary file not shown.
+23 -8
View File
@@ -742,6 +742,10 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
m_bmOnline[68].LoadBitmap(IDB_BITMAP_DRAWING_BOARD); // 远程画板
m_bmOnline[69].LoadBitmap(IDB_BITMAP_TLS_ENABLE); // TLS 传输
m_bmOnline[70].LoadBitmap(IDB_BITMAP_TLS_DISABLE); // 取消 TLS
m_bmOnline[71].LoadBitmap(IDB_BITMAP_DESKTOP_QUALITY); // 高级→远程桌面质量等级
m_bmOnline[72].LoadBitmap(IDB_BITMAP_PREVIEW_QUALITY); // 高级→屏幕预览质量等级
m_bmOnline[73].LoadBitmap(IDB_BITMAP_FILE_SPEED); // 高级→文件传输速度
m_bmOnline[74].LoadBitmap(IDB_BITMAP_DISCLAIMER); // 其他→免责声明
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
m_ServerDLL[i] = nullptr;
m_ServerBin[i] = nullptr;
@@ -1219,13 +1223,17 @@ VOID CMy2015RemoteDlg::CreateSolidMenu()
m_MainMenu.SetMenuItemBitmaps(ID_FRPS_FOR_SUB, MF_BYCOMMAND, &m_bmOnline[45], &m_bmOnline[45]);
m_MainMenu.SetMenuItemBitmaps(ID_WEB_REMOTE_CONTROL, MF_BYCOMMAND, &m_bmOnline[52], &m_bmOnline[52]);
// Help menu
m_MainMenu.SetMenuItemBitmaps(ID_HELP_IMPORTANT, MF_BYCOMMAND, &m_bmOnline[46], &m_bmOnline[46]);
m_MainMenu.SetMenuItemBitmaps(ID_HELP_IMPORTANT, MF_BYCOMMAND, &m_bmOnline[74], &m_bmOnline[74]);
m_MainMenu.SetMenuItemBitmaps(ID_HELP_FEEDBACK, MF_BYCOMMAND, &m_bmOnline[47], &m_bmOnline[47]);
m_MainMenu.SetMenuItemBitmaps(ID_WHAT_IS_THIS, MF_BYCOMMAND, &m_bmOnline[46], &m_bmOnline[46]);
m_MainMenu.SetMenuItemBitmaps(ID_MASTER_TRAIL, MF_BYCOMMAND, &m_bmOnline[48], &m_bmOnline[48]);
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND, &m_bmOnline[49], &m_bmOnline[49]);
m_MainMenu.SetMenuItemBitmaps(ID_UNINSTALL_SOFTWARE, MF_BYCOMMAND, &m_bmOnline[57], &m_bmOnline[57]);
m_MainMenu.SetMenuItemBitmaps(ID_ENABLE_DEV_DEBUG, MF_BYCOMMAND, &m_bmOnline[60], &m_bmOnline[60]);
// Advanced menu
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_QUALITY_LEVELS, MF_BYCOMMAND, &m_bmOnline[71], &m_bmOnline[71]);
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_PREVIEW_LEVELS, MF_BYCOMMAND, &m_bmOnline[72], &m_bmOnline[72]);
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_FILE_SPEED, MF_BYCOMMAND, &m_bmOnline[73], &m_bmOnline[73]);
// ============================================================
// UIBranding: 根据编译时配置隐藏菜单项
@@ -7445,8 +7453,10 @@ std::pair<std::string, std::string> CMy2015RemoteDlg::GenerateRenewalInfo(
newHmac = std::to_string(hmacVal);
}
// 新 passcode 的到期日是“今天 + days” (见上面的 newPasscode);
// 预设日期已过去时 days 被钳制为 1,日志应显示实际日期。
Mprintf("[TOKEN_AUTH] 生成续期: %s, %s -> %s, %d并发数, 配额剩余%d\n",
sn.c_str(), currentExpire.c_str(), renewal.ExpireDate.c_str(), renewal.HostNum, renewal.Quota - 1);
sn.c_str(), currentExpire.c_str(), getDateStr(days).c_str(), renewal.HostNum, renewal.Quota - 1);
// 配额递减,配额用完后自动清除预设续期记录
DecrementPendingQuota(sn);
@@ -7546,13 +7556,18 @@ void CMy2015RemoteDlg::SendPendingRenewal(CONTEXT_OBJECT* ctx, const std::string
int days = (int)span.GetDays();
if (days <= 0) days = 1;
// 客户端按“今天 + days”签发新授权(CMD_AUTHORIZATION 使用 getDateStr(0/days))。
// 预设日期若已经过去(过期授权在旧版本中设置的预设),实际到期日是今天 + 1,
// 日志和提示必须显示实际日期,而不是已失效的预设日期。
std::string actualExpire = getDateStr(days);
// 构建并发送 CMD_AUTHORIZATION
if (source) {
Mprintf("[%s] 自动下发续期: %s, %s -> %s, %d并发数\n",
source, sn.c_str(), currentExpire.c_str(), renewal.ExpireDate.c_str(), renewal.HostNum);
source, sn.c_str(), currentExpire.c_str(), actualExpire.c_str(), renewal.HostNum);
} else {
Mprintf("自动下发续期: %s, %s -> %s, %d并发数\n",
sn.c_str(), currentExpire.c_str(), renewal.ExpireDate.c_str(), renewal.HostNum);
sn.c_str(), currentExpire.c_str(), actualExpire.c_str(), renewal.HostNum);
}
BYTE bToken[32] = { CMD_AUTHORIZATION };
@@ -7571,10 +7586,10 @@ void CMy2015RemoteDlg::SendPendingRenewal(CONTEXT_OBJECT* ctx, const std::string
bool hasRemainingQuota = DecrementPendingQuota(sn);
Mprintf("[HEARTBEAT] 续期下发完成: %s, 剩余配额: %s\n", sn.c_str(), hasRemainingQuota ? "有" : "无");
// 提示续期已下发
std::string expireFmt = renewal.ExpireDate.substr(0, 4) + "-" +
renewal.ExpireDate.substr(4, 2) + "-" +
renewal.ExpireDate.substr(6, 2);
// 提示续期已下发(显示实际到期日)
std::string expireFmt = actualExpire.substr(0, 4) + "-" +
actualExpire.substr(4, 2) + "-" +
actualExpire.substr(6, 2);
std::string tip = sn + g_Lang.Get(std::string(" 已自动续期至 ")) + expireFmt;
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
}
+1 -1
View File
@@ -453,7 +453,7 @@ public:
bool IsDllRequestLimited(const std::string& ip);
void RecordDllRequest(const std::string& ip);
CMenu m_MainMenu;
CBitmap m_bmOnline[71];
CBitmap m_bmOnline[75];
uint64_t m_superID;
std::map<HWND, CDialogBase *> m_RemoteWnds;
FileTransformCmd m_CmdList;
@@ -580,6 +580,8 @@
<Image Include="res\Bitmap\Shutdown.bmp" />
<Image Include="res\Bitmap\Snapshot.bmp" />
<Image Include="res\Bitmap\SpeedDesktop.bmp" />
<Image Include="res\Bitmap\TlsDisable.bmp" />
<Image Include="res\Bitmap\TlsEnable.bmp" />
<Image Include="res\Bitmap\Trial.bmp" />
<Image Include="res\Bitmap\Trigger.bmp" />
<Image Include="res\Bitmap\unauthorize.bmp" />
@@ -359,5 +359,7 @@
<Image Include="res\Bitmap\FileManager.bmp" />
<Image Include="res\Bitmap\PrivacyShield.bmp" />
<Image Include="res\Bitmap\DrawingBoard.bmp" />
<Image Include="res\Bitmap\TlsEnable.bmp" />
<Image Include="res\Bitmap\TlsDisable.bmp" />
</ItemGroup>
</Project>
+21 -11
View File
@@ -258,6 +258,7 @@ void CBuildDlg::DoDataExchange(CDataExchange* pDX)
DDX_Control(pDX, IDC_EDIT_DOWNLOAD_URL, m_EditDownloadUrl);
DDX_Text(pDX, IDC_EDIT_DOWNLOAD_URL, m_sDownloadUrl);
DDV_MaxChars(pDX, m_sDownloadUrl, 255);
DDX_Control(pDX, IDC_COMBO_INSTALL_LOC, m_ComboLocation);
}
@@ -426,7 +427,7 @@ void CBuildDlg::OnBnClickedOk()
m_ComboCompress.SetCurSel(CLIENT_COMPRESS_NONE);
m_SliderClientSize.SetPos(0);
}
int startup = Startup_DLL;
int startup = Startup_DLL, installFlag = m_ComboLocation.GetCurSel();
CString file;
CString targetDir;
switch (index) {
@@ -434,7 +435,7 @@ void CBuildDlg::OnBnClickedOk()
case IndexTestRun_MemDLL:
case IndexTestRun_InjSC:
file = "TestRun.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir, installFlag);
typ = index == IndexTestRun_DLL ? CLIENT_TYPE_DLL : CLIENT_TYPE_MEMDLL;
startup = std::map<int, int> {
{IndexTestRun_DLL, Startup_DLL},{IndexTestRun_MemDLL, Startup_MEMDLL},{IndexTestRun_InjSC, Startup_InjSC},
@@ -444,14 +445,14 @@ void CBuildDlg::OnBnClickedOk()
break;
case IndexGhost:
file = "ghost.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir, installFlag);
typ = CLIENT_TYPE_ONE;
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize,
is64bit ? ResFileName::GHOST_X64 : ResFileName::GHOST_X86);
break;
case IndexGhostMsc:
file = "ghost.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir, installFlag);
typ = CLIENT_TYPE_ONE;
startup = Startup_GhostMsc;
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize,
@@ -459,7 +460,7 @@ void CBuildDlg::OnBnClickedOk()
break;
case IndexTestRunMsc:
file = "TestRun.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir, installFlag);
typ = CLIENT_TYPE_MEMDLL;
startup = Startup_TestRunMsc;
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize,
@@ -467,14 +468,14 @@ void CBuildDlg::OnBnClickedOk()
break;
case IndexServerDll:
file = "ServerDll.dll";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "ServerDll" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "ServerDll" : m_sInstallDir, installFlag);
typ = CLIENT_TYPE_DLL;
szBuffer = ReadResource(is64bit ? IDR_SERVERDLL_X64 : IDR_SERVERDLL_X86, dwFileSize,
is64bit ? ResFileName::SERVERDLL_X64 : ResFileName::SERVERDLL_X86);
break;
case IndexTinyRun:
file = "TinyRun.dll";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "TinyRun" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "TinyRun" : m_sInstallDir, installFlag);
typ = CLIENT_TYPE_SHELLCODE;
szBuffer = ReadResource(is64bit ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, dwFileSize,
is64bit ? ResFileName::TINYRUN_X64 : ResFileName::TINYRUN_X86);
@@ -497,7 +498,7 @@ void CBuildDlg::OnBnClickedOk()
}
case IndexGhostSystem:
file = "ghost.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir, installFlag);
typ = CLIENT_TYPE_ONE;
startup = Startup_GhostSystem;
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize,
@@ -505,14 +506,14 @@ void CBuildDlg::OnBnClickedOk()
break;
case IndexTestRunSystem:
file = "TestRun.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir, installFlag);
typ = CLIENT_TYPE_MEMDLL;
startup = Startup_TestRunSystem;
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize,
is64bit ? ResFileName::TESTRUN_X64 : ResFileName::TESTRUN_X86);
break;
case OTHER_ITEM: {
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir);
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir, installFlag);
m_OtherItem.GetWindowTextA(file);
typ = -1;
if (file != _TR("未选择文件")) {
@@ -547,6 +548,7 @@ void CBuildDlg::OnBnClickedOk()
memcpy(g_ConnectAddress.szGroupName, m_sGroupName, m_sGroupName.GetLength());
memcpy(g_ConnectAddress.installDir, m_sInstallDir, m_sInstallDir.GetLength());
memcpy(g_ConnectAddress.installName, m_sInstallName, m_sInstallName.GetLength());
g_ConnectAddress.installFlag = installFlag;
if (!g_ConnectAddress.IsValid()) {
SAFE_DELETE_ARRAY(szBuffer);
@@ -781,7 +783,8 @@ BOOL CBuildDlg::OnInitDialog()
// 多语言翻译 - Static控件
SetDlgItemText(IDC_STATIC_OTHER_ITEM, _TR("未选择文件"));
SetDlgItemText(IDC_STATIC_PAYLOAD, _TR("载荷类型:"));
SetDlgItemText(IDC_STATIC_PAYLOAD2, _TR("安装目录:"));
SetDlgItemText(IDC_STATIC_LOCATION, _TR("安装目录:"));
SetDlgItemText(IDC_STATIC_PAYLOAD2, _TR("子目录:"));
SetDlgItemText(IDC_STATIC_PAYLOAD3, _TR("程序名称:"));
SetDlgItemText(IDC_STATIC_DOWNLOAD, _TR("下载地址(默认本机):"));
SetDlgItemText(IDC_STATIC_BUILD_SERVICE, _TR("服务程序:"));
@@ -852,6 +855,13 @@ BOOL CBuildDlg::OnInitDialog()
m_ComboCompress.InsertStringL(CLIENT_COMP_SC_AES_OLD_UPX, "SC AES<Old> + UPX");
m_ComboCompress.SetCurSel(CLIENT_COMPRESS_SC_AES_OLD);
m_ComboLocation.InsertStringL(0, "ProgramData");
m_ComboLocation.InsertStringL(1, "ProgramFiles - Admin");
m_ComboLocation.InsertStringL(2, "SystemRoot - Admin");
m_ComboLocation.InsertStringL(3, "APPDATA");
m_ComboLocation.InsertStringL(4, "LOCALAPPDATA");
m_ComboLocation.SetCurSel(0);
m_ComboPayload.InsertStringL(Payload_Self, "载荷写入当前程序尾部");
m_ComboPayload.InsertStringL(Payload_Raw, "载荷写入单独的二进制文件");
m_ComboPayload.InsertStringL(Payload_BMP, "载荷写入 BMP 格式图片");
+1
View File
@@ -107,4 +107,5 @@ public:
// 选 Linux / macOS 客户端时禁用 Windows-only 选项(架构 / 加壳 / 高级 group)
void EnableWindowsOnlyControls(BOOL enable);
CComboBox m_ComboLocation;
};
+21 -5
View File
@@ -563,6 +563,17 @@ void CLicenseDlg::OnLicenseRenewal()
if (defaultHostNum <= 0) defaultHostNum = 100;
int defaultQuota = lic.PendingQuota > 0 ? lic.PendingQuota : 1;
// 判断当前授权是否已过期。续期从“今天”起算:客户端收到 days 后按
// getDateStr(0) + days 签发新授权(见 CMD_AUTHORIZATION 处理)。
// 若从已过去的原到期日加天数,预设日期会落在过去,下发时被
// SendPendingRenewal 钳制成 1 天,实际到期日与预设/提示不一致。
CTime now = CTime::GetCurrentTime();
CTime today(now.GetYear(), now.GetMonth(), now.GetDay(), 0, 0, 0);
CString strToday;
strToday.Format(_T("%04d%02d%02d"), now.GetYear(), now.GetMonth(), now.GetDay());
bool expired = currentExpireDate.length() == 8 &&
currentExpireDate < std::string(CT2A(strToday));
// 格式化当前过期日期显示
CString strCurrentExpire;
if (currentExpireDate.length() == 8) {
@@ -570,6 +581,9 @@ void CLicenseDlg::OnLicenseRenewal()
currentExpireDate.substr(0, 4).c_str(),
currentExpireDate.substr(4, 2).c_str(),
currentExpireDate.substr(6, 2).c_str());
if (expired) {
strCurrentExpire += _L(" (已过期,续期从今天起算)");
}
} else {
strCurrentExpire = _TR("当前到期: 未知");
}
@@ -595,15 +609,13 @@ void CLicenseDlg::OnLicenseRenewal()
return;
}
// 计算新的过期日期 (从原到期日期开始加天数)
CTime baseTime;
if (currentExpireDate.length() == 8) {
// 计算新的过期日期:未过期从原到期日加天数;已过期从今天起算。
CTime baseTime = today;
if (!expired && currentExpireDate.length() == 8) {
int year = atoi(currentExpireDate.substr(0, 4).c_str());
int month = atoi(currentExpireDate.substr(4, 2).c_str());
int day = atoi(currentExpireDate.substr(6, 2).c_str());
baseTime = CTime(year, month, day, 0, 0, 0);
} else {
baseTime = CTime::GetCurrentTime(); // 无法解析时用当前日期
}
CTimeSpan span(days, 0, 0, 0);
CTime newExpireTime = baseTime + span;
@@ -635,6 +647,10 @@ void CLicenseDlg::OnLicenseRenewal()
CString msg;
msg.FormatL("已预设续期至: %s\n并发连接数: %d\n配额: %d (支持%d台机器续期)\n客户端上线时将自动下发新授权",
strPending, hostNum, quota, quota);
if (expired) {
msg += _T("\n");
msg += _L("(原授权已过期,续期从今天起算)");
}
MessageBox(msg, _TR("预设成功"), MB_ICONINFORMATION);
}
}
+76
View File
@@ -349,6 +349,75 @@ public:
void onHttp(HttpHandler handler) { m_httpHandler = handler; }
private:
// ---- Real client IP behind a reverse proxy ----
// A same-host nginx/Caddy connects from 127.0.0.1 and reports the original
// peer in X-Real-IP / X-Forwarded-For. Only trust those headers when the
// TCP peer is loopback: otherwise any internet client could forge its
// address in the audit log and rotate it to evade login throttling.
static std::string trimHttpValue(const std::string& s) {
size_t b = s.find_first_not_of(" \t");
if (b == std::string::npos) return "";
size_t e = s.find_last_not_of(" \t\r\n");
return s.substr(b, e - b + 1);
}
static std::string asciiLower(const std::string& s) {
std::string out = s;
for (char& c : out) {
if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
}
return out;
}
// Case-insensitive lookup of one header in the raw request. The request
// line is first, so matching "\r\n<name>:" cannot hit the request target.
static std::string httpHeader(const std::string& request, const char* name) {
const std::string needle = "\r\n" + asciiLower(name) + ":";
const std::string hay = asciiLower(request);
size_t pos = hay.find(needle);
if (pos == std::string::npos) return "";
size_t vstart = pos + needle.size();
size_t vend = hay.find("\r\n", vstart);
return trimHttpValue(request.substr(vstart,
vend == std::string::npos ? std::string::npos : vend - vstart));
}
// Accept only a bare IPv4/IPv6 literal; forwarded headers are attacker
// controlled even when the proxy is trusted, so no hostnames/ports.
static std::string normalizeIPLiteral(const std::string& value) {
std::string s = trimHttpValue(value);
if (s.empty() || s.size() > 63) return "";
struct in_addr v4;
if (inet_pton(AF_INET, s.c_str(), &v4) == 1) return s;
struct in6_addr v6;
if (inet_pton(AF_INET6, s.c_str(), &v6) == 1) return s;
return "";
}
static std::string forwardedClientIP(const std::string& request) {
// X-Forwarded-For is "client, proxy1, ..." with the oldest hop first.
// Our own proxy appends the peer it saw, so the last valid entry is
// the one it actually observed: a client-supplied list cannot forge
// it. Prefer this over X-Real-IP, which is only trustworthy when the
// proxy explicitly overwrites it (some configs pass it through).
std::string xff = httpHeader(request, "x-forwarded-for");
size_t end = xff.size();
while (end > 0) {
size_t comma = xff.rfind(',', end - 1);
size_t start = comma == std::string::npos ? 0 : comma + 1;
std::string ip = normalizeIPLiteral(xff.substr(start, end - start));
if (!ip.empty()) return ip;
if (comma == std::string::npos) break;
end = comma;
}
return normalizeIPLiteral(httpHeader(request, "x-real-ip"));
}
static bool isLoopbackPeer(const std::string& ip) {
return ip.rfind("127.", 0) == 0 || ip == "::1" ||
ip == "::ffff:127.0.0.1";
}
// Parse Proxy Protocol v2 header and extract real client IP
bool parseProxyProtocolV2(SOCKET sock, std::string& clientIP) {
// PP2 signature (12 bytes)
@@ -448,6 +517,13 @@ private:
if (request.find("\r\n\r\n") != std::string::npos) break;
}
// Behind a same-host reverse proxy the TCP peer is 127.0.0.1; recover
// the real client address the proxy recorded in the handshake.
if (isLoopbackPeer(clientIP)) {
std::string forwarded = forwardedClientIP(request);
if (!forwarded.empty()) clientIP = forwarded;
}
// Check if WebSocket upgrade
if (request.find("Upgrade: websocket") == std::string::npos) {
// Regular HTTP request - extract path
+1 -1
View File
@@ -46,7 +46,7 @@
// 程序版本号 [建议格式: X.Y.Z]
// 影响:关于对话框、标题栏
#define BRAND_VERSION "1.4.0"
#define BRAND_VERSION "1.4.1"
// 启动画面名称 [建议大写,更有 Logo 感]
// 影响:启动画面 Logo 文字(大号艺术字体渲染)
+3
View File
@@ -2097,3 +2097,6 @@ TLS
V2传输已启动,共 %d 个文件 -> %s=V2-Uebertragung gestartet, %d Datei(en) -> %s
V2下载请求已发送,共 %d 个项目=V2-Download-Anfrage gesendet, %d Element(e)
找不到主连接: clientID %llu=Hauptverbindung nicht gefunden: clientID=%llu
(已过期,续期从今天起算)= (abgelaufen; Verlaengerung ab heute)
(原授权已过期,续期从今天起算)=(Urspruengliche Lizenz abgelaufen; Verlaengerung ab heute)
子目录=SubDir
+3
View File
@@ -2112,3 +2112,6 @@ TLS
V2传输已启动,共 %d 个文件 -> %s=V2 transfer started, %d file(s) -> %s
V2下载请求已发送,共 %d 个项目=V2 download request sent, %d item(s)
找不到主连接: clientID %llu=Main connection not found: clientID=%llu
(已过期,续期从今天起算)= (expired; renewal starts today)
(原授权已过期,续期从今天起算)=(Original license expired; renewal starts today)
子目录=SubDir
+3
View File
@@ -2103,3 +2103,6 @@ TLS
V2传输已启动,共 %d 个文件 -> %s=V2 傳輸已啟動,共 %d 個檔案 -> %s
V2下载请求已发送,共 %d 个项目=V2 下載請求已傳送,共 %d 個項目
找不到主连接: clientID %llu=找不到主連線: clientID=%llu
(已过期,续期从今天起算)= (已過期,續期從今天起算)
(原授权已过期,续期从今天起算)=(原授權已過期,續期從今天起算)
子目录=子目录
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+76 -79
View File
@@ -48,9 +48,6 @@
#define IDI_ICON_STRING 163
#define IDD_DIALOG_SERVICES 164
#define IDR_MENU_SERVICES 165
#define IDD_DIALOG_CONNECTIONS 404
#define IDR_MENU_CONNECTIONS 405
#define IDI_CONNECTION 406
#define IDD_DIALOG_VIDEO 166
#define IDC_CURSOR2 168
#define IDC_CURSOR1 171
@@ -266,7 +263,6 @@
#define IDR_WEB_XTERM_CSS 383
#define IDR_WEB_XTERM_FIT_JS 384
#define IDR_WEB_INDEX_HTML 385
#define IDR_WEB_OPUS_DECODER_JS 401
#define IDB_BITMAP_COMPRESS 386
#define IDB_BITMAP_UNCOMPRESS 387
#define IDB_BITMAP9 388
@@ -284,11 +280,21 @@
#define IDB_BITMAP_HISTORY_ACTIVITY 398
#define IDB_BITMAP_VIEW_WND 399
#define IDB_BITMAP_PROXY_AUTORUN 400
#define IDR_WEB_OPUS_DECODER_JS 401
#define IDD_DIALOG_SHARE_HOST 402
#define IDD_FILE_SPEED 403
#define IDD_DIALOG_CONNECTIONS 404
#define IDR_MENU_CONNECTIONS 405
#define IDI_CONNECTION 406
#define IDB_BITMAP_FILE_MANAGER 407
#define IDB_BITMAP_PRIVACY_SHIELD 408
#define IDB_BITMAP_DRAWING_BOARD 409
#define IDB_BITMAP_TLS_ENABLE 410
#define IDB_BITMAP_TLS_DISABLE 411
#define IDB_BITMAP_DESKTOP_QUALITY 412
#define IDB_BITMAP_PREVIEW_QUALITY 413
#define IDB_BITMAP_FILE_SPEED 414
#define IDB_BITMAP_DISCLAIMER 415
#define IDC_MESSAGE 1000
#define IDC_ONLINE 1001
#define IDC_STATIC_TIPS 1002
@@ -362,8 +368,6 @@
#define IDI_SYSTEM 2035
#define IDC_DOT 2036
#define IDD_AUDIO 2038
#define IDD_QUALITY_LEVELS 3000
#define IDD_PREVIEW_LEVELS 3001
#define IDI_AUDIO 2039
#define IDC_LIST_REMOTE 2040
#define IDC_LIST_LOCAL 2041
@@ -527,6 +531,7 @@
#define IDC_PROGRESS_FILESEND 2214
#define IDC_STATIC_PAYLOAD3 2214
#define IDC_STATIC_CURRENTINDEX 2215
#define IDC_STATIC_LOCATION 2215
#define IDC_STATIC_CURRENTPERCENT 2216
#define IDC_EDIT_INSTALL_DIR 2216
#define IDC_EDIT_INSTALL_NAME 2217
@@ -777,6 +782,52 @@
#define IDC_STATIC_TRIGGER_TYPE 2544
#define IDC_STATIC_TRIGGER_ACTION 2545
#define IDC_EDIT_LOG 2546
#define IDC_QLEVEL_FPS 2547
#define IDC_QLEVEL_WIDTH 2553
#define IDC_QLEVEL_ALGO 2559
#define IDC_QLEVEL_BITRATE 2565
#define IDC_QLEVEL_RESET 2571
#define IDC_PLEVEL_WIDTH 2572
#define IDC_PLEVEL_QUALITY 2578
#define IDC_PLEVEL_RESET 2584
#define IDC_STATIC_SHARE_ADDRESS 2585
#define IDC_EDIT_SHARE_ADDRESS 2586
#define IDC_CHECK_USE_SHINE 2587
#define IDC_STATIC_SHARE_DAYS 2588
#define IDC_EDIT_SHARE_DAYS 2589
#define IDC_FSPEED_SLIDER 2590
#define IDC_FSPEED_LABEL 2591
#define IDC_FSPEED_RESET 2592
#define IDC_QLEVEL_LABEL_LEVEL 2593
#define IDC_QLEVEL_LABEL_FPS 2594
#define IDC_QLEVEL_LABEL_WIDTH 2595
#define IDC_QLEVEL_LABEL_ALGO 2596
#define IDC_QLEVEL_LABEL_BITRATE 2597
#define IDC_QLEVEL_LABEL_LEVEL0 2598
#define IDC_QLEVEL_LABEL_LEVEL1 2599
#define IDC_QLEVEL_LABEL_LEVEL2 2600
#define IDC_QLEVEL_LABEL_LEVEL3 2601
#define IDC_QLEVEL_LABEL_LEVEL4 2602
#define IDC_QLEVEL_LABEL_LEVEL5 2603
#define IDC_QLEVEL_LABEL_HINT 2604
#define IDC_PLEVEL_LABEL_LEVEL 2605
#define IDC_PLEVEL_LABEL_WIDTH 2606
#define IDC_PLEVEL_LABEL_QUALITY 2607
#define IDC_PLEVEL_LABEL_LEVEL0 2608
#define IDC_PLEVEL_LABEL_LEVEL1 2609
#define IDC_PLEVEL_LABEL_LEVEL2 2610
#define IDC_PLEVEL_LABEL_LEVEL3 2611
#define IDC_PLEVEL_LABEL_LEVEL4 2612
#define IDC_PLEVEL_LABEL_LEVEL5 2613
#define IDC_PLEVEL_LABEL_HINT 2614
#define IDC_FSPEED_HINT 2615
#define IDC_FSPEED_LABEL_SLOWEST 2616
#define IDC_FSPEED_LABEL_DEFAULT 2617
#define IDC_FSPEED_LABEL_UNLIMITED 2618
#define IDC_STATIC_TRIGGER_LIST 2619
#define IDC_COMBO_INSTALL_LOC 2620
#define IDD_QUALITY_LEVELS 3000
#define IDD_PREVIEW_LEVELS 3001
#define ID_ONLINE_UPDATE 32772
#define ID_ONLINE_MESSAGE 32773
#define ID_ONLINE_DELETE 32775
@@ -1006,24 +1057,8 @@
#define ID_MSGLOG_CLEAR 33041
#define ID_CANCEL_SHARE 33042
#define ID_MSGLOG_COPY 33043
#define ID_MSGLOG_SEARCH 33078
#define ID_MSGLOG_LOGIN_NOTIFY 33079
#define ID_MSGLOG_BAN_IP 33080
#define ID_MSGLOG_UNBAN_IP 33081
#define ID_MSGLOG_WHITELIST_IP 33096
#define ID_WEB_REMOTE_CONTROL 33044
#define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_MCP_SETTINGS 33077
#define ID_TLS_CERTIFICATE 33093
#define ID_CONNECTION_MANAGE 33086
#define ID_CONNECTION_KILL 33087
#define ID_CONNECTION_REFRESH 33088
#define ID_TALK_MANAGE 33089
#define ID_EXPERIMENT_FILE_MANAGER 33090
#define ID_EXPERIMENT_PRIVATE_SCREEN 33091
#define ID_EXPERIMENT_DRAWING_BOARD 33092
#define ID_EXPERIMENT_TLS_ENABLE 33094
#define ID_EXPERIMENT_TLS_DISABLE 33095
#define ID_33046 33046
#define ID_PROXY_PORT_AUTORUN 33047
#define ID_TRIGGER_SETTINGS 33048
@@ -1056,73 +1091,35 @@
#define ID_ONLINE_CLIENT_LOG 33074
#define ID_ONLINE_33075 33075
#define ID_ONLINE_FORBIDDEN 33076
#define ID_MCP_SETTINGS 33077
#define ID_MSGLOG_SEARCH 33078
#define ID_MSGLOG_LOGIN_NOTIFY 33079
#define ID_MSGLOG_BAN_IP 33080
#define ID_MSGLOG_UNBAN_IP 33081
#define ID_ONLINE_HISTORY_ACTIVITY 33082
#define ID_EXIT_FULLSCREEN 40001
#define ID_TOOL_QUALITY_LEVELS 33083
#define ID_TOOL_PREVIEW_LEVELS 33084
#define IDC_QLEVEL_FPS 2547
#define IDC_QLEVEL_WIDTH 2553
#define IDC_QLEVEL_ALGO 2559
#define IDC_QLEVEL_BITRATE 2565
#define IDC_QLEVEL_RESET 2571
#define IDC_PLEVEL_WIDTH 2572
#define IDC_PLEVEL_QUALITY 2578
#define IDC_PLEVEL_RESET 2584
#define IDD_DIALOG_SHARE_HOST 402
#define IDC_STATIC_SHARE_ADDRESS 2585
#define IDC_EDIT_SHARE_ADDRESS 2586
#define IDC_CHECK_USE_SHINE 2587
#define IDC_STATIC_SHARE_DAYS 2588
#define IDC_EDIT_SHARE_DAYS 2589
// 文件传输速度调节对话框
#define IDD_FILE_SPEED 403
#define ID_TOOL_FILE_SPEED 33085
#define IDC_FSPEED_SLIDER 2590
#define IDC_FSPEED_LABEL 2591
#define IDC_FSPEED_RESET 2592
// 高级菜单三个对话框的静态文本(显式在 OnInitDialog 设置中文,修复非中文系统乱码)
#define IDC_QLEVEL_LABEL_LEVEL 2593
#define IDC_QLEVEL_LABEL_FPS 2594
#define IDC_QLEVEL_LABEL_WIDTH 2595
#define IDC_QLEVEL_LABEL_ALGO 2596
#define IDC_QLEVEL_LABEL_BITRATE 2597
#define IDC_QLEVEL_LABEL_LEVEL0 2598 // 2598~2603:Ultra..Minimal 六档
#define IDC_QLEVEL_LABEL_LEVEL1 2599
#define IDC_QLEVEL_LABEL_LEVEL2 2600
#define IDC_QLEVEL_LABEL_LEVEL3 2601
#define IDC_QLEVEL_LABEL_LEVEL4 2602
#define IDC_QLEVEL_LABEL_LEVEL5 2603
#define IDC_QLEVEL_LABEL_HINT 2604
#define IDC_PLEVEL_LABEL_LEVEL 2605
#define IDC_PLEVEL_LABEL_WIDTH 2606
#define IDC_PLEVEL_LABEL_QUALITY 2607
#define IDC_PLEVEL_LABEL_LEVEL0 2608 // 2608~2613:Ultra..Minimal 六档
#define IDC_PLEVEL_LABEL_LEVEL1 2609
#define IDC_PLEVEL_LABEL_LEVEL2 2610
#define IDC_PLEVEL_LABEL_LEVEL3 2611
#define IDC_PLEVEL_LABEL_LEVEL4 2612
#define IDC_PLEVEL_LABEL_LEVEL5 2613
#define IDC_PLEVEL_LABEL_HINT 2614
#define IDC_FSPEED_HINT 2615
#define IDC_FSPEED_LABEL_SLOWEST 2616
#define IDC_FSPEED_LABEL_DEFAULT 2617
#define IDC_FSPEED_LABEL_UNLIMITED 2618
// 触发器设置对话框的分组框(显式在 OnInitDialog 设置中文,修复非中文系统乱码)
#define IDC_STATIC_TRIGGER_LIST 2619
#define ID_CONNECTION_MANAGE 33086
#define ID_CONNECTION_KILL 33087
#define ID_CONNECTION_REFRESH 33088
#define ID_TALK_MANAGE 33089
#define ID_EXPERIMENT_FILE_MANAGER 33090
#define ID_EXPERIMENT_PRIVATE_SCREEN 33091
#define ID_EXPERIMENT_DRAWING_BOARD 33092
#define ID_TLS_CERTIFICATE 33093
#define ID_EXPERIMENT_TLS_ENABLE 33094
#define ID_EXPERIMENT_TLS_DISABLE 33095
#define ID_MSGLOG_WHITELIST_IP 33096
#define ID_EXIT_FULLSCREEN 40001
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 412
#define _APS_NEXT_RESOURCE_VALUE 416
#define _APS_NEXT_COMMAND_VALUE 33097
#define _APS_NEXT_CONTROL_VALUE 2620
#define _APS_NEXT_CONTROL_VALUE 2621
#define _APS_NEXT_SYMED_VALUE 105
#endif
#endif
+48 -30
View File
@@ -721,8 +721,19 @@ void CMachineDlg::ShowTaskList()
continue;
m_list.InsertItem(i, str);
// 目录列:只去掉末尾“\任务名”。不能用 Replace 全量替换——任务名也可能出现在父目录里,
// 那样算出的目录是错的,执行/停止/删除任务会定位到错误的文件夹。
str = taskpath;
str.Replace(taskname, _T(""));
{
CString tail = _T("\\");
tail += taskname;
if (str.GetLength() >= tail.GetLength() &&
str.Right(tail.GetLength()).CompareNoCase(tail) == 0) {
str = str.Left(str.GetLength() - tail.GetLength());
if (str.IsEmpty())
str = _T("\\");
}
}
// 任务名/目录/程序路径来自客户端 BSTRToUTF8,为 UTF-8;状态为 GBK 字面量(运行中/已禁用…)
SetMachineItemText(m_list, i, 1, str.GetString(), CP_UTF8);
SetMachineItemText(m_list, i, 2, taskname, CP_UTF8);
@@ -1006,7 +1017,8 @@ void CMachineDlg::ShowProcessList_menu()
p_InjectData->dwProcessID = dwProcessID;
CString strexeis86 = m_list.GetItemText(nItem, 9);
strexeis86 == _T("x86") ? p_InjectData->ExeIsx86 = 1 : p_InjectData->ExeIsx86 = 0;
memcpy(p_InjectData->strpath, dlg.Str_remote, dlg.Str_remote.GetLength() * 2 + 2);
// Str_remote 是窄字符串、strpath 是 char[1024]:按字符数+1 拷贝;超长时截断,避免堆溢出
strncpy_s(p_InjectData->strpath, sizeof(p_InjectData->strpath), dlg.Str_remote, _TRUNCATE);
//读取文件
BYTE* lpBuffer = NULL;
HANDLE hFile = CreateFile(dlg.Str_loacal, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
@@ -1412,16 +1424,18 @@ void CMachineDlg::ShowTaskList_menu()
taskpath = m_list.GetItemText(nItem, 1);
taskname = m_list.GetItemText(nItem, 2);
int nPacketLength = lstrlen(taskpath.GetBuffer()) * 2 + lstrlen(taskname.GetBuffer()) * 2 + 5;
// 服务端是 MultiByte 工程:按窄字符拼接,包长 = 1(令牌) + (目录+1) + (任务名+1)
int nPacketLength = lstrlen(taskpath.GetBuffer()) + lstrlen(taskname.GetBuffer()) + 3;
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, nPacketLength);
if (!lpBuffer) return;
lpBuffer[0] = COMMAND_TASKSTART;
offset++;
memcpy(lpBuffer + offset, taskpath.GetBuffer(), lstrlen(taskpath.GetBuffer()) * 2 + 2);
offset += lstrlen(taskpath.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, taskpath.GetBuffer(), lstrlen(taskpath.GetBuffer()) + 1);
offset += lstrlen(taskpath.GetBuffer()) + 1;
memcpy(lpBuffer + offset, taskname.GetBuffer(), lstrlen(taskname.GetBuffer()) * 2 + 2);
offset += lstrlen(taskname.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, taskname.GetBuffer(), lstrlen(taskname.GetBuffer()) + 1);
offset += lstrlen(taskname.GetBuffer()) + 1;
m_ContextObject->Send2Client(lpBuffer, nPacketLength);
@@ -1440,16 +1454,18 @@ void CMachineDlg::ShowTaskList_menu()
taskpath = m_list.GetItemText(nItem, 1);
taskname = m_list.GetItemText(nItem, 2);
int nPacketLength = lstrlen(taskpath.GetBuffer()) * 2 + lstrlen(taskname.GetBuffer()) * 2 + 5;
// 服务端是 MultiByte 工程:按窄字符拼接,包长 = 1(令牌) + (目录+1) + (任务名+1)
int nPacketLength = lstrlen(taskpath.GetBuffer()) + lstrlen(taskname.GetBuffer()) + 3;
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, nPacketLength);
if (!lpBuffer) return;
lpBuffer[0] = COMMAND_TASKSTOP;
offset++;
memcpy(lpBuffer + offset, taskpath.GetBuffer(), lstrlen(taskpath.GetBuffer()) * 2 + 2);
offset += lstrlen(taskpath.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, taskpath.GetBuffer(), lstrlen(taskpath.GetBuffer()) + 1);
offset += lstrlen(taskpath.GetBuffer()) + 1;
memcpy(lpBuffer + offset, taskname.GetBuffer(), lstrlen(taskname.GetBuffer()) * 2 + 2);
offset += lstrlen(taskname.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, taskname.GetBuffer(), lstrlen(taskname.GetBuffer()) + 1);
offset += lstrlen(taskname.GetBuffer()) + 1;
m_ContextObject->Send2Client(lpBuffer, nPacketLength);
@@ -1468,16 +1484,18 @@ void CMachineDlg::ShowTaskList_menu()
taskpath = m_list.GetItemText(nItem, 1);
taskname = m_list.GetItemText(nItem, 2);
int nPacketLength = lstrlen(taskpath.GetBuffer()) * 2 + lstrlen(taskname.GetBuffer()) * 2 + 5;
// 服务端是 MultiByte 工程:按窄字符拼接,包长 = 1(令牌) + (目录+1) + (任务名+1)
int nPacketLength = lstrlen(taskpath.GetBuffer()) + lstrlen(taskname.GetBuffer()) + 3;
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, nPacketLength);
if (!lpBuffer) return;
lpBuffer[0] = COMMAND_TASKDEL;
offset++;
memcpy(lpBuffer + offset, taskpath.GetBuffer(), lstrlen(taskpath.GetBuffer()) * 2 + 2);
offset += lstrlen(taskpath.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, taskpath.GetBuffer(), lstrlen(taskpath.GetBuffer()) + 1);
offset += lstrlen(taskpath.GetBuffer()) + 1;
memcpy(lpBuffer + offset, taskname.GetBuffer(), lstrlen(taskname.GetBuffer()) * 2 + 2);
offset += lstrlen(taskname.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, taskname.GetBuffer(), lstrlen(taskname.GetBuffer()) + 1);
offset += lstrlen(taskname.GetBuffer()) + 1;
m_ContextObject->Send2Client(lpBuffer, nPacketLength);
@@ -1489,28 +1507,28 @@ void CMachineDlg::ShowTaskList_menu()
DWORD offset = 0;
CCreateTaskDlg* dlg = new CCreateTaskDlg(this);
if (IDOK == dlg->DoModal()) {
// 计算字符串长度
len = lstrlen(dlg->m_TaskPath.GetBuffer()) * 2 + lstrlen(dlg->m_TaskNames.GetBuffer()) * 2 + lstrlen(dlg->m_ExePath.GetBuffer()) *
2 + lstrlen(dlg->m_Author.GetBuffer()) * 2 + lstrlen(dlg->m_Description.GetBuffer()) * 2 + 12;
// 服务端是 MultiByte 工程:5 个窄字符串各带 1 字节结束符,再加 1 字节令牌
len = lstrlen(dlg->m_TaskPath.GetBuffer()) + lstrlen(dlg->m_TaskNames.GetBuffer()) + lstrlen(dlg->m_ExePath.GetBuffer()) +
lstrlen(dlg->m_Author.GetBuffer()) + lstrlen(dlg->m_Description.GetBuffer()) + 6;
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, len);
if (lpBuffer) {
lpBuffer[0] = COMMAND_TASKCREAT;
offset++;
memcpy(lpBuffer + offset, dlg->m_TaskPath.GetBuffer(), lstrlen(dlg->m_TaskPath.GetBuffer()) * 2 + 2);
offset += lstrlen(dlg->m_TaskPath.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, dlg->m_TaskPath.GetBuffer(), lstrlen(dlg->m_TaskPath.GetBuffer()) + 1);
offset += lstrlen(dlg->m_TaskPath.GetBuffer()) + 1;
memcpy(lpBuffer + offset, dlg->m_TaskNames.GetBuffer(), lstrlen(dlg->m_TaskNames.GetBuffer()) * 2 + 2);
offset += lstrlen(dlg->m_TaskNames.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, dlg->m_TaskNames.GetBuffer(), lstrlen(dlg->m_TaskNames.GetBuffer()) + 1);
offset += lstrlen(dlg->m_TaskNames.GetBuffer()) + 1;
memcpy(lpBuffer + offset, dlg->m_ExePath.GetBuffer(), lstrlen(dlg->m_ExePath.GetBuffer()) * 2 + 2);
offset += lstrlen(dlg->m_ExePath.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, dlg->m_ExePath.GetBuffer(), lstrlen(dlg->m_ExePath.GetBuffer()) + 1);
offset += lstrlen(dlg->m_ExePath.GetBuffer()) + 1;
memcpy(lpBuffer + offset, dlg->m_Author.GetBuffer(), lstrlen(dlg->m_Author.GetBuffer()) * 2 + 2);
offset += lstrlen(dlg->m_Author.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, dlg->m_Author.GetBuffer(), lstrlen(dlg->m_Author.GetBuffer()) + 1);
offset += lstrlen(dlg->m_Author.GetBuffer()) + 1;
memcpy(lpBuffer + offset, dlg->m_Description.GetBuffer(), lstrlen(dlg->m_Description.GetBuffer()) * 2 + 2);
offset += lstrlen(dlg->m_Description.GetBuffer()) * 2 + 2;
memcpy(lpBuffer + offset, dlg->m_Description.GetBuffer(), lstrlen(dlg->m_Description.GetBuffer()) + 1);
offset += lstrlen(dlg->m_Description.GetBuffer()) + 1;
m_ContextObject->Send2Client(lpBuffer, len);
LocalFree(lpBuffer);
+208 -20
View File
@@ -391,6 +391,10 @@
padding: 16px;
margin-bottom: 14px;
}
.audit-filters.collapsed { display: none; }
/* Filter labels are grid items; without this a wide intrinsic child
(notably datetime-local on mobile) can blow out the grid track. */
.audit-filters > * { min-width: 0; }
.audit-filters label {
display: flex;
flex-direction: column;
@@ -409,8 +413,37 @@
font-size: 13px;
outline: none;
min-width: 0;
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
.audit-filters input:focus, .audit-filters select:focus { border-color: #e67e22; }
/* iOS Safari mis-sizes temporal controls: with horizontal padding on
the input itself, width:100% adds the padding again (WebKit #301648),
and iOS 17 ignores width entirely on the native inline-flex control.
Move the visual box to a wrapper and keep the input padding-free so
it can never exceed its parent. */
.audit-date-wrap {
display: block;
min-width: 0;
padding: 0 10px;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
background: rgba(15, 52, 96, 0.7);
}
.audit-date-wrap:focus-within { border-color: #e67e22; }
.audit-date-wrap input[type="datetime-local"] {
display: block;
width: 100%;
max-width: 100%;
min-width: 0;
padding: 8px 0;
border: none;
border-radius: 0;
background: transparent;
color: #fff;
font-size: 13px;
}
.audit-filters label.audit-warn {
flex-direction: row;
align-items: center;
@@ -430,6 +463,22 @@
.audit-search-btn:hover { transform: translateY(-1px); }
.audit-reset-btn { background: rgba(255,255,255,0.08); color: #ccc; }
.audit-reset-btn:hover { background: rgba(255,255,255,0.15); }
/* Filters visibility toggle: collapsed by default; the same button
stays in the toolbar and flips to an "open" style (accent tint +
caret pointing up) so it reads as "click to collapse". */
.view-btn.audit-filter-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid rgba(230, 126, 34, 0.45);
border-radius: 8px;
color: #e67e22;
background: rgba(230, 126, 34, 0.12);
}
.view-btn.audit-filter-toggle:hover { background: rgba(230, 126, 34, 0.22); color: #fff; }
.view-btn.audit-filter-toggle.open { background: rgba(230, 126, 34, 0.30); color: #fff; }
.audit-filter-toggle .caret { display: inline-block; font-size: 10px; transition: transform 0.2s; }
.audit-filter-toggle.open .caret { transform: rotate(180deg); }
.audit-status { color: #888; font-size: 12px; margin-bottom: 10px; min-height: 16px; }
.audit-status.warn { color: #e67e22; }
.audit-table-wrap {
@@ -470,8 +519,11 @@
.audit-level.warn { background: rgba(230, 126, 34, 0.18); color: #e67e22; }
.audit-empty { padding: 40px; text-align: center; color: #666; }
@media (max-width: 640px) {
.audit-filters { grid-template-columns: 1fr 1fr; }
.audit-actions { grid-column: 1 / -1; }
/* One control per row: two columns plus the intrinsic width of
datetime-local inputs overflows a portrait phone viewport. */
.audit-filters { grid-template-columns: minmax(0, 1fr); padding: 14px; }
.audit-actions { grid-column: 1 / -1; flex-wrap: wrap; }
.audit-actions button { flex: 1 1 auto; }
.audit-table td, .audit-table th { padding: 8px; }
}
@@ -522,6 +574,14 @@
color: #fff;
}
.confirm-modal-actions .danger-btn:hover { box-shadow: 0 4px 12px rgba(192, 57, 43, 0.4); transform: translateY(-1px); }
/* Info variant for the generic alert dialog (single OK button). */
.confirm-modal-content.info { border-color: rgba(230, 126, 34, 0.25); }
.confirm-modal-icon.info { background: rgba(230, 126, 34, 0.15); color: #e67e22; }
.confirm-modal-actions .primary-btn {
background: linear-gradient(135deg, #b9770e 0%, #e67e22 100%);
color: #fff;
}
.confirm-modal-actions .primary-btn:hover { box-shadow: 0 4px 12px rgba(230, 126, 34, 0.4); transform: translateY(-1px); }
.device-grid {
display: grid;
@@ -1787,15 +1847,24 @@
<h2>Audit Log <span id="audit-count" class="device-count">0</span></h2>
</div>
<div class="toolbar">
<button class="view-btn audit-filter-toggle" id="audit-filter-toggle" type="button"
aria-expanded="false" aria-controls="audit-filters"
onclick="toggleAuditFilters()">
<span class="label">Filters</span><span class="caret">&#9662;</span>
</button>
<button class="view-btn" onclick="queryAudit(0)" title="Reload newest records">Refresh</button>
</div>
</div>
<div class="audit-filters">
<div class="audit-filters collapsed" id="audit-filters">
<label>From
<span class="audit-date-wrap">
<input type="datetime-local" id="audit-from" onkeydown="if(event.key==='Enter')queryAudit(0)">
</span>
</label>
<label>To
<span class="audit-date-wrap">
<input type="datetime-local" id="audit-to" onkeydown="if(event.key==='Enter')queryAudit(0)">
</span>
</label>
<label>Event (exact or prefix)
<input type="text" id="audit-event" list="audit-event-options" placeholder="e.g. web.* or web.login.failure"
@@ -1913,6 +1982,23 @@
</div>
</div>
<!-- Generic alert / confirmation dialog. Replaces native alert()/confirm():
their button labels come from the browser locale (e.g. German on a
German system) and do not match the app. -->
<div class="modal-overlay" id="app-dialog" onclick="if(event.target===this)closeAppDialog(false)"
onkeydown="if(event.key==='Escape')closeAppDialog(false)">
<div class="confirm-modal-content" id="app-dialog-content" role="dialog" aria-modal="true"
aria-labelledby="app-dialog-title" aria-describedby="app-dialog-message">
<div class="confirm-modal-icon" id="app-dialog-icon"></div>
<h3 id="app-dialog-title">Notice</h3>
<p id="app-dialog-message"></p>
<div class="confirm-modal-actions">
<button class="cancel-btn" id="app-dialog-cancel" onclick="closeAppDialog(false)">Cancel</button>
<button class="danger-btn" id="app-dialog-ok" onclick="closeAppDialog(true)">OK</button>
</div>
</div>
</div>
<script src="__BASE_PATH__/static/xterm.js"></script>
<script src="__BASE_PATH__/static/xterm-fit.js"></script>
@@ -2333,7 +2419,17 @@
wallAllocBudget();
getDevices();
} else {
// Hiding the tab closes the socket on non-streaming
// pages and returning reconnects here. Keep an open
// audit log visible instead of yanking the admin back
// to the device list. Every other page still moves to
// devices: a terminal session died with the socket,
// and the login page needs to advance with a cached
// token.
const active = document.querySelector('.page.active');
if (!active || active.id !== 'audit-page') {
showPage('devices-page');
}
getDevices();
}
}
@@ -2642,7 +2738,7 @@
// mode=removed:历史记录已删除,刷新后卡片消失。
getDevices();
} else {
alert(msg.msg || 'Failed to delete device');
appAlert(msg.msg || 'Failed to delete device', 'Unable to Delete Device');
}
break;
case 'list_users_result':
@@ -3228,7 +3324,10 @@
const existing = wall.sessions.get(l32);
if (existing && existing.fullId !== id) {
// 两台设备的低 32 位相同:绝不能共用一个解码器。
if (!restoring) alert('Device ID collision (low 32 bits). Cannot open both on the same wall.');
if (!restoring) {
appAlert('Device ID collision (low 32 bits). Cannot open both on the same wall.',
'Device ID Collision');
}
return null;
}
const uid = 'w' + (wall.uidSeq++);
@@ -3250,11 +3349,11 @@
function addWallTile(deviceId) {
if (!serverCaps.multiWatch) {
alert('Screen Wall is not supported by this server');
appAlert('Screen Wall is not supported by this server.', 'Screen Wall Unavailable');
return;
}
if (wall.tiles.length >= WALL_MAX_TILES) {
alert('Wall is full (max ' + WALL_MAX_TILES + ' tiles)');
appAlert('Wall is full (max ' + WALL_MAX_TILES + ' tiles).', 'Screen Wall Full');
return;
}
const dev = devices.find(d => String(d.id) === String(deviceId));
@@ -3294,9 +3393,11 @@
wallUpdateSidebarBadges();
}
function clearWall() {
async function clearWall() {
if (!wall.tiles.length) return;
if (!confirm('Close all tiles on the wall?')) return;
const ok = await appConfirm('Close all tiles on the wall?',
{ title: 'Clear Screen Wall', okText: 'Clear' });
if (!ok) return;
const tiles = wall.tiles.slice();
wall.tiles = [];
for (const tile of tiles) {
@@ -3508,8 +3609,8 @@
const tile = wall.tiles.find(t => t.uid === uid);
if (!tile) return;
const dev = devices.find(d => String(d.id) === tile.deviceId);
if (!dev) { alert('Device not found'); return; }
if (!dev.online) { alert('Device is offline'); return; }
if (!dev) { appAlert('Device not found.', 'Device Not Found'); return; }
if (!dev.online) { appAlert('Device is offline.', 'Device Offline'); return; }
const s = wall.sessions.get(tile.low32);
if (s && s.status === 'offline') {
// 设备重新上线:离线时服务端已移除 watch,且 streams 条目被整体删除,
@@ -3548,12 +3649,13 @@
function showWall() {
if (!serverCaps.multiWatch) {
alert('Screen Wall requires a newer server with multi-view support.');
appAlert('Screen Wall requires a newer server with multi-view support.',
'Screen Wall Unavailable');
return;
}
const compat = checkWebCodecs();
if (!compat.supported) {
alert('Browser does not support H.264: ' + compat.reason);
appAlert('Browser does not support H.264: ' + compat.reason, 'H.264 Not Supported');
return;
}
if (!wallRestored) wallRestoreState();
@@ -4389,6 +4491,74 @@
updateAdminButtons();
}
// ===== In-app alert / confirmation dialog =====
// Native alert()/confirm() use the browser locale for their buttons
// (e.g. "Abbrechen"/"OK" on a German system) and ignore the app theme.
// Both now go through the same themed modal as the logout prompt.
const APP_DIALOG_ICONS = {
danger: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
'<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>' +
'<line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
info: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
'<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/>' +
'<line x1="12" y1="8" x2="12.01" y2="8"/></svg>'
};
let appDialogResolve = null;
function openAppDialog(opts) {
// A pending dialog is superseded (e.g. an async error arrives while
// a confirm is open): resolve it as cancelled so no caller hangs.
if (appDialogResolve) {
const previous = appDialogResolve;
appDialogResolve = null;
previous(false);
}
const danger = opts.danger !== false;
const content = document.getElementById('app-dialog-content');
const icon = document.getElementById('app-dialog-icon');
const cancel = document.getElementById('app-dialog-cancel');
const ok = document.getElementById('app-dialog-ok');
content.className = 'confirm-modal-content' + (danger ? '' : ' info');
icon.className = 'confirm-modal-icon' + (danger ? '' : ' info');
icon.innerHTML = danger ? APP_DIALOG_ICONS.danger : APP_DIALOG_ICONS.info;
document.getElementById('app-dialog-title').textContent =
opts.title || (danger ? 'Confirm' : 'Notice');
// textContent: server/device/user supplied strings must never be
// interpreted as markup.
document.getElementById('app-dialog-message').textContent = opts.message || '';
cancel.style.display = opts.cancelText ? '' : 'none';
cancel.textContent = opts.cancelText || 'Cancel';
ok.className = danger ? 'danger-btn' : 'primary-btn';
ok.textContent = opts.okText || 'OK';
document.getElementById('app-dialog').classList.add('active');
setTimeout(() => ok.focus(), 0);
return new Promise(resolve => { appDialogResolve = resolve; });
}
function closeAppDialog(result) {
document.getElementById('app-dialog').classList.remove('active');
const resolve = appDialogResolve;
appDialogResolve = null;
if (resolve) resolve(!!result);
}
// Fire-and-forget replacement for alert().
function appAlert(message, title) {
return openAppDialog({ title: title || 'Notice', message, okText: 'OK', danger: false });
}
// Promise<boolean> replacement for confirm().
function appConfirm(message, opts) {
opts = opts || {};
return openAppDialog({
title: opts.title || 'Confirm',
message,
okText: opts.okText || 'OK',
cancelText: opts.cancelText || 'Cancel',
danger: opts.danger !== false
});
}
// ===== 删除设备 =====
// 两种情况都删除服务端 devices.json 中的历史记录;在线设备额外按 MFC
// 的做法发送单字节 TOKEN_UNINSTALL(卸载主机)。都先弹二次确认。
@@ -4402,7 +4572,7 @@
function askDeleteDevice(id) {
if (!canDeleteDevices()) return;
const d = devices.find(x => String(x.id) === String(id));
if (!d) { alert('Device not found'); return; }
if (!d) { appAlert('Device not found.', 'Device Not Found'); return; }
const offline = d.online === false;
const name = displayName(d);
pendingDeleteDeviceId = String(id);
@@ -4498,8 +4668,10 @@
}
}
function deleteUser(username) {
if (!confirm('Delete user "' + username + '"?')) return;
async function deleteUser(username) {
const ok = await appConfirm('Delete user "' + username + '"?',
{ title: 'Delete User', okText: 'Delete' });
if (!ok) return;
if (ws && ws.readyState === WebSocket.OPEN && token) {
ws.send(JSON.stringify({ cmd: 'delete_user', token, username }));
}
@@ -4542,6 +4714,20 @@
if (auditBtn) auditBtn.classList.toggle('visible', admin && !!serverCaps.auditLog);
}
// Search conditions start collapsed (especially important on portrait
// phones); the toolbar button stays put and switches to a highlighted
// "up" state while the panel is open, meaning clicking again folds it.
function toggleAuditFilters() {
const panel = document.getElementById('audit-filters');
const btn = document.getElementById('audit-filter-toggle');
if (!panel || !btn) return;
const collapsed = panel.classList.toggle('collapsed');
btn.classList.toggle('open', !collapsed);
btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
const label = btn.querySelector('.label');
if (label) label.textContent = collapsed ? 'Filters' : 'Hide filters';
}
function openAuditPage() {
if (currentUserRole !== 'admin' || !serverCaps.auditLog) return;
fillAuditEventOptions();
@@ -4717,7 +4903,7 @@
function connectDevice(id, fromWall) {
const dev = devices.find(d => d.id === id || d.id === String(id));
if (!dev || !dev.online) {
alert('Device is offline');
appAlert('Device is offline.', 'Device Offline');
return;
}
// 无显示器(screen 字段格式 "n:WxH",n=0 表示无显示器)→ 远程桌面没有意义,
@@ -4729,7 +4915,7 @@
return;
}
const compat = checkWebCodecs();
if (!compat.supported) { alert('Browser does not support H264: ' + compat.reason); return; }
if (!compat.supported) { appAlert('Browser does not support H264: ' + compat.reason, 'H.264 Not Supported'); return; }
const sid = String(id);
// 同页直接切设备(正常 UI 不会发生):先释放旧 focus 引用。
if (currentDevice && String(currentDevice.id) !== sid) {
@@ -4767,11 +4953,11 @@
function openTerminal(id) {
if (typeof Terminal === 'undefined') {
alert('Terminal library not loaded yet, please retry');
appAlert('Terminal library not loaded yet, please retry.', 'Terminal Unavailable');
return;
}
const dev = devices.find(d => d.id === id || d.id === String(id));
if (!dev || !dev.online) { alert('Device is offline'); return; }
if (!dev || !dev.online) { appAlert('Device is offline.', 'Device Offline'); return; }
// 已经有终端在跑:直接 show(重连同设备视为重置)
if (termState.deviceId && termState.deviceId !== String(id)) {
closeTerminal();
@@ -6317,6 +6503,8 @@
// Desktop keyboard handling
document.addEventListener('keydown', function(e) {
if (!document.getElementById('screen-page').classList.contains('active')) return;
// Keys belong to an open in-app dialog (buttons are not INPUT/TEXTAREA).
if (document.getElementById('app-dialog').classList.contains('active')) return;
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
// F11 belongs to the local fullscreen toggle UNLESS we're actively
// controlling the remote — see the F11 handler above. Skip here