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