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