#pragma once #include #include #include #include #include #include #include #include #include #include // Forward declarations class context; class CMy2015RemoteDlg; class CONTEXT_OBJECT; // Web client state struct WebClient { std::string token; std::string username; std::string role; // "admin" | "viewer" uint64_t watch_device_id; // 0 = not watching any device uint64_t connected_at; uint64_t last_activity; // Last message/pong received (for heartbeat timeout) std::string client_ip; WebClient() : watch_device_id(0), connected_at(0), last_activity(0) {} }; // Login attempt tracking for rate limiting struct LoginAttempt { int failed_count; time_t locked_until; LoginAttempt() : failed_count(0), locked_until(0) {} }; // Web user account struct WebUser { std::string username; std::string password_hash; // SHA256(password + salt) std::string salt; std::string role; // "admin" | "viewer" std::vector allowed_groups; // Groups this user can view (empty = no access, admin = all) }; // Device info for web clients struct WebDeviceInfo { uint64_t id; std::string name; std::string ip; std::string os; int screen_width; int screen_height; bool online; // 当前会话的音频开关。-1=未知(客户端 BITMAPINFO 还没回来),0=关,1=开 int audio_enabled = -1; // Keyframe cache for new web clients std::vector keyframe_cache; std::mutex cache_mutex; WebDeviceInfo() : id(0), screen_width(0), screen_height(0), online(false) {} }; // Main Web Service class class CWebService { public: static CWebService& Instance(); // Lifecycle bool Start(int port = 8080); void Stop(); bool IsRunning() const; // Set parent dialog for device list access void SetParentDlg(CMy2015RemoteDlg* pDlg) { m_pParentDlg = pDlg; } // Set admin password (use master password) void SetAdminPassword(const std::string& password); // User management bool CreateUser(const std::string& username, const std::string& password, const std::string& role, const std::vector& allowed_groups = {}); bool DeleteUser(const std::string& username); std::vector> ListUsers(); // Returns [(username, role), ...] // Device management (called from main app) void MarkDeviceOnline(uint64_t device_id); void MarkDeviceOffline(uint64_t device_id); void FlushDeviceChanges(); // Called by timer to batch-notify web clients // H264 frame broadcasting (called from ScreenSpyDlg) void BroadcastFrame(uint64_t device_id, const uint8_t* data, size_t len, bool is_keyframe); void BroadcastH264Frame(uint64_t device_id, const uint8_t* data, size_t len); void CacheKeyframe(uint64_t device_id, const uint8_t* data, size_t len); // Resolution change notification void NotifyResolutionChange(uint64_t device_id, int width, int height); // Audio enable/disable notification — pushes current state to all web // clients watching this device and caches it for newcomers. void NotifyAudioState(uint64_t device_id, bool enabled); // Cursor change notification (called from ScreenSpyDlg) void BroadcastCursor(uint64_t device_id, uint8_t cursor_index); // Get count of web clients watching a device int GetWebClientCount(uint64_t device_id); // Start remote desktop session for web viewing bool StartRemoteDesktop(uint64_t device_id); // Stop remote desktop session void StopRemoteDesktop(uint64_t device_id); private: CWebService(); ~CWebService(); CWebService(const CWebService&) = delete; CWebService& operator=(const CWebService&) = delete; // Server thread void ServerThread(int port); // Signaling handlers void HandleLogin(void* ws_ptr, const std::string& msg, const std::string& client_ip); void HandleGetSalt(void* ws_ptr, const std::string& msg); void HandleGetDevices(void* ws_ptr, const std::string& token); 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); void HandlePing(void* ws_ptr, const std::string& token); void HandleMouse(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 HandleAudioToggle(void* ws_ptr, const std::string& token); // Token management std::string GenerateToken(const std::string& username, const std::string& role); bool ValidateToken(const std::string& token, std::string& username, std::string& role); // Client management void RegisterClient(void* ws_ptr, const std::string& client_ip); void UnregisterClient(void* ws_ptr); WebClient* FindClient(void* ws_ptr); // Rate limiting bool CheckRateLimit(const std::string& ip); void RecordFailedLogin(const std::string& ip); void RecordSuccessLogin(const std::string& ip); // JSON helpers std::string BuildJsonResponse(const std::string& cmd, bool ok, const std::string& msg = ""); std::string BuildDeviceListJson(const std::string& username = ""); // Password verification bool VerifyPassword(const std::string& input, const WebUser& user); std::string ComputeHash(const std::string& input); // User management helpers std::string GetUsersFilePath(); void LoadUsers(); void SaveUsers(); void HandleCreateUser(void* ws_ptr, const std::string& msg); void HandleDeleteUser(void* ws_ptr, const std::string& msg); void HandleListUsers(void* ws_ptr, const std::string& token); void HandleGetGroups(void* ws_ptr, const std::string& token); // Send to WebSocket void SendText(void* ws_ptr, const std::string& text); void SendBinary(void* ws_ptr, const uint8_t* data, size_t len); // Build binary frame packet std::vector BuildFramePacket(uint64_t device_id, bool is_keyframe, const uint8_t* data, size_t len); private: // Server state std::thread m_ServerThread; std::thread m_HeartbeatThread; // Heartbeat checker thread std::atomic m_bRunning; std::atomic m_bStopping; void* m_pServer; // ws::Server* // Heartbeat settings 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 // Heartbeat thread function void HeartbeatThread(); // Parent dialog for device access CMy2015RemoteDlg* m_pParentDlg; // Web clients: ws_ptr -> WebClient std::map m_Clients; std::mutex m_ClientsMutex; // Device keyframe cache: device_id -> cache std::map> m_DeviceCache; std::mutex m_DeviceCacheMutex; // Login rate limiting: ip -> LoginAttempt std::map m_LoginAttempts; std::mutex m_LoginMutex; // User accounts (loaded from config) std::vector m_Users; std::mutex m_UsersMutex; // Token secret key (generated on startup) std::string m_SecretKey; // Config int m_nMaxClientsPerDevice; int m_nTokenExpireSeconds; bool m_bHideWebSessions; // Whether to hide web-triggered dialogs (default: true) std::string m_PayloadsDir; // Directory for file downloads (Payloads/) std::string m_ConfigDir; // Directory for config files (users.json, etc.) // Web-triggered sessions (should be hidden) std::set m_WebTriggeredDevices; std::mutex m_WebTriggeredMutex; // Dirty device tracking for batch notifications std::set m_OnlineDevices; // Devices that came online since last flush std::set m_OfflineDevices; // Devices that went offline since last flush std::mutex m_DirtyDevicesMutex; public: // Check if a device session was triggered by web (should be hidden) bool IsWebTriggered(uint64_t device_id); void ClearWebTriggered(uint64_t device_id); // MFC trigger management - MFC dialogs should always be visible void SetMfcTriggered(uint64_t device_id); bool IsMfcTriggered(uint64_t device_id); void ClearMfcTriggered(uint64_t device_id); // Check if a remote desktop session already exists for device bool HasActiveSession(uint64_t device_id); // Config accessors void SetHideWebSessions(bool hide) { m_bHideWebSessions = hide; } bool GetHideWebSessions() const { return m_bHideWebSessions; } // Real-time device updates void NotifyDeviceUpdate(uint64_t device_id, const std::string& rtt, const std::string& activeWindow); // Screen context registry (for mouse/keyboard control) void RegisterScreenContext(uint64_t device_id, CONTEXT_OBJECT* ctx); void UnregisterScreenContext(uint64_t device_id); CONTEXT_OBJECT* GetScreenContext(uint64_t device_id); // ========== Web Terminal (Phase 1: 1 user per device) ========== // Web 终端会话桥:把浏览器端 xterm.js ↔ 客户端 shell 子上下文连起来。 // 设计:每台主机最多一个 Web 终端会话;如果别的浏览器请求同一台主机的终端, // 拒绝(UX 上后续可改成共享只读)。 // 生命周期:term_open → COMMAND_SHELL → 客户端建子上下文 → MessageHandle // 看到 TOKEN_TERMINAL_START / TOKEN_SHELL_START + IsTermPending(d) → // 调 RegisterTerminalContext 接管,跳过 MFC dialog 打开。 // 浏览器侧入口 void HandleTermOpen(void* ws_ptr, const std::string& msg); void HandleTermInput(void* ws_ptr, const std::string& msg); void HandleTermResize(void* ws_ptr, const std::string& msg); void HandleTermClose(void* ws_ptr, const std::string& msg); // MessageHandle 向 WebService 询问 / 移交的钩子 bool IsTermPending(uint64_t device_id); // 决定是否要拦截 dialog 打开 void RegisterTerminalContext(uint64_t device_id, CONTEXT_OBJECT* ctx, bool isPty); bool IsTerminalContext(CONTEXT_OBJECT* ctx); // 是否是 Web 终端持有的上下文 void OnTerminalData(CONTEXT_OBJECT* ctx, const BYTE* data, ULONG len);// 把 shell 输出泵到对应 web client void OnTerminalClosed(CONTEXT_OBJECT* ctx); // shell 子上下文断开时清理 private: // Screen context registry: device_id -> ScreenManager's CONTEXT_OBJECT std::map m_ScreenContexts; std::mutex m_ScreenContextsMutex; // MFC triggered devices: dialogs created by MFC should always be visible std::set m_MfcTriggeredDevices; std::mutex m_MfcTriggeredMutex; // Web 终端会话状态 struct WebTermSession { void* ws_ptr; // browser WebSocket uint64_t device_id; CONTEXT_OBJECT* shell_ctx; // shell 子上下文(首条消息抵达后才填) bool is_pty; // true=TOKEN_TERMINAL(现代 PTY), false=TOKEN_SHELL(老 cmd 管道) }; std::map m_TermSessions; // by device_id std::map m_TermContextToDevice; // 反查 ctx → device_id std::set m_TermPending; // 已发 COMMAND_SHELL 待响应 std::mutex m_TermMutex; // 内部清理(已持锁版本) void CloseTermSessionLocked(uint64_t device_id); }; // Global accessor inline CWebService& WebService() { return CWebService::Instance(); }