Update copyright.
[shibboleth/sp.git] / shibsp / ServiceProvider.cpp
1 /*
2  *  Copyright 2001-2007 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  * ServiceProvider.cpp
19  * 
20  * Interface to a Shibboleth ServiceProvider instance.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "AccessControl.h"
26 #include "Application.h"
27 #include "Handler.h"
28 #include "ServiceProvider.h"
29 #include "SessionCache.h"
30 #include "SPRequest.h"
31 #include "util/TemplateParameters.h"
32
33 #include <fstream>
34 #include <sstream>
35 #include <saml/saml2/metadata/Metadata.h>
36 #include <saml/util/SAMLConstants.h>
37 #include <xmltooling/XMLToolingConfig.h>
38 #include <xmltooling/util/NDC.h>\r
39 #include <xmltooling/util/XMLHelper.h>\r
40
41 using namespace shibsp;
42 using namespace opensaml::saml2md;
43 using namespace opensaml;
44 using namespace xmltooling;
45 using namespace std;
46
47 namespace shibsp {
48     //SHIBSP_DLLLOCAL PluginManager<ServiceProvider,const DOMElement*>::Factory XMLServiceProviderFactory;
49
50     long SHIBSP_DLLLOCAL sendError(
51         SPRequest& request, const Application* app, const char* page, TemplateParameters& tp, const XMLToolingException* ex=NULL
52         )
53     {
54         request.setContentType("text/html");
55         request.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
56         request.setResponseHeader("Cache-Control","private,no-store,no-cache");
57     
58         const PropertySet* props=app ? app->getPropertySet("Errors") : NULL;
59         if (props) {
60             pair<bool,const char*> p=props->getString(page);
61             if (p.first) {
62                 ifstream infile(p.second);
63                 if (infile) {
64                     tp.setPropertySet(props);
65                     stringstream str;
66                     XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp, ex);
67                     return request.sendResponse(str);
68                 }
69             }
70             else if (!strcmp(page,"access")) {
71                 istringstream msg("Access Denied");
72                 return static_cast<opensaml::GenericResponse&>(request).sendResponse(msg, HTTPResponse::SAML_HTTP_STATUS_FORBIDDEN);
73             }
74         }
75     
76         string errstr = string("sendError could not process error template (") + page + ")";
77         request.log(SPRequest::SPError, errstr);
78         istringstream msg("Internal Server Error. Please contact the site administrator.");
79         return request.sendError(msg);
80     }
81     
82     void SHIBSP_DLLLOCAL clearHeaders(SPRequest& request) {
83         // Clear invariant stuff.
84         request.clearHeader("Shib-Origin-Site");
85         request.clearHeader("Shib-Identity-Provider");
86         request.clearHeader("Shib-Authentication-Method");
87         request.clearHeader("Shib-NameIdentifier-Format");
88         request.clearHeader("Shib-Attributes");
89         request.clearHeader("Shib-Application-ID");
90     
91         // Clear out the list of mapped attributes
92         /* TODO: port
93         Iterator<IAAP*> provs=dynamic_cast<const IApplication&>(getApplication()).getAAPProviders();
94         while (provs.hasNext()) {
95             IAAP* aap=provs.next();
96             xmltooling::Locker locker(aap);
97             Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
98             while (rules.hasNext()) {
99                 const char* header=rules.next()->getHeader();
100                 if (header)
101                     request.clearHeader(header);
102             }
103         }
104         */
105     }
106
107     static const XMLCh SessionInitiator[] =     UNICODE_LITERAL_16(S,e,s,s,i,o,n,I,n,i,t,i,a,t,o,r);\r
108 };
109
110 void SHIBSP_API shibsp::registerServiceProviders()
111 {
112     //SPConfig::getConfig().ServiceProviderManager.registerFactory(XML_SERVICE_PROVIDER, XMLServiceProviderFactory);
113 }
114
115 pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handler) const
116 {
117 #ifdef _DEBUG\r
118     xmltooling::NDC ndc("doAuthentication");\r
119 #endif\r
120 \r
121     const Application* app=NULL;\r
122     const char* procState = "Request Processing Error";\r
123     string targetURL = request.getRequestURL();\r
124 \r
125     try {\r
126         RequestMapper::Settings settings = request.getRequestSettings();\r
127         app = &(request.getApplication());\r
128 \r
129         // If not SSL, check to see if we should block or redirect it.\r
130         if (!request.isSecure()) {\r
131             pair<bool,const char*> redirectToSSL = settings.first->getString("redirectToSSL");\r
132             if (redirectToSSL.first) {\r
133 #ifdef HAVE_STRCASECMP\r
134                 if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) {\r
135 #else\r
136                 if (!stricmp("GET",request.getMethod()) || !stricmp("HEAD",request.getMethod())) {\r
137 #endif\r
138                     // Compute the new target URL\r
139                     string redirectURL = string("https://") + request.getHostname();\r
140                     if (strcmp(redirectToSSL.second,"443")) {\r
141                         redirectURL = redirectURL + ':' + redirectToSSL.second;\r
142                     }\r
143                     redirectURL += request.getRequestURI();\r
144                     return make_pair(true, request.sendRedirect(redirectURL.c_str()));\r
145                 }\r
146                 else {\r
147                     TemplateParameters tp;\r
148                     tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
149                     return make_pair(true,sendError(request, app, "ssl", tp));\r
150                 }\r
151             }\r
152         }\r
153         \r
154         const char* handlerURL=request.getHandlerURL(targetURL.c_str());\r
155         if (!handlerURL)\r
156             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");\r
157 \r
158         // If the request URL contains the handler base URL for this application, either dispatch\r
159         // directly (mainly Apache 2.0) or just pass back control.\r
160         if (strstr(targetURL.c_str(),handlerURL)) {\r
161             if (handler)\r
162                 return doHandler(request);\r
163             else\r
164                 return make_pair(true, request.returnOK());\r
165         }\r
166 \r
167         // Three settings dictate how to proceed.\r
168         pair<bool,const char*> authType = settings.first->getString("authType");\r
169         pair<bool,bool> requireSession = settings.first->getBool("requireSession");\r
170         pair<bool,const char*> requireSessionWith = settings.first->getString("requireSessionWith");\r
171 \r
172         // If no session is required AND the AuthType (an Apache-derived concept) isn't shibboleth,\r
173         // then we ignore this request and consider it unprotected. Apache might lie to us if\r
174         // ShibBasicHijack is on, but that's up to it.\r
175         if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first &&\r
176 #ifdef HAVE_STRCASECMP\r
177                 (!authType.first || strcasecmp(authType.second,"shibboleth")))\r
178 #else\r
179                 (!authType.first || _stricmp(authType.second,"shibboleth")))\r
180 #endif\r
181             return make_pair(true,request.returnDecline());\r
182 \r
183         // Fix for secadv 20050901\r
184         clearHeaders(request);\r
185 \r
186         pair<string,const char*> shib_cookie = app->getCookieNameProps("_shibsession_");\r
187         const char* session_id = request.getCookie(shib_cookie.first.c_str());\r
188         if (!session_id || !*session_id) {\r
189             // No session.  Maybe that's acceptable?\r
190             if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first)\r
191                 return make_pair(true,request.returnOK());\r
192 \r
193             // No cookie, but we require a session. Initiate a new session using the indicated method.\r
194             procState = "Session Initiator Error";\r
195             const Handler* initiator=NULL;\r
196             if (requireSessionWith.first) {\r
197                 initiator=app->getSessionInitiatorById(requireSessionWith.second);\r
198                 if (!initiator)\r
199                     throw ConfigurationException(\r
200                         "No session initiator found with id ($1), check requireSessionWith command.",\r
201                         params(1,requireSessionWith.second)\r
202                         );\r
203             }\r
204             else {\r
205                 initiator=app->getDefaultSessionInitiator();\r
206                 if (!initiator)\r
207                     throw ConfigurationException("No default session initiator found, check configuration.");\r
208             }\r
209 \r
210             return initiator->run(request,false);\r
211         }\r
212 \r
213         procState = "Session Processing Error";\r
214         const Session* session=NULL;\r
215         try {\r
216             session=request.getSession();\r
217             // Make a localized exception throw if the session isn't valid.\r
218             if (!session)\r
219                 throw RetryableProfileException("Session no longer valid.");\r
220         }\r
221         catch (exception& e) {\r
222             request.log(SPRequest::SPWarn, string("session processing failed: ") + e.what());\r
223 \r
224             // If no session is required, bail now.\r
225             if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first)\r
226                 // Has to be OK because DECLINED will just cause Apache\r
227                 // to fail when it can't locate anything to process the\r
228                 // AuthType.  No session plus requireSession false means\r
229                 // do not authenticate the user at this time.\r
230                 return make_pair(true, request.returnOK());\r
231 \r
232             // Try and cast down.\r
233             exception* base = &e;\r
234             RetryableProfileException* trycast=dynamic_cast<RetryableProfileException*>(base);\r
235             if (trycast) {\r
236                 // Session is invalid but we can retry -- initiate a new session.\r
237                 procState = "Session Initiator Error";\r
238                 const Handler* initiator=NULL;\r
239                 if (requireSessionWith.first) {\r
240                     initiator=app->getSessionInitiatorById(requireSessionWith.second);\r
241                     if (!initiator)\r
242                         throw ConfigurationException(\r
243                             "No session initiator found with id ($1), check requireSessionWith command.",\r
244                             params(1,requireSessionWith.second)\r
245                             );\r
246                 }\r
247                 else {\r
248                     initiator=app->getDefaultSessionInitiator();\r
249                     if (!initiator)\r
250                         throw ConfigurationException("No default session initiator found, check configuration.");\r
251                 }\r
252                 return initiator->run(request,false);\r
253             }\r
254             throw;    // send it to the outer handler\r
255         }\r
256 \r
257         // We're done.  Everything is okay.  Nothing to report.  Nothing to do..\r
258         // Let the caller decide how to proceed.\r
259         request.log(SPRequest::SPDebug, "doAuthentication succeeded");\r
260         return make_pair(false,0);\r
261     }\r
262     catch (XMLToolingException& e) {\r
263         TemplateParameters tp;\r
264         tp.m_map["errorType"] = procState;\r
265         tp.m_map["errorText"] = e.what();\r
266         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
267         return make_pair(true,sendError(request, app, "session", tp, &e));\r
268     }\r
269     catch (exception& e) {\r
270         TemplateParameters tp;\r
271         tp.m_map["errorType"] = procState;\r
272         tp.m_map["errorText"] = e.what();\r
273         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
274         return make_pair(true,sendError(request, app, "session", tp));\r
275     }\r
276 #ifndef _DEBUG\r
277     catch (...) {\r
278         TemplateParameters tp;\r
279         tp.m_map["errorType"] = procState;\r
280         tp.m_map["errorText"] = "Caught an unknown exception.";\r
281         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
282         return make_pair(true,sendError(request, app, "session", tp));\r
283     }\r
284 #endif\r
285 }
286
287 pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
288 {
289 #ifdef _DEBUG\r
290     xmltooling::NDC ndc("doAuthorization");\r
291 #endif\r
292 \r
293     const Application* app=NULL;\r
294     const char* procState = "Authorization Processing Error";\r
295     string targetURL = request.getRequestURL();\r
296 \r
297     try {\r
298         RequestMapper::Settings settings = request.getRequestSettings();\r
299         app = &(request.getApplication());\r
300 \r
301         // Three settings dictate how to proceed.\r
302         pair<bool,const char*> authType = settings.first->getString("authType");\r
303         pair<bool,bool> requireSession = settings.first->getBool("requireSession");\r
304         pair<bool,const char*> requireSessionWith = settings.first->getString("requireSessionWith");\r
305 \r
306         // If no session is required AND the AuthType (an Apache-derived concept) isn't shibboleth,\r
307         // then we ignore this request and consider it unprotected. Apache might lie to us if\r
308         // ShibBasicHijack is on, but that's up to it.\r
309         if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first &&\r
310 #ifdef HAVE_STRCASECMP\r
311                 (!authType.first || strcasecmp(authType.second,"shibboleth")))\r
312 #else\r
313                 (!authType.first || _stricmp(authType.second,"shibboleth")))\r
314 #endif\r
315             return make_pair(true,request.returnDecline());\r
316 \r
317         // Do we have an access control plugin?\r
318         if (settings.second) {\r
319             const Session* session =NULL;\r
320                 pair<string,const char*> shib_cookie=app->getCookieNameProps("_shibsession_");\r
321             const char *session_id = request.getCookie(shib_cookie.first.c_str());\r
322             try {\r
323                         if (session_id && *session_id) {\r
324                     session = request.getSession();\r
325                         }\r
326             }\r
327             catch (exception&) {\r
328                 request.log(SPRequest::SPWarn, "unable to obtain session information to pass to access control provider");\r
329             }\r
330         \r
331             Locker acllock(settings.second);\r
332             if (settings.second->authorized(request,session)) {\r
333                 // Let the caller decide how to proceed.\r
334                 request.log(SPRequest::SPDebug, "access control provider granted access");\r
335                 return make_pair(false,0);\r
336             }\r
337             else {\r
338                 request.log(SPRequest::SPWarn, "access control provider denied access");\r
339                 TemplateParameters tp;\r
340                 tp.m_map["requestURL"] = targetURL;\r
341                 return make_pair(true,sendError(request, app, "access", tp));\r
342             }\r
343             return make_pair(false,0);\r
344         }\r
345         else\r
346             return make_pair(true,request.returnDecline());\r
347     }\r
348     catch (XMLToolingException& e) {
349         TemplateParameters tp;
350         tp.m_map["errorType"] = procState;
351         tp.m_map["errorText"] = e.what();
352         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
353         return make_pair(true,sendError(request, app, "session", tp, &e));
354     }
355     catch (exception& e) {\r
356         TemplateParameters tp;\r
357         tp.m_map["errorType"] = procState;\r
358         tp.m_map["errorText"] = e.what();\r
359         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
360         return make_pair(true,sendError(request, app, "access", tp));\r
361     }\r
362 #ifndef _DEBUG\r
363     catch (...) {\r
364         TemplateParameters tp;\r
365         tp.m_map["errorType"] = procState;\r
366         tp.m_map["errorText"] = "Caught an unknown exception.";\r
367         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
368         return make_pair(true,sendError(request, app, "access", tp));\r
369     }\r
370 #endif\r
371 }
372
373 pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSession) const
374 {
375 #ifdef _DEBUG\r
376     xmltooling::NDC ndc("doExport");\r
377 #endif\r
378 \r
379     const Application* app=NULL;\r
380     const char* procState = "Attribute Processing Error";\r
381     string targetURL = request.getRequestURL();\r
382 \r
383     try {\r
384         RequestMapper::Settings settings = request.getRequestSettings();
385         app = &(request.getApplication());
386 \r
387         const Session* session=NULL;\r
388         pair<string,const char*> shib_cookie=app->getCookieNameProps("_shibsession_");\r
389         const char *session_id = request.getCookie(shib_cookie.first.c_str());\r
390         try {\r
391                 if (session_id && *session_id) {\r
392                 session = request.getSession();\r
393                 }\r
394         }\r
395         catch (exception&) {\r
396             request.log(SPRequest::SPWarn, "unable to obtain session information to export into request headers");\r
397                 // If we have to have a session, then this is a fatal error.\r
398                 if (requireSession)\r
399                         throw;\r
400         }\r
401 \r
402                 // Still no data?\r
403         if (!session) {\r
404                 if (requireSession)\r
405                         throw RetryableProfileException("Unable to obtain session information for request.");\r
406                 else\r
407                         return make_pair(false,0);      // just bail silently\r
408         }\r
409         \r
410         /*\r
411         TODO: port to new cache API\r
412         // Extract data from session.\r
413         pair<const char*,const SAMLSubject*> sub=m_cacheEntry->getSubject(false,true);\r
414         pair<const char*,const SAMLResponse*> unfiltered=m_cacheEntry->getTokens(true,false);\r
415         pair<const char*,const SAMLResponse*> filtered=m_cacheEntry->getTokens(false,true);\r
416 \r
417         // Maybe export the tokens.\r
418         pair<bool,bool> exp=m_settings.first->getBool("exportAssertion");\r
419         if (exp.first && exp.second && unfiltered.first && *unfiltered.first) {\r
420             unsigned int outlen;\r
421             XMLByte* serialized =\r
422                 Base64::encode(reinterpret_cast<XMLByte*>((char*)unfiltered.first), XMLString::stringLen(unfiltered.first), &outlen);\r
423             XMLByte *pos, *pos2;\r
424             for (pos=serialized, pos2=serialized; *pos2; pos2++)\r
425                 if (isgraph(*pos2))\r
426                     *pos++=*pos2;\r
427             *pos=0;\r
428             setHeader("Shib-Attributes", reinterpret_cast<char*>(serialized));\r
429             XMLString::release(&serialized);\r
430         }\r
431 \r
432         // Export the SAML AuthnMethod and the origin site name, and possibly the NameIdentifier.\r
433         setHeader("Shib-Origin-Site", m_cacheEntry->getProviderId());\r
434         setHeader("Shib-Identity-Provider", m_cacheEntry->getProviderId());\r
435         setHeader("Shib-Authentication-Method", m_cacheEntry->getAuthnContext());\r
436         \r
437         // Get the AAP providers, which contain the attribute policy info.\r
438         Iterator<IAAP*> provs=m_app->getAAPProviders();\r
439 \r
440         // Export NameID?\r
441         while (provs.hasNext()) {\r
442             IAAP* aap=provs.next();\r
443             xmltooling::Locker locker(aap);\r
444             const XMLCh* format = sub.second->getNameIdentifier()->getFormat();\r
445             const IAttributeRule* rule=aap->lookup(format ? format : SAMLNameIdentifier::UNSPECIFIED);\r
446             if (rule && rule->getHeader()) {\r
447                 auto_ptr_char form(format ? format : SAMLNameIdentifier::UNSPECIFIED);\r
448                 auto_ptr_char nameid(sub.second->getNameIdentifier()->getName());\r
449                 setHeader("Shib-NameIdentifier-Format", form.get());\r
450                 if (!strcmp(rule->getHeader(),"REMOTE_USER"))\r
451                     setRemoteUser(nameid.get());\r
452                 else\r
453                     setHeader(rule->getHeader(), nameid.get());\r
454             }\r
455         }\r
456         \r
457         setHeader("Shib-Application-ID", m_app->getId());\r
458     \r
459         // Export the attributes.\r
460         Iterator<SAMLAssertion*> a_iter(filtered.second ? filtered.second->getAssertions() : EMPTY(SAMLAssertion*));\r
461         while (a_iter.hasNext()) {\r
462             SAMLAssertion* assert=a_iter.next();\r
463             Iterator<SAMLStatement*> statements=assert->getStatements();\r
464             while (statements.hasNext()) {\r
465                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());\r
466                 if (!astate)\r
467                     continue;\r
468                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();\r
469                 while (attrs.hasNext()) {\r
470                     SAMLAttribute* attr=attrs.next();\r
471             \r
472                     // Are we supposed to export it?\r
473                     provs.reset();\r
474                     while (provs.hasNext()) {\r
475                         IAAP* aap=provs.next();\r
476                         xmltooling::Locker locker(aap);\r
477                         const IAttributeRule* rule=aap->lookup(attr->getName(),attr->getNamespace());\r
478                         if (!rule || !rule->getHeader())\r
479                             continue;\r
480                     \r
481                         Iterator<string> vals=attr->getSingleByteValues();\r
482                         if (!strcmp(rule->getHeader(),"REMOTE_USER") && vals.hasNext())\r
483                             setRemoteUser(vals.next().c_str());\r
484                         else {\r
485                             int it=0;\r
486                             string header = getSecureHeader(rule->getHeader());\r
487                             if (!header.empty())\r
488                                 it++;\r
489                             for (; vals.hasNext(); it++) {\r
490                                 string value = vals.next();\r
491                                 for (string::size_type pos = value.find_first_of(";", string::size_type(0));\r
492                                         pos != string::npos;\r
493                                         pos = value.find_first_of(";", pos)) {\r
494                                     value.insert(pos, "\\");\r
495                                     pos += 2;\r
496                                 }\r
497                                 if (it)\r
498                                     header += ";";\r
499                                 header += value;\r
500                             }\r
501                             setHeader(rule->getHeader(), header.c_str());\r
502                         }\r
503                     }\r
504                 }\r
505             }\r
506         }\r
507         */\r
508     \r
509         return make_pair(false,0);\r
510     }\r
511     catch (XMLToolingException& e) {\r
512         TemplateParameters tp;
513         tp.m_map["errorType"] = procState;\r
514         tp.m_map["errorText"] = e.what();\r
515         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
516         return make_pair(true,sendError(request, app, "rm", tp, &e));\r
517     }\r
518     catch (exception& e) {
519         TemplateParameters tp;
520         tp.m_map["errorType"] = procState;
521         tp.m_map["errorText"] = e.what();
522         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
523         return make_pair(true,sendError(request, app, "rm", tp));
524     }
525 #ifndef _DEBUG\r
526     catch (...) {\r
527         TemplateParameters tp;
528         tp.m_map["errorType"] = procState;\r
529         tp.m_map["errorText"] = "Caught an unknown exception.";\r
530         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));\r
531         return make_pair(true,sendError(request, app, "rm", tp));\r
532     }\r
533 #endif\r
534 }
535
536 pair<bool,long> ServiceProvider::doHandler(SPRequest& request) const
537 {
538 #ifdef _DEBUG
539     xmltooling::NDC ndc("doHandler");
540 #endif
541
542     const Application* app=NULL;
543     const char* procState = "Shibboleth Handler Error";
544     string targetURL = request.getRequestURL();
545
546     try {
547         RequestMapper::Settings settings = request.getRequestSettings();
548         app = &(request.getApplication());
549
550         const char* handlerURL=request.getHandlerURL(targetURL.c_str());
551         if (!handlerURL)
552             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
553
554         // Make sure we only process handler requests.
555         if (!strstr(targetURL.c_str(),handlerURL))
556             return make_pair(true, request.returnDecline());
557
558         const PropertySet* sessionProps=app->getPropertySet("Sessions");
559         if (!sessionProps)
560             throw ConfigurationException("Unable to map request to application session settings, check configuration.");
561
562         // Process incoming request.
563         pair<bool,bool> handlerSSL=sessionProps->getBool("handlerSSL");
564       
565         // Make sure this is SSL, if it should be
566         if ((!handlerSSL.first || handlerSSL.second) && strcmp(request.getScheme(),"https"))
567             throw FatalProfileException("Blocked non-SSL access to Shibboleth handler.");
568
569         // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
570         // so the path info is the next character (or null).
571         const Handler* handler=app->getHandler(targetURL.c_str() + strlen(handlerURL));
572         if (!handler)
573             throw BindingException("Shibboleth handler invoked at an unconfigured location.");
574
575         if (XMLHelper::isNodeNamed(handler->getElement(),samlconstants::SAML20MD_NS,AssertionConsumerService::LOCAL_NAME))
576             procState = "Session Creation Error";
577         else if (XMLString::equals(handler->getElement()->getLocalName(),SessionInitiator))
578             procState = "Session Initiator Error";
579         else if (XMLHelper::isNodeNamed(handler->getElement(),samlconstants::SAML20MD_NS,SingleLogoutService::LOCAL_NAME))
580             procState = "Session Termination Error";
581         else
582             procState = "Protocol Handler Error";
583         pair<bool,long> hret=handler->run(request);
584
585         // Did the handler run successfully?
586         if (hret.first)
587             return hret;
588        
589         throw BindingException("Configured Shibboleth handler failed to process the request.");
590     }
591     catch (MetadataException& e) {
592         TemplateParameters tp;
593         tp.m_map["errorText"] = e.what();
594         // See if a metadata error page is installed.
595         const PropertySet* props=app->getPropertySet("Errors");
596         if (props) {
597             pair<bool,const char*> p=props->getString("metadata");
598             if (p.first) {
599                 tp.m_map["errorType"] = procState;
600                 tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
601                 return make_pair(true,sendError(request, app, "metadata", tp, &e));
602             }
603         }
604         throw;
605     }
606     catch (XMLToolingException& e) {
607         TemplateParameters tp;
608         tp.m_map["errorType"] = procState;
609         tp.m_map["errorText"] = e.what();
610         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
611         return make_pair(true,sendError(request, app, "session", tp, &e));
612     }
613     catch (exception& e) {
614         TemplateParameters tp;
615         tp.m_map["errorType"] = procState;
616         tp.m_map["errorText"] = e.what();
617         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
618         return make_pair(true,sendError(request, app, "session", tp));
619     }
620 #ifndef _DEBUG
621     catch (...) {
622         TemplateParameters tp;
623         tp.m_map["errorType"] = procState;
624         tp.m_map["errorText"] = "Caught an unknown exception.";
625         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
626         return make_pair(true,sendError(request, app, "session", tp));
627     }
628 #endif
629 }