fix: Restore active desktop name check in Windows daemon

- Use `PIPE_NOWAIT` to skip empty stderr output on process output reading from Windows daemon watchdog
- Use `CreateProcess` result to determine when to call `CloseHandle` in Windows daemon watchdog
- Trim output from active desktop process in Windows daemon watchdog
- Improve error logging in Windows daemon watchdog
This commit is contained in:
Nick Bolton 2025-02-13 17:37:58 +00:00
parent 3de2c1bcf0
commit c9a360a144
11 changed files with 453 additions and 277 deletions

View file

@ -28,6 +28,30 @@ int main(int argc, char **argv)
Log log; Log log;
EventQueue events; EventQueue events;
// HACK: the `--active-desktop` arg actually belongs in the `deskflow-core` binary,
// but we are placing it here in the server binary temporarily until we are ready to
// ship the `deskflow-core` binary. we are deliberately not integrating `--active-desktop`
// into the existing `ServerApp` arg parsing code as that would be a waste of time.
#if SYSAPI_WIN32
for (int i = 1; i < argc; ++i) {
std::string arg(argv[i]);
// This is called by the daemon (running in session 0) when it needs to know the name of the
// interactive desktop.
// It is necessary to run a utility process because the daemon runs in session 0, which does not
// have access to the active desktop, and so cannot query it's name.
if (arg == "--active-desktop") {
const auto name = ArchMiscWindows::getActiveDesktopName();
if (name.empty()) {
LOG((CLOG_CRIT "failed to get active desktop name"));
return kExitFailed;
}
LOG((CLOG_PRINT "%s", name.c_str()));
return kExitSuccess;
}
}
#endif
ServerApp app(&events); ServerApp app(&events);
return app.run(argc, argv); return app.run(argc, argv);
} }

View file

@ -7,6 +7,7 @@
#include "arch/win32/ArchMiscWindows.h" #include "arch/win32/ArchMiscWindows.h"
#include "arch/win32/ArchDaemonWindows.h" #include "arch/win32/ArchDaemonWindows.h"
#include "arch/win32/XArchWindows.h"
#include "base/Log.h" #include "base/Log.h"
#include "common/constants.h" #include "common/constants.h"
@ -457,3 +458,19 @@ void ArchMiscWindows::setInstanceWin32(HINSTANCE instance)
assert(instance != NULL); assert(instance != NULL);
s_instanceWin32 = instance; s_instanceWin32 = instance;
} }
std::string ArchMiscWindows::getActiveDesktopName()
{
HDESK desk = OpenInputDesktop(0, TRUE, GENERIC_READ);
if (desk == nullptr) {
LOG((CLOG_ERR "could not open input desktop"));
throw XArch(new XArchEvalWindows());
}
DWORD size;
GetUserObjectInformation(desk, UOI_NAME, nullptr, 0, &size);
auto *name = (TCHAR *)alloca(size + sizeof(TCHAR));
GetUserObjectInformation(desk, UOI_NAME, name, size, &size);
CloseDesktop(desk);
return name;
}

View file

@ -155,12 +155,11 @@ public:
static bool getParentProcessName(std::string &name); static bool getParentProcessName(std::string &name);
static HINSTANCE instanceWin32(); static HINSTANCE instanceWin32();
static void setInstanceWin32(HINSTANCE instance); static void setInstanceWin32(HINSTANCE instance);
static BOOL WINAPI getProcessEntry(PROCESSENTRY32 &entry, DWORD processID); static BOOL WINAPI getProcessEntry(PROCESSENTRY32 &entry, DWORD processID);
static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32 &entry); static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32 &entry);
static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32 &entry); static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32 &entry);
static std::string getActiveDesktopName();
private: private:
//! Open and return a registry key, closing the parent key //! Open and return a registry key, closing the parent key

View file

@ -376,7 +376,7 @@ void DaemonApp::handleIpcMessage(const Event &e, void *)
} }
#if SYSAPI_WIN32 #if SYSAPI_WIN32
std::string watchdogStatus = m_watchdog->isProcessActive() ? "active" : "idle"; std::string watchdogStatus = m_watchdog->isProcessRunning() ? "active" : "idle";
LOG((CLOG_INFO "service status: %s", watchdogStatus.c_str())); LOG((CLOG_INFO "service status: %s", watchdogStatus.c_str()));
#endif #endif

View file

@ -43,6 +43,8 @@ if(WIN32)
MSWindowsKeyState.h MSWindowsKeyState.h
MSWindowsPowerManager.cpp MSWindowsPowerManager.cpp
MSWindowsPowerManager.h MSWindowsPowerManager.h
MSWindowsProcess.cpp
MSWindowsProcess.h
MSWindowsScreen.cpp MSWindowsScreen.cpp
MSWindowsScreen.h MSWindowsScreen.h
MSWindowsScreenSaver.cpp MSWindowsScreenSaver.cpp

View file

