blob: a24b651c18ec73190517d132f2c25ad1f6ef0502 [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19// -=- Service.cxx
20
21#include <rfb_win32/Service.h>
22#include <rfb_win32/MsgWindow.h>
23#include <rfb_win32/DynamicFn.h>
24#include <rfb_win32/ModuleFileName.h>
25#include <rfb_win32/Registry.h>
26#include <rfb_win32/OSVersion.h>
27#include <rfb/Threading.h>
28#include <logmessages/messages.h>
29#include <rdr/Exception.h>
30#include <rfb/LogWriter.h>
31
32
33using namespace rdr;
34using namespace rfb;
35using namespace win32;
36
37static LogWriter vlog("Service");
38
39
40// - Internal service implementation functions
41
42Service* service = 0;
43
44VOID WINAPI serviceHandler(DWORD control) {
45 switch (control) {
46 case SERVICE_CONTROL_INTERROGATE:
47 vlog.info("cmd: report status");
48 service->setStatus();
49 return;
50 case SERVICE_CONTROL_PARAMCHANGE:
51 vlog.info("cmd: param change");
52 service->readParams();
53 return;
54 case SERVICE_CONTROL_SHUTDOWN:
55 vlog.info("cmd: OS shutdown");
56 service->osShuttingDown();
57 return;
58 case SERVICE_CONTROL_STOP:
59 vlog.info("cmd: stop");
60 service->setStatus(SERVICE_STOP_PENDING);
61 service->stop();
62 return;
63 };
64 vlog.debug("cmd: unknown %lu", control);
65}
66
67
68// -=- Message window derived class used under Win9x to implement stopService
69
70#define WM_SMSG_SERVICE_STOP WM_USER
71
72class ServiceMsgWindow : public MsgWindow {
73public:
74 ServiceMsgWindow(const TCHAR* name) : MsgWindow(name) {}
75 LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
76 switch (msg) {
77 case WM_SMSG_SERVICE_STOP:
78 service->stop();
79 return TRUE;
80 }
81 return MsgWindow::processMessage(msg, wParam, lParam);
82 }
83
84 static const TCHAR* baseName;
85};
86
87const TCHAR* ServiceMsgWindow::baseName = _T("ServiceWindow:");
88
89
90// -=- Service main procedure, used under WinNT/2K/XP by the SCM
91
92VOID WINAPI serviceProc(DWORD dwArgc, LPTSTR* lpszArgv) {
93 vlog.debug("entering %s serviceProc", service->getName());
94 vlog.info("registering handler...");
95 service->status_handle = RegisterServiceCtrlHandler(service->getName(), serviceHandler);
96 if (!service->status_handle) {
97 DWORD err = GetLastError();
98 vlog.error("failed to register handler: %lu", err);
99 ExitProcess(err);
100 }
101 vlog.debug("registered handler (%lx)", service->status_handle);
102 service->setStatus(SERVICE_START_PENDING);
103 vlog.debug("entering %s serviceMain", service->getName());
104 service->status.dwWin32ExitCode = service->serviceMain(dwArgc, lpszArgv);
105 vlog.debug("leaving %s serviceMain", service->getName());
106 service->setStatus(SERVICE_STOPPED);
107}
108
109
110// -=- Service
111
112Service::Service(const TCHAR* name_) : name(name_) {
113 vlog.debug("Service");
114 status_handle = 0;
115 status.dwControlsAccepted = SERVICE_CONTROL_INTERROGATE | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
116 status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
117 status.dwWin32ExitCode = NO_ERROR;
118 status.dwServiceSpecificExitCode = 0;
119 status.dwCheckPoint = 0;
120 status.dwWaitHint = 30000;
121 status.dwCurrentState = SERVICE_STOPPED;
122}
123
124void
125Service::start() {
126 if (osVersion.isPlatformNT) {
127 SERVICE_TABLE_ENTRY entry[2];
128 entry[0].lpServiceName = (TCHAR*)name;
129 entry[0].lpServiceProc = serviceProc;
130 entry[1].lpServiceName = NULL;
131 entry[1].lpServiceProc = NULL;
132 vlog.debug("entering dispatcher");
133 if (!SetProcessShutdownParameters(0x100, 0))
134 vlog.error("unable to set shutdown parameters: %d", GetLastError());
135 service = this;
136 if (!StartServiceCtrlDispatcher(entry))
137 throw SystemException("unable to start service", GetLastError());
138 } else {
139
140 // - Create the service window, so the service can be stopped
141 TCharArray wndName(_tcslen(getName()) + _tcslen(ServiceMsgWindow::baseName) + 1);
142 _tcscpy(wndName.buf, ServiceMsgWindow::baseName);
143 _tcscat(wndName.buf, getName());
144 ServiceMsgWindow service_window(wndName.buf);
145
146 // - Locate the RegisterServiceProcess function
147 typedef DWORD (WINAPI * _RegisterServiceProcess_proto)(DWORD, DWORD);
148 DynamicFn<_RegisterServiceProcess_proto> _RegisterServiceProcess(_T("kernel32.dll"), "RegisterServiceProcess");
149 if (!_RegisterServiceProcess.isValid())
150 throw Exception("unable to find RegisterServiceProcess");
151
152 // - Run the service
153 (*_RegisterServiceProcess)(NULL, 1);
154 service = this;
155 serviceMain(0, 0);
156 (*_RegisterServiceProcess)(NULL, 0);
157 }
158}
159
160void
161Service::setStatus() {
162 setStatus(status.dwCurrentState);
163}
164
165void
166Service::setStatus(DWORD state) {
167 if (!osVersion.isPlatformNT)
168 return;
169 if (status_handle == 0) {
170 vlog.debug("warning - cannot setStatus");
171 return;
172 }
173 status.dwCurrentState = state;
174 status.dwCheckPoint++;
175 if (!SetServiceStatus(status_handle, &status)) {
176 status.dwCurrentState = SERVICE_STOPPED;
177 status.dwWin32ExitCode = GetLastError();
178 vlog.error("unable to set service status:%u", status.dwWin32ExitCode);
179 }
180 vlog.debug("set status to %u(%u)", state, status.dwCheckPoint);
181}
182
183Service::~Service() {
184 vlog.debug("~Service");
185 service = 0;
186}
187
188
189// Find out whether this process is running as the WinVNC service
190bool thisIsService() {
191 return service && (service->status.dwCurrentState != SERVICE_STOPPED);
192}
193
194
195// -=- Desktop handling code
196
197// Switch the current thread to the specified desktop
198static bool
199switchToDesktop(HDESK desktop) {
200 HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());
201 if (!SetThreadDesktop(desktop)) {
202 vlog.debug("switchToDesktop failed:%u", GetLastError());
203 return false;
204 }
205 if (!CloseDesktop(old_desktop))
206 vlog.debug("unable to close old desktop:%u", GetLastError());
207 return true;
208}
209
210// Determine whether the thread's current desktop is the input one
211static bool
212inputDesktopSelected() {
213 HDESK current = GetThreadDesktop(GetCurrentThreadId());
214 HDESK input = OpenInputDesktop(0, FALSE,
215 DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
216 DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
217 DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
218 DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
219 if (!input) {
220 vlog.debug("unable to OpenInputDesktop(1):%u", GetLastError());
221 return false;
222 }
223
224 DWORD size;
225 char currentname[256];
226 char inputname[256];
227
228 if (!GetUserObjectInformation(current, UOI_NAME, currentname, 256, &size)) {
229 vlog.debug("unable to GetUserObjectInformation(1):%u", GetLastError());
230 CloseDesktop(input);
231 return false;
232 }
233 if (!GetUserObjectInformation(input, UOI_NAME, inputname, 256, &size)) {
234 vlog.debug("unable to GetUserObjectInformation(2):%u", GetLastError());
235 CloseDesktop(input);
236 return false;
237 }
238 if (!CloseDesktop(input))
239 vlog.debug("unable to close input desktop:%u", GetLastError());
240
241 // *** vlog.debug("current=%s, input=%s", currentname, inputname);
242 bool result = strcmp(currentname, inputname) == 0;
243 return result;
244}
245
246// Switch the current thread into the input desktop
247static bool
248selectInputDesktop() {
249 // - Open the input desktop
250 HDESK desktop = OpenInputDesktop(0, FALSE,
251 DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
252 DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
253 DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
254 DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
255 if (!desktop) {
256 vlog.debug("unable to OpenInputDesktop(2):%u", GetLastError());
257 return false;
258 }
259
260 // - Switch into it
261 if (!switchToDesktop(desktop)) {
262 CloseDesktop(desktop);
263 return false;
264 }
265
266 // ***
267 DWORD size = 256;
268 char currentname[256];
269 if (GetUserObjectInformation(desktop, UOI_NAME, currentname, 256, &size)) {
270 vlog.debug("switched to %s", currentname);
271 }
272 // ***
273
274 vlog.debug("switched to input desktop");
275
276 return true;
277}
278
279
280// -=- Access points to desktop-switching routines
281
282bool
283rfb::win32::desktopChangeRequired() {
284 if (!osVersion.isPlatformNT)
285 return false;
286
287 return !inputDesktopSelected();
288}
289
290bool
291rfb::win32::changeDesktop() {
292 if (!osVersion.isPlatformNT)
293 return true;
294 if (osVersion.cannotSwitchDesktop)
295 return false;
296
297 return selectInputDesktop();
298}
299
300
301// -=- Ctrl-Alt-Del emulation
302
303class CADThread : public Thread {
304public:
305 CADThread() : Thread("CtrlAltDel Emulator"), result(false) {}
306 virtual void run() {
307 HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());
308
309 if (switchToDesktop(OpenDesktop(_T("Winlogon"), 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
310 DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
311 DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
312 DESKTOP_SWITCHDESKTOP | GENERIC_WRITE))) {
313 PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE));
314 switchToDesktop(old_desktop);
315 result = true;
316 }
317 }
318 bool result;
319};
320
321bool
322rfb::win32::emulateCtrlAltDel() {
323 if (!osVersion.isPlatformNT)
324 return false;
325
326 CADThread* cad_thread = new CADThread();
327 vlog.debug("emulate Ctrl-Alt-Del");
328 if (cad_thread) {
329 cad_thread->start();
330 cad_thread->join();
331 bool result = cad_thread->result;
332 delete cad_thread;
333 return result;
334 }
335 return false;
336}
337
338
339// -=- Application Event Log target Logger class
340
341class Logger_EventLog : public Logger {
342public:
343 Logger_EventLog(const TCHAR* srcname) : Logger("EventLog") {
344 eventlog = RegisterEventSource(NULL, srcname);
345 if (!eventlog)
346 printf("Unable to open event log:%ld\n", GetLastError());
347 }
348 ~Logger_EventLog() {
349 if (eventlog)
350 DeregisterEventSource(eventlog);
351 }
352
353 virtual void write(int level, const char *logname, const char *message) {
354 if (!eventlog) return;
355 TStr log(logname), msg(message);
356 const TCHAR* strings[] = {log, msg};
357 WORD type = EVENTLOG_INFORMATION_TYPE;
358 if (level == 0) type = EVENTLOG_ERROR_TYPE;
359 if (!ReportEvent(eventlog, type, 0, VNC4LogMessage, NULL, 2, 0, strings, NULL)) {
360 // *** It's not at all clear what is the correct behaviour if this fails...
361 printf("ReportEvent failed:%ld\n", GetLastError());
362 }
363 }
364
365protected:
366 HANDLE eventlog;
367};
368
369static Logger_EventLog* logger = 0;
370
371bool rfb::win32::initEventLogLogger(const TCHAR* srcname) {
372 if (logger)
373 return false;
374 if (osVersion.isPlatformNT) {
375 logger = new Logger_EventLog(srcname);
376 logger->registerLogger();
377 return true;
378 } else {
379 return false;
380 }
381}
382
383
384// -=- Registering and unregistering the service
385
386bool rfb::win32::registerService(const TCHAR* name, const TCHAR* desc,
387 int argc, const char* argv[]) {
388
389 // - Initialise the default service parameters
390 const TCHAR* defaultcmdline;
391 if (osVersion.isPlatformNT)
392 defaultcmdline = _T("-service");
393 else
394 defaultcmdline = _T("-noconsole -service");
395
396 // - Get the full pathname of our executable
397 ModuleFileName buffer;
398
399 // - Calculate the command-line length
400 int cmdline_len = _tcslen(buffer.buf) + 4;
401 int i;
402 for (i=0; i<argc; i++) {
403 cmdline_len += strlen(argv[i]) + 3;
404 }
405
406 // - Add the supplied extra parameters to the command line
407 TCharArray cmdline(cmdline_len+_tcslen(defaultcmdline));
408 _stprintf(cmdline.buf, _T("\"%s\" %s"), buffer.buf, defaultcmdline);
409 for (i=0; i<argc; i++) {
410 _tcscat(cmdline.buf, _T(" \""));
411 _tcscat(cmdline.buf, TStr(argv[i]));
412 _tcscat(cmdline.buf, _T("\""));
413 }
414
415 // - Register the service
416
417 if (osVersion.isPlatformNT) {
418
419 // - Open the SCM
420 ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
421 if (!scm)
422 throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
423
424
425 ServiceHandle service = CreateService(scm,
426 name, desc, SC_MANAGER_ALL_ACCESS,
427 SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
428 SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
429 cmdline.buf, NULL, NULL, NULL, NULL, NULL);
430 if (!service)
431 throw rdr::SystemException("unable to create service", GetLastError());
432
433 // - Register the event log source
434 RegKey hk, hk2;
435
436 hk2.createKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application"));
437 hk.createKey(hk2, name);
438
439 for (i=_tcslen(buffer.buf); i>0; i--) {
440 if (buffer.buf[i] == _T('\\')) {
441 buffer.buf[i+1] = 0;
442 break;
443 }
444 }
445
446 const TCHAR* dllFilename = _T("logmessages.dll");
447 TCharArray dllPath(_tcslen(buffer.buf) + _tcslen(dllFilename) + 1);
448 _tcscpy(dllPath.buf, buffer.buf);
449 _tcscat(dllPath.buf, dllFilename);
450
451 hk.setExpandString(_T("EventMessageFile"), dllPath.buf);
452 hk.setInt(_T("TypesSupported"), EVENTLOG_ERROR_TYPE | EVENTLOG_INFORMATION_TYPE);
453
454 } else {
455
456 RegKey services;
457 services.createKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices"));
458 services.setString(name, cmdline.buf);
459
460 }
461
462 Sleep(500);
463
464 return true;
465}
466
467bool rfb::win32::unregisterService(const TCHAR* name) {
468 if (osVersion.isPlatformNT) {
469
470 // - Open the SCM
471 ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
472 if (!scm)
473 throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
474
475 // - Create the service
476 ServiceHandle service = OpenService(scm, name, SC_MANAGER_ALL_ACCESS);
477 if (!service)
478 throw rdr::SystemException("unable to locate the service", GetLastError());
479 if (!DeleteService(service))
480 throw rdr::SystemException("unable to remove the service", GetLastError());
481
482 // - Register the event log source
483 RegKey hk;
484 hk.openKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application"));
485 hk.deleteKey(name);
486
487 } else {
488
489 RegKey services;
490 services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices"));
491 services.deleteValue(name);
492
493 }
494
495 Sleep(500);
496
497 return true;
498}
499
500
501// -=- Starting and stopping the service
502
503HWND findServiceWindow(const TCHAR* name) {
504 TCharArray wndName(_tcslen(ServiceMsgWindow::baseName)+_tcslen(name)+1);
505 _tcscpy(wndName.buf, ServiceMsgWindow::baseName);
506 _tcscat(wndName.buf, name);
507 vlog.debug("searching for %s window", CStr(wndName.buf));
508 return FindWindow(0, wndName.buf);
509}
510
511bool rfb::win32::startService(const TCHAR* name) {
512
513 if (osVersion.isPlatformNT) {
514 // - Open the SCM
515 ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
516 if (!scm)
517 throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
518
519 // - Locate the service
520 ServiceHandle service = OpenService(scm, name, SERVICE_START);
521 if (!service)
522 throw rdr::SystemException("unable to open the service", GetLastError());
523
524 // - Start the service
525 if (!StartService(service, 0, NULL))
526 throw rdr::SystemException("unable to start the service", GetLastError());
527 } else {
528 // - Check there is no service window
529 if (findServiceWindow(name))
530 throw rdr::Exception("the service is already running");
531
532 // - Find the RunServices registry key
533 RegKey services;
534 services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices"));
535
536 // - Read the command-line from it
Peter Åstrandb22dbef2008-12-09 14:57:53 +0000537 TCharArray cmdLine(services.getString(name));
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000538
539 // - Start the service
540 PROCESS_INFORMATION proc_info;
541 STARTUPINFO startup_info;
542 ZeroMemory(&startup_info, sizeof(startup_info));
543 startup_info.cb = sizeof(startup_info);
544 if (!CreateProcess(0, cmdLine.buf, 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, 0, &startup_info, &proc_info)) {
545 throw SystemException("unable to start service", GetLastError());
546 }
547 }
548
549 Sleep(500);
550
551 return true;
552}
553
554bool rfb::win32::stopService(const TCHAR* name) {
555 if (osVersion.isPlatformNT) {
556 // - Open the SCM
557 ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
558 if (!scm)
559 throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
560
561 // - Locate the service
562 ServiceHandle service = OpenService(scm, name, SERVICE_STOP);
563 if (!service)
564 throw rdr::SystemException("unable to open the service", GetLastError());
565
566 // - Start the service
567 SERVICE_STATUS status;
568 if (!ControlService(service, SERVICE_CONTROL_STOP, &status))
569 throw rdr::SystemException("unable to stop the service", GetLastError());
570
571 } else {
572 // - Find the service window
573 HWND service_window = findServiceWindow(name);
574 if (!service_window)
575 throw Exception("unable to locate running service");
576
577 // Tell it to quit
578 vlog.debug("sending service stop request");
579 if (!SendMessage(service_window, WM_SMSG_SERVICE_STOP, 0, 0))
580 throw Exception("unable to stop service");
581
582 // Check it's quitting...
583 DWORD process_id = 0;
584 HANDLE process = 0;
585 if (!GetWindowThreadProcessId(service_window, &process_id))
586 throw SystemException("unable to verify service has quit", GetLastError());
587 process = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, process_id);
588 if (!process)
589 throw SystemException("unable to obtain service handle", GetLastError());
590 int retries = 5;
591 vlog.debug("checking status");
592 while (retries-- && (WaitForSingleObject(process, 1000) != WAIT_OBJECT_0)) {}
593 if (!retries) {
594 vlog.debug("failed to quit - terminating");
595 // May not have quit because of silly Win9x registry watching bug..
596 if (!TerminateProcess(process, 1))
597 throw SystemException("unable to terminate process!", GetLastError());
598 throw Exception("service failed to quit - called TerminateProcess");
599 }
600 }
601
602 Sleep(500);
603
604 return true;
605}
606
607DWORD rfb::win32::getServiceState(const TCHAR* name) {
608 if (osVersion.isPlatformNT) {
609 // - Open the SCM
610 ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
611 if (!scm)
612 throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
613
614 // - Locate the service
615 ServiceHandle service = OpenService(scm, name, SERVICE_INTERROGATE);
616 if (!service)
617 throw rdr::SystemException("unable to open the service", GetLastError());
618
619 // - Get the service status
620 SERVICE_STATUS status;
621 if (!ControlService(service, SERVICE_CONTROL_INTERROGATE, (SERVICE_STATUS*)&status))
622 throw rdr::SystemException("unable to query the service", GetLastError());
623
624 return status.dwCurrentState;
625 } else {
626 HWND service_window = findServiceWindow(name);
627 return service_window ? SERVICE_RUNNING : SERVICE_STOPPED;
628 }
629}
630
631char* rfb::win32::serviceStateName(DWORD state) {
632 switch (state) {
633 case SERVICE_RUNNING: return strDup("Running");
634 case SERVICE_STOPPED: return strDup("Stopped");
635 case SERVICE_STOP_PENDING: return strDup("Stopping");
636 };
637 CharArray tmp(32);
638 sprintf(tmp.buf, "Unknown (%lu)", state);
639 return tmp.takeBuf();
640}
641
642
643bool rfb::win32::isServiceProcess() {
644 return service != 0;
645}