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