2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
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.
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
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.
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.
51 * shib-shire.cpp -- Shibboleth SHIRE functions
53 * Created by: Derek Atkins <derek@ihtfp.com>
65 #include <log4cpp/Category.hh>
68 using namespace log4cpp;
70 using namespace shibboleth;
71 using namespace shibtarget;
73 /* Parsing routines modified from NCSA source. */
74 static char *makeword(char *line, char stop)
77 char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
79 for(x=0;((line[x]) && (line[x] != stop));x++)
88 line[y++] = line[x++];
93 static char *fmakeword(char stop, unsigned int *cl, const char** ppch)
101 word = (char *) malloc(sizeof(char) * (wsize + 1));
105 word[ll] = *((*ppch)++);
110 word = (char *)realloc(word,sizeof(char)*(wsize+1));
113 if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
124 static char x2c(char *what)
128 digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
130 digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
134 static void unescape_url(char *url)
138 for(x=0,y=0;url[y];++x,++y)
140 if((url[x] = url[y]) == '%')
142 url[x] = x2c(&url[y+1]);
149 static void plustospace(char *str)
154 if(str[x] == '+') str[x] = ' ';
157 static inline char hexchar(unsigned short s)
159 return (s<=9) ? ('0' + s) : ('A' + s - 10);
162 static string url_encode(const char* s)
164 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
168 if (strchr(badchars,*s) || *s<=0x1F || *s>=0x7F) {
170 ret+=hexchar(*s >> 4);
171 ret+=hexchar(*s & 0x0F);
179 namespace shibtarget {
183 CgiParse(const char* data, unsigned int len);
185 const char* get_value(const char* name) const;
188 map<string,char*> kvp_map;
192 CgiParse::CgiParse(const char* data, unsigned int len)
194 const char* pch = data;
195 unsigned int cl = len;
200 value=fmakeword('&',&cl,&pch);
203 name=makeword(value,'=');
209 CgiParse::~CgiParse()
211 for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
215 const char* CgiParse::get_value(const char* name) const
217 map<string,char*>::const_iterator i=kvp_map.find(name);
218 if (i==kvp_map.end())
228 const char* SHIRE::getShireURL(const char* resource)
230 if (!m_shireURL.empty())
231 return m_shireURL.c_str();
233 bool shire_ssl_only=false;
234 const char* shire=NULL;
235 const IPropertySet* props=m_app->getPropertySet("Sessions");
237 pair<bool,bool> p=props->getBool("shireSSL");
239 shire_ssl_only=p.second;
240 pair<bool,const char*> p2=props->getString("shireURL");
245 // Should never happen...
249 // The "shireURL" property can be in one of three formats:
251 // 1) a full URI: http://host/foo/bar
252 // 2) a hostless URI: http:///foo/bar
253 // 3) a relative path: /foo/bar
255 // # Protocol Host Path
256 // 1 shire shire shire
257 // 2 shire resource shire
258 // 3 resource resource shire
260 // note: if shire_ssl_only is true, make sure the protocol is https
262 const char* path = NULL;
264 // Decide whether to use the shire or the resource for the "protocol"
274 // break apart the "protocol" string into protocol, host, and "the rest"
275 const char* colon=strchr(prot,':');
277 const char* slash=strchr(colon,'/');
281 // Compute the actual protocol and store in member.
283 m_shireURL.assign("https://");
285 m_shireURL.assign(prot, colon-prot);
287 // create the "host" from either the colon/slash or from the target string
288 // If prot == shire then we're in either #1 or #2, else #3.
289 // If slash == colon then we're in #2.
290 if (prot != shire || slash == colon) {
291 colon = strchr(resource, ':');
292 colon += 3; // Get past the ://
293 slash = strchr(colon, '/');
295 string host(colon, slash-colon);
297 // Build the shire URL
298 m_shireURL+=host + path;
299 return m_shireURL.c_str();
302 const char* SHIRE::getAuthnRequest(const char* resource)
304 if (!m_authnRequest.empty())
305 return m_authnRequest.c_str();
308 sprintf(timebuf,"%u",time(NULL));
310 const IPropertySet* props=m_app->getPropertySet("Sessions");
312 pair<bool,const char*> wayf=props->getString("wayfURL");
314 m_authnRequest=m_authnRequest + wayf.second + "?shire=" + url_encode(getShireURL(resource)) +
315 "&target=" + url_encode(resource) + "&time=" + timebuf;
316 pair<bool,bool> old=m_app->getBool("oldAuthnRequest");
317 if (!old.first || !old.second) {
318 wayf=m_app->getString("providerId");
320 m_authnRequest=m_authnRequest + "&providerId=" + wayf.second;
324 return m_authnRequest.c_str();
327 const char* SHIRE::getLazyAuthnRequest(const char* query_string)
329 CgiParse parser(query_string,strlen(query_string));
330 const char* target=parser.get_value("target");
331 if (!target || !*target)
333 return getAuthnRequest(target);
336 pair<const char*,const char*> SHIRE::getFormSubmission(const char* post, unsigned int len)
338 m_parser = new CgiParse(post,len);
339 return pair<const char*,const char*>(m_parser->get_value("SAMLResponse"),m_parser->get_value("TARGET"));
342 RPCError* SHIRE::sessionIsValid(const char* session_id, const char* ip)
344 saml::NDC ndc("sessionIsValid");
345 Category& log = Category::getInstance("shibtarget.SHIRE");
347 if (!session_id || !*session_id) {
348 log.error ("No cookie value was provided");
349 return new RPCError(SHIBRPC_NO_SESSION, "No cookie value was provided");
353 log.error ("Invalid IP Address");
354 return new RPCError(SHIBRPC_IPADDR_MISSING, "Invalid IP Address");
357 log.info ("is session valid: %s", ip);
358 log.debug ("session cookie: %s", session_id);
360 shibrpc_session_is_valid_args_1 arg;
362 arg.cookie.cookie = (char*)session_id;
363 arg.cookie.client_addr = (char *)ip;
364 arg.application_id = (char *)m_app->getId();
366 // Get rest of input from the application Session properties.
369 arg.checkIPAddress = true;
370 const IPropertySet* props=m_app->getPropertySet("Sessions");
372 pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
374 arg.lifetime = p.second;
375 p=props->getUnsignedInt("timeout");
377 arg.timeout = p.second;
378 pair<bool,bool> pcheck=props->getBool("checkAddress");
380 arg.checkIPAddress = pcheck.second;
383 shibrpc_session_is_valid_ret_1 ret;
384 memset (&ret, 0, sizeof(ret));
386 // Loop on the RPC in case we lost contact the first time through
391 clnt = rpc->connect();
392 if (shibrpc_session_is_valid_1(&arg, &ret, clnt) != RPC_SUCCESS) {
393 // FAILED. Release, disconnect, and try again...
394 log.debug("RPC Failure: %p (%p): %s", this, clnt, clnt_spcreateerror(""));
399 log.error("RPC Failure: %p (%p)", this, clnt);
400 return new RPCError(-1, "RPC Failure");
404 // SUCCESS. Return to the pool.
410 log.debug("RPC completed with status %d, %p", ret.status.status, this);
413 if (ret.status.status)
414 retval = new RPCError(&ret.status);
416 retval = new RPCError();
418 clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_session_is_valid_ret_1, (caddr_t)&ret);
420 log.debug("returning");
424 RPCError* SHIRE::sessionCreate(const char* response, const char* ip, string& cookie)
426 saml::NDC ndc("sessionCreate");
427 Category& log = Category::getInstance("shibtarget.SHIRE");
429 if (!response || !*response) {
430 log.error ("Empty SAML response content");
431 return new RPCError(-1, "Empty SAML response content");
435 log.error ("Invalid IP address");
436 return new RPCError(-1, "Invalid IP address");
439 shibrpc_new_session_args_1 arg;
440 arg.shire_location = (char*) m_shireURL.c_str();
441 arg.application_id = (char*) m_app->getId();
442 arg.saml_post = (char*)response;
443 arg.client_addr = (char*)ip;
444 arg.checkIPAddress = true;
446 log.info ("create session for user at %s for application %s", ip, arg.application_id);
448 const IPropertySet* props=m_app->getPropertySet("Sessions");
450 pair<bool,bool> pcheck=props->getBool("checkAddress");
452 arg.checkIPAddress = pcheck.second;
455 shibrpc_new_session_ret_1 ret;
456 memset (&ret, 0, sizeof(ret));
458 // Loop on the RPC in case we lost contact the first time through
463 clnt = rpc->connect();
464 if (shibrpc_new_session_1 (&arg, &ret, clnt) != RPC_SUCCESS) {
465 // FAILED. Release, disconnect, and retry
466 log.debug("RPC Failure: %p (%p): %s", this, clnt, clnt_spcreateerror (""));
471 log.error("RPC Failure: %p (%p)", this, clnt);
472 return new RPCError(-1, "RPC Failure");
476 // SUCCESS. Pool and continue
482 log.debug("RPC completed with status %d (%p)", ret.status.status, this);
485 if (ret.status.status)
486 retval = new RPCError(&ret.status);
488 log.debug ("new cookie: %s", ret.cookie);
490 retval = new RPCError();
493 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_1, (caddr_t)&ret);
495 log.debug("returning");