@ -0,0 +1,225 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "MSWindowsProcess.h"
#include "arch/win32/XArchWindows.h"
#include "base/Log.h"
#include "ipc/IpcMessage.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <UserEnv.h>
#include <string>
namespace deskflow::platform {
MSWindowsProcess::MSWindowsProcess(const std::string &command, HANDLE stdOutput, HANDLE stdError)
: m_command(command),
m_stdOutput(stdOutput),
m_stdError(stdError)
{
}
MSWindowsProcess::~MSWindowsProcess()
{
if (m_createProcessResult) {
CloseHandle(m_info.hProcess);
CloseHandle(m_info.hThread);
}
}
BOOL MSWindowsProcess::startInForeground()
{
// clear, as we're reusing process info struct
ZeroMemory(&m_info, sizeof(PROCESS_INFORMATION));
// show the console window when in foreground mode,
// so we can close it gracefully, but minimize it
// so it doesn't get in the way.
STARTUPINFO si;
setStartupInfo(si);
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_MINIMIZE;
m_createProcessResult =
CreateProcess(nullptr, LPSTR(m_command.c_str()), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &m_info);
return m_createProcessResult;
}
BOOL MSWindowsProcess::startAsUser(HANDLE userToken, LPSECURITY_ATTRIBUTES sa)
{
STARTUPINFO si;
setStartupInfo(si);
LPVOID environment;
if (!CreateEnvironmentBlock(&environment, userToken, FALSE)) {
LOG((CLOG_ERR "could not create environment block"));
throw XArch(new XArchEvalWindows);
}
ZeroMemory(&m_info, sizeof(PROCESS_INFORMATION));
const DWORD creationFlags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
m_createProcessResult = CreateProcessAsUser(
userToken, nullptr, LPSTR(m_command.c_str()), sa, nullptr, TRUE, creationFlags, environment, nullptr, &si, &m_info
);
DestroyEnvironmentBlock(environment);
CloseHandle(userToken);
return m_createProcessResult;
}
void MSWindowsProcess::setStartupInfo(STARTUPINFO &si)
{
static char g_desktopName[] = "winsta0\\Default"; // NOSONAR -- Idiomatic Win32
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = g_desktopName;
si.hStdOutput = m_stdOutput;
si.hStdError = m_stdError;
si.dwFlags |= STARTF_USESTDHANDLES;
}
DWORD MSWindowsProcess::waitForExit()
{
const auto kMaxWaitMilliseconds = 10000;
LOG_INFO("waiting for process to exit, pid: %lu", m_info.dwProcessId);
if (WaitForSingleObject(m_info.hProcess, kMaxWaitMilliseconds) != WAIT_OBJECT_0) {
LOG_ERR("process did not exit within the expected time");
TerminateProcess(m_info.hProcess, 1);
throw XArch(new XArchEvalWindows());
}
DWORD exitCode = 0;
if (!GetExitCodeProcess(m_info.hProcess, &exitCode)) {
LOG_ERR("failed to get exit code, error: %lu", GetLastError());
throw XArch(new XArchEvalWindows());
}
if (exitCode != 0) {
LOG_WARN("process failed with exit code: %lu", exitCode);
} else {
LOG_DEBUG("process exited with code: %lu", exitCode);
}
return exitCode;
}
void MSWindowsProcess::shutdown(IpcServer &ipcServer, int timeout)
{
shutdown(m_info.hProcess, m_info.dwProcessId, ipcServer, timeout);
}
void MSWindowsProcess::shutdown(HANDLE handle, DWORD pid, IpcServer &ipcServer, int timeout)
{
LOG_DEBUG("shutting down process %d", pid);
DWORD exitCode;
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
LOG_DEBUG("process %d is already shutdown", pid);
return;
}
IpcShutdownMessage shutdown;
ipcServer.send(shutdown, IpcClientType::Node);
// wait for process to exit gracefully.
double start = ARCH->time();
while (true) { // NOSONAR -- Multiple breaks necessary
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
// yay, we got a graceful shutdown. there should be no hook in use errors!
LOG((CLOG_DEBUG "process %d was shutdown gracefully", pid));
break;
}
if (double elapsed = (ARCH->time() - start); elapsed > timeout) {
// if timeout reached, kill forcefully.
// calling TerminateProcess on deskflow is very bad!
// it causes the hook DLL to stay loaded in some apps,
// making it impossible to start deskflow again.
LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed));
TerminateProcess(handle, kExitSuccess);
break;
}
ARCH->sleep(1);
}
}
void MSWindowsProcess::createPipes()
{
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = nullptr;
if (!CreatePipe(&m_outputPipe, &m_stdOutput, &saAttr, 0)) {
LOG_ERR("could not create output pipe");
throw XArch(new XArchEvalWindows());
}
if (!CreatePipe(&m_errorPipe, &m_stdError, &saAttr, 0)) {
LOG_ERR("could not create error pipe");
throw XArch(new XArchEvalWindows());
}
// Set the pipes to non-blocking mode
DWORD mode = PIPE_NOWAIT;
if (!SetNamedPipeHandleState(m_outputPipe, &mode, nullptr, nullptr)) {
throw XArch(new XArchEvalWindows());
}
if (!SetNamedPipeHandleState(m_errorPipe, &mode, nullptr, nullptr)) {
throw XArch(new XArchEvalWindows());
}
}
std::string MSWindowsProcess::readStdOutput()
{
return readOutput(m_outputPipe);
}
std::string MSWindowsProcess::readStdError()
{
return readOutput(m_errorPipe);
}
std::string MSWindowsProcess::readOutput(HANDLE handle)
{
const auto kOutputBufferSize = 4096;
char buffer[kOutputBufferSize]; // NOSONAR -- Idiomatic Win32
DWORD bytesRead;
DWORD totalBytesAvail;
DWORD bytesLeftThisMessage;
// Check if there is data available in the pipe, which prevents `ReadFile` from freezing execution.
if (!PeekNamedPipe(handle, nullptr, 0, nullptr, &totalBytesAvail, &bytesLeftThisMessage)) {
LOG_ERR("could not peek into pipe");
throw XArch(new XArchEvalWindows());
}
if (totalBytesAvail == 0) {
return "";
}
if (!ReadFile(handle, buffer, kOutputBufferSize, &bytesRead, nullptr)) {
LOG_ERR("could not read from pipe");
throw XArch(new XArchEvalWindows());
}
return std::string(buffer, bytesRead);
}
} // namespace deskflow::platform

