mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-04-24 04:03:54 +08:00
2022 lines
67 KiB
C++
2022 lines
67 KiB
C++
#include "service_host.h"
|
|
|
|
#include <TlHelp32.h>
|
|
#include <Userenv.h>
|
|
#include <WtsApi32.h>
|
|
#include <sddl.h>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
#include <nlohmann/json.hpp>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "path_manager.h"
|
|
#include "rd_log.h"
|
|
#include "session_helper_shared.h"
|
|
|
|
namespace crossdesk {
|
|
|
|
namespace {
|
|
|
|
using Json = nlohmann::json;
|
|
|
|
constexpr char kSecureDesktopKeyboardIpcCommandPrefix[] = "secure-input-key:";
|
|
constexpr char kSecureDesktopMouseIpcCommandPrefix[] = "secure-input-mouse:";
|
|
|
|
using SendSasFunction = VOID(WINAPI*)(BOOL);
|
|
|
|
struct SasResult {
|
|
bool success = false;
|
|
DWORD error_code = 0;
|
|
std::string error;
|
|
};
|
|
|
|
struct InputDesktopInfo {
|
|
bool available = false;
|
|
DWORD error_code = 0;
|
|
std::string name;
|
|
};
|
|
|
|
struct ScopedEnvironmentBlock {
|
|
~ScopedEnvironmentBlock() {
|
|
if (environment != nullptr) {
|
|
DestroyEnvironmentBlock(environment);
|
|
}
|
|
}
|
|
|
|
LPVOID environment = nullptr;
|
|
};
|
|
|
|
std::string WideToUtf8(const std::wstring& value);
|
|
|
|
std::wstring GetCurrentExecutablePathW() {
|
|
wchar_t path[MAX_PATH] = {0};
|
|
DWORD length = GetModuleFileNameW(nullptr, path, MAX_PATH);
|
|
if (length == 0 || length >= MAX_PATH) {
|
|
return L"";
|
|
}
|
|
return std::wstring(path, length);
|
|
}
|
|
|
|
std::wstring QuoteWindowsArgument(const std::wstring& value) {
|
|
std::wstring escaped = L"\"";
|
|
for (wchar_t character : value) {
|
|
if (character == L'\"') {
|
|
escaped += L"\\\"";
|
|
continue;
|
|
}
|
|
escaped += character;
|
|
}
|
|
escaped += L"\"";
|
|
return escaped;
|
|
}
|
|
|
|
void InitializeServiceLogger() {
|
|
static std::once_flag once_flag;
|
|
std::call_once(once_flag, []() {
|
|
PathManager path_manager("CrossDesk");
|
|
std::filesystem::path log_path = path_manager.GetLogPath() / "service";
|
|
if (!log_path.empty() && path_manager.CreateDirectories(log_path)) {
|
|
InitLogger(log_path.string());
|
|
return;
|
|
}
|
|
InitLogger("logs/service");
|
|
});
|
|
}
|
|
|
|
std::string EscapeJsonString(const std::string& value) {
|
|
std::string escaped;
|
|
escaped.reserve(value.size());
|
|
for (char character : value) {
|
|
switch (character) {
|
|
case '\\':
|
|
escaped += "\\\\";
|
|
break;
|
|
case '"':
|
|
escaped += "\\\"";
|
|
break;
|
|
case '\b':
|
|
escaped += "\\b";
|
|
break;
|
|
case '\f':
|
|
escaped += "\\f";
|
|
break;
|
|
case '\n':
|
|
escaped += "\\n";
|
|
break;
|
|
case '\r':
|
|
escaped += "\\r";
|
|
break;
|
|
case '\t':
|
|
escaped += "\\t";
|
|
break;
|
|
default:
|
|
escaped += character;
|
|
break;
|
|
}
|
|
}
|
|
return escaped;
|
|
}
|
|
|
|
std::string Trim(const std::string& value) {
|
|
size_t begin = 0;
|
|
while (begin < value.size() &&
|
|
std::isspace(static_cast<unsigned char>(value[begin])) != 0) {
|
|
++begin;
|
|
}
|
|
|
|
size_t end = value.size();
|
|
while (end > begin &&
|
|
std::isspace(static_cast<unsigned char>(value[end - 1])) != 0) {
|
|
--end;
|
|
}
|
|
|
|
return value.substr(begin, end - begin);
|
|
}
|
|
|
|
std::string ToLower(std::string value) {
|
|
std::transform(value.begin(), value.end(), value.begin(),
|
|
[](unsigned char character) {
|
|
return static_cast<char>(std::tolower(character));
|
|
});
|
|
return value;
|
|
}
|
|
|
|
std::string BuildErrorJson(const char* error, DWORD error_code = 0) {
|
|
std::ostringstream stream;
|
|
stream << "{\"ok\":false,\"error\":\"" << error << "\"";
|
|
if (error_code != 0) {
|
|
stream << ",\"code\":" << error_code;
|
|
}
|
|
stream << "}";
|
|
return stream.str();
|
|
}
|
|
|
|
std::string QueryNamedPipeMessage(const std::wstring& pipe_name,
|
|
const std::string& command,
|
|
DWORD timeout_ms) {
|
|
constexpr int kPipeConnectRetryCount = 3;
|
|
constexpr DWORD kPipeConnectRetryDelayMs = 15;
|
|
|
|
auto is_transient_pipe_error = [](DWORD error) {
|
|
return error == ERROR_FILE_NOT_FOUND || error == ERROR_PIPE_BUSY ||
|
|
error == ERROR_SEM_TIMEOUT;
|
|
};
|
|
|
|
HANDLE pipe = INVALID_HANDLE_VALUE;
|
|
for (int attempt = 0; attempt < kPipeConnectRetryCount; ++attempt) {
|
|
if (!WaitNamedPipeW(pipe_name.c_str(), timeout_ms)) {
|
|
const DWORD error = GetLastError();
|
|
if (attempt + 1 < kPipeConnectRetryCount &&
|
|
is_transient_pipe_error(error)) {
|
|
Sleep(kPipeConnectRetryDelayMs);
|
|
continue;
|
|
}
|
|
return BuildErrorJson("pipe_unavailable", error);
|
|
}
|
|
|
|
pipe = CreateFileW(pipe_name.c_str(), GENERIC_READ | GENERIC_WRITE, 0,
|
|
nullptr, OPEN_EXISTING, 0, nullptr);
|
|
if (pipe != INVALID_HANDLE_VALUE) {
|
|
break;
|
|
}
|
|
|
|
const DWORD error = GetLastError();
|
|
if (attempt + 1 < kPipeConnectRetryCount &&
|
|
is_transient_pipe_error(error)) {
|
|
Sleep(kPipeConnectRetryDelayMs);
|
|
continue;
|
|
}
|
|
return BuildErrorJson("pipe_connect_failed", error);
|
|
}
|
|
|
|
DWORD pipe_mode = PIPE_READMODE_MESSAGE;
|
|
SetNamedPipeHandleState(pipe, &pipe_mode, nullptr, nullptr);
|
|
|
|
DWORD bytes_written = 0;
|
|
if (!WriteFile(pipe, command.data(), static_cast<DWORD>(command.size()),
|
|
&bytes_written, nullptr)) {
|
|
std::string error = BuildErrorJson("pipe_write_failed", GetLastError());
|
|
CloseHandle(pipe);
|
|
return error;
|
|
}
|
|
|
|
char buffer[4096] = {0};
|
|
DWORD bytes_read = 0;
|
|
if (!ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr)) {
|
|
std::string error = BuildErrorJson("pipe_read_failed", GetLastError());
|
|
CloseHandle(pipe);
|
|
return error;
|
|
}
|
|
|
|
CloseHandle(pipe);
|
|
return std::string(buffer, buffer + bytes_read);
|
|
}
|
|
|
|
std::string BuildSecureDesktopKeyboardIpcCommand(int key_code, bool is_down) {
|
|
std::ostringstream stream;
|
|
stream << kSecureDesktopKeyboardIpcCommandPrefix << key_code << ":"
|
|
<< (is_down ? 1 : 0);
|
|
return stream.str();
|
|
}
|
|
|
|
std::string BuildSecureDesktopMouseIpcCommand(int x, int y, int wheel,
|
|
int flag) {
|
|
std::ostringstream stream;
|
|
stream << kSecureDesktopMouseIpcCommandPrefix << x << ":" << y << ":" << wheel
|
|
<< ":" << flag;
|
|
return stream.str();
|
|
}
|
|
|
|
std::string BuildSecureInputHelperKeyboardCommand(int key_code, bool is_down) {
|
|
std::ostringstream stream;
|
|
stream << kCrossDeskSecureInputKeyboardCommandPrefix << key_code << ":"
|
|
<< (is_down ? 1 : 0);
|
|
return stream.str();
|
|
}
|
|
|
|
bool ParseSecureDesktopKeyboardIpcCommand(const std::string& command,
|
|
int* key_code_out,
|
|
bool* is_down_out) {
|
|
if (key_code_out == nullptr || is_down_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (command.rfind(kSecureDesktopKeyboardIpcCommandPrefix, 0) != 0) {
|
|
return false;
|
|
}
|
|
|
|
const size_t key_begin = sizeof(kSecureDesktopKeyboardIpcCommandPrefix) - 1;
|
|
const size_t separator = command.find(':', key_begin);
|
|
if (separator == std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
*key_code_out = std::stoi(command.substr(key_begin, separator - key_begin));
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
const std::string state = command.substr(separator + 1);
|
|
if (state == "1" || state == "down") {
|
|
*is_down_out = true;
|
|
return true;
|
|
}
|
|
if (state == "0" || state == "up") {
|
|
*is_down_out = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CreateSessionSystemToken(DWORD session_id, HANDLE* token_out,
|
|
DWORD* error_code_out = nullptr) {
|
|
if (token_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
*token_out = nullptr;
|
|
if (error_code_out != nullptr) {
|
|
*error_code_out = 0;
|
|
}
|
|
|
|
HANDLE process_token = nullptr;
|
|
if (!OpenProcessToken(GetCurrentProcess(),
|
|
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY |
|
|
TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
|
&process_token)) {
|
|
if (error_code_out != nullptr) {
|
|
*error_code_out = GetLastError();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
HANDLE primary_token = nullptr;
|
|
BOOL duplicated = DuplicateTokenEx(
|
|
process_token,
|
|
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY |
|
|
TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
|
nullptr, SecurityImpersonation, TokenPrimary, &primary_token);
|
|
CloseHandle(process_token);
|
|
if (!duplicated) {
|
|
if (error_code_out != nullptr) {
|
|
*error_code_out = GetLastError();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!SetTokenInformation(primary_token, TokenSessionId, &session_id,
|
|
sizeof(session_id))) {
|
|
if (error_code_out != nullptr) {
|
|
*error_code_out = GetLastError();
|
|
}
|
|
CloseHandle(primary_token);
|
|
return false;
|
|
}
|
|
|
|
*token_out = primary_token;
|
|
return true;
|
|
}
|
|
|
|
const char* SessionEventToString(DWORD event_type) {
|
|
switch (event_type) {
|
|
case WTS_CONSOLE_CONNECT:
|
|
return "console-connect";
|
|
case WTS_CONSOLE_DISCONNECT:
|
|
return "console-disconnect";
|
|
case WTS_REMOTE_CONNECT:
|
|
return "remote-connect";
|
|
case WTS_REMOTE_DISCONNECT:
|
|
return "remote-disconnect";
|
|
case WTS_SESSION_LOGON:
|
|
return "session-logon";
|
|
case WTS_SESSION_LOGOFF:
|
|
return "session-logoff";
|
|
case WTS_SESSION_LOCK:
|
|
return "session-lock";
|
|
case WTS_SESSION_UNLOCK:
|
|
return "session-unlock";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
const char* DetermineInteractiveStage(bool lock_app_visible,
|
|
bool credential_ui_visible,
|
|
bool secure_desktop_active) {
|
|
if (credential_ui_visible) {
|
|
return "credential-ui";
|
|
}
|
|
if (lock_app_visible) {
|
|
return "lock-screen";
|
|
}
|
|
if (secure_desktop_active) {
|
|
return "secure-desktop";
|
|
}
|
|
return "user-desktop";
|
|
}
|
|
|
|
bool GetSessionUserName(DWORD session_id, std::wstring* username_out) {
|
|
if (username_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
username_out->clear();
|
|
LPWSTR username = nullptr;
|
|
DWORD bytes = 0;
|
|
if (!WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id,
|
|
WTSUserName, &username, &bytes)) {
|
|
return false;
|
|
}
|
|
|
|
if (username != nullptr) {
|
|
*username_out = username;
|
|
WTSFreeMemory(username);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QuerySessionLockState(DWORD session_id, bool* session_locked_out) {
|
|
if (session_locked_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
*session_locked_out = false;
|
|
PWTSINFOEXW session_info = nullptr;
|
|
DWORD bytes = 0;
|
|
if (!WTSQuerySessionInformationW(
|
|
WTS_CURRENT_SERVER_HANDLE, session_id, WTSSessionInfoEx,
|
|
reinterpret_cast<LPWSTR*>(&session_info), &bytes)) {
|
|
return false;
|
|
}
|
|
|
|
bool success = false;
|
|
if (session_info != nullptr && bytes >= sizeof(WTSINFOEXW) &&
|
|
session_info->Level == 1) {
|
|
const LONG session_flags = session_info->Data.WTSInfoExLevel1.SessionFlags;
|
|
if (session_flags == WTS_SESSIONSTATE_LOCK) {
|
|
*session_locked_out = true;
|
|
success = true;
|
|
} else if (session_flags == WTS_SESSIONSTATE_UNLOCK) {
|
|
*session_locked_out = false;
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
if (session_info != nullptr) {
|
|
WTSFreeMemory(session_info);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool IsLogonUiRunningInSession(DWORD session_id) {
|
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (snapshot == INVALID_HANDLE_VALUE) {
|
|
return false;
|
|
}
|
|
|
|
PROCESSENTRY32W entry{};
|
|
entry.dwSize = sizeof(entry);
|
|
bool found = false;
|
|
if (Process32FirstW(snapshot, &entry)) {
|
|
do {
|
|
if (_wcsicmp(entry.szExeFile, L"LogonUI.exe") != 0) {
|
|
continue;
|
|
}
|
|
|
|
DWORD process_session_id = 0xFFFFFFFF;
|
|
if (ProcessIdToSessionId(entry.th32ProcessID, &process_session_id) &&
|
|
process_session_id == session_id) {
|
|
found = true;
|
|
break;
|
|
}
|
|
} while (Process32NextW(snapshot, &entry));
|
|
}
|
|
|
|
CloseHandle(snapshot);
|
|
return found;
|
|
}
|
|
|
|
InputDesktopInfo GetInputDesktopInfo() {
|
|
InputDesktopInfo info;
|
|
HDESK desktop = OpenInputDesktop(0, FALSE, GENERIC_READ);
|
|
if (desktop == nullptr) {
|
|
info.error_code = GetLastError();
|
|
return info;
|
|
}
|
|
|
|
DWORD bytes_needed = 0;
|
|
GetUserObjectInformationW(desktop, UOI_NAME, nullptr, 0, &bytes_needed);
|
|
if (bytes_needed == 0) {
|
|
info.error_code = GetLastError();
|
|
CloseDesktop(desktop);
|
|
return info;
|
|
}
|
|
|
|
std::wstring desktop_name(bytes_needed / sizeof(wchar_t), L'\0');
|
|
if (!GetUserObjectInformationW(desktop, UOI_NAME, desktop_name.data(),
|
|
bytes_needed, &bytes_needed)) {
|
|
info.error_code = GetLastError();
|
|
CloseDesktop(desktop);
|
|
return info;
|
|
}
|
|
|
|
CloseDesktop(desktop);
|
|
while (!desktop_name.empty() && desktop_name.back() == L'\0') {
|
|
desktop_name.pop_back();
|
|
}
|
|
info.available = true;
|
|
info.name = WideToUtf8(desktop_name);
|
|
return info;
|
|
}
|
|
|
|
std::string WideToUtf8(const std::wstring& value) {
|
|
if (value.empty()) {
|
|
return {};
|
|
}
|
|
|
|
int size_needed = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr,
|
|
0, nullptr, nullptr);
|
|
if (size_needed <= 1) {
|
|
return {};
|
|
}
|
|
|
|
std::string result(static_cast<size_t>(size_needed), '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, result.data(), size_needed,
|
|
nullptr, nullptr);
|
|
result.pop_back();
|
|
return result;
|
|
}
|
|
|
|
bool QuerySoftwareSasGeneration(DWORD* value_out, bool* existed_out) {
|
|
if (value_out == nullptr || existed_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
*value_out = 0;
|
|
*existed_out = false;
|
|
HKEY key = nullptr;
|
|
constexpr wchar_t kPolicyKey[] =
|
|
L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
|
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, kPolicyKey, 0,
|
|
KEY_QUERY_VALUE | KEY_SET_VALUE, &key) != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
DWORD value = 0;
|
|
DWORD value_size = sizeof(value);
|
|
DWORD type = REG_DWORD;
|
|
LONG result = RegQueryValueExW(key, L"SoftwareSASGeneration", nullptr, &type,
|
|
reinterpret_cast<LPBYTE>(&value), &value_size);
|
|
if (result == ERROR_SUCCESS && type == REG_DWORD) {
|
|
*value_out = value;
|
|
*existed_out = true;
|
|
}
|
|
|
|
RegCloseKey(key);
|
|
return true;
|
|
}
|
|
|
|
bool SetSoftwareSasGeneration(DWORD value) {
|
|
HKEY key = nullptr;
|
|
constexpr wchar_t kPolicyKey[] =
|
|
L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
|
|
DWORD disposition = 0;
|
|
LONG result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, kPolicyKey, 0, nullptr, 0,
|
|
KEY_SET_VALUE, nullptr, &key, &disposition);
|
|
if (result != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
result = RegSetValueExW(key, L"SoftwareSASGeneration", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
|
RegCloseKey(key);
|
|
return result == ERROR_SUCCESS;
|
|
}
|
|
|
|
bool RestoreSoftwareSasGeneration(DWORD original_value, bool existed_before) {
|
|
HKEY key = nullptr;
|
|
constexpr wchar_t kPolicyKey[] =
|
|
L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
|
|
LONG result =
|
|
RegOpenKeyExW(HKEY_LOCAL_MACHINE, kPolicyKey, 0, KEY_SET_VALUE, &key);
|
|
if (result != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
if (!existed_before) {
|
|
result = RegDeleteValueW(key, L"SoftwareSASGeneration");
|
|
RegCloseKey(key);
|
|
return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
result = RegSetValueExW(key, L"SoftwareSASGeneration", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>(&original_value),
|
|
sizeof(original_value));
|
|
RegCloseKey(key);
|
|
return result == ERROR_SUCCESS;
|
|
}
|
|
|
|
SasResult SendSasNow() {
|
|
SasResult result;
|
|
HMODULE sas_module = LoadLibraryW(L"sas.dll");
|
|
if (sas_module == nullptr) {
|
|
result.error = "sas_dll_unavailable";
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
auto* send_sas =
|
|
reinterpret_cast<SendSasFunction>(GetProcAddress(sas_module, "SendSAS"));
|
|
if (send_sas == nullptr) {
|
|
result.error = "send_sas_proc_missing";
|
|
result.error_code = GetLastError();
|
|
FreeLibrary(sas_module);
|
|
return result;
|
|
}
|
|
|
|
DWORD original_value = 0;
|
|
bool existed_before = false;
|
|
bool queried = QuerySoftwareSasGeneration(&original_value, &existed_before);
|
|
bool mutated_policy = false;
|
|
if (queried) {
|
|
if (!existed_before || (original_value != 1 && original_value != 3)) {
|
|
if (!SetSoftwareSasGeneration(1)) {
|
|
result.error = "set_software_sas_generation_failed";
|
|
result.error_code = GetLastError();
|
|
FreeLibrary(sas_module);
|
|
return result;
|
|
}
|
|
mutated_policy = true;
|
|
}
|
|
}
|
|
|
|
send_sas(FALSE);
|
|
|
|
if (mutated_policy) {
|
|
RestoreSoftwareSasGeneration(original_value, existed_before);
|
|
}
|
|
|
|
FreeLibrary(sas_module);
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
struct PipeSecurityAttributes {
|
|
PipeSecurityAttributes() = default;
|
|
~PipeSecurityAttributes() {
|
|
if (security_descriptor_ != nullptr) {
|
|
LocalFree(security_descriptor_);
|
|
}
|
|
}
|
|
|
|
bool Initialize() {
|
|
constexpr wchar_t kPipeSddl[] = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;AU)";
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
|
kPipeSddl, SDDL_REVISION_1, &security_descriptor_, nullptr)) {
|
|
return false;
|
|
}
|
|
|
|
attributes_.nLength = sizeof(attributes_);
|
|
attributes_.lpSecurityDescriptor = security_descriptor_;
|
|
attributes_.bInheritHandle = FALSE;
|
|
return true;
|
|
}
|
|
|
|
SECURITY_ATTRIBUTES* get() { return &attributes_; }
|
|
|
|
private:
|
|
SECURITY_ATTRIBUTES attributes_{};
|
|
PSECURITY_DESCRIPTOR security_descriptor_ = nullptr;
|
|
};
|
|
|
|
struct KernelObjectSecurityAttributes {
|
|
KernelObjectSecurityAttributes() = default;
|
|
~KernelObjectSecurityAttributes() {
|
|
if (security_descriptor_ != nullptr) {
|
|
LocalFree(security_descriptor_);
|
|
}
|
|
}
|
|
|
|
bool Initialize() {
|
|
constexpr wchar_t kObjectSddl[] = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;AU)";
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
|
kObjectSddl, SDDL_REVISION_1, &security_descriptor_, nullptr)) {
|
|
return false;
|
|
}
|
|
|
|
attributes_.nLength = sizeof(attributes_);
|
|
attributes_.lpSecurityDescriptor = security_descriptor_;
|
|
attributes_.bInheritHandle = FALSE;
|
|
return true;
|
|
}
|
|
|
|
SECURITY_ATTRIBUTES* get() { return &attributes_; }
|
|
|
|
private:
|
|
SECURITY_ATTRIBUTES attributes_{};
|
|
PSECURITY_DESCRIPTOR security_descriptor_ = nullptr;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
CrossDeskServiceHost* CrossDeskServiceHost::instance_ = nullptr;
|
|
|
|
CrossDeskServiceHost::CrossDeskServiceHost() { InitializeServiceLogger(); }
|
|
|
|
CrossDeskServiceHost::~CrossDeskServiceHost() { ShutdownRuntime(); }
|
|
|
|
void WINAPI CrossDeskServiceHost::ServiceMain([[maybe_unused]] DWORD argc,
|
|
[[maybe_unused]] LPWSTR* argv) {
|
|
if (instance_ != nullptr) {
|
|
instance_->RunServiceLoop(true);
|
|
}
|
|
}
|
|
|
|
BOOL WINAPI CrossDeskServiceHost::ConsoleControlHandler(DWORD control_type) {
|
|
if (instance_ == nullptr) {
|
|
return FALSE;
|
|
}
|
|
|
|
switch (control_type) {
|
|
case CTRL_C_EVENT:
|
|
case CTRL_BREAK_EVENT:
|
|
case CTRL_CLOSE_EVENT:
|
|
case CTRL_SHUTDOWN_EVENT:
|
|
instance_->RequestStop();
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
DWORD WINAPI CrossDeskServiceHost::ServiceControlHandler(
|
|
DWORD control, DWORD event_type, LPVOID event_data,
|
|
[[maybe_unused]] LPVOID context) {
|
|
if (instance_ == nullptr) {
|
|
return ERROR_CALL_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
switch (control) {
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
instance_->ReportServiceStatus(instance_->service_status_.dwCurrentState,
|
|
NO_ERROR, 0);
|
|
return NO_ERROR;
|
|
case SERVICE_CONTROL_STOP:
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
instance_->ReportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 3000);
|
|
instance_->RequestStop();
|
|
return NO_ERROR;
|
|
case SERVICE_CONTROL_SESSIONCHANGE: {
|
|
DWORD session_id = 0xFFFFFFFF;
|
|
if (event_data != nullptr) {
|
|
auto* session = reinterpret_cast<WTSSESSION_NOTIFICATION*>(event_data);
|
|
session_id = session->dwSessionId;
|
|
}
|
|
instance_->RecordSessionEvent(event_type, session_id);
|
|
return NO_ERROR;
|
|
}
|
|
default:
|
|
return ERROR_CALL_NOT_IMPLEMENTED;
|
|
}
|
|
}
|
|
|
|
int CrossDeskServiceHost::RunAsService() {
|
|
instance_ = this;
|
|
|
|
SERVICE_TABLE_ENTRYW service_table[] = {
|
|
{const_cast<LPWSTR>(kCrossDeskServiceName),
|
|
&CrossDeskServiceHost::ServiceMain},
|
|
{nullptr, nullptr}};
|
|
|
|
if (!StartServiceCtrlDispatcherW(service_table)) {
|
|
DWORD error = GetLastError();
|
|
LOG_ERROR("StartServiceCtrlDispatcherW failed, error={}", error);
|
|
return static_cast<int>(error);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CrossDeskServiceHost::RunInConsole() {
|
|
instance_ = this;
|
|
return RunServiceLoop(false);
|
|
}
|
|
|
|
int CrossDeskServiceHost::InitializeRuntime() {
|
|
stop_event_ = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
if (stop_event_ == nullptr) {
|
|
DWORD error = GetLastError();
|
|
LOG_ERROR("CreateEventW failed, error={}", error);
|
|
return static_cast<int>(error);
|
|
}
|
|
|
|
started_at_tick_ = GetTickCount64();
|
|
last_sas_tick_ = 0;
|
|
active_session_id_ = WTSGetActiveConsoleSessionId();
|
|
process_session_id_ = 0xFFFFFFFF;
|
|
input_desktop_error_code_ = 0;
|
|
session_helper_process_id_ = 0;
|
|
session_helper_session_id_ = 0xFFFFFFFF;
|
|
session_helper_exit_code_ = 0;
|
|
session_helper_last_error_code_ = 0;
|
|
session_helper_status_error_code_ = 0;
|
|
session_helper_report_session_id_ = 0xFFFFFFFF;
|
|
session_helper_report_process_id_ = 0;
|
|
session_helper_report_input_desktop_error_code_ = 0;
|
|
secure_input_helper_process_id_ = 0;
|
|
secure_input_helper_session_id_ = 0xFFFFFFFF;
|
|
secure_input_helper_exit_code_ = 0;
|
|
secure_input_helper_last_error_code_ = 0;
|
|
session_locked_ = false;
|
|
logon_ui_visible_ = false;
|
|
prelogin_ = false;
|
|
secure_desktop_active_ = false;
|
|
input_desktop_available_ = false;
|
|
session_helper_running_ = false;
|
|
session_helper_status_ok_ = false;
|
|
session_helper_report_input_desktop_available_ = false;
|
|
session_helper_report_lock_app_visible_ = false;
|
|
session_helper_report_logon_ui_visible_ = false;
|
|
session_helper_report_secure_desktop_active_ = false;
|
|
session_helper_report_credential_ui_visible_ = false;
|
|
session_helper_report_unlock_ui_visible_ = false;
|
|
secure_input_helper_running_ = false;
|
|
last_sas_error_code_ = 0;
|
|
last_sas_success_ = false;
|
|
session_helper_started_at_tick_ = 0;
|
|
session_helper_report_state_age_ms_ = 0;
|
|
session_helper_report_uptime_ms_ = 0;
|
|
secure_input_helper_started_at_tick_ = 0;
|
|
session_helper_process_handle_ = nullptr;
|
|
session_helper_stop_event_ = nullptr;
|
|
secure_input_helper_process_handle_ = nullptr;
|
|
secure_input_helper_stop_event_ = nullptr;
|
|
input_desktop_name_.clear();
|
|
last_sas_error_.clear();
|
|
session_helper_last_error_.clear();
|
|
session_helper_status_error_.clear();
|
|
session_helper_report_input_desktop_.clear();
|
|
session_helper_report_interactive_stage_.clear();
|
|
secure_input_helper_last_error_.clear();
|
|
last_session_event_type_ = 0;
|
|
last_session_event_session_id_ = active_session_id_;
|
|
RefreshSessionState();
|
|
EnsureSessionHelper();
|
|
ipc_thread_ = std::thread(&CrossDeskServiceHost::IpcServerLoop, this);
|
|
LOG_INFO("CrossDesk service runtime initialized, session_id={}",
|
|
active_session_id_);
|
|
return 0;
|
|
}
|
|
|
|
void CrossDeskServiceHost::ShutdownRuntime() {
|
|
StopSecureInputHelper();
|
|
StopSessionHelper();
|
|
|
|
if (stop_event_ != nullptr) {
|
|
SetEvent(stop_event_);
|
|
}
|
|
|
|
if (ipc_thread_.joinable()) {
|
|
ipc_thread_.join();
|
|
}
|
|
|
|
if (stop_event_ != nullptr) {
|
|
CloseHandle(stop_event_);
|
|
stop_event_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void CrossDeskServiceHost::RequestStop() {
|
|
if (stop_event_ != nullptr) {
|
|
SetEvent(stop_event_);
|
|
}
|
|
}
|
|
|
|
void CrossDeskServiceHost::ReportServiceStatus(DWORD current_state,
|
|
DWORD win32_exit_code,
|
|
DWORD wait_hint) {
|
|
if (status_handle_ == nullptr) {
|
|
return;
|
|
}
|
|
|
|
service_status_.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
|
service_status_.dwCurrentState = current_state;
|
|
service_status_.dwWin32ExitCode = win32_exit_code;
|
|
service_status_.dwWaitHint = wait_hint;
|
|
service_status_.dwControlsAccepted =
|
|
current_state == SERVICE_RUNNING
|
|
? (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN |
|
|
SERVICE_ACCEPT_SESSIONCHANGE)
|
|
: 0;
|
|
SetServiceStatus(status_handle_, &service_status_);
|
|
}
|
|
|
|
int CrossDeskServiceHost::RunServiceLoop(bool as_service) {
|
|
console_mode_ = !as_service;
|
|
|
|
if (as_service) {
|
|
status_handle_ = RegisterServiceCtrlHandlerExW(
|
|
kCrossDeskServiceName, &CrossDeskServiceHost::ServiceControlHandler,
|
|
this);
|
|
if (status_handle_ == nullptr) {
|
|
DWORD error = GetLastError();
|
|
LOG_ERROR("RegisterServiceCtrlHandlerExW failed, error={}", error);
|
|
return static_cast<int>(error);
|
|
}
|
|
ReportServiceStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
}
|
|
|
|
int init_error = InitializeRuntime();
|
|
if (init_error != 0) {
|
|
if (as_service) {
|
|
ReportServiceStatus(SERVICE_STOPPED, static_cast<DWORD>(init_error), 0);
|
|
}
|
|
return init_error;
|
|
}
|
|
|
|
if (as_service) {
|
|
ReportServiceStatus(SERVICE_RUNNING, NO_ERROR, 0);
|
|
}
|
|
|
|
if (console_mode_) {
|
|
SetConsoleCtrlHandler(&CrossDeskServiceHost::ConsoleControlHandler, TRUE);
|
|
std::cout << "CrossDesk service skeleton running in console mode. Press "
|
|
"Ctrl+C to stop."
|
|
<< std::endl;
|
|
}
|
|
|
|
WaitForSingleObject(stop_event_, INFINITE);
|
|
|
|
if (as_service) {
|
|
ReportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 3000);
|
|
}
|
|
|
|
ShutdownRuntime();
|
|
|
|
if (console_mode_) {
|
|
SetConsoleCtrlHandler(&CrossDeskServiceHost::ConsoleControlHandler, FALSE);
|
|
}
|
|
|
|
if (as_service) {
|
|
ReportServiceStatus(SERVICE_STOPPED, NO_ERROR, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CrossDeskServiceHost::IpcServerLoop() {
|
|
PipeSecurityAttributes security_attributes;
|
|
SECURITY_ATTRIBUTES* pipe_attributes = nullptr;
|
|
if (security_attributes.Initialize()) {
|
|
pipe_attributes = security_attributes.get();
|
|
} else {
|
|
LOG_WARN("Pipe security initialization failed, error={}", GetLastError());
|
|
}
|
|
|
|
while (stop_event_ != nullptr &&
|
|
WaitForSingleObject(stop_event_, 0) != WAIT_OBJECT_0) {
|
|
HANDLE pipe = CreateNamedPipeW(
|
|
kCrossDeskServicePipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 4096, 4096, 0,
|
|
pipe_attributes);
|
|
if (pipe == INVALID_HANDLE_VALUE) {
|
|
DWORD error = GetLastError();
|
|
LOG_ERROR("CreateNamedPipeW failed, error={}", error);
|
|
WaitForSingleObject(stop_event_, 1000);
|
|
continue;
|
|
}
|
|
|
|
OVERLAPPED overlapped{};
|
|
overlapped.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
if (overlapped.hEvent == nullptr) {
|
|
LOG_ERROR("CreateEventW for pipe failed, error={}", GetLastError());
|
|
CloseHandle(pipe);
|
|
break;
|
|
}
|
|
|
|
BOOL connected = ConnectNamedPipe(pipe, &overlapped);
|
|
DWORD connect_error = connected ? ERROR_SUCCESS : GetLastError();
|
|
if (connected) {
|
|
SetEvent(overlapped.hEvent);
|
|
}
|
|
if (!connected && connect_error == ERROR_PIPE_CONNECTED) {
|
|
SetEvent(overlapped.hEvent);
|
|
connected = TRUE;
|
|
}
|
|
|
|
if (!connected && connect_error != ERROR_IO_PENDING) {
|
|
LOG_WARN("ConnectNamedPipe failed, error={}", connect_error);
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
continue;
|
|
}
|
|
|
|
if (!connected) {
|
|
HANDLE wait_handles[] = {stop_event_, overlapped.hEvent};
|
|
DWORD wait_result =
|
|
WaitForMultipleObjects(2, wait_handles, FALSE, INFINITE);
|
|
if (wait_result == WAIT_OBJECT_0) {
|
|
CancelIoEx(pipe, &overlapped);
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
break;
|
|
}
|
|
}
|
|
|
|
char buffer[1024] = {0};
|
|
DWORD bytes_read = 0;
|
|
if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) &&
|
|
bytes_read > 0) {
|
|
std::string response =
|
|
HandleIpcCommand(std::string(buffer, buffer + bytes_read));
|
|
DWORD bytes_written = 0;
|
|
WriteFile(pipe, response.data(), static_cast<DWORD>(response.size()),
|
|
&bytes_written, nullptr);
|
|
FlushFileBuffers(pipe);
|
|
}
|
|
|
|
DisconnectNamedPipe(pipe);
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
}
|
|
}
|
|
|
|
void CrossDeskServiceHost::RefreshSessionState() {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
active_session_id_ = WTSGetActiveConsoleSessionId();
|
|
DWORD process_session_id = 0xFFFFFFFF;
|
|
if (ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id)) {
|
|
process_session_id_ = process_session_id;
|
|
}
|
|
logon_ui_visible_ = IsLogonUiRunningInSession(active_session_id_);
|
|
InputDesktopInfo desktop_info = GetInputDesktopInfo();
|
|
input_desktop_available_ = desktop_info.available;
|
|
input_desktop_error_code_ = desktop_info.error_code;
|
|
input_desktop_name_ = desktop_info.name;
|
|
secure_desktop_active_ =
|
|
_stricmp(input_desktop_name_.c_str(), "Winlogon") == 0;
|
|
|
|
std::wstring username;
|
|
bool username_available = GetSessionUserName(active_session_id_, &username);
|
|
prelogin_ = !username_available || username.empty();
|
|
if (!QuerySessionLockState(active_session_id_, &session_locked_)) {
|
|
session_locked_ =
|
|
(logon_ui_visible_ || secure_desktop_active_) && !prelogin_;
|
|
}
|
|
}
|
|
|
|
void CrossDeskServiceHost::ResetSessionHelperReportedStateLocked(
|
|
const char* error, DWORD error_code) {
|
|
session_helper_status_ok_ = false;
|
|
session_helper_status_error_ = error != nullptr ? error : "";
|
|
session_helper_status_error_code_ = error_code;
|
|
session_helper_report_session_id_ = 0xFFFFFFFF;
|
|
session_helper_report_process_id_ = 0;
|
|
session_helper_report_session_locked_ = false;
|
|
session_helper_report_input_desktop_available_ = false;
|
|
session_helper_report_input_desktop_error_code_ = 0;
|
|
session_helper_report_input_desktop_.clear();
|
|
session_helper_report_lock_app_visible_ = false;
|
|
session_helper_report_logon_ui_visible_ = false;
|
|
session_helper_report_secure_desktop_active_ = false;
|
|
session_helper_report_credential_ui_visible_ = false;
|
|
session_helper_report_unlock_ui_visible_ = false;
|
|
session_helper_report_interactive_stage_.clear();
|
|
session_helper_report_state_age_ms_ = 0;
|
|
session_helper_report_uptime_ms_ = 0;
|
|
}
|
|
|
|
bool CrossDeskServiceHost::GetEffectiveSessionLockedLocked() const {
|
|
return session_helper_status_ok_ ? session_helper_report_session_locked_
|
|
: session_locked_;
|
|
}
|
|
|
|
bool CrossDeskServiceHost::IsHelperReportingLockScreenLocked() const {
|
|
return session_helper_report_lock_app_visible_ ||
|
|
session_helper_report_interactive_stage_ == "lock-screen";
|
|
}
|
|
|
|
bool CrossDeskServiceHost::HasSecureInputUiLocked() const {
|
|
return prelogin_ || secure_desktop_active_ || logon_ui_visible_ ||
|
|
session_helper_report_credential_ui_visible_ ||
|
|
session_helper_report_secure_desktop_active_ ||
|
|
session_helper_report_unlock_ui_visible_ ||
|
|
session_helper_report_interactive_stage_ == "credential-ui" ||
|
|
session_helper_report_interactive_stage_ == "secure-desktop";
|
|
}
|
|
|
|
bool CrossDeskServiceHost::ShouldKeepSecureInputHelperLocked(
|
|
DWORD target_session_id) const {
|
|
if (target_session_id == 0xFFFFFFFF) {
|
|
return false;
|
|
}
|
|
|
|
return HasSecureInputUiLocked() || (GetEffectiveSessionLockedLocked() &&
|
|
IsHelperReportingLockScreenLocked());
|
|
}
|
|
|
|
std::wstring CrossDeskServiceHost::GetSessionHelperPath() const {
|
|
std::wstring current_executable = GetCurrentExecutablePathW();
|
|
if (current_executable.empty()) {
|
|
return L"";
|
|
}
|
|
|
|
return (std::filesystem::path(current_executable).parent_path() /
|
|
L"crossdesk_session_helper.exe")
|
|
.wstring();
|
|
}
|
|
|
|
std::wstring CrossDeskServiceHost::GetSessionHelperStopEventName(
|
|
DWORD session_id) const {
|
|
return L"Global\\CrossDeskSessionHelperStop-" + std::to_wstring(session_id);
|
|
}
|
|
|
|
std::wstring CrossDeskServiceHost::GetSecureInputHelperPath() const {
|
|
return GetSessionHelperPath();
|
|
}
|
|
|
|
std::wstring CrossDeskServiceHost::GetSecureInputHelperStopEventName(
|
|
DWORD session_id) const {
|
|
return L"Global\\CrossDeskSecureInputHelperStop-" +
|
|
std::to_wstring(session_id);
|
|
}
|
|
|
|
void CrossDeskServiceHost::ReapSessionHelper() {
|
|
HANDLE process_handle = nullptr;
|
|
HANDLE stop_event_handle = nullptr;
|
|
DWORD exit_code = 0;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
if (session_helper_process_handle_ == nullptr) {
|
|
return;
|
|
}
|
|
|
|
DWORD wait_result = WaitForSingleObject(session_helper_process_handle_, 0);
|
|
if (wait_result != WAIT_OBJECT_0) {
|
|
session_helper_running_ = true;
|
|
return;
|
|
}
|
|
|
|
GetExitCodeProcess(session_helper_process_handle_, &exit_code);
|
|
process_handle = session_helper_process_handle_;
|
|
stop_event_handle = session_helper_stop_event_;
|
|
session_helper_process_handle_ = nullptr;
|
|
session_helper_stop_event_ = nullptr;
|
|
session_helper_running_ = false;
|
|
session_helper_process_id_ = 0;
|
|
session_helper_exit_code_ = exit_code;
|
|
session_helper_started_at_tick_ = 0;
|
|
}
|
|
|
|
if (process_handle != nullptr) {
|
|
CloseHandle(process_handle);
|
|
}
|
|
if (stop_event_handle != nullptr) {
|
|
CloseHandle(stop_event_handle);
|
|
}
|
|
}
|
|
|
|
void CrossDeskServiceHost::ReapSecureInputHelper() {
|
|
HANDLE process_handle = nullptr;
|
|
HANDLE stop_event_handle = nullptr;
|
|
DWORD exit_code = 0;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
if (secure_input_helper_process_handle_ == nullptr) {
|
|
return;
|
|
}
|
|
|
|
DWORD wait_result =
|
|
WaitForSingleObject(secure_input_helper_process_handle_, 0);
|
|
if (wait_result != WAIT_OBJECT_0) {
|
|
secure_input_helper_running_ = true;
|
|
return;
|
|
}
|
|
|
|
GetExitCodeProcess(secure_input_helper_process_handle_, &exit_code);
|
|
process_handle = secure_input_helper_process_handle_;
|
|
stop_event_handle = secure_input_helper_stop_event_;
|
|
secure_input_helper_process_handle_ = nullptr;
|
|
secure_input_helper_stop_event_ = nullptr;
|
|
secure_input_helper_running_ = false;
|
|
secure_input_helper_process_id_ = 0;
|
|
secure_input_helper_exit_code_ = exit_code;
|
|
secure_input_helper_started_at_tick_ = 0;
|
|
}
|
|
|
|
if (process_handle != nullptr) {
|
|
CloseHandle(process_handle);
|
|
}
|
|
if (stop_event_handle != nullptr) {
|
|
CloseHandle(stop_event_handle);
|
|
}
|
|
}
|
|
|
|
void CrossDeskServiceHost::StopSessionHelper() {
|
|
HANDLE process_handle = nullptr;
|
|
HANDLE stop_event_handle = nullptr;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
process_handle = session_helper_process_handle_;
|
|
stop_event_handle = session_helper_stop_event_;
|
|
session_helper_process_handle_ = nullptr;
|
|
session_helper_stop_event_ = nullptr;
|
|
session_helper_running_ = false;
|
|
session_helper_process_id_ = 0;
|
|
session_helper_started_at_tick_ = 0;
|
|
ResetSessionHelperReportedStateLocked(nullptr, 0);
|
|
}
|
|
|
|
if (stop_event_handle != nullptr) {
|
|
SetEvent(stop_event_handle);
|
|
}
|
|
|
|
if (process_handle != nullptr) {
|
|
if (WaitForSingleObject(process_handle, 3000) == WAIT_TIMEOUT) {
|
|
TerminateProcess(process_handle, ERROR_PROCESS_ABORTED);
|
|
WaitForSingleObject(process_handle, 1000);
|
|
}
|
|
}
|
|
|
|
if (process_handle != nullptr) {
|
|
CloseHandle(process_handle);
|
|
}
|
|
if (stop_event_handle != nullptr) {
|
|
CloseHandle(stop_event_handle);
|
|
}
|
|
}
|
|
|
|
void CrossDeskServiceHost::StopSecureInputHelper() {
|
|
HANDLE process_handle = nullptr;
|
|
HANDLE stop_event_handle = nullptr;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
process_handle = secure_input_helper_process_handle_;
|
|
stop_event_handle = secure_input_helper_stop_event_;
|
|
secure_input_helper_process_handle_ = nullptr;
|
|
secure_input_helper_stop_event_ = nullptr;
|
|
secure_input_helper_running_ = false;
|
|
secure_input_helper_process_id_ = 0;
|
|
secure_input_helper_started_at_tick_ = 0;
|
|
}
|
|
|
|
if (stop_event_handle != nullptr) {
|
|
SetEvent(stop_event_handle);
|
|
}
|
|
|
|
if (process_handle != nullptr) {
|
|
if (WaitForSingleObject(process_handle, 3000) == WAIT_TIMEOUT) {
|
|
TerminateProcess(process_handle, ERROR_PROCESS_ABORTED);
|
|
WaitForSingleObject(process_handle, 1000);
|
|
}
|
|
}
|
|
|
|
if (process_handle != nullptr) {
|
|
CloseHandle(process_handle);
|
|
}
|
|
if (stop_event_handle != nullptr) {
|
|
CloseHandle(stop_event_handle);
|
|
}
|
|
}
|
|
|
|
bool CrossDeskServiceHost::LaunchSessionHelper(DWORD session_id) {
|
|
std::wstring helper_path = GetSessionHelperPath();
|
|
if (helper_path.empty() || !std::filesystem::exists(helper_path)) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_last_error_ = "helper_binary_missing";
|
|
session_helper_last_error_code_ = ERROR_FILE_NOT_FOUND;
|
|
return false;
|
|
}
|
|
|
|
std::wstring stop_event_name = GetSessionHelperStopEventName(session_id);
|
|
KernelObjectSecurityAttributes event_security;
|
|
SECURITY_ATTRIBUTES* event_attributes = nullptr;
|
|
if (event_security.Initialize()) {
|
|
event_attributes = event_security.get();
|
|
}
|
|
|
|
HANDLE stop_event_handle =
|
|
CreateEventW(event_attributes, TRUE, FALSE, stop_event_name.c_str());
|
|
if (stop_event_handle == nullptr) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_last_error_ = "create_helper_stop_event_failed";
|
|
session_helper_last_error_code_ = GetLastError();
|
|
return false;
|
|
}
|
|
|
|
std::wstring command_line = QuoteWindowsArgument(helper_path) +
|
|
L" --session-helper --session-id " +
|
|
std::to_wstring(session_id) + L" --stop-event " +
|
|
QuoteWindowsArgument(stop_event_name);
|
|
std::wstring mutable_command_line = command_line;
|
|
|
|
STARTUPINFOW startup_info{};
|
|
startup_info.cb = sizeof(startup_info);
|
|
startup_info.lpDesktop = const_cast<LPWSTR>(L"winsta0\\default");
|
|
PROCESS_INFORMATION process_info{};
|
|
BOOL created = FALSE;
|
|
|
|
if (console_mode_ && process_session_id_ == session_id) {
|
|
created = CreateProcessW(helper_path.c_str(), mutable_command_line.data(),
|
|
nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr,
|
|
nullptr, &startup_info, &process_info);
|
|
} else {
|
|
HANDLE user_token = nullptr;
|
|
HANDLE primary_token = nullptr;
|
|
ScopedEnvironmentBlock environment_block;
|
|
|
|
if (!WTSQueryUserToken(session_id, &user_token)) {
|
|
DWORD error = GetLastError();
|
|
CloseHandle(stop_event_handle);
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_last_error_ = "wts_query_user_token_failed";
|
|
session_helper_last_error_code_ = error;
|
|
return false;
|
|
}
|
|
|
|
BOOL duplicated = DuplicateTokenEx(
|
|
user_token,
|
|
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY |
|
|
TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
|
nullptr, SecurityImpersonation, TokenPrimary, &primary_token);
|
|
CloseHandle(user_token);
|
|
if (!duplicated) {
|
|
DWORD error = GetLastError();
|
|
CloseHandle(stop_event_handle);
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_last_error_ = "duplicate_token_failed";
|
|
session_helper_last_error_code_ = error;
|
|
return false;
|
|
}
|
|
|
|
CreateEnvironmentBlock(&environment_block.environment, primary_token,
|
|
FALSE);
|
|
created = CreateProcessAsUserW(
|
|
primary_token, helper_path.c_str(), mutable_command_line.data(),
|
|
nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW,
|
|
environment_block.environment, nullptr, &startup_info, &process_info);
|
|
DWORD error = created ? ERROR_SUCCESS : GetLastError();
|
|
CloseHandle(primary_token);
|
|
if (!created) {
|
|
CloseHandle(stop_event_handle);
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_last_error_ = "create_process_as_user_failed";
|
|
session_helper_last_error_code_ = error;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CloseHandle(process_info.hThread);
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_process_handle_ = process_info.hProcess;
|
|
session_helper_stop_event_ = stop_event_handle;
|
|
session_helper_process_id_ = process_info.dwProcessId;
|
|
session_helper_session_id_ = session_id;
|
|
session_helper_exit_code_ = STILL_ACTIVE;
|
|
session_helper_last_error_code_ = 0;
|
|
session_helper_last_error_.clear();
|
|
session_helper_running_ = true;
|
|
session_helper_started_at_tick_ = GetTickCount64();
|
|
}
|
|
|
|
LOG_INFO("Session helper started: session_id={}, pid={}", session_id,
|
|
process_info.dwProcessId);
|
|
return true;
|
|
}
|
|
|
|
bool CrossDeskServiceHost::LaunchSecureInputHelper(DWORD session_id) {
|
|
std::wstring helper_path = GetSecureInputHelperPath();
|
|
if (helper_path.empty() || !std::filesystem::exists(helper_path)) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
secure_input_helper_last_error_ = "secure_input_helper_binary_missing";
|
|
secure_input_helper_last_error_code_ = ERROR_FILE_NOT_FOUND;
|
|
return false;
|
|
}
|
|
|
|
std::wstring stop_event_name = GetSecureInputHelperStopEventName(session_id);
|
|
KernelObjectSecurityAttributes event_security;
|
|
SECURITY_ATTRIBUTES* event_attributes = nullptr;
|
|
if (event_security.Initialize()) {
|
|
event_attributes = event_security.get();
|
|
}
|
|
|
|
HANDLE stop_event_handle =
|
|
CreateEventW(event_attributes, TRUE, FALSE, stop_event_name.c_str());
|
|
if (stop_event_handle == nullptr) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
secure_input_helper_last_error_ =
|
|
"create_secure_input_helper_stop_event_failed";
|
|
secure_input_helper_last_error_code_ = GetLastError();
|
|
return false;
|
|
}
|
|
|
|
std::wstring command_line = QuoteWindowsArgument(helper_path) +
|
|
L" --secure-input-helper --session-id " +
|
|
std::to_wstring(session_id) + L" --stop-event " +
|
|
QuoteWindowsArgument(stop_event_name);
|
|
std::wstring mutable_command_line = command_line;
|
|
|
|
STARTUPINFOW startup_info{};
|
|
startup_info.cb = sizeof(startup_info);
|
|
startup_info.lpDesktop = const_cast<LPWSTR>(L"winsta0\\Winlogon");
|
|
PROCESS_INFORMATION process_info{};
|
|
BOOL created = FALSE;
|
|
|
|
if (console_mode_ && process_session_id_ == session_id) {
|
|
created = CreateProcessW(helper_path.c_str(), mutable_command_line.data(),
|
|
nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr,
|
|
nullptr, &startup_info, &process_info);
|
|
} else {
|
|
HANDLE primary_token = nullptr;
|
|
ScopedEnvironmentBlock environment_block;
|
|
DWORD error = 0;
|
|
if (!CreateSessionSystemToken(session_id, &primary_token, &error)) {
|
|
CloseHandle(stop_event_handle);
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
secure_input_helper_last_error_ = "create_session_system_token_failed";
|
|
secure_input_helper_last_error_code_ = error;
|
|
return false;
|
|
}
|
|
|
|
CreateEnvironmentBlock(&environment_block.environment, primary_token,
|
|
FALSE);
|
|
created = CreateProcessAsUserW(
|
|
primary_token, helper_path.c_str(), mutable_command_line.data(),
|
|
nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW,
|
|
environment_block.environment, nullptr, &startup_info, &process_info);
|
|
error = created ? ERROR_SUCCESS : GetLastError();
|
|
CloseHandle(primary_token);
|
|
if (!created) {
|
|
CloseHandle(stop_event_handle);
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
secure_input_helper_last_error_ = "create_secure_input_helper_failed";
|
|
secure_input_helper_last_error_code_ = error;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CloseHandle(process_info.hThread);
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
secure_input_helper_process_handle_ = process_info.hProcess;
|
|
secure_input_helper_stop_event_ = stop_event_handle;
|
|
secure_input_helper_process_id_ = process_info.dwProcessId;
|
|
secure_input_helper_session_id_ = session_id;
|
|
secure_input_helper_exit_code_ = STILL_ACTIVE;
|
|
secure_input_helper_last_error_code_ = 0;
|
|
secure_input_helper_last_error_.clear();
|
|
secure_input_helper_running_ = true;
|
|
secure_input_helper_started_at_tick_ = GetTickCount64();
|
|
}
|
|
|
|
LOG_INFO("Secure input helper started: session_id={}, pid={}", session_id,
|
|
process_info.dwProcessId);
|
|
return true;
|
|
}
|
|
|
|
void CrossDeskServiceHost::EnsureSessionHelper() {
|
|
ReapSessionHelper();
|
|
|
|
DWORD target_session_id = 0xFFFFFFFF;
|
|
bool has_active_user = false;
|
|
bool already_running = false;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
target_session_id = active_session_id_;
|
|
has_active_user = !prelogin_;
|
|
already_running = session_helper_running_ &&
|
|
session_helper_session_id_ == target_session_id;
|
|
}
|
|
|
|
if (already_running) {
|
|
return;
|
|
}
|
|
|
|
if (target_session_id == 0xFFFFFFFF || !has_active_user) {
|
|
StopSessionHelper();
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_last_error_ = target_session_id == 0xFFFFFFFF
|
|
? "no_active_console_session"
|
|
: "no_active_user_session";
|
|
session_helper_last_error_code_ = 0;
|
|
return;
|
|
}
|
|
|
|
StopSessionHelper();
|
|
LaunchSessionHelper(target_session_id);
|
|
}
|
|
|
|
void CrossDeskServiceHost::RefreshSessionHelperReportedState() {
|
|
DWORD target_session_id = 0xFFFFFFFF;
|
|
bool helper_running = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
target_session_id = session_helper_session_id_;
|
|
helper_running = session_helper_running_;
|
|
}
|
|
|
|
if (!helper_running || target_session_id == 0xFFFFFFFF) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
ResetSessionHelperReportedStateLocked("helper_not_running", 0);
|
|
return;
|
|
}
|
|
|
|
std::string response = QueryNamedPipeMessage(
|
|
GetCrossDeskSessionHelperPipeName(target_session_id),
|
|
kCrossDeskSessionHelperStatusCommand, 300);
|
|
Json json = Json::parse(response, nullptr, false);
|
|
if (json.is_discarded()) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
ResetSessionHelperReportedStateLocked("invalid_helper_status_json", 0);
|
|
return;
|
|
}
|
|
|
|
if (!json.value("ok", false)) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
const std::string error =
|
|
json.value("error", std::string("helper_status_failed"));
|
|
ResetSessionHelperReportedStateLocked(
|
|
error.c_str(), json.value("code", static_cast<DWORD>(0)));
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
session_helper_status_ok_ = true;
|
|
session_helper_status_error_.clear();
|
|
session_helper_status_error_code_ = 0;
|
|
session_helper_report_session_id_ =
|
|
json.value("session_id", static_cast<DWORD>(0xFFFFFFFF));
|
|
session_helper_report_process_id_ = json.value("process_id", 0u);
|
|
session_helper_report_session_locked_ = json.value("session_locked", false);
|
|
session_helper_report_input_desktop_available_ =
|
|
json.value("input_desktop_available", false);
|
|
session_helper_report_input_desktop_error_code_ =
|
|
json.value("input_desktop_error_code", 0u);
|
|
session_helper_report_input_desktop_ =
|
|
json.value("input_desktop", std::string());
|
|
session_helper_report_lock_app_visible_ =
|
|
json.value("lock_app_visible", false);
|
|
session_helper_report_logon_ui_visible_ =
|
|
json.value("logon_ui_visible", false);
|
|
session_helper_report_secure_desktop_active_ =
|
|
json.value("secure_desktop_active", false);
|
|
session_helper_report_credential_ui_visible_ =
|
|
json.value("credential_ui_visible", false);
|
|
session_helper_report_unlock_ui_visible_ =
|
|
json.value("unlock_ui_visible", false);
|
|
session_helper_report_interactive_stage_ =
|
|
json.value("interactive_stage", std::string());
|
|
session_helper_report_state_age_ms_ = json.value("state_age_ms", 0ull);
|
|
session_helper_report_uptime_ms_ = json.value("uptime_ms", 0ull);
|
|
}
|
|
|
|
void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type,
|
|
DWORD session_id) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
last_session_event_type_ = event_type;
|
|
last_session_event_session_id_ = session_id;
|
|
active_session_id_ = WTSGetActiveConsoleSessionId();
|
|
DWORD process_session_id = 0xFFFFFFFF;
|
|
if (ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id)) {
|
|
process_session_id_ = process_session_id;
|
|
}
|
|
logon_ui_visible_ = IsLogonUiRunningInSession(active_session_id_);
|
|
InputDesktopInfo desktop_info = GetInputDesktopInfo();
|
|
input_desktop_available_ = desktop_info.available;
|
|
input_desktop_error_code_ = desktop_info.error_code;
|
|
input_desktop_name_ = desktop_info.name;
|
|
secure_desktop_active_ =
|
|
_stricmp(input_desktop_name_.c_str(), "Winlogon") == 0;
|
|
|
|
std::wstring username;
|
|
bool username_available = GetSessionUserName(active_session_id_, &username);
|
|
prelogin_ = !username_available || username.empty();
|
|
|
|
if (!QuerySessionLockState(active_session_id_, &session_locked_)) {
|
|
if (event_type == WTS_SESSION_LOCK) {
|
|
session_locked_ = true;
|
|
} else if (event_type == WTS_SESSION_UNLOCK ||
|
|
event_type == WTS_SESSION_LOGON) {
|
|
session_locked_ = false;
|
|
} else if (logon_ui_visible_ || secure_desktop_active_) {
|
|
session_locked_ = !prelogin_;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG_INFO("Session event: type={}, session_id={}, active_session_id={}",
|
|
SessionEventToString(event_type), session_id, active_session_id_);
|
|
EnsureSessionHelper();
|
|
if (!secure_desktop_active_ && !logon_ui_visible_) {
|
|
StopSecureInputHelper();
|
|
}
|
|
}
|
|
|
|
std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command) {
|
|
std::string normalized = ToLower(Trim(command));
|
|
if (normalized == "ping") {
|
|
return "{\"ok\":true,\"reply\":\"pong\"}";
|
|
}
|
|
if (normalized == "status") {
|
|
return BuildStatusResponse();
|
|
}
|
|
if (normalized == "sas") {
|
|
return SendSecureAttentionSequence();
|
|
}
|
|
int key_code = 0;
|
|
bool is_down = false;
|
|
if (ParseSecureDesktopKeyboardIpcCommand(normalized, &key_code, &is_down)) {
|
|
return SendSecureDesktopKeyboardInput(key_code, is_down);
|
|
}
|
|
return BuildErrorJson("unknown_command");
|
|
}
|
|
|
|
std::string CrossDeskServiceHost::BuildStatusResponse() {
|
|
ReapSecureInputHelper();
|
|
ReapSessionHelper();
|
|
RefreshSessionState();
|
|
EnsureSessionHelper();
|
|
RefreshSessionHelperReportedState();
|
|
bool keep_secure_input_helper = false;
|
|
bool launch_secure_input_helper = false;
|
|
DWORD secure_input_target_session_id = 0xFFFFFFFF;
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
secure_input_target_session_id = active_session_id_;
|
|
keep_secure_input_helper =
|
|
ShouldKeepSecureInputHelperLocked(secure_input_target_session_id);
|
|
launch_secure_input_helper =
|
|
keep_secure_input_helper &&
|
|
(!secure_input_helper_running_ ||
|
|
secure_input_helper_session_id_ != secure_input_target_session_id);
|
|
}
|
|
|
|
if (keep_secure_input_helper) {
|
|
if (launch_secure_input_helper) {
|
|
StopSecureInputHelper();
|
|
LaunchSecureInputHelper(secure_input_target_session_id);
|
|
}
|
|
} else {
|
|
StopSecureInputHelper();
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
std::wstring username;
|
|
GetSessionUserName(active_session_id_, &username);
|
|
std::string username_utf8 = EscapeJsonString(WideToUtf8(username));
|
|
std::string input_desktop = EscapeJsonString(input_desktop_name_);
|
|
std::string last_sas_error = EscapeJsonString(last_sas_error_);
|
|
std::string session_helper_last_error =
|
|
EscapeJsonString(session_helper_last_error_);
|
|
std::string session_helper_status_error =
|
|
EscapeJsonString(session_helper_status_error_);
|
|
std::string session_helper_path =
|
|
EscapeJsonString(WideToUtf8(GetSessionHelperPath()));
|
|
std::string secure_input_helper_path =
|
|
EscapeJsonString(WideToUtf8(GetSecureInputHelperPath()));
|
|
std::string helper_input_desktop =
|
|
EscapeJsonString(session_helper_report_input_desktop_);
|
|
std::string secure_input_helper_last_error =
|
|
EscapeJsonString(secure_input_helper_last_error_);
|
|
bool interactive_state_ready = session_helper_status_ok_;
|
|
const char* interactive_state_source =
|
|
interactive_state_ready ? "session-helper" : "service-host";
|
|
const bool effective_session_locked = GetEffectiveSessionLockedLocked();
|
|
const bool interactive_lock_screen_visible =
|
|
interactive_state_ready
|
|
? (effective_session_locked && IsHelperReportingLockScreenLocked())
|
|
: false;
|
|
bool credential_ui_visible =
|
|
interactive_state_ready ? session_helper_report_credential_ui_visible_
|
|
: logon_ui_visible_;
|
|
bool unlock_ui_visible = interactive_state_ready
|
|
? session_helper_report_unlock_ui_visible_
|
|
: (logon_ui_visible_ || secure_desktop_active_);
|
|
bool interactive_secure_desktop_active =
|
|
interactive_state_ready ? session_helper_report_secure_desktop_active_
|
|
: secure_desktop_active_;
|
|
bool interactive_logon_ui_visible =
|
|
interactive_state_ready ? session_helper_report_logon_ui_visible_
|
|
: logon_ui_visible_;
|
|
bool interactive_session_locked = effective_session_locked ||
|
|
interactive_lock_screen_visible ||
|
|
unlock_ui_visible;
|
|
std::string interactive_input_desktop = EscapeJsonString(
|
|
interactive_state_ready ? session_helper_report_input_desktop_
|
|
: input_desktop_name_);
|
|
std::string interactive_stage = EscapeJsonString(DetermineInteractiveStage(
|
|
interactive_lock_screen_visible, credential_ui_visible,
|
|
interactive_secure_desktop_active));
|
|
std::ostringstream stream;
|
|
stream << "{\"ok\":true,\"service\":\"CrossDeskService\""
|
|
<< ",\"active_session_id\":" << active_session_id_
|
|
<< ",\"process_session_id\":" << process_session_id_
|
|
<< ",\"session_locked\":" << (session_locked_ ? "true" : "false")
|
|
<< ",\"interactive_state_ready\":"
|
|
<< (interactive_state_ready ? "true" : "false")
|
|
<< ",\"interactive_state_source\":\"" << interactive_state_source
|
|
<< "\""
|
|
<< ",\"interactive_session_locked\":"
|
|
<< (interactive_session_locked ? "true" : "false")
|
|
<< ",\"interactive_stage\":\"" << interactive_stage << "\""
|
|
<< ",\"interactive_input_desktop\":\"" << interactive_input_desktop
|
|
<< "\""
|
|
<< ",\"interactive_lock_screen_visible\":"
|
|
<< (interactive_lock_screen_visible ? "true" : "false")
|
|
<< ",\"interactive_logon_ui_visible\":"
|
|
<< (interactive_logon_ui_visible ? "true" : "false")
|
|
<< ",\"interactive_secure_desktop_active\":"
|
|
<< (interactive_secure_desktop_active ? "true" : "false")
|
|
<< ",\"unlock_ui_visible\":" << (unlock_ui_visible ? "true" : "false")
|
|
<< ",\"credential_ui_visible\":"
|
|
<< (credential_ui_visible ? "true" : "false")
|
|
<< ",\"password_box_visible\":"
|
|
<< (credential_ui_visible ? "true" : "false")
|
|
<< ",\"logon_ui_visible\":" << (logon_ui_visible_ ? "true" : "false")
|
|
<< ",\"secure_desktop_active\":"
|
|
<< (secure_desktop_active_ ? "true" : "false")
|
|
<< ",\"input_desktop_available\":"
|
|
<< (input_desktop_available_ ? "true" : "false")
|
|
<< ",\"input_desktop_error_code\":" << input_desktop_error_code_
|
|
<< ",\"input_desktop\":\"" << input_desktop << "\""
|
|
<< ",\"prelogin\":" << (prelogin_ ? "true" : "false")
|
|
<< ",\"session_user\":\"" << username_utf8 << "\""
|
|
<< ",\"session_helper_path\":\"" << session_helper_path << "\""
|
|
<< ",\"session_helper_running\":"
|
|
<< (session_helper_running_ ? "true" : "false")
|
|
<< ",\"session_helper_pid\":" << session_helper_process_id_
|
|
<< ",\"session_helper_session_id\":" << session_helper_session_id_
|
|
<< ",\"session_helper_exit_code\":" << session_helper_exit_code_
|
|
<< ",\"session_helper_last_error\":\"" << session_helper_last_error
|
|
<< "\""
|
|
<< ",\"session_helper_last_error_code\":"
|
|
<< session_helper_last_error_code_ << ",\"session_helper_status_ok\":"
|
|
<< (session_helper_status_ok_ ? "true" : "false")
|
|
<< ",\"session_helper_status_error\":\"" << session_helper_status_error
|
|
<< "\""
|
|
<< ",\"session_helper_status_error_code\":"
|
|
<< session_helper_status_error_code_
|
|
<< ",\"session_helper_report_session_id\":"
|
|
<< session_helper_report_session_id_
|
|
<< ",\"session_helper_report_process_id\":"
|
|
<< session_helper_report_process_id_
|
|
<< ",\"session_helper_report_session_locked\":"
|
|
<< (session_helper_report_session_locked_ ? "true" : "false")
|
|
<< ",\"session_helper_report_input_desktop_available\":"
|
|
<< (session_helper_report_input_desktop_available_ ? "true" : "false")
|
|
<< ",\"session_helper_report_input_desktop_error_code\":"
|
|
<< session_helper_report_input_desktop_error_code_
|
|
<< ",\"session_helper_report_input_desktop\":\""
|
|
<< helper_input_desktop << "\""
|
|
<< ",\"session_helper_report_lock_app_visible\":"
|
|
<< (session_helper_report_lock_app_visible_ ? "true" : "false")
|
|
<< ",\"session_helper_report_logon_ui_visible\":"
|
|
<< (session_helper_report_logon_ui_visible_ ? "true" : "false")
|
|
<< ",\"session_helper_report_secure_desktop_active\":"
|
|
<< (session_helper_report_secure_desktop_active_ ? "true" : "false")
|
|
<< ",\"session_helper_report_credential_ui_visible\":"
|
|
<< (session_helper_report_credential_ui_visible_ ? "true" : "false")
|
|
<< ",\"session_helper_report_unlock_ui_visible\":"
|
|
<< (session_helper_report_unlock_ui_visible_ ? "true" : "false")
|
|
<< ",\"session_helper_report_interactive_stage\":\""
|
|
<< EscapeJsonString(session_helper_report_interactive_stage_) << "\""
|
|
<< ",\"session_helper_report_state_age_ms\":"
|
|
<< session_helper_report_state_age_ms_
|
|
<< ",\"session_helper_report_uptime_ms\":"
|
|
<< session_helper_report_uptime_ms_ << ",\"session_helper_uptime_ms\":"
|
|
<< (session_helper_started_at_tick_ >= started_at_tick_
|
|
? (GetTickCount64() - session_helper_started_at_tick_)
|
|
: 0)
|
|
<< ",\"secure_input_helper_path\":\"" << secure_input_helper_path
|
|
<< "\""
|
|
<< ",\"secure_input_helper_running\":"
|
|
<< (secure_input_helper_running_ ? "true" : "false")
|
|
<< ",\"secure_input_helper_pid\":" << secure_input_helper_process_id_
|
|
<< ",\"secure_input_helper_session_id\":"
|
|
<< secure_input_helper_session_id_
|
|
<< ",\"secure_input_helper_exit_code\":"
|
|
<< secure_input_helper_exit_code_
|
|
<< ",\"secure_input_helper_last_error\":\""
|
|
<< secure_input_helper_last_error << "\""
|
|
<< ",\"secure_input_helper_last_error_code\":"
|
|
<< secure_input_helper_last_error_code_
|
|
<< ",\"secure_input_helper_uptime_ms\":"
|
|
<< (secure_input_helper_started_at_tick_ >= started_at_tick_
|
|
? (GetTickCount64() - secure_input_helper_started_at_tick_)
|
|
: 0)
|
|
<< ",\"last_sas_success\":" << (last_sas_success_ ? "true" : "false")
|
|
<< ",\"last_sas_error\":\"" << last_sas_error << "\""
|
|
<< ",\"last_sas_error_code\":" << last_sas_error_code_
|
|
<< ",\"last_sas_uptime_ms\":"
|
|
<< (last_sas_tick_ >= started_at_tick_
|
|
? (last_sas_tick_ - started_at_tick_)
|
|
: 0)
|
|
<< ",\"last_session_event\":\""
|
|
<< SessionEventToString(last_session_event_type_) << "\""
|
|
<< ",\"last_session_event_id\":" << last_session_event_type_
|
|
<< ",\"last_session_id\":" << last_session_event_session_id_
|
|
<< ",\"uptime_ms\":"
|
|
<< (GetTickCount64() >= started_at_tick_
|
|
? (GetTickCount64() - started_at_tick_)
|
|
: 0)
|
|
<< "}";
|
|
return stream.str();
|
|
}
|
|
|
|
std::string CrossDeskServiceHost::SendSecureAttentionSequence() {
|
|
RefreshSessionState();
|
|
LOG_INFO("Received SAS request for session_id={}", active_session_id_);
|
|
SasResult result = SendSasNow();
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
last_sas_tick_ = GetTickCount64();
|
|
last_sas_success_ = result.success;
|
|
last_sas_error_code_ = result.error_code;
|
|
last_sas_error_ = result.error;
|
|
}
|
|
|
|
if (!result.success) {
|
|
return BuildErrorJson(result.error.c_str(), result.error_code);
|
|
}
|
|
return "{\"ok\":true,\"sent\":\"sas\"}";
|
|
}
|
|
|
|
std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput(int key_code,
|
|
bool is_down) {
|
|
RefreshSessionState();
|
|
ReapSecureInputHelper();
|
|
EnsureSessionHelper();
|
|
|
|
DWORD target_session_id = 0xFFFFFFFF;
|
|
bool helper_running = false;
|
|
bool can_inject = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
target_session_id = active_session_id_;
|
|
helper_running = secure_input_helper_running_ &&
|
|
secure_input_helper_session_id_ == target_session_id;
|
|
can_inject = GetEffectiveSessionLockedLocked() || HasSecureInputUiLocked();
|
|
}
|
|
|
|
if (target_session_id == 0xFFFFFFFF) {
|
|
return BuildErrorJson("no_active_console_session");
|
|
}
|
|
if (!can_inject) {
|
|
return BuildErrorJson("secure_input_not_active");
|
|
}
|
|
|
|
if (!helper_running) {
|
|
StopSecureInputHelper();
|
|
if (!LaunchSecureInputHelper(target_session_id)) {
|
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
|
return BuildErrorJson(secure_input_helper_last_error_.c_str(),
|
|
secure_input_helper_last_error_code_);
|
|
}
|
|
}
|
|
|
|
return QueryNamedPipeMessage(
|
|
GetCrossDeskSecureInputHelperPipeName(target_session_id),
|
|
BuildSecureInputHelperKeyboardCommand(key_code, is_down), 1000);
|
|
}
|
|
|
|
bool InstallCrossDeskService(const std::wstring& binary_path) {
|
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
|
if (manager == nullptr) {
|
|
LOG_ERROR("OpenSCManagerW failed, error={}", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
std::wstring service_command = L"\"" + binary_path + L"\" --service";
|
|
SC_HANDLE service = CreateServiceW(
|
|
manager, kCrossDeskServiceName, kCrossDeskServiceDisplayName,
|
|
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
|
|
SERVICE_ERROR_NORMAL, service_command.c_str(), nullptr, nullptr, nullptr,
|
|
nullptr, nullptr);
|
|
|
|
if (service == nullptr) {
|
|
DWORD error = GetLastError();
|
|
if (error != ERROR_SERVICE_EXISTS) {
|
|
LOG_ERROR("CreateServiceW failed, error={}", error);
|
|
CloseServiceHandle(manager);
|
|
return false;
|
|
}
|
|
|
|
service = OpenServiceW(manager, kCrossDeskServiceName,
|
|
SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS);
|
|
if (service == nullptr) {
|
|
LOG_ERROR("OpenServiceW failed, error={}", GetLastError());
|
|
CloseServiceHandle(manager);
|
|
return false;
|
|
}
|
|
|
|
if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
|
|
SERVICE_NO_CHANGE, service_command.c_str(),
|
|
nullptr, nullptr, nullptr, nullptr, nullptr,
|
|
kCrossDeskServiceDisplayName)) {
|
|
LOG_ERROR("ChangeServiceConfigW failed, error={}", GetLastError());
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return true;
|
|
}
|
|
|
|
bool StartCrossDeskService() {
|
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
|
if (manager == nullptr) {
|
|
LOG_ERROR("OpenSCManagerW failed, error={}", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
SC_HANDLE service =
|
|
OpenServiceW(manager, kCrossDeskServiceName, SERVICE_START);
|
|
if (service == nullptr) {
|
|
LOG_ERROR("OpenServiceW failed, error={}", GetLastError());
|
|
CloseServiceHandle(manager);
|
|
return false;
|
|
}
|
|
|
|
bool success = StartServiceW(service, 0, nullptr) != FALSE;
|
|
DWORD error = success ? ERROR_SUCCESS : GetLastError();
|
|
if (!success && error != ERROR_SERVICE_ALREADY_RUNNING) {
|
|
LOG_ERROR("StartServiceW failed, error={}", error);
|
|
}
|
|
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return success || error == ERROR_SERVICE_ALREADY_RUNNING;
|
|
}
|
|
|
|
bool StopCrossDeskService(DWORD timeout_ms) {
|
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
|
if (manager == nullptr) {
|
|
LOG_ERROR("OpenSCManagerW failed, error={}", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
SC_HANDLE service = OpenServiceW(manager, kCrossDeskServiceName,
|
|
SERVICE_STOP | SERVICE_QUERY_STATUS);
|
|
if (service == nullptr) {
|
|
DWORD error = GetLastError();
|
|
CloseServiceHandle(manager);
|
|
if (error == ERROR_SERVICE_DOES_NOT_EXIST) {
|
|
return true;
|
|
}
|
|
LOG_ERROR("OpenServiceW failed, error={}", error);
|
|
return false;
|
|
}
|
|
|
|
SERVICE_STATUS_PROCESS status{};
|
|
DWORD bytes_needed = 0;
|
|
QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO,
|
|
reinterpret_cast<LPBYTE>(&status), sizeof(status),
|
|
&bytes_needed);
|
|
|
|
if (status.dwCurrentState != SERVICE_STOPPED &&
|
|
status.dwCurrentState != SERVICE_STOP_PENDING) {
|
|
SERVICE_STATUS service_status{};
|
|
if (!ControlService(service, SERVICE_CONTROL_STOP, &service_status)) {
|
|
DWORD error = GetLastError();
|
|
if (error != ERROR_SERVICE_NOT_ACTIVE) {
|
|
LOG_ERROR("ControlService failed, error={}", error);
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD deadline = GetTickCount() + timeout_ms;
|
|
while (GetTickCount() < deadline) {
|
|
if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO,
|
|
reinterpret_cast<LPBYTE>(&status), sizeof(status),
|
|
&bytes_needed)) {
|
|
LOG_ERROR("QueryServiceStatusEx failed, error={}", GetLastError());
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return false;
|
|
}
|
|
|
|
if (status.dwCurrentState == SERVICE_STOPPED) {
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return true;
|
|
}
|
|
Sleep(200);
|
|
}
|
|
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return false;
|
|
}
|
|
|
|
bool UninstallCrossDeskService() {
|
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
|
if (manager == nullptr) {
|
|
LOG_ERROR("OpenSCManagerW failed, error={}", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
SC_HANDLE service =
|
|
OpenServiceW(manager, kCrossDeskServiceName,
|
|
DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);
|
|
if (service == nullptr) {
|
|
DWORD error = GetLastError();
|
|
CloseServiceHandle(manager);
|
|
if (error == ERROR_SERVICE_DOES_NOT_EXIST) {
|
|
return true;
|
|
}
|
|
LOG_ERROR("OpenServiceW failed, error={}", error);
|
|
return false;
|
|
}
|
|
|
|
StopCrossDeskService();
|
|
|
|
bool success = DeleteService(service) != FALSE;
|
|
if (!success) {
|
|
LOG_ERROR("DeleteService failed, error={}", GetLastError());
|
|
}
|
|
|
|
CloseServiceHandle(service);
|
|
CloseServiceHandle(manager);
|
|
return success;
|
|
}
|
|
|
|
std::string QueryCrossDeskService(const std::string& command,
|
|
DWORD timeout_ms) {
|
|
return QueryNamedPipeMessage(kCrossDeskServicePipeName, command, timeout_ms);
|
|
}
|
|
|
|
std::string SendCrossDeskSecureDesktopKeyInput(int key_code, bool is_down,
|
|
DWORD timeout_ms) {
|
|
return QueryCrossDeskService(
|
|
BuildSecureDesktopKeyboardIpcCommand(key_code, is_down), timeout_ms);
|
|
}
|
|
|
|
std::string SendCrossDeskSecureDesktopMouseInput(int x, int y, int wheel,
|
|
int flag, DWORD timeout_ms) {
|
|
return QueryCrossDeskService(
|
|
BuildSecureDesktopMouseIpcCommand(x, y, wheel, flag), timeout_ms);
|
|
}
|
|
|
|
} // namespace crossdesk
|