1 /**
2 * This file is part of Xpra.
3 * Copyright (C) 2017-2020 Antoine Martin <antoine@xpra.org>
4 *
5 * This service code is based on "The Complete Service Sample":
6 * https://msdn.microsoft.com/en-us/library/bb540476(v=VS.85).aspx
7 */
8
9 #include <windows.h>
10 #include <tchar.h>
11 #undef __CRT__NO_INLINE
12 #include <strsafe.h>
13 #include "event_log.h"
14
15 #pragma comment(lib, "advapi32.lib")
16
17 #define SVCNAME TEXT("Xpra")
18
19 SERVICE_STATUS gSvcStatus;
20 SERVICE_STATUS_HANDLE gSvcStatusHandle;
21 HANDLE ghSvcStopEvent = NULL;
22
23 VOID SvcInstall(void);
24 VOID SvcUninstall(void);
25 VOID WINAPI SvcCtrlHandler(DWORD);
26 VOID WINAPI SvcMain(DWORD, LPTSTR *);
27
28 VOID ReportSvcStatus(DWORD, DWORD, DWORD);
29 VOID SvcInit(DWORD, LPTSTR *);
30 VOID SvcReportEvent(LPTSTR);
31
32
main(int argc,TCHAR * argv[])33 int __cdecl main(int argc, TCHAR *argv[])
34 {
35 if (lstrcmpi(argv[1], TEXT("install")) == 0)
36 {
37 SvcInstall();
38 return 0;
39 }
40 else if (lstrcmpi(argv[1], TEXT("uninstall")) == 0)
41 {
42 SvcUninstall();
43 return 0;
44 }
45
46 // TO_DO: Add any additional services for the process to this table.
47 SERVICE_TABLE_ENTRY DispatchTable[] =
48 {
49 { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
50 { NULL, NULL }
51 };
52
53 // This call returns when the service has stopped.
54 // The process should simply terminate when the call returns.
55 if (!StartServiceCtrlDispatcher(DispatchTable))
56 {
57 SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
58 }
59 return 0;
60 }
61
SvcInstall()62 VOID SvcInstall() {
63 SC_HANDLE schSCManager;
64 SC_HANDLE schService;
65 TCHAR szPath[MAX_PATH];
66
67 if (!GetModuleFileName(NULL, szPath, MAX_PATH))
68 {
69 printf("Cannot install service (%d)\n", GetLastError());
70 return;
71 }
72
73 // Get a handle to the SCM database.
74 schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
75 if (schSCManager==NULL)
76 {
77 printf("OpenSCManager failed (%d)\n", GetLastError());
78 return;
79 }
80
81 schService = CreateService(
82 schSCManager, // SCM database
83 SVCNAME, // name of service
84 SVCNAME, // service name to display
85 SERVICE_ALL_ACCESS, // desired access
86 SERVICE_WIN32_OWN_PROCESS, // service type
87 SERVICE_DEMAND_START, // start type
88 SERVICE_ERROR_NORMAL, // error control type
89 szPath, // path to service's binary
90 NULL, // no load ordering group
91 NULL, // no tag identifier
92 NULL, // no dependencies
93 NULL, // LocalSystem account
94 NULL); // no password
95
96 if (schService==NULL)
97 {
98 printf("CreateService failed (%d)\n", GetLastError());
99 CloseServiceHandle(schSCManager);
100 return;
101 }
102 else
103 {
104 printf("Service installed successfully\n");
105 }
106 CloseServiceHandle(schService);
107 CloseServiceHandle(schSCManager);
108 }
109
110
SvcUninstall(void)111 VOID SvcUninstall(void)
112 {
113 SC_HANDLE schSCManager;
114 SC_HANDLE schService;
115 SERVICE_STATUS ssStatus;
116
117 // Get a handle to the SCM database.
118
119 schSCManager = OpenSCManager(
120 NULL, // local computer
121 NULL, // ServicesActive database
122 SC_MANAGER_ALL_ACCESS); // full access rights
123
124 if (NULL == schSCManager)
125 {
126 printf("OpenSCManager failed (%d)\n", GetLastError());
127 return;
128 }
129
130 // Get a handle to the service.
131 schService = OpenService(
132 schSCManager, // SCM database
133 SVCNAME, // name of service
134 SERVICE_ALL_ACCESS); // need delete access
135
136 if (schService == NULL)
137 {
138 printf("OpenService failed (%d)\n", GetLastError());
139 CloseServiceHandle(schSCManager);
140 return;
141 }
142
143 // Check if the service is started
144 SERVICE_STATUS stat;
145 if (!QueryServiceStatus(schService, &stat)) {
146 printf("QueryServiceStatus failed (%d)\n", GetLastError());
147 CloseServiceHandle(schSCManager);
148 return;
149 }
150
151 if (stat.dwCurrentState != SERVICE_STOPPED) {
152 // The service is running, stop it
153 SERVICE_CONTROL_STATUS_REASON_PARAMS reason;
154 memset(&reason, 0, sizeof(SERVICE_CONTROL_STATUS_REASON_PARAMS));
155 reason.dwReason = SERVICE_STOP_REASON_FLAG_PLANNED|SERVICE_STOP_REASON_MAJOR_OTHER|SERVICE_STOP_REASON_MINOR_INSTALLATION;
156 //SERVICE_CONTROL_STATUS_REASON_INFO=1
157 if (!ControlServiceEx(schService, SERVICE_CONTROL_STOP, 1, &reason)) {
158 printf("ControlServiceEx failed (%d)\n", GetLastError());
159 CloseServiceHandle(schSCManager);
160 return;
161 }
162 }
163
164 // Delete the service.
165 if (!DeleteService(schService))
166 {
167 printf("DeleteService failed (%d)\n", GetLastError());
168 }
169 else {
170 printf("Service deleted successfully\n");
171 }
172
173 CloseServiceHandle(schService);
174 CloseServiceHandle(schSCManager);
175 }
176
177
178 //
179 // Purpose:
180 // Entry point for the service
181 //
182 // Parameters:
183 // dwArgc - Number of arguments in the lpszArgv array
184 // lpszArgv - Array of strings. The first string is the name of
185 // the service and subsequent strings are passed by the process
186 // that called the StartService function to start the service.
187 //
188 // Return value:
189 // None.
190 //
SvcMain(DWORD dwArgc,LPTSTR * lpszArgv)191 VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
192 {
193 gSvcStatusHandle = RegisterServiceCtrlHandler(SVCNAME, SvcCtrlHandler);
194 if (!gSvcStatusHandle)
195 {
196 SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));
197 return;
198 }
199 // These SERVICE_STATUS members remain as set here
200 gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
201 gSvcStatus.dwServiceSpecificExitCode = 0;
202
203 // Report initial status to the SCM
204 ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
205
206 // Perform service-specific initialization and work.
207 SvcInit(dwArgc, lpszArgv);
208 }
209
210 //
211 // Purpose:
212 // The service code
213 //
214 // Parameters:
215 // dwArgc - Number of arguments in the lpszArgv array
216 // lpszArgv - Array of strings. The first string is the name of
217 // the service and subsequent strings are passed by the process
218 // that called the StartService function to start the service.
219 //
220 // Return value:
221 // None
222 //
SvcInit(DWORD dwArgc,LPTSTR * lpszArgv)223 VOID SvcInit(DWORD dwArgc, LPTSTR *lpszArgv)
224 {
225 HANDLE event_log = RegisterEventSource(NULL, SVCNAME);
226 const char* message = "Going to start Xpra service";
227 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
228 char buf[1024];
229
230 // Create an event. The control handler function, SvcCtrlHandler,
231 // signals this event when it receives the stop control code.
232 ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
233 if (ghSvcStopEvent==NULL)
234 {
235 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
236 return;
237 }
238
239 //LPCTSTR default_command = "\"C:\\Program Files\\Xpra\\Xpra-Proxy.exe\"";
240 LPCTSTR default_start_command = "C:\\Program Files\\Xpra\\paexec.exe -w \"C:\\Program Files\\Xpra\" -s \"C:\\Program Files\\Xpra\\Xpra-Proxy.exe\" start";
241 LPCTSTR default_stop_command = "C:\\Program Files\\Xpra\\paexec.exe -w \"C:\\Program Files\\Xpra\" -s \"C:\\Program Files\\Xpra\\Xpra-Proxy.exe\" stop";
242 LPCTSTR default_cwd = "C:\\Program Files\\Xpra\\";
243 LPTSTR start_command = (LPTSTR) default_start_command;
244 LPTSTR stop_command = (LPTSTR) default_stop_command;
245 LPTSTR cwd = (LPTSTR) default_cwd;
246
247 DWORD lpcbData = 1024;
248 DWORD dwType = REG_SZ;
249 HKEY hKey = 0;
250 const char* subkey = "SOFTWARE\\Xpra";
251 if (!RegOpenKey(HKEY_LOCAL_MACHINE, subkey, &hKey)) {
252 if (!RegQueryValueEx(hKey, "InstallPath", NULL, &dwType, (LPBYTE) buf, &lpcbData)) {
253 cwd = (LPTSTR) malloc(1024);
254 StringCbPrintfA(cwd, 1024, "%s", buf);
255 StringCbPrintfA(buf, 1024, "Found installation path: '%s'\n", cwd);
256 message = (const char*) &buf;
257 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
258
259 start_command = (LPTSTR) malloc(1024);
260 StringCbPrintfA(start_command, 1024,
261 "%s\\paexec.exe -w \"%s\" -s \"%s\\Xpra-Proxy.exe\" start",
262 cwd, cwd, cwd);
263
264 stop_command = (LPTSTR) malloc(1024);
265 StringCbPrintfA(stop_command, 1024,
266 "%s\\paexec.exe -w \"%s\" -s \"%s\\Xpra-Proxy.exe\" stop",
267 cwd, cwd, cwd);
268 }
269 else {
270 snprintf(buf, 512, "Registry key entry 'InstallPath' is missing, using default path '%s'\n", cwd);
271 message = (const char*) &buf;
272 ReportEvent(event_log, EVENTLOG_ERROR_TYPE, 0, 0, NULL, 1, 0, &message, NULL);
273 DeregisterEventSource(event_log);
274 }
275 }
276 else {
277 snprintf(buf, 512, "Registry key '%s' is missing, using default path '%s'\n", subkey, cwd);
278 message = (const char*) &buf;
279 ReportEvent(event_log, EVENTLOG_ERROR_TYPE, 0, 0, NULL, 1, 0, &message, NULL);
280 DeregisterEventSource(event_log);
281 }
282
283 StringCbPrintfA(buf, 1024, "Starting Xpra service: '%s'\n", start_command);
284 message = (const char*) &buf;
285 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
286
287 STARTUPINFO si;
288 PROCESS_INFORMATION pi;
289 ZeroMemory(&si, sizeof(si));
290 si.cb = sizeof(si);
291 ZeroMemory(&pi, sizeof(pi));
292 si.dwFlags = STARTF_USESHOWWINDOW;
293 si.wShowWindow = SW_HIDE;
294
295 if (!CreateProcess(NULL, start_command, NULL, NULL, FALSE, 0, NULL, cwd, &si, &pi))
296 {
297 snprintf(buf, 64, "CreateProcess failed (%d).\n", GetLastError());
298 message = (const char*) &buf;
299 ReportEvent(event_log, EVENTLOG_ERROR_TYPE, 0, 0, NULL, 1, 0, &message, NULL);
300 DeregisterEventSource(event_log);
301
302 ReportSvcStatus(SERVICE_STOPPED, 1, 0);
303 return;
304 }
305 HANDLE process = pi.hProcess;
306 DWORD pid = pi.dwProcessId;
307
308 snprintf(buf, 64, "Xpra service started with pid=%d.\n", pid);
309 message = (const char*) &buf;
310 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
311 ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
312
313 WaitForSingleObject(ghSvcStopEvent, INFINITE);
314
315 DWORD status = WaitForSingleObject(process, 10);
316 if(status == WAIT_OBJECT_0)
317 {
318 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
319 }
320
321 message = "Xpra service asked to close";
322 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
323
324 //ask politely:
325 ZeroMemory(&si, sizeof(si));
326 si.cb = sizeof(si);
327 ZeroMemory(&pi, sizeof(pi));
328 si.dwFlags = STARTF_USESHOWWINDOW;
329 si.wShowWindow = SW_HIDE;
330 if (!CreateProcess(NULL, stop_command, NULL, NULL, FALSE, 0, NULL, cwd, &si, &pi))
331 {
332 snprintf(buf, 64, "Xpra stop command failed (%d).\n", GetLastError());
333 message = (const char*) &buf;
334 ReportEvent(event_log, EVENTLOG_ERROR_TYPE, 0, 0, NULL, 1, 0, &message, NULL);
335 DeregisterEventSource(event_log);
336 }
337 status = WaitForSingleObject(process, 5000);
338 if(status == WAIT_OBJECT_0)
339 {
340 message = "Xpra service terminated after 'stop'";
341 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
342 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
343 return;
344 }
345
346 PostMessage((HWND) pi.hProcess, WM_CLOSE, 0, 0);
347 status = WaitForSingleObject(process, 1000);
348 if(status == WAIT_OBJECT_0)
349 {
350 message = "Xpra service terminated after WM_CLOSE";
351 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
352 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
353 return;
354 }
355 PostMessage((HWND) pi.hProcess, WM_QUIT, 0, 0);
356 status = WaitForSingleObject(process, 1000);
357 if(status == WAIT_OBJECT_0)
358 {
359 message = "Xpra service terminated after WM_QUIT";
360 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
361 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
362 return;
363 }
364 PostMessage((HWND) pi.hProcess, WM_DESTROY, 0, 0);
365 status = WaitForSingleObject(process, 1000);
366 if(status == WAIT_OBJECT_0)
367 {
368 message = "Xpra service terminated after WM_DESTROY";
369 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
370 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
371 return;
372 }
373
374 message = "Xpra Service forced to terminate";
375 ReportEvent(event_log, EVENTLOG_SUCCESS, 0, 0, NULL, 1, 0, &message, NULL);
376 DeregisterEventSource(event_log);
377 TerminateProcess(process, 0);
378
379 ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
380 }
381
382 //
383 // Purpose:
384 // Sets the current service status and reports it to the SCM.
385 //
386 // Parameters:
387 // dwCurrentState - The current state (see SERVICE_STATUS)
388 // dwWin32ExitCode - The system error code
389 // dwWaitHint - Estimated time for pending operation,
390 // in milliseconds
391 //
392 // Return value:
393 // None
394 //
ReportSvcStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode,DWORD dwWaitHint)395 VOID ReportSvcStatus( DWORD dwCurrentState,
396 DWORD dwWin32ExitCode,
397 DWORD dwWaitHint)
398 {
399 static DWORD dwCheckPoint = 1;
400
401 // Fill in the SERVICE_STATUS structure.
402
403 gSvcStatus.dwCurrentState = dwCurrentState;
404 gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
405 gSvcStatus.dwWaitHint = dwWaitHint;
406
407 if (dwCurrentState == SERVICE_START_PENDING)
408 {
409 gSvcStatus.dwControlsAccepted = 0;
410 }
411 else {
412 gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
413 }
414
415 if ((dwCurrentState==SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
416 {
417 gSvcStatus.dwCheckPoint = 0;
418 }
419 else
420 {
421 gSvcStatus.dwCheckPoint = dwCheckPoint++;
422 }
423 // Report the status of the service to the SCM.
424 SetServiceStatus( gSvcStatusHandle, &gSvcStatus );
425 }
426
427 //
428 // Purpose:
429 // Called by SCM whenever a control code is sent to the service
430 // using the ControlService function.
431 //
432 // Parameters:
433 // dwCtrl - control code
434 //
435 // Return value:
436 // None
437 //
SvcCtrlHandler(DWORD dwCtrl)438 VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )
439 {
440 switch(dwCtrl)
441 {
442 case SERVICE_CONTROL_STOP:
443 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
444 SetEvent(ghSvcStopEvent);
445 ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
446 return;
447
448 case SERVICE_CONTROL_INTERROGATE:
449 break;
450
451 default:
452 break;
453 }
454 }
455
456 //
457 // Purpose:
458 // Logs messages to the event log
459 //
460 // Parameters:
461 // szFunction - name of function that failed
462 //
463 // Return value:
464 // None
465 //
466 // Remarks:
467 // The service must have an entry in the Application event log.
468 //
SvcReportEvent(LPTSTR szFunction)469 VOID SvcReportEvent(LPTSTR szFunction)
470 {
471 HANDLE hEventSource;
472 LPCTSTR lpszStrings[2];
473 TCHAR Buffer[80];
474
475 hEventSource = RegisterEventSource(NULL, SVCNAME);
476
477 if( NULL != hEventSource )
478 {
479 StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());
480
481 lpszStrings[0] = SVCNAME;
482 lpszStrings[1] = Buffer;
483
484 ReportEvent(hEventSource, // event log handle
485 EVENTLOG_ERROR_TYPE, // event type
486 0, // event category
487 SVC_ERROR, // event identifier
488 NULL, // no security identifier
489 2, // size of lpszStrings array
490 0, // no binary data
491 lpszStrings, // array of strings
492 NULL); // no binary data
493
494 DeregisterEventSource(hEventSource);
495 }
496 }
497