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