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