2dbbe2897dc0c88506b63c27a94c45f730dd41d3
[shibboleth/sp.git] / shib-target / shib-shire.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-shire.cpp -- Shibboleth SHIRE functions
52  *
53  * Created by:    Derek Atkins <derek@ihtfp.com>
54  *
55  * $Id$
56  */
57
58 #include "internal.h"
59
60 #ifdef HAVE_UNISTD_H
61 # include <unistd.h>
62 #endif
63
64 #include <stdexcept>
65 #include <log4cpp/Category.hh>
66
67 using namespace std;
68 using namespace log4cpp;
69 using namespace saml;
70 using namespace shibboleth;
71 using namespace shibtarget;
72
73 /* Parsing routines modified from NCSA source. */
74 static char *makeword(char *line, char stop)
75 {
76     int x = 0,y;
77     char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
78
79     for(x=0;((line[x]) && (line[x] != stop));x++)
80         word[x] = line[x];
81
82     word[x] = '\0';
83     if(line[x])
84         ++x;
85     y=0;
86
87     while(line[x])
88       line[y++] = line[x++];
89     line[y] = '\0';
90     return word;
91 }
92
93 static char *fmakeword(char stop, unsigned int *cl, const char** ppch)
94 {
95     int wsize;
96     char *word;
97     int ll;
98
99     wsize = 1024;
100     ll=0;
101     word = (char *) malloc(sizeof(char) * (wsize + 1));
102
103     while(1)
104     {
105         word[ll] = *((*ppch)++);
106         if(ll==wsize-1)
107         {
108             word[ll+1] = '\0';
109             wsize+=1024;
110             word = (char *)realloc(word,sizeof(char)*(wsize+1));
111         }
112         --(*cl);
113         if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
114         {
115             if(word[ll] != stop)
116                 ll++;
117             word[ll] = '\0';
118             return word;
119         }
120         ++ll;
121     }
122 }
123
124 static char x2c(char *what)
125 {
126     register char digit;
127
128     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
129     digit *= 16;
130     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
131     return(digit);
132 }
133
134 static void unescape_url(char *url)
135 {
136     register int x,y;
137
138     for(x=0,y=0;url[y];++x,++y)
139     {
140         if((url[x] = url[y]) == '%')
141         {
142             url[x] = x2c(&url[y+1]);
143             y+=2;
144         }
145     }
146     url[x] = '\0';
147 }
148
149 static void plustospace(char *str)
150 {
151     register int x;
152
153     for(x=0;str[x];x++)
154         if(str[x] == '+') str[x] = ' ';
155 }
156
157 static inline char hexchar(unsigned short s)
158 {
159     return (s<=9) ? ('0' + s) : ('A' + s - 10);
160 }
161
162 static string url_encode(const char* s)
163 {
164     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
165
166     string ret;
167     for (; *s; s++) {
168         if (strchr(badchars,*s) || *s<=0x1F || *s>=0x7F) {
169             ret+='%';
170         ret+=hexchar(*s >> 4);
171         ret+=hexchar(*s & 0x0F);
172         }
173         else
174             ret+=*s;
175     }
176     return ret;
177 }
178
179 namespace shibtarget {
180     class CgiParse
181     {
182     public:
183         CgiParse(const char* data, unsigned int len);
184         ~CgiParse();
185         const char* get_value(const char* name) const;
186     
187     private:
188         map<string,char*> kvp_map;
189     };
190 }
191
192 CgiParse::CgiParse(const char* data, unsigned int len)
193 {
194     const char* pch = data;
195     unsigned int cl = len;
196         
197     while (cl && pch) {
198         char *name;
199         char *value;
200         value=fmakeword('&',&cl,&pch);
201         plustospace(value);
202         unescape_url(value);
203         name=makeword(value,'=');
204         kvp_map[name]=value;
205         free(name);
206     }
207 }
208
209 CgiParse::~CgiParse()
210 {
211     for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
212         free(i->second);
213 }
214
215 const char* CgiParse::get_value(const char* name) const
216 {
217     map<string,char*>::const_iterator i=kvp_map.find(name);
218     if (i==kvp_map.end())
219         return NULL;
220     return i->second;
221 }
222
223 SHIRE::~SHIRE()
224 {
225     delete m_parser;
226 }
227
228 const char* SHIRE::getShireURL(const char* resource)
229 {
230     if (!m_shireURL.empty())
231         return m_shireURL.c_str();
232
233     bool shire_ssl_only=false;
234     const char* shire=NULL;
235     const IPropertySet* props=m_app->getPropertySet("Sessions");
236     if (props) {
237         pair<bool,bool> p=props->getBool("shireSSL");
238         if (p.first)
239             shire_ssl_only=p.second;
240         pair<bool,const char*> p2=props->getString("shireURL");
241         if (p2.first)
242             shire=p2.second;
243     }
244     
245     // Should never happen...
246     if (!shire)
247         return NULL;
248
249     // The "shireURL" property can be in one of three formats:
250     //
251     // 1) a full URI:       http://host/foo/bar
252     // 2) a hostless URI:   http:///foo/bar
253     // 3) a relative path:  /foo/bar
254     //
255     // #  Protocol  Host        Path
256     // 1  shire     shire       shire
257     // 2  shire     resource    shire
258     // 3  resource  resource    shire
259     //
260     // note: if shire_ssl_only is true, make sure the protocol is https
261
262     const char* path = NULL;
263
264     // Decide whether to use the shire or the resource for the "protocol"
265     const char* prot;
266     if (*shire != '/') {
267         prot = shire;
268     }
269     else {
270         prot = resource;
271         path = shire;
272     }
273
274     // break apart the "protocol" string into protocol, host, and "the rest"
275     const char* colon=strchr(prot,':');
276     colon += 3;
277     const char* slash=strchr(colon,'/');
278     if (!path)
279         path = slash;
280
281     // Compute the actual protocol and store in member.
282     if (shire_ssl_only)
283         m_shireURL.assign("https://");
284     else
285         m_shireURL.assign(prot, colon-prot);
286
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, '/');
294     }
295     string host(colon, slash-colon);
296
297     // Build the shire URL
298     m_shireURL+=host + path;
299     return m_shireURL.c_str();
300 }
301
302 const char* SHIRE::getAuthnRequest(const char* resource)
303 {
304     if (!m_authnRequest.empty())
305         return m_authnRequest.c_str();
306         
307     char timebuf[16];
308     sprintf(timebuf,"%u",time(NULL));
309     
310     const IPropertySet* props=m_app->getPropertySet("Sessions");
311     if (props) {
312         pair<bool,const char*> wayf=props->getString("wayfURL");
313         if (wayf.first) {
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");
319                 if (wayf.first)
320                     m_authnRequest=m_authnRequest + "&providerId=" + wayf.second;
321             }
322         }
323     }
324     return m_authnRequest.c_str();
325 }
326
327 const char* SHIRE::getLazyAuthnRequest(const char* query_string)
328 {
329     CgiParse parser(query_string,strlen(query_string));
330     const char* target=parser.get_value("target");
331     if (!target || !*target)
332         return NULL;
333     return getAuthnRequest(target);
334 }
335
336 pair<const char*,const char*> SHIRE::getFormSubmission(const char* post, unsigned int len)
337 {
338     m_parser = new CgiParse(post,len);
339     return pair<const char*,const char*>(m_parser->get_value("SAMLResponse"),m_parser->get_value("TARGET"));
340 }
341
342 RPCError* SHIRE::sessionIsValid(const char* session_id, const char* ip)
343 {
344   saml::NDC ndc("sessionIsValid");
345   Category& log = Category::getInstance("shibtarget.SHIRE");
346
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");
350   }
351
352   if (!ip || !*ip) {
353     log.error ("Invalid IP Address");
354     return new RPCError(SHIBRPC_IPADDR_MISSING, "Invalid IP Address");
355   }
356
357   log.info ("is session valid: %s", ip);
358   log.debug ("session cookie: %s", session_id);
359
360   shibrpc_session_is_valid_args_1 arg;
361
362   arg.cookie.cookie = (char*)session_id;
363   arg.cookie.client_addr = (char *)ip;
364   arg.application_id = (char *)m_app->getId();
365   
366   // Get rest of input from the application Session properties.
367   arg.lifetime = 3600;
368   arg.timeout = 1800;
369   arg.checkIPAddress = true;
370   const IPropertySet* props=m_app->getPropertySet("Sessions");
371   if (props) {
372       pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
373       if (p.first)
374           arg.lifetime = p.second;
375       p=props->getUnsignedInt("timeout");
376       if (p.first)
377           arg.timeout = p.second;
378       pair<bool,bool> pcheck=props->getBool("checkAddress");
379       if (pcheck.first)
380           arg.checkIPAddress = pcheck.second;
381   }
382   
383   shibrpc_session_is_valid_ret_1 ret;
384   memset (&ret, 0, sizeof(ret));
385
386   // Loop on the RPC in case we lost contact the first time through
387   int retry = 1;
388   CLIENT *clnt;
389   RPC rpc;
390   do {
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(""));
395       rpc->disconnect();
396       if (retry)
397           retry--;
398       else {
399         log.error("RPC Failure: %p (%p)", this, clnt);
400         return new RPCError(-1, "RPC Failure");
401       }
402     }
403     else {
404       // SUCCESS.  Return to the pool.
405       rpc.pool();
406       retry = -1;
407     }
408   } while (retry>=0);
409
410   log.debug("RPC completed with status %d, %p", ret.status.status, this);
411
412   RPCError* retval;
413   if (ret.status.status)
414     retval = new RPCError(&ret.status);
415   else
416     retval = new RPCError();
417
418   clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_session_is_valid_ret_1, (caddr_t)&ret);
419
420   log.debug("returning");
421   return retval;
422 }
423
424 RPCError* SHIRE::sessionCreate(const char* response, const char* ip, string& cookie)
425 {
426   saml::NDC ndc("sessionCreate");
427   Category& log = Category::getInstance("shibtarget.SHIRE");
428
429   if (!response || !*response) {
430     log.error ("Empty SAML response content");
431     return new RPCError(-1,  "Empty SAML response content");
432   }
433
434   if (!ip || !*ip) {
435     log.error ("Invalid IP address");
436     return new RPCError(-1, "Invalid IP address");
437   }
438   
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;
445
446   log.info ("create session for user at %s for application %s", ip, arg.application_id);
447
448   const IPropertySet* props=m_app->getPropertySet("Sessions");
449   if (props) {
450       pair<bool,bool> pcheck=props->getBool("checkAddress");
451       if (pcheck.first)
452           arg.checkIPAddress = pcheck.second;
453   }
454
455   shibrpc_new_session_ret_1 ret;
456   memset (&ret, 0, sizeof(ret));
457
458   // Loop on the RPC in case we lost contact the first time through
459   int retry = 1;
460   CLIENT* clnt;
461   RPC rpc;
462   do {
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 (""));
467       rpc->disconnect();
468       if (retry)
469        retry--;
470       else {
471         log.error("RPC Failure: %p (%p)", this, clnt);
472         return new RPCError(-1, "RPC Failure");
473       }
474     }
475     else {
476       // SUCCESS.  Pool and continue
477       rpc.pool();
478       retry = -1;
479     }
480   } while (retry>=0);
481
482   log.debug("RPC completed with status %d (%p)", ret.status.status, this);
483
484   RPCError* retval;
485   if (ret.status.status)
486     retval = new RPCError(&ret.status);
487   else {
488     log.debug ("new cookie: %s", ret.cookie);
489     cookie = ret.cookie;
490     retval = new RPCError();
491   }
492
493   clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_1, (caddr_t)&ret);
494
495   log.debug("returning");
496   return retval;
497 }