View file

@ -0,0 +1,57 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include "ipc/IpcServer.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <string>
namespace deskflow::platform {
namespace { // NOSONAR -- Deliberate anonymous
const auto kDefaultShutdownTimeout = 10;
}
class MSWindowsProcess
{
public:
explicit MSWindowsProcess(const std::string &command, HANDLE stdOutput = nullptr, HANDLE stdError = nullptr);
~MSWindowsProcess();
BOOL startInForeground();
BOOL startAsUser(HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
void shutdown(IpcServer &ipcServer, int timeout = kDefaultShutdownTimeout);
DWORD waitForExit();
void createPipes();
std::string readStdOutput();
std::string readStdError();
PROCESS_INFORMATION info() const
{
return m_info;
}
static void shutdown(HANDLE handle, DWORD pid, IpcServer &ipcServer, int timeout = kDefaultShutdownTimeout);
private:
void setStartupInfo(STARTUPINFO &si);
static std::string readOutput(HANDLE handle);
std::string m_command;
HANDLE m_stdOutput;
HANDLE m_stdError;
HANDLE m_outputPipe = nullptr;
HANDLE m_errorPipe = nullptr;
PROCESS_INFORMATION m_info;
BOOL m_createProcessResult = FALSE;
};
} // namespace deskflow::platform

View file

@ -158,23 +158,3 @@ BOOL MSWindowsSession::nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry)
return gotEntry; return gotEntry;
} }
std::string MSWindowsSession::getActiveDesktopName()
{
std::string result;
try {
HDESK hd = OpenInputDesktop(0, TRUE, GENERIC_READ);
if (hd != NULL) {
DWORD size;
GetUserObjectInformation(hd, UOI_NAME, NULL, 0, &size);
TCHAR *name = (TCHAR *)alloca(size + sizeof(TCHAR));
GetUserObjectInformation(hd, UOI_NAME, name, size, &size);
result = name;
CloseDesktop(hd);
}
} catch (std::exception &error) {
LOG((CLOG_ERR "failed to get active desktop name: %s", error.what()));
}
return result;
}

View file

