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