Boost changes
[shibboleth/cpp-sp.git] / shibsp / handler / impl / StatusHandler.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  * StatusHandler.cpp
23  *
24  * Handler for exposing information about the internals of the SP.
25  */
26
27 #include "internal.h"
28 #include "Application.h"
29 #include "exceptions.h"
30 #include "ServiceProvider.h"
31 #include "SPRequest.h"
32 #include "handler/AbstractHandler.h"
33 #include "handler/RemotedHandler.h"
34 #include "util/IPRange.h"
35 #include "util/CGIParser.h"
36
37 #include <boost/bind.hpp>
38 #include <boost/iterator/indirect_iterator.hpp>
39 #include <boost/scoped_ptr.hpp>
40 #include <boost/algorithm/string.hpp>
41 #include <xmltooling/version.h>
42 #include <xmltooling/util/DateTime.h>
43
44 #ifdef HAVE_SYS_UTSNAME_H
45 # include <sys/utsname.h>
46 #endif
47
48 using namespace shibsp;
49 #ifndef SHIBSP_LITE
50 # include "SessionCache.h"
51 # include "metadata/MetadataProviderCriteria.h"
52 # include <saml/version.h>
53 # include <saml/saml2/metadata/Metadata.h>
54 # include <xmltooling/security/Credential.h>
55 # include <xmltooling/security/CredentialCriteria.h>
56 using namespace opensaml::saml2md;
57 using namespace opensaml;
58 using namespace xmlsignature;
59 #endif
60 using namespace xmltooling;
61 using namespace boost;
62 using namespace std;
63
64 namespace shibsp {
65
66 #if defined (_MSC_VER)
67     #pragma warning( push )
68     #pragma warning( disable : 4250 )
69 #endif
70
71     class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
72     {
73     public:
74 #ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
75         short
76 #else
77         FilterAction
78 #endif
79         acceptNode(const DOMNode* node) const {
80             return FILTER_REJECT;
81         }
82     };
83
84     static SHIBSP_DLLLOCAL Blocker g_Blocker;
85
86     class SHIBSP_API StatusHandler : public AbstractHandler, public RemotedHandler
87     {
88     public:
89         StatusHandler(const DOMElement* e, const char* appId);
90         virtual ~StatusHandler() {}
91
92         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
93         void receive(DDF& in, ostream& out);
94
95     private:
96         pair<bool,long> processMessage(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
97         ostream& systemInfo(ostream& os) const;
98         void parseACL(const string& acl) {
99             try {
100                 m_acl.push_back(IPRange::parseCIDRBlock(acl.c_str()));
101             }
102             catch (std::exception& ex) {
103                 m_log.error("invalid CIDR block (%s): %s", acl.c_str(), ex.what());
104             }
105         }
106
107         vector<IPRange> m_acl;
108     };
109
110 #if defined (_MSC_VER)
111     #pragma warning( pop )
112 #endif
113
114     Handler* SHIBSP_DLLLOCAL StatusHandlerFactory(const pair<const DOMElement*,const char*>& p)
115     {
116         return new StatusHandler(p.first, p.second);
117     }
118
119 #ifndef XMLTOOLING_NO_XMLSEC
120     vector<XSECCryptoX509*> g_NoCerts;
121 #else
122     vector<string> g_NoCerts;
123 #endif
124
125     static char _x2c(const char *what)
126     {
127         register char digit;
128
129         digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
130         digit *= 16;
131         digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
132         return(digit);
133     }
134
135     class DummyRequest : public HTTPRequest
136     {
137     public:
138         DummyRequest(const char* url) : m_parser(nullptr), m_url(url), m_scheme(nullptr), m_query(nullptr), m_port(0) {
139 #ifdef HAVE_STRCASECMP
140             if (url && !strncasecmp(url,"http://", 7)) {
141                 m_scheme = "http";
142                 m_port = 80;
143                 url += 7;
144             }
145             else if (url && !strncasecmp(url,"https://", 8)) {
146                 m_scheme = "https";
147                 m_port = 443;
148                 url += 8;
149             }
150             else
151 #else
152             if (url && !strnicmp(url,"http://", 7)) {
153                 m_scheme = "http";
154                 m_port = 80;
155                 url += 7;
156             }
157             else if (url && !strnicmp(url,"https://", 8)) {
158                 m_scheme="https";
159                 m_port = 443;
160                 url += 8;
161             }
162             else
163 #endif
164                 throw invalid_argument("Target parameter was not an absolute URL.");
165
166             m_query = strchr(url,'?');
167             if (m_query)
168                 m_query++;
169
170             const char* slash = strchr(url, '/');
171             const char* colon = strchr(url, ':');
172             if (colon && colon < slash) {
173                 m_hostname.assign(url, colon-url);
174                 string port(colon + 1, slash - colon);
175                 m_port = atoi(port.c_str());
176             }
177             else {
178                 m_hostname.assign(url, slash - url);
179             }
180
181             while (*slash) {
182                 if (*slash == '?') {
183                     m_uri += slash;
184                     break;
185                 }
186                 else if (*slash != '%') {
187                     m_uri += *slash;
188                 }
189                 else {
190                     ++slash;
191                     if (!isxdigit(*slash) || !isxdigit(*(slash+1)))
192                         throw invalid_argument("Bad request, contained unsupported encoded characters.");
193                     m_uri += _x2c(slash);
194                     ++slash;
195                 }
196                 ++slash;
197             }
198         }
199
200         virtual ~DummyRequest() {}
201
202         const char* getRequestURL() const {
203             return m_url;
204         }
205         const char* getScheme() const {
206             return m_scheme;
207         }
208         const char* getHostname() const {
209             return m_hostname.c_str();
210         }
211         int getPort() const {
212             return m_port;
213         }
214         const char* getRequestURI() const {
215             return m_uri.c_str();
216         }
217         const char* getMethod() const {
218             return "GET";
219         }
220         string getContentType() const {
221             return "";
222         }
223         long getContentLength() const {
224             return 0;
225         }
226         string getRemoteAddr() const {
227             return "";
228         }
229         string getRemoteUser() const {
230             return "";
231         }
232         const char* getRequestBody() const {
233             return nullptr;
234         }
235         const char* getQueryString() const {
236             return m_query;
237         }
238         const char* getParameter(const char* name) const
239         {
240             if (!m_parser)
241                 m_parser.reset(new CGIParser(*this));
242
243             pair<CGIParser::walker,CGIParser::walker> bounds = m_parser->getParameters(name);
244             return (bounds.first == bounds.second) ? nullptr : bounds.first->second;
245         }
246         vector<const char*>::size_type getParameters(const char* name, vector<const char*>& values) const
247         {
248             if (!m_parser)
249                 m_parser.reset(new CGIParser(*this));
250
251             pair<CGIParser::walker,CGIParser::walker> bounds = m_parser->getParameters(name);
252             while (bounds.first != bounds.second) {
253                 values.push_back(bounds.first->second);
254                 ++bounds.first;
255             }
256             return values.size();
257         }
258         string getHeader(const char* name) const {
259             return "";
260         }
261         virtual const
262 #ifndef XMLTOOLING_NO_XMLSEC
263             std::vector<XSECCryptoX509*>&
264 #else
265             std::vector<std::string>&
266 #endif
267             getClientCertificates() const {
268                 return g_NoCerts;
269         }
270
271     private:
272         mutable scoped_ptr<CGIParser> m_parser;
273         const char* m_url;
274         const char* m_scheme;
275         const char* m_query;
276         int m_port;
277         string m_hostname,m_uri;
278     };
279 };
280
281 StatusHandler::StatusHandler(const DOMElement* e, const char* appId)
282     : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".StatusHandler"), &g_Blocker)
283 {
284     if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
285         pair<bool,const char*> acl = getString("acl");
286         if (acl.first) {
287             string aclbuf=acl.second;
288             vector<string> aclarray;
289             split(aclarray, aclbuf, is_space(), algorithm::token_compress_on);
290             for_each(aclarray.begin(), aclarray.end(), boost::bind(&StatusHandler::parseACL, this, _1));
291             if (m_acl.empty()) {
292                 m_log.warn("invalid CIDR range(s) in Status handler acl property, allowing 127.0.0.1 as a fall back");
293                 m_acl.push_back(IPRange::parseCIDRBlock("127.0.0.1"));
294             }
295         }
296     }
297
298     string address(appId);
299     address += getString("Location").second;
300     setAddress(address.c_str());
301 }
302
303 pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
304 {
305     SPConfig& conf = SPConfig::getConfig();
306     if (conf.isEnabled(SPConfig::InProcess) && !m_acl.empty()) {
307         static bool (IPRange::* contains)(const char*) const = &IPRange::contains;
308         if (find_if(m_acl.begin(), m_acl.end(), boost::bind(contains, _1, request.getRemoteAddr().c_str())) == m_acl.end()) {
309             m_log.error("status handler request blocked from invalid address (%s)", request.getRemoteAddr().c_str());
310             istringstream msg("Status Handler Blocked");
311             return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN));
312         }
313     }
314
315     const char* target = request.getParameter("target");
316     if (target) {
317         // RequestMap query, so handle it inproc.
318         DummyRequest dummy(target);
319         RequestMapper::Settings settings = request.getApplication().getServiceProvider().getRequestMapper()->getSettings(dummy);
320         map<string,const char*> props;
321         settings.first->getAll(props);
322
323         DateTime now(time(nullptr));
324         now.parseDateTime();
325         auto_ptr_char timestamp(now.getFormattedString());
326         request.setContentType("text/xml");
327         stringstream msg;
328         msg << "<StatusHandler time='" << timestamp.get() << "'>";
329             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
330                 << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
331 #ifndef SHIBSP_LITE
332                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
333                 << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
334 #endif
335                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
336             systemInfo(msg) << "<RequestSettings";
337             for (map<string,const char*>::const_iterator p = props.begin(); p != props.end(); ++p)
338                 msg << ' ' << p->first << "='" << p->second << "'";
339             msg << '>' << target << "</RequestSettings>";
340             msg << "<Status><OK/></Status>";
341         msg << "</StatusHandler>";
342         return make_pair(true, request.sendResponse(msg));
343     }
344
345     try {
346         if (conf.isEnabled(SPConfig::OutOfProcess)) {
347             // When out of process, we run natively and directly process the message.
348             return processMessage(request.getApplication(), request, request);
349         }
350         else {
351             // When not out of process, we remote all the message processing.
352             DDF out,in = wrap(request);
353             DDFJanitor jin(in), jout(out);
354             out=request.getServiceProvider().getListenerService()->send(in);
355             return unwrap(request, out);
356         }
357     }
358     catch (XMLToolingException& ex) {
359         m_log.error("error while processing request: %s", ex.what());
360         DateTime now(time(nullptr));
361         now.parseDateTime();
362         auto_ptr_char timestamp(now.getFormattedString());
363         request.setContentType("text/xml");
364         stringstream msg;
365         msg << "<StatusHandler time='" << timestamp.get() << "'>";
366             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
367                 << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
368 #ifndef SHIBSP_LITE
369                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
370                 << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
371 #endif
372                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
373             systemInfo(msg) << "<Status><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></Status>";
374         msg << "</StatusHandler>";
375         return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
376     }
377     catch (std::exception& ex) {
378         m_log.error("error while processing request: %s", ex.what());
379         DateTime now(time(nullptr));
380         now.parseDateTime();
381         auto_ptr_char timestamp(now.getFormattedString());
382         request.setContentType("text/xml");
383         stringstream msg;
384         msg << "<StatusHandler time='" << timestamp.get() << "'>";
385             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
386                 << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
387 #ifndef SHIBSP_LITE
388                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
389                 << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
390 #endif
391                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
392             systemInfo(msg) << "<Status><Exception type='std::exception'>" << ex.what() << "</Exception></Status>";
393         msg << "</StatusHandler>";
394         return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
395     }
396 }
397
398 void StatusHandler::receive(DDF& in, ostream& out)
399 {
400     // Find application.
401     const char* aid = in["application_id"].string();
402     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
403     if (!app) {
404         // Something's horribly wrong.
405         m_log.error("couldn't find application (%s) for status request", aid ? aid : "(missing)");
406         throw ConfigurationException("Unable to locate application for status request, deleted?");
407     }
408
409     // Wrap a response shim.
410     DDF ret(nullptr);
411     DDFJanitor jout(ret);
412     scoped_ptr<HTTPRequest> req(getRequest(in));
413     scoped_ptr<HTTPResponse> resp(getResponse(ret));
414
415     // Since we're remoted, the result should either be a throw, a false/0 return,
416     // which we just return as an empty structure, or a response/redirect,
417     // which we capture in the facade and send back.
418     processMessage(*app, *req, *resp);
419     out << ret;
420 }
421
422 pair<bool,long> StatusHandler::processMessage(
423     const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
424     ) const
425 {
426 #ifndef SHIBSP_LITE
427     m_log.debug("processing status request");
428
429     DateTime now(time(nullptr));
430     now.parseDateTime();
431     auto_ptr_char timestamp(now.getFormattedString());
432
433     stringstream s;
434     s << "<StatusHandler time='" << timestamp.get() << "'>";
435     const char* status = "<OK/>";
436
437     s << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
438         << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
439         << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
440         << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
441         << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
442
443     systemInfo(s);
444
445     const char* param = nullptr;
446     if (param) {
447     }
448     else {
449         // General configuration and status report.
450         try {
451             SessionCache* sc = application.getServiceProvider().getSessionCache(false);
452             if (sc) {
453                 sc->test();
454                 s << "<SessionCache><OK/></SessionCache>";
455             }
456             else {
457                 s << "<SessionCache><None/></SessionCache>";
458             }
459         }
460         catch (XMLToolingException& ex) {
461             s << "<SessionCache><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></SessionCache>";
462             status = "<Partial/>";
463         }
464         catch (std::exception& ex) {
465             s << "<SessionCache><Exception type='std::exception'>" << ex.what() << "</Exception></SessionCache>";
466             status = "<Partial/>";
467         }
468
469         MetadataProvider* m = application.getMetadataProvider();
470         Locker mlock(m);
471
472         const PropertySet* relyingParty = nullptr;
473         param=httpRequest.getParameter("entityID");
474         if (param)
475             relyingParty = application.getRelyingParty(m->getEntityDescriptor(MetadataProviderCriteria(application, param)).first);
476         else
477             relyingParty = &application;
478
479         s << "<Application id='" << application.getId() << "' entityID='" << relyingParty->getString("entityID").second << "'/>";
480
481         m->outputStatus(s);
482
483         s << "<Handlers>";
484         vector<const Handler*> handlers;
485         application.getHandlers(handlers);
486         for (indirect_iterator<vector<const Handler*>::const_iterator> h = make_indirect_iterator(handlers.begin());
487                 h != make_indirect_iterator(handlers.end()); ++h) {
488             s << "<Handler type='" << h->getType() << "' Location='" << h->getString("Location").second << "'";
489             if (h->getString("Binding").first)
490                 s << " Binding='" << h->getString("Binding").second << "'";
491             s << "/>";
492         }
493         s << "</Handlers>";
494
495         CredentialResolver* credResolver = application.getCredentialResolver();
496         if (credResolver) {
497             Locker credLocker(credResolver);
498             CredentialCriteria cc;
499             cc.setUsage(Credential::SIGNING_CREDENTIAL);
500             pair<bool,const char*> keyName = relyingParty->getString("keyName");
501             if (keyName.first)
502                 cc.getKeyNames().insert(keyName.second);
503             vector<const Credential*> creds;
504             credResolver->resolve(creds, &cc);
505             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
506                 KeyInfo* kinfo = (*c)->getKeyInfo();
507                 if (kinfo) {
508                     scoped_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
509                     kd->setUse(KeyDescriptor::KEYTYPE_SIGNING);
510                     kd->setKeyInfo(kinfo);
511                     s << *(kd.get());
512                 }
513             }
514
515             cc.setUsage(Credential::ENCRYPTION_CREDENTIAL);
516             creds.clear();
517             cc.getKeyNames().clear();
518             credResolver->resolve(creds, &cc);
519             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
520                 KeyInfo* kinfo = (*c)->getKeyInfo();
521                 if (kinfo) {
522                     scoped_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
523                     kd->setUse(KeyDescriptor::KEYTYPE_ENCRYPTION);
524                     kd->setKeyInfo(kinfo);
525                     s << *(kd.get());
526                 }
527             }
528         }
529     }
530
531     s << "<Status>" << status << "</Status></StatusHandler>";
532
533     httpResponse.setContentType("text/xml");
534     return make_pair(true, httpResponse.sendResponse(s));
535 #else
536     return make_pair(false, 0L);
537 #endif
538 }
539
540 #ifdef WIN32
541 typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
542 #endif
543
544 ostream& StatusHandler::systemInfo(ostream& os) const
545 {
546 #if defined(HAVE_SYS_UTSNAME_H)
547     struct utsname sysinfo;
548     if (uname(&sysinfo) == 0) {
549         os << "<NonWindows";
550         if (*sysinfo.sysname)
551             os << " sysname='" << sysinfo.sysname << "'";
552         if (*sysinfo.nodename)
553             os << " nodename='" << sysinfo.nodename << "'";
554         if (*sysinfo.release)
555             os << " release='" << sysinfo.release << "'";
556         if (*sysinfo.version)
557             os << " version='" << sysinfo.version << "'";
558         if (*sysinfo.machine)
559             os << " machine='" << sysinfo.machine << "'";
560         os << "/>";
561     }
562 #elif defined(WIN32)
563     OSVERSIONINFOEX osvi;
564     memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
565     osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
566     if(GetVersionEx((OSVERSIONINFO*)&osvi)) {
567         os << "<Windows"
568            << " version='" << osvi.dwMajorVersion << "." << osvi.dwMinorVersion << "'"
569            << " build='" << osvi.dwBuildNumber << "'";
570         if (osvi.wServicePackMajor > 0)
571             os << " servicepack='" << osvi.wServicePackMajor << "." << osvi.wServicePackMinor << "'";
572         switch (osvi.wProductType) {
573             case VER_NT_WORKSTATION:
574                 os << " producttype='Workstation'";
575                 break;
576             case VER_NT_SERVER:
577             case VER_NT_DOMAIN_CONTROLLER:
578                 os << " producttype='Server'";
579                 break;
580         }
581
582         SYSTEM_INFO si;
583         memset(&si, 0, sizeof(SYSTEM_INFO));
584         PGNSI pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo");
585         if(pGNSI)
586             pGNSI(&si);
587         else
588             GetSystemInfo(&si);
589         switch (si.dwProcessorType) {
590             case PROCESSOR_ARCHITECTURE_INTEL:
591                 os << " arch='i386'";
592                 break;
593             case PROCESSOR_ARCHITECTURE_AMD64:
594                 os << " arch='x86_64'";
595                 break;
596             case PROCESSOR_ARCHITECTURE_IA64:
597                 os << " arch='IA64'";
598                 break;
599         }
600         os << " cpucount='" << si.dwNumberOfProcessors << "'";
601
602         MEMORYSTATUSEX ms;
603         memset(&ms, 0, sizeof(MEMORYSTATUSEX));
604         ms.dwLength = sizeof(MEMORYSTATUSEX);
605         if (GlobalMemoryStatusEx(&ms)) {
606             os << " memory='" << (ms.ullTotalPhys / (1024 * 1024)) << "M'";
607         }
608
609         os << "/>";
610     }
611 #endif
612     return os;
613 }