9f6545c4f406954db6886930ddb721b78375f7c7
[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         pair<bool,void*> hret;
345         Iterator<const IHandler*> handlers=m_priv->m_app->getHandlers(targetURL + strlen(handlerURL));
346
347         if (handlers.size()==0)
348             throw SAMLException("Shibboleth handler invoked at an unconfigured location.");
349
350         while (handlers.hasNext()) {
351             const IHandler* handler=handlers.next();
352
353             if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SAML2META_NS,SHIBT_L(AssertionConsumerService)))
354                 procState = "Session Creation Error";
355             else if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(SessionInitiator)))
356                 procState = "Session Initiator Error";
357             else if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SAML2META_NS,SHIBT_L(SingleLogoutService)))
358                 procState = "Session Termination Error";
359             else if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(DiagnosticService)))
360                 procState = "Diagnostics Error";
361             else
362                 procState = "Extension Service Error";
363             hret=handler->run(this);
364
365             // Did the handler run successfully?
366             if (hret.first)
367                 return hret;
368         }
369         
370         throw SAMLException("Configured Shibboleth handler(s) failed to fully process the request.");
371     }
372     catch (MetadataException& e) {
373         mlp.insert(e);
374         // See if a metadata error page is installed.
375         const IPropertySet* props=m_priv->m_app->getPropertySet("Errors");
376         if (props) {
377             pair<bool,const char*> p=props->getString("metadata");
378             if (p.first) {
379                 mlp.insert("errorType", procState);
380                 if (targetURL)
381                     mlp.insert("requestURL", targetURL);
382                 return make_pair(true,m_priv->sendError(this,"metadata", mlp));
383             }
384         }
385     }
386     catch (SAMLException& e) {
387         mlp.insert(e);
388     }
389 #ifndef _DEBUG
390     catch (...) {
391         mlp.insert("errorText", "Caught an unknown exception.");
392     }
393 #endif
394
395     // If we get here then we've got an error.
396     mlp.insert("errorType", procState);
397
398     if (targetURL)
399         mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
400
401     return make_pair(true,m_priv->sendError(this,"session", mlp));
402 }
403
404 pair<bool,void*> ShibTarget::doCheckAuthZ(void)
405 {
406 #ifdef _DEBUG
407     saml::NDC ndc("doCheckAuthZ");
408 #endif
409
410     ShibMLP mlp;
411     const char* procState = "Authorization Processing Error";
412     const char* targetURL = m_url.c_str();
413
414     try {
415         if (!m_priv->m_app)
416             throw ConfigurationException("System uninitialized, application did not supply request information.");
417
418         // Three settings dictate how to proceed.
419         pair<bool,const char*> authType = m_priv->m_settings.first->getString("authType");
420         pair<bool,bool> requireSession = m_priv->m_settings.first->getBool("requireSession");
421         pair<bool,const char*> requireSessionWith = m_priv->m_settings.first->getString("requireSessionWith");
422
423         // If no session is required AND the AuthType (an Apache-derived concept) isn't shibboleth,
424         // then we ignore this request and consider it unprotected. Apache might lie to us if
425         // ShibBasicHijack is on, but that's up to it.
426         if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first &&
427 #ifdef HAVE_STRCASECMP
428                 (!authType.first || strcasecmp(authType.second,"shibboleth")))
429 #else
430                 (!authType.first || _stricmp(authType.second,"shibboleth")))
431 #endif
432             return pair<bool,void*>(true,returnDecline());
433
434         // Do we have an access control plugin?
435         if (m_priv->m_settings.second) {
436                 
437                 if (!m_priv->m_cacheEntry) {
438                     // No data yet, so we may need to try and get the session.
439                         pair<string,const char*> shib_cookie=getCookieNameProps("_shibsession_");
440                         const char *session_id = getCookie(shib_cookie.first);
441                     try {
442                                 if (session_id && *session_id) {
443                         m_priv->m_cacheEntry=m_priv->m_conf->getSessionCache()->find(
444                             session_id,
445                             m_priv->m_app,
446                             m_remote_addr.c_str()
447                             );
448                                 }
449                     }
450                     catch (SAMLException&) {
451                         log(LogLevelError, "doCheckAuthZ: unable to obtain session information to pass to access control provider");
452                     }
453                 }
454         
455             Locker acllock(m_priv->m_settings.second);
456             if (m_priv->m_settings.second->authorized(this,m_priv->m_cacheEntry)) {
457                 // Let the caller decide how to proceed.
458                 log(LogLevelDebug, "doCheckAuthZ: access control provider granted access");
459                 return pair<bool,void*>(false,NULL);
460             }
461             else {
462                 log(LogLevelWarn, "doCheckAuthZ: access control provider denied access");
463                 if (targetURL)
464                     mlp.insert("requestURL", targetURL);
465                 return make_pair(true,m_priv->sendError(this, "access", mlp));
466             }
467         }
468         else
469             return make_pair(true,returnDecline());
470     }
471     catch (SAMLException& e) {
472         mlp.insert(e);
473     }
474 #ifndef _DEBUG
475     catch (...) {
476         mlp.insert("errorText", "Caught an unknown exception.");
477     }
478 #endif
479
480     // If we get here then we've got an error.
481     mlp.insert("errorType", procState);
482
483     if (targetURL)
484         mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
485
486     return make_pair(true,m_priv->sendError(this, "access", mlp));
487 }
488
489 pair<bool,void*> ShibTarget::doExportAssertions(bool requireSession)
490 {
491 #ifdef _DEBUG
492     saml::NDC ndc("doExportAssertions");
493 #endif
494
495     ShibMLP mlp;
496     const char* procState = "Attribute Processing Error";
497     const char* targetURL = m_url.c_str();
498
499     try {
500         if (!m_priv->m_app)
501             throw ConfigurationException("System uninitialized, application did not supply request information.");
502
503         if (!m_priv->m_cacheEntry) {
504             // No data yet, so we need to get the session. This can only happen
505             // if the call to doCheckAuthn doesn't happen in the same object lifetime.
506                 pair<string,const char*> shib_cookie=getCookieNameProps("_shibsession_");
507                 const char *session_id = getCookie(shib_cookie.first);
508             try {
509                         if (session_id && *session_id) {
510                     m_priv->m_cacheEntry=m_priv->m_conf->getSessionCache()->find(
511                         session_id,
512                         m_priv->m_app,
513                         m_remote_addr.c_str()
514                         );
515                         }
516             }
517             catch (SAMLException&) {
518                 log(LogLevelError, "unable to obtain session information to export into request headers");
519                 // If we have to have a session, then this is a fatal error.
520                 if (requireSession)
521                         throw;
522             }
523         }
524
525                 // Still no data?
526         if (!m_priv->m_cacheEntry) {
527                 if (requireSession)
528                         throw InvalidSessionException("Unable to obtain session information for request.");
529                 else
530                         return pair<bool,void*>(false,NULL);    // just bail silently
531         }
532         
533         // Extract data from session.
534         pair<const char*,const SAMLSubject*> sub=m_priv->m_cacheEntry->getSubject(false,true);
535         pair<const char*,const SAMLResponse*> unfiltered=m_priv->m_cacheEntry->getTokens(true,false);
536         pair<const char*,const SAMLResponse*> filtered=m_priv->m_cacheEntry->getTokens(false,true);
537
538         // Maybe export the tokens.
539         pair<bool,bool> exp=m_priv->m_settings.first->getBool("exportAssertion");
540         if (exp.first && exp.second && unfiltered.first && *unfiltered.first) {
541             unsigned int outlen;
542             XMLByte* serialized =
543                 Base64::encode(reinterpret_cast<XMLByte*>((char*)unfiltered.first), XMLString::stringLen(unfiltered.first), &outlen);
544             XMLByte *pos, *pos2;
545             for (pos=serialized, pos2=serialized; *pos2; pos2++)
546                 if (isgraph(*pos2))
547                     *pos++=*pos2;
548             *pos=0;
549             setHeader("Shib-Attributes", reinterpret_cast<char*>(serialized));
550             XMLString::release(&serialized);
551         }
552
553         // Export the SAML AuthnMethod and the origin site name, and possibly the NameIdentifier.
554         setHeader("Shib-Origin-Site", m_priv->m_cacheEntry->getProviderId());
555         setHeader("Shib-Identity-Provider", m_priv->m_cacheEntry->getProviderId());
556         setHeader("Shib-Authentication-Method", m_priv->m_cacheEntry->getAuthnContext());
557         
558         // Get the AAP providers, which contain the attribute policy info.
559         Iterator<IAAP*> provs=m_priv->m_app->getAAPProviders();
560
561         // Export NameID?
562         while (provs.hasNext()) {
563             IAAP* aap=provs.next();
564             Locker locker(aap);
565             const XMLCh* format = sub.second->getNameIdentifier()->getFormat();
566             const IAttributeRule* rule=aap->lookup(format ? format : SAMLNameIdentifier::UNSPECIFIED);
567             if (rule && rule->getHeader()) {
568                 auto_ptr_char form(format ? format : SAMLNameIdentifier::UNSPECIFIED);
569                 auto_ptr_char nameid(sub.second->getNameIdentifier()->getName());
570                 setHeader("Shib-NameIdentifier-Format", form.get());
571                 if (!strcmp(rule->getHeader(),"REMOTE_USER"))
572                     setRemoteUser(nameid.get());
573                 else
574                     setHeader(rule->getHeader(), nameid.get());
575             }
576         }
577         
578         setHeader("Shib-Application-ID", m_priv->m_app->getId());
579     
580         // Export the attributes.
581         Iterator<SAMLAssertion*> a_iter(filtered.second ? filtered.second->getAssertions() : EMPTY(SAMLAssertion*));
582         while (a_iter.hasNext()) {
583             SAMLAssertion* assert=a_iter.next();
584             Iterator<SAMLStatement*> statements=assert->getStatements();
585             while (statements.hasNext()) {
586                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
587                 if (!astate)
588                     continue;
589                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
590                 while (attrs.hasNext()) {
591                     SAMLAttribute* attr=attrs.next();
592             
593                     // Are we supposed to export it?
594                     provs.reset();
595                     while (provs.hasNext()) {
596                         IAAP* aap=provs.next();
597                         Locker locker(aap);
598                         const IAttributeRule* rule=aap->lookup(attr->getName(),attr->getNamespace());
599                         if (!rule || !rule->getHeader())
600                             continue;
601                     
602                         Iterator<string> vals=attr->getSingleByteValues();
603                         if (!strcmp(rule->getHeader(),"REMOTE_USER") && vals.hasNext())
604                             setRemoteUser(vals.next());
605                         else {
606                             int it=0;
607                             string header = getHeader(rule->getHeader());
608                             if (!header.empty())
609                                 it++;
610                             for (; vals.hasNext(); it++) {
611                                 string value = vals.next();
612                                 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
613                                         pos != string::npos;
614                                         pos = value.find_first_of(";", pos)) {
615                                     value.insert(pos, "\\");
616                                     pos += 2;
617                                 }
618                                 if (it)
619                                     header += ";";
620                                 header += value;
621                             }
622                             setHeader(rule->getHeader(), header);
623                         }
624                     }
625                 }
626             }
627         }
628     
629         return pair<bool,void*>(false,NULL);
630     }
631     catch (SAMLException& e) {
632         mlp.insert(e);
633     }
634 #ifndef _DEBUG
635     catch (...) {
636         mlp.insert("errorText", "Caught an unknown exception.");
637     }
638 #endif
639
640     // If we get here then we've got an error.
641     mlp.insert("errorType", procState);
642
643     if (targetURL)
644         mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
645
646     return make_pair(true,m_priv->sendError(this, "rm", mlp));
647 }
648
649 const char* ShibTarget::getRequestParameter(const char* param, size_t index) const
650 {
651     if (!m_priv->m_cgiParser)
652         m_priv->m_cgiParser=new CgiParse(this);
653     
654     pair<CgiParse::walker,CgiParse::walker> bounds=m_priv->m_cgiParser->get_values(param);
655     
656     // Advance to the right index.
657     while (index && bounds.first!=bounds.second) {
658         index--;
659         bounds.first++;
660     }
661
662     return (bounds.first==bounds.second) ? NULL : bounds.first->second;
663 }
664
665 const char* ShibTarget::getCookie(const string& name) const
666 {
667     if (m_priv->m_cookieMap.empty()) {
668         string cookies=getCookies();
669
670         string::size_type pos=0,cname,namelen,val,vallen;
671         while (pos !=string::npos && pos < cookies.length()) {
672             while (isspace(cookies[pos])) pos++;
673             cname=pos;
674             pos=cookies.find_first_of("=",pos);
675             if (pos == string::npos)
676                 break;
677             namelen=pos-cname;
678             pos++;
679             if (pos==cookies.length())
680                 break;
681             val=pos;
682             pos=cookies.find_first_of(";",pos);
683             if (pos != string::npos) {
684                 vallen=pos-val;
685                 pos++;
686                 m_priv->m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val,vallen)));
687             }
688             else
689                 m_priv->m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val)));
690         }
691     }
692     map<string,string>::const_iterator lookup=m_priv->m_cookieMap.find(name);
693     return (lookup==m_priv->m_cookieMap.end()) ? NULL : lookup->second.c_str();
694 }
695
696 pair<string,const char*> ShibTarget::getCookieNameProps(const char* prefix) const
697 {
698     static const char* defProps="; path=/";
699     
700     const IPropertySet* props=m_priv->m_app ? m_priv->m_app->getPropertySet("Sessions") : NULL;
701     if (props) {
702         pair<bool,const char*> p=props->getString("cookieProps");
703         if (!p.first)
704             p.second=defProps;
705         pair<bool,const char*> p2=props->getString("cookieName");
706         if (p2.first)
707             return make_pair(string(prefix) + p2.second,p.second);
708         return make_pair(string(prefix) + m_priv->m_app->getHash(),p.second);
709     }
710     
711     // Shouldn't happen, but just in case..
712     return pair<string,const char*>(prefix,defProps);
713 }
714
715 string ShibTarget::getHandlerURL(const char* resource) const
716 {
717     if (!m_priv->m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
718         return m_priv->m_handlerURL;
719         
720     if (!m_priv->m_app)
721         throw ConfigurationException("Internal error in ShibTargetPriv::getHandlerURL, missing application pointer.");
722
723     bool ssl_only=false;
724     const char* handler=NULL;
725     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
726     if (props) {
727         pair<bool,bool> p=props->getBool("handlerSSL");
728         if (p.first)
729             ssl_only=p.second;
730         pair<bool,const char*> p2=props->getString("handlerURL");
731         if (p2.first)
732             handler=p2.second;
733     }
734     
735     // Should never happen...
736     if (!handler || (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)))
737         throw ConfigurationException(
738             "Invalid handlerURL property ($1) in Application ($2)",
739             params(2, handler ? handler : "null", m_priv->m_app->getId())
740             );
741
742     // The "handlerURL" property can be in one of three formats:
743     //
744     // 1) a full URI:       http://host/foo/bar
745     // 2) a hostless URI:   http:///foo/bar
746     // 3) a relative path:  /foo/bar
747     //
748     // #  Protocol  Host        Path
749     // 1  handler   handler     handler
750     // 2  handler   resource    handler
751     // 3  resource  resource    handler
752     //
753     // note: if ssl_only is true, make sure the protocol is https
754
755     const char* path = NULL;
756
757     // Decide whether to use the handler or the resource for the "protocol"
758     const char* prot;
759     if (*handler != '/') {
760         prot = handler;
761     }
762     else {
763         prot = resource;
764         path = handler;
765     }
766
767     // break apart the "protocol" string into protocol, host, and "the rest"
768     const char* colon=strchr(prot,':');
769     colon += 3;
770     const char* slash=strchr(colon,'/');
771     if (!path)
772         path = slash;
773
774     // Compute the actual protocol and store in member.
775     if (ssl_only)
776         m_priv->m_handlerURL.assign("https://");
777     else
778         m_priv->m_handlerURL.assign(prot, colon-prot);
779
780     // create the "host" from either the colon/slash or from the target string
781     // If prot == handler then we're in either #1 or #2, else #3.
782     // If slash == colon then we're in #2.
783     if (prot != handler || slash == colon) {
784         colon = strchr(resource, ':');
785         colon += 3;      // Get past the ://
786         slash = strchr(colon, '/');
787     }
788     string host(colon, (slash ? slash-colon : strlen(colon)));
789
790     // Build the handler URL
791     m_priv->m_handlerURL+=host + path;
792     return m_priv->m_handlerURL;
793 }
794
795 void ShibTarget::log(ShibLogLevel level, const string& msg)
796 {
797     Category::getInstance("shibtarget.ShibTarget").log(
798         (level == LogLevelDebug ? Priority::DEBUG :
799         (level == LogLevelInfo ? Priority::INFO :
800         (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
801         msg
802     );
803 }
804
805 const IApplication* ShibTarget::getApplication() const
806 {
807     return m_priv->m_app;
808 }
809
810 const IConfig* ShibTarget::getConfig() const
811 {
812     return m_priv->m_conf;
813 }
814
815 void* ShibTarget::returnDecline(void)
816 {
817     return NULL;
818 }
819
820 void* ShibTarget::returnOK(void)
821 {
822     return NULL;
823 }
824
825 static char x2c(char *what)
826 {
827     register char digit;
828
829     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
830     digit *= 16;
831     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
832     return(digit);
833 }
834
835 void ShibTarget::url_decode(char* s)
836 {
837     register int x,y;
838
839     for(x=0,y=0;s[y];++x,++y)
840     {
841         if((s[x] = s[y]) == '%')
842         {
843             s[x] = x2c(&s[y+1]);
844             y+=2;
845         }
846     }
847     s[x] = '\0';
848 }
849
850 static inline char hexchar(unsigned short s)
851 {
852     return (s<=9) ? ('0' + s) : ('A' + s - 10);
853 }
854
855 string ShibTarget::url_encode(const char* s)
856 {
857     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
858
859     string ret;
860     for (; *s; s++) {
861         if (strchr(badchars,*s) || *s<=0x20 || *s>=0x7F) {
862             ret+='%';
863         ret+=hexchar(*s >> 4);
864         ret+=hexchar(*s & 0x0F);
865         }
866         else
867             ret+=*s;
868     }
869     return ret;
870 }
871
872 /*************************************************************************
873  * Shib Target Private implementation
874  */
875
876 ShibTargetPriv::ShibTargetPriv()
877     : m_app(NULL), m_mapper(NULL), m_conf(NULL), m_Config(NULL), m_cacheEntry(NULL), m_cgiParser(NULL) {}
878
879 ShibTargetPriv::~ShibTargetPriv()
880 {
881     if (m_cacheEntry) {
882         m_cacheEntry->unlock();
883         m_cacheEntry = NULL;
884     }
885
886     if (m_mapper) {
887         m_mapper->unlock();
888         m_mapper = NULL;
889     }
890     
891     if (m_conf) {
892         m_conf->unlock();
893         m_conf = NULL;
894     }
895
896     delete m_cgiParser;
897     m_app = NULL;
898     m_Config = NULL;
899 }
900
901 void ShibTargetPriv::get_application(ShibTarget* st, const string& protocol, const string& hostname, int port, const string& uri)
902 {
903   if (m_app)
904     return;
905
906   // XXX: Do we need to keep conf and mapper locked while we hold m_app?
907   // TODO: No, should be able to hold the conf but release the mapper.
908
909   // We lock the configuration system for the duration.
910   m_conf=m_Config->getINI();
911   m_conf->lock();
912     
913   // Map request to application and content settings.
914   m_mapper=m_conf->getRequestMapper();
915   m_mapper->lock();
916
917   // Obtain the application settings from the parsed URL
918   m_settings = m_mapper->getSettings(st);
919
920   // Now find the application from the URL settings
921   pair<bool,const char*> application_id=m_settings.first->getString("applicationId");
922   m_app=m_conf->getApplication(application_id.second);
923   if (!m_app) {
924     m_mapper->unlock();
925     m_mapper = NULL;
926     m_conf->unlock();
927     m_conf = NULL;
928     throw ConfigurationException("Unable to map request to application settings, check configuration.");
929   }
930
931   // Compute the full target URL
932   st->m_url = protocol + "://" + hostname;
933   if ((protocol == "http" && port != 80) || (protocol == "https" && port != 443)) {
934         ostringstream portstr;
935         portstr << port;
936     st->m_url += ":" + portstr.str();
937   }
938   st->m_url += uri;
939 }
940
941 void* ShibTargetPriv::sendError(ShibTarget* st, const char* page, ShibMLP &mlp)
942 {
943     ShibTarget::header_t hdrs[] = {
944         ShibTarget::header_t("Expires","01-Jan-1997 12:00:00 GMT"),
945         ShibTarget::header_t("Cache-Control","private,no-store,no-cache")
946         };
947     
948     const IPropertySet* props=m_app->getPropertySet("Errors");
949     if (props) {
950         pair<bool,const char*> p=props->getString(page);
951         if (p.first) {
952             ifstream infile(p.second);
953             if (!infile.fail()) {
954                 const char* res = mlp.run(infile,props);
955                 if (res)
956                     return st->sendPage(res, 200, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2));
957             }
958         }
959         else if (!strcmp(page,"access"))
960             return st->sendPage("Access Denied", 403, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2));
961     }
962
963     string errstr = string("sendError could not process error template (") + page + ") for application (";
964     errstr += m_app->getId();
965     errstr += ")";
966     st->log(ShibTarget::LogLevelError, errstr);
967     return st->sendPage(
968         "Internal Server Error. Please contact the site administrator.", 500, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2)
969         );
970 }
971
972 void ShibTargetPriv::clearHeaders(ShibTarget* st)
973 {
974     // Clear invariant stuff.
975     st->clearHeader("Shib-Origin-Site");
976     st->clearHeader("Shib-Identity-Provider");
977     st->clearHeader("Shib-Authentication-Method");
978     st->clearHeader("Shib-NameIdentifier-Format");
979     st->clearHeader("Shib-Attributes");
980     st->clearHeader("Shib-Application-ID");
981
982     // Clear out the list of mapped attributes
983     Iterator<IAAP*> provs=m_app->getAAPProviders();
984     while (provs.hasNext()) {
985         IAAP* aap=provs.next();
986         Locker locker(aap);
987         Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
988         while (rules.hasNext()) {
989             const char* header=rules.next()->getHeader();
990             if (header)
991                 st->clearHeader(header);
992         }
993     }
994 }
995
996 /*************************************************************************
997  * CGI Parser implementation
998  */
999
1000 CgiParse::CgiParse(const ShibTarget* st)
1001 {
1002     const char* pch=NULL;
1003     if (!strcmp(st->getRequestMethod(),"POST"))
1004         pch=st->getRequestBody();
1005     else
1006         pch=st->getQueryString();
1007     size_t cl=pch ? strlen(pch) : 0;
1008     
1009         
1010     while (cl && pch) {
1011         char *name;
1012         char *value;
1013         value=fmakeword('&',&cl,&pch);
1014         plustospace(value);
1015         ShibTarget::url_decode(value);
1016         name=makeword(value,'=');
1017         kvp_map.insert(pair<string,char*>(name,value));
1018         free(name);
1019     }
1020 }
1021
1022 CgiParse::~CgiParse()
1023 {
1024     for (multimap<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
1025         free(i->second);
1026 }
1027
1028 pair<CgiParse::walker,CgiParse::walker> CgiParse::get_values(const char* name) const
1029 {
1030     return kvp_map.equal_range(name);
1031 }
1032
1033 /* Parsing routines modified from NCSA source. */
1034 char* CgiParse::makeword(char *line, char stop)
1035 {
1036     int x = 0,y;
1037     char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
1038
1039     for(x=0;((line[x]) && (line[x] != stop));x++)
1040         word[x] = line[x];
1041
1042     word[x] = '\0';
1043     if(line[x])
1044         ++x;
1045     y=0;
1046
1047     while(line[x])
1048       line[y++] = line[x++];
1049     line[y] = '\0';
1050     return word;
1051 }
1052
1053 char* CgiParse::fmakeword(char stop, size_t *cl, const char** ppch)
1054 {
1055     int wsize;
1056     char *word;
1057     int ll;
1058
1059     wsize = 1024;
1060     ll=0;
1061     word = (char *) malloc(sizeof(char) * (wsize + 1));
1062
1063     while(1)
1064     {
1065         word[ll] = *((*ppch)++);
1066         if(ll==wsize-1)
1067         {
1068             word[ll+1] = '\0';
1069             wsize+=1024;
1070             word = (char *)realloc(word,sizeof(char)*(wsize+1));
1071         }
1072         --(*cl);
1073         if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
1074         {
1075             if(word[ll] != stop)
1076                 ll++;
1077             word[ll] = '\0';
1078             return word;
1079         }
1080         ++ll;
1081     }
1082 }
1083
1084 void CgiParse::plustospace(char *str)
1085 {
1086     register int x;
1087
1088     for(x=0;str[x];x++)
1089         if(str[x] == '+') str[x] = ' ';
1090 }