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