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 pair<const char*,const char*> SHIRE::getCookieNameProps() const
230 static const char* defProps="; path=/";
231 static const char* defName="_shibsession_";
233 const IPropertySet* props=m_app->getPropertySet("Sessions");
235 pair<bool,const char*> p=props->getString("cookieProps");
238 if (!m_cookieName.empty())
239 return pair<const char*,const char*>(m_cookieName.c_str(),p.second);
240 pair<bool,const char*> p2=props->getString("cookieName");
242 m_cookieName=p2.second;
243 return pair<const char*,const char*>(p2.second,p.second);
245 m_cookieName=defName;
246 m_cookieName+=m_app->getId();
247 return pair<const char*,const char*>(m_cookieName.c_str(),p.second);
249 m_cookieName=defName;
250 m_cookieName+=m_app->getId();
251 return pair<const char*,const char*>(m_cookieName.c_str(),defProps);
254 const char* SHIRE::getShireURL(const char* resource) const
256 if (!m_shireURL.empty())
257 return m_shireURL.c_str();
259 bool shire_ssl_only=false;
260 const char* shire=NULL;
261 const IPropertySet* props=m_app->getPropertySet("Sessions");
263 pair<bool,bool> p=props->getBool("shireSSL");
265 shire_ssl_only=p.second;
266 pair<bool,const char*> p2=props->getString("shireURL");
271 // Should never happen...
272 if (!shire || (*shire!='/' && strncmp(shire,"http:",5) && strncmp(shire,"https:",6)))
275 // The "shireURL" property can be in one of three formats:
277 // 1) a full URI: http://host/foo/bar
278 // 2) a hostless URI: http:///foo/bar
279 // 3) a relative path: /foo/bar
281 // # Protocol Host Path
282 // 1 shire shire shire
283 // 2 shire resource shire
284 // 3 resource resource shire
286 // note: if shire_ssl_only is true, make sure the protocol is https
288 const char* path = NULL;
290 // Decide whether to use the shire or the resource for the "protocol"
300 // break apart the "protocol" string into protocol, host, and "the rest"
301 const char* colon=strchr(prot,':');
303 const char* slash=strchr(colon,'/');
307 // Compute the actual protocol and store in member.
309 m_shireURL.assign("https://");
311 m_shireURL.assign(prot, colon-prot);
313 // create the "host" from either the colon/slash or from the target string
314 // If prot == shire then we're in either #1 or #2, else #3.
315 // If slash == colon then we're in #2.
316 if (prot != shire || slash == colon) {
317 colon = strchr(resource, ':');
318 colon += 3; // Get past the ://
319 slash = strchr(colon, '/');
321 string host(colon, slash-colon);
323 // Build the shire URL
324 m_shireURL+=host + path;
325 return m_shireURL.c_str();
328 const char* SHIRE::getAuthnRequest(const char* resource) const
330 if (!m_authnRequest.empty())
331 return m_authnRequest.c_str();
334 sprintf(timebuf,"%u",time(NULL));
336 const IPropertySet* props=m_app->getPropertySet("Sessions");
338 pair<bool,const char*> wayf=props->getString("wayfURL");
340 m_authnRequest=m_authnRequest + wayf.second + "?shire=" + url_encode(getShireURL(resource)) +
341 "&target=" + url_encode(resource) + "&time=" + timebuf;
342 pair<bool,bool> old=m_app->getBool("oldAuthnRequest");
343 if (!old.first || !old.second) {
344 wayf=m_app->getString("providerId");
346 m_authnRequest=m_authnRequest + "&providerId=" + url_encode(wayf.second);
350 return m_authnRequest.c_str();
353 const char* SHIRE::getLazyAuthnRequest(const char* query_string) const
355 CgiParse parser(query_string,strlen(query_string));
356 const char* target=parser.get_value("target");
357 if (!target || !*target)
359 return getAuthnRequest(target);
362 pair<const char*,const char*> SHIRE::getFormSubmission(const char* post, unsigned int len) const
364 m_parser = new CgiParse(post,len);
365 return pair<const char*,const char*>(m_parser->get_value("SAMLResponse"),m_parser->get_value("TARGET"));
368 RPCError* SHIRE::sessionIsValid(const char* session_id, const char* ip) const
370 saml::NDC ndc("sessionIsValid");
371 Category& log = Category::getInstance("shibtarget.SHIRE");
373 if (!session_id || !*session_id) {
374 log.error ("No cookie value was provided");
375 return new RPCError(SHIBRPC_NO_SESSION, "No cookie value was provided");
379 log.error ("Invalid IP Address");
380 return new RPCError(SHIBRPC_IPADDR_MISSING, "Invalid IP Address");
383 log.info ("is session valid: %s", ip);
384 log.debug ("session cookie: %s", session_id);
386 shibrpc_session_is_valid_args_1 arg;
388 arg.cookie.cookie = (char*)session_id;
389 arg.cookie.client_addr = (char *)ip;
390 arg.application_id = (char *)m_app->getId();
392 // Get rest of input from the application Session properties.
395 arg.checkIPAddress = true;
396 const IPropertySet* props=m_app->getPropertySet("Sessions");
398 pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
400 arg.lifetime = p.second;
401 p=props->getUnsignedInt("timeout");
403 arg.timeout = p.second;
404 pair<bool,bool> pcheck=props->getBool("checkAddress");
406 arg.checkIPAddress = pcheck.second;
409 shibrpc_session_is_valid_ret_1 ret;
410 memset (&ret, 0, sizeof(ret));
412 // Loop on the RPC in case we lost contact the first time through
417 clnt = rpc->connect();
418 clnt_stat status = shibrpc_session_is_valid_1(&arg, &ret, clnt);
419 if (status != RPC_SUCCESS) {
420 // FAILED. Release, disconnect, and try again...
421 log.error("RPC Failure: %p (%p) (%d) %s", this, clnt, status, clnt_spcreateerror("shibrpc_session_is_valid_1"));
426 return new RPCError(-1, "RPC Failure");
434 log.debug("RPC completed with status %d, %p", ret.status.status, this);
437 if (ret.status.status)
438 retval = new RPCError(&ret.status);
440 retval = new RPCError();
442 clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_session_is_valid_ret_1, (caddr_t)&ret);
445 log.debug("returning");
449 RPCError* SHIRE::sessionCreate(const char* response, const char* ip, string& cookie) const
451 saml::NDC ndc("sessionCreate");
452 Category& log = Category::getInstance("shibtarget.SHIRE");
454 if (!response || !*response) {
455 log.error ("Empty SAML response content");
456 return new RPCError(-1, "Empty SAML response content");
460 log.error ("Invalid IP address");
461 return new RPCError(-1, "Invalid IP address");
464 shibrpc_new_session_args_1 arg;
465 arg.shire_location = (char*) m_shireURL.c_str();
466 arg.application_id = (char*) m_app->getId();
467 arg.saml_post = (char*)response;
468 arg.client_addr = (char*)ip;
469 arg.checkIPAddress = true;
471 log.info ("create session for user at %s for application %s", ip, arg.application_id);
473 const IPropertySet* props=m_app->getPropertySet("Sessions");
475 pair<bool,bool> pcheck=props->getBool("checkAddress");
477 arg.checkIPAddress = pcheck.second;
480 shibrpc_new_session_ret_1 ret;
481 memset (&ret, 0, sizeof(ret));
483 // Loop on the RPC in case we lost contact the first time through
488 clnt = rpc->connect();
489 clnt_stat status = shibrpc_new_session_1 (&arg, &ret, clnt);
490 if (status != RPC_SUCCESS) {
491 // FAILED. Release, disconnect, and retry
492 log.error("RPC Failure: %p (%p) (%d): %s", this, clnt, status, clnt_spcreateerror("shibrpc_new_session_1"));
497 return new RPCError(-1, "RPC Failure");
500 // SUCCESS. Pool and continue
505 log.debug("RPC completed with status %d (%p)", ret.status.status, this);
508 if (ret.status.status)
509 retval = new RPCError(&ret.status);
511 log.debug ("new cookie: %s", ret.cookie);
513 retval = new RPCError();
516 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_1, (caddr_t)&ret);
519 log.debug("returning");