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