SSPCPP-412 - systemd integration changes
[shibboleth/cpp-sp.git] / shibd / shibd.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  * shibd.cpp -- the shibd "main" code.
23  */
24
25
26 // eventually we might be able to support autoconf via cygwin...
27 #if defined (_MSC_VER) || defined(__BORLANDC__)
28 # include "config_win32.h"
29 #else
30 # include "config.h"
31 #endif
32
33 #ifdef WIN32
34 # define _CRT_NONSTDC_NO_DEPRECATE 1
35 # define _CRT_SECURE_NO_DEPRECATE 1
36 #endif
37
38 #include <shibsp/SPConfig.h>
39
40 #ifdef HAVE_UNISTD_H
41 # include <unistd.h>
42 # include <sys/select.h>
43 #endif
44
45 #if defined(HAVE_GRP_H) && defined(HAVE_PWD_H)
46 # include <pwd.h>
47 # include <grp.h>
48 #endif
49
50 #include <stdio.h>
51 #include <signal.h>
52 #include <shibsp/ServiceProvider.h>
53 #include <shibsp/remoting/ListenerService.h>
54 #include <xercesc/util/XMLUniDefs.hpp>
55 #include <xmltooling/XMLToolingConfig.h>
56 #include <xmltooling/util/XMLConstants.h>
57 #include <xmltooling/util/XMLHelper.h>
58
59 #ifdef HAVE_SD_NOTIFY
60 #include <systemd/sd-daemon.h>
61 #else
62 #define SD_EMERG   ""
63 #define SD_ALERT   ""
64 #define SD_CRIT    ""
65 #define SD_ERR     ""
66 #define SD_WARNING ""
67 #define SD_NOTICE  ""
68 #define SD_INFO    ""
69 #define SD_DEBUG   ""
70 #endif
71
72 using namespace shibsp;
73 using namespace xmltooling;
74 using namespace std;
75
76 bool shibd_shutdown = false;
77 const char* shar_config = nullptr;
78 const char* shar_schemadir = nullptr;
79 const char* shar_prefix = nullptr;
80 bool shar_checkonly = false;
81 bool shar_version = false;
82 static bool unlink_socket = false;
83 const char* pidfile = nullptr;
84
85 #ifdef WIN32
86
87 //#include <CRTDBG.H>
88
89 #define nNoMansLandSize 4
90 typedef struct _CrtMemBlockHeader
91 {
92         struct _CrtMemBlockHeader * pBlockHeaderNext;
93         struct _CrtMemBlockHeader * pBlockHeaderPrev;
94         char *                      szFileName;
95         int                         nLine;
96         size_t                      nDataSize;
97         int                         nBlockUse;
98         long                        lRequest;
99         unsigned char               gap[nNoMansLandSize];
100         /* followed by:
101          *  unsigned char           data[nDataSize];
102          *  unsigned char           anotherGap[nNoMansLandSize];
103          */
104 } _CrtMemBlockHeader;
105
106 /*
107 int MyAllocHook(int nAllocType, void *pvData,
108       size_t nSize, int nBlockUse, long lRequest,
109       const unsigned char * szFileName, int nLine)
110 {
111     if ( nBlockUse == _CRT_BLOCK )
112       return( TRUE );
113     if (nAllocType == _HOOK_FREE) {
114         _CrtMemBlockHeader* ptr = (_CrtMemBlockHeader*)(((_CrtMemBlockHeader *)pvData)-1);
115         if (ptr->nDataSize == 8192)
116             fprintf(stderr,"free  request %u size %u\n", ptr->lRequest, ptr->nDataSize);
117     }
118     else if (nAllocType == _HOOK_ALLOC && nSize == 8192)
119         fprintf(stderr,"%s request %u size %u\n", ((nAllocType == _HOOK_ALLOC) ? "alloc" : "realloc"), lRequest, nSize);
120     return (TRUE);
121 }
122 */
123
124 int real_main(int preinit)
125 {
126     if (shar_version) {
127         if (preinit)
128             fprintf(stdout, PACKAGE_STRING"\n");
129         return 0;
130     }
131
132     SPConfig& conf = SPConfig::getConfig();
133     if (preinit) {
134         // Initialize the SP library.
135         conf.setFeatures(
136             SPConfig::Listener |
137             SPConfig::Caching |
138             SPConfig::Metadata |
139             SPConfig::Trust |
140             SPConfig::Credentials |
141             SPConfig::AttributeResolution |
142             SPConfig::Handlers |
143             SPConfig::OutOfProcess |
144             (shar_checkonly ? SPConfig::RequestMapping : SPConfig::Logging)
145             );
146         if (!conf.init(shar_schemadir, shar_prefix)) {
147             fprintf(stderr, "configuration is invalid, see console for specific problems\n");
148             return -1;
149         }
150
151         if (!conf.instantiate(shar_config)) {
152             fprintf(stderr, "configuration is invalid, check console for specific problems\n");
153             conf.term();
154             return -2;
155         }
156
157         // If just a test run, bail.
158         if (shar_checkonly) {
159             fprintf(stdout, "overall configuration is loadable, check console for non-fatal problems\n");
160             return 0;
161         }
162     }
163     else {
164
165         //_CrtSetAllocHook(MyAllocHook);
166
167         if (!shar_checkonly) {
168             // Run the listener.
169             ListenerService* listener = conf.getServiceProvider()->getListenerService();
170             if (!listener->init(unlink_socket)) {
171                 fprintf(stderr, "listener failed to initialize\n");
172                 conf.term();
173                 return -3;
174             }
175             else if (!listener->run(&shibd_shutdown)) {
176                 fprintf(stderr, "listener failed during service\n");
177                 listener->term();
178                 conf.term();
179                 return -3;
180             }
181             listener->term();
182         }
183
184         conf.term();
185     }
186     return 0;
187 }
188
189 #else
190
191 int daemon_wait = 3;
192 bool shibd_running = false;
193 bool daemonize = true;
194 const char* runasuser = nullptr;
195 const char* runasgroup = nullptr;
196
197 static void term_handler(int arg)
198 {
199     shibd_shutdown = true;
200 }
201
202 static void run_handler(int arg)
203 {
204     shibd_running = true;
205 }
206
207 static void child_handler(int arg)
208 {
209     // Terminate the parent's wait/sleep if the newly born daemon dies early.
210 }
211
212 static int setup_signals(void)
213 {
214     struct sigaction sa;
215     memset(&sa, 0, sizeof (sa));
216     sa.sa_handler = SIG_IGN;
217     sa.sa_flags = SA_RESTART;
218
219     if (sigaction(SIGPIPE, &sa, nullptr) < 0) {
220         return -1;
221     }
222
223     memset(&sa, 0, sizeof (sa));
224     sa.sa_handler = term_handler;
225     sa.sa_flags = SA_RESTART;
226
227     if (sigaction(SIGHUP, &sa, nullptr) < 0) {
228         return -1;
229     }
230     if (sigaction(SIGINT, &sa, nullptr) < 0) {
231         return -1;
232     }
233     if (sigaction(SIGQUIT, &sa, nullptr) < 0) {
234         return -1;
235     }
236     if (sigaction(SIGTERM, &sa, nullptr) < 0) {
237         return -1;
238     }
239
240     if (daemonize) {
241         memset(&sa, 0, sizeof (sa));
242         sa.sa_handler = run_handler;
243
244         if (sigaction(SIGUSR1, &sa, nullptr) < 0) {
245             return -1;
246         }
247
248         memset(&sa, 0, sizeof (sa));
249         sa.sa_handler = child_handler;
250
251         if (sigaction(SIGCHLD, &sa, nullptr) < 0) {
252             return -1;
253         }
254     }
255
256     return 0;
257 }
258
259 static void usage(char* whoami)
260 {
261     fprintf(stderr, "usage: %s [-dcxtfFpwugvh]\n", whoami);
262     fprintf(stderr, "  -d\tinstallation prefix to use\n");
263     fprintf(stderr, "  -c\tconfig file to use\n");
264     fprintf(stderr, "  -x\tXML schema catalogs to use\n");
265     fprintf(stderr, "  -t\ttest configuration file for problems\n");
266     fprintf(stderr, "  -f\tforce removal of listener socket\n");
267     fprintf(stderr, "  -F\tstay in the foreground\n");
268     fprintf(stderr, "  -p\tpid file to use\n");
269     fprintf(stderr, "  -w\tseconds to wait for successful daemonization\n");
270     fprintf(stderr, "  -u\tuser to run under\n");
271     fprintf(stderr, "  -g\tgroup to run under\n");
272     fprintf(stderr, "  -v\tprint software version\n");
273     fprintf(stderr, "  -h\tprint this help message\n");
274     exit(1);
275 }
276
277 static int parse_args(int argc, char* argv[])
278 {
279     int opt;
280
281     while ((opt = getopt(argc, argv, "d:c:x:p:w:u:g:fFtvh")) > 0) {
282         switch (opt) {
283             case 'd':
284                 shar_prefix=optarg;
285                 break;
286             case 'c':
287                 shar_config=optarg;
288                 break;
289             case 'x':
290                 shar_schemadir=optarg;
291                 break;
292             case 'f':
293                 unlink_socket = true;
294                 break;
295             case 'F':
296                 daemonize = false;
297                 break;
298             case 't':
299                 shar_checkonly=true;
300                 daemonize=false;
301                 break;
302             case 'v':
303                 shar_version=true;
304                 break;
305             case 'p':
306                 pidfile=optarg;
307                 break;
308             case 'w':
309                 if (optarg)
310                     daemon_wait = atoi(optarg);
311                 if (daemon_wait <= 0)
312                     daemon_wait = 3;
313                 break;
314             case 'u':
315                 if (optarg)
316                     runasuser = optarg;
317                 break;
318             case 'g':
319                 if (optarg)
320                     runasgroup = optarg;
321                 break;
322             default:
323                 return -1;
324         }
325     }
326     return 0;
327 }
328
329 int main(int argc, char *argv[])
330 {
331     if (parse_args(argc, argv) != 0)
332         usage(argv[0]);
333     else if (shar_version) {
334         fprintf(stdout, PACKAGE_STRING"\n");
335         return 0;
336     }
337
338     if (setup_signals() != 0)
339         return -1;
340
341     if (runasgroup) {
342 #ifdef HAVE_GETGRNAM
343         struct group* grp = getgrnam(runasgroup);
344         if (!grp) {
345             fprintf(stderr, "getgrnam failed, check -g option\n");
346             return -1;
347         }
348         if (setgid(grp->gr_gid) != 0) {
349             fprintf(stderr, "setgid failed, check -g option\n");
350             return -1;
351         }
352 #else
353         fprintf(stderr, "-g not supported on this platform");
354         return -1;
355 #endif
356     }
357
358     if (runasuser) {
359 #ifdef HAVE_GETPWNAM
360         struct passwd* pwd = getpwnam(runasuser);
361         if (!pwd) {
362             fprintf(stderr, "getpwnam failed, check -u option\n");
363             return -1;
364         }
365 #ifdef HAVE_INITGROUPS
366         // w/out initgroups/setgroups process retains supplementary groups
367         if (initgroups(pwd->pw_name, pwd->pw_gid) != 0) {
368             fprintf(stderr, "initgroups failed, check -u option\n");
369             return -1;
370         }
371 #endif
372         if (setuid(pwd->pw_uid) != 0) {
373             fprintf(stderr, "setuid failed, check -u option\n");
374             return -1;
375         }
376 #else
377         fprintf(stderr, "-u not supported on this platform");
378         return -1;
379 #endif
380     }
381
382     // initialize the shib-target library
383     SPConfig& conf=SPConfig::getConfig();
384     conf.setFeatures(
385         SPConfig::Listener |
386         SPConfig::Caching |
387         SPConfig::Metadata |
388         SPConfig::Trust |
389         SPConfig::Credentials |
390         SPConfig::AttributeResolution |
391         SPConfig::Handlers |
392         SPConfig::OutOfProcess |
393         (shar_checkonly ? SPConfig::RequestMapping : SPConfig::Logging)
394         );
395     if (!conf.init(shar_schemadir, shar_prefix)) {
396         fprintf(stderr, SD_ERR "configuration is invalid, check console for specific problems\n");
397         return -1;
398     }
399
400     if (daemonize) {
401         // We must fork() early, while we're single threaded.
402         // StorageService cleanup thread is about to start.
403         switch (fork()) {
404             case 0:
405                 break;
406             case -1:
407                 perror("forking");
408                 exit(EXIT_FAILURE);
409             default:
410                 sleep(daemon_wait);
411                 exit(shibd_running ? EXIT_SUCCESS : EXIT_FAILURE);
412         }
413     }
414
415     if (!conf.instantiate(shar_config)) {
416         fprintf(stderr, SD_ERR "configuration is invalid, check console for specific problems\n");
417         conf.term();
418         return -2;
419     }
420
421     if (shar_checkonly)
422         fprintf(stderr, "overall configuration is loadable, check console for non-fatal problems\n");
423     else {
424         // Init the listener.
425         ListenerService* listener = conf.getServiceProvider()->getListenerService();
426         if (!listener->init(unlink_socket)) {
427             fprintf(stderr, SD_ERR "listener failed to initialize\n");
428             conf.term();
429             return -3;
430         }
431
432         if (daemonize) {
433             if (setsid() == -1) {
434                 perror("setsid");
435                 exit(EXIT_FAILURE);
436             }
437             if (chdir("/") == -1) {
438                 perror("chdir to root");
439                 exit(EXIT_FAILURE);
440             }
441
442             if (pidfile) {
443                 FILE* pidf = fopen(pidfile, "w");
444                 if (pidf) {
445                     fprintf(pidf, "%d\n", getpid());
446                     fclose(pidf);
447                 }
448                 else {
449                     perror(pidfile);
450                 }
451             }
452
453             freopen("/dev/null", "r", stdin);
454             freopen("/dev/null", "w", stdout);
455             freopen("/dev/null", "w", stderr);
456
457             // Signal our parent that we are A-OK.
458             kill(getppid(), SIGUSR1);
459         }
460
461         // Run the listener.
462 #ifdef HAVE_SD_NOTIFY
463         sd_notify(0, "READY=1");
464 #endif
465         if (!listener->run(&shibd_shutdown)) {
466             fprintf(stderr, SD_ERR "listener failure during service\n");
467             listener->term();
468             conf.term();
469             if (daemonize && pidfile)
470                 unlink(pidfile);
471             return -3;
472         }
473         listener->term();
474     }
475 #ifdef HAVE_SD_NOTIFY
476     sd_notify(0, "STOPPING=1");
477 #endif
478     conf.term();
479     if (daemonize && pidfile)
480         unlink(pidfile);
481     return 0;
482 }
483
484 #endif