SSPCPP-616 - clean up concatenated string literals
[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/RemotedHandler.h"
33 #include "handler/SecuredHandler.h"
34 #include "util/CGIParser.h"
35
36 #include <boost/iterator/indirect_iterator.hpp>
37 #include <boost/scoped_ptr.hpp>
38 #include <xmltooling/version.h>
39 #include <xmltooling/util/DateTime.h>
40
41 #ifdef HAVE_SYS_UTSNAME_H
42 # include <sys/utsname.h>
43 #endif
44
45 using namespace shibsp;
46 #ifndef SHIBSP_LITE
47 # include "SessionCache.h"
48 # include "metadata/MetadataProviderCriteria.h"
49 # include <saml/version.h>
50 # include <saml/saml2/metadata/Metadata.h>
51 # include <xmltooling/security/Credential.h>
52 # include <xmltooling/security/CredentialCriteria.h>
53 using namespace opensaml::saml2md;
54 using namespace opensaml;
55 using namespace xmlsignature;
56 #endif
57 using namespace xmltooling;
58 using namespace boost;
59 using namespace std;
60
61 namespace shibsp {
62
63 #if defined (_MSC_VER)
64     #pragma warning( push )
65     #pragma warning( disable : 4250 )
66 #endif
67
68     class SHIBSP_API StatusHandler : public SecuredHandler, public RemotedHandler
69     {
70     public:
71         StatusHandler(const DOMElement* e, const char* appId);
72         virtual ~StatusHandler() {}
73
74         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
75         void receive(DDF& in, ostream& out);
76
77     private:
78         pair<bool,long> processMessage(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
79         ostream& systemInfo(ostream& os) const;
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(nullptr), m_url(url), m_scheme(nullptr), m_query(nullptr), m_port(0) {
111 #ifdef HAVE_STRCASECMP
112             if (url && !strncasecmp(url,"http://", 7)) {
113                 m_scheme = "http";
114                 m_port = 80;
115                 url += 7;
116             }
117             else if (url && !strncasecmp(url,"https://", 8)) {
118                 m_scheme = "https";
119                 m_port = 443;
120                 url += 8;
121             }
122             else
123 #else
124             if (url && !strnicmp(url,"http://", 7)) {
125                 m_scheme = "http";
126                 m_port = 80;
127                 url += 7;
128             }
129             else if (url && !strnicmp(url,"https://", 8)) {
130                 m_scheme="https";
131                 m_port = 443;
132                 url += 8;
133             }
134             else
135 #endif
136                 throw invalid_argument("Target parameter was not an absolute URL.");
137
138             m_query = strchr(url,'?');
139             if (m_query)
140                 m_query++;
141
142             const char* slash = strchr(url, '/');
143             const char* colon = strchr(url, ':');
144             if (colon && colon < slash) {
145                 m_hostname.assign(url, colon-url);
146                 string port(colon + 1, slash - colon);
147                 m_port = atoi(port.c_str());
148             }
149             else {
150                 m_hostname.assign(url, slash - url);
151             }
152
153             while (*slash) {
154                 if (*slash == '?') {
155                     m_uri += slash;
156                     break;
157                 }
158                 else if (*slash != '%') {
159                     m_uri += *slash;
160                 }
161                 else {
162                     ++slash;
163                     if (!isxdigit(*slash) || !isxdigit(*(slash+1)))
164                         throw invalid_argument("Bad request, contained unsupported encoded characters.");
165                     m_uri += _x2c(slash);
166                     ++slash;
167                 }
168                 ++slash;
169             }
170         }
171
172         virtual ~DummyRequest() {}
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 nullptr;
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.reset(new CGIParser(*this));
214
215             pair<CGIParser::walker,CGIParser::walker> bounds = m_parser->getParameters(name);
216             return (bounds.first == bounds.second) ? nullptr : 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.reset(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 scoped_ptr<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     : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT ".StatusHandler"))
255 {
256     string address(appId);
257     address += getString("Location").second;
258     setAddress(address.c_str());
259 }
260
261 pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
262 {
263     // Check ACL in base class.
264     pair<bool,long> ret = SecuredHandler::run(request, isHandler);
265     if (ret.first)
266         return ret;
267
268     const char* target = request.getParameter("target");
269     if (target) {
270         // RequestMap query, so handle it inproc.
271         DummyRequest dummy(target);
272         RequestMapper::Settings settings = request.getApplication().getServiceProvider().getRequestMapper()->getSettings(dummy);
273         map<string,const char*> props;
274         settings.first->getAll(props);
275
276         DateTime now(time(nullptr));
277         now.parseDateTime();
278         auto_ptr_char timestamp(now.getFormattedString());
279         request.setContentType("text/xml");
280         stringstream msg;
281         msg << "<StatusHandler time='" << timestamp.get() << "'>";
282             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
283                 << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
284 #ifndef SHIBSP_LITE
285                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
286                 << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
287 #endif
288                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
289             systemInfo(msg) << "<RequestSettings";
290             for (map<string,const char*>::const_iterator p = props.begin(); p != props.end(); ++p)
291                 msg << ' ' << p->first << "='" << p->second << "'";
292             msg << '>' << target << "</RequestSettings>";
293             msg << "<Status><OK/></Status>";
294         msg << "</StatusHandler>";
295         return make_pair(true, request.sendResponse(msg));
296     }
297
298     try {
299         if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
300             // When out of process, we run natively and directly process the message.
301             return processMessage(request.getApplication(), request, request);
302         }
303         else {
304             // When not out of process, we remote all the message processing.
305             DDF out,in = wrap(request);
306             DDFJanitor jin(in), jout(out);
307             out=request.getServiceProvider().getListenerService()->send(in);
308             return unwrap(request, out);
309         }
310     }
311     catch (XMLToolingException& ex) {
312         m_log.error("error while processing request: %s", ex.what());
313         DateTime now(time(nullptr));
314         now.parseDateTime();
315         auto_ptr_char timestamp(now.getFormattedString());
316         request.setContentType("text/xml");
317         stringstream msg;
318         msg << "<StatusHandler time='" << timestamp.get() << "'>";
319             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
320                 << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
321 #ifndef SHIBSP_LITE
322                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
323                 << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
324 #endif
325                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
326             systemInfo(msg) << "<Status><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></Status>";
327         msg << "</StatusHandler>";
328         return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
329     }
330     catch (std::exception& ex) {
331         m_log.error("error while processing request: %s", ex.what());
332         DateTime now(time(nullptr));
333         now.parseDateTime();
334         auto_ptr_char timestamp(now.getFormattedString());
335         request.setContentType("text/xml");
336         stringstream msg;
337         msg << "<StatusHandler time='" << timestamp.get() << "'>";
338             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
339                 << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
340 #ifndef SHIBSP_LITE
341                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
342                 << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
343 #endif
344                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
345             systemInfo(msg) << "<Status><Exception type='std::exception'>" << ex.what() << "</Exception></Status>";
346         msg << "</StatusHandler>";
347         return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
348     }
349 }
350
351 void StatusHandler::receive(DDF& in, ostream& out)
352 {
353     // Find application.
354     const char* aid = in["application_id"].string();
355     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
356     if (!app) {
357         // Something's horribly wrong.
358         m_log.error("couldn't find application (%s) for status request", aid ? aid : "(missing)");
359         throw ConfigurationException("Unable to locate application for status request, deleted?");
360     }
361
362     // Wrap a response shim.
363     DDF ret(nullptr);
364     DDFJanitor jout(ret);
365     scoped_ptr<HTTPRequest> req(getRequest(in));
366     scoped_ptr<HTTPResponse> resp(getResponse(ret));
367
368     // Since we're remoted, the result should either be a throw, a false/0 return,
369     // which we just return as an empty structure, or a response/redirect,
370     // which we capture in the facade and send back.
371     processMessage(*app, *req, *resp);
372     out << ret;
373 }
374
375 pair<bool,long> StatusHandler::processMessage(
376     const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
377     ) const
378 {
379 #ifndef SHIBSP_LITE
380     m_log.debug("processing status request");
381
382     DateTime now(time(nullptr));
383     now.parseDateTime();
384     auto_ptr_char timestamp(now.getFormattedString());
385
386     stringstream s;
387     s << "<StatusHandler time='" << timestamp.get() << "'>";
388     const char* status = "<OK/>";
389
390     s << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
391         << "' XML-Tooling-C='" << gXMLToolingDotVersionStr
392         << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
393         << "' OpenSAML-C='" << gOpenSAMLDotVersionStr
394         << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
395
396     systemInfo(s);
397
398     const char* param = nullptr;
399     if (param) {
400     }
401     else {
402         // General configuration and status report.
403         try {
404             SessionCache* sc = application.getServiceProvider().getSessionCache(false);
405             if (sc) {
406                 sc->test();
407                 s << "<SessionCache><OK/></SessionCache>";
408             }
409             else {
410                 s << "<SessionCache><None/></SessionCache>";
411             }
412         }
413         catch (XMLToolingException& ex) {
414             s << "<SessionCache><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></SessionCache>";
415             status = "<Partial/>";
416         }
417         catch (std::exception& ex) {
418             s << "<SessionCache><Exception type='std::exception'>" << ex.what() << "</Exception></SessionCache>";
419             status = "<Partial/>";
420         }
421
422         MetadataProvider* m = application.getMetadataProvider(false);
423         Locker mlock(m);
424
425         const PropertySet* relyingParty = nullptr;
426         param=httpRequest.getParameter("entityID");
427         if (m && param)
428             relyingParty = application.getRelyingParty(m->getEntityDescriptor(MetadataProviderCriteria(application, param)).first);
429         else
430             relyingParty = &application;
431
432         s << "<Application id='" << application.getId() << "' entityID='" << relyingParty->getString("entityID").second << "'/>";
433
434         if (m)
435             m->outputStatus(s);
436
437         s << "<Handlers>";
438         vector<const Handler*> handlers;
439         application.getHandlers(handlers);
440         for (indirect_iterator<vector<const Handler*>::const_iterator> h = make_indirect_iterator(handlers.begin());
441                 h != make_indirect_iterator(handlers.end()); ++h) {
442             s << "<Handler type='" << h->getType() << "' Location='" << h->getString("Location").second << "'";
443             if (h->getString("Binding").first)
444                 s << " Binding='" << h->getString("Binding").second << "'";
445             s << "/>";
446         }
447         s << "</Handlers>";
448
449         CredentialResolver* credResolver = application.getCredentialResolver();
450         if (credResolver) {
451             Locker credLocker(credResolver);
452             CredentialCriteria cc;
453             cc.setUsage(Credential::SIGNING_CREDENTIAL);
454             pair<bool,const char*> keyName = relyingParty->getString("keyName");
455             if (keyName.first)
456                 cc.getKeyNames().insert(keyName.second);
457             vector<const Credential*> creds;
458             credResolver->resolve(creds, &cc);
459             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
460                 KeyInfo* kinfo = (*c)->getKeyInfo();
461                 if (kinfo) {
462                     scoped_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
463                     kd->setUse(KeyDescriptor::KEYTYPE_SIGNING);
464                     kd->setKeyInfo(kinfo);
465                     s << *(kd.get());
466                 }
467             }
468
469             cc.setUsage(Credential::ENCRYPTION_CREDENTIAL);
470             creds.clear();
471             cc.getKeyNames().clear();
472             credResolver->resolve(creds, &cc);
473             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
474                 KeyInfo* kinfo = (*c)->getKeyInfo();
475                 if (kinfo) {
476                     scoped_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
477                     kd->setUse(KeyDescriptor::KEYTYPE_ENCRYPTION);
478                     kd->setKeyInfo(kinfo);
479                     s << *(kd.get());
480                 }
481             }
482         }
483     }
484
485     s << "<Status>" << status << "</Status></StatusHandler>";
486
487     httpResponse.setContentType("text/xml");
488     return make_pair(true, httpResponse.sendResponse(s));
489 #else
490     return make_pair(false, 0L);
491 #endif
492 }
493
494 #ifdef WIN32
495 typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
496 #endif
497
498 ostream& StatusHandler::systemInfo(ostream& os) const
499 {
500 #if defined(HAVE_SYS_UTSNAME_H)
501     struct utsname sysinfo;
502     if (uname(&sysinfo) == 0) {
503         os << "<NonWindows";
504         if (*sysinfo.sysname)
505             os << " sysname='" << sysinfo.sysname << "'";
506         if (*sysinfo.nodename)
507             os << " nodename='" << sysinfo.nodename << "'";
508         if (*sysinfo.release)
509             os << " release='" << sysinfo.release << "'";
510         if (*sysinfo.version)
511             os << " version='" << sysinfo.version << "'";
512         if (*sysinfo.machine)
513             os << " machine='" << sysinfo.machine << "'";
514         os << "/>";
515     }
516 #elif defined(WIN32)
517     OSVERSIONINFOEX osvi;
518     memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
519     osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
520     if(GetVersionEx((OSVERSIONINFO*)&osvi)) {
521         os << "<Windows"
522            << " version='" << osvi.dwMajorVersion << "." << osvi.dwMinorVersion << "'"
523            << " build='" << osvi.dwBuildNumber << "'";
524         if (osvi.wServicePackMajor > 0)
525             os << " servicepack='" << osvi.wServicePackMajor << "." << osvi.wServicePackMinor << "'";
526         switch (osvi.wProductType) {
527             case VER_NT_WORKSTATION:
528                 os << " producttype='Workstation'";
529                 break;
530             case VER_NT_SERVER:
531             case VER_NT_DOMAIN_CONTROLLER:
532                 os << " producttype='Server'";
533                 break;
534         }
535
536         SYSTEM_INFO si;
537         memset(&si, 0, sizeof(SYSTEM_INFO));
538         PGNSI pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo");
539         if(pGNSI)
540             pGNSI(&si);
541         else
542             GetSystemInfo(&si);
543         switch (si.dwProcessorType) {
544             case PROCESSOR_ARCHITECTURE_INTEL:
545                 os << " arch='i386'";
546                 break;
547             case PROCESSOR_ARCHITECTURE_AMD64:
548                 os << " arch='x86_64'";
549                 break;
550             case PROCESSOR_ARCHITECTURE_IA64:
551                 os << " arch='IA64'";
552                 break;
553         }
554         os << " cpucount='" << si.dwNumberOfProcessors << "'";
555
556         MEMORYSTATUSEX ms;
557         memset(&ms, 0, sizeof(MEMORYSTATUSEX));
558         ms.dwLength = sizeof(MEMORYSTATUSEX);
559         if (GlobalMemoryStatusEx(&ms)) {
560             os << " memory='" << (ms.ullTotalPhys / (1024 * 1024)) << "M'";
561         }
562
563         os << "/>";
564     }
565 #endif
566     return os;
567 }