Reducing header overuse, non-inlining selected methods (CPPOST-35).
[shibboleth/cpp-sp.git] / shibsp / handler / impl / StatusHandler.cpp
1 /*
2  *  Copyright 2001-2009 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 "handler/AbstractHandler.h"
28 #include "handler/RemotedHandler.h"
29 #include "util/CGIParser.h"
30
31 using namespace shibsp;
32 #ifndef SHIBSP_LITE
33 # include "SessionCache.h"
34 # include "metadata/MetadataProviderCriteria.h"
35 # include <saml/version.h>
36 # include <xmltooling/security/Credential.h>
37 # include <xmltooling/security/CredentialCriteria.h>
38 using namespace opensaml::saml2md;
39 using namespace opensaml;
40 using namespace xmlsignature;
41 #endif
42 using namespace xmltooling;
43 using namespace std;
44
45 namespace shibsp {
46
47 #if defined (_MSC_VER)
48     #pragma warning( push )
49     #pragma warning( disable : 4250 )
50 #endif
51
52     class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
53     {
54     public:
55 #ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
56         short
57 #else
58         FilterAction
59 #endif
60         acceptNode(const DOMNode* node) const {
61             return FILTER_REJECT;
62         }
63     };
64
65     static SHIBSP_DLLLOCAL Blocker g_Blocker;
66
67     class SHIBSP_API StatusHandler : public AbstractHandler, public RemotedHandler
68     {
69     public:
70         StatusHandler(const DOMElement* e, const char* appId);
71         virtual ~StatusHandler() {}
72
73         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
74         void receive(DDF& in, ostream& out);
75
76     private:
77         pair<bool,long> processMessage(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
78
79         set<string> m_acl;
80     };
81
82 #if defined (_MSC_VER)
83     #pragma warning( pop )
84 #endif
85
86     Handler* SHIBSP_DLLLOCAL StatusHandlerFactory(const pair<const DOMElement*,const char*>& p)
87     {
88         return new StatusHandler(p.first, p.second);
89     }
90
91 #ifndef XMLTOOLING_NO_XMLSEC
92     vector<XSECCryptoX509*> g_NoCerts;
93 #else
94     vector<string> g_NoCerts;
95 #endif
96
97     static char _x2c(const char *what)
98     {
99         register char digit;
100
101         digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
102         digit *= 16;
103         digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
104         return(digit);
105     }
106
107     class DummyRequest : public HTTPRequest
108     {
109     public:
110         DummyRequest(const char* url) : m_parser(NULL), m_url(url), m_scheme(NULL), m_query(NULL), m_port(0) {
111 #ifdef HAVE_STRCASECMP
112             if (url && !strncasecmp(url,"http://",7)) {
113                 m_scheme="http";
114                 url+=7;
115             }
116             else if (url && !strncasecmp(url,"https://",8)) {
117                 m_scheme="https";
118                 url+=8;
119             }
120             else
121 #else
122             if (url && !strnicmp(url,"http://",7)) {
123                 m_scheme="http";
124                 m_port = 80;
125                 url+=7;
126             }
127             else if (url && !strnicmp(url,"https://",8)) {
128                 m_scheme="https";
129                 m_port = 443;
130                 url+=8;
131             }
132             else
133 #endif
134                 throw invalid_argument("Target parameter was not an absolute URL.");
135
136             m_query = strchr(url,'?');
137             if (m_query)
138                 m_query++;
139
140             const char* slash = strchr(url, '/');
141             const char* colon = strchr(url, ':');
142             if (colon && colon < slash) {
143                 m_hostname.assign(url, colon-url);
144                 string port(colon + 1, slash - colon);
145                 m_port = atoi(port.c_str());
146             }
147             else {
148                 m_hostname.assign(url, slash - url);
149             }
150
151             while (*slash) {
152                 if (*slash == '?') {
153                     m_uri += slash;
154                     break;
155                 }
156                 else if (*slash != '%') {
157                     m_uri += *slash;
158                 }
159                 else {
160                     ++slash;
161                     if (!isxdigit(*slash) || !isxdigit(*(slash+1)))
162                         throw invalid_argument("Bad request, contained unsupported encoded characters.");
163                     m_uri += _x2c(slash);
164                     ++slash;
165                 }
166                 ++slash;
167             }
168         }
169
170         ~DummyRequest() {
171             delete m_parser;
172         }
173
174         const char* getRequestURL() const {
175             return m_url;
176         }
177         const char* getScheme() const {
178             return m_scheme;
179         }
180         const char* getHostname() const {
181             return m_hostname.c_str();
182         }
183         int getPort() const {
184             return m_port;
185         }
186         const char* getRequestURI() const {
187             return m_uri.c_str();
188         }
189         const char* getMethod() const {
190             return "GET";
191         }
192         string getContentType() const {
193             return "";
194         }
195         long getContentLength() const {
196             return 0;
197         }
198         string getRemoteAddr() const {
199             return "";
200         }
201         string getRemoteUser() const {
202             return "";
203         }
204         const char* getRequestBody() const {
205             return NULL;
206         }
207         const char* getQueryString() const {
208             return m_query;
209         }
210         const char* getParameter(const char* name) const
211         {
212             if (!m_parser)
213                 m_parser=new CGIParser(*this);
214
215             pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
216             return (bounds.first==bounds.second) ? NULL : bounds.first->second;
217         }
218         vector<const char*>::size_type getParameters(const char* name, vector<const char*>& values) const
219         {
220             if (!m_parser)
221                 m_parser=new CGIParser(*this);
222
223             pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
224             while (bounds.first!=bounds.second) {
225                 values.push_back(bounds.first->second);
226                 ++bounds.first;
227             }
228             return values.size();
229         }
230         string getHeader(const char* name) const {
231             return "";
232         }
233         virtual const
234 #ifndef XMLTOOLING_NO_XMLSEC
235             std::vector<XSECCryptoX509*>&
236 #else
237             std::vector<std::string>&
238 #endif
239             getClientCertificates() const {
240                 return g_NoCerts;
241         }
242
243     private:
244         mutable CGIParser* m_parser;
245         const char* m_url;
246         const char* m_scheme;
247         const char* m_query;
248         int m_port;
249         string m_hostname,m_uri;
250     };
251 };
252
253 StatusHandler::StatusHandler(const DOMElement* e, const char* appId)
254     : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".StatusHandler"), &g_Blocker)
255 {
256     string address(appId);
257     address += getString("Location").second;
258     setAddress(address.c_str());
259     if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
260         pair<bool,const char*> acl = getString("acl");
261         if (acl.first) {
262             string aclbuf=acl.second;
263             int j = 0;
264             for (unsigned int i=0;  i < aclbuf.length();  i++) {
265                 if (aclbuf.at(i)==' ') {
266                     m_acl.insert(aclbuf.substr(j, i-j));
267                     j = i+1;
268                 }
269             }
270             m_acl.insert(aclbuf.substr(j, aclbuf.length()-j));
271         }
272     }
273 }
274
275 pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
276 {
277     SPConfig& conf = SPConfig::getConfig();
278     if (conf.isEnabled(SPConfig::InProcess)) {
279         if (!m_acl.empty() && m_acl.count(request.getRemoteAddr()) == 0) {
280             m_log.error("status handler request blocked from invalid address (%s)", request.getRemoteAddr().c_str());
281             istringstream msg("Status Handler Blocked");
282             return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN));
283         }
284     }
285
286     const char* target = request.getParameter("target");
287     if (target) {
288         // RequestMap query, so handle it inproc.
289         DummyRequest dummy(target);
290         RequestMapper::Settings settings = request.getApplication().getServiceProvider().getRequestMapper()->getSettings(dummy);
291         map<string,const char*> props;
292         settings.first->getAll(props);
293
294         request.setContentType("text/xml");
295         stringstream msg;
296         msg << "<StatusHandler>";
297             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
298 #ifndef SHIBSP_LITE
299                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
300                 << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
301 #endif
302                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
303             msg << "<RequestSettings";
304             for (map<string,const char*>::const_iterator p = props.begin(); p != props.end(); ++p)
305                 msg << ' ' << p->first << "='" << p->second << "'";
306             msg << '>' << target << "</RequestSettings>";
307             msg << "<Status><OK/></Status>";
308         msg << "</StatusHandler>";
309         return make_pair(true,request.sendResponse(msg));
310     }
311
312     try {
313         if (conf.isEnabled(SPConfig::OutOfProcess)) {
314             // When out of process, we run natively and directly process the message.
315             return processMessage(request.getApplication(), request, request);
316         }
317         else {
318             // When not out of process, we remote all the message processing.
319             DDF out,in = wrap(request);
320             DDFJanitor jin(in), jout(out);
321             out=request.getServiceProvider().getListenerService()->send(in);
322             return unwrap(request, out);
323         }
324     }
325     catch (XMLToolingException& ex) {
326         m_log.error("error while processing request: %s", ex.what());
327         request.setContentType("text/xml");
328         stringstream msg;
329         msg << "<StatusHandler>";
330             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
331 #ifndef SHIBSP_LITE
332                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
333                 << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
334 #endif
335                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
336             msg << "<Status><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></Status>";
337         msg << "</StatusHandler>";
338         return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
339     }
340     catch (exception& ex) {
341         m_log.error("error while processing request: %s", ex.what());
342         request.setContentType("text/xml");
343         stringstream msg;
344         msg << "<StatusHandler>";
345             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
346 #ifndef SHIBSP_LITE
347                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
348                 << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
349 #endif
350                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
351             msg << "<Status><Exception type='std::exception'>" << ex.what() << "</Exception></Status>";
352         msg << "</StatusHandler>";
353         return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
354     }
355 }
356
357 void StatusHandler::receive(DDF& in, ostream& out)
358 {
359     // Find application.
360     const char* aid=in["application_id"].string();
361     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
362     if (!app) {
363         // Something's horribly wrong.
364         m_log.error("couldn't find application (%s) for status request", aid ? aid : "(missing)");
365         throw ConfigurationException("Unable to locate application for status request, deleted?");
366     }
367
368     // Wrap a response shim.
369     DDF ret(NULL);
370     DDFJanitor jout(ret);
371     auto_ptr<HTTPRequest> req(getRequest(in));
372     auto_ptr<HTTPResponse> resp(getResponse(ret));
373
374     // Since we're remoted, the result should either be a throw, a false/0 return,
375     // which we just return as an empty structure, or a response/redirect,
376     // which we capture in the facade and send back.
377     processMessage(*app, *req.get(), *resp.get());
378     out << ret;
379 }
380
381 pair<bool,long> StatusHandler::processMessage(
382     const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
383     ) const
384 {
385 #ifndef SHIBSP_LITE
386     m_log.debug("processing status request");
387
388     stringstream s;
389     s << "<StatusHandler>";
390     const char* status = "<OK/>";
391
392     s << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
393         << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
394         << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
395         << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
396
397     const char* param = NULL;
398     if (param) {
399     }
400     else {
401         // General configuration and status report.
402         try {
403             SessionCache* sc = application.getServiceProvider().getSessionCache(false);
404             if (sc) {
405                 sc->test();
406                 s << "<SessionCache><OK/></SessionCache>";
407             }
408             else {
409                 s << "<SessionCache><None/></SessionCache>";
410             }
411         }
412         catch (XMLToolingException& ex) {
413             s << "<SessionCache><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></SessionCache>";
414             status = "<Partial/>";
415         }
416         catch (exception& ex) {
417             s << "<SessionCache><Exception type='std::exception'>" << ex.what() << "</Exception></SessionCache>";
418             status = "<Partial/>";
419         }
420
421         const PropertySet* relyingParty=NULL;
422         param=httpRequest.getParameter("entityID");
423         if (param) {
424             MetadataProvider* m = application.getMetadataProvider();
425             Locker mlock(m);
426             relyingParty = application.getRelyingParty(m->getEntityDescriptor(MetadataProviderCriteria(application, param)).first);
427         }
428         else {
429             relyingParty = &application;
430         }
431
432         s << "<Application id='" << application.getId() << "' entityID='" << relyingParty->getString("entityID").second << "'/>";
433
434         s << "<Handlers>";
435         vector<const Handler*> handlers;
436         application.getHandlers(handlers);
437         for (vector<const Handler*>::const_iterator h = handlers.begin(); h != handlers.end(); ++h) {
438             s << "<Handler type='" << (*h)->getType() << "' Location='" << (*h)->getString("Location").second << "'";
439             if ((*h)->getString("Binding").first)
440                 s << " Binding='" << (*h)->getString("Binding").second << "'";
441             s << "/>";
442         }
443         s << "</Handlers>";
444
445         CredentialResolver* credResolver=application.getCredentialResolver();
446         if (credResolver) {
447             Locker credLocker(credResolver);
448             CredentialCriteria cc;
449             cc.setUsage(Credential::SIGNING_CREDENTIAL);
450             pair<bool,const char*> keyName = relyingParty->getString("keyName");
451             if (keyName.first)
452                 cc.getKeyNames().insert(keyName.second);
453             vector<const Credential*> creds;
454             credResolver->resolve(creds,&cc);
455             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
456                 KeyInfo* kinfo = (*c)->getKeyInfo();
457                 if (kinfo) {
458                     auto_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
459                     kd->setUse(KeyDescriptor::KEYTYPE_SIGNING);
460                     kd->setKeyInfo(kinfo);
461                     s << *(kd.get());
462                 }
463             }
464
465             cc.setUsage(Credential::ENCRYPTION_CREDENTIAL);
466             creds.clear();
467             cc.getKeyNames().clear();
468             credResolver->resolve(creds,&cc);
469             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
470                 KeyInfo* kinfo = (*c)->getKeyInfo();
471                 if (kinfo) {
472                     auto_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
473                     kd->setUse(KeyDescriptor::KEYTYPE_ENCRYPTION);
474                     kd->setKeyInfo(kinfo);
475                     s << *(kd.get());
476                 }
477             }
478         }
479     }
480
481     s << "<Status>" << status << "</Status></StatusHandler>";
482
483     httpResponse.setContentType("text/xml");
484     return make_pair(true, httpResponse.sendResponse(s));
485 #else
486     return make_pair(false,0L);
487 #endif
488 }