Files
SimpleRemoter/client/LoginServer.cpp
T
yuanyuanxianganddeepseek-v4-pro 99e3bcbc6d Feat: Per-client TLS transport toggle (TLS column + remote enable/disable)
Add a per-client TLS transport toggle controlled from the server, using the
presence of the client's trust-anchor certificate in the registry as the
single source of truth (no separate tls_enable flag).

- Client reports its TLS status via a new RES_TLS token (cert present -> 1)
  appended to LOGIN_INFOR's reserved field.
- Server online list gains a "TLS" column after the permission column.
- The "experimental features" right-click menu gains "TLS传输" / "取消TLS"
  with confirmation and a guard that prompts (instead of sending) when the
  server has no TLS certificate ready.
- New COMMAND_TLS_ENABLE / COMMAND_TLS_DISABLE (166/167): the server pushes
  its cert PEM into the client registry settings\tls_cert (enable) or deletes
  it (disable). The client picks this up on next startup via the existing
  ReadTrustAnchor logic, so the switch is non-live by design.
- Icons and en_US/zh_TW/de_DE translations for the new menu items.

Co-Authored-By: deepseek-v4-pro
2026-10-16 07:04:54 +02:00

477 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "StdAfx.h"
#include "LoginServer.h"
#include "Common.h"
#include <string>
#include <iostream>
#include <iomanip>
#include <ctime>
#include <NTSecAPI.h>
#include "common/skCrypter.h"
#include <common/iniFile.h>
#include <location.h>
#include "ScreenCapture.h"
std::string getSystemName()
{
typedef void(__stdcall* NTPROC)(DWORD*, DWORD*, DWORD*);
HINSTANCE hinst = LoadLibrary("ntdll.dll");
if (!hinst) {
return "未知操作系统";
}
NTPROC proc = (NTPROC)GetProcAddress(hinst, "RtlGetNtVersionNumbers");
if (!proc) {
FreeLibrary(hinst);
return "未知操作系统";
}
DWORD dwMajor, dwMinor, dwBuildNumber;
proc(&dwMajor, &dwMinor, &dwBuildNumber);
dwBuildNumber &= 0xFFFF; // 高位是标志位,只取低16位
FreeLibrary(hinst);
// 判断是否为 Server 版本
OSVERSIONINFOEX osvi = { sizeof(osvi) };
GetVersionEx((OSVERSIONINFO*)&osvi);
bool isServer = (osvi.wProductType != VER_NT_WORKSTATION);
std::string vname;
if (dwMajor == 10 && dwMinor == 0) {
if (isServer) {
// Windows Server
if (dwBuildNumber >= 20348)
vname = "Windows Server 2022";
else if (dwBuildNumber >= 17763)
vname = "Windows Server 2019";
else
vname = "Windows Server 2016";
} else {
// Windows 桌面版
if (dwBuildNumber >= 22000)
vname = "Windows 11";
else
vname = "Windows 10";
}
} else if (dwMajor == 6) {
switch (dwMinor) {
case 3:
vname = isServer ? "Windows Server 2012 R2" : "Windows 8.1";
break;
case 2:
vname = isServer ? "Windows Server 2012" : "Windows 8";
break;
case 1:
vname = isServer ? "Windows Server 2008 R2" : "Windows 7";
break;
case 0:
vname = isServer ? "Windows Server 2008" : "Windows Vista";
break;
}
} else if (dwMajor == 5) {
switch (dwMinor) {
case 2:
vname = "Windows Server 2003";
break;
case 1:
vname = "Windows XP";
break;
case 0:
vname = "Windows 2000";
break;
}
}
if (vname.empty()) {
char buf[64];
sprintf(buf, "Windows (Build %d)", dwBuildNumber);
vname = buf;
}
Mprintf("此电脑的版本为:%s (Build %d)\n", vname.c_str(), dwBuildNumber);
return vname;
}
std::string formatTime(const FILETIME& fileTime)
{
// 转换为 64 位时间
ULARGE_INTEGER ull;
ull.LowPart = fileTime.dwLowDateTime;
ull.HighPart = fileTime.dwHighDateTime;
// 转换为秒级时间戳
std::time_t startTime = static_cast<std::time_t>((ull.QuadPart / 10000000ULL) - 11644473600ULL);
// 格式化输出
std::tm* localTime = std::localtime(&startTime);
char buffer[100];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localTime);
return std::string(buffer);
}
std::string getProcessTime()
{
FILETIME creationTime, exitTime, kernelTime, userTime;
// 获取当前进程的时间信息
if (GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)) {
return formatTime(creationTime);
}
std::time_t now = std::time(nullptr);
std::tm* t = std::localtime(&now);
char buffer[100];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", t);
return buffer;
}
int getOSBits()
{
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64) {
return 64;
} else {
return 32;
}
}
// 检查CPU核心数
// SYSTEM_INFO.dwNumberOfProcessors
int GetCPUCores()
{
INT i = 0;
#ifdef _WIN64
// 在 x64 下,我们需要使用 `NtQuerySystemInformation`
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
i = sysInfo.dwNumberOfProcessors; // 获取 CPU 核心数
#else
_asm { // x64编译模式下不支持__asm的汇编嵌入
mov eax, dword ptr fs : [0x18] ; // TEB
mov eax, dword ptr ds : [eax + 0x30] ; // PEB
mov eax, dword ptr ds : [eax + 0x64] ;
mov i, eax;
}
#endif
Mprintf("此计算机CPU核心: %d\n", i);
return i;
}
double GetMemorySizeGB()
{
_MEMORYSTATUSEX mst;
mst.dwLength = sizeof(mst);
GlobalMemoryStatusEx(&mst);
double GB = mst.ullTotalPhys / (1024.0 * 1024 * 1024);
Mprintf("此计算机内存: %fGB\n", GB);
return GB;
}
#pragma comment(lib, "Version.lib")
std::string GetCurrentExeVersion()
{
TCHAR filePath[MAX_PATH];
if (GetModuleFileName(NULL, filePath, MAX_PATH) == 0) {
return "Unknown";
}
DWORD handle = 0;
DWORD verSize = GetFileVersionInfoSize(filePath, &handle);
if (verSize == 0) {
return "Unknown";
}
std::vector<BYTE> verData(verSize);
if (!GetFileVersionInfo(filePath, handle, verSize, verData.data())) {
return "Unknown";
}
VS_FIXEDFILEINFO* pFileInfo = nullptr;
UINT len = 0;
if (!VerQueryValue(verData.data(), "\\", reinterpret_cast<LPVOID*>(&pFileInfo), &len)) {
return "Unknown";
}
if (pFileInfo) {
DWORD major = HIWORD(pFileInfo->dwFileVersionMS);
DWORD minor = LOWORD(pFileInfo->dwFileVersionMS);
DWORD build = HIWORD(pFileInfo->dwFileVersionLS);
DWORD revision = LOWORD(pFileInfo->dwFileVersionLS);
std::ostringstream oss;
oss << major << "." << minor << "." << build << "." << revision;
return "v" + oss.str();
}
return "Unknown";
}
std::string GetCurrentUserNameA()
{
// 用 W 接口取宽字符再转 UTF-8,避免依赖系统 ANSI 代码页(中文账号名在英语系统上
// 用 GetUserNameA 取出来是 '?',与 LOGIN_INFOR 的 CLIENT_CAP_UTF8 声明也不一致)。
wchar_t wname[256] = {};
DWORD wsize = _countof(wname);
if (!GetUserNameW(wname, &wsize)) {
return "Unknown";
}
char buf[256 * 3] = {};
if (WideCharToMultiByte(CP_UTF8, 0, wname, -1, buf, sizeof(buf), NULL, NULL) <= 0) {
return "Unknown";
}
return std::string(buf);
}
#define XXH_INLINE_ALL
#include "common/xxhash.h"
// 老算法:基于客户端信息计算唯一ID: { IP, PC, OS, CPU, PATH }
// 注意:pubIP 不稳定(DHCP/换网络)会让 ID 跳变;同 hostname+同安装路径的多机会撞库。
// 保留此函数仅为协议兼容(老服务端仍按这个算法验算 RES_CLIENT_ID)。
uint64_t CalcalateID(const std::vector<std::string>& clientInfo)
{
std::string s;
for (int i = 0; i < 5; i++) {
s += clientInfo[i] + "|";
}
s.erase(s.length()-1);
return XXH64(s.c_str(), s.length(), 0);
}
// 读取 Windows 安装时生成的机器 GUID。
// HKLM\Software\Microsoft\Cryptography\MachineGuid 是 Windows 安装时生成的随机 GUID,
// 重装系统才会变;局域网每台机器都不同(即便同镜像,sysprep 也会重置)。
// 这是比 pubIP/PCName/CPU 都更稳定且更具区分度的硬件标识。
std::string GetMachineGuidWindows()
{
HKEY hKey = NULL;
// KEY_WOW64_64KEY: 32 位进程也访问 64 位注册表视图,避免 WOW6432Node 重定向。
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Cryptography",
0, KEY_READ | KEY_WOW64_64KEY, &hKey) != ERROR_SUCCESS) {
return std::string();
}
char buf[64] = {};
DWORD sz = sizeof(buf) - 1; // 留 1 字节给 NUL
DWORD type = 0;
LSTATUS s = RegQueryValueExA(hKey, "MachineGuid", NULL, &type,
(BYTE*)buf, &sz);
RegCloseKey(hKey);
if (s != ERROR_SUCCESS || type != REG_SZ) return std::string();
return std::string(buf);
}
// 路径归一化:先尝试展开成长路径(如 PROGRA~1 -> Program Files),再小写化。
// 用于 V2 ID 的输入,保证大小写或长短名变化时同一可执行文件得到同一 ID。
static std::string NormalizeExePathLower(const char* path)
{
char longPath[MAX_PATH] = {};
if (GetLongPathNameA(path, longPath, MAX_PATH) == 0) {
// 展开失败(路径不存在等罕见情况):直接用原值
strcpy_s(longPath, path);
}
CharLowerA(longPath); // 原地小写化(对 ASCII 简单,对中文路径会按宽字符规则处理)
return std::string(longPath);
}
// 新算法:machineGuid + 归一化路径
// - 同机同程序:永远同 ID(不依赖 IP/PCName/OS/CPU)。
// - 局域网多机相同镜像:MachineGuid 必不同 → ID 必不同。
// - 一台机两份程序在不同目录 → ID 不同。
uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normalizedPath, bool isAuth)
{
std::string s = isAuth ? machineGuid : machineGuid + "|" + normalizedPath;
return XXH64(s.c_str(), s.length(), 0);
}
BOOL IsAuthKernel(std::string &str) {
BOOL isAuthKernel = FALSE;
std::string pid = std::to_string(GetCurrentProcessId());
HANDLE hEvent1 = OpenEventA(SYNCHRONIZE, FALSE, std::string("YAMA_" + pid).c_str());
HANDLE hEvent2 = OpenEventA(SYNCHRONIZE, FALSE, std::string("EVENT_" + pid).c_str());
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
char buf[_MAX_PATH] = {};
GetModuleFileNameA(NULL, buf, sizeof(buf));
GetFileAttributesExA(buf, GetFileExInfoStandard, &fileInfo);
if ((hEvent1 != NULL || hEvent2 != NULL) && fileInfo.nFileSizeLow > 16 * 1024 * 1024) {
Mprintf("Check event handle: %d, %d\n", hEvent1 != NULL, hEvent2 != NULL);
isAuthKernel = TRUE;
config* cfg = IsDebug ? new config : new iniFile;
str = cfg->GetStr("settings", "Password", "");
delete cfg;
str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
auto list = StringToVector(str, '-', 3);
str = list[1].empty() ? "Unknown" : list[1];
}
SAFE_CLOSE_HANDLE(hEvent1);
SAFE_CLOSE_HANDLE(hEvent2);
return isAuthKernel;
}
LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string& expiredDate, bool isAuth)
{
std::string str = expiredDate;
iniFile cfg(CLIENT_PATH);
LOGIN_INFOR LoginInfor;
LoginInfor.bToken = TOKEN_LOGIN; // 令牌为登录
//获得操作系统信息
strcpy_s(LoginInfor.OsVerInfoEx, getSystemName().c_str());
//获得PCName
char szPCName[MAX_PATH] = {0};
gethostname(szPCName, MAX_PATH);
DWORD dwCPUMHz;
dwCPUMHz = CPUClockMHz();
BOOL bWebCamIsExist = WebCamIsExist();
std::string group = cfg.GetStr("settings", "group_name");
if (!group.empty())
strcpy_s(conn.szGroupName, group.c_str());
if (conn.szGroupName[0] == 0)
memcpy(LoginInfor.szPCName, szPCName, sizeof(LoginInfor.szPCName));
else
sprintf(LoginInfor.szPCName, "%s/%s", szPCName, conn.szGroupName);
LoginInfor.dwSpeed = dwSpeed;
LoginInfor.dwCPUMHz = dwCPUMHz;
LoginInfor.bWebCamIsExist = bWebCamIsExist;
strcpy_s(LoginInfor.szStartTime, getProcessTime().c_str());
LoginInfor.AddReserved(GetClientType(conn.ClientType())); // 类型
LoginInfor.AddReserved(getOSBits()); // 系统位数
LoginInfor.AddReserved(GetCPUCores()); // CPU核数
LoginInfor.AddReserved(GetMemorySizeGB()); // 系统内存
// 路径分两份处理:
// - buf (CP_ACP): 保留给 CalcalateIDv2 / 老 CalculateID 用,保证升级后 client ID
// 不变(老版客户端用的是 GetModuleFileNameA 的 CP_ACP 字节,
// 若改成 UTF-8 同一物理路径会算出不同 ID,丢授权/备注)。
// - utf8Path: 发给服务端的 RES_FILE_PATH,与 CLIENT_CAP_UTF8 一致。
char buf[_MAX_PATH] = {};
GetModuleFileNameA(NULL, buf, sizeof(buf)); // CP_ACP, 留给 ID 计算用
wchar_t wbuf[_MAX_PATH] = {};
GetModuleFileNameW(NULL, wbuf, _MAX_PATH);
char utf8Path[_MAX_PATH * 3] = {}; // UTF-8 最多 3 字节/中文,给足
WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, utf8Path, sizeof(utf8Path), NULL, NULL);
LoginInfor.AddReserved(utf8Path); // 文件路径 (UTF-8 发给服务端显示)
LoginInfor.AddReserved("?"); // test
std::string installTime = cfg.GetStr("settings", "install_time");
if (installTime.empty()) {
installTime = ToPekingTimeAsString(nullptr);
cfg.SetStr("settings", "install_time", installTime);
}
LoginInfor.AddReserved(installTime.c_str()); // 安装时间
LoginInfor.AddReserved("?"); // 安装信息
LoginInfor.AddReserved(sizeof(void*)==4 ? 32 : 64); // 程序位数
std::string masterHash(skCrypt(MASTER_HASH));
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
GetFileAttributesExW(wbuf, GetFileExInfoStandard, &fileInfo);
LoginInfor.AddReserved(str.c_str()); // 授权信息
bool isDefault = strlen(conn.szFlag) == 0 || strcmp(conn.szFlag, skCrypt(FLAG_GHOST)) == 0 ||
strcmp(conn.szFlag, skCrypt("Happy New Year!")) == 0;
const char* id = isDefault ? masterHash.c_str() : conn.szFlag;
memcpy(LoginInfor.szMasterID, id, min(strlen(id), 16));
std::string loc = cfg.GetStr("settings", "location", "");
std::string pubIP = cfg.GetStr("settings", "public_ip", "");
auto ip_time = cfg.GetInt("settings", "ip_time");
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;
if (loc.empty() || pubIP.empty() || timeout || cacheVerChanged) {
std::string newPubIP = 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", "public_ip", pubIP);
cfg.SetInt("settings", "ip_time", time(0));
cfg.SetStr("settings", "loc_cache_ver", __DATE__);
}
}
LoginInfor.AddReserved(loc.c_str());
LoginInfor.AddReserved(pubIP.c_str());
LoginInfor.AddReserved(GetCurrentExeVersion().c_str());
BOOL IsRunningAsAdmin();
LoginInfor.AddReserved(GetCurrentUserNameA().c_str());
LoginInfor.AddReserved(IsRunningAsAdmin());
char cpuInfo[32];
sprintf(cpuInfo, "%dMHz", dwCPUMHz);
std::string clientID = cfg.GetStr("settings", "client_id");
if (clientID.empty()) {
// V2 ID 算法:MachineGuid + 主机名 + 归一化路径
// - 主机名兜底区分同镜像克隆机:MachineGuid 相同、路径相同仍能靠主机名区分。
// - 代价:改名会让 client_id 变化,丢该主机的备注/授权/历史;主机名非硬件唯一,重名仍撞。
// MachineGuid 读取失败的极端情况退化到老算法,保兼容。
std::string machineGuid = GetMachineGuidWindows();
if (!machineGuid.empty()) {
conn.clientID = CalcalateIDv2(machineGuid + "|" + szPCName, NormalizeExePathLower(buf), isAuth);
} else {
Mprintf("WARN: MachineGuid 读取失败,回退到老 ID 算法\n");
conn.clientID = CalcalateID({ pubIP, szPCName, LoginInfor.OsVerInfoEx, cpuInfo, buf });
}
cfg.SetStr("settings", "client_id", std::to_string(conn.clientID));
clientID = std::to_string(conn.clientID);
Mprintf("初始化此客户端的唯一标识为: %s\n", clientID.c_str());
}
else {
conn.clientID = std::stoull(clientID);
Mprintf("此客户端的唯一标识为: %s\n", clientID.c_str());
}
char reservedInfo[64];
int m_iScreenX = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int m_iScreenY = GetSystemMetrics(SM_CYVIRTUALSCREEN);
auto list = ScreenCapture::GetAllMonitors();
sprintf_s(reservedInfo, "%d:%d*%d", (int)list.size(), m_iScreenX, m_iScreenY);
LoginInfor.AddReserved(reservedInfo); // 屏幕分辨率
LoginInfor.AddReserved(clientID.c_str()); // 客户端路径
LoginInfor.AddReserved((int)GetCurrentProcessId()); // 进程ID
char fileSize[32];
double MB = fileInfo.nFileSizeLow > 1024 * 1024 ? fileInfo.nFileSizeLow / (1024.0 * 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");
LoginInfor.AddReserved(fileSize); // 文件大小
LoginInfor.AddReserved(TlsEnabled() ? 1 : 0); // TLS 启用标志:有信任锚(证书)即 1
return LoginInfor;
}
DWORD CPUClockMHz()
{
HKEY hKey;
DWORD dwCPUMHz;
DWORD dwReturn = sizeof(DWORD);
DWORD dwType = REG_DWORD;
RegOpenKey(HKEY_LOCAL_MACHINE,
"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", &hKey);
RegQueryValueEx(hKey, "~MHz", NULL, &dwType, (PBYTE)&dwCPUMHz, &dwReturn);
RegCloseKey(hKey);
return dwCPUMHz;
}
BOOL WebCamIsExist()
{
BOOL bOk = FALSE;
char szDeviceName[100], szVer[50];
for (int i = 0; i < 10 && !bOk; ++i) {
bOk = capGetDriverDescription(i, szDeviceName, sizeof(szDeviceName),
//系统的API函数
szVer, sizeof(szVer));
}
return bOk;
}