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