Tagging 2.4RC1 release.
[shibboleth/sp.git] / shibsp / handler / impl / RemotedHandler.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  * RemotedHandler.cpp
19  * 
20  * Base class for handlers that need SP request/response layer to be remoted. 
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "Application.h"
26 #include "GSSRequest.h"
27 #include "ServiceProvider.h"
28 #include "SPRequest.h"
29 #include "handler/RemotedHandler.h"
30
31 #include <algorithm>
32 #include <xmltooling/unicode.h>
33 #include <xercesc/util/Base64.hpp>
34
35 #ifndef SHIBSP_LITE
36 # include "util/CGIParser.h"
37 # include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
38 # include <xsec/enc/XSECCryptoException.hpp>
39 # include <xsec/framework/XSECException.hpp>
40 # include <xsec/framework/XSECProvider.hpp>
41 #endif
42
43 using namespace shibsp;
44 using namespace opensaml;
45 using namespace xmltooling;
46 using namespace xercesc;
47 using namespace std;
48
49 #ifndef SHIBSP_LITE
50 namespace shibsp {
51     class SHIBSP_DLLLOCAL RemotedRequest : 
52 #ifdef HAVE_GSSAPI
53         public GSSRequest,
54 #endif
55         public HTTPRequest
56     {
57         DDF& m_input;
58         mutable CGIParser* m_parser;
59         mutable vector<XSECCryptoX509*> m_certs;
60 #ifdef HAVE_GSSAPI
61         mutable gss_ctx_id_t m_gss;
62 #endif
63     public:
64         RemotedRequest(DDF& input) : m_input(input), m_parser(nullptr)
65 #ifdef HAVE_GSSAPI
66             , m_gss(GSS_C_NO_CONTEXT)
67 #endif
68         {
69         }
70
71         virtual ~RemotedRequest() {
72             for_each(m_certs.begin(), m_certs.end(), xmltooling::cleanup<XSECCryptoX509>());
73             delete m_parser;
74 #ifdef HAVE_GSSAPI
75             if (m_gss != GSS_C_NO_CONTEXT) {
76                 OM_uint32 minor;
77                 gss_delete_sec_context(&minor, &m_gss, GSS_C_NO_BUFFER);
78             }
79 #endif
80         }
81
82         // GenericRequest
83         const char* getScheme() const {
84             return m_input["scheme"].string();
85         }
86         bool isSecure() const {
87             return HTTPRequest::isSecure();
88         }
89         const char* getHostname() const {
90             return m_input["hostname"].string();
91         }
92         int getPort() const {
93             return m_input["port"].integer();
94         }
95         std::string getContentType() const {
96             DDF s = m_input["content_type"];
97             return s.string() ? s.string() : "";
98         }
99         long getContentLength() const {
100             return m_input["content_length"].integer();
101         }
102         const char* getRequestBody() const {
103             return m_input["body"].string();
104         }
105
106         const char* getParameter(const char* name) const;
107         std::vector<const char*>::size_type getParameters(const char* name, std::vector<const char*>& values) const;
108         
109         std::string getRemoteUser() const {
110             DDF s = m_input["remote_user"];
111             return s.string() ? s.string() : "";
112         }
113         std::string getRemoteAddr() const {
114             DDF s = m_input["client_addr"];
115             return s.string() ? s.string() : "";
116         }
117
118         const std::vector<XSECCryptoX509*>& getClientCertificates() const;
119         
120 #ifdef HAVE_GSSAPI
121         // GSSRequest
122         gss_ctx_id_t getGSSContext() const;
123 #endif
124
125         // HTTPRequest
126         const char* getMethod() const {
127             return m_input["method"].string();
128         }
129         const char* getRequestURI() const {
130             return m_input["uri"].string();
131         }
132         const char* getRequestURL() const {
133             return m_input["url"].string();
134         }
135         const char* getQueryString() const {
136             return m_input["query"].string();
137         }
138         std::string getHeader(const char* name) const {
139             DDF s = m_input["headers"][name];
140             return s.string() ? s.string() : "";
141         }
142     };
143
144     class SHIBSP_DLLLOCAL RemotedResponse : public virtual HTTPResponse 
145     {
146         DDF& m_output;
147     public:
148         RemotedResponse(DDF& output) : m_output(output) {}
149         virtual ~RemotedResponse() {}
150        
151         // GenericResponse
152         long sendResponse(std::istream& inputStream, long status);
153         
154         // HTTPResponse
155         void setResponseHeader(const char* name, const char* value);
156         long sendRedirect(const char* url);
157     };
158 }
159
160 const char* RemotedRequest::getParameter(const char* name) const
161 {
162     if (!m_parser)
163         m_parser=new CGIParser(*this);
164     
165     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
166     return (bounds.first==bounds.second) ? nullptr : bounds.first->second;
167 }
168
169 std::vector<const char*>::size_type RemotedRequest::getParameters(const char* name, std::vector<const char*>& values) const
170 {
171     if (!m_parser)
172         m_parser=new CGIParser(*this);
173
174     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
175     while (bounds.first!=bounds.second) {
176         values.push_back(bounds.first->second);
177         ++bounds.first;
178     }
179     return values.size();
180 }
181
182 const std::vector<XSECCryptoX509*>& RemotedRequest::getClientCertificates() const
183 {
184     if (m_certs.empty()) {
185         DDF certs = m_input["certificates"];
186         DDF cert = certs.first();
187         while (cert.string()) {
188             try {
189                 auto_ptr<XSECCryptoX509> x509(XSECPlatformUtils::g_cryptoProvider->X509());
190                 if (strstr(cert.string(), "BEGIN"))
191                     x509->loadX509PEM(cert.string(), cert.strlen());
192                 else
193                     x509->loadX509Base64Bin(cert.string(), cert.strlen());
194                 m_certs.push_back(x509.release());
195             }
196             catch(XSECException& e) {
197                 auto_ptr_char temp(e.getMsg());
198                 Category::getInstance(SHIBSP_LOGCAT".SPRequest").error("XML-Security exception loading client certificate: %s", temp.get());
199             }
200             catch(XSECCryptoException& e) {
201                 Category::getInstance(SHIBSP_LOGCAT".SPRequest").error("XML-Security exception loading client certificate: %s", e.getMsg());
202             }
203             cert = certs.next();
204         }
205     }
206     return m_certs;
207 }
208
209 #ifdef HAVE_GSSAPI
210 gss_ctx_id_t RemotedRequest::getGSSContext() const
211 {
212     if (m_gss == GSS_C_NO_CONTEXT) {
213         const char* encoded = m_input["gss_context"].string();
214         if (encoded) {
215             xsecsize_t x;
216             XMLByte* decoded=Base64::decode(reinterpret_cast<const XMLByte*>(encoded), &x);
217             if (decoded) {
218                 gss_buffer_desc importbuf;
219                 importbuf.length = x;
220                 importbuf.value = decoded;
221                 OM_uint32 minor;
222                 OM_uint32 major = gss_import_sec_context(&minor, &importbuf, &m_gss);
223                 if (major != GSS_S_COMPLETE)
224                     m_gss = GSS_C_NO_CONTEXT;
225 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
226                 XMLString::release(&decoded);
227 #else
228                 XMLString::release((char**)&decoded);
229 #endif
230             }
231         }
232     }
233     return m_gss;
234 }
235 #endif
236
237 long RemotedResponse::sendResponse(std::istream& in, long status)
238 {
239     string msg;
240     char buf[1024];
241     while (in) {
242         in.read(buf,1024);
243         msg.append(buf,in.gcount());
244     }
245     if (!m_output.isstruct())
246         m_output.structure();
247     m_output.addmember("response.data").string(msg.c_str());
248     m_output.addmember("response.status").integer(status);
249     return status;
250 }
251
252 void RemotedResponse::setResponseHeader(const char* name, const char* value)
253 {
254     if (!m_output.isstruct())
255         m_output.structure();
256     DDF hdrs = m_output["headers"];
257     if (hdrs.isnull())
258         hdrs = m_output.addmember("headers").list();
259     DDF h = DDF(name).string(value);
260     hdrs.add(h);
261 }
262
263 long RemotedResponse::sendRedirect(const char* url)
264 {
265     if (!m_output.isstruct())
266         m_output.structure();
267     m_output.addmember("redirect").unsafe_string(url);
268     return HTTPResponse::XMLTOOLING_HTTP_STATUS_MOVED;
269 }
270
271 #endif
272
273 void RemotedHandler::setAddress(const char* address)
274 {
275     if (!m_address.empty())
276         throw ConfigurationException("Cannot register a remoting address twice for the same Handler.");
277     m_address = address;
278     SPConfig& conf = SPConfig::getConfig();
279     if (!conf.isEnabled(SPConfig::InProcess)) {
280         ListenerService* listener = conf.getServiceProvider()->getListenerService(false);
281         if (listener)
282             listener->regListener(m_address.c_str(),this);
283         else
284             Category::getInstance(SHIBSP_LOGCAT".Handler").info("no ListenerService available, handler remoting disabled");
285     }
286 }
287
288 set<string> RemotedHandler::m_remotedHeaders;
289
290 RemotedHandler::RemotedHandler()
291 {
292 }
293
294 RemotedHandler::~RemotedHandler()
295 {
296     SPConfig& conf = SPConfig::getConfig();
297     ListenerService* listener=conf.getServiceProvider()->getListenerService(false);
298     if (listener && conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess))
299         listener->unregListener(m_address.c_str(),this);
300 }
301
302 void RemotedHandler::addRemotedHeader(const char* header)
303 {
304     m_remotedHeaders.insert(header);
305 }
306
307 DDF RemotedHandler::wrap(const SPRequest& request, const vector<string>* headers, bool certs) const
308 {
309     DDF in = DDF(m_address.c_str()).structure();
310     in.addmember("application_id").string(request.getApplication().getId());
311     in.addmember("scheme").string(request.getScheme());
312     in.addmember("hostname").unsafe_string(request.getHostname());
313     in.addmember("port").integer(request.getPort());
314     in.addmember("content_type").string(request.getContentType().c_str());
315     in.addmember("content_length").integer(request.getContentLength());
316     in.addmember("body").string(request.getRequestBody());
317     in.addmember("remote_user").string(request.getRemoteUser().c_str());
318     in.addmember("client_addr").string(request.getRemoteAddr().c_str());
319     in.addmember("method").string(request.getMethod());
320     in.addmember("uri").unsafe_string(request.getRequestURI());
321     in.addmember("url").unsafe_string(request.getRequestURL());
322     in.addmember("query").string(request.getQueryString());
323
324     if (headers || !m_remotedHeaders.empty()) {
325         string hdr;
326         DDF hin = in.addmember("headers").structure();
327         if (headers) {
328             for (vector<string>::const_iterator h = headers->begin(); h!=headers->end(); ++h) {
329                 hdr = request.getHeader(h->c_str());
330                 if (!hdr.empty())
331                     hin.addmember(h->c_str()).unsafe_string(hdr.c_str());
332             }
333         }
334         for (set<string>::const_iterator hh = m_remotedHeaders.begin(); hh != m_remotedHeaders.end(); ++hh) {
335             hdr = request.getHeader(hh->c_str());
336             if (!hdr.empty())
337                 hin.addmember(hh->c_str()).unsafe_string(hdr.c_str());
338         }
339     }
340
341     if (certs) {
342 #ifndef SHIBSP_LITE
343         const vector<XSECCryptoX509*>& xvec = request.getClientCertificates();
344         if (!xvec.empty()) {
345             DDF clist = in.addmember("certificates").list();
346             for (vector<XSECCryptoX509*>::const_iterator x = xvec.begin(); x!=xvec.end(); ++x) {
347                 DDF x509 = DDF(nullptr).string((*x)->getDEREncodingSB().rawCharBuffer());
348                 clist.add(x509);
349             }
350         }
351 #else
352         const vector<string>& xvec = request.getClientCertificates();
353         if (!xvec.empty()) {
354             DDF clist = in.addmember("certificates").list();
355             for (vector<string>::const_iterator x = xvec.begin(); x!=xvec.end(); ++x) {
356                 DDF x509 = DDF(nullptr).string(x->c_str());
357                 clist.add(x509);
358             }
359         }
360 #endif
361     }
362
363 #ifdef HAVE_GSSAPI
364     const GSSRequest* gss = dynamic_cast<const GSSRequest*>(&request);
365     if (gss) {
366         gss_ctx_id_t ctx = gss->getGSSContext();
367         if (ctx != GSS_C_NO_CONTEXT) {
368             OM_uint32 minor;
369             gss_buffer_desc contextbuf;
370             contextbuf.length = 0;
371             contextbuf.value = nullptr;
372             OM_uint32 major = gss_export_sec_context(&minor, &ctx, &contextbuf);
373             if (major == GSS_S_COMPLETE) {
374                 xsecsize_t len=0;
375                 XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf.value), contextbuf.length, &len);
376                 if (out) {
377                     string ctx;
378                     ctx.append(reinterpret_cast<char*>(out), len);
379 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
380                     XMLString::release(&out);
381 #else
382                     XMLString::release((char**)&out);
383 #endif
384                     in.addmember("gss_context").string(ctx.c_str());
385                 }
386                 else {
387                     request.log(SPRequest::SPError, "error while base64-encoding GSS context");
388                 }
389             }
390             else {
391                 request.log(SPRequest::SPError, "error while exporting GSS context");
392             }
393         }
394     }
395 #endif
396
397     return in;
398 }
399
400 pair<bool,long> RemotedHandler::unwrap(SPRequest& request, DDF& out) const
401 {
402     DDF h = out["headers"];
403     DDF hdr = h.first();
404     while (hdr.isstring()) {
405 #ifdef HAVE_STRCASECMP
406         if (!strcasecmp(hdr.name(), "Content-Type"))
407 #else
408         if (!stricmp(hdr.name(), "Content-Type"))
409 #endif
410             request.setContentType(hdr.string());
411         else
412             request.setResponseHeader(hdr.name(), hdr.string());
413         hdr = h.next();
414     }
415     h = out["redirect"];
416     if (h.isstring())
417         return make_pair(true, request.sendRedirect(h.string()));
418     h = out["response"];
419     if (h.isstruct()) {
420         istringstream s(h["data"].string());
421         return make_pair(true, request.sendResponse(s, h["status"].integer()));
422     }
423     return make_pair(false,0L);
424 }
425
426 HTTPRequest* RemotedHandler::getRequest(DDF& in) const
427 {
428 #ifndef SHIBSP_LITE
429     return new RemotedRequest(in);
430 #else
431     throw ConfigurationException("Cannot process message using lite version of shibsp library.");
432 #endif
433 }
434
435 HTTPResponse* RemotedHandler::getResponse(DDF& out) const
436 {
437 #ifndef SHIBSP_LITE
438     return new RemotedResponse(out);
439 #else
440     throw ConfigurationException("Cannot process message using lite version of shibsp library.");
441 #endif
442 }