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