https://issues.shibboleth.net/jira/browse/SSPCPP-407
[shibboleth/cpp-sp.git] / shibd / shibd_win32.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /*
22  * shar_win32.cpp -- the SHAR "main" code on Win32
23  */
24
25 #include "config_win32.h"
26
27 #define _CRT_NONSTDC_NO_DEPRECATE 1
28 #define _CRT_SECURE_NO_DEPRECATE 1
29
30 #include <shibsp/base.h>
31 #include <string>
32 #include <windows.h>
33
34 using namespace std;
35
36 extern bool shibd_shutdown;                    // signals shutdown to Unix side
37 extern const char* shar_schemadir;
38 extern const char* shar_config;
39 extern const char* shar_prefix;
40 extern bool shar_checkonly;
41
42 // internal variables
43 SERVICE_STATUS          ssStatus;       // current status of the service
44 SERVICE_STATUS_HANDLE   sshStatusHandle;
45 DWORD                   dwErr = 0;
46 BOOL                    bConsole = FALSE;
47 char                    szErr[256];
48 LPCSTR                  lpszInstall = nullptr;
49 LPCSTR                  lpszRemove = nullptr;
50
51 // internal function prototypes
52 VOID WINAPI service_ctrl(DWORD dwCtrlCode);
53 VOID WINAPI service_main(DWORD dwArgc, LPSTR *lpszArgv);
54 VOID CmdInstallService(LPCSTR);
55 VOID CmdRemoveService(LPCSTR);
56 LPTSTR GetLastErrorText( LPSTR lpszBuf, DWORD dwSize );
57
58 BOOL LogEvent(
59     LPCTSTR  lpUNCServerName,
60     WORD  wType,
61     DWORD  dwEventID,
62     PSID  lpUserSid,
63     LPCTSTR  message);
64
65 VOID ServiceStart(DWORD dwArgc, LPSTR *lpszArgv);
66 VOID ServiceStop();
67 BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint);
68 void AddToMessageLog(LPSTR lpszMsg);
69
70 BOOL WINAPI BreakHandler(DWORD dwCtrlType)
71 {
72    switch(dwCtrlType)
73     {
74         case CTRL_BREAK_EVENT:  // use Ctrl+C or Ctrl+Break to simulate
75         case CTRL_C_EVENT:      // SERVICE_CONTROL_STOP in console mode
76             ServiceStop();
77             return TRUE;
78     }
79     return FALSE;
80 }
81
82
83 int real_main(int);  // The revised two-phase main() in shibd.cpp
84
85 int main(int argc, char *argv[])
86 {
87     int i=1;
88     while ((argc > i) && ((*argv[i] == '-') || (*argv[i] == '/')))
89     {
90         if (_stricmp("install", argv[i]+1) == 0)
91         {
92             if (argc > ++i)
93                 lpszInstall = argv[i++];
94         }
95         else if (_stricmp("remove", argv[i]+1) == 0)
96         {
97             if (argc > ++i)
98                 lpszRemove = argv[i++];
99         }
100         else if (_stricmp("stdout", argv[i]+1) == 0)
101         {
102             if (argc > ++i)
103                 freopen(argv[i++], "a+", stdout);
104         }
105         else if (_stricmp("stderr", argv[i]+1) == 0)
106         {
107             if (argc > ++i)
108                 freopen(argv[i++], "a+", stderr);
109         }
110         else if (_stricmp( "console", argv[i]+1) == 0)
111         {
112             i++;
113             bConsole = TRUE;
114         }
115         else if (_stricmp( "check", argv[i]+1) == 0)
116         {
117             i++;
118             bConsole = TRUE;
119             shar_checkonly=true;
120         }
121         else if (_stricmp( "config", argv[i]+1) == 0)
122         {
123             if (argc > ++i)
124                 shar_config = argv[i++];
125         }
126         else if (_stricmp( "prefix", argv[i]+1) == 0)
127         {
128             if (argc > ++i)
129                 shar_prefix = argv[i++];
130         }
131         else if (_stricmp( "catalogs", argv[i]+1) == 0)
132         {
133             if (argc > ++i)
134                 shar_schemadir = argv[i++];
135         }
136         else
137         {
138             goto dispatch;
139         }
140     }
141     
142     if (bConsole)
143     {
144         // Install break handler, then run the C routine twice, once to setup, once to start running.
145         SetConsoleCtrlHandler(&BreakHandler,TRUE);
146         if ((i=real_main(1))!=0)
147         {
148             LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "shibd startup failed, check shibd.log for further details");
149             return i;
150         }
151         return real_main(0);
152     }
153     else if (lpszInstall)
154     {
155         CmdInstallService(lpszInstall);
156         return 0;
157     }
158     else if (lpszRemove)
159     {
160         CmdRemoveService(lpszRemove);
161         return 0;
162     }
163     
164
165     // if it doesn't match any of the above parameters
166     // the service control manager may be starting the service
167     // so we must call StartServiceCtrlDispatcher
168     dispatch:
169         // this is just to be friendly
170         printf("%s -install <name>   to install the named service\n", argv[0]);
171         printf("%s -remove <name>    to remove the named service\n", argv[0]);
172         printf("%s -console          to run as a console app for debugging\n", argv[0]);
173         printf("%s -check            to run as a console app and check configuration\n", argv[0]);
174         printf("\t-stdout <path> to redirect stdout stream\n");
175         printf("\t-stderr <path> to redirect stderr stream\n");
176         printf("\t-prefix <dir> to specify the installation directory\n");
177         printf("\t-config <file> to specify the config file to use\n");
178         printf("\t-catalogs <catalog1:catalog2> to specify schema catalogs\n");
179         printf("\nService starting.\nThis may take several seconds. Please wait.\n" );
180
181     SERVICE_TABLE_ENTRY dispatchTable[] =
182     {
183         { "SHIBD", (LPSERVICE_MAIN_FUNCTION)service_main },
184         { nullptr, nullptr }
185     };
186
187     if (!StartServiceCtrlDispatcher(dispatchTable))
188         LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "StartServiceCtrlDispatcher failed.");
189     return 0;
190 }
191
192 //
193 //  FUNCTION: ServiceStart
194 //
195 //  PURPOSE: Actual code of the service
196 //          that does the work.
197 //
198 VOID ServiceStart (DWORD dwArgc, LPSTR *lpszArgv)
199 {
200
201     if (real_main(1)!=0)
202     {
203         LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "shibd startup failed, check shibd.log for further details");
204         return;
205     }
206
207     LogEvent(nullptr, EVENTLOG_INFORMATION_TYPE, 7700, nullptr, "shibd started successfully.");
208
209     if (!ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0))
210         return;
211
212     real_main(0);
213 }
214
215
216 //
217 //  FUNCTION: ServiceStop
218 //
219 //   PURPOSE: Stops the service
220 //
221 VOID ServiceStop()
222 {
223     if (!bConsole)
224         LogEvent(nullptr, EVENTLOG_INFORMATION_TYPE, 7701, nullptr, "shibd stopping...");
225     shibd_shutdown=true;
226 }
227
228
229 void WINAPI service_main(DWORD dwArgc, LPSTR *lpszArgv)
230 {
231
232     // register our service control handler:
233     sshStatusHandle=RegisterServiceCtrlHandler(lpszArgv[0], service_ctrl);
234     if (!sshStatusHandle)
235         goto cleanup;
236
237     // SERVICE_STATUS members that don't change in example
238     ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
239     ssStatus.dwServiceSpecificExitCode = 0;
240
241
242     // report the status to the service control manager.
243     if (!ReportStatusToSCMgr(
244         SERVICE_START_PENDING, // service state
245         NO_ERROR,              // exit code
246         3000))                 // wait hint
247         goto cleanup;
248
249
250     ServiceStart(dwArgc, lpszArgv);
251
252 cleanup:
253
254     // try to report the stopped status to the service control manager.
255     //
256     if (sshStatusHandle)
257         (VOID)ReportStatusToSCMgr(
258                             SERVICE_STOPPED,
259                             dwErr,
260                             0);
261
262     return;
263 }
264
265
266 //
267 //  FUNCTION: service_ctrl
268 //
269 //  PURPOSE: This function is called by the SCM whenever
270 //           ControlService() is called on this service.
271 //
272 //  PARAMETERS:
273 //    dwCtrlCode - type of control requested
274 //
275 //  RETURN VALUE:
276 //    none
277 //
278 VOID WINAPI service_ctrl(DWORD dwCtrlCode)
279 {
280     // Handle the requested control code.
281     //
282     switch(dwCtrlCode)
283     {
284         // Stop the service.
285         //
286         case SERVICE_CONTROL_STOP:
287             ssStatus.dwCurrentState = SERVICE_STOP_PENDING;
288             ServiceStop();
289             break;
290
291         // Update the service status.
292         //
293         case SERVICE_CONTROL_INTERROGATE:
294             break;
295
296         // invalid control code
297         //
298         default:
299             break;
300
301     }
302
303     ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
304 }
305
306
307 //
308 //  FUNCTION: ReportStatusToSCMgr()
309 //
310 //  PURPOSE: Sets the current status of the service and
311 //           reports it to the Service Control Manager
312 //
313 //  PARAMETERS:
314 //    dwCurrentState - the state of the service
315 //    dwWin32ExitCode - error code to report
316 //    dwWaitHint - worst case estimate to next checkpoint
317 //
318 //  RETURN VALUE:
319 //    TRUE  - success
320 //    FALSE - failure
321 //
322 BOOL ReportStatusToSCMgr(DWORD dwCurrentState,
323                          DWORD dwWin32ExitCode,
324                          DWORD dwWaitHint)
325 {
326     static DWORD dwCheckPoint = 1;
327     BOOL fResult = TRUE;
328
329
330     if (!bConsole) // when console we don't report to the SCM
331     {
332         if (dwCurrentState == SERVICE_START_PENDING)
333             ssStatus.dwControlsAccepted = 0;
334         else
335             ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
336
337         ssStatus.dwCurrentState = dwCurrentState;
338         ssStatus.dwWin32ExitCode = dwWin32ExitCode;
339         ssStatus.dwWaitHint = dwWaitHint;
340
341         if ( ( dwCurrentState == SERVICE_RUNNING ) ||
342              ( dwCurrentState == SERVICE_STOPPED ) )
343             ssStatus.dwCheckPoint = 0;
344         else
345             ssStatus.dwCheckPoint = dwCheckPoint++;
346
347
348         // Report the status of the service to the service control manager.
349         //
350         if (!(fResult = SetServiceStatus(sshStatusHandle, &ssStatus)))
351             LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "SetServiceStatus failed.");
352     }
353     return fResult;
354 }
355
356
357 ///////////////////////////////////////////////////////////////////
358 //
359 //  The following code handles service installation and removal
360 //
361 //
362 void CmdInstallService(LPCSTR name)
363 {
364     SC_HANDLE   schService;
365     SC_HANDLE   schSCManager;
366
367     char szPath[256];
368
369     if ( GetModuleFileName( nullptr, szPath, 256 ) == 0 )
370     {
371         printf("Unable to install %s - %s\n", name, GetLastErrorText(szErr, 256));
372         return;
373     }
374     
375     string dispName = string("Shibboleth 2 Daemon (") + name + ")";
376     string realName = string("shibd_") + name;
377     string cmd(szPath);
378     if (shar_prefix)
379         cmd = cmd + " -prefix " + shar_prefix;
380     if (shar_config)
381         cmd = cmd + " -config " + shar_config;
382     if (shar_schemadir)
383         cmd = cmd + " -schemadir " + shar_schemadir;
384
385     schSCManager = OpenSCManager(
386                         nullptr,                   // machine (nullptr == local)
387                         nullptr,                   // database (nullptr == default)
388                         SC_MANAGER_ALL_ACCESS   // access required
389                         );
390     
391     
392     if ( schSCManager )
393     {
394         schService = CreateService(
395             schSCManager,               // SCManager database
396             realName.c_str(),           // name of service
397             dispName.c_str(),           // name to display
398             SERVICE_ALL_ACCESS,         // desired access
399             SERVICE_WIN32_OWN_PROCESS,  // service type
400             SERVICE_AUTO_START,         // start type
401             SERVICE_ERROR_NORMAL,       // error control type
402             cmd.c_str(),                // service's command line
403             nullptr,                       // no load ordering group
404             nullptr,                       // no tag identifier
405             nullptr,                       // dependencies
406             nullptr,                       // LocalSystem account
407             nullptr);                      // no password
408
409         if ( schService )
410         {
411             printf("%s installed.\n", realName.c_str());
412             CloseServiceHandle(schService);
413         }
414         else
415         {
416             printf("CreateService failed - %s\n", GetLastErrorText(szErr, 256));
417         }
418
419         CloseServiceHandle(schSCManager);
420     }
421     else
422         printf("OpenSCManager failed - %s\n", GetLastErrorText(szErr,256));
423 }
424
425 void CmdRemoveService(LPCSTR name)
426 {
427     SC_HANDLE   schService;
428     SC_HANDLE   schSCManager;
429     char        realName[512];
430
431     _snprintf(realName,sizeof(realName),"shibd_%s",name);
432
433     schSCManager = OpenSCManager(
434                         nullptr,                   // machine (nullptr == local)
435                         nullptr,                   // database (nullptr == default)
436                         SC_MANAGER_ALL_ACCESS   // access required
437                         );
438     if ( schSCManager )
439     {
440         schService = OpenService(schSCManager, realName, SERVICE_ALL_ACCESS);
441
442         if (schService)
443         {
444             // try to stop the service
445             if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) )
446             {
447                 printf("Stopping shibd (%s).", name);
448                 Sleep( 1000 );
449
450                 while( QueryServiceStatus( schService, &ssStatus ) )
451                 {
452                     if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING )
453                     {
454                         printf(".");
455                         Sleep( 1000 );
456                     }
457                     else
458                         break;
459                 }
460
461                 if ( ssStatus.dwCurrentState == SERVICE_STOPPED )
462                     printf("\n%s stopped.\n", realName);
463                 else
464                     printf("\n%s failed to stop.\n", realName);
465
466             }
467
468             // now remove the service
469             if( DeleteService(schService) )
470                 printf("%s removed.\n", realName);
471             else
472                 printf("DeleteService failed - %s\n", GetLastErrorText(szErr,256));
473
474
475             CloseServiceHandle(schService);
476         }
477         else
478             printf("OpenService failed - %s\n", GetLastErrorText(szErr,256));
479
480         CloseServiceHandle(schSCManager);
481     }
482     else
483         printf("OpenSCManager failed - %s\n", GetLastErrorText(szErr,256));
484 }
485
486
487 //
488 //  FUNCTION: GetLastErrorText
489 //
490 //  PURPOSE: copies error message text to string
491 //
492 //  PARAMETERS:
493 //    lpszBuf - destination buffer
494 //    dwSize - size of buffer
495 //
496 //  RETURN VALUE:
497 //    destination buffer
498 //
499 //  COMMENTS:
500 //
501 LPTSTR GetLastErrorText( LPSTR lpszBuf, DWORD dwSize )
502 {
503     DWORD dwRet;
504     LPSTR lpszTemp = nullptr;
505
506     dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY,
507                            nullptr,
508                            GetLastError(),
509                            LANG_NEUTRAL,
510                            (LPSTR)&lpszTemp,
511                            0,
512                            nullptr );
513
514     // supplied buffer is not long enough
515     if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) )
516         lpszBuf[0] = '\0';
517     else
518     {
519         lpszTemp[lstrlen(lpszTemp)-2] = '\0';  //remove cr and newline character
520         sprintf( lpszBuf, "%s (0x%x)", lpszTemp, GetLastError() );
521     }
522
523     if ( lpszTemp )
524         LocalFree((HLOCAL) lpszTemp );
525
526     return lpszBuf;
527 }
528
529 BOOL LogEvent(
530     LPCSTR  lpUNCServerName,
531     WORD  wType,
532     DWORD  dwEventID,
533     PSID  lpUserSid,
534     LPCSTR  message)
535 {
536     LPCSTR  messages[] = {message, nullptr};
537     
538     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth Daemon");
539     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, nullptr);
540     return (DeregisterEventSource(hElog) && res);
541 }