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