@ -19,7 +19,6 @@ public:
MSWindowsSession(); MSWindowsSession();
~MSWindowsSession(); ~MSWindowsSession();
//!
/*! /*!
Returns true if the session ID has changed since updateActiveSession was Returns true if the session ID has changed since updateActiveSession was
called. called.
@ -27,18 +26,14 @@ public:
BOOL hasChanged(); BOOL hasChanged();
bool isProcessInSession(const char *name, PHANDLE process); bool isProcessInSession(const char *name, PHANDLE process);
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security); HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
void updateActiveSession();
DWORD getActiveSessionId() DWORD getActiveSessionId()
{ {
return m_activeSessionId; return m_activeSessionId;
} }
void updateActiveSession();
std::string getActiveDesktopName();
private: private:
BOOL nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry); BOOL nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry);

View file

@ -10,7 +10,6 @@
#include "arch/Arch.h" #include "arch/Arch.h"
#include "arch/win32/XArchWindows.h" #include "arch/win32/XArchWindows.h"
#include "base/Log.h" #include "base/Log.h"
#include "base/String.h"
#include "base/TMethodJob.h" #include "base/TMethodJob.h"
#include "base/log_outputters.h" #include "base/log_outputters.h"
#include "common/ipc.h" #include "common/ipc.h"
@ -31,9 +30,6 @@
#define CURRENT_PROCESS_ID 0 #define CURRENT_PROCESS_ID 0
#define MAXIMUM_WAIT_TIME 3 #define MAXIMUM_WAIT_TIME 3
// TODO: maybe this should be \winlogon if we have logonui.exe?
static char g_desktopName[] = "winsta0\\Default";
namespace { namespace {
std::string trimDesktopName(const std::string &nameFromTraces) std::string trimDesktopName(const std::string &nameFromTraces)
{ {
@ -50,17 +46,6 @@ std::string trimDesktopName(const std::string &nameFromTraces)
return name; return name;
} }
bool isDesktopRunnable(const std::string &desktopName)
{
const std::string winlogon = "Winlogon";
bool isNotLoginScreen = std::strncmp(desktopName.c_str(), winlogon.c_str(), winlogon.length());
const auto setting = ARCH->setting("runOnLoginScreen");
bool runOnLoginScreen = (setting.empty() || setting == "true");
return (runOnLoginScreen || isNotLoginScreen);
}
} // namespace } // namespace
enum enum
@ -79,15 +64,14 @@ MSWindowsWatchdog::MSWindowsWatchdog(
m_autoDetectCommand(autoDetectCommand), m_autoDetectCommand(autoDetectCommand),
m_monitoring(true), m_monitoring(true),
m_commandChanged(false), m_commandChanged(false),
m_stdOutWrite(NULL), m_outputWritePipe(nullptr),
m_stdOutRead(NULL), m_outputReadPipe(nullptr),
m_ipcServer(ipcServer), m_ipcServer(ipcServer),
m_ipcLogOutputter(ipcLogOutputter), m_ipcLogOutputter(ipcLogOutputter),
m_elevateProcess(false), m_elevateProcess(false),
m_processFailures(0), m_processFailures(0),
m_processRunning(false), m_processStarted(false),
m_fileLogOutputter(NULL), m_fileLogOutputter(NULL),
m_autoElevated(false),
m_ready(false), m_ready(false),
m_foreground(foreground) m_foreground(foreground)
{ {
@ -153,22 +137,30 @@ MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES s
} }
HANDLE HANDLE
MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security) MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedToken)
{ {
m_session.updateActiveSession();
// always elevate if we are at the vista/7 login screen. we could also // always elevate if we are at the vista/7 login screen. we could also
// elevate for the uac dialog (consent.exe) but this would be pointless, // elevate for the uac dialog (consent.exe) but this would be pointless,
// since deskflow would re-launch as non-elevated after the desk switch, // since deskflow would re-launch as non-elevated after the desk switch,
// and so would be unusable with the new elevated process taking focus. // and so would be unusable with the new elevated process taking focus.
if (m_elevateProcess || m_autoElevated || m_session.isProcessInSession("logonui.exe", NULL)) { if (elevatedToken || m_session.isProcessInSession("logonui.exe", NULL)) {
LOG((CLOG_DEBUG "getting elevated token, %s", (m_elevateProcess ? "elevation required" : "at login screen"))); LOG((CLOG_DEBUG "getting elevated token, %s", (elevatedToken ? "elevation required" : "at login screen")));
HANDLE process; HANDLE process;
if (!m_session.isProcessInSession("winlogon.exe", &process)) { if (!m_session.isProcessInSession("winlogon.exe", &process)) {
throw XMSWindowsWatchdogError("cannot get user token without winlogon.exe"); throw XMSWindowsWatchdogError("cannot get user token without winlogon.exe");
} }
return duplicateProcessToken(process, security); try {
return duplicateProcessToken(process, security);
} catch (XArch &e) {
LOG_ERR("failed to duplicate user token from winlogon.exe");
CloseHandle(process);
throw e;
}
} else { } else {
LOG((CLOG_DEBUG "getting non-elevated token")); LOG((CLOG_DEBUG "getting non-elevated token"));
return m_session.getUserToken(security); return m_session.getUserToken(security);
@ -177,12 +169,13 @@ MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security)
void MSWindowsWatchdog::mainLoop(void *) void MSWindowsWatchdog::mainLoop(void *)
{ {
LOG_DEBUG("starting main loop");
shutdownExistingProcesses(); shutdownExistingProcesses();
SendSas sendSasFunc = NULL; SendSas sendSasFunc = NULL;
HINSTANCE sasLib = LoadLibrary("sas.dll"); HINSTANCE sasLib = LoadLibrary("sas.dll");
if (sasLib) { if (sasLib) {
LOG((CLOG_DEBUG "found sas.dll")); LOG((CLOG_DEBUG "loaded sas.dll, used to simulate ctrl-alt-del"));
sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS"); sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
} }
@ -191,26 +184,24 @@ void MSWindowsWatchdog::mainLoop(void *)
saAttr.bInheritHandle = TRUE; saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL; saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) { if (!CreatePipe(&m_outputReadPipe, &m_outputWritePipe, &saAttr, 0)) {
LOG((CLOG_ERR "could not create output pipe"));
throw XArch(new XArchEvalWindows()); throw XArch(new XArchEvalWindows());
} }
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
while (m_monitoring) { while (m_monitoring) {
try { try {
if (m_processRunning && getCommand().empty()) { if (m_processStarted && getCommand().empty()) {
LOG((CLOG_INFO "process started but command is empty, shutting down")); LOG((CLOG_DEBUG "process started but command is empty, shutting down"));
shutdownExistingProcesses(); shutdownExistingProcesses();
m_processRunning = false;
continue; continue;
} }
if (m_processFailures != 0) { if (m_processFailures != 0) {
// increasing backoff period, maximum of 10 seconds. // increasing backoff period, maximum of 10 seconds.
int timeout = (m_processFailures * 2) < 10 ? (m_processFailures * 2) : 10; int timeout = (m_processFailures * 2) < 10 ? (m_processFailures * 2) : 10;
LOG((CLOG_INFO "backing off, wait=%ds, failures=%d", timeout, m_processFailures)); LOG((CLOG_WARN "backing off, wait=%ds, failures=%d", timeout, m_processFailures));
ARCH->sleep(timeout); ARCH->sleep(timeout);
} }
@ -230,12 +221,12 @@ void MSWindowsWatchdog::mainLoop(void *)
} }
} }
if (m_processRunning && !isProcessActive()) { if (m_processStarted && !isProcessRunning()) {
m_processFailures++; m_processFailures++;
m_processRunning = false; m_processStarted = false;
LOG((CLOG_WARN "detected application not running, pid=%d", m_processInfo.dwProcessId)); LOG((CLOG_WARN "detected application not running, pid=%d", m_process->info().dwProcessId));
} }
if (sendSasFunc != NULL) { if (sendSasFunc != NULL) {
@ -260,28 +251,34 @@ void MSWindowsWatchdog::mainLoop(void *)
} catch (std::exception &e) { } catch (std::exception &e) {
LOG((CLOG_CRIT "failed to launch, error: %s", e.what())); LOG((CLOG_CRIT "failed to launch, error: %s", e.what()));
m_processFailures++; m_processFailures++;
m_processRunning = false; m_processStarted = false;
continue; continue;
} catch (...) { } catch (...) {
LOG((CLOG_CRIT "failed to launch, unknown error.")); LOG((CLOG_CRIT "failed to launch, unknown error."));
m_processFailures++; m_processFailures++;
m_processRunning = false; m_processStarted = false;
continue; continue;
} }
} }
if (m_processRunning) { if (m_process != nullptr) {
LOG((CLOG_DEBUG "terminated running process on exit")); LOG((CLOG_DEBUG "terminated running process on exit"));
shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20); m_process->shutdown(m_ipcServer);
m_process.reset();
m_processStarted = false;
} }
LOG((CLOG_DEBUG "watchdog main thread finished")); LOG((CLOG_DEBUG "watchdog main thread finished"));
} }
bool MSWindowsWatchdog::isProcessActive() bool MSWindowsWatchdog::isProcessRunning()
{ {
if (m_process == nullptr) {
return false;
}
DWORD exitCode; DWORD exitCode;
GetExitCodeProcess(m_processInfo.hProcess, &exitCode); GetExitCodeProcess(m_process->info().hProcess, &exitCode);
return exitCode == STILL_ACTIVE; return exitCode == STILL_ACTIVE;
} }
@ -292,64 +289,63 @@ void MSWindowsWatchdog::setFileLogOutputter(FileLogOutputter *outputter)
void MSWindowsWatchdog::startProcess() void MSWindowsWatchdog::startProcess()
{ {
LOG_INFO("daemon starting new process");
if (m_command.empty()) { if (m_command.empty()) {
throw XMSWindowsWatchdogError("cannot start process, command is empty"); throw XMSWindowsWatchdogError("cannot start process, command is empty");
} }
m_commandChanged = false; m_commandChanged = false;
if (m_processRunning) { if (m_process != nullptr) {
LOG((CLOG_DEBUG "closing existing process to make way for new one")); LOG((CLOG_DEBUG "closing existing process to make way for new one"));
shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20); m_process->shutdown(m_ipcServer);
m_processRunning = false; m_process.reset();
m_processStarted = false;
} }
m_process = std::make_unique<deskflow::platform::MSWindowsProcess>(m_command, m_outputWritePipe, m_outputWritePipe);
BOOL createRet; BOOL createRet;
if (m_foreground) { if (m_foreground) {
LOG((CLOG_DEBUG "starting command in foreground")); LOG_DEBUG("starting command in foreground");
createRet = startProcessInForeground(m_command); createRet = m_process->startInForeground();
} else { } else {
LOG((CLOG_DEBUG "starting command as session user")); LOG_DEBUG("starting new process in user session");
m_session.updateActiveSession();
LOG_DEBUG("getting active desktop name");
const auto activeDesktopName = runActiveDesktopUtility();
LOG_DEBUG("active desktop name: %s", activeDesktopName.c_str());
// When we're at a UAC prompt, lock screen, or the login screen, Windows switches to the Winlogon desktop.
const auto isOnSecureDesktop = activeDesktopName == "Winlogon";
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
HANDLE userToken = getUserToken(&sa, isOnSecureDesktop || m_elevateProcess);
getActiveDesktop(&sa);
if (!isDesktopRunnable(m_activeDesktop)) {
LOG((CLOG_INFO, "starting on the login screen is disabled"));
return;
}
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
HANDLE userToken = getUserToken(&sa);
m_elevateProcess = m_autoElevated ? m_autoElevated : m_elevateProcess;
m_autoElevated = false;
// patch by Jack Zhou and Henry Tung
// set UIAccess to fix Windows 8 GUI interaction // set UIAccess to fix Windows 8 GUI interaction
DWORD uiAccess = 1; DWORD uiAccess = 1;
SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD)); SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD));
createRet = startProcessAsUser(m_command, userToken, &sa); createRet = m_process->startAsUser(userToken, &sa);
} }
if (!createRet) { if (!createRet) {
LOG((CLOG_CRIT "could not launch command")); LOG((CLOG_CRIT "could not launch command"));
DWORD exitCode = 0; DWORD exitCode = 0;
GetExitCodeProcess(m_processInfo.hProcess, &exitCode); GetExitCodeProcess(m_process->info().hProcess, &exitCode);
LOG((CLOG_ERR "exit code: %d", exitCode)); LOG((CLOG_ERR "exit code: %d", exitCode));
throw XArch(new XArchEvalWindows); throw XArch(new XArchEvalWindows);
} else { } else {
// wait for program to fail. // wait for program to fail.
ARCH->sleep(1); ARCH->sleep(1);
if (!isProcessActive()) { if (!isProcessRunning()) {
closeProcessHandles(m_processInfo.dwProcessId); m_process.reset();
throw XMSWindowsWatchdogError("process immediately stopped"); throw XMSWindowsWatchdogError("process immediately stopped");
} }
m_processRunning = true; m_processStarted = true;
m_processFailures = 0; m_processFailures = 0;
LOG((CLOG_DEBUG "started core process from daemon")); LOG((CLOG_DEBUG "started core process from daemon"));
@ -360,70 +356,9 @@ void MSWindowsWatchdog::startProcess()
} }
} }
void MSWindowsWatchdog::setStartupInfo(STARTUPINFO &si)
{
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = g_desktopName;
si.hStdError = m_stdOutWrite;
si.hStdOutput = m_stdOutWrite;
si.dwFlags |= STARTF_USESTDHANDLES;
}
BOOL MSWindowsWatchdog::startProcessInForeground(std::string &command)
{
// clear, as we're reusing process info struct
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
// show the console window when in foreground mode,
// so we can close it gracefully, but minimize it
// so it doesn't get in the way.
STARTUPINFO si;
setStartupInfo(si);
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_MINIMIZE;
BOOL result = CreateProcess(NULL, LPSTR(command.c_str()), NULL, NULL, TRUE, 0, NULL, NULL, &si, &m_processInfo);
m_children.insert(std::make_pair(m_processInfo.dwProcessId, m_processInfo));
return result;
}
BOOL MSWindowsWatchdog::startProcessAsUser(std::string &command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa)
{
// clear, as we're reusing process info struct
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
STARTUPINFO si;
setStartupInfo(si);
LPVOID environment;
BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE);
if (!blockRet) {
LOG((CLOG_ERR "could not create environment block"));
throw XArch(new XArchEvalWindows);
}
DWORD creationFlags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
// re-launch in current active user session
LOG((CLOG_INFO "starting new process"));
BOOL createRet = CreateProcessAsUser(
userToken, NULL, LPSTR(command.c_str()), sa, NULL, TRUE, creationFlags, environment, NULL, &si, &m_processInfo
);
m_children.insert(std::make_pair(m_processInfo.dwProcessId, m_processInfo));
DestroyEnvironmentBlock(environment);
CloseHandle(userToken);
return createRet;
}
void MSWindowsWatchdog::setCommand(const std::string &command, bool elevate) void MSWindowsWatchdog::setCommand(const std::string &command, bool elevate)
{ {
LOG((CLOG_INFO "service command updated")); LOG_DEBUG("command parameters updated");
m_command = command; m_command = command;
m_elevateProcess = elevate; m_elevateProcess = elevate;
m_commandChanged = true; m_commandChanged = true;
@ -463,7 +398,7 @@ void MSWindowsWatchdog::outputLoop(void *)
while (m_monitoring) { while (m_monitoring) {
DWORD bytesRead; DWORD bytesRead;
BOOL success = ReadFile(m_stdOutRead, buffer, kOutputBufferSize, &bytesRead, NULL); BOOL success = ReadFile(m_outputReadPipe, buffer, kOutputBufferSize, &bytesRead, NULL);
// assume the process has gone away? slow down // assume the process has gone away? slow down
// the reads until another one turns up. // the reads until another one turns up.
@ -472,8 +407,6 @@ void MSWindowsWatchdog::outputLoop(void *)
} else { } else {
buffer[bytesRead] = '\0'; buffer[bytesRead] = '\0';
testOutput(buffer);
m_ipcLogOutputter.write(kINFO, buffer); m_ipcLogOutputter.write(kINFO, buffer);
if (m_fileLogOutputter != NULL) { if (m_fileLogOutputter != NULL) {
@ -493,48 +426,16 @@ void MSWindowsWatchdog::outputLoop(void *)
} }
} }
void MSWindowsWatchdog::shutdownProcess(HANDLE handle, DWORD pid, int timeout)
{
DWORD exitCode;
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
return;
}
IpcShutdownMessage shutdown;
m_ipcServer.send(shutdown, IpcClientType::Node);
// wait for process to exit gracefully.
double start = ARCH->time();
while (true) {
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
// yay, we got a graceful shutdown. there should be no hook in use errors!
LOG((CLOG_DEBUG "process %d was shutdown gracefully", pid));
break;
} else {
double elapsed = (ARCH->time() - start);
if (elapsed > timeout) {
// if timeout reached, kill forcefully.
// calling TerminateProcess on deskflow is very bad!
// it causes the hook DLL to stay loaded in some apps,
// making it impossible to start deskflow again.
LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed));
TerminateProcess(handle, kExitSuccess);
break;
}
ARCH->sleep(1);
}
}
closeProcessHandles(pid);
}
void MSWindowsWatchdog::shutdownExistingProcesses() void MSWindowsWatchdog::shutdownExistingProcesses()
{ {
LOG_INFO("deamon shutting down existing processes");
if (m_process != nullptr) {
m_process->shutdown(m_ipcServer);
m_process.reset();
m_processStarted = false;
}
// first we need to take a snapshot of the running processes // first we need to take a snapshot of the running processes
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, CURRENT_PROCESS_ID); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, CURRENT_PROCESS_ID);
if (snapshot == INVALID_HANDLE_VALUE) { if (snapshot == INVALID_HANDLE_VALUE) {
@ -565,7 +466,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
_stricmp(entry.szExeFile, "deskflow-core.exe") == 0) { _stricmp(entry.szExeFile, "deskflow-core.exe") == 0) {
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
shutdownProcess(handle, entry.th32ProcessID, 10); deskflow::platform::MSWindowsProcess::shutdown(handle, entry.th32ProcessID, m_ipcServer);
CloseHandle(handle); CloseHandle(handle);
} }
} }
@ -578,64 +479,53 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
if (err != ERROR_NO_MORE_FILES) { if (err != ERROR_NO_MORE_FILES) {
// only worry about error if it's not the end of the snapshot // only worry about error if it's not the end of the snapshot
LOG((CLOG_ERR "could not get subsiquent process entry")); LOG((CLOG_ERR "could not get next process entry"));
throw XArch(new XArchEvalWindows); throw XArch(new XArchEvalWindows);
} }
} }
} }
clearAllChildren();
CloseHandle(snapshot); CloseHandle(snapshot);
m_processRunning = false;
} }
void MSWindowsWatchdog::getActiveDesktop(LPSECURITY_ATTRIBUTES security) std::string MSWindowsWatchdog::runActiveDesktopUtility()
{ {
std::string installedDir = ARCH->getInstalledDirectory(); const auto installDir = ARCH->getInstalledDirectory();
if (!installedDir.empty()) { const auto coreBinPath = installDir + "\\deskflow-server.exe";
MSWindowsSession session; std::string utilityCommand = "\"" + coreBinPath + "\" --active-desktop";
std::string name = session.getActiveDesktopName();
if (name.empty()) {
LOG((CLOG_DEBUG "no active desktop in current session"));
} else {
LOG((CLOG_INFO "active desktop name: %s", name.c_str()));
}
}
}
void MSWindowsWatchdog::testOutput(std::string buffer) LOG_DEBUG("starting active desktop utility: %s", utilityCommand.c_str());
{
// HACK: check standard output seems hacky.
size_t i = buffer.find(g_activeDesktop);
if (i != std::string::npos) {
size_t s = sizeof(g_activeDesktop);
std::string defaultScreen = "Default";
m_activeDesktop = trimDesktopName(buffer.substr(i + s - 1));
m_autoElevated = std::strncmp(m_activeDesktop.c_str(), defaultScreen.c_str(), defaultScreen.length());
ARCH->lockMutex(m_mutex); SECURITY_ATTRIBUTES sa;
m_ready = true; ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
ARCH->broadcastCondVar(m_condVar); HANDLE userToken = getUserToken(&sa, true);
ARCH->unlockMutex(m_mutex);
}
}
void MSWindowsWatchdog::closeProcessHandles(unsigned long pid, bool removeFromMap) deskflow::platform::MSWindowsProcess process(utilityCommand);
{ process.createPipes();
auto processInfo = m_children.find(pid);
if (processInfo != m_children.end()) {
CloseHandle(processInfo->second.hProcess);
CloseHandle(processInfo->second.hThread);
if (removeFromMap) {
m_children.erase(processInfo);
}
}
}
void MSWindowsWatchdog::clearAllChildren() if (!process.startAsUser(userToken, &sa)) {
{ LOG_ERR("could not start active desktop process");
for (auto it = m_children.begin(); it != m_children.end(); ++it) { throw XArch(new XArchEvalWindows());
closeProcessHandles(it->second.dwThreadId, false);
} }
m_children.clear();
LOG_DEBUG("started active desktop process, pid=%d", process.info().dwProcessId);
if (const auto exitCode = process.waitForExit(); exitCode != kExitSuccess) {
LOG_ERR("active desktop process, exit code: %d", exitCode);
throw XMSWindowsWatchdogError("could not get active desktop");
}
LOG_DEBUG("reading active desktop std error");
if (const auto error = process.readStdError(); !error.empty()) {
LOG_WARN("active desktop process, error: %s", error.c_str());
}
LOG_DEBUG("reading active desktop std output");
auto output = process.readStdOutput();
if (output.empty()) {
LOG_ERR("could not get active desktop, no output");
throw XMSWindowsWatchdogError("could not get active desktop");
}
output.erase(output.find_last_not_of("\r\n") + 1);
return output;
} }

View file

@ -9,14 +9,15 @@
#include "arch/IArchMultithread.h" #include "arch/IArchMultithread.h"
#include "deskflow/XDeskflow.h" #include "deskflow/XDeskflow.h"
#include "platform/MSWindowsProcess.h"
#include "platform/MSWindowsSession.h" #include "platform/MSWindowsSession.h"
#include <map>
#include <string>
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <Windows.h> #include <Windows.h>
#include <memory>
#include <string>
class Thread; class Thread;
class IpcLogOutputter; class IpcLogOutputter;
class IpcServer; class IpcServer;
@ -32,36 +33,28 @@ public:
std::string getCommand() const; std::string getCommand() const;
void setCommand(const std::string &command, bool elevate); void setCommand(const std::string &command, bool elevate);
void stop(); void stop();
bool isProcessActive(); bool isProcessRunning();
void setFileLogOutputter(FileLogOutputter *outputter); void setFileLogOutputter(FileLogOutputter *outputter);
private: private:
void mainLoop(void *); void mainLoop(void *);
void outputLoop(void *); void outputLoop(void *);
void shutdownProcess(HANDLE handle, DWORD pid, int timeout);
void shutdownExistingProcesses(); void shutdownExistingProcesses();
HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security); HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security); HANDLE getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedToken);
void startProcess(); void startProcess();
BOOL startProcessAsUser(std::string &command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
BOOL startProcessInForeground(std::string &command);
void sendSas(); void sendSas();
void getActiveDesktop(LPSECURITY_ATTRIBUTES security);
void testOutput(std::string buffer);
void setStartupInfo(STARTUPINFO &si); void setStartupInfo(STARTUPINFO &si);
void checkChildren();
/** /**
* @brief This closes the handles held to a child thread * @brief Re-run the process to get the active desktop name.
* @param pid the ID of the process to kill, will do nothing if PID is not a *
* valid child * It is necessary to run a utility process because the daemon runs in session 0, which does not
* @param removeFromMap should the function remove the item from the children * have access to the active desktop, and so cannot query it's name.
* map *
* @return std::string The name of the active desktop.
*/ */
void closeProcessHandles(unsigned long pid, bool removeFromMap = true); std::string runActiveDesktopUtility();
/**
* @brief This kills off all children's handles created by this process
*/
void clearAllChildren();
private: private:
Thread *m_thread; Thread *m_thread;
@ -69,28 +62,22 @@ private:
std::string m_command; std::string m_command;
bool m_monitoring; bool m_monitoring;
bool m_commandChanged; bool m_commandChanged;
HANDLE m_stdOutWrite; HANDLE m_outputWritePipe;
HANDLE m_stdOutRead; HANDLE m_outputReadPipe;
Thread *m_outputThread; Thread *m_outputThread;
IpcServer &m_ipcServer; IpcServer &m_ipcServer;
IpcLogOutputter &m_ipcLogOutputter; IpcLogOutputter &m_ipcLogOutputter;
bool m_elevateProcess; bool m_elevateProcess;
MSWindowsSession m_session; MSWindowsSession m_session;
PROCESS_INFORMATION m_processInfo;
int m_processFailures; int m_processFailures;
bool m_processRunning; bool m_processStarted;
FileLogOutputter *m_fileLogOutputter; FileLogOutputter *m_fileLogOutputter;
bool m_autoElevated;
ArchMutex m_mutex; ArchMutex m_mutex;
ArchCond m_condVar; ArchCond m_condVar;
bool m_ready; bool m_ready;
bool m_foreground; bool m_foreground;
std::string m_activeDesktop; std::string m_activeDesktop;
std::unique_ptr<deskflow::platform::MSWindowsProcess> m_process;
/// @brief Save the info of all process made
/// We will use this to track all processes we make and
/// kill off handels and children that we no longer need
std::map<unsigned long, PROCESS_INFORMATION> m_children;
}; };
//! Relauncher error //! Relauncher error