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