]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - win/win32service.cpp
Fix thread handle leak in StartServiceThread()
[user/henk/code/inspircd.git] / win / win32service.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd_config.h"
21 #include "inspircd.h"
22 #include "exitcodes.h"
23 #include <windows.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <iostream>
28
29 static SERVICE_STATUS_HANDLE serviceStatusHandle;
30 static HANDLE hThreadEvent;
31 static HANDLE killServiceEvent;
32 static int serviceCurrentStatus;
33
34 /** This is used to define ChangeServiceConf2() as we can't link
35  * directly against this symbol (see below where it is used)
36  */
37 typedef BOOL (CALLBACK* SETSERVDESC)(SC_HANDLE,DWORD,LPVOID);
38
39 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint);
40 void terminateService(int code, int wincode);
41
42 /* A commandline parameter handler for service specific commandline parameters */
43 typedef void (*CommandlineParameterHandler)(void);
44
45 /* Represents a commandline and its handler */
46 struct Commandline
47 {
48         const char* Switch;
49         CommandlineParameterHandler Handler;
50 };
51
52 /* A function pointer for dynamic linking tricks */
53 SETSERVDESC ChangeServiceConf;
54
55 LPCSTR RetrieveLastError()
56 {
57         static char err[100];
58         DWORD LastError = GetLastError();
59         if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 0, LastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)err, sizeof(err), 0) == 0)
60                 snprintf(err, sizeof(err), "Error code: %d", LastError);
61         SetLastError(ERROR_SUCCESS);
62         return err;
63 }
64
65 /* Returns true if this program is running as a service, false if it is running interactive */
66 bool IsAService()
67 {
68         USEROBJECTFLAGS uoflags;
69         HWINSTA winstation = GetProcessWindowStation();
70         if (GetUserObjectInformation(winstation, UOI_FLAGS, &uoflags, sizeof(uoflags), NULL))
71                 return ((uoflags.dwFlags & WSF_VISIBLE) == 0);
72         else
73                 return false;
74 }
75
76 /* Kills the service by setting an event which the other thread picks up and exits */
77 void KillService()
78 {
79         SetEvent(hThreadEvent);
80         Sleep(2000);
81         SetEvent(killServiceEvent);
82 }
83
84 /** The main part of inspircd runs within this thread function. This allows the service part to run
85  * seperately on its own and to be able to kill the worker thread when its time to quit.
86  */
87 DWORD WINAPI WorkerThread(LPDWORD param)
88 {
89         char modname[MAX_PATH];
90         GetModuleFileNameA(NULL, modname, sizeof(modname));
91         char* argv[] = { modname, "--nofork" };
92         smain(2, argv);
93         KillService();
94         return 0;
95 }
96
97 /* This is called when all startup is done */
98 void SetServiceRunning()
99 {
100         if (!IsAService())
101                 return;
102
103         serviceCurrentStatus = SERVICE_RUNNING;
104         BOOL success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
105         if (!success)
106         {
107                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
108                 return;
109         }
110 }
111
112
113 /** Starts the worker thread above */
114 void StartServiceThread()
115 {
116         HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkerThread,NULL,0,NULL);
117         if (hThread != NULL)
118                 CloseHandle(hThread);
119 }
120
121 /** This function updates the status of the service in the SCM
122  * (service control manager, the services.msc applet)
123  */
124 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint)
125 {
126         BOOL success;
127         SERVICE_STATUS serviceStatus;
128         serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
129         serviceStatus.dwCurrentState = dwCurrentState;
130
131         if (dwCurrentState == SERVICE_START_PENDING)
132         {
133                 serviceStatus.dwControlsAccepted = 0;
134         }
135         else
136         {
137                 serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
138         }
139
140         if (dwServiceSpecificExitCode == 0)
141         {
142                 serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
143         }
144         else
145         {
146                 serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
147         }
148         serviceStatus.dwServiceSpecificExitCode =   dwServiceSpecificExitCode;
149         serviceStatus.dwCheckPoint = dwCheckPoint;
150         serviceStatus.dwWaitHint = dwWaitHint;
151
152         success = SetServiceStatus (serviceStatusHandle, &serviceStatus);
153         if (!success)
154         {
155                 KillService();
156         }
157         return success;
158 }
159
160 /** This function is called by us when the service is being shut down or when it can't be started */
161 void terminateService(int code, int wincode)
162 {
163         UpdateSCMStatus(SERVICE_STOPPED, wincode ? wincode : ERROR_SERVICE_SPECIFIC_ERROR, wincode ? 0 : code, 0, 0);
164         return;
165 }
166
167 /* In windows we hook this to InspIRCd::Exit() */
168 void SetServiceStopped(int status)
169 {
170         if (!IsAService())
171                 exit(status);
172
173         /* Are we running as a service? If so, trigger the service specific exit code */
174         terminateService(status, 0);
175         KillService();
176         exit(status);
177 }
178
179 /** This callback is called by windows when the state of the service has been changed */
180 VOID ServiceCtrlHandler(DWORD controlCode)
181 {
182         switch(controlCode)
183         {
184                 case SERVICE_CONTROL_INTERROGATE:
185                 break;
186                 case SERVICE_CONTROL_SHUTDOWN:
187                 case SERVICE_CONTROL_STOP:
188                         serviceCurrentStatus = SERVICE_STOP_PENDING;
189                         UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
190                         KillService();
191                         UpdateSCMStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
192                         return;
193                 default:
194                 break;
195         }
196         UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0);
197 }
198
199 /** This callback is called by windows when the service is started */
200 VOID ServiceMain(DWORD argc, LPTSTR *argv)
201 {
202         BOOL success;
203
204         serviceStatusHandle = RegisterServiceCtrlHandler(TEXT("InspIRCd"), (LPHANDLER_FUNCTION)ServiceCtrlHandler);
205         if (!serviceStatusHandle)
206         {
207                 terminateService(EXIT_STATUS_RSCH_FAILED, GetLastError());
208                 return;
209         }
210
211         success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000);
212         if (!success)
213         {
214                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
215                 return;
216         }
217
218         killServiceEvent = CreateEvent(NULL, true, false, NULL);
219         hThreadEvent = CreateEvent(NULL, true, false, NULL);
220
221         if (!killServiceEvent || !hThreadEvent)
222         {
223                 terminateService(EXIT_STATUS_CREATE_EVENT_FAILED, GetLastError());
224                 return;
225         }
226
227         success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
228         if (!success)
229         {
230                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
231                 return;
232         }
233
234         StartServiceThread();
235         WaitForSingleObject (killServiceEvent, INFINITE);
236 }
237
238 /** Install the windows service. This requires administrator privileges. */
239 void InstallService()
240 {
241         SC_HANDLE myService, scm;
242         SERVICE_DESCRIPTION svDesc;
243         HINSTANCE advapi32;
244
245         TCHAR modname[MAX_PATH];
246         GetModuleFileName(NULL, modname, sizeof(modname));
247
248         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
249         if (!scm)
250         {
251                 std::cout << "Unable to open service control manager: " << RetrieveLastError() << std::endl;
252                 return;
253         }
254
255         myService = CreateService(scm,TEXT("InspIRCd"),TEXT("Inspire IRC Daemon"), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
256                 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, modname, 0, 0, 0, NULL, NULL);
257
258         if (!myService)
259         {
260                 std::cout << "Unable to create service: " << RetrieveLastError() << std::endl;
261                 CloseServiceHandle(scm);
262                 return;
263         }
264
265         // *** Set service description ***
266         // this is supported from 5.0 (win2k) onwards only, so we can't link to the definition of
267         // this function in advapi32.lib, otherwise the program will not run on windows NT 4. We
268         // must use LoadLibrary and GetProcAddress to export the function name from advapi32.dll
269         advapi32 = LoadLibrary(TEXT("advapi32.dll"));
270         if (advapi32)
271         {
272                 ChangeServiceConf = (SETSERVDESC)GetProcAddress(advapi32,"ChangeServiceConfig2A");
273                 if (ChangeServiceConf)
274                 {
275                         TCHAR desc[] = TEXT("The Inspire Internet Relay Chat Daemon hosts IRC channels and conversations.\
276  If this service is stopped, the IRC server will not run.");
277                         svDesc.lpDescription = desc;
278                         BOOL success = ChangeServiceConf(myService,SERVICE_CONFIG_DESCRIPTION, &svDesc);
279                         if (!success)
280                         {
281                                 std::cout << "Unable to set service description: " << RetrieveLastError() << std::endl;
282                                 CloseServiceHandle(myService);
283                                 CloseServiceHandle(scm);
284                                 return;
285                         }
286                 }
287                 FreeLibrary(advapi32);
288         }
289
290         std::cout << "Service installed." << std::endl;
291         CloseServiceHandle(myService);
292         CloseServiceHandle(scm);
293 }
294
295 /** Remove the windows service. This requires administrator privileges. */
296 void RemoveService()
297 {
298         SC_HANDLE myService, scm;
299
300         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
301         if (!scm)
302         {
303                 std::cout << "Unable to open service control manager: " << RetrieveLastError() << std::endl;
304                 return;
305         }
306
307         myService = OpenService(scm,TEXT("InspIRCd"),SERVICE_ALL_ACCESS);
308         if (!myService)
309         {
310                 std::cout << "Unable to open service: " << RetrieveLastError() << std::endl;
311                 CloseServiceHandle(scm);
312                 return;
313         }
314
315         if (!DeleteService(myService))
316         {
317                 std::cout << "Unable to delete service: " << RetrieveLastError() << std::endl;
318                 CloseServiceHandle(myService);
319                 CloseServiceHandle(scm);
320                 return;
321         }
322
323         std::cout << "Service removed." << std::endl;
324         CloseServiceHandle(myService);
325         CloseServiceHandle(scm);
326 }
327
328 /* In windows, our main() flows through here, before calling the 'real' main, smain() in inspircd.cpp */
329 int main(int argc, char** argv)
330 {
331         /* List of parameters and handlers */
332         Commandline params[] = {
333                 { "--installservice", InstallService },
334                 { "--removeservice", RemoveService },
335                 { NULL }
336         };
337
338         /* Check for parameters */
339         if (argc > 1)
340         {
341                 for (int z = 0; params[z].Switch; ++z)
342                 {
343                         if (!_stricmp(argv[1], params[z].Switch))
344                         {
345                                 params[z].Handler();
346                                 return 0;
347                         }
348                 }
349         }
350
351         /* First, check if the service is installed.
352          * if it is not, or we're starting as non-administrator,
353          * just call smain() and start as normal non-service
354          * process.
355          */
356         SC_HANDLE myService, scm;
357         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
358         if (scm)
359         {
360                 myService = OpenService(scm,TEXT("InspIRCd"),SERVICE_ALL_ACCESS);
361                 if (!myService)
362                 {
363                         /* Service not installed or no permission to modify it */
364                         CloseServiceHandle(scm);
365                         return smain(argc, argv);
366                 }
367         }
368         else
369         {
370                 /* Not enough privileges to open the SCM */
371                 return smain(argc, argv);
372         }
373
374         CloseServiceHandle(myService);
375         CloseServiceHandle(scm);
376
377         /* Check if the process is running interactively. InspIRCd does not run interactively
378          * as a service so if this is true, we just run the non-service inspircd.
379          */
380         if (!IsAService())
381                 return smain(argc, argv);
382
383         /* If we get here, we know the service is installed so we can start it */
384
385         SERVICE_TABLE_ENTRY serviceTable[] =
386         {
387                 {TEXT("InspIRCd"), (LPSERVICE_MAIN_FUNCTION) ServiceMain },
388                 {NULL, NULL}
389         };
390
391         StartServiceCtrlDispatcher(serviceTable);
392         return 0;
393 }