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