Revised metadata API.
[shibboleth/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 <stdexcept>
67
68 #include <shib/shib-threads.h>
69 #include <log4cpp/Category.hh>
70 #include <log4cpp/PropertyConfigurator.hh>
71 #include <xercesc/util/Base64.hpp>
72
73 using namespace std;
74 using namespace saml;
75 using namespace shibboleth;
76 using namespace shibtarget;
77 using namespace log4cpp;
78
79 namespace shibtarget {
80   class CgiParse
81   {
82   public:
83     CgiParse(const char* data, unsigned int len);
84     ~CgiParse();
85     const char* get_value(const char* name) const;
86     
87   private:
88     char * fmakeword(char stop, unsigned int *cl, const char** ppch);
89     char * makeword(char *line, char stop);
90     void plustospace(char *str);
91     char x2c(char *what);
92     void url_decode(char *url);
93
94     map<string,char*> kvp_map;
95   };
96
97   class ShibTargetPriv
98   {
99   public:
100     ShibTargetPriv();
101     ~ShibTargetPriv();
102
103     string url_encode(const char* s);
104     void get_application(string protocol, string hostname, int port, string uri);
105
106     IRequestMapper::Settings m_settings;
107     const IApplication *m_app;
108     string m_cookieName;
109     string m_shireURL;
110     string m_authnRequest;
111     CgiParse* m_parser;
112
113     // These are the actual request parameters set via the init method.
114     string m_url;
115     string m_method;
116     string m_content_type;
117     string m_remote_addr;
118     int m_total_bytes;
119
120     ShibTargetConfig* m_Config;
121   };
122 }
123
124
125 /*************************************************************************
126  * Shib Target implementation
127  */
128
129 ShibTarget::ShibTarget(void) : m_priv(NULL)
130 {
131   m_priv = new ShibTargetPriv();
132 }
133
134 ShibTarget::ShibTarget(const IApplication *app) : m_priv(NULL)
135 {
136   m_priv = new ShibTargetPriv();
137   m_priv->m_app = app;
138 }
139
140 ShibTarget::~ShibTarget(void)
141 {
142   if (m_priv) delete m_priv;
143 }
144
145 void ShibTarget::init(ShibTargetConfig *config,
146                       string protocol, string hostname, int port,
147                       string uri, string content_type, string remote_host,
148                       int total_bytes)
149 {
150   m_priv->m_method = protocol;
151   m_priv->m_content_type = content_type;
152   m_priv->m_remote_addr = remote_host;
153   m_priv->m_total_bytes = total_bytes;
154   m_priv->m_Config = config;
155   m_priv->get_application(protocol, hostname, port, uri);
156 }
157
158
159 // These functions implement the server-agnostic shibboleth engine
160 // The web server modules implement a subclass and then call into 
161 // these methods once they instantiate their request object.
162 void*
163 ShibTarget::doCheckAuthN(void)
164 {
165
166   const char *targetURL = NULL;
167   const char *procState = "Process Initialization Error";
168   ShibMLP mlp;
169
170   try {
171     if (! m_priv->m_app)
172       throw ShibTargetException(SHIBRPC_OK, "ShibTarget Uninitialized.  Application did not supply request information.");
173
174     targetURL = m_priv->m_url.c_str();
175     const char *shireURL = getShireURL(targetURL);
176     if (! shireURL)
177       throw ShibTargetException(SHIBRPC_OK, "Cannot map target URL to Shire URL.  Check configuration");
178
179     if (strstr(targetURL,shireURL))
180       return doHandlePOST();
181
182     string auth_type = getAuthType();
183 #ifdef HAVE_STRCASECMP
184     if (strcasecmp(auth_type.c_str(),"shibboleth"))
185 #else
186     if (stricmp(auth_type.c_str(),"shibboleth"))
187 #endif
188       return returnDecline();
189
190     pair<bool,bool> requireSession = getRequireSession(m_priv->m_settings);
191     pair<const char*, const char *> shib_cookie = getCookieNameProps();
192
193     const char *session_id = NULL;
194     string cookies = getCookies();
195     if (!cookies.empty()) {
196       if (session_id = strstr(cookies.c_str(), shib_cookie.first)) {
197         // We found a cookie.  pull it out (our session_id)
198         session_id += strlen(shib_cookie.first) + 1; // skip over the '='
199         char *cookieend = strchr(session_id, ';');
200         if (cookieend)
201           *cookieend = '\0';
202       }
203     }
204     
205     if (!session_id || !*session_id) {
206       // No session.  Maybe that's acceptable?
207
208       if (!requireSession.second)
209         return returnOK();
210
211       // No cookie, but we require a session.  Generate an AuthnRequest.
212       return sendRedirect(getAuthnRequest(targetURL));
213     }
214
215     procState = "Session Processing Error";
216     RPCError *status = sessionIsValid(session_id, m_priv->m_remote_addr.c_str());
217
218     if (status->isError()) {
219
220       // If no session is required, bail now.
221       if (!requireSession.second)
222         return returnOK(); // XXX: Or should this be DECLINED?
223                            // Has to be OK because DECLINED will just cause Apache
224                            // to fail when it can't locate anything to process the
225                            // AuthType.  No session plus requireSession false means
226                            // do not authenticate the user at this time.
227       else if (status->isRetryable()) {
228         // Session is invalid but we can retry the auth -- generate an AuthnRequest
229         delete status;
230         return sendRedirect(getAuthnRequest(targetURL));
231
232       } else {
233
234         mlp.insert(*status);
235         delete status;
236         goto out;
237       }
238
239       delete status;
240       
241     }
242
243   } catch (ShibTargetException &e) {
244     mlp.insert("errorText", e.what());
245
246   } catch (...) {
247     mlp.insert("errorText", "Unexpected Exception");
248
249   }
250
251   // If we get here then we've got an error.
252   mlp.insert("errorType", procState);
253   mlp.insert("errorDesc", "An error occurred while processing your request.");
254
255  out:
256   if (targetURL)
257     mlp.insert("requestURL", targetURL);
258
259   string res = "xxx";
260   return sendPage(res);
261 }
262
263 void*
264 ShibTarget::doHandlePOST(void)
265 {
266   return NULL;
267 }
268
269 void*
270 ShibTarget::doCheckAuthZ(void)
271 {
272   return NULL;
273 }
274
275
276 // SHIRE APIs
277
278 // Get the session cookie name and properties for the application
279 std::pair<const char*,const char*>
280 ShibTarget::getCookieNameProps() const
281 {
282     static const char* defProps="; path=/";
283     static const char* defName="_shibsession_";
284     
285     // XXX: What to do if m_app isn't set?
286
287     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
288     if (props) {
289         pair<bool,const char*> p=props->getString("cookieProps");
290         if (!p.first)
291             p.second=defProps;
292         if (!m_priv->m_cookieName.empty())
293             return pair<const char*,const char*>(m_priv->m_cookieName.c_str(),
294                                                  p.second);
295         pair<bool,const char*> p2=props->getString("cookieName");
296         if (p2.first) {
297             m_priv->m_cookieName=p2.second;
298             return pair<const char*,const char*>(p2.second,p.second);
299         }
300         m_priv->m_cookieName=defName;
301         m_priv->m_cookieName+=m_priv->m_app->getId();
302         return pair<const char*,const char*>(m_priv->m_cookieName.c_str(),p.second);
303     }
304     m_priv->m_cookieName=defName;
305     m_priv->m_cookieName+=m_priv->m_app->getId();
306     return pair<const char*,const char*>(m_priv->m_cookieName.c_str(),defProps);
307 }
308         
309 // Find the default assertion consumer service for the resource
310 const char*
311 ShibTarget::getShireURL(const char* resource) const
312 {
313     if (!m_priv->m_shireURL.empty())
314         return m_priv->m_shireURL.c_str();
315
316     // XXX: what to do is m_app is NULL?
317
318     bool shire_ssl_only=false;
319     const char* shire=NULL;
320     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
321     if (props) {
322         pair<bool,bool> p=props->getBool("shireSSL");
323         if (p.first)
324             shire_ssl_only=p.second;
325         pair<bool,const char*> p2=props->getString("shireURL");
326         if (p2.first)
327             shire=p2.second;
328     }
329     
330     // Should never happen...
331     if (!shire || (*shire!='/' && strncmp(shire,"http:",5) && strncmp(shire,"https:",6)))
332         return NULL;
333
334     // The "shireURL" property can be in one of three formats:
335     //
336     // 1) a full URI:       http://host/foo/bar
337     // 2) a hostless URI:   http:///foo/bar
338     // 3) a relative path:  /foo/bar
339     //
340     // #  Protocol  Host        Path
341     // 1  shire     shire       shire
342     // 2  shire     resource    shire
343     // 3  resource  resource    shire
344     //
345     // note: if shire_ssl_only is true, make sure the protocol is https
346
347     const char* path = NULL;
348
349     // Decide whether to use the shire or the resource for the "protocol"
350     const char* prot;
351     if (*shire != '/') {
352         prot = shire;
353     }
354     else {
355         prot = resource;
356         path = shire;
357     }
358
359     // break apart the "protocol" string into protocol, host, and "the rest"
360     const char* colon=strchr(prot,':');
361     colon += 3;
362     const char* slash=strchr(colon,'/');
363     if (!path)
364         path = slash;
365
366     // Compute the actual protocol and store in member.
367     if (shire_ssl_only)
368         m_priv->m_shireURL.assign("https://");
369     else
370         m_priv->m_shireURL.assign(prot, colon-prot);
371
372     // create the "host" from either the colon/slash or from the target string
373     // If prot == shire then we're in either #1 or #2, else #3.
374     // If slash == colon then we're in #2.
375     if (prot != shire || slash == colon) {
376         colon = strchr(resource, ':');
377         colon += 3;      // Get past the ://
378         slash = strchr(colon, '/');
379     }
380     string host(colon, slash-colon);
381
382     // Build the shire URL
383     m_priv->m_shireURL+=host + path;
384     return m_priv->m_shireURL.c_str();
385 }
386         
387 // Generate a Shib 1.x AuthnRequest redirect URL for the resource
388 const char*
389 ShibTarget::getAuthnRequest(const char* resource) const
390 {
391     if (!m_priv->m_authnRequest.empty())
392         return m_priv->m_authnRequest.c_str();
393         
394     // XXX: what to do if m_app is NULL?
395
396     char timebuf[16];
397     sprintf(timebuf,"%u",time(NULL));
398     
399     const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
400     if (props) {
401         pair<bool,const char*> wayf=props->getString("wayfURL");
402         if (wayf.first) {
403             m_priv->m_authnRequest=m_priv->m_authnRequest + wayf.second + "?shire=" + m_priv->url_encode(getShireURL(resource)) +
404                 "&target=" + m_priv->url_encode(resource) + "&time=" + timebuf;
405             pair<bool,bool> old=m_priv->m_app->getBool("oldAuthnRequest");
406             if (!old.first || !old.second) {
407                 wayf=m_priv->m_app->getString("providerId");
408                 if (wayf.first)
409                     m_priv->m_authnRequest=m_priv->m_authnRequest + "&providerId=" + m_priv->url_encode(wayf.second);
410             }
411         }
412     }
413     return m_priv->m_authnRequest.c_str();
414 }
415         
416 // Process a lazy session setup request and turn it into an AuthnRequest
417 const char*
418 ShibTarget::getLazyAuthnRequest(const char* query_string) const
419 {
420     CgiParse parser(query_string,strlen(query_string));
421     const char* target=parser.get_value("target");
422     if (!target || !*target)
423         return NULL;
424     return getAuthnRequest(target);
425 }
426         
427 // Process a POST profile submission, and return (SAMLResponse,TARGET) pair.
428 std::pair<const char*,const char*>
429 ShibTarget::getFormSubmission(const char* post, unsigned int len) const
430 {
431     m_priv->m_parser = new CgiParse(post,len);
432     return pair<const char*,const char*>(m_priv->m_parser->get_value("SAMLResponse"),m_priv->m_parser->get_value("TARGET"));
433 }
434         
435 RPCError* 
436 ShibTarget::sessionCreate(const char* response, const char* ip, std::string &cookie)
437   const
438 {
439   saml::NDC ndc("sessionCreate");
440   Category& log = Category::getInstance("shibtarget.SHIRE");
441
442   if (!response || !*response) {
443     log.error ("Empty SAML response content");
444     return new RPCError(-1,  "Empty SAML response content");
445   }
446
447   if (!ip || !*ip) {
448     log.error ("Invalid IP address");
449     return new RPCError(-1, "Invalid IP address");
450   }
451   
452   shibrpc_new_session_args_1 arg;
453   arg.shire_location = (char*) m_priv->m_shireURL.c_str();
454   arg.application_id = (char*) m_priv->m_app->getId();
455   arg.saml_post = (char*)response;
456   arg.client_addr = (char*)ip;
457   arg.checkIPAddress = true;
458
459   log.info ("create session for user at %s for application %s", ip, arg.application_id);
460
461   const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
462   if (props) {
463       pair<bool,bool> pcheck=props->getBool("checkAddress");
464       if (pcheck.first)
465           arg.checkIPAddress = pcheck.second;
466   }
467
468   shibrpc_new_session_ret_1 ret;
469   memset (&ret, 0, sizeof(ret));
470
471   // Loop on the RPC in case we lost contact the first time through
472   int retry = 1;
473   CLIENT* clnt;
474   RPC rpc;
475   do {
476     clnt = rpc->connect();
477     clnt_stat status = shibrpc_new_session_1 (&arg, &ret, clnt);
478     if (status != RPC_SUCCESS) {
479       // FAILED.  Release, disconnect, and retry
480       log.error("RPC Failure: %p (%p) (%d): %s", this, clnt, status, clnt_spcreateerror("shibrpc_new_session_1"));
481       rpc->disconnect();
482       if (retry)
483         retry--;
484       else
485         return new RPCError(-1, "RPC Failure");
486     }
487     else {
488       // SUCCESS.  Pool and continue
489       retry = -1;
490     }
491   } while (retry>=0);
492
493   log.debug("RPC completed with status %d (%p)", ret.status.status, this);
494
495   RPCError* retval;
496   if (ret.status.status)
497     retval = new RPCError(&ret.status);
498   else {
499     log.debug ("new cookie: %s", ret.cookie);
500     cookie = ret.cookie;
501     retval = new RPCError();
502   }
503
504   clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_1, (caddr_t)&ret);
505   rpc.pool();
506
507   log.debug("returning");
508   return retval;
509 }
510
511 RPCError*
512 ShibTarget::sessionIsValid(const char* session_id, const char* ip) const
513 {
514   saml::NDC ndc("sessionIsValid");
515   Category& log = Category::getInstance("shibtarget.SHIRE");
516
517   if (!session_id || !*session_id) {
518     log.error ("No cookie value was provided");
519     return new RPCError(SHIBRPC_NO_SESSION, "No cookie value was provided");
520   }
521   else if (strchr(session_id,'=')) {
522     log.error ("The cookie value wasn't extracted successfully, use a more unique cookie name for your installation.");
523     return new RPCError(SHIBRPC_INTERNAL_ERROR, "The cookie value wasn't extracted successfully, use a more unique cookie name for your installation.");
524   }
525
526   if (!ip || !*ip) {
527     log.error ("Invalid IP Address");
528     return new RPCError(SHIBRPC_IPADDR_MISSING, "Invalid IP Address");
529   }
530
531   log.info ("is session valid: %s", ip);
532   log.debug ("session cookie: %s", session_id);
533
534   shibrpc_session_is_valid_args_1 arg;
535
536   arg.cookie.cookie = (char*)session_id;
537   arg.cookie.client_addr = (char *)ip;
538   arg.application_id = (char *)m_priv->m_app->getId();
539   
540   // Get rest of input from the application Session properties.
541   arg.lifetime = 3600;
542   arg.timeout = 1800;
543   arg.checkIPAddress = true;
544   const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
545   if (props) {
546       pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
547       if (p.first)
548           arg.lifetime = p.second;
549       p=props->getUnsignedInt("timeout");
550       if (p.first)
551           arg.timeout = p.second;
552       pair<bool,bool> pcheck=props->getBool("checkAddress");
553       if (pcheck.first)
554           arg.checkIPAddress = pcheck.second;
555   }
556   
557   shibrpc_session_is_valid_ret_1 ret;
558   memset (&ret, 0, sizeof(ret));
559
560   // Loop on the RPC in case we lost contact the first time through
561   int retry = 1;
562   CLIENT *clnt;
563   RPC rpc;
564   do {
565     clnt = rpc->connect();
566     clnt_stat status = shibrpc_session_is_valid_1(&arg, &ret, clnt);
567     if (status != RPC_SUCCESS) {
568       // FAILED.  Release, disconnect, and try again...
569       log.error("RPC Failure: %p (%p) (%d) %s", this, clnt, status, clnt_spcreateerror("shibrpc_session_is_valid_1"));
570       rpc->disconnect();
571       if (retry)
572           retry--;
573       else
574           return new RPCError(-1, "RPC Failure");
575     }
576     else {
577       // SUCCESS
578       retry = -1;
579     }
580   } while (retry>=0);
581
582   log.debug("RPC completed with status %d, %p", ret.status.status, this);
583
584   RPCError* retval;
585   if (ret.status.status)
586     retval = new RPCError(&ret.status);
587   else
588     retval = new RPCError();
589
590   clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_session_is_valid_ret_1, (caddr_t)&ret);
591   rpc.pool();
592
593   log.debug("returning");
594   return retval;
595 }
596
597 // RM APIS
598
599 RPCError*
600 ShibTarget::getAssertions(const char* cookie, const char* ip,
601                           std::vector<saml::SAMLAssertion*>& assertions,
602                           saml::SAMLAuthenticationStatement **statement
603                           ) const
604 {
605   saml::NDC ndc("getAssertions");
606   Category& log=Category::getInstance("shibtarget.RM");
607   log.info("get assertions...");
608
609   if (!cookie || !*cookie) {
610     log.error ("No cookie value provided.");
611     return new RPCError(-1, "No cookie value provided.");
612   }
613
614   if (!ip || !*ip) {
615     log.error ("Invalid ip address");
616     return new RPCError(-1, "Invalid IP address");
617   }
618
619   log.debug("session cookie: %s", cookie);
620
621   shibrpc_get_assertions_args_1 arg;
622   arg.cookie.cookie = (char*)cookie;
623   arg.cookie.client_addr = (char*)ip;
624   arg.checkIPAddress = true;
625   arg.application_id = (char *)m_priv->m_app->getId();
626
627   log.info("request from %s for \"%s\"", ip, arg.application_id);
628
629   const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
630   if (props) {
631       pair<bool,bool> pcheck=props->getBool("checkAddress");
632       if (pcheck.first)
633           arg.checkIPAddress = pcheck.second;
634   }
635
636   shibrpc_get_assertions_ret_1 ret;
637   memset (&ret, 0, sizeof(ret));
638
639   // Loop on the RPC in case we lost contact the first time through
640   int retry = 1;
641   CLIENT *clnt;
642   RPC rpc;
643   do {
644     clnt = rpc->connect();
645     clnt_stat status = shibrpc_get_assertions_1(&arg, &ret, clnt);
646     if (status != RPC_SUCCESS) {
647       // FAILED.  Release, disconnect, and try again.
648       log.debug("RPC Failure: %p (%p) (%d): %s", this, clnt, status, clnt_spcreateerror("shibrpc_get_assertions_1"));
649       rpc->disconnect();
650       if (retry)
651         retry--;
652       else
653         return new RPCError(-1, "RPC Failure");
654     }
655     else {
656       // SUCCESS.  Release back into pool
657       retry = -1;
658     }
659   } while (retry>=0);
660
661   log.debug("RPC completed with status %d (%p)", ret.status.status, this);
662
663   RPCError* retval = NULL;
664   if (ret.status.status)
665     retval = new RPCError(&ret.status);
666   else {
667     try {
668       try {
669         for (u_int i = 0; i < ret.assertions.assertions_len; i++) {
670           istringstream attrstream(ret.assertions.assertions_val[i].xml_string);
671           SAMLAssertion *as = NULL;
672           log.debugStream() << "Trying to decode assertion " << i << ": " <<
673                 ret.assertions.assertions_val[i].xml_string << CategoryStream::ENDLINE;
674           assertions.push_back(new SAMLAssertion(attrstream));
675         }
676
677         // return the Authentication Statement
678         if (statement) {
679           istringstream authstream(ret.auth_statement.xml_string);
680           SAMLAuthenticationStatement *auth = NULL;
681           
682           log.debugStream() << "Trying to decode authentication statement: " <<
683                 ret.auth_statement.xml_string << CategoryStream::ENDLINE;
684             auth = new SAMLAuthenticationStatement(authstream);
685         
686             // Save off the statement
687             *statement = auth;
688         }
689       }
690       catch (SAMLException& e) {
691         log.error ("SAML Exception: %s", e.what());
692         ostringstream os;
693         os << e;
694         throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str());
695       }
696       catch (XMLException& e) {
697         log.error ("XML Exception: %s", e.getMessage());
698         auto_ptr_char msg(e.getMessage());
699         throw ShibTargetException (SHIBRPC_XML_EXCEPTION, msg.get());
700       }
701     }
702     catch (ShibTargetException &e) {
703       retval = new RPCError(e);
704     }
705
706     if (!retval)
707       retval = new RPCError();
708   }
709
710   clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_assertions_ret_1, (caddr_t)&ret);
711   rpc.pool();
712
713   log.debug ("returning..");
714   return retval;
715 }
716
717 void
718 ShibTarget::serialize(saml::SAMLAssertion &assertion, std::string &result)
719 {
720   saml::NDC ndc("serialize");
721   Category& log=Category::getInstance("shibtarget.RM");
722
723   ostringstream os;
724   os << assertion;
725   unsigned int outlen;
726   char* assn = (char*) os.str().c_str();
727   XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(assn), os.str().length(), &outlen);
728   result = (char*) serialized;
729   XMLString::release(&serialized);
730 }
731
732
733 /*************************************************************************
734  * Shib Target Private implementation
735  */
736
737 ShibTargetPriv::ShibTargetPriv() : m_parser(NULL), m_app(NULL)
738 {
739   m_total_bytes = 0;
740 }
741
742 ShibTargetPriv::~ShibTargetPriv()
743 {
744   if (m_parser) delete m_parser;
745   //if (m_app) delete m_app;
746 }
747
748 static inline char hexchar(unsigned short s)
749 {
750     return (s<=9) ? ('0' + s) : ('A' + s - 10);
751 }
752
753 string
754 ShibTargetPriv::url_encode(const char* s)
755 {
756     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
757
758     string ret;
759     for (; *s; s++) {
760         if (strchr(badchars,*s) || *s<=0x1F || *s>=0x7F) {
761             ret+='%';
762         ret+=hexchar(*s >> 4);
763         ret+=hexchar(*s & 0x0F);
764         }
765         else
766             ret+=*s;
767     }
768     return ret;
769 }
770
771 void
772 ShibTargetPriv::get_application(string protocol, string hostname, int port,
773                                 string uri)
774 {
775   if (m_app)
776     return;
777
778   // We lock the configuration system for the duration.
779   IConfig* conf=m_Config->getINI();
780   Locker locker(conf);
781     
782   // Map request to application and content settings.
783   IRequestMapper* mapper=conf->getRequestMapper();
784   Locker locker2(mapper);
785
786   // Obtain the application settings from the parsed URL
787   m_settings = mapper->getSettingsFromParsedURL(protocol.c_str(), hostname.c_str(),
788                                                 port, uri.c_str());
789
790   // Now find the application from the URL settings
791   pair<bool,const char*> application_id=m_settings.first->getString("applicationId");
792   const IApplication* application=conf->getApplication(application_id.second);
793   if (!application) {
794     throw ShibTargetException(SHIBRPC_OK, "unable to map request to application settings.  Check configuration");
795   }
796
797   // Store the application for later use
798   m_app = application;
799
800   // Compute the target URL
801   m_url = protocol + "://" + hostname;
802   if ((protocol == "http" && port != 80) || (protocol == "https" && port != 443))
803     m_url += ":" + port;
804   m_url += uri;
805 }
806
807
808 /*************************************************************************
809  * CGI Parser implementation
810  */
811
812 CgiParse::CgiParse(const char* data, unsigned int len)
813 {
814     const char* pch = data;
815     unsigned int cl = len;
816         
817     while (cl && pch) {
818         char *name;
819         char *value;
820         value=fmakeword('&',&cl,&pch);
821         plustospace(value);
822         url_decode(value);
823         name=makeword(value,'=');
824         kvp_map[name]=value;
825         free(name);
826     }
827 }
828
829 CgiParse::~CgiParse()
830 {
831     for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
832         free(i->second);
833 }
834
835 const char*
836 CgiParse::get_value(const char* name) const
837 {
838     map<string,char*>::const_iterator i=kvp_map.find(name);
839     if (i==kvp_map.end())
840         return NULL;
841     return i->second;
842 }
843
844 /* Parsing routines modified from NCSA source. */
845 char *
846 CgiParse::makeword(char *line, char stop)
847 {
848     int x = 0,y;
849     char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
850
851     for(x=0;((line[x]) && (line[x] != stop));x++)
852         word[x] = line[x];
853
854     word[x] = '\0';
855     if(line[x])
856         ++x;
857     y=0;
858
859     while(line[x])
860       line[y++] = line[x++];
861     line[y] = '\0';
862     return word;
863 }
864
865 char *
866 CgiParse::fmakeword(char stop, unsigned int *cl, const char** ppch)
867 {
868     int wsize;
869     char *word;
870     int ll;
871
872     wsize = 1024;
873     ll=0;
874     word = (char *) malloc(sizeof(char) * (wsize + 1));
875
876     while(1)
877     {
878         word[ll] = *((*ppch)++);
879         if(ll==wsize-1)
880         {
881             word[ll+1] = '\0';
882             wsize+=1024;
883             word = (char *)realloc(word,sizeof(char)*(wsize+1));
884         }
885         --(*cl);
886         if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
887         {
888             if(word[ll] != stop)
889                 ll++;
890             word[ll] = '\0';
891             return word;
892         }
893         ++ll;
894     }
895 }
896
897 void
898 CgiParse::plustospace(char *str)
899 {
900     register int x;
901
902     for(x=0;str[x];x++)
903         if(str[x] == '+') str[x] = ' ';
904 }
905
906 char
907 CgiParse::x2c(char *what)
908 {
909     register char digit;
910
911     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
912     digit *= 16;
913     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
914     return(digit);
915 }
916
917 void
918 CgiParse::url_decode(char *url)
919 {
920     register int x,y;
921
922     for(x=0,y=0;url[y];++x,++y)
923     {
924         if((url[x] = url[y]) == '%')
925         {
926             url[x] = x2c(&url[y+1]);
927             y+=2;
928         }
929     }
930     url[x] = '\0';
931 }
932
933 /*
934  * We need to implement this so the SHIRE (and RM) recodes work
935  * in terms of the ShibTarget
936  */
937 void ShibTarget::log(ShibLogLevel level, string &msg)
938 {
939   throw runtime_error("Invalid Usage");
940 }
941 string ShibTarget::getCookies(void)
942 {
943   throw runtime_error("Invalid Usage");
944 }
945 void ShibTarget::setCookie(string &name, string &value)
946 {
947   throw runtime_error("Invalid Usage");
948 }
949 string ShibTarget::getPostData(void)
950 {
951   throw runtime_error("Invalid Usage");
952 }
953 void ShibTarget::setAuthType(std::string)
954 {
955   throw runtime_error("Invalid Usage");
956 }
957 //virtual HTAccessInfo& getAccessInfo(void);
958 void* ShibTarget::sendPage(string &msg, pair<string,string> headers[], int code)
959 {
960   throw runtime_error("Invalid Usage");
961 }
962 void* ShibTarget::sendRedirect(std::string url)
963 {
964   throw runtime_error("Invalid Usage");
965 }
966
967 // Subclasses may not need to override these particular virtual methods.
968 string ShibTarget::getAuthType(void)
969 {
970   return string("shibboleth");
971 }
972 void* ShibTarget::returnDecline(void)
973 {
974   return NULL;
975 }
976 void* ShibTarget::returnOK(void)
977 {
978   return NULL;
979 }
980 pair<bool,bool>
981 ShibTarget::getRequireSession(IRequestMapper::Settings &settings)
982 {
983   return settings.first->getBool("requireSession");
984 }