Added strcasecmp check/redefine at top.
[shibboleth/cpp-sp.git] / shib-target / shib-target.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 /*
51  * shib-target.cpp -- The ShibTarget class, a superclass for general
52  *                    target code
53  *
54  * Created by:  Derek Atkins <derek@ihtfp.com>
55  *
56  * $Id$
57  */
58
59 #include "internal.h"
60
61 #ifdef HAVE_UNISTD_H
62 # include <unistd.h>
63 #endif
64
65 #include <sstream>
66 #include <fstream>
67 #include <stdexcept>
68
69 #include <shib/shib-threads.h>
70 #include <log4cpp/Category.hh>
71 #include <log4cpp/PropertyConfigurator.hh>
72 #include <xercesc/util/Base64.hpp>
73 #include <xercesc/util/regx/RegularExpression.hpp>
74
75 #ifndef HAVE_STRCASECMP
76 # define strcasecmp stricmp
77 #endif
78
79 using namespace std;
80 using namespace saml;
81 using namespace shibboleth;
82 using namespace shibtarget;
83 using namespace log4cpp;
84
85 namespace shibtarget {
86   class CgiParse
87   {
88   public:
89     CgiParse(const char* data, unsigned int len);
90     ~CgiParse();
91     const char* get_value(const char* name) const;
92     
93   private:
94     char * fmakeword(char stop, unsigned int *cl, const char** ppch);
95     char * makeword(char *line, char stop);
96     void plustospace(char *str);
97     char x2c(char *what);
98     void url_decode(char *url);
99
100     map<string,char*> kvp_map;
101   };
102
103   class ShibTargetPriv
104   {
105   public:
106     ShibTargetPriv();
107     ~ShibTargetPriv();
108
109     string url_encode(const char* s);
110     void get_application(string protocol, string hostname, int port, string uri);
111     void* sendError(ShibTarget* st, string page, ShibMLP &mlp);
112     const char *getSessionId(ShibTarget* st);
113     bool get_assertions(ShibTarget *st, const char *session_id, ShibMLP &mlp);
114
115     IRequestMapper::Settings m_settings;
116     const IApplication *m_app;
117     string m_cookieName;
118     string m_shireURL;
119     string m_authnRequest;
120     CgiParse* m_parser;
121
122     const char *session_id;
123     string m_cookies;
124
125     vector<SAMLAssertion*> m_assertions;
126     SAMLAuthenticationStatement* m_sso_statement;
127     
128     // These are the actual request parameters set via the init method.
129     string m_url;
130     string m_method;
131     string m_protocol;
132     string m_content_type;
133     string m_remote_addr;
134
135     ShibTargetConfig* m_Config;
136
137   private:
138     IConfig* m_conf;
139     IRequestMapper* m_mapper;
140   };
141 }
142
143
144 /*************************************************************************
145  * Shib Target implementation
146  */
147
148 ShibTarget::ShibTarget(void) : m_priv(NULL)
149 {
150   m_priv = new ShibTargetPriv();
151 }
152
153 ShibTarget::ShibTarget(const IApplication *app) : m_priv(NULL)
154 {
155   m_priv = new ShibTargetPriv();
156   m_priv->m_app = app;
157 }
158
159 ShibTarget::~ShibTarget(void)
160 {
161   if (m_priv) delete m_priv;
162 }
163
164 void ShibTarget::init(ShibTargetConfig *config,
165                       string protocol, string hostname, int port,
166                       string uri, string content_type, string remote_host,
167                       string method)
168 {
169   saml::NDC ndc("ShibTarget::init");
170
171   if (m_priv->m_app)
172     throw runtime_error("ShibTarget Already Initialized");
173   if (!config)
174     throw runtime_error("config is NULL.  Oops.");
175
176   m_priv->m_protocol = protocol;
177   m_priv->m_content_type = content_type;
178   m_priv->m_remote_addr = remote_host;
179   m_priv->m_Config = config;
180   m_priv->m_method = method;
181   m_priv->get_application(protocol, hostname, port, uri);
182 }
183
184
185 // These functions implement the server-agnostic shibboleth engine
186 // The web server modules implement a subclass and then call into 
187 // these methods once they instantiate their request object.
188 pair<bool,void*>
189 ShibTarget::doCheckAuthN(bool requireSessionFlag)
190 {
191   saml::NDC ndc("ShibTarget::doCheckAuthN");
192
193   const char *targetURL = NULL;
194   const char *procState = "Process Initialization Error";
195   ShibMLP mlp;
196
197   try {
198     if (! m_priv->m_app)
199       throw ShibTargetException(SHIBRPC_OK, "ShibTarget Uninitialized.  Application did not supply request information.");
200
201     targetURL = m_priv->m_url.c_str();
202     const char *shireURL = getShireURL(targetURL);
203     if (! shireURL)
204       throw ShibTargetException(SHIBRPC_OK, "Cannot map target URL to Shire URL.  Check configuration");
205
206     if (strstr(targetURL,shireURL))
207       return doHandlePOST();
208
209     string auth_type = getAuthType();
210     if (strcasecmp(auth_type.c_str(),"shibboleth"))
211       return pair<bool,void*>(true,returnDecline());
212
213     pair<bool,bool> requireSession =
214       m_priv->m_settings.first->getBool("requireSession");
215     if (!requireSession.first || !requireSession.second)
216       if (requireSessionFlag)
217         requireSession.second=true;
218
219     const char *session_id = m_priv->getSessionId(this);
220     
221     if (!session_id || !*session_id) {
222       // No session.  Maybe that's acceptable?
223
224       if (!requireSession.second)
225         return pair<bool,void*>(true,returnOK());
226
227       // No cookie, but we require a session.  Generate an AuthnRequest.
228       return pair<bool,void*>(true,sendRedirect(getAuthnRequest(targetURL)));
229     }
230
231     procState = "Session Processing Error";
232     RPCError *status = sessionIsValid(session_id, m_priv->m_remote_addr.c_str());
233
234     if (status->isError()) {
235
236       // If no session is required, bail now.
237       if (!requireSession.second)
238         return pair<bool,void*>(true, returnOK());
239                            // XXX: Or should this be DECLINED?
240                            // Has to be OK because DECLINED will just cause Apache
241                            // to fail when it can't locate anything to process the
242                            // AuthType.  No session plus requireSession false means
243                            // do not authenticate the user at this time.
244       else if (status->isRetryable()) {
245         // Session is invalid but we can retry the auth -- generate an AuthnRequest
246         delete status;
247         return pair<bool,void*>(true,sendRedirect(getAuthnRequest(targetURL)));
248
249       } else {
250
251         string er = "Unretryable error: " ;
252         er += status->getText();
253         log(LogLevelError, er);
254         mlp.insert(*status);
255         delete status;
256         goto out;
257       }
258
259       delete status;
260       
261     }
262
263     // We're done.  Everything is okay.  Nothing to report.  Nothing to do..
264     // Let the caller decide how to proceed.
265     log(LogLevelInfo, "doCheckAuthN Succeeded\n");
266     return pair<bool,void*>(false,NULL);
267
268   } catch (ShibTargetException &e) {
269     mlp.insert("errorText", e.what());
270
271 #ifndef _DEBUG
272   } catch (...) {
273     mlp.insert("errorText", "Unexpected Exception");
274 #endif
275   }
276
277   // If we get here then we've got an error.
278   mlp.insert("errorType", procState);
279   mlp.insert("errorDesc", "An error occurred while processing your request.");
280
281  out:
282   if (targetURL)
283     mlp.insert("requestURL", targetURL);
284
285   return pair<bool,void*>(true,m_priv->sendError(this, "shire", mlp));
286 }
287
288 pair<bool,void*>
289 ShibTarget::doHandlePOST(void)
290 {
291   saml::NDC ndc("ShibTarget::doHandlePOST");
292
293   const char *targetURL = NULL;
294   const char *procState = "Session Creation Service Error";
295   ShibMLP mlp;
296
297   try {
298     if (! m_priv->m_app)
299       throw ShibTargetException(SHIBRPC_OK, "ShibTarget Uninitialized.  Application did not supply request information.");
300
301     targetURL = m_priv->m_url.c_str();
302     const char *shireURL = getShireURL(targetURL);
303
304     if (!shireURL)
305       throw ShibTargetException(SHIBRPC_OK, "doHandlePOST: unable to map request to a proper shireURL setting.  Check Configuration.");
306
307
308     // Make sure we only process the SHIRE requests.
309     if (!strstr(targetURL, shireURL))
310       return pair<bool,void*>(true, returnDecline());
311
312     const IPropertySet* sessionProps=m_priv->m_app->getPropertySet("Sessions");
313     if (!sessionProps)
314       throw ShibTargetException(SHIBRPC_OK, "doHandlePOST: unable to map request to application session settings.  Check configuration");
315
316     // this always returns something
317     pair<const char*,const char*> shib_cookie=getCookieNameProps();
318
319     // Process SHIRE request
320       
321     pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
322       
323     // Make sure this is SSL, if it should be
324     if ((!shireSSL.first || shireSSL.second) && m_priv->m_protocol == "https")
325       throw ShibTargetException(SHIBRPC_OK, "blocked non-SSL access to session creation service");
326
327     // If this is a GET, we manufacture an AuthnRequest.
328     if (!strcasecmp(m_priv->m_method.c_str(), "GET")) {
329       string args = getArgs();
330       const char* areq=args.empty() ? NULL : getLazyAuthnRequest(args.c_str());
331       if (!areq)
332         throw ShibTargetException(SHIBRPC_OK, "malformed GET arguments to request a new session");
333       return pair<bool,void*>(true, sendRedirect(areq));
334     }
335     else if (strcasecmp(m_priv->m_method.c_str(), "POST")) {
336       throw ShibTargetException(SHIBRPC_OK, "blocked non-POST to SHIRE POST processor");
337     }
338
339     // Make sure this POST is an appropriate content type
340     if (m_priv->m_content_type.empty() ||
341         strcasecmp(m_priv->m_content_type.c_str(),
342                    "application/x-www-form-urlencoded")) {
343       string er = string("blocked bad content-type to SHIRE POST processor: ") +
344         m_priv->m_content_type;
345       throw ShibTargetException(SHIBRPC_OK, er.c_str());
346     }
347         
348     // Read the POST Data
349     string cgistr = getPostData();
350
351     // Parse the submission.
352     pair<const char*,const char*> elements =
353       getFormSubmission(cgistr.c_str(),cgistr.length());
354     
355     // Make sure the SAML Response parameter exists
356     if (!elements.first || !*elements.first)
357       throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
358     
359     // Make sure the target parameter exists
360     if (!elements.second || !*elements.second)
361       throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
362     
363     // process the post
364     string cookie;
365     RPCError* status = sessionCreate(elements.first, m_priv->m_remote_addr.c_str(),
366                                      cookie);
367
368     if (status->isError()) {
369       char buf[25];
370       sprintf(buf, "(%d): ", status->getCode());
371       string er = string("doHandlePost() POST process failed ") + buf +
372                          status->getText();
373       log(LogLevelError, er);
374
375       if (status->isRetryable()) {
376         delete status;
377
378         return pair<bool,void*>(true, sendRedirect(getAuthnRequest(elements.second)));
379       }
380
381       // return this error to the user.
382       mlp.insert(*status);
383       delete status;
384       goto out;
385     }
386     delete status;
387
388     log(LogLevelDebug,
389         string("doHandlePost() POST process succeeded. New session: ") + cookie);
390
391     // We've got a good session, set the cookie...
392     cookie += shib_cookie.second;
393     setCookie(shib_cookie.first, cookie);
394
395     // ... and redirect to the target
396     return pair<bool,void*>(true, sendRedirect(elements.second));
397
398   } catch (ShibTargetException &e) {
399     mlp.insert("errorText", e.what());
400
401 #ifndef _DEBUG
402   } catch (...) {
403     mlp.insert("errorText", "Unexpected Exception");
404 #endif
405   }
406
407   // If we get here then we've got an error.
408   mlp.insert("errorType", procState);
409   mlp.insert("errorDesc", "An error occurred while processing your request.");
410
411  out:
412   if (targetURL)
413     mlp.insert("requestURL", targetURL);
414
415   return pair<bool,void*>(true,m_priv->sendError(this, "shire", mlp));
416 }
417
418 pair<bool,void*>
419 ShibTarget::doCheckAuthZ(void)
420 {
421   saml::NDC ndc("ShibTarget::doCheckAuthZ");
422
423   ShibMLP mlp;
424   const char *procState = "Authorization Processing Error";
425   const char *targetURL = NULL;
426   HTAccessInfo *ht = NULL;
427   HTGroupTable* grpstatus = NULL;
428
429   try {
430     if (! m_priv->m_app)
431       throw ShibTargetException(SHIBRPC_OK, "ShibTarget Uninitialized.  Application did not supply request information.");
432
433     targetURL = m_priv->m_url.c_str();
434     const char *session_id = m_priv->getSessionId(this);
435
436     // XXX: need to make sure that export assertions was already called.
437     //if (m_priv->get_assertions(this, session_id, mlp))
438     //goto out;
439
440     // Do we have an access control plugin?
441     if (m_priv->m_settings.second) {
442       Locker acllock(m_priv->m_settings.second);
443       if (!m_priv->m_settings.second->authorized(*m_priv->m_sso_statement,
444                                                  m_priv->m_assertions)) {
445         log(LogLevelError, "doCheckAuthZ: access control provider denied access");
446         goto out;
447       }
448     }
449
450     // Perform HTAccess Checks
451     ht = getAccessInfo();
452
453     // No Info means OK.  Just return
454     if (!ht)
455       return pair<bool,void*>(false, NULL);
456
457     vector<bool> auth_OK(ht->elements.size(), false);
458     bool method_restricted=false;
459     string remote_user = getRemoteUser();
460
461     #define CHECK_OK { \
462       if (ht->requireAll) { \
463         delete ht; \
464         if (grpstatus) delete grpstatus; \
465         return pair<bool,void*>(false, NULL); \
466       } \
467       auth_OK[x] = true; \
468       continue; \
469     }
470
471     for (int x = 0; x < ht->elements.size(); x++) {
472       auth_OK[x] = false;
473       HTAccessInfo::RequireLine *line = ht->elements[x];
474       if (! line->use_line)
475         continue;
476       method_restricted = true;
477
478       const char *w = line->tokens[0].c_str();
479
480       if (!strcasecmp(w,"Shibboleth")) {
481         // This is a dummy rule needed because Apache conflates authn and authz.
482         // Without some require rule, AuthType is ignored and no check_user hooks run.
483         CHECK_OK;
484       }
485       else if (!strcmp(w,"valid-user")) {
486         log(LogLevelDebug, "doCheckAuthZ accepting valid-user");
487         sleep(60);
488         CHECK_OK;
489       }
490       else if (!strcmp(w,"user") && !remote_user.empty()) {
491         bool regexp=false;
492         for (int i = 1; i < line->tokens.size(); i++) {
493           w = line->tokens[i].c_str();
494           if (*w == '~') {
495             regexp = true;
496             continue;
497           }
498                 
499           if (regexp) {
500             try {
501               // To do regex matching, we have to convert from UTF-8.
502               auto_ptr<XMLCh> trans(fromUTF8(w));
503               RegularExpression re(trans.get());
504               auto_ptr<XMLCh> trans2(fromUTF8(remote_user.c_str()));
505               if (re.matches(trans2.get())) {
506                 log(LogLevelDebug, string("doCheckAuthZ accepting user: ") + w);
507                 CHECK_OK;
508               }
509             }
510             catch (XMLException& ex) {
511               auto_ptr_char tmp(ex.getMessage());
512               log(LogLevelError, string("doCheckAuthZ caught exception while parsing regular expression (")
513                   + w + "): " + tmp.get());
514             }
515           }
516           else if (!strcmp(remote_user.c_str(), w)) {
517             log(LogLevelDebug, string("doCheckAuthZ accepting user: ") + w);
518             CHECK_OK;
519           }
520         }
521       }
522       else if (!strcmp(w,"group")) {
523         grpstatus = getGroupTable(remote_user);
524
525         if (!grpstatus) {
526           delete ht;
527           return pair<bool,void*>(true, returnDecline());
528         }
529     
530         for (int i = 1; i < line->tokens.size(); i++) {
531           w = line->tokens[i].c_str();
532           if (grpstatus->lookup(w)) {
533             log(LogLevelDebug, string("doCheckAuthZ accepting group: ") + w);
534             CHECK_OK;
535           }
536         }
537         delete grpstatus;
538         grpstatus = NULL;
539       }
540       else {
541         Iterator<IAAP*> provs = m_priv->m_app->getAAPProviders();
542         AAP wrapper(provs, w);
543         if (wrapper.fail()) {
544           log(LogLevelWarn, string("doCheckAuthZ didn't recognize require rule: ")
545                                    + w);
546           continue;
547         }
548
549         bool regexp = false;
550         string vals = getHeader(wrapper->getHeader());
551         for (int i = 1; i < line->tokens.size() && !vals.empty(); i++) {
552           w = line->tokens[i].c_str();
553           if (*w == '~') {
554             regexp = true;
555             continue;
556           }
557
558           try {
559             auto_ptr<RegularExpression> re;
560             if (regexp) {
561               delete re.release();
562               auto_ptr<XMLCh> trans(fromUTF8(w));
563               auto_ptr<RegularExpression> temp(new RegularExpression(trans.get()));
564               re=temp;
565             }
566                     
567             string vals_str(vals);
568             int j = 0;
569             for (int i = 0;  i < vals_str.length();  i++) {
570               if (vals_str.at(i) == ';') {
571                 if (i == 0) {
572                   log(LogLevelError, string("doCheckAuthZ invalid header encoding")+
573                       vals + ": starts with a semicolon");
574                   goto out;
575                 }
576
577                 if (vals_str.at(i-1) == '\\') {
578                   vals_str.erase(i-1, 1);
579                   i--;
580                   continue;
581                 }
582
583                 string val = vals_str.substr(j, i-j);
584                 j = i+1;
585                 if (regexp) {
586                   auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
587                   if (re->matches(trans.get())) {
588                     log(LogLevelDebug, string("doCheckAuthZ expecting ") + w +
589                         ", got " + val + ": authorization granted");
590                     CHECK_OK;
591                   }
592                 }
593                 else if ((wrapper->getCaseSensitive() && val==w) ||
594                          (!wrapper->getCaseSensitive() && !strcasecmp(val.c_str(),w))) {
595                   log(LogLevelDebug, string("doCheckAuthZ expecting ") + w +
596                       ", got " + val + ": authorization granted.");
597                   CHECK_OK;
598                 }
599                 else {
600                   log(LogLevelDebug, string("doCheckAuthZ expecting ") + w +
601                       ", got " + val + ": authoritzation not granted.");
602                 }
603               }
604             }
605     
606             string val = vals_str.substr(j, vals_str.length()-j);
607             if (regexp) {
608               auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
609               if (re->matches(trans.get())) {
610                 log(LogLevelDebug, string("doCheckAuthZ expecting ") + w +
611                     ", got " + val + ": authorization granted.");
612                 CHECK_OK;
613               }
614             }
615             else if ((wrapper->getCaseSensitive() && val==w) ||
616                      (!wrapper->getCaseSensitive() && !strcasecmp(val.c_str(),w))) {
617               log(LogLevelDebug, string("doCheckAuthZ expecting ") + w +
618                   ", got " + val + ": authorization granted");
619               CHECK_OK;
620             }
621             else {
622               log(LogLevelDebug, string("doCheckAuthZ expecting ") + w +
623                   ", got " + val + ": authorization not granted");
624             }
625           }
626           catch (XMLException& ex) {
627             auto_ptr_char tmp(ex.getMessage());
628             log(LogLevelError, string("doCheckAuthZ caught exception while parsing regular expression (")
629                 + w + "): " + tmp.get());
630           }
631         }
632       }
633     } // for x
634
635
636     // check if all require directives are true
637     bool auth_all_OK = true;
638     for (int i = 0; i < ht->elements.size(); i++) {
639         auth_all_OK &= auth_OK[i];
640     }
641
642     delete ht;
643     if (grpstatus) delete grpstatus;
644     if (auth_all_OK || !method_restricted)
645       return pair<bool,void*>(false, NULL);
646
647     // If we get here there's an access error, so just fall through
648
649   } catch (ShibTargetException &e) {
650     mlp.insert("errorText", e.what());
651
652 #ifndef _DEBUG
653   } catch (...) {
654     mlp.insert("errorText", "Unexpected Exception");
655 #endif
656   }
657
658   // If we get here then we've got an error.
659   mlp.insert("errorType", procState);
660   mlp.insert("errorDesc", "An error occurred while processing your request.");
661
662  out:
663   if (targetURL)
664     mlp.insert("requestURL", targetURL);
665
666   if (ht)
667     delete ht;
668
669   return pair<bool,void*>(true,m_priv->sendError(this, "access", mlp));
670 }
671
672 pair<bool,void*>
673 ShibTarget::doExportAssertions(bool exportAssertion)
674 {
675   saml::NDC ndc("ShibTarget::doExportAssertions");
676
677   ShibMLP mlp;
678   const char *procState = "Attribute Processing Error";
679   const char *targetURL = NULL;
680   char *page = "rm";
681
682   try {
683     if (! m_priv->m_app)
684       throw ShibTargetException(SHIBRPC_OK, "ShibTarget Uninitialized.  Application did not supply request information.");
685
686     targetURL = m_priv->m_url.c_str();
687     const char *session_id = m_priv->getSessionId(this);
688
689     if (m_priv->get_assertions(this, session_id, mlp))
690       goto out;
691
692     // Get the AAP providers, which contain the attribute policy info.
693     Iterator<IAAP*> provs=m_priv->m_app->getAAPProviders();
694
695     // Clear out the list of mapped attributes
696     while (provs.hasNext()) {
697       IAAP* aap=provs.next();
698       aap->lock();
699       try {
700         Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
701         while (rules.hasNext()) {
702           const char* header=rules.next()->getHeader();
703           if (header)
704             clearHeader(header);
705         }
706       }
707       catch(...) {
708         aap->unlock();
709         log(LogLevelError, "caught unexpected error while clearing headers");
710         throw;
711       }
712       aap->unlock();
713     }
714     provs.reset();
715     
716     // Maybe export the first assertion.
717     clearHeader("Shib-Attributes");
718     pair<bool,bool> exp=m_priv->m_settings.first->getBool("exportAssertion");
719     if (!exp.first || !exp.second)
720       if (exportAssertion)
721         exp.second=true;
722     if (exp.second && m_priv->m_assertions.size()) {
723       string assertion;
724       RM::serialize(*(m_priv->m_assertions[0]), assertion);
725       setHeader("Shib-Attributes", assertion.c_str());
726     }
727
728     // Export the SAML AuthnMethod and the origin site name, and possibly the NameIdentifier.
729     clearHeader("Shib-Origin-Site");
730     clearHeader("Shib-Authentication-Method");
731     clearHeader("Shib-NameIdentifier-Format");
732     auto_ptr_char os(m_priv->m_sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
733     auto_ptr_char am(m_priv->m_sso_statement->getAuthMethod());
734     setHeader("Shib-Origin-Site", os.get());
735     setHeader("Shib-Authentication-Method", am.get());
736     
737     // Export NameID?
738     AAP wrapper(provs,
739                 m_priv->m_sso_statement->getSubject()->getNameIdentifier()->getFormat(),
740                 Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
741     if (!wrapper.fail() && wrapper->getHeader()) {
742       auto_ptr_char form(m_priv->m_sso_statement->getSubject()->getNameIdentifier()->getFormat());
743       auto_ptr_char nameid(m_priv->m_sso_statement->getSubject()->getNameIdentifier()->getName());
744       setHeader("Shib-NameIdentifier-Format", form.get());
745       if (!strcmp(wrapper->getHeader(),"REMOTE_USER"))
746         setRemoteUser(nameid.get());
747       else
748         setHeader(wrapper->getHeader(), nameid.get());
749     }
750     
751     clearHeader("Shib-Application-ID");
752     setHeader("Shib-Application-ID", m_priv->m_app->getId());
753
754     // Export the attributes.
755     Iterator<SAMLAssertion*> a_iter(m_priv->m_assertions);
756     while (a_iter.hasNext()) {
757       SAMLAssertion* assert=a_iter.next();
758       Iterator<SAMLStatement*> statements=assert->getStatements();
759       while (statements.hasNext()) {
760         SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
761         if (!astate)
762           continue;
763         Iterator<SAMLAttribute*> attrs=astate->getAttributes();
764         while (attrs.hasNext()) {
765           SAMLAttribute* attr=attrs.next();
766         
767           // Are we supposed to export it?
768           AAP wrapper(provs,attr->getName(),attr->getNamespace());
769           if (wrapper.fail() || !wrapper->getHeader())
770             continue;
771                 
772           Iterator<string> vals=attr->getSingleByteValues();
773           if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext())
774             setRemoteUser(vals.next());
775           else {
776             int it=0;
777             string header = getHeader(wrapper->getHeader());
778             if (! header.empty())
779               it++;
780             for (; vals.hasNext(); it++) {
781               string value = vals.next();
782               for (string::size_type pos = value.find_first_of(";", string::size_type(0));
783                    pos != string::npos;
784                    pos = value.find_first_of(";", pos)) {
785                 value.insert(pos, "\\");
786                 pos += 2;
787               }
788               if (it)
789                 header += ";";
790               header += value;
791             }
792             setHeader(wrapper->getHeader(), header);
793           }
794         }
795       }
796     }
797
798     return pair<bool,void*>(false,NULL);
799
800   } catch (ShibTargetException &e) {
801     mlp.insert("errorText", e.what());
802
803 #ifndef _DEBUG
804   } catch (...) {
805     mlp.insert("errorText", "Unexpected Exception");
806 #endif
807
808   }
809
810   // If we get here then we've got an error.
811   mlp.insert("errorType", procState);
812   mlp.insert("errorDesc", "An error occurred while processing your request.");
813
814  out:
815   if (targetURL)
816     mlp.insert("requestURL", targetURL);
817
818   return pair<bool,void*>(true,m_priv->sendError(this, page, mlp));
819 }
820
821
822 // SHIRE APIs
823
824 // Get the session cookie name and properties for the application
825 std::pair<const char*,const char*>
826 ShibTarget::getCookieNameProps() const
827 {
828     static const char* defProps="; path=/";
829     static const char* defName="_shibsession_";
830     
831     // XXX: What to do if m_app isn't set?
832
833     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
834     if (props) {
835         pair<bool,const char*> p=props->getString("cookieProps");
836         if (!p.first)
837             p.second=defProps;
838         if (!m_priv->m_cookieName.empty())
839             return pair<const char*,const char*>(m_priv->m_cookieName.c_str(),
840                                                  p.second);
841         pair<bool,const char*> p2=props->getString("cookieName");
842         if (p2.first) {
843             m_priv->m_cookieName=p2.second;
844             return pair<const char*,const char*>(p2.second,p.second);
845         }
846         m_priv->m_cookieName=defName;
847         m_priv->m_cookieName+=m_priv->m_app->getId();
848         return pair<const char*,const char*>(m_priv->m_cookieName.c_str(),p.second);
849     }
850     m_priv->m_cookieName=defName;
851     m_priv->m_cookieName+=m_priv->m_app->getId();
852     return pair<const char*,const char*>(m_priv->m_cookieName.c_str(),defProps);
853 }
854         
855 // Find the default assertion consumer service for the resource
856 const char*
857 ShibTarget::getShireURL(const char* resource) const
858 {
859     if (!m_priv->m_shireURL.empty())
860         return m_priv->m_shireURL.c_str();
861
862     // XXX: what to do is m_app is NULL?
863
864     bool shire_ssl_only=false;
865     const char* shire=NULL;
866     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
867     if (props) {
868         pair<bool,bool> p=props->getBool("shireSSL");
869         if (p.first)
870             shire_ssl_only=p.second;
871         pair<bool,const char*> p2=props->getString("shireURL");
872         if (p2.first)
873             shire=p2.second;
874     }
875     
876     // Should never happen...
877     if (!shire || (*shire!='/' && strncmp(shire,"http:",5) && strncmp(shire,"https:",6)))
878         return NULL;
879
880     // The "shireURL" property can be in one of three formats:
881     //
882     // 1) a full URI:       http://host/foo/bar
883     // 2) a hostless URI:   http:///foo/bar
884     // 3) a relative path:  /foo/bar
885     //
886     // #  Protocol  Host        Path
887     // 1  shire     shire       shire
888     // 2  shire     resource    shire
889     // 3  resource  resource    shire
890     //
891     // note: if shire_ssl_only is true, make sure the protocol is https
892
893     const char* path = NULL;
894
895     // Decide whether to use the shire or the resource for the "protocol"
896     const char* prot;
897     if (*shire != '/') {
898         prot = shire;
899     }
900     else {
901         prot = resource;
902         path = shire;
903     }
904
905     // break apart the "protocol" string into protocol, host, and "the rest"
906     const char* colon=strchr(prot,':');
907     colon += 3;
908     const char* slash=strchr(colon,'/');
909     if (!path)
910         path = slash;
911
912     // Compute the actual protocol and store in member.
913     if (shire_ssl_only)
914         m_priv->m_shireURL.assign("https://");
915     else
916         m_priv->m_shireURL.assign(prot, colon-prot);
917
918     // create the "host" from either the colon/slash or from the target string
919     // If prot == shire then we're in either #1 or #2, else #3.
920     // If slash == colon then we're in #2.
921     if (prot != shire || slash == colon) {
922         colon = strchr(resource, ':');
923         colon += 3;      // Get past the ://
924         slash = strchr(colon, '/');
925     }
926     string host(colon, slash-colon);
927
928     // Build the shire URL
929     m_priv->m_shireURL+=host + path;
930     return m_priv->m_shireURL.c_str();
931 }
932         
933 // Generate a Shib 1.x AuthnRequest redirect URL for the resource
934 const char*
935 ShibTarget::getAuthnRequest(const char* resource) const
936 {
937     if (!m_priv->m_authnRequest.empty())
938         return m_priv->m_authnRequest.c_str();
939         
940     // XXX: what to do if m_app is NULL?
941
942     char timebuf[16];
943     sprintf(timebuf,"%u",time(NULL));
944     
945     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
946     if (props) {
947         pair<bool,const char*> wayf=props->getString("wayfURL");
948         if (wayf.first) {
949             m_priv->m_authnRequest=m_priv->m_authnRequest + wayf.second + "?shire=" + m_priv->url_encode(getShireURL(resource)) +
950                 "&target=" + m_priv->url_encode(resource) + "&time=" + timebuf;
951             pair<bool,bool> old=m_priv->m_app->getBool("oldAuthnRequest");
952             if (!old.first || !old.second) {
953                 wayf=m_priv->m_app->getString("providerId");
954                 if (wayf.first)
955                     m_priv->m_authnRequest=m_priv->m_authnRequest + "&providerId=" + m_priv->url_encode(wayf.second);
956             }
957         }
958     }
959     return m_priv->m_authnRequest.c_str();
960 }
961         
962 // Process a lazy session setup request and turn it into an AuthnRequest
963 const char*
964 ShibTarget::getLazyAuthnRequest(const char* query_string) const
965 {
966     CgiParse parser(query_string,strlen(query_string));
967     const char* target=parser.get_value("target");
968     if (!target || !*target)
969         return NULL;
970     return getAuthnRequest(target);
971 }
972         
973 // Process a POST profile submission, and return (SAMLResponse,TARGET) pair.
974 std::pair<const char*,const char*>
975 ShibTarget::getFormSubmission(const char* post, unsigned int len) const
976 {
977     m_priv->m_parser = new CgiParse(post,len);
978     return pair<const char*,const char*>(m_priv->m_parser->get_value("SAMLResponse"),m_priv->m_parser->get_value("TARGET"));
979 }
980         
981 RPCError* 
982 ShibTarget::sessionCreate(const char* response, const char* ip, std::string &cookie)
983   const
984 {
985   saml::NDC ndc("sessionCreate");
986   Category& log = Category::getInstance("shibtarget.SHIRE");
987
988   if (!response || !*response) {
989     log.error ("Empty SAML response content");
990     return new RPCError(-1,  "Empty SAML response content");
991   }
992
993   if (!ip || !*ip) {
994     log.error ("Invalid IP address");
995     return new RPCError(-1, "Invalid IP address");
996   }
997   
998   shibrpc_new_session_args_1 arg;
999   arg.shire_location = (char*) m_priv->m_shireURL.c_str();
1000   arg.application_id = (char*) m_priv->m_app->getId();
1001   arg.saml_post = (char*)response;
1002   arg.client_addr = (char*)ip;
1003   arg.checkIPAddress = true;
1004
1005   log.info ("create session for user at %s for application %s", ip, arg.application_id);
1006
1007   const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
1008   if (props) {
1009       pair<bool,bool> pcheck=props->getBool("checkAddress");
1010       if (pcheck.first)
1011           arg.checkIPAddress = pcheck.second;
1012   }
1013
1014   shibrpc_new_session_ret_1 ret;
1015   memset (&ret, 0, sizeof(ret));
1016
1017   // Loop on the RPC in case we lost contact the first time through
1018   int retry = 1;
1019   CLIENT* clnt;
1020   RPC rpc;
1021   do {
1022     clnt = rpc->connect();
1023     clnt_stat status = shibrpc_new_session_1 (&arg, &ret, clnt);
1024     if (status != RPC_SUCCESS) {
1025       // FAILED.  Release, disconnect, and retry
1026       log.error("RPC Failure: %p (%p) (%d): %s", this, clnt, status, clnt_spcreateerror("shibrpc_new_session_1"));
1027       rpc->disconnect();
1028       if (retry)
1029         retry--;
1030       else
1031         return new RPCError(-1, "RPC Failure");
1032     }
1033     else {
1034       // SUCCESS.  Pool and continue
1035       retry = -1;
1036     }
1037   } while (retry>=0);
1038
1039   log.debug("RPC completed with status %d (%p)", ret.status.status, this);
1040
1041   RPCError* retval;
1042   if (ret.status.status)
1043     retval = new RPCError(&ret.status);
1044   else {
1045     log.debug ("new cookie: %s", ret.cookie);
1046     cookie = ret.cookie;
1047     retval = new RPCError();
1048   }
1049
1050   clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_1, (caddr_t)&ret);
1051   rpc.pool();
1052
1053   log.debug("returning");
1054   return retval;
1055 }
1056
1057 RPCError*
1058 ShibTarget::sessionIsValid(const char* session_id, const char* ip) const
1059 {
1060   saml::NDC ndc("sessionIsValid");
1061   Category& log = Category::getInstance("shibtarget.SHIRE");
1062
1063   if (!session_id || !*session_id) {
1064     log.error ("No cookie value was provided");
1065     return new RPCError(SHIBRPC_NO_SESSION, "No cookie value was provided");
1066   }
1067   else if (strchr(session_id,'=')) {
1068     log.error ("The cookie value wasn't extracted successfully, use a more unique cookie name for your installation.");
1069     return new RPCError(SHIBRPC_INTERNAL_ERROR, "The cookie value wasn't extracted successfully, use a more unique cookie name for your installation.");
1070   }
1071
1072   if (!ip || !*ip) {
1073     log.error ("Invalid IP Address");
1074     return new RPCError(SHIBRPC_IPADDR_MISSING, "Invalid IP Address");
1075   }
1076
1077   log.info ("is session valid: %s", ip);
1078   log.debug ("session cookie: %s", session_id);
1079
1080   shibrpc_session_is_valid_args_1 arg;
1081
1082   arg.cookie.cookie = (char*)session_id;
1083   arg.cookie.client_addr = (char *)ip;
1084   arg.application_id = (char *)m_priv->m_app->getId();
1085   
1086   // Get rest of input from the application Session properties.
1087   arg.lifetime = 3600;
1088   arg.timeout = 1800;
1089   arg.checkIPAddress = true;
1090   const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
1091   if (props) {
1092       pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
1093       if (p.first)
1094           arg.lifetime = p.second;
1095       p=props->getUnsignedInt("timeout");
1096       if (p.first)
1097           arg.timeout = p.second;
1098       pair<bool,bool> pcheck=props->getBool("checkAddress");
1099       if (pcheck.first)
1100           arg.checkIPAddress = pcheck.second;
1101   }
1102   
1103   shibrpc_session_is_valid_ret_1 ret;
1104   memset (&ret, 0, sizeof(ret));
1105
1106   // Loop on the RPC in case we lost contact the first time through
1107   int retry = 1;
1108   CLIENT *clnt;
1109   RPC rpc;
1110   do {
1111     clnt = rpc->connect();
1112     clnt_stat status = shibrpc_session_is_valid_1(&arg, &ret, clnt);
1113     if (status != RPC_SUCCESS) {
1114       // FAILED.  Release, disconnect, and try again...
1115       log.error("RPC Failure: %p (%p) (%d) %s", this, clnt, status, clnt_spcreateerror("shibrpc_session_is_valid_1"));
1116       rpc->disconnect();
1117       if (retry)
1118           retry--;
1119       else
1120           return new RPCError(-1, "RPC Failure");
1121     }
1122     else {
1123       // SUCCESS
1124       retry = -1;
1125     }
1126   } while (retry>=0);
1127
1128   log.debug("RPC completed with status %d, %p", ret.status.status, this);
1129
1130   RPCError* retval;
1131   if (ret.status.status)
1132     retval = new RPCError(&ret.status);
1133   else
1134     retval = new RPCError();
1135
1136   clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_session_is_valid_ret_1, (caddr_t)&ret);
1137   rpc.pool();
1138
1139   log.debug("returning");
1140   return retval;
1141 }
1142
1143 // RM APIS
1144
1145 RPCError*
1146 ShibTarget::getAssertions(const char* cookie, const char* ip,
1147                           std::vector<saml::SAMLAssertion*>& assertions,
1148                           saml::SAMLAuthenticationStatement **statement
1149                           ) const
1150 {
1151   saml::NDC ndc("getAssertions");
1152   Category& log=Category::getInstance("shibtarget.RM");
1153   log.info("get assertions...");
1154
1155   if (!cookie || !*cookie) {
1156     log.error ("No cookie value provided.");
1157     return new RPCError(-1, "No cookie value provided.");
1158   }
1159
1160   if (!ip || !*ip) {
1161     log.error ("Invalid ip address");
1162     return new RPCError(-1, "Invalid IP address");
1163   }
1164
1165   log.debug("session cookie: %s", cookie);
1166
1167   shibrpc_get_assertions_args_1 arg;
1168   arg.cookie.cookie = (char*)cookie;
1169   arg.cookie.client_addr = (char*)ip;
1170   arg.checkIPAddress = true;
1171   arg.application_id = (char *)m_priv->m_app->getId();
1172
1173   log.info("request from %s for \"%s\"", ip, arg.application_id);
1174
1175   const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
1176   if (props) {
1177       pair<bool,bool> pcheck=props->getBool("checkAddress");
1178       if (pcheck.first)
1179           arg.checkIPAddress = pcheck.second;
1180   }
1181
1182   shibrpc_get_assertions_ret_1 ret;
1183   memset (&ret, 0, sizeof(ret));
1184
1185   // Loop on the RPC in case we lost contact the first time through
1186   int retry = 1;
1187   CLIENT *clnt;
1188   RPC rpc;
1189   do {
1190     clnt = rpc->connect();
1191     clnt_stat status = shibrpc_get_assertions_1(&arg, &ret, clnt);
1192     if (status != RPC_SUCCESS) {
1193       // FAILED.  Release, disconnect, and try again.
1194       log.debug("RPC Failure: %p (%p) (%d): %s", this, clnt, status, clnt_spcreateerror("shibrpc_get_assertions_1"));
1195       rpc->disconnect();
1196       if (retry)
1197         retry--;
1198       else
1199         return new RPCError(-1, "RPC Failure");
1200     }
1201     else {
1202       // SUCCESS.  Release back into pool
1203       retry = -1;
1204     }
1205   } while (retry>=0);
1206
1207   log.debug("RPC completed with status %d (%p)", ret.status.status, this);
1208
1209   RPCError* retval = NULL;
1210   if (ret.status.status)
1211     retval = new RPCError(&ret.status);
1212   else {
1213     try {
1214       try {
1215         for (u_int i = 0; i < ret.assertions.assertions_len; i++) {
1216           istringstream attrstream(ret.assertions.assertions_val[i].xml_string);
1217           SAMLAssertion *as = NULL;
1218           log.debugStream() << "Trying to decode assertion " << i << ": " <<
1219                 ret.assertions.assertions_val[i].xml_string << CategoryStream::ENDLINE;
1220           assertions.push_back(new SAMLAssertion(attrstream));
1221         }
1222
1223         // return the Authentication Statement
1224         if (statement) {
1225           istringstream authstream(ret.auth_statement.xml_string);
1226           SAMLAuthenticationStatement *auth = NULL;
1227           
1228           log.debugStream() << "Trying to decode authentication statement: " <<
1229                 ret.auth_statement.xml_string << CategoryStream::ENDLINE;
1230             auth = new SAMLAuthenticationStatement(authstream);
1231         
1232             // Save off the statement
1233             *statement = auth;
1234         }
1235       }
1236       catch (SAMLException& e) {
1237         log.error ("SAML Exception: %s", e.what());
1238         ostringstream os;
1239         os << e;
1240         throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str());
1241       }
1242       catch (XMLException& e) {
1243         log.error ("XML Exception: %s", e.getMessage());
1244         auto_ptr_char msg(e.getMessage());
1245         throw ShibTargetException (SHIBRPC_XML_EXCEPTION, msg.get());
1246       }
1247     }
1248     catch (ShibTargetException &e) {
1249       retval = new RPCError(e);
1250     }
1251
1252     if (!retval)
1253       retval = new RPCError();
1254   }
1255
1256   clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_assertions_ret_1, (caddr_t)&ret);
1257   rpc.pool();
1258
1259   log.debug ("returning..");
1260   return retval;
1261 }
1262
1263 void
1264 ShibTarget::serialize(saml::SAMLAssertion &assertion, std::string &result)
1265 {
1266   saml::NDC ndc("serialize");
1267   Category& log=Category::getInstance("shibtarget.RM");
1268
1269   ostringstream os;
1270   os << assertion;
1271   unsigned int outlen;
1272   char* assn = (char*) os.str().c_str();
1273   XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(assn), os.str().length(), &outlen);
1274   result = (char*) serialized;
1275   XMLString::release(&serialized);
1276 }
1277
1278
1279 /*************************************************************************
1280  * Shib Target Private implementation
1281  */
1282
1283 ShibTargetPriv::ShibTargetPriv() : m_parser(NULL), m_app(NULL), m_mapper(NULL),
1284                                    m_conf(NULL), m_Config(NULL), m_assertions()
1285 {
1286   session_id = NULL;
1287   m_sso_statement = NULL;
1288 }
1289
1290 ShibTargetPriv::~ShibTargetPriv()
1291 {
1292   if (m_sso_statement) {
1293     delete m_sso_statement;
1294     m_sso_statement = NULL;
1295   }
1296   for (int k = 0; k < m_assertions.size(); k++)
1297     delete m_assertions[k];
1298   m_assertions = vector<SAMLAssertion*>(); 
1299
1300   if (m_parser) {
1301     delete m_parser;
1302     m_parser = NULL;
1303   }
1304   if (m_mapper) {
1305     m_mapper->unlock();
1306     m_mapper = NULL;
1307   }
1308   if (m_conf) {
1309     m_conf->unlock();
1310     m_conf = NULL;
1311   }
1312   m_app = NULL;
1313   m_Config = NULL;
1314 }
1315
1316 static inline char hexchar(unsigned short s)
1317 {
1318     return (s<=9) ? ('0' + s) : ('A' + s - 10);
1319 }
1320
1321 string
1322 ShibTargetPriv::url_encode(const char* s)
1323 {
1324     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
1325
1326     string ret;
1327     for (; *s; s++) {
1328         if (strchr(badchars,*s) || *s<=0x1F || *s>=0x7F) {
1329             ret+='%';
1330         ret+=hexchar(*s >> 4);
1331         ret+=hexchar(*s & 0x0F);
1332         }
1333         else
1334             ret+=*s;
1335     }
1336     return ret;
1337 }
1338
1339 void
1340 ShibTargetPriv::get_application(string protocol, string hostname, int port,
1341                                 string uri)
1342 {
1343   if (m_app)
1344     return;
1345
1346   // XXX: Do we need to keep conf and mapper locked while we hold m_app?
1347
1348   // We lock the configuration system for the duration.
1349   m_conf=m_Config->getINI();
1350   m_conf->lock();
1351     
1352   // Map request to application and content settings.
1353   m_mapper=m_conf->getRequestMapper();
1354   m_mapper->lock();
1355
1356   // Obtain the application settings from the parsed URL
1357   m_settings = m_mapper->getSettingsFromParsedURL(protocol.c_str(),
1358                                                   hostname.c_str(),
1359                                                   port, uri.c_str());
1360
1361   // Now find the application from the URL settings
1362   pair<bool,const char*> application_id=m_settings.first->getString("applicationId");
1363   const IApplication* application=m_conf->getApplication(application_id.second);
1364   if (!application) {
1365     m_mapper->unlock();
1366     m_mapper = NULL;
1367     m_conf->unlock();
1368     m_conf = NULL;
1369     throw ShibTargetException(SHIBRPC_OK, "unable to map request to application settings.  Check configuration");
1370   }
1371
1372   // Store the application for later use
1373   m_app = application;
1374
1375   // Compute the target URL
1376   m_url = protocol + "://" + hostname;
1377   if ((protocol == "http" && port != 80) || (protocol == "https" && port != 443))
1378     m_url += ":" + port;
1379   m_url += uri;
1380 }
1381
1382
1383 void*
1384 ShibTargetPriv::sendError(ShibTarget* st, string page, ShibMLP &mlp)
1385 {
1386   const IPropertySet* props=m_app->getPropertySet("Errors");
1387   if (props) {
1388     pair<bool,const char*> p=props->getString(page.c_str());
1389     if (p.first) {
1390       ifstream infile(p.second);
1391       if (!infile.fail()) {
1392         const char* res = mlp.run(infile,props);
1393         if (res)
1394           return st->sendPage(res);
1395       }
1396     }
1397   }
1398
1399   string errstr = "sendError could not process the error template for application ";
1400   errstr += m_app->getId();
1401   st->log(ShibTarget::LogLevelError, errstr);
1402   return st->sendPage("Internal Server Error.  Please contact the server administrator.");
1403 }
1404
1405 const char *
1406 ShibTargetPriv::getSessionId(ShibTarget* st)
1407 {
1408   if (session_id) {
1409     //string m = string("getSessionId returning precreated session_id: ") + session_id;
1410     //st->log(ShibTarget::LogLevelDebug, m);
1411     return session_id;
1412   }
1413
1414   char *sid;
1415   pair<const char*, const char *> shib_cookie = st->getCookieNameProps();
1416   m_cookies = st->getCookies();
1417   if (!m_cookies.empty()) {
1418     if (sid = strstr(m_cookies.c_str(), shib_cookie.first)) {
1419       // We found a cookie.  pull it out (our session_id)
1420       sid += strlen(shib_cookie.first) + 1; // skip over the '='
1421       char *cookieend = strchr(sid, ';');
1422       if (cookieend)
1423         *cookieend = '\0';
1424       session_id = sid;
1425     }
1426   }
1427
1428   //string m = string("getSessionId returning new session_id: ") + session_id;
1429   //st->log(ShibTarget::LogLevelDebug, m);
1430   return session_id;
1431 }
1432
1433 bool
1434 ShibTargetPriv::get_assertions(ShibTarget* st, const char *session_id, ShibMLP &mlp)
1435 {
1436   if (m_sso_statement)
1437     return false;
1438
1439   RPCError *status = NULL;
1440   status = st->getAssertions(session_id, m_remote_addr.c_str(),
1441                              m_assertions, &m_sso_statement);
1442
1443   if (status->isError()) {
1444     string er = "getAssertions failed: ";
1445     er += status->getText();
1446     st->log(ShibTarget::LogLevelError, er);
1447     mlp.insert(*status);
1448     delete status;
1449     return true;
1450   }
1451   delete status;
1452   return false;
1453 }
1454
1455
1456 /*************************************************************************
1457  * CGI Parser implementation
1458  */
1459
1460 CgiParse::CgiParse(const char* data, unsigned int len)
1461 {
1462     const char* pch = data;
1463     unsigned int cl = len;
1464         
1465     while (cl && pch) {
1466         char *name;
1467         char *value;
1468         value=fmakeword('&',&cl,&pch);
1469         plustospace(value);
1470         url_decode(value);
1471         name=makeword(value,'=');
1472         kvp_map[name]=value;
1473         free(name);
1474     }
1475 }
1476
1477 CgiParse::~CgiParse()
1478 {
1479     for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
1480         free(i->second);
1481 }
1482
1483 const char*
1484 CgiParse::get_value(const char* name) const
1485 {
1486     map<string,char*>::const_iterator i=kvp_map.find(name);
1487     if (i==kvp_map.end())
1488         return NULL;
1489     return i->second;
1490 }
1491
1492 /* Parsing routines modified from NCSA source. */
1493 char *
1494 CgiParse::makeword(char *line, char stop)
1495 {
1496     int x = 0,y;
1497     char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
1498
1499     for(x=0;((line[x]) && (line[x] != stop));x++)
1500         word[x] = line[x];
1501
1502     word[x] = '\0';
1503     if(line[x])
1504         ++x;
1505     y=0;
1506
1507     while(line[x])
1508       line[y++] = line[x++];
1509     line[y] = '\0';
1510     return word;
1511 }
1512
1513 char *
1514 CgiParse::fmakeword(char stop, unsigned int *cl, const char** ppch)
1515 {
1516     int wsize;
1517     char *word;
1518     int ll;
1519
1520     wsize = 1024;
1521     ll=0;
1522     word = (char *) malloc(sizeof(char) * (wsize + 1));
1523
1524     while(1)
1525     {
1526         word[ll] = *((*ppch)++);
1527         if(ll==wsize-1)
1528         {
1529             word[ll+1] = '\0';
1530             wsize+=1024;
1531             word = (char *)realloc(word,sizeof(char)*(wsize+1));
1532         }
1533         --(*cl);
1534         if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
1535         {
1536             if(word[ll] != stop)
1537                 ll++;
1538             word[ll] = '\0';
1539             return word;
1540         }
1541         ++ll;
1542     }
1543 }
1544
1545 void
1546 CgiParse::plustospace(char *str)
1547 {
1548     register int x;
1549
1550     for(x=0;str[x];x++)
1551         if(str[x] == '+') str[x] = ' ';
1552 }
1553
1554 char
1555 CgiParse::x2c(char *what)
1556 {
1557     register char digit;
1558
1559     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
1560     digit *= 16;
1561     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
1562     return(digit);
1563 }
1564
1565 void
1566 CgiParse::url_decode(char *url)
1567 {
1568     register int x,y;
1569
1570     for(x=0,y=0;url[y];++x,++y)
1571     {
1572         if((url[x] = url[y]) == '%')
1573         {
1574             url[x] = x2c(&url[y+1]);
1575             y+=2;
1576         }
1577     }
1578     url[x] = '\0';
1579 }
1580
1581 /*
1582  * We need to implement this so the SHIRE (and RM) recodes work
1583  * in terms of the ShibTarget
1584  */
1585 void ShibTarget::log(ShibLogLevel level, const string &msg)
1586 {
1587   throw runtime_error("Invalid Usage");
1588 }
1589 string ShibTarget::getCookies(void)
1590 {
1591   throw runtime_error("Invalid Usage");
1592 }
1593 void ShibTarget::setCookie(const string &name, const string &value)
1594 {
1595   throw runtime_error("Invalid Usage");
1596 }
1597 void ShibTarget::clearHeader(const string &name)
1598 {
1599   throw runtime_error("Invalid Usage");
1600 }
1601 void ShibTarget::setHeader(const string &name, const string &value)
1602 {
1603   throw runtime_error("Invalid Usage");
1604 }
1605 string ShibTarget::getHeader(const string &name)
1606 {
1607   throw runtime_error("Invalid Usage");
1608 }
1609 void ShibTarget::setRemoteUser(const string &name)
1610 {
1611   throw runtime_error("Invalid Usage");
1612 }
1613 string ShibTarget::getRemoteUser(void)
1614 {
1615   throw runtime_error("Invalid Usage");
1616 }
1617 string ShibTarget::getArgs(void)
1618 {
1619   throw runtime_error("Invalid Usage");
1620 }
1621 string ShibTarget::getPostData(void)
1622 {
1623   throw runtime_error("Invalid Usage");
1624 }
1625 //virtual HTAccessInfo& getAccessInfo(void);
1626 void* ShibTarget::sendPage(const string &msg, const string content_type, const pair<string,string> headers[], int code)
1627 {
1628   throw runtime_error("Invalid Usage");
1629 }
1630 void* ShibTarget::sendRedirect(const std::string url)
1631 {
1632   throw runtime_error("Invalid Usage");
1633 }
1634
1635 // Subclasses may not need to override these particular virtual methods.
1636 string ShibTarget::getAuthType(void)
1637 {
1638   return string("shibboleth");
1639 }
1640 void* ShibTarget::returnDecline(void)
1641 {
1642   return NULL;
1643 }
1644 void* ShibTarget::returnOK(void)
1645 {
1646   return NULL;
1647 }
1648 HTAccessInfo* ShibTarget::getAccessInfo(void)
1649 {
1650   return NULL;
1651 }
1652 HTGroupTable* ShibTarget::getGroupTable(string &user)
1653 {
1654   return NULL;
1655 }