Move past decoded bytes in path
[shibboleth/cpp-sp.git] / shib-target / shib-target.cpp
1 /*
2  *  Copyright 2001-2005 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  * shib-target.cpp -- The ShibTarget class, a superclass for general
19  *                    target code
20  *
21  * Created by:  Derek Atkins <derek@ihtfp.com>
22  *
23  * $Id$
24  */
25
26 #include "internal.h"
27
28 #ifdef HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31
32 #include <sstream>
33 #include <fstream>
34 #include <stdexcept>
35
36 #include <shib/shib-threads.h>
37 #include <xercesc/util/Base64.hpp>
38
39 #ifndef HAVE_STRCASECMP
40 # define strcasecmp stricmp
41 #endif
42
43 using namespace std;
44 using namespace saml;
45 using namespace shibboleth;
46 using namespace shibtarget;
47 using namespace log4cpp;
48
49 namespace shibtarget {
50   class ShibTargetPriv
51   {
52   public:
53     ShibTargetPriv();
54     ~ShibTargetPriv();
55
56     // Helper functions
57     void get_application(ShibTarget* st, const string& protocol, const string& hostname, int port, const string& uri);
58     void* sendError(ShibTarget* st, const char* page, ShibMLP &mlp);
59     void clearHeaders(ShibTarget* st);
60     
61     // Handlers do the real Shibboleth work
62     pair<bool,void*> dispatch(
63         ShibTarget* st,
64         const IPropertySet* handler,
65         bool isHandler=true,
66         const char* forceType=NULL
67         ) const;
68
69   private:
70     friend class ShibTarget;
71     IRequestMapper::Settings m_settings;
72     const IApplication *m_app;
73     mutable string m_handlerURL;
74     mutable map<string,string> m_cookieMap;
75
76     ISessionCacheEntry* m_cacheEntry;
77
78     ShibTargetConfig* m_Config;
79
80     IConfig* m_conf;
81     IRequestMapper* m_mapper;
82   };
83 }
84
85
86 /*************************************************************************
87  * Shib Target implementation
88  */
89
90 static char _x2c(const char *what)
91 {
92     register char digit;
93
94     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
95     digit *= 16;
96     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
97     return(digit);
98 }
99
100 ShibTarget::ShibTarget(void) : m_priv(NULL)
101 {
102   m_priv = new ShibTargetPriv();
103 }
104
105 ShibTarget::ShibTarget(const IApplication *app) : m_priv(NULL)
106 {
107   m_priv = new ShibTargetPriv();
108   m_priv->m_app = app;
109 }
110
111 ShibTarget::~ShibTarget(void)
112 {
113   if (m_priv) delete m_priv;
114 }
115
116 void ShibTarget::init(
117     const char* protocol,
118     const char* hostname,
119     int port,
120     const char* uri,
121     const char* content_type,
122     const char* remote_addr,
123     const char* method
124     )
125 {
126 #ifdef _DEBUG
127     saml::NDC ndc("init");
128 #endif
129
130     if (m_priv->m_app)
131         throw SAMLException("Request initialization occurred twice!");
132
133     if (method) m_method = method;
134     if (protocol) m_protocol = protocol;
135     if (hostname) m_hostname = hostname;
136     if (content_type) m_content_type = content_type;
137     if (remote_addr) m_remote_addr = remote_addr;
138     m_port = port;
139   
140     // Fix for bug 574, secadv 20061002
141     // Unescape URI up to query string delimiter by looking for %XX escapes.
142     // Adapted from Apache's util.c, ap_unescape_url function.
143     if (uri) {
144         while (*uri) {
145             if (*uri == '?') {
146                 m_uri += uri;
147                 break;
148             }
149             else if (*uri != '%') {
150                 m_uri += *uri;
151             }
152             else {
153                 m_uri += _x2c(++uri);
154                 ++uri;
155             }
156             ++uri;
157         }
158     }
159   
160     m_priv->m_Config = &ShibTargetConfig::getConfig();
161     m_priv->get_application(this, protocol, hostname, port, m_uri);
162 }
163
164
165 // These functions implement the server-agnostic shibboleth engine
166 // The web server modules implement a subclass and then call into 
167 // these methods once they instantiate their request object.
168
169 pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
170 {
171 #ifdef _DEBUG
172     saml::NDC ndc("doCheckAuthN");
173 #endif
174
175     const char* procState = "Request Processing Error";
176     const char* targetURL = m_url.c_str();
177     ShibMLP mlp;
178
179     try {
180         if (!m_priv->m_app)
181             throw ConfigurationException("System uninitialized, application did not supply request information.");
182
183         // If not SSL, check to see if we should block or redirect it.
184         if (!strcmp("http",getProtocol())) {
185             pair<bool,const char*> redirectToSSL = m_priv->m_settings.first->getString("redirectToSSL");
186             if (redirectToSSL.first) {
187                 if (!strcasecmp("GET",getRequestMethod()) || !strcasecmp("HEAD",getRequestMethod())) {
188                     // Compute the new target URL
189                     string redirectURL = string("https://") + getHostname();
190                     if (strcmp(redirectToSSL.second,"443")) {
191                         redirectURL = redirectURL + ':' + redirectToSSL.second;
192                     }
193                     redirectURL += getRequestURI();
194                     return make_pair(true, sendRedirect(redirectURL));
195                 }
196                 else {
197                     mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
198                     return make_pair(true,m_priv->sendError(this,"ssl", mlp));
199                 }
200             }
201         }
202         
203         string hURL = getHandlerURL(targetURL);
204         const char* handlerURL=hURL.c_str();
205         if (!handlerURL)
206             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
207
208         // If the request URL contains the handler base URL for this application, either dispatch
209         // directly (mainly Apache 2.0) or just pass back control.
210         if (strstr(targetURL,handlerURL)) {
211             if (handler)
212                 return doHandler();
213             else
214                 return pair<bool,void*>(true, returnOK());
215         }
216
217         // Three settings dictate how to proceed.
218         pair<bool,const char*> authType = m_priv->m_settings.first->getString("authType");
219         pair<bool,bool> requireSession = m_priv->m_settings.first->getBool("requireSession");
220         pair<bool,const char*> requireSessionWith = m_priv->m_settings.first->getString("requireSessionWith");
221
222         // If no session is required AND the AuthType (an Apache-derived concept) isn't shibboleth,
223         // then we ignore this request and consider it unprotected. Apache might lie to us if
224         // ShibBasicHijack is on, but that's up to it.
225         if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first &&
226 #ifdef HAVE_STRCASECMP
227                 (!authType.first || strcasecmp(authType.second,"shibboleth")))
228 #else
229                 (!authType.first || stricmp(authType.second,"shibboleth")))
230 #endif
231             return pair<bool,void*>(true,returnDecline());
232
233         // Fix for secadv 20050901
234         m_priv->clearHeaders(this);
235
236         pair<string,const char*> shib_cookie = getCookieNameProps("_shibsession_");
237         const char* session_id = getCookie(shib_cookie.first);
238         if (!session_id || !*session_id) {
239             // No session.  Maybe that's acceptable?
240             if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first)
241                 return pair<bool,void*>(true,returnOK());
242
243             // No cookie, but we require a session. Initiate a new session using the indicated method.
244             procState = "Session Initiator Error";
245             const IPropertySet* initiator=NULL;
246             if (requireSessionWith.first)
247                 initiator=m_priv->m_app->getSessionInitiatorById(requireSessionWith.second);
248             if (!initiator)
249                 initiator=m_priv->m_app->getDefaultSessionInitiator();
250             
251             // Standard handler element, so we dispatch normally.
252             if (initiator)
253                 return m_priv->dispatch(this,initiator,false);
254             else {
255                 // Legacy config support maps the Sessions element to the Shib profile.
256                 auto_ptr_char binding(Constants::SHIB_SESSIONINIT_PROFILE_URI);
257                 return m_priv->dispatch(this, m_priv->m_app->getPropertySet("Sessions"), false, binding.get());
258             }
259         }
260
261         procState = "Session Processing Error";
262         try {
263             // Localized exception throw if the session isn't valid.
264             m_priv->m_conf->getListener()->sessionGet(
265                 m_priv->m_app,
266                 session_id,
267                 m_remote_addr.c_str(),
268                 &m_priv->m_cacheEntry
269                 );
270         }
271         catch (SAMLException& e) {
272             log(LogLevelError, string("session processing failed: ") + e.what());
273
274             // If no session is required, bail now.
275             if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first)
276                 // Has to be OK because DECLINED will just cause Apache
277                 // to fail when it can't locate anything to process the
278                 // AuthType.  No session plus requireSession false means
279                 // do not authenticate the user at this time.
280                 return pair<bool,void*>(true, returnOK());
281
282             // Try and cast down.
283             SAMLException* base = &e;
284             RetryableProfileException* trycast=dynamic_cast<RetryableProfileException*>(base);
285             if (trycast) {
286                 // Session is invalid but we can retry -- initiate a new session.
287                 procState = "Session Initiator Error";
288                 const IPropertySet* initiator=NULL;
289                 if (requireSessionWith.first)
290                     initiator=m_priv->m_app->getSessionInitiatorById(requireSessionWith.second);
291                 if (!initiator)
292                     initiator=m_priv->m_app->getDefaultSessionInitiator();
293                 // Standard handler element, so we dispatch normally.
294                 if (initiator)
295                     return m_priv->dispatch(this,initiator,false);
296                 else {
297                     // Legacy config support maps the Sessions element to the Shib profile.
298                     auto_ptr_char binding(Constants::SHIB_SESSIONINIT_PROFILE_URI);
299                     return m_priv->dispatch(this, m_priv->m_app->getPropertySet("Sessions"), false, binding.get());
300                 }
301             }
302             throw;    // send it to the outer handler
303         }
304
305         // We're done.  Everything is okay.  Nothing to report.  Nothing to do..
306         // Let the caller decide how to proceed.
307         log(LogLevelDebug, "doCheckAuthN succeeded");
308         return pair<bool,void*>(false,NULL);
309     }
310     catch (SAMLException& e) {
311         mlp.insert(e);
312     }
313 #ifndef _DEBUG
314     catch (...) {
315         mlp.insert("errorText", "Caught an unknown exception.");
316     }
317 #endif
318
319     // If we get here then we've got an error.
320     mlp.insert("errorType", procState);
321     if (targetURL)
322         mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
323
324     return pair<bool,void*>(true,m_priv->sendError(this,"session", mlp));
325 }
326
327 pair<bool,void*> ShibTarget::doHandler(void)
328 {
329 #ifdef _DEBUG
330     saml::NDC ndc("doHandler");
331 #endif
332
333     const char* procState = "Shibboleth Handler Error";
334     const char* targetURL = m_url.c_str();
335     ShibMLP mlp;
336
337     try {
338         if (!m_priv->m_app)
339             throw ConfigurationException("System uninitialized, application did not supply request information.");
340
341         string hURL = getHandlerURL(targetURL);
342         const char* handlerURL=hURL.c_str();
343         if (!handlerURL)
344             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
345
346         // Make sure we only process handler requests.
347         if (!strstr(targetURL,handlerURL))
348             return pair<bool,void*>(true, returnDecline());
349
350         const IPropertySet* sessionProps=m_priv->m_app->getPropertySet("Sessions");
351         if (!sessionProps)
352             throw ConfigurationException("Unable to map request to application session settings, check configuration.");
353
354         // Process incoming request.
355         pair<bool,bool> handlerSSL=sessionProps->getBool("handlerSSL");
356       
357         // Make sure this is SSL, if it should be
358         if ((!handlerSSL.first || handlerSSL.second) && m_protocol != "https")
359             throw FatalProfileException("Blocked non-SSL access to Shibboleth handler.");
360
361         // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
362         // so the path info is the next character (or null).
363         const IPropertySet* handler=m_priv->m_app->getHandlerConfig(targetURL + strlen(handlerURL));
364         if (handler) {
365             if (saml::XML::isElementNamed(handler->getElement(),shibtarget::XML::SAML2META_NS,SHIBT_L(AssertionConsumerService))) {
366                 procState = "Session Creation Error";
367                 return m_priv->dispatch(this,handler);
368             }
369             else if (saml::XML::isElementNamed(handler->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(SessionInitiator))) {
370                 procState = "Session Initiator Error";
371                 return m_priv->dispatch(this,handler);
372             }
373             else if (saml::XML::isElementNamed(handler->getElement(),shibtarget::XML::SAML2META_NS,SHIBT_L(SingleLogoutService))) {
374                 procState = "Session Termination Error";
375                 return m_priv->dispatch(this,handler);
376             }
377             else if (saml::XML::isElementNamed(handler->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(DiagnosticService))) {
378                 procState = "Diagnostics Error";
379                 return m_priv->dispatch(this,handler);
380             }
381             else {
382                 procState = "Extension Service Error";
383                 return m_priv->dispatch(this,handler);
384             }
385         }
386         
387         if (strlen(targetURL)>strlen(handlerURL) && targetURL[strlen(handlerURL)]!='?')
388             throw SAMLException("Shibboleth handler invoked at an unconfigured location.");
389         
390         // This is a legacy direct execution of the handler (the old shireURL).
391         // If this is a GET, we see if it's a lazy session request, otherwise
392         // assume it's a SAML 1.x POST profile response and process it.
393         if (!strcasecmp(m_method.c_str(), "GET")) {
394             procState = "Session Initiator Error";
395             auto_ptr_char binding(Constants::SHIB_SESSIONINIT_PROFILE_URI);
396             return m_priv->dispatch(this, sessionProps, true, binding.get());
397         }
398         
399         procState = "Session Creation Error";
400         auto_ptr_char binding(SAMLBrowserProfile::BROWSER_POST);
401         return m_priv->dispatch(this, sessionProps, true, binding.get());
402     }
403     catch (MetadataException& e) {
404         mlp.insert(e);
405         // See if a metadata error page is installed.
406         const IPropertySet* props=m_priv->m_app->getPropertySet("Errors");
407         if (props) {
408             pair<bool,const char*> p=props->getString("metadata");
409             if (p.first) {
410                 mlp.insert("errorType", procState);
411                 if (targetURL)
412                     mlp.insert("requestURL", targetURL);
413                 return make_pair(true,m_priv->sendError(this,"metadata", mlp));
414             }
415         }
416     }
417     catch (SAMLException& e) {
418         mlp.insert(e);
419     }
420 #ifndef _DEBUG
421     catch (...) {
422         mlp.insert("errorText", "Caught an unknown exception.");
423     }
424 #endif
425
426     // If we get here then we've got an error.
427     mlp.insert("errorType", procState);
428
429     if (targetURL)
430         mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
431
432     return make_pair(true,m_priv->sendError(this,"session", mlp));
433 }
434
435 pair<bool,void*> ShibTarget::doCheckAuthZ(void)
436 {
437 #ifdef _DEBUG
438     saml::NDC ndc("doCheckAuthZ");
439 #endif
440
441     ShibMLP mlp;
442     const char* procState = "Authorization Processing Error";
443     const char* targetURL = m_url.c_str();
444
445     try {
446         if (!m_priv->m_app)
447             throw ConfigurationException("System uninitialized, application did not supply request information.");
448
449         // Three settings dictate how to proceed.
450         pair<bool,const char*> authType = m_priv->m_settings.first->getString("authType");
451         pair<bool,bool> requireSession = m_priv->m_settings.first->getBool("requireSession");
452         pair<bool,const char*> requireSessionWith = m_priv->m_settings.first->getString("requireSessionWith");
453
454         // If no session is required AND the AuthType (an Apache-derived concept) isn't shibboleth,
455         // then we ignore this request and consider it unprotected. Apache might lie to us if
456         // ShibBasicHijack is on, but that's up to it.
457         if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first &&
458 #ifdef HAVE_STRCASECMP
459                 (!authType.first || strcasecmp(authType.second,"shibboleth")))
460 #else
461                 (!authType.first || stricmp(authType.second,"shibboleth")))
462 #endif
463             return pair<bool,void*>(true,returnDecline());
464
465         // Do we have an access control plugin?
466         if (m_priv->m_settings.second) {
467                 
468                 if (!m_priv->m_cacheEntry) {
469                     // No data yet, so we may need to try and get the session.
470                         pair<string,const char*> shib_cookie=getCookieNameProps("_shibsession_");
471                         const char *session_id = getCookie(shib_cookie.first);
472                     try {
473                                 if (session_id && *session_id) {
474                                     m_priv->m_conf->getListener()->sessionGet(
475                                         m_priv->m_app,
476                                         session_id,
477                                         m_remote_addr.c_str(),
478                                         &m_priv->m_cacheEntry
479                                         );
480                                 }
481                     }
482                     catch (SAMLException&) {
483                         log(LogLevelError, "doCheckAuthZ: unable to obtain session information to pass to access control provider");
484                     }
485                 }
486         
487             Locker acllock(m_priv->m_settings.second);
488             if (m_priv->m_settings.second->authorized(this,m_priv->m_cacheEntry)) {
489                 // Let the caller decide how to proceed.
490                 log(LogLevelDebug, "doCheckAuthZ: access control provider granted access");
491                 return pair<bool,void*>(false,NULL);
492             }
493             else {
494                 log(LogLevelWarn, "doCheckAuthZ: access control provider denied access");
495                 if (targetURL)
496                     mlp.insert("requestURL", targetURL);
497                 return make_pair(true,m_priv->sendError(this, "access", mlp));
498             }
499         }
500         else
501             return make_pair(true,returnDecline());
502     }
503     catch (SAMLException& e) {
504         mlp.insert(e);
505     }
506 #ifndef _DEBUG
507     catch (...) {
508         mlp.insert("errorText", "Caught an unknown exception.");
509     }
510 #endif
511
512     // If we get here then we've got an error.
513     mlp.insert("errorType", procState);
514
515     if (targetURL)
516         mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
517
518     return make_pair(true,m_priv->sendError(this, "access", mlp));
519 }
520
521 pair<bool,void*> ShibTarget::doExportAssertions(bool requireSession)
522 {
523 #ifdef _DEBUG
524     saml::NDC ndc("doExportAssertions");
525 #endif
526
527     ShibMLP mlp;
528     const char* procState = "Attribute Processing Error";
529     const char* targetURL = m_url.c_str();
530
531     try {
532         if (!m_priv->m_app)
533             throw ConfigurationException("System uninitialized, application did not supply request information.");
534
535         if (!m_priv->m_cacheEntry) {
536             // No data yet, so we need to get the session. This can only happen
537             // if the call to doCheckAuthn doesn't happen in the same object lifetime.
538                 pair<string,const char*> shib_cookie=getCookieNameProps("_shibsession_");
539                 const char *session_id = getCookie(shib_cookie.first);
540             try {
541                         if (session_id && *session_id) {
542                             m_priv->m_conf->getListener()->sessionGet(
543                                 m_priv->m_app,
544                                 session_id,
545                                 m_remote_addr.c_str(),
546                                 &m_priv->m_cacheEntry
547                                 );
548                         }
549             }
550             catch (SAMLException&) {
551                 log(LogLevelError, "unable to obtain session information to export into request headers");
552                 // If we have to have a session, then this is a fatal error.
553                 if (requireSession)
554                         throw;
555             }
556         }
557
558                 // Still no data?
559         if (!m_priv->m_cacheEntry) {
560                 if (requireSession)
561                         throw InvalidSessionException("Unable to obtain session information for request.");
562                 else
563                         return pair<bool,void*>(false,NULL);    // just bail silently
564         }
565
566         ISessionCacheEntry::CachedResponse cr=m_priv->m_cacheEntry->getResponse();
567
568         // Maybe export the response.
569         pair<bool,bool> exp=m_priv->m_settings.first->getBool("exportAssertion");
570         if (exp.first && exp.second && cr.unfiltered) {
571             ostringstream os;
572             os << *cr.unfiltered;
573             unsigned int outlen;
574             XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>((char*)os.str().c_str()), os.str().length(), &outlen);
575             XMLByte *pos, *pos2;
576             for (pos=serialized, pos2=serialized; *pos2; pos2++)
577                 if (isgraph(*pos2))
578                     *pos++=*pos2;
579             *pos=0;
580             setHeader("Shib-Attributes", reinterpret_cast<char*>(serialized));
581             XMLString::release(&serialized);
582         }
583     
584         // Export the SAML AuthnMethod and the origin site name, and possibly the NameIdentifier.
585         setHeader("Shib-Origin-Site", m_priv->m_cacheEntry->getProviderId());
586         setHeader("Shib-Identity-Provider", m_priv->m_cacheEntry->getProviderId());
587         auto_ptr_char am(m_priv->m_cacheEntry->getAuthnStatement()->getAuthMethod());
588         setHeader("Shib-Authentication-Method", am.get());
589         
590         // Get the AAP providers, which contain the attribute policy info.
591         Iterator<IAAP*> provs=m_priv->m_app->getAAPProviders();
592
593         // Export NameID?
594         while (provs.hasNext()) {
595             IAAP* aap=provs.next();
596             Locker locker(aap);
597             const XMLCh* format = m_priv->m_cacheEntry->getAuthnStatement()->getSubject()->getNameIdentifier()->getFormat();
598             const IAttributeRule* rule=aap->lookup(format ? format : SAMLNameIdentifier::UNSPECIFIED);
599             if (rule && rule->getHeader()) {
600                 auto_ptr_char form(format ? format : SAMLNameIdentifier::UNSPECIFIED);
601                 auto_ptr_char nameid(m_priv->m_cacheEntry->getAuthnStatement()->getSubject()->getNameIdentifier()->getName());
602                 setHeader("Shib-NameIdentifier-Format", form.get());
603                 if (!strcmp(rule->getHeader(),"REMOTE_USER"))
604                     setRemoteUser(nameid.get());
605                 else
606                     setHeader(rule->getHeader(), nameid.get());
607             }
608         }
609         
610         setHeader("Shib-Application-ID", m_priv->m_app->getId());
611     
612         string strUnsetValue;
613         const IPropertySet* localProps=m_priv->m_Config->getINI()->getPropertySet("Local");
614         if (localProps) {
615             pair<bool,const char*> unsetValue=localProps->getString("unsetHeaderValue");
616             if (unsetValue.first)
617                 strUnsetValue = unsetValue.second;
618         }
619
620         // Export the attributes.
621         Iterator<SAMLAssertion*> a_iter(cr.filtered ? cr.filtered->getAssertions() : EMPTY(SAMLAssertion*));
622         while (a_iter.hasNext()) {
623             SAMLAssertion* assert=a_iter.next();
624             Iterator<SAMLStatement*> statements=assert->getStatements();
625             while (statements.hasNext()) {
626                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
627                 if (!astate)
628                     continue;
629                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
630                 while (attrs.hasNext()) {
631                     SAMLAttribute* attr=attrs.next();
632             
633                     // Are we supposed to export it?
634                     provs.reset();
635                     while (provs.hasNext()) {
636                         IAAP* aap=provs.next();
637                         Locker locker(aap);
638                         const IAttributeRule* rule=aap->lookup(attr->getName(),attr->getNamespace());
639                         if (!rule || !rule->getHeader())
640                             continue;
641                     
642                         Iterator<string> vals=attr->getSingleByteValues();
643                         if (!strcmp(rule->getHeader(),"REMOTE_USER") && vals.hasNext())
644                             setRemoteUser(vals.next());
645                         else {
646                             int it=0;
647                             string header = getHeader(rule->getHeader());
648                             if (header == strUnsetValue)
649                                 header.erase();
650                             else
651                                 it++;
652                             for (; vals.hasNext(); it++) {
653                                 string value = vals.next();
654                                 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
655                                         pos != string::npos;
656                                         pos = value.find_first_of(";", pos)) {
657                                     value.insert(pos, "\\");
658                                     pos += 2;
659                                 }
660                                 if (it)
661                                     header += ";";
662                                 header += value;
663                             }
664                             setHeader(rule->getHeader(), header);
665                         }
666                     }
667                 }
668             }
669         }
670     
671         return pair<bool,void*>(false,NULL);
672     }
673     catch (SAMLException& e) {
674         mlp.insert(e);
675     }
676 #ifndef _DEBUG
677     catch (...) {
678         mlp.insert("errorText", "Caught an unknown exception.");
679     }
680 #endif
681
682     // If we get here then we've got an error.
683     mlp.insert("errorType", procState);
684
685     if (targetURL)
686         mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
687
688     return make_pair(true,m_priv->sendError(this, "rm", mlp));
689 }
690
691 const char* ShibTarget::getCookie(const string& name) const
692 {
693     if (m_priv->m_cookieMap.empty()) {
694         string cookies=getCookies();
695
696         string::size_type pos=0,cname,namelen,val,vallen;
697         while (pos !=string::npos && pos < cookies.length()) {
698             while (isspace(cookies[pos])) pos++;
699             cname=pos;
700             pos=cookies.find_first_of("=",pos);
701             if (pos == string::npos)
702                 break;
703             namelen=pos-cname;
704             pos++;
705             if (pos==cookies.length())
706                 break;
707             val=pos;
708             pos=cookies.find_first_of(";",pos);
709             if (pos != string::npos) {
710                 vallen=pos-val;
711                 pos++;
712                 m_priv->m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val,vallen)));
713             }
714             else
715                 m_priv->m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val)));
716         }
717     }
718     map<string,string>::const_iterator lookup=m_priv->m_cookieMap.find(name);
719     return (lookup==m_priv->m_cookieMap.end()) ? NULL : lookup->second.c_str();
720 }
721
722 pair<string,const char*> ShibTarget::getCookieNameProps(const char* prefix) const
723 {
724     static const char* defProps="; path=/";
725     
726     const IPropertySet* props=m_priv->m_app ? m_priv->m_app->getPropertySet("Sessions") : NULL;
727     if (props) {
728         pair<bool,const char*> p=props->getString("cookieProps");
729         if (!p.first)
730             p.second=defProps;
731         pair<bool,const char*> p2=props->getString("cookieName");
732         if (p2.first)
733             return make_pair(string(prefix) + p2.second,p.second);
734         return make_pair(string(prefix) + m_priv->m_app->getHash(),p.second);
735     }
736     
737     // Shouldn't happen, but just in case..
738     return pair<string,const char*>(prefix,defProps);
739 }
740
741 string ShibTarget::getHandlerURL(const char* resource) const
742 {
743     if (!m_priv->m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
744         return m_priv->m_handlerURL;
745         
746     if (!m_priv->m_app)
747         throw ConfigurationException("Internal error in ShibTargetPriv::getHandlerURL, missing application pointer.");
748
749     bool ssl_only=false;
750     const char* handler=NULL;
751     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
752     if (props) {
753         pair<bool,bool> p=props->getBool("handlerSSL");
754         if (p.first)
755             ssl_only=p.second;
756         pair<bool,const char*> p2=props->getString("handlerURL");
757         if (p2.first)
758             handler=p2.second;
759     }
760     
761     // Should never happen...
762     if (!handler || (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)))
763         throw ConfigurationException(
764             "Invalid handlerURL property ($1) in Application ($2)",
765             params(2, handler ? handler : "null", m_priv->m_app->getId())
766             );
767
768     // The "handlerURL" property can be in one of three formats:
769     //
770     // 1) a full URI:       http://host/foo/bar
771     // 2) a hostless URI:   http:///foo/bar
772     // 3) a relative path:  /foo/bar
773     //
774     // #  Protocol  Host        Path
775     // 1  handler   handler     handler
776     // 2  handler   resource    handler
777     // 3  resource  resource    handler
778     //
779     // note: if ssl_only is true, make sure the protocol is https
780
781     const char* path = NULL;
782
783     // Decide whether to use the handler or the resource for the "protocol"
784     const char* prot;
785     if (*handler != '/') {
786         prot = handler;
787     }
788     else {
789         prot = resource;
790         path = handler;
791     }
792
793     // break apart the "protocol" string into protocol, host, and "the rest"
794     const char* colon=strchr(prot,':');
795     colon += 3;
796     const char* slash=strchr(colon,'/');
797     if (!path)
798         path = slash;
799
800     // Compute the actual protocol and store in member.
801     if (ssl_only)
802         m_priv->m_handlerURL.assign("https://");
803     else
804         m_priv->m_handlerURL.assign(prot, colon-prot);
805
806     // create the "host" from either the colon/slash or from the target string
807     // If prot == handler then we're in either #1 or #2, else #3.
808     // If slash == colon then we're in #2.
809     if (prot != handler || slash == colon) {
810         colon = strchr(resource, ':');
811         colon += 3;      // Get past the ://
812         slash = strchr(colon, '/');
813     }
814     string host(colon, (slash ? slash-colon : strlen(colon)));
815
816     // Build the handler URL
817     m_priv->m_handlerURL+=host + path;
818     return m_priv->m_handlerURL;
819 }
820
821 void ShibTarget::log(ShibLogLevel level, const string& msg)
822 {
823     Category::getInstance("shibtarget.ShibTarget").log(
824         (level == LogLevelDebug ? Priority::DEBUG :
825         (level == LogLevelInfo ? Priority::INFO :
826         (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
827         msg
828     );
829 }
830
831 const IApplication* ShibTarget::getApplication() const
832 {
833     return m_priv->m_app;
834 }
835
836 const IConfig* ShibTarget::getConfig() const
837 {
838     return m_priv->m_conf;
839 }
840
841 void* ShibTarget::returnDecline(void)
842 {
843     return NULL;
844 }
845
846 void* ShibTarget::returnOK(void)
847 {
848     return NULL;
849 }
850
851
852 /*************************************************************************
853  * Shib Target Private implementation
854  */
855
856 ShibTargetPriv::ShibTargetPriv() : m_app(NULL), m_cacheEntry(NULL), m_Config(NULL), m_conf(NULL), m_mapper(NULL) {}
857
858 ShibTargetPriv::~ShibTargetPriv()
859 {
860     if (m_cacheEntry) {
861         m_cacheEntry->unlock();
862         m_cacheEntry = NULL;
863     }
864
865     if (m_mapper) {
866         m_mapper->unlock();
867         m_mapper = NULL;
868     }
869     
870     if (m_conf) {
871         m_conf->unlock();
872         m_conf = NULL;
873     }
874
875     m_app = NULL;
876     m_Config = NULL;
877 }
878
879 void ShibTargetPriv::get_application(ShibTarget* st, const string& protocol, const string& hostname, int port, const string& uri)
880 {
881   if (m_app)
882     return;
883
884   // XXX: Do we need to keep conf and mapper locked while we hold m_app?
885   // TODO: No, should be able to hold the conf but release the mapper.
886
887   // We lock the configuration system for the duration.
888   m_conf=m_Config->getINI();
889   m_conf->lock();
890     
891   // Map request to application and content settings.
892   m_mapper=m_conf->getRequestMapper();
893   m_mapper->lock();
894
895   // Obtain the application settings from the parsed URL
896   m_settings = m_mapper->getSettings(st);
897
898   // Now find the application from the URL settings
899   pair<bool,const char*> application_id=m_settings.first->getString("applicationId");
900   m_app=m_conf->getApplication(application_id.second);
901   if (!m_app) {
902     m_mapper->unlock();
903     m_mapper = NULL;
904     m_conf->unlock();
905     m_conf = NULL;
906     throw ConfigurationException("Unable to map request to application settings, check configuration.");
907   }
908
909   // Compute the full target URL
910   st->m_url = protocol + "://" + hostname;
911   if ((protocol == "http" && port != 80) || (protocol == "https" && port != 443)) {
912         ostringstream portstr;
913         portstr << port;
914     st->m_url += ":" + portstr.str();
915   }
916   st->m_url += uri;
917 }
918
919 void* ShibTargetPriv::sendError(ShibTarget* st, const char* page, ShibMLP &mlp)
920 {
921     ShibTarget::header_t hdrs[] = {
922         ShibTarget::header_t("Expires","01-Jan-1997 12:00:00 GMT"),
923         ShibTarget::header_t("Cache-Control","private,no-store,no-cache")
924         };
925     
926     const IPropertySet* props=m_app->getPropertySet("Errors");
927     if (props) {
928         pair<bool,const char*> p=props->getString(page);
929         if (p.first) {
930             ifstream infile(p.second);
931             if (!infile.fail()) {
932                 const char* res = mlp.run(infile,props);
933                 if (res)
934                     return st->sendPage(res, 200, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2));
935             }
936         }
937         else if (!strcmp(page,"access"))
938             return st->sendPage("Access Denied", 403, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2));
939     }
940
941     string errstr = string("sendError could not process error template (") + page + ") for application (";
942     errstr += m_app->getId();
943     errstr += ")";
944     st->log(ShibTarget::LogLevelError, errstr);
945     return st->sendPage(
946         "Internal Server Error. Please contact the site administrator.", 500, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2)
947         );
948 }
949
950 void ShibTargetPriv::clearHeaders(ShibTarget* st)
951 {
952     // Clear invariant stuff.
953     st->clearHeader("Shib-Origin-Site");
954     st->clearHeader("Shib-Identity-Provider");
955     st->clearHeader("Shib-Authentication-Method");
956     st->clearHeader("Shib-NameIdentifier-Format");
957     st->clearHeader("Shib-Attributes");
958     st->clearHeader("Shib-Application-ID");
959
960     // Clear out the list of mapped attributes
961     Iterator<IAAP*> provs=m_app->getAAPProviders();
962     while (provs.hasNext()) {
963         IAAP* aap=provs.next();
964         Locker locker(aap);
965         Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
966         while (rules.hasNext()) {
967             const char* header=rules.next()->getHeader();
968             if (header)
969                 st->clearHeader(header);
970         }
971     }
972 }
973
974 pair<bool,void*> ShibTargetPriv::dispatch(
975     ShibTarget* st,
976     const IPropertySet* config,
977     bool isHandler,
978     const char* forceType
979     ) const
980 {
981     pair<bool,const char*> binding=forceType ? make_pair(true,forceType) : config->getString("Binding");
982     if (binding.first) {
983         auto_ptr<IPlugIn> plugin(SAMLConfig::getConfig().getPlugMgr().newPlugin(binding.second,config->getElement()));
984         IHandler* handler=dynamic_cast<IHandler*>(plugin.get());
985         if (!handler)
986             throw UnsupportedProfileException(
987                 "Plugin for binding ($1) does not implement IHandler interface.",params(1,binding.second)
988                 );
989         return handler->run(st,config,isHandler);
990     }
991     throw UnsupportedProfileException("Handler element is missing Binding attribute to determine what handler to run.");
992 }