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