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