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