Pull nsapi_shib up to HEAD.
[shibboleth/sp.git] / nsapi_shib / nsapi_shib.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 /* nsapi_shib.cpp - Shibboleth NSAPI filter
51
52    Scott Cantor
53    12/13/04
54 */
55
56 #include "config_win32.h"
57
58 // SAML Runtime
59 #include <saml/saml.h>
60 #include <shib/shib.h>
61 #include <shib/shib-threads.h>
62 #include <shib-target/shib-target.h>
63
64 #include <log4cpp/Category.hh>
65
66 #include <ctime>
67 #include <fstream>
68 #include <sstream>
69 #include <stdexcept>
70
71 #ifdef WIN32
72 # define XP_WIN32
73 #else
74 # define XP_UNIX
75 #endif
76
77 #define MCC_HTTPD
78 #define NET_SSL
79
80 extern "C"
81 {
82 #include <nsapi.h>
83 }
84
85 using namespace std;
86 using namespace log4cpp;
87 using namespace saml;
88 using namespace shibboleth;
89 using namespace shibtarget;
90
91 // macros to output text to client
92 #define NET_WRITE(str) \
93     if (IO_ERROR==net_write(sn->csd,str,strlen(str))) return REQ_EXIT
94
95 #define NET_WRITE1(buf,fmstr,param) \
96     do { sprintf(buf,fmstr,param); NET_WRITE(buf); } while(0)
97
98 #define NET_WRITE2(buf,fmstr,param1,param2) \
99     do { sprintf(buf,fmstr,param1,param2); NET_WRITE(buf); } while(0)
100
101 #define NET_WRITE3(buf,fmstr,param1,param2,param3) \
102     do { sprintf(buf,fmstr,param1,param2,param3); NET_WRITE(buf); } while(0)
103
104 #define NET_WRITE4(buf,fmstr,param1,param2,param3,param4) \
105     do { sprintf(buf,fmstr,param1,param2,param3,param4); NET_WRITE(buf); } while(0)
106
107 namespace {
108     ShibTargetConfig* g_Config=NULL;
109     string g_ServerName;
110     string g_ServerScheme;
111 }
112
113 extern "C" NSAPI_PUBLIC void nsapi_shib_exit(void*)
114 {
115     if (g_Config)
116         g_Config->shutdown();
117     g_Config = NULL;
118 }
119
120 extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq)
121 {
122     // Save off a default hostname for this virtual server.
123     char* name=pblock_findval("server-name",pb);
124     if (name)
125         g_ServerName=name;
126     else {
127         name=server_hostname;
128         if (name)
129             g_ServerName=name;
130         else {
131             name=util_hostname();
132             if (name) {
133                 g_ServerName=name;
134                 FREE(name);
135             }
136             else {
137                 pblock_nvinsert("error","unable to determine web server hostname",pb);
138                 return REQ_ABORTED;
139             }
140         }
141     }
142     name=pblock_findval("server-scheme",pb);
143     if (name)
144         g_ServerScheme=name;
145
146     log_error(LOG_INFORM,"nsapi_shib_init",sn,rq,"nsapi_shib loaded for host (%s)",g_ServerName.c_str());
147
148     try
149     {
150         const char* schemadir=pblock_findval("shib-schemas",pb);
151         if (!schemadir)
152             schemadir=getenv("SHIBSCHEMAS");
153         if (!schemadir)
154             schemadir=SHIB_SCHEMAS;
155         const char* config=pblock_findval("shib-config",pb);
156         if (!config)
157             config=getenv("SHIBCONFIG");
158         if (!config)
159             config=SHIB_CONFIG;
160         g_Config=&ShibTargetConfig::getConfig();
161         g_Config->setFeatures(
162             ShibTargetConfig::Listener |
163             ShibTargetConfig::Metadata |
164             ShibTargetConfig::AAP |
165             ShibTargetConfig::RequestMapper |
166             ShibTargetConfig::SHIREExtensions |
167             ShibTargetConfig::Logging
168             );
169         if (!g_Config->init(schemadir,config)) {
170             g_Config=NULL;
171             pblock_nvinsert("error","unable to initialize Shibboleth libraries",pb);
172             return REQ_ABORTED;
173         }
174
175         daemon_atrestart(nsapi_shib_exit,NULL);
176     }
177     catch (...)
178     {
179 #ifdef _DEBUG
180         throw;
181 #endif
182         g_Config=NULL;
183         pblock_nvinsert("error","caught exception, unable to initialize Shibboleth libraries",pb);
184         return REQ_ABORTED;
185     }
186     return REQ_PROCEED;
187 }
188
189 IRequestMapper::Settings map_request(pblock* pb, Session* sn, Request* rq, IRequestMapper* mapper, string& target)
190 {
191     // Get everything but hostname...
192     const char* uri=pblock_findval("uri",rq->reqpb);
193     const char* qstr=pblock_findval("query",rq->reqpb);
194     int port=server_portnum;
195     const char* scheme=security_active ? "https" : "http";
196     const char* host=NULL;
197
198     string url;
199     if (uri)
200         url=uri;
201     if (qstr)
202         url=url + '?' + qstr;
203     
204 #ifdef vs_is_default_vs
205     // This is 6.0 or later, so we can distinguish requests to name-based vhosts.
206     if (!vs_is_default_vs)
207         // The beauty here is, a non-default vhost can *only* be accessed if the client
208         // specified the exact name in the Host header. So we can trust the Host header.
209         host=pblock_findval("host", rq->headers);
210     else
211 #endif
212     // In other cases, we're going to rely on the initialization process...
213     host=g_ServerName.c_str();
214         
215     target=(g_ServerScheme.empty() ? string(scheme) : g_ServerScheme) + "://" + host;
216     
217     // If port is non-default, append it.
218     if ((!security_active && port!=80) || (security_active && port!=443)) {
219         char portbuf[10];
220         util_snprintf(portbuf,9,"%d",port);
221         target = target + ':' + portbuf;
222     }
223
224     target+=url;
225         
226     return mapper->getSettingsFromParsedURL(scheme,host,port,url.c_str());
227 }
228
229 int WriteClientError(Session* sn, Request* rq, char* func, char* msg)
230 {
231     log_error(LOG_FAILURE,func,sn,rq,msg);
232     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,msg);
233     return REQ_ABORTED;
234 }
235
236 int WriteClientError(Session* sn, Request* rq, const IApplication* app, const char* page, ShibMLP& mlp)
237 {
238     const IPropertySet* props=app->getPropertySet("Errors");
239     if (props) {
240         pair<bool,const char*> p=props->getString(page);
241         if (p.first) {
242             ifstream infile(p.second);
243             if (!infile.fail()) {
244                 const char* res = mlp.run(infile,props);
245                 if (res) {
246                     pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
247                     pblock_nninsert("Content-Length",strlen(res),rq->srvhdrs);
248                     pblock_nvinsert("Connection","close",rq->srvhdrs);
249                     protocol_status(sn,rq,PROTOCOL_OK,NULL);
250                     NET_WRITE(const_cast<char*>(res));
251                     return REQ_EXIT;
252                 }
253             }
254         }
255     }
256
257     log_error(LOG_FAILURE,"WriteClientError",sn,rq,"Unable to open error template, check settings.");
258     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,"Unable to open error template, check settings.");
259     return REQ_ABORTED;
260 }
261
262 int WriteRedirectPage(Session* sn, Request* rq, const IApplication* app, const char* file, ShibMLP& mlp)
263 {
264     ifstream infile(file);
265     if (!infile.fail()) {
266         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
267         if (res) {
268             pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
269             pblock_nninsert("Content-Length",strlen(res),rq->srvhdrs);
270             protocol_status(sn,rq,PROTOCOL_OK,NULL);
271             NET_WRITE(const_cast<char*>(res));
272             return REQ_EXIT;
273         }
274     }
275     log_error(LOG_FAILURE,"WriteRedirectPage",sn,rq,"Unable to open redirect template, check settings.");
276     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,"Unable to open redirect template, check settings.");
277     return REQ_ABORTED;
278 }
279
280 #undef FUNC
281 #define FUNC "shibboleth"
282 extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
283 {
284     try
285     {
286         ostringstream threadid;
287         threadid << "[" << getpid() << "] nsapi_shib" << '\0';
288         saml::NDC ndc(threadid.str().c_str());
289         
290         // We lock the configuration system for the duration.
291         IConfig* conf=g_Config->getINI();
292         Locker locker(conf);
293         
294         // Map request to application and content settings.
295         string targeturl;
296         IRequestMapper* mapper=conf->getRequestMapper();
297         Locker locker2(mapper);
298         IRequestMapper::Settings settings=map_request(pb,sn,rq,mapper,targeturl);
299         pair<bool,const char*> application_id=settings.first->getString("applicationId");
300         const IApplication* application=conf->getApplication(application_id.second);
301         if (!application)
302             return WriteClientError(sn,rq,FUNC,"Unable to map request to application settings, check configuration.");
303         
304         // Declare SHIRE object for this request.
305         SHIRE shire(application);
306         
307         const char* shireURL=shire.getShireURL(targeturl.c_str());
308         if (!shireURL)
309             return WriteClientError(sn,rq,FUNC,"Unable to map request to proper shireURL setting, check configuration.");
310
311         // If the user is accessing the SHIRE acceptance point, pass it on.
312         if (targeturl.find(shireURL)!=string::npos)
313             return REQ_PROCEED;
314
315         // Now check the policy for this request.
316         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
317         if (!requireSession.first || !requireSession.second) {
318             const char* param=pblock_findval("require-session",pb);
319             if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
320                 requireSession.second=true;
321         }
322         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
323         pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
324         pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
325         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
326             return WriteClientError(sn,rq,FUNC,"HTML-based redirection requires a redirectPage property.");
327
328         // Check for session cookie.
329         const char* session_id=NULL;
330         string cookie;
331         if (request_header("cookie",(char**)&session_id,sn,rq)==REQ_ABORTED)
332             return WriteClientError(sn,rq,FUNC,"error accessing cookie header");
333
334         Category::getInstance("nsapi_shib."FUNC).debug("cookie header is {%s}",session_id ? session_id : "NULL");
335         if (session_id && (session_id=strstr(session_id,shib_cookie.first))) {
336             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
337             char* cookieend=strchr(session_id,';');
338             if (cookieend) {
339                 // Chop out just the value portion.
340                 cookie.assign(session_id,cookieend-session_id-1);
341                 session_id=cookie.c_str();
342             }
343         }
344         
345         if (!session_id || !*session_id) {
346             // If no session required, bail now.
347             if (!requireSession.second)
348                 return REQ_PROCEED;
349     
350             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
351             const char* areq = shire.getAuthnRequest(targeturl.c_str());
352             if (!httpRedirects.first || httpRedirects.second) {
353                 pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
354                 pblock_nvinsert("Content-Length","40",rq->srvhdrs);
355                 pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
356                 pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
357                 pblock_nvinsert("Location",areq,rq->srvhdrs);
358                 protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
359                 protocol_start_response(sn,rq);
360                 NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
361                 return REQ_EXIT;
362             }
363             else {
364                 ShibMLP markupProcessor;
365                 markupProcessor.insert("requestURL",areq);
366                 return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
367             }
368         }
369
370         // Make sure this session is still valid.
371         RPCError* status = NULL;
372         ShibMLP markupProcessor;
373         markupProcessor.insert("requestURL", targeturl);
374     
375         try {
376             status = shire.sessionIsValid(session_id, pblock_findval("ip",sn->client));
377         }
378         catch (ShibTargetException &e) {
379             markupProcessor.insert("errorType", "Session Processing Error");
380             markupProcessor.insert("errorText", e.what());
381             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
382             return WriteClientError(sn, rq, application, "shire", markupProcessor);
383         }
384 #ifndef _DEBUG
385         catch (...) {
386             markupProcessor.insert("errorType", "Session Processing Error");
387             markupProcessor.insert("errorText", "Unexpected Exception");
388             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
389             return WriteClientError(sn, rq, application, "shire", markupProcessor);
390         }
391 #endif
392
393         // Check the status
394         if (status->isError()) {
395             if (!requireSession.second)
396                 return REQ_PROCEED;
397             else if (status->isRetryable()) {
398                 // Oops, session is invalid. Generate AuthnRequest.
399                 delete status;
400                 const char* areq = shire.getAuthnRequest(targeturl.c_str());
401                 if (!httpRedirects.first || httpRedirects.second) {
402                     pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
403                     pblock_nvinsert("Content-Length","40",rq->srvhdrs);
404                     pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
405                     pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
406                     pblock_nvinsert("Location",areq,rq->srvhdrs);
407                     protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
408                     protocol_start_response(sn,rq);
409                     NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
410                     return REQ_EXIT;
411                 }
412                 else {
413                     markupProcessor.insert("requestURL",areq);
414                     return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
415                 }
416             }
417             else {
418                 // return the error page to the user
419                 markupProcessor.insert(*status);
420                 delete status;
421                 return WriteClientError(sn, rq, application, "shire", markupProcessor);
422             }
423         }
424         delete status;
425     
426         // Move to RM phase.
427         RM rm(application);
428         vector<SAMLAssertion*> assertions;
429         SAMLAuthenticationStatement* sso_statement=NULL;
430
431         try {
432             status = rm.getAssertions(session_id, pblock_findval("ip",sn->client), assertions, &sso_statement);
433         }
434         catch (ShibTargetException &e) {
435             markupProcessor.insert("errorType", "Attribute Processing Error");
436             markupProcessor.insert("errorText", e.what());
437             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
438             return WriteClientError(sn, rq, application, "rm", markupProcessor);
439         }
440     #ifndef _DEBUG
441         catch (...) {
442             markupProcessor.insert("errorType", "Attribute Processing Error");
443             markupProcessor.insert("errorText", "Unexpected Exception");
444             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
445             return WriteClientError(sn, rq, application, "rm", markupProcessor);
446         }
447     #endif
448     
449         if (status->isError()) {
450             markupProcessor.insert(*status);
451             delete status;
452             return WriteClientError(sn, rq, application, "rm", markupProcessor);
453         }
454         delete status;
455
456         // Do we have an access control plugin?
457         if (settings.second) {
458             Locker acllock(settings.second);
459             if (!settings.second->authorized(*sso_statement,assertions)) {
460                 for (int k = 0; k < assertions.size(); k++)
461                     delete assertions[k];
462                 delete sso_statement;
463                 return WriteClientError(sn, rq, application, "access", markupProcessor);
464             }
465         }
466
467         // Get the AAP providers, which contain the attribute policy info.
468         Iterator<IAAP*> provs=application->getAAPProviders();
469     
470         // Clear out the list of mapped attributes
471         while (provs.hasNext()) {
472             IAAP* aap=provs.next();
473             aap->lock();
474             try {
475                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
476                 while (rules.hasNext()) {
477                     const char* header=rules.next()->getHeader();
478                     if (header)
479                         param_free(pblock_remove(header,rq->headers));
480                 }
481             }
482             catch(...) {
483                 aap->unlock();
484                 for (int k = 0; k < assertions.size(); k++)
485                   delete assertions[k];
486                 delete sso_statement;
487                 markupProcessor.insert("errorType", "Attribute Processing Error");
488                 markupProcessor.insert("errorText", "Unexpected Exception");
489                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
490                 return WriteClientError(sn, rq, application, "rm", markupProcessor);
491             }
492             aap->unlock();
493         }
494         provs.reset();
495
496         // Maybe export the first assertion.
497         param_free(pblock_remove("remote-user",rq->headers));
498         param_free(pblock_remove("auth-user",rq->vars));
499         param_free(pblock_remove("Shib-Attributes",rq->headers));
500         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
501         if (!exp.first || !exp.second) {
502             const char* param=pblock_findval("export-assertion",pb);
503             if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
504                 exp.second=true;
505         }
506         if (exp.second && assertions.size()) {
507             string assertion;
508             RM::serialize(*(assertions[0]), assertion);
509             string::size_type lfeed;
510             while ((lfeed=assertion.find('\n'))!=string::npos)
511                 assertion.erase(lfeed,1);
512             pblock_nvinsert("Shib-Attributes",assertion.c_str(),rq->headers);
513         }
514         
515         pblock_nvinsert("auth-type","shibboleth",rq->vars);
516         param_free(pblock_remove("Shib-Origin-Site",rq->headers));
517         param_free(pblock_remove("Shib-Authentication-Method",rq->headers));
518         param_free(pblock_remove("Shib-NameIdentifier-Format",rq->headers));
519
520         // Export the SAML AuthnMethod and the origin site name.
521         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
522         auto_ptr_char am(sso_statement->getAuthMethod());
523         pblock_nvinsert("Shib-Origin-Site",os.get(),rq->headers);
524         pblock_nvinsert("Shib-Authentication-Method",am.get(),rq->headers);
525
526         // Export NameID?
527         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
528         if (!wrapper.fail() && wrapper->getHeader()) {
529             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
530             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
531             pblock_nvinsert("Shib-NameIdentifier-Format",form.get(),pb);
532             if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
533                 pblock_nvinsert("remote-user",nameid.get(),rq->headers);
534                 pblock_nvinsert("auth-user",nameid.get(),rq->vars);
535             }
536             else {
537                 pblock_nvinsert(wrapper->getHeader(),nameid.get(),rq->headers);
538             }
539         }
540
541         param_free(pblock_remove("Shib-Application-ID",rq->headers));
542         pblock_nvinsert("Shib-Application-ID",application_id.second,rq->headers);
543
544         // Export the attributes.
545         Iterator<SAMLAssertion*> a_iter(assertions);
546         while (a_iter.hasNext()) {
547             SAMLAssertion* assert=a_iter.next();
548             Iterator<SAMLStatement*> statements=assert->getStatements();
549             while (statements.hasNext()) {
550                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
551                 if (!astate)
552                     continue;
553                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
554                 while (attrs.hasNext()) {
555                     SAMLAttribute* attr=attrs.next();
556         
557                     // Are we supposed to export it?
558                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
559                     if (wrapper.fail() || !wrapper->getHeader())
560                         continue;
561                 
562                     Iterator<string> vals=attr->getSingleByteValues();
563                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
564                         char* principal=const_cast<char*>(vals.next().c_str());
565                         pblock_nvinsert("remote-user",principal,rq->headers);
566                         pblock_nvinsert("auth-user",principal,rq->vars);
567                     }
568                     else {
569                         int it=0;
570                         string header;
571                         const char* h=pblock_findval(wrapper->getHeader(),rq->headers);
572                         if (h) {
573                             header=h;
574                             param_free(pblock_remove(wrapper->getHeader(),rq->headers));
575                             it++;
576                         }
577                         for (; vals.hasNext(); it++) {
578                             string value = vals.next();
579                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
580                                     pos != string::npos;
581                                     pos = value.find_first_of(";", pos)) {
582                                 value.insert(pos, "\\");
583                                 pos += 2;
584                             }
585                             if (it == 0)
586                                 header=value;
587                             else
588                                 header=header + ';' + value;
589                         }
590                         pblock_nvinsert(wrapper->getHeader(),header.c_str(),rq->headers);
591                         }
592                 }
593             }
594         }
595     
596         // clean up memory
597         for (int k = 0; k < assertions.size(); k++)
598           delete assertions[k];
599         delete sso_statement;
600
601         return REQ_PROCEED;
602     }
603     catch(bad_alloc) {
604         return WriteClientError(sn, rq, FUNC,"Out of Memory");
605     }
606 #ifndef _DEBUG
607     catch(...) {
608         return WriteClientError(sn, rq, FUNC,"Server caught an unknown exception.");
609     }
610 #endif
611
612     return WriteClientError(sn, rq, FUNC,"Server reached unreachable code, save my walrus!");
613 }
614
615 #undef FUNC
616 #define FUNC "shib_handler"
617 extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq)
618 {
619     string targeturl;
620     const IApplication* application=NULL;
621     try
622     {
623         ostringstream threadid;
624         threadid << "[" << getpid() << "] shib_handler" << '\0';
625         saml::NDC ndc(threadid.str().c_str());
626
627         // We lock the configuration system for the duration.
628         IConfig* conf=g_Config->getINI();
629         Locker locker(conf);
630         
631         // Map request to application and content settings.
632         IRequestMapper* mapper=conf->getRequestMapper();
633         Locker locker2(mapper);
634         IRequestMapper::Settings settings=map_request(pb,sn,rq,mapper,targeturl);
635         pair<bool,const char*> application_id=settings.first->getString("applicationId");
636         application=conf->getApplication(application_id.second);
637         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
638         if (!application || !sessionProps)
639             return WriteClientError(sn,rq,FUNC,"Unable to map request to application settings, check configuration.");
640
641         SHIRE shire(application);
642         
643         const char* shireURL=shire.getShireURL(targeturl.c_str());
644         if (!shireURL)
645             return WriteClientError(sn,rq,FUNC,"Unable to map request to proper shireURL setting, check configuration.");
646
647         // Make sure we only process the SHIRE requests.
648         if (!strstr(targeturl.c_str(),shireURL))
649             return WriteClientError(sn,rq,FUNC,"NSAPI service function can only be invoked to process incoming sessions."
650                 "Make sure the mapped file extension or URL doesn't match actual content.");
651
652         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
653
654         // Make sure this is SSL, if it should be
655         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
656         if (!shireSSL.first || shireSSL.second) {
657             if (!security_active)
658                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to Shibboleth session processor");
659         }
660         
661         pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
662         pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
663         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
664             return WriteClientError(sn,rq,FUNC,"HTML-based redirection requires a redirectPage property.");
665                 
666         // If this is a GET, we manufacture an AuthnRequest.
667         if (!strcasecmp(pblock_findval("method",rq->reqpb),"GET")) {
668             const char* areq=pblock_findval("query",rq->reqpb) ? shire.getLazyAuthnRequest(pblock_findval("query",rq->reqpb)) : NULL;
669             if (!areq)
670                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
671             if (!httpRedirects.first || httpRedirects.second) {
672                 pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
673                 pblock_nvinsert("Content-Length","40",rq->srvhdrs);
674                 pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
675                 pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
676                 pblock_nvinsert("Location",areq,rq->srvhdrs);
677                 protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
678                 protocol_start_response(sn,rq);
679                 NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
680                 return REQ_EXIT;
681             }
682             else {
683                 ShibMLP markupProcessor;
684                 markupProcessor.insert("requestURL",areq);
685                 return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
686             }
687         }
688         else if (strcasecmp(pblock_findval("method",rq->reqpb),"POST"))
689             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to Shibboleth session processor");
690
691         // Make sure this POST is an appropriate content type
692         char* content_type=NULL;
693         if (request_header("content-type",&content_type,sn,rq)!=REQ_PROCEED ||
694                 !content_type || strcasecmp(content_type,"application/x-www-form-urlencoded"))
695             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to Shibboleth session processor");
696     
697         // Read the data.
698         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
699         char* content_length=NULL;
700         if (request_header("content-length",&content_length,sn,rq)!=REQ_PROCEED ||
701                 atoi(content_length) > 1024*1024) // 1MB?
702             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to Shibboleth session processor");
703         else {
704             char ch=IO_EOF+1;
705             int cl=atoi(content_length);
706             string cgistr;
707             while (cl && ch!=IO_EOF) {
708                 ch=netbuf_getc(sn->inbuf);
709         
710                 // Check for error.
711                 if(ch==IO_ERROR)
712                     break;
713                 cgistr+=ch;
714                 cl--;
715             }
716             if (cl)
717                 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
718             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
719         }
720     
721         // Make sure the SAML Response parameter exists
722         if (!elements.first || !*elements.first)
723             throw ShibTargetException(SHIBRPC_OK, "Shibboleth POST failed to find SAMLResponse form element");
724     
725         // Make sure the target parameter exists
726         if (!elements.second || !*elements.second)
727             throw ShibTargetException(SHIBRPC_OK, "Shibboleth POST failed to find TARGET form element");
728             
729         // Process the post.
730         string cookie;
731         RPCError* status=NULL;
732         ShibMLP markupProcessor;
733         markupProcessor.insert("requestURL", targeturl.c_str());
734         try {
735             status = shire.sessionCreate(elements.first,pblock_findval("ip",sn->client),cookie);
736         }
737         catch (ShibTargetException &e) {
738             markupProcessor.insert("errorType", "Session Creation Service Error");
739             markupProcessor.insert("errorText", e.what());
740             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
741             return WriteClientError(sn, rq, application, "shire", markupProcessor);
742         }
743 #ifndef _DEBUG
744         catch (...) {
745             markupProcessor.insert("errorType", "Session Creation Service Error");
746             markupProcessor.insert("errorText", "Unexpected Exception");
747             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
748             return WriteClientError(sn, rq, application, "shire", markupProcessor);
749         }
750 #endif
751
752         if (status->isError()) {
753             if (status->isRetryable()) {
754                 delete status;
755                 const char* loc=shire.getAuthnRequest(elements.second);
756                 if (!httpRedirects.first || httpRedirects.second) {
757                     pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
758                     pblock_nvinsert("Content-Length","40",rq->srvhdrs);
759                     pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
760                     pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
761                     pblock_nvinsert("Location",loc,rq->srvhdrs);
762                     protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
763                     protocol_start_response(sn,rq);
764                     NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
765                     return REQ_EXIT;
766                 }
767                 else {
768                     markupProcessor.insert("requestURL",loc);
769                     return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
770                 }
771             }
772     
773             // Return this error to the user.
774             markupProcessor.insert(*status);
775             delete status;
776             return WriteClientError(sn,rq,application,"shire",markupProcessor);
777         }
778         delete status;
779     
780         // We've got a good session, set the cookie and redirect to target.
781         cookie = string(shib_cookie.first) + '=' + cookie + shib_cookie.second;
782         pblock_nvinsert("Set-Cookie",cookie.c_str(),rq->srvhdrs);
783         if (!httpRedirects.first || httpRedirects.second) {
784             pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
785             pblock_nvinsert("Content-Length","40",rq->srvhdrs);
786             pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
787             pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
788             pblock_nvinsert("Location",elements.second,rq->srvhdrs);
789             protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
790             protocol_start_response(sn,rq);
791             NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
792             return REQ_EXIT;
793         }
794         else {
795             markupProcessor.insert("requestURL",elements.second);
796             return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
797         }
798     }
799     catch (ShibTargetException &e) {
800         if (application) {
801             ShibMLP markupProcessor;
802             markupProcessor.insert("requestURL", targeturl.c_str());
803             markupProcessor.insert("errorType", "Session Creation Service Error");
804             markupProcessor.insert("errorText", e.what());
805             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
806             return WriteClientError(sn,rq,application,"shire",markupProcessor);
807         }
808     }
809 #ifndef _DEBUG
810     catch (...) {
811         if (application) {
812             ShibMLP markupProcessor;
813             markupProcessor.insert("requestURL", targeturl.c_str());
814             markupProcessor.insert("errorType", "Session Creation Service Error");
815             markupProcessor.insert("errorText", "Unexpected Exception");
816             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
817             return WriteClientError(sn,rq,application,"shire",markupProcessor);
818         }
819     }
820 #endif    
821     return REQ_EXIT;
822 }