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