diff options
Diffstat (limited to 'win/rfb_win32/Service.cxx')
-rw-r--r-- | win/rfb_win32/Service.cxx | 645 |
1 files changed, 645 insertions, 0 deletions
diff --git a/win/rfb_win32/Service.cxx b/win/rfb_win32/Service.cxx new file mode 100644 index 00000000..2b11a22d --- /dev/null +++ b/win/rfb_win32/Service.cxx @@ -0,0 +1,645 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Service.cxx + +#include <rfb_win32/Service.h> +#include <rfb_win32/MsgWindow.h> +#include <rfb_win32/DynamicFn.h> +#include <rfb_win32/ModuleFileName.h> +#include <rfb_win32/Registry.h> +#include <rfb_win32/OSVersion.h> +#include <rfb/Threading.h> +#include <logmessages/messages.h> +#include <rdr/Exception.h> +#include <rfb/LogWriter.h> + + +using namespace rdr; +using namespace rfb; +using namespace win32; + +static LogWriter vlog("Service"); + + +// - Internal service implementation functions + +Service* service = 0; + +VOID WINAPI serviceHandler(DWORD control) { + switch (control) { + case SERVICE_CONTROL_INTERROGATE: + vlog.info("cmd: report status"); + service->setStatus(); + return; + case SERVICE_CONTROL_PARAMCHANGE: + vlog.info("cmd: param change"); + service->readParams(); + return; + case SERVICE_CONTROL_SHUTDOWN: + vlog.info("cmd: OS shutdown"); + service->osShuttingDown(); + return; + case SERVICE_CONTROL_STOP: + vlog.info("cmd: stop"); + service->setStatus(SERVICE_STOP_PENDING); + service->stop(); + return; + }; + vlog.debug("cmd: unknown %lu", control); +} + + +// -=- Message window derived class used under Win9x to implement stopService + +#define WM_SMSG_SERVICE_STOP WM_USER + +class ServiceMsgWindow : public MsgWindow { +public: + ServiceMsgWindow(const TCHAR* name) : MsgWindow(name) {} + LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_SMSG_SERVICE_STOP: + service->stop(); + return TRUE; + } + return MsgWindow::processMessage(msg, wParam, lParam); + } + + static const TCHAR* baseName; +}; + +const TCHAR* ServiceMsgWindow::baseName = _T("ServiceWindow:"); + + +// -=- Service main procedure, used under WinNT/2K/XP by the SCM + +VOID WINAPI serviceProc(DWORD dwArgc, LPTSTR* lpszArgv) { + vlog.debug("entering %s serviceProc", service->getName()); + vlog.info("registering handler..."); + service->status_handle = RegisterServiceCtrlHandler(service->getName(), serviceHandler); + if (!service->status_handle) { + DWORD err = GetLastError(); + vlog.error("failed to register handler: %lu", err); + ExitProcess(err); + } + vlog.debug("registered handler (%lx)", service->status_handle); + service->setStatus(SERVICE_START_PENDING); + vlog.debug("entering %s serviceMain", service->getName()); + service->status.dwWin32ExitCode = service->serviceMain(dwArgc, lpszArgv); + vlog.debug("leaving %s serviceMain", service->getName()); + service->setStatus(SERVICE_STOPPED); +} + + +// -=- Service + +Service::Service(const TCHAR* name_) : name(name_) { + vlog.debug("Service"); + status_handle = 0; + status.dwControlsAccepted = SERVICE_CONTROL_INTERROGATE | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = 0; + status.dwWaitHint = 30000; + status.dwCurrentState = SERVICE_STOPPED; +} + +void +Service::start() { + if (osVersion.isPlatformNT) { + SERVICE_TABLE_ENTRY entry[2]; + entry[0].lpServiceName = (TCHAR*)name; + entry[0].lpServiceProc = serviceProc; + entry[1].lpServiceName = NULL; + entry[1].lpServiceProc = NULL; + vlog.debug("entering dispatcher"); + if (!SetProcessShutdownParameters(0x100, 0)) + vlog.error("unable to set shutdown parameters: %d", GetLastError()); + service = this; + if (!StartServiceCtrlDispatcher(entry)) + throw SystemException("unable to start service", GetLastError()); + } else { + + // - Create the service window, so the service can be stopped + TCharArray wndName(_tcslen(getName()) + _tcslen(ServiceMsgWindow::baseName) + 1); + _tcscpy(wndName.buf, ServiceMsgWindow::baseName); + _tcscat(wndName.buf, getName()); + ServiceMsgWindow service_window(wndName.buf); + + // - Locate the RegisterServiceProcess function + typedef DWORD (WINAPI * _RegisterServiceProcess_proto)(DWORD, DWORD); + DynamicFn<_RegisterServiceProcess_proto> _RegisterServiceProcess(_T("kernel32.dll"), "RegisterServiceProcess"); + if (!_RegisterServiceProcess.isValid()) + throw Exception("unable to find RegisterServiceProcess"); + + // - Run the service + (*_RegisterServiceProcess)(NULL, 1); + service = this; + serviceMain(0, 0); + (*_RegisterServiceProcess)(NULL, 0); + } +} + +void +Service::setStatus() { + setStatus(status.dwCurrentState); +} + +void +Service::setStatus(DWORD state) { + if (!osVersion.isPlatformNT) + return; + if (status_handle == 0) { + vlog.debug("warning - cannot setStatus"); + return; + } + status.dwCurrentState = state; + status.dwCheckPoint++; + if (!SetServiceStatus(status_handle, &status)) { + status.dwCurrentState = SERVICE_STOPPED; + status.dwWin32ExitCode = GetLastError(); + vlog.error("unable to set service status:%u", status.dwWin32ExitCode); + } + vlog.debug("set status to %u(%u)", state, status.dwCheckPoint); +} + +Service::~Service() { + vlog.debug("~Service"); + service = 0; +} + + +// Find out whether this process is running as the WinVNC service +bool thisIsService() { + return service && (service->status.dwCurrentState != SERVICE_STOPPED); +} + + +// -=- Desktop handling code + +// Switch the current thread to the specified desktop +static bool +switchToDesktop(HDESK desktop) { + HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId()); + if (!SetThreadDesktop(desktop)) { + vlog.debug("switchToDesktop failed:%u", GetLastError()); + return false; + } + if (!CloseDesktop(old_desktop)) + vlog.debug("unable to close old desktop:%u", GetLastError()); + return true; +} + +// Determine whether the thread's current desktop is the input one +static bool +inputDesktopSelected() { + HDESK current = GetThreadDesktop(GetCurrentThreadId()); + HDESK input = OpenInputDesktop(0, FALSE, + DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE); + if (!input) { + vlog.debug("unable to OpenInputDesktop(1):%u", GetLastError()); + return false; + } + + DWORD size; + char currentname[256]; + char inputname[256]; + + if (!GetUserObjectInformation(current, UOI_NAME, currentname, 256, &size)) { + vlog.debug("unable to GetUserObjectInformation(1):%u", GetLastError()); + CloseDesktop(input); + return false; + } + if (!GetUserObjectInformation(input, UOI_NAME, inputname, 256, &size)) { + vlog.debug("unable to GetUserObjectInformation(2):%u", GetLastError()); + CloseDesktop(input); + return false; + } + if (!CloseDesktop(input)) + vlog.debug("unable to close input desktop:%u", GetLastError()); + + // *** vlog.debug("current=%s, input=%s", currentname, inputname); + bool result = strcmp(currentname, inputname) == 0; + return result; +} + +// Switch the current thread into the input desktop +static bool +selectInputDesktop() { + // - Open the input desktop + HDESK desktop = OpenInputDesktop(0, FALSE, + DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE); + if (!desktop) { + vlog.debug("unable to OpenInputDesktop(2):%u", GetLastError()); + return false; + } + + // - Switch into it + if (!switchToDesktop(desktop)) { + CloseDesktop(desktop); + return false; + } + + // *** + DWORD size = 256; + char currentname[256]; + if (GetUserObjectInformation(desktop, UOI_NAME, currentname, 256, &size)) { + vlog.debug("switched to %s", currentname); + } + // *** + + vlog.debug("switched to input desktop"); + + return true; +} + + +// -=- Access points to desktop-switching routines + +bool +rfb::win32::desktopChangeRequired() { + if (!osVersion.isPlatformNT) + return false; + + return !inputDesktopSelected(); +} + +bool +rfb::win32::changeDesktop() { + if (!osVersion.isPlatformNT) + return true; + if (osVersion.cannotSwitchDesktop) + return false; + + return selectInputDesktop(); +} + + +// -=- Ctrl-Alt-Del emulation + +class CADThread : public Thread { +public: + CADThread() : Thread("CtrlAltDel Emulator"), result(false) {} + virtual void run() { + HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId()); + + if (switchToDesktop(OpenDesktop(_T("Winlogon"), 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE))) { + PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE)); + switchToDesktop(old_desktop); + result = true; + } + } + bool result; +}; + +bool +rfb::win32::emulateCtrlAltDel() { + if (!osVersion.isPlatformNT) + return false; + + CADThread* cad_thread = new CADThread(); + vlog.debug("emulate Ctrl-Alt-Del"); + if (cad_thread) { + cad_thread->start(); + cad_thread->join(); + bool result = cad_thread->result; + delete cad_thread; + return result; + } + return false; +} + + +// -=- Application Event Log target Logger class + +class Logger_EventLog : public Logger { +public: + Logger_EventLog(const TCHAR* srcname) : Logger("EventLog") { + eventlog = RegisterEventSource(NULL, srcname); + if (!eventlog) + printf("Unable to open event log:%ld\n", GetLastError()); + } + ~Logger_EventLog() { + if (eventlog) + DeregisterEventSource(eventlog); + } + + virtual void write(int level, const char *logname, const char *message) { + if (!eventlog) return; + TStr log(logname), msg(message); + const TCHAR* strings[] = {log, msg}; + WORD type = EVENTLOG_INFORMATION_TYPE; + if (level == 0) type = EVENTLOG_ERROR_TYPE; + if (!ReportEvent(eventlog, type, 0, VNC4LogMessage, NULL, 2, 0, strings, NULL)) { + // *** It's not at all clear what is the correct behaviour if this fails... + printf("ReportEvent failed:%ld\n", GetLastError()); + } + } + +protected: + HANDLE eventlog; +}; + +static Logger_EventLog* logger = 0; + +bool rfb::win32::initEventLogLogger(const TCHAR* srcname) { + if (logger) + return false; + if (osVersion.isPlatformNT) { + logger = new Logger_EventLog(srcname); + logger->registerLogger(); + return true; + } else { + return false; + } +} + + +// -=- Registering and unregistering the service + +bool rfb::win32::registerService(const TCHAR* name, const TCHAR* desc, + int argc, const char* argv[]) { + + // - Initialise the default service parameters + const TCHAR* defaultcmdline; + if (osVersion.isPlatformNT) + defaultcmdline = _T("-service"); + else + defaultcmdline = _T("-noconsole -service"); + + // - Get the full pathname of our executable + ModuleFileName buffer; + + // - Calculate the command-line length + int cmdline_len = _tcslen(buffer.buf) + 4; + int i; + for (i=0; i<argc; i++) { + cmdline_len += strlen(argv[i]) + 3; + } + + // - Add the supplied extra parameters to the command line + TCharArray cmdline(cmdline_len+_tcslen(defaultcmdline)); + _stprintf(cmdline.buf, _T("\"%s\" %s"), buffer.buf, defaultcmdline); + for (i=0; i<argc; i++) { + _tcscat(cmdline.buf, _T(" \"")); + _tcscat(cmdline.buf, TStr(argv[i])); + _tcscat(cmdline.buf, _T("\"")); + } + + // - Register the service + + if (osVersion.isPlatformNT) { + + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + + ServiceHandle service = CreateService(scm, + name, desc, SC_MANAGER_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, + cmdline.buf, NULL, NULL, NULL, NULL, NULL); + if (!service) + throw rdr::SystemException("unable to create service", GetLastError()); + + // - Register the event log source + RegKey hk, hk2; + + hk2.createKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application")); + hk.createKey(hk2, name); + + for (i=_tcslen(buffer.buf); i>0; i--) { + if (buffer.buf[i] == _T('\\')) { + buffer.buf[i+1] = 0; + break; + } + } + + const TCHAR* dllFilename = _T("logmessages.dll"); + TCharArray dllPath(_tcslen(buffer.buf) + _tcslen(dllFilename) + 1); + _tcscpy(dllPath.buf, buffer.buf); + _tcscat(dllPath.buf, dllFilename); + + hk.setExpandString(_T("EventMessageFile"), dllPath.buf); + hk.setInt(_T("TypesSupported"), EVENTLOG_ERROR_TYPE | EVENTLOG_INFORMATION_TYPE); + + } else { + + RegKey services; + services.createKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices")); + services.setString(name, cmdline.buf); + + } + + Sleep(500); + + return true; +} + +bool rfb::win32::unregisterService(const TCHAR* name) { + if (osVersion.isPlatformNT) { + + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Create the service + ServiceHandle service = OpenService(scm, name, SC_MANAGER_ALL_ACCESS); + if (!service) + throw rdr::SystemException("unable to locate the service", GetLastError()); + if (!DeleteService(service)) + throw rdr::SystemException("unable to remove the service", GetLastError()); + + // - Register the event log source + RegKey hk; + hk.openKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application")); + hk.deleteKey(name); + + } else { + + RegKey services; + services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices")); + services.deleteValue(name); + + } + + Sleep(500); + + return true; +} + + +// -=- Starting and stopping the service + +HWND findServiceWindow(const TCHAR* name) { + TCharArray wndName(_tcslen(ServiceMsgWindow::baseName)+_tcslen(name)+1); + _tcscpy(wndName.buf, ServiceMsgWindow::baseName); + _tcscat(wndName.buf, name); + vlog.debug("searching for %s window", CStr(wndName.buf)); + return FindWindow(0, wndName.buf); +} + +bool rfb::win32::startService(const TCHAR* name) { + + if (osVersion.isPlatformNT) { + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Locate the service + ServiceHandle service = OpenService(scm, name, SERVICE_START); + if (!service) + throw rdr::SystemException("unable to open the service", GetLastError()); + + // - Start the service + if (!StartService(service, 0, NULL)) + throw rdr::SystemException("unable to start the service", GetLastError()); + } else { + // - Check there is no service window + if (findServiceWindow(name)) + throw rdr::Exception("the service is already running"); + + // - Find the RunServices registry key + RegKey services; + services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices")); + + // - Read the command-line from it + TCharArray cmdLine = services.getString(name); + + // - Start the service + PROCESS_INFORMATION proc_info; + STARTUPINFO startup_info; + ZeroMemory(&startup_info, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + if (!CreateProcess(0, cmdLine.buf, 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, 0, &startup_info, &proc_info)) { + throw SystemException("unable to start service", GetLastError()); + } + } + + Sleep(500); + + return true; +} + +bool rfb::win32::stopService(const TCHAR* name) { + if (osVersion.isPlatformNT) { + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Locate the service + ServiceHandle service = OpenService(scm, name, SERVICE_STOP); + if (!service) + throw rdr::SystemException("unable to open the service", GetLastError()); + + // - Start the service + SERVICE_STATUS status; + if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) + throw rdr::SystemException("unable to stop the service", GetLastError()); + + } else { + // - Find the service window + HWND service_window = findServiceWindow(name); + if (!service_window) + throw Exception("unable to locate running service"); + + // Tell it to quit + vlog.debug("sending service stop request"); + if (!SendMessage(service_window, WM_SMSG_SERVICE_STOP, 0, 0)) + throw Exception("unable to stop service"); + + // Check it's quitting... + DWORD process_id = 0; + HANDLE process = 0; + if (!GetWindowThreadProcessId(service_window, &process_id)) + throw SystemException("unable to verify service has quit", GetLastError()); + process = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, process_id); + if (!process) + throw SystemException("unable to obtain service handle", GetLastError()); + int retries = 5; + vlog.debug("checking status"); + while (retries-- && (WaitForSingleObject(process, 1000) != WAIT_OBJECT_0)) {} + if (!retries) { + vlog.debug("failed to quit - terminating"); + // May not have quit because of silly Win9x registry watching bug.. + if (!TerminateProcess(process, 1)) + throw SystemException("unable to terminate process!", GetLastError()); + throw Exception("service failed to quit - called TerminateProcess"); + } + } + + Sleep(500); + + return true; +} + +DWORD rfb::win32::getServiceState(const TCHAR* name) { + if (osVersion.isPlatformNT) { + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Locate the service + ServiceHandle service = OpenService(scm, name, SERVICE_INTERROGATE); + if (!service) + throw rdr::SystemException("unable to open the service", GetLastError()); + + // - Get the service status + SERVICE_STATUS status; + if (!ControlService(service, SERVICE_CONTROL_INTERROGATE, (SERVICE_STATUS*)&status)) + throw rdr::SystemException("unable to query the service", GetLastError()); + + return status.dwCurrentState; + } else { + HWND service_window = findServiceWindow(name); + return service_window ? SERVICE_RUNNING : SERVICE_STOPPED; + } +} + +char* rfb::win32::serviceStateName(DWORD state) { + switch (state) { + case SERVICE_RUNNING: return strDup("Running"); + case SERVICE_STOPPED: return strDup("Stopped"); + case SERVICE_STOP_PENDING: return strDup("Stopping"); + }; + CharArray tmp(32); + sprintf(tmp.buf, "Unknown (%lu)", state); + return tmp.takeBuf(); +} + + +bool rfb::win32::isServiceProcess() { + return service != 0; +} |