From 9b1b081cfdb1c0fb6457278775e0823f8bc10f62 Mon Sep 17 00:00:00 2001 From: Unit 193 Date: Wed, 25 Apr 2018 18:07:30 -0400 Subject: Import Upstream version 2.0.0+dfsg --- src/lib/arch/win32/ArchDaemonWindows.cpp | 704 +++++++++++++++++++++++++++++++ 1 file changed, 704 insertions(+) create mode 100644 src/lib/arch/win32/ArchDaemonWindows.cpp (limited to 'src/lib/arch/win32/ArchDaemonWindows.cpp') diff --git a/src/lib/arch/win32/ArchDaemonWindows.cpp b/src/lib/arch/win32/ArchDaemonWindows.cpp new file mode 100644 index 0000000..efcf235 --- /dev/null +++ b/src/lib/arch/win32/ArchDaemonWindows.cpp @@ -0,0 +1,704 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "arch/win32/ArchDaemonWindows.h" +#include "arch/win32/ArchMiscWindows.h" +#include "arch/win32/XArchWindows.h" +#include "arch/Arch.h" +#include "common/stdvector.h" + +#include + +// +// ArchDaemonWindows +// + +ArchDaemonWindows* ArchDaemonWindows::s_daemon = NULL; + +ArchDaemonWindows::ArchDaemonWindows() : +m_daemonThreadID(0) +{ + m_quitMessage = RegisterWindowMessage("BarrierDaemonExit"); +} + +ArchDaemonWindows::~ArchDaemonWindows() +{ + // do nothing +} + +int +ArchDaemonWindows::runDaemon(RunFunc runFunc) +{ + assert(s_daemon != NULL); + return s_daemon->doRunDaemon(runFunc); +} + +void +ArchDaemonWindows::daemonRunning(bool running) +{ + if (s_daemon != NULL) { + s_daemon->doDaemonRunning(running); + } +} + +UINT +ArchDaemonWindows::getDaemonQuitMessage() +{ + if (s_daemon != NULL) { + return s_daemon->doGetDaemonQuitMessage(); + } + else { + return 0; + } +} + +void +ArchDaemonWindows::daemonFailed(int result) +{ + assert(s_daemon != NULL); + throw XArchDaemonRunFailed(result); +} + +void +ArchDaemonWindows::installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonInstallFailed(new XArchEvalWindows); + } + + // create the service + SC_HANDLE service = CreateService( + mgr, + name, + name, + 0, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + pathname, + NULL, + NULL, + dependencies, + NULL, + NULL); + + if (service == NULL) { + // can't create service + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_EXISTS) { + CloseServiceHandle(mgr); + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + } + else { + // done with service (but only try to close if not null) + CloseServiceHandle(service); + } + + // done with manager + CloseServiceHandle(mgr); + + // open the registry key for this service + HKEY key = openNTServicesKey(); + key = ArchMiscWindows::addKey(key, name); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + try { + uninstallDaemon(name); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + + // set the description + ArchMiscWindows::setValue(key, _T("Description"), description); + + // set command line + key = ArchMiscWindows::addKey(key, _T("Parameters")); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + ArchMiscWindows::closeKey(key); + try { + uninstallDaemon(name); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + ArchMiscWindows::setValue(key, _T("CommandLine"), commandLine); + + // done with registry + ArchMiscWindows::closeKey(key); +} + +void +ArchDaemonWindows::uninstallDaemon(const char* name) +{ + // remove parameters for this service. ignore failures. + HKEY key = openNTServicesKey(); + key = ArchMiscWindows::openKey(key, name); + if (key != NULL) { + ArchMiscWindows::deleteKey(key, _T("Parameters")); + ArchMiscWindows::closeKey(key); + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonUninstallFailed(new XArchEvalWindows); + } + + // open the service. oddly, you must open a service to delete it. + SC_HANDLE service = OpenService(mgr, name, DELETE | SERVICE_STOP); + if (service == NULL) { + DWORD err = GetLastError(); + CloseServiceHandle(mgr); + if (err != ERROR_SERVICE_DOES_NOT_EXIST) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } + + // stop the service. we don't care if we fail. + SERVICE_STATUS status; + ControlService(service, SERVICE_CONTROL_STOP, &status); + + // delete the service + const bool okay = (DeleteService(service) == 0); + const DWORD err = GetLastError(); + + // clean up + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + // give windows a chance to remove the service before + // we check if it still exists. + ARCH->sleep(1); + + // handle failure. ignore error if service isn't installed anymore. + if (!okay && isDaemonInstalled(name)) { + if (err == ERROR_SUCCESS) { + // this seems to occur even though the uninstall was successful. + // it could be a timing issue, i.e., isDaemonInstalled is + // called too soon. i've added a sleep to try and stop this. + return; + } + if (err == ERROR_IO_PENDING) { + // this seems to be a spurious error + return; + } + if (err != ERROR_SERVICE_MARKED_FOR_DELETE) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } +} + +int +ArchDaemonWindows::daemonize(const char* name, DaemonFunc func) +{ + assert(name != NULL); + assert(func != NULL); + + // save daemon function + m_daemonFunc = func; + + // construct the service entry + SERVICE_TABLE_ENTRY entry[2]; + entry[0].lpServiceName = const_cast(name); + entry[0].lpServiceProc = &ArchDaemonWindows::serviceMainEntry; + entry[1].lpServiceName = NULL; + entry[1].lpServiceProc = NULL; + + // hook us up to the service control manager. this won't return + // (if successful) until the processes have terminated. + s_daemon = this; + if (StartServiceCtrlDispatcher(entry) == 0) { + // StartServiceCtrlDispatcher failed + s_daemon = NULL; + throw XArchDaemonFailed(new XArchEvalWindows); + } + + s_daemon = NULL; + return m_daemonResult; +} + +bool +ArchDaemonWindows::canInstallDaemon(const char* /*name*/) +{ + // check if we can open service manager for write + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + return false; + } + CloseServiceHandle(mgr); + + // check if we can open the registry key + HKEY key = openNTServicesKey(); + ArchMiscWindows::closeKey(key); + + return (key != NULL); +} + +bool +ArchDaemonWindows::isDaemonInstalled(const char* name) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + return false; + } + + // open the service + SC_HANDLE service = OpenService(mgr, name, GENERIC_READ); + + // clean up + if (service != NULL) { + CloseServiceHandle(service); + } + CloseServiceHandle(mgr); + + return (service != NULL); +} + +HKEY +ArchDaemonWindows::openNTServicesKey() +{ + static const char* s_keyNames[] = { + _T("SYSTEM"), + _T("CurrentControlSet"), + _T("Services"), + NULL + }; + + return ArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +bool +ArchDaemonWindows::isRunState(DWORD state) +{ + switch (state) { + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + case SERVICE_RUNNING: + return true; + + default: + return false; + } +} + +int +ArchDaemonWindows::doRunDaemon(RunFunc run) +{ + // should only be called from DaemonFunc + assert(m_serviceMutex != NULL); + assert(run != NULL); + + // create message queue for this thread + MSG dummy; + PeekMessage(&dummy, NULL, 0, 0, PM_NOREMOVE); + + int result = 0; + ARCH->lockMutex(m_serviceMutex); + m_daemonThreadID = GetCurrentThreadId(); + while (m_serviceState != SERVICE_STOPPED) { + // wait until we're told to start + while (!isRunState(m_serviceState) && + m_serviceState != SERVICE_STOP_PENDING) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + + // run unless told to stop + if (m_serviceState != SERVICE_STOP_PENDING) { + ARCH->unlockMutex(m_serviceMutex); + try { + result = run(); + } + catch (...) { + ARCH->lockMutex(m_serviceMutex); + setStatusError(0); + m_serviceState = SERVICE_STOPPED; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + ARCH->unlockMutex(m_serviceMutex); + throw; + } + ARCH->lockMutex(m_serviceMutex); + } + + // notify of new state + if (m_serviceState == SERVICE_PAUSE_PENDING) { + m_serviceState = SERVICE_PAUSED; + } + else { + m_serviceState = SERVICE_STOPPED; + } + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); + return result; +} + +void +ArchDaemonWindows::doDaemonRunning(bool running) +{ + ARCH->lockMutex(m_serviceMutex); + if (running) { + m_serviceState = SERVICE_RUNNING; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); +} + +UINT +ArchDaemonWindows::doGetDaemonQuitMessage() +{ + return m_quitMessage; +} + +void +ArchDaemonWindows::setStatus(DWORD state) +{ + setStatus(state, 0, 0); +} + +void +ArchDaemonWindows::setStatus(DWORD state, DWORD step, DWORD waitHint) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = state; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = step; + status.dwWaitHint = waitHint; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +ArchDaemonWindows::setStatusError(DWORD error) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = SERVICE_STOPPED; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + status.dwServiceSpecificExitCode = error; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +ArchDaemonWindows::serviceMain(DWORD argc, LPTSTR* argvIn) +{ + typedef std::vector ArgList; + typedef std::vector Arguments; + const char** argv = const_cast(argvIn); + + // create synchronization objects + m_serviceMutex = ARCH->newMutex(); + m_serviceCondVar = ARCH->newCondVar(); + + // register our service handler function + m_statusHandle = RegisterServiceCtrlHandler(argv[0], + &ArchDaemonWindows::serviceHandlerEntry); + if (m_statusHandle == 0) { + // cannot start as service + m_daemonResult = -1; + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); + return; + } + + // tell service control manager that we're starting + m_serviceState = SERVICE_START_PENDING; + setStatus(m_serviceState, 0, 10000); + + std::string commandLine; + + // if no arguments supplied then try getting them from the registry. + // the first argument doesn't count because it's the service name. + Arguments args; + ArgList myArgv; + if (argc <= 1) { + // read command line + HKEY key = openNTServicesKey(); + key = ArchMiscWindows::openKey(key, argvIn[0]); + key = ArchMiscWindows::openKey(key, _T("Parameters")); + if (key != NULL) { + commandLine = ArchMiscWindows::readValueString(key, + _T("CommandLine")); + } + + // if the command line isn't empty then parse and use it + if (!commandLine.empty()) { + // parse, honoring double quoted substrings + std::string::size_type i = commandLine.find_first_not_of(" \t"); + while (i != std::string::npos && i != commandLine.size()) { + // find end of string + std::string::size_type e; + if (commandLine[i] == '\"') { + // quoted. find closing quote. + ++i; + e = commandLine.find("\"", i); + + // whitespace must follow closing quote + if (e == std::string::npos || + (e + 1 != commandLine.size() && + commandLine[e + 1] != ' ' && + commandLine[e + 1] != '\t')) { + args.clear(); + break; + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + else { + // unquoted. find next whitespace. + e = commandLine.find_first_of(" \t", i); + if (e == std::string::npos) { + e = commandLine.size(); + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + + // next argument + i = commandLine.find_first_not_of(" \t", i); + } + + // service name goes first + myArgv.push_back(argv[0]); + + // get pointers + for (size_t j = 0; j < args.size(); ++j) { + myArgv.push_back(args[j].c_str()); + } + + // adjust argc/argv + argc = (DWORD)myArgv.size(); + argv = &myArgv[0]; + } + } + + m_commandLine = commandLine; + + try { + // invoke daemon function + m_daemonResult = m_daemonFunc(static_cast(argc), argv); + } + catch (XArchDaemonRunFailed& e) { + setStatusError(e.m_result); + m_daemonResult = -1; + } + catch (...) { + setStatusError(1); + m_daemonResult = -1; + } + + // clean up + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); + + // we're going to exit now, so set status to stopped + m_serviceState = SERVICE_STOPPED; + setStatus(m_serviceState, 0, 10000); +} + +void WINAPI +ArchDaemonWindows::serviceMainEntry(DWORD argc, LPTSTR* argv) +{ + s_daemon->serviceMain(argc, argv); +} + +void +ArchDaemonWindows::serviceHandler(DWORD ctrl) +{ + assert(m_serviceMutex != NULL); + assert(m_serviceCondVar != NULL); + + ARCH->lockMutex(m_serviceMutex); + + // ignore request if service is already stopped + if (s_daemon == NULL || m_serviceState == SERVICE_STOPPED) { + if (s_daemon != NULL) { + setStatus(m_serviceState); + } + ARCH->unlockMutex(m_serviceMutex); + return; + } + + switch (ctrl) { + case SERVICE_CONTROL_PAUSE: + m_serviceState = SERVICE_PAUSE_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + case SERVICE_CONTROL_CONTINUE: + // FIXME -- maybe should flush quit messages from queue + m_serviceState = SERVICE_CONTINUE_PENDING; + setStatus(m_serviceState, 0, 5000); + ARCH->broadcastCondVar(m_serviceCondVar); + break; + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + m_serviceState = SERVICE_STOP_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + ARCH->broadcastCondVar(m_serviceCondVar); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + default: + // unknown service command + // fall through + + case SERVICE_CONTROL_INTERROGATE: + setStatus(m_serviceState); + break; + } + + ARCH->unlockMutex(m_serviceMutex); +} + +void WINAPI +ArchDaemonWindows::serviceHandlerEntry(DWORD ctrl) +{ + s_daemon->serviceHandler(ctrl); +} + +void +ArchDaemonWindows::start(const char* name) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // open the service + SC_HANDLE service = OpenService( + mgr, name, SERVICE_START); + + if (service == NULL) { + CloseServiceHandle(mgr); + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // start the service + if (!StartService(service, 0, NULL)) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } +} + +void +ArchDaemonWindows::stop(const char* name) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // open the service + SC_HANDLE service = OpenService( + mgr, name, + SERVICE_STOP | SERVICE_QUERY_STATUS); + + if (service == NULL) { + CloseServiceHandle(mgr); + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // ask the service to stop, asynchronously + SERVICE_STATUS ss; + if (!ControlService(service, SERVICE_CONTROL_STOP, &ss)) { + DWORD dwErrCode = GetLastError(); + if (dwErrCode != ERROR_SERVICE_NOT_ACTIVE) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + } +} + +void +ArchDaemonWindows::installDaemon() +{ + // install default daemon if not already installed. + if (!isDaemonInstalled(DEFAULT_DAEMON_NAME)) { + char path[MAX_PATH]; + GetModuleFileName(ArchMiscWindows::instanceWin32(), path, MAX_PATH); + + // wrap in quotes so a malicious user can't start \Program.exe as admin. + std::stringstream ss; + ss << '"'; + ss << path; + ss << '"'; + + installDaemon(DEFAULT_DAEMON_NAME, DEFAULT_DAEMON_INFO, ss.str().c_str(), "", ""); + } + + start(DEFAULT_DAEMON_NAME); +} + +void +ArchDaemonWindows::uninstallDaemon() +{ + // remove service if installed. + if (isDaemonInstalled(DEFAULT_DAEMON_NAME)) { + uninstallDaemon(DEFAULT_DAEMON_NAME); + } +} -- cgit v1.2.3