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