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