ShibTargetException -> FatalProfileException
[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 #if 0
96 /**************************************************************************/
97 /* This isn't used anywhere -- why have it? */
98 #define NET_WRITE1(buf,fmstr,param) \
99     do { sprintf(buf,fmstr,param); NET_WRITE(buf); } while(0)
100
101 #define NET_WRITE2(buf,fmstr,param1,param2) \
102     do { sprintf(buf,fmstr,param1,param2); NET_WRITE(buf); } while(0)
103
104 #define NET_WRITE3(buf,fmstr,param1,param2,param3) \
105     do { sprintf(buf,fmstr,param1,param2,param3); NET_WRITE(buf); } while(0)
106
107 #define NET_WRITE4(buf,fmstr,param1,param2,param3,param4) \
108     do { sprintf(buf,fmstr,param1,param2,param3,param4); NET_WRITE(buf); } while(0)
109 /**************************************************************************/
110 #endif
111
112 namespace {
113     ShibTargetConfig* g_Config=NULL;
114     string g_ServerName;
115     string g_ServerScheme;
116 }
117
118 extern "C" NSAPI_PUBLIC void nsapi_shib_exit(void*)
119 {
120     if (g_Config)
121         g_Config->shutdown();
122     g_Config = NULL;
123 }
124
125 extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq)
126 {
127     // Save off a default hostname for this virtual server.
128     char* name=pblock_findval("server-name",pb);
129     if (name)
130         g_ServerName=name;
131     else {
132         name=server_hostname;
133         if (name)
134             g_ServerName=name;
135         else {
136             name=util_hostname();
137             if (name) {
138                 g_ServerName=name;
139                 FREE(name);
140             }
141             else {
142                 pblock_nvinsert("error","unable to determine web server hostname",pb);
143                 return REQ_ABORTED;
144             }
145         }
146     }
147     name=pblock_findval("server-scheme",pb);
148     if (name)
149         g_ServerScheme=name;
150
151     log_error(LOG_INFORM,"nsapi_shib_init",sn,rq,"nsapi_shib loaded for host (%s)",g_ServerName.c_str());
152
153     try
154     {
155         const char* schemadir=pblock_findval("shib-schemas",pb);
156         if (!schemadir)
157             schemadir=getenv("SHIBSCHEMAS");
158         if (!schemadir)
159             schemadir=SHIB_SCHEMAS;
160         const char* config=pblock_findval("shib-config",pb);
161         if (!config)
162             config=getenv("SHIBCONFIG");
163         if (!config)
164             config=SHIB_CONFIG;
165         g_Config=&ShibTargetConfig::getConfig();
166         g_Config->setFeatures(
167             ShibTargetConfig::Listener |
168             ShibTargetConfig::Metadata |
169             ShibTargetConfig::AAP |
170             ShibTargetConfig::RequestMapper |
171             ShibTargetConfig::SHIREExtensions |
172             ShibTargetConfig::Logging
173             );
174         if (!g_Config->init(schemadir,config)) {
175             g_Config=NULL;
176             pblock_nvinsert("error","unable to initialize Shibboleth libraries",pb);
177             return REQ_ABORTED;
178         }
179
180         daemon_atrestart(nsapi_shib_exit,NULL);
181     }
182     catch (...)
183     {
184 #ifdef _DEBUG
185         throw;
186 #endif
187         g_Config=NULL;
188         pblock_nvinsert("error","caught exception, unable to initialize Shibboleth libraries",pb);
189         return REQ_ABORTED;
190     }
191     return REQ_PROCEED;
192 }
193
194 /********************************************************************************/
195 // NSAPI Shib Target Subclass
196
197 class ShibTargetNSAPI : public ShibTarget
198 {
199 public:
200   ShibTargetNSAPI(pblock* pb, Session* sn, Request* rq) {
201     // Get everything but hostname...
202     const char* uri=pblock_findval("uri", rq->reqpb);
203     const char* qstr=pblock_findval("query", rq->reqpb);
204     int port=server_portnum;
205     const char* scheme=security_active ? "https" : "http";
206     const char* host=NULL;
207
208     string url;
209     if (uri)
210         url=uri;
211     if (qstr)
212         url=url + '?' + qstr;
213     
214 #ifdef vs_is_default_vs
215     // This is 6.0 or later, so we can distinguish requests to name-based vhosts.
216     if (!vs_is_default_vs)
217         // The beauty here is, a non-default vhost can *only* be accessed if the client
218         // specified the exact name in the Host header. So we can trust the Host header.
219         host=pblock_findval("host", rq->headers);
220     else
221 #endif
222     // In other cases, we're going to rely on the initialization process...
223     host=g_ServerName.c_str();
224
225     char *content_type = NULL;
226     if (request_header("content-type", &content_type, sn, rq) != REQ_PROCEED)
227       throw("Bad Content Type");
228       
229     const char *remote_ip = pblock_findval("ip", sn->client);
230     const char *method = pblock_findval("method", rq->reqpb);
231
232     init(
233          g_Config, scheme, host, port, url.c_str(), content_type,
234          remote_ip, method
235          );
236
237     m_pb = pb;
238     m_sn = sn;
239     m_rq = rq;
240   }
241   ~ShibTargetNSAPI() { }
242
243   virtual void log(ShibLogLevel level, const string &msg) {
244     log_error((level == LogLevelDebug ? LOG_INFORM :
245                    (level == LogLevelInfo ? LOG_INFORM :
246                     (level == LogLevelWarn ? LOG_FAILURE :
247                      LOG_FAILURE))),
248               "NSAPI_SHIB", m_sn, m_rq, msg.c_str());
249   }
250   virtual string getCookies(void) {
251     char *cookies = NULL;
252     if (request_header("cookie", &cookies, m_sn, m_rq) == REQ_ABORTED)
253       throw("error accessing cookie header");
254     return string(cookies ? cookies : "");
255   }
256   virtual void setCookie(const string &name, const string &value) {
257     string cookie = name + '=' + value;
258     pblock_nvinsert("Set-Cookie", cookie.c_str(), m_rq->srvhdrs);
259   }
260   virtual string getArgs(void) { 
261     const char *q = pblock_findval("query", m_rq->reqpb);
262     return string(q ? q : "");
263   }
264   virtual string getPostData(void) {
265     char* content_length=NULL;
266     if (request_header("content-length", &content_length, m_sn, m_rq)
267         !=REQ_PROCEED || atoi(content_length) > 1024*1024) // 1MB?
268       throw FatalProfileException("Blocked too-large a submittion to profile endpoint.");
269     else {
270       char ch=IO_EOF+1;
271       int cl=atoi(content_length);
272       string cgistr;
273       while (cl && ch != IO_EOF) {
274         ch=netbuf_getc(m_sn->inbuf);
275       
276         // Check for error.
277         if(ch==IO_ERROR)
278           break;
279         cgistr += ch;
280         cl--;
281       }
282       if (cl)
283         throw FatalProfileException("error reading POST data from browser");
284       return cgistr;
285     }
286   }
287   virtual void clearHeader(const string &name) {
288     // srvhdrs or headers?
289     param_free(pblock_remove(name.c_str(), m_rq->headers));
290   }
291   virtual void setHeader(const string &name, const string &value) {
292     // srvhdrs or headers?
293     pblock_nvinsert(name.c_str(), value.c_str() ,m_rq->srvhdrs);
294   }
295   virtual string getHeader(const string &name) {
296     const char *hdr = NULL;
297     if (request_header(name.c_str(), &hdr, m_sn, m_rq) != REQ_PROCEED)
298       hdr = NULL;               // XXX: throw an exception here?
299     return string(hdr ? hdr : "");
300   }
301   virtual void setRemoteUser(const string &user) {
302     pblock_nvinsert("remote-user", user.c_str(), m_rq->headers);
303     pblock_nvinsert("auth-user", user.c_str(), m_rq->vars);
304   }
305   virtual string getRemoteUser(void) {
306     return getHeader("remote-user");
307   }
308   // Override this function because we want to add the NSAPI Directory override
309   virtual pair<bool,bool> getRequireSession(IRequestMapper::Settings &settings) {
310     pair<bool,bool> requireSession=settings.first->getBool("requireSession");
311     if (!requireSession.first || !requireSession.second) {
312       const char* param=pblock_findval("require-session",pb);
313       if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
314         requireSession.second=true;
315     }
316     return requireSession;
317   }
318
319   virtual void* sendPage(
320     const string& msg,
321     const string& content_type,
322     const saml::Iterator<header_t>& headers=EMPTY(header_t),
323     int code=200
324     ) {
325     pblock_nvinsert("Content-Type", content_type.c_str(), m_rq->srvhdrs);
326     // XXX: Do we need content-length: or connection: close headers?
327     while (headers.hasNext()) {
328         const header_t& h=headers.next();
329         pblock_nvinsert(h.first.c_str(), h.second.c_str(), m_rq->srvhdrs);
330     }
331     protocol_status(m_sn, m_rq, PROTOCOL_OK, NULL);
332     NET_WRITE(const_cast<char*>(msg.c_str()));
333     return (VOID*)REQ_EXIT;
334   }
335   virtual void* sendRedirect(const string& url) {
336     pblock_nvinsert("Content-Type", "text/html", m_rq->srvhdrs);
337     pblock_nvinsert("Content-Length", "40", m_rq->srvhdrs);
338     pblock_nvinsert("Expires", "01-Jan-1997 12:00:00 GMT", m_rq->srvhdrs);
339     pblock_nvinsert("Cache-Control", "private,no-store,no-cache", m_rq->srvhdrs);
340     pblock_nvinsert("Location", url.c_str(), m_rq->srvhdrs);
341     protocol_status(m_sn, m_rq, PROTOCOL_REDIRECT, "302 Please wait");
342     protocol_start_response(m_sn, m_rq);
343     NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
344     return (void*)REQ_EXIT;
345   }
346   virtual void* returnDecline(void) { return (void*)REQ_PROCEED; } // XXX?
347   virtual void* returnOK(void) { return (void*)REQ_PROCEED; }
348
349   pblock* m_pb;
350   Session* m_sn;
351   Request* m_rq;
352 };
353
354 /********************************************************************************/
355
356 int WriteClientError(Session* sn, Request* rq, char* func, char* msg)
357 {
358     log_error(LOG_FAILURE,func,sn,rq,msg);
359     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,msg);
360     return REQ_ABORTED;
361 }
362
363 #undef FUNC
364 #define FUNC "shibboleth"
365 extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
366 {
367   ostringstream threadid;
368   threadid << "[" << getpid() << "] nsapi_shib" << '\0';
369   saml::NDC ndc(threadid.str().c_str());
370
371 #ifndef _DEBUG
372   try {
373 #endif
374     ShibTargetNSAPI stn(pb, sn, rq);
375
376     // Check user authentication
377     pair<bool,void*> res = stn.doCheckAuthN();
378     if (res.first) return (int)res.second;
379
380     // user authN was okay -- export the assertions now
381     const char* param=pblock_findval("export-assertion", pb);
382     bool doExportAssn = false;
383     if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
384       doExportAssn = true;
385     res = stn.doExportAssertions(doExportAssn);
386     if (res.first) return (int)res.second;
387
388     // Check the Authorization
389     res = stf.doCheckAuthZ();
390     if (res.first) return (int)res.second;
391
392     // this user is ok.
393     return REQ_PROCEED;
394
395 #ifndef _DEBUG
396   } catch (...) {
397     return WriteClientError(sn, rq, FUNC, "threw an uncaught exception.");
398   }
399 #endif
400 }
401
402
403 #undef FUNC
404 #define FUNC "shib_handler"
405 extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq)
406 {
407   ostringstream threadid;
408   threadid << "[" << getpid() << "] shib_handler" << '\0';
409   saml::NDC ndc(threadid.str().c_str());
410
411 #ifndef _DEBUG
412   try {
413 #endif
414     ShibTargetNSAPI stn(pb, sn, rq);
415
416     pair<bool,void*> res = stn.doHandleProfile();
417     if (res.first) return (int)res.second;
418
419     return WriteClientError(sn, rq, FUNC, "doHandleProfile() did not do anything.")
420
421 #ifndef _DEBUG
422   } catch (...) {
423     return WriteClientError(sn, rq, FUNC, "threw an uncaught exception.");
424   }
425 #endif
426 }
427
428
429 #if 0
430
431
432 IRequestMapper::Settings map_request(pblock* pb, Session* sn, Request* rq, IRequestMapper* mapper, string& target)
433 {
434     // Get everything but hostname...
435     const char* uri=pblock_findval("uri",rq->reqpb);
436     const char* qstr=pblock_findval("query",rq->reqpb);
437     int port=server_portnum;
438     const char* scheme=security_active ? "https" : "http";
439     const char* host=NULL;
440
441     string url;
442     if (uri)
443         url=uri;
444     if (qstr)
445         url=url + '?' + qstr;
446     
447 #ifdef vs_is_default_vs
448     // This is 6.0 or later, so we can distinguish requests to name-based vhosts.
449     if (!vs_is_default_vs)
450         // The beauty here is, a non-default vhost can *only* be accessed if the client
451         // specified the exact name in the Host header. So we can trust the Host header.
452         host=pblock_findval("host", rq->headers);
453     else
454 #endif
455     // In other cases, we're going to rely on the initialization process...
456     host=g_ServerName.c_str();
457         
458     target=(g_ServerScheme.empty() ? string(scheme) : g_ServerScheme) + "://" + host;
459     
460     // If port is non-default, append it.
461     if ((!security_active && port!=80) || (security_active && port!=443)) {
462         char portbuf[10];
463         util_snprintf(portbuf,9,"%d",port);
464         target = target + ':' + portbuf;
465     }
466
467     target+=url;
468         
469     return mapper->getSettingsFromParsedURL(scheme,host,port,url.c_str());
470 }
471
472 int WriteClientError(Session* sn, Request* rq, const IApplication* app, const char* page, ShibMLP& mlp)
473 {
474     const IPropertySet* props=app->getPropertySet("Errors");
475     if (props) {
476         pair<bool,const char*> p=props->getString(page);
477         if (p.first) {
478             ifstream infile(p.second);
479             if (!infile.fail()) {
480                 const char* res = mlp.run(infile,props);
481                 if (res) {
482                     pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
483                     pblock_nninsert("Content-Length",strlen(res),rq->srvhdrs);
484                     pblock_nvinsert("Connection","close",rq->srvhdrs);
485                     protocol_status(sn,rq,PROTOCOL_OK,NULL);
486                     NET_WRITE(const_cast<char*>(res));
487                     return REQ_EXIT;
488                 }
489             }
490         }
491     }
492
493     log_error(LOG_FAILURE,"WriteClientError",sn,rq,"Unable to open error template, check settings.");
494     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,"Unable to open error template, check settings.");
495     return REQ_ABORTED;
496 }
497
498 int WriteRedirectPage(Session* sn, Request* rq, const IApplication* app, const char* file, ShibMLP& mlp)
499 {
500     ifstream infile(file);
501     if (!infile.fail()) {
502         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
503         if (res) {
504             pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
505             pblock_nninsert("Content-Length",strlen(res),rq->srvhdrs);
506             protocol_status(sn,rq,PROTOCOL_OK,NULL);
507             NET_WRITE(const_cast<char*>(res));
508             return REQ_EXIT;
509         }
510     }
511     log_error(LOG_FAILURE,"WriteRedirectPage",sn,rq,"Unable to open redirect template, check settings.");
512     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,"Unable to open redirect template, check settings.");
513     return REQ_ABORTED;
514 }
515
516 #undef FUNC
517 #define FUNC "shibboleth"
518 extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
519 {
520     try
521     {
522         ostringstream threadid;
523         threadid << "[" << getpid() << "] nsapi_shib" << '\0';
524         saml::NDC ndc(threadid.str().c_str());
525         
526         // We lock the configuration system for the duration.
527         IConfig* conf=g_Config->getINI();
528         Locker locker(conf);
529         
530         // Map request to application and content settings.
531         string targeturl;
532         IRequestMapper* mapper=conf->getRequestMapper();
533         Locker locker2(mapper);
534         IRequestMapper::Settings settings=map_request(pb,sn,rq,mapper,targeturl);
535         pair<bool,const char*> application_id=settings.first->getString("applicationId");
536         const IApplication* application=conf->getApplication(application_id.second);
537         if (!application)
538             return WriteClientError(sn,rq,FUNC,"Unable to map request to application settings, check configuration.");
539         
540         // Declare SHIRE object for this request.
541         SHIRE shire(application);
542         
543         const char* shireURL=shire.getShireURL(targeturl.c_str());
544         if (!shireURL)
545             return WriteClientError(sn,rq,FUNC,"Unable to map request to proper shireURL setting, check configuration.");
546
547         // If the user is accessing the SHIRE acceptance point, pass it on.
548         if (targeturl.find(shireURL)!=string::npos)
549             return REQ_PROCEED;
550
551         // Now check the policy for this request.
552         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
553         if (!requireSession.first || !requireSession.second) {
554             const char* param=pblock_findval("require-session",pb);
555             if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
556                 requireSession.second=true;
557         }
558         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
559         pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
560         pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
561         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
562             return WriteClientError(sn,rq,FUNC,"HTML-based redirection requires a redirectPage property.");
563
564         // Check for session cookie.
565         const char* session_id=NULL;
566         string cookie;
567         if (request_header("cookie",(char**)&session_id,sn,rq)==REQ_ABORTED)
568             return WriteClientError(sn,rq,FUNC,"error accessing cookie header");
569
570         Category::getInstance("nsapi_shib."FUNC).debug("cookie header is {%s}",session_id ? session_id : "NULL");
571         if (session_id && (session_id=strstr(session_id,shib_cookie.first))) {
572             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
573             char* cookieend=strchr(session_id,';');
574             if (cookieend) {
575                 // Chop out just the value portion.
576                 cookie.assign(session_id,cookieend-session_id-1);
577                 session_id=cookie.c_str();
578             }
579         }
580         
581         if (!session_id || !*session_id) {
582             // If no session required, bail now.
583             if (!requireSession.second)
584                 return REQ_PROCEED;
585     
586             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
587             const char* areq = shire.getAuthnRequest(targeturl.c_str());
588             if (!httpRedirects.first || httpRedirects.second) {
589                 pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
590                 pblock_nvinsert("Content-Length","40",rq->srvhdrs);
591                 pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
592                 pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
593                 pblock_nvinsert("Location",areq,rq->srvhdrs);
594                 protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
595                 protocol_start_response(sn,rq);
596                 NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
597                 return REQ_EXIT;
598             }
599             else {
600                 ShibMLP markupProcessor;
601                 markupProcessor.insert("requestURL",areq);
602                 return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
603             }
604         }
605
606         // Make sure this session is still valid.
607         RPCError* status = NULL;
608         ShibMLP markupProcessor;
609         markupProcessor.insert("requestURL", targeturl);
610     
611         try {
612             status = shire.sessionIsValid(session_id, pblock_findval("ip",sn->client));
613         }
614         catch (ShibTargetException &e) {
615             markupProcessor.insert("errorType", "Session Processing Error");
616             markupProcessor.insert("errorText", e.what());
617             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
618             return WriteClientError(sn, rq, application, "shire", markupProcessor);
619         }
620 #ifndef _DEBUG
621         catch (...) {
622             markupProcessor.insert("errorType", "Session Processing Error");
623             markupProcessor.insert("errorText", "Unexpected Exception");
624             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
625             return WriteClientError(sn, rq, application, "shire", markupProcessor);
626         }
627 #endif
628
629         // Check the status
630         if (status->isError()) {
631             if (!requireSession.second)
632                 return REQ_PROCEED;
633             else if (status->isRetryable()) {
634                 // Oops, session is invalid. Generate AuthnRequest.
635                 delete status;
636                 const char* areq = shire.getAuthnRequest(targeturl.c_str());
637                 if (!httpRedirects.first || httpRedirects.second) {
638                     pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
639                     pblock_nvinsert("Content-Length","40",rq->srvhdrs);
640                     pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
641                     pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
642                     pblock_nvinsert("Location",areq,rq->srvhdrs);
643                     protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
644                     protocol_start_response(sn,rq);
645                     NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
646                     return REQ_EXIT;
647                 }
648                 else {
649                     markupProcessor.insert("requestURL",areq);
650                     return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
651                 }
652             }
653             else {
654                 // return the error page to the user
655                 markupProcessor.insert(*status);
656                 delete status;
657                 return WriteClientError(sn, rq, application, "shire", markupProcessor);
658             }
659         }
660         delete status;
661     
662         // Move to RM phase.
663         RM rm(application);
664         vector<SAMLAssertion*> assertions;
665         SAMLAuthenticationStatement* sso_statement=NULL;
666
667         try {
668             status = rm.getAssertions(session_id, pblock_findval("ip",sn->client), assertions, &sso_statement);
669         }
670         catch (ShibTargetException &e) {
671             markupProcessor.insert("errorType", "Attribute Processing Error");
672             markupProcessor.insert("errorText", e.what());
673             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
674             return WriteClientError(sn, rq, application, "rm", markupProcessor);
675         }
676     #ifndef _DEBUG
677         catch (...) {
678             markupProcessor.insert("errorType", "Attribute Processing Error");
679             markupProcessor.insert("errorText", "Unexpected Exception");
680             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
681             return WriteClientError(sn, rq, application, "rm", markupProcessor);
682         }
683     #endif
684     
685         if (status->isError()) {
686             markupProcessor.insert(*status);
687             delete status;
688             return WriteClientError(sn, rq, application, "rm", markupProcessor);
689         }
690         delete status;
691
692         // Do we have an access control plugin?
693         if (settings.second) {
694             Locker acllock(settings.second);
695             if (!settings.second->authorized(*sso_statement,assertions)) {
696                 for (int k = 0; k < assertions.size(); k++)
697                     delete assertions[k];
698                 delete sso_statement;
699                 return WriteClientError(sn, rq, application, "access", markupProcessor);
700             }
701         }
702
703         // Get the AAP providers, which contain the attribute policy info.
704         Iterator<IAAP*> provs=application->getAAPProviders();
705     
706         // Clear out the list of mapped attributes
707         while (provs.hasNext()) {
708             IAAP* aap=provs.next();
709             aap->lock();
710             try {
711                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
712                 while (rules.hasNext()) {
713                     const char* header=rules.next()->getHeader();
714                     if (header)
715                         param_free(pblock_remove(header,rq->headers));
716                 }
717             }
718             catch(...) {
719                 aap->unlock();
720                 for (int k = 0; k < assertions.size(); k++)
721                   delete assertions[k];
722                 delete sso_statement;
723                 markupProcessor.insert("errorType", "Attribute Processing Error");
724                 markupProcessor.insert("errorText", "Unexpected Exception");
725                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
726                 return WriteClientError(sn, rq, application, "rm", markupProcessor);
727             }
728             aap->unlock();
729         }
730         provs.reset();
731
732         // Maybe export the first assertion.
733         param_free(pblock_remove("remote-user",rq->headers));
734         param_free(pblock_remove("auth-user",rq->vars));
735         param_free(pblock_remove("Shib-Attributes",rq->headers));
736         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
737         if (!exp.first || !exp.second) {
738             const char* param=pblock_findval("export-assertion",pb);
739             if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
740                 exp.second=true;
741         }
742         if (exp.second && assertions.size()) {
743             string assertion;
744             RM::serialize(*(assertions[0]), assertion);
745             string::size_type lfeed;
746             while ((lfeed=assertion.find('\n'))!=string::npos)
747                 assertion.erase(lfeed,1);
748             pblock_nvinsert("Shib-Attributes",assertion.c_str(),rq->headers);
749         }
750         
751         pblock_nvinsert("auth-type","shibboleth",rq->vars);
752         param_free(pblock_remove("Shib-Origin-Site",rq->headers));
753         param_free(pblock_remove("Shib-Authentication-Method",rq->headers));
754         param_free(pblock_remove("Shib-NameIdentifier-Format",rq->headers));
755
756         // Export the SAML AuthnMethod and the origin site name.
757         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
758         auto_ptr_char am(sso_statement->getAuthMethod());
759         pblock_nvinsert("Shib-Origin-Site",os.get(),rq->headers);
760         pblock_nvinsert("Shib-Authentication-Method",am.get(),rq->headers);
761
762         // Export NameID?
763         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
764         if (!wrapper.fail() && wrapper->getHeader()) {
765             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
766             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
767             pblock_nvinsert("Shib-NameIdentifier-Format",form.get(),pb);
768             if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
769                 pblock_nvinsert("remote-user",nameid.get(),rq->headers);
770                 pblock_nvinsert("auth-user",nameid.get(),rq->vars);
771             }
772             else {
773                 pblock_nvinsert(wrapper->getHeader(),nameid.get(),rq->headers);
774             }
775         }
776
777         param_free(pblock_remove("Shib-Application-ID",rq->headers));
778         pblock_nvinsert("Shib-Application-ID",application_id.second,rq->headers);
779
780         // Export the attributes.
781         Iterator<SAMLAssertion*> a_iter(assertions);
782         while (a_iter.hasNext()) {
783             SAMLAssertion* assert=a_iter.next();
784             Iterator<SAMLStatement*> statements=assert->getStatements();
785             while (statements.hasNext()) {
786                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
787                 if (!astate)
788                     continue;
789                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
790                 while (attrs.hasNext()) {
791                     SAMLAttribute* attr=attrs.next();
792         
793                     // Are we supposed to export it?
794                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
795                     if (wrapper.fail() || !wrapper->getHeader())
796                         continue;
797                 
798                     Iterator<string> vals=attr->getSingleByteValues();
799                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
800                         char* principal=const_cast<char*>(vals.next().c_str());
801                         pblock_nvinsert("remote-user",principal,rq->headers);
802                         pblock_nvinsert("auth-user",principal,rq->vars);
803                     }
804                     else {
805                         int it=0;
806                         string header;
807                         const char* h=pblock_findval(wrapper->getHeader(),rq->headers);
808                         if (h) {
809                             header=h;
810                             param_free(pblock_remove(wrapper->getHeader(),rq->headers));
811                             it++;
812                         }
813                         for (; vals.hasNext(); it++) {
814                             string value = vals.next();
815                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
816                                     pos != string::npos;
817                                     pos = value.find_first_of(";", pos)) {
818                                 value.insert(pos, "\\");
819                                 pos += 2;
820                             }
821                             if (it == 0)
822                                 header=value;
823                             else
824                                 header=header + ';' + value;
825                         }
826                         pblock_nvinsert(wrapper->getHeader(),header.c_str(),rq->headers);
827                         }
828                 }
829             }
830         }
831     
832         // clean up memory
833         for (int k = 0; k < assertions.size(); k++)
834           delete assertions[k];
835         delete sso_statement;
836
837         return REQ_PROCEED;
838     }
839     catch(bad_alloc) {
840         return WriteClientError(sn, rq, FUNC,"Out of Memory");
841     }
842 #ifndef _DEBUG
843     catch(...) {
844         return WriteClientError(sn, rq, FUNC,"Server caught an unknown exception.");
845     }
846 #endif
847
848     return WriteClientError(sn, rq, FUNC,"Server reached unreachable code, save my walrus!");
849 }
850
851 #undef FUNC
852 #define FUNC "shib_handler"
853 extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq)
854 {
855     string targeturl;
856     const IApplication* application=NULL;
857     try
858     {
859         ostringstream threadid;
860         threadid << "[" << getpid() << "] shib_handler" << '\0';
861         saml::NDC ndc(threadid.str().c_str());
862
863         // We lock the configuration system for the duration.
864         IConfig* conf=g_Config->getINI();
865         Locker locker(conf);
866         
867         // Map request to application and content settings.
868         IRequestMapper* mapper=conf->getRequestMapper();
869         Locker locker2(mapper);
870         IRequestMapper::Settings settings=map_request(pb,sn,rq,mapper,targeturl);
871         pair<bool,const char*> application_id=settings.first->getString("applicationId");
872         application=conf->getApplication(application_id.second);
873         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
874         if (!application || !sessionProps)
875             return WriteClientError(sn,rq,FUNC,"Unable to map request to application settings, check configuration.");
876
877         SHIRE shire(application);
878         
879         const char* shireURL=shire.getShireURL(targeturl.c_str());
880         if (!shireURL)
881             return WriteClientError(sn,rq,FUNC,"Unable to map request to proper shireURL setting, check configuration.");
882
883         // Make sure we only process the SHIRE requests.
884         if (!strstr(targeturl.c_str(),shireURL))
885             return WriteClientError(sn,rq,FUNC,"NSAPI service function can only be invoked to process incoming sessions."
886                 "Make sure the mapped file extension or URL doesn't match actual content.");
887
888         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
889
890         // Make sure this is SSL, if it should be
891         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
892         if (!shireSSL.first || shireSSL.second) {
893             if (!security_active)
894                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to Shibboleth session processor");
895         }
896         
897         pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
898         pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
899         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
900             return WriteClientError(sn,rq,FUNC,"HTML-based redirection requires a redirectPage property.");
901                 
902         // If this is a GET, we manufacture an AuthnRequest.
903         if (!strcasecmp(pblock_findval("method",rq->reqpb),"GET")) {
904             const char* areq=pblock_findval("query",rq->reqpb) ? shire.getLazyAuthnRequest(pblock_findval("query",rq->reqpb)) : NULL;
905             if (!areq)
906                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
907             if (!httpRedirects.first || httpRedirects.second) {
908                 pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
909                 pblock_nvinsert("Content-Length","40",rq->srvhdrs);
910                 pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
911                 pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
912                 pblock_nvinsert("Location",areq,rq->srvhdrs);
913                 protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
914                 protocol_start_response(sn,rq);
915                 NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
916                 return REQ_EXIT;
917             }
918             else {
919                 ShibMLP markupProcessor;
920                 markupProcessor.insert("requestURL",areq);
921                 return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
922             }
923         }
924         else if (strcasecmp(pblock_findval("method",rq->reqpb),"POST"))
925             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to Shibboleth session processor");
926
927         // Make sure this POST is an appropriate content type
928         char* content_type=NULL;
929         if (request_header("content-type",&content_type,sn,rq)!=REQ_PROCEED ||
930                 !content_type || strcasecmp(content_type,"application/x-www-form-urlencoded"))
931             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to Shibboleth session processor");
932     
933         // Read the data.
934         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
935         char* content_length=NULL;
936         if (request_header("content-length",&content_length,sn,rq)!=REQ_PROCEED ||
937                 atoi(content_length) > 1024*1024) // 1MB?
938             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to Shibboleth session processor");
939         else {
940             char ch=IO_EOF+1;
941             int cl=atoi(content_length);
942             string cgistr;
943             while (cl && ch!=IO_EOF) {
944                 ch=netbuf_getc(sn->inbuf);
945         
946                 // Check for error.
947                 if(ch==IO_ERROR)
948                     break;
949                 cgistr+=ch;
950                 cl--;
951             }
952             if (cl)
953                 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
954             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
955         }
956     
957         // Make sure the SAML Response parameter exists
958         if (!elements.first || !*elements.first)
959             throw ShibTargetException(SHIBRPC_OK, "Shibboleth POST failed to find SAMLResponse form element");
960     
961         // Make sure the target parameter exists
962         if (!elements.second || !*elements.second)
963             throw ShibTargetException(SHIBRPC_OK, "Shibboleth POST failed to find TARGET form element");
964             
965         // Process the post.
966         string cookie;
967         RPCError* status=NULL;
968         ShibMLP markupProcessor;
969         markupProcessor.insert("requestURL", targeturl.c_str());
970         try {
971             status = shire.sessionCreate(elements.first,pblock_findval("ip",sn->client),cookie);
972         }
973         catch (ShibTargetException &e) {
974             markupProcessor.insert("errorType", "Session Creation Service Error");
975             markupProcessor.insert("errorText", e.what());
976             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
977             return WriteClientError(sn, rq, application, "shire", markupProcessor);
978         }
979 #ifndef _DEBUG
980         catch (...) {
981             markupProcessor.insert("errorType", "Session Creation Service Error");
982             markupProcessor.insert("errorText", "Unexpected Exception");
983             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
984             return WriteClientError(sn, rq, application, "shire", markupProcessor);
985         }
986 #endif
987
988         if (status->isError()) {
989             if (status->isRetryable()) {
990                 delete status;
991                 const char* loc=shire.getAuthnRequest(elements.second);
992                 if (!httpRedirects.first || httpRedirects.second) {
993                     pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
994                     pblock_nvinsert("Content-Length","40",rq->srvhdrs);
995                     pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
996                     pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
997                     pblock_nvinsert("Location",loc,rq->srvhdrs);
998                     protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
999                     protocol_start_response(sn,rq);
1000                     NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
1001                     return REQ_EXIT;
1002                 }
1003                 else {
1004                     markupProcessor.insert("requestURL",loc);
1005                     return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
1006                 }
1007             }
1008     
1009             // Return this error to the user.
1010             markupProcessor.insert(*status);
1011             delete status;
1012             return WriteClientError(sn,rq,application,"shire",markupProcessor);
1013         }
1014         delete status;
1015     
1016         // We've got a good session, set the cookie and redirect to target.
1017         cookie = string(shib_cookie.first) + '=' + cookie + shib_cookie.second;
1018         pblock_nvinsert("Set-Cookie",cookie.c_str(),rq->srvhdrs);
1019         if (!httpRedirects.first || httpRedirects.second) {
1020             pblock_nvinsert("Content-Type","text/html",rq->srvhdrs);
1021             pblock_nvinsert("Content-Length","40",rq->srvhdrs);
1022             pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs);
1023             pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs);
1024             pblock_nvinsert("Location",elements.second,rq->srvhdrs);
1025             protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait");
1026             protocol_start_response(sn,rq);
1027             NET_WRITE("<HTML><BODY>Redirecting...</BODY></HTML>");
1028             return REQ_EXIT;
1029         }
1030         else {
1031             markupProcessor.insert("requestURL",elements.second);
1032             return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor);
1033         }
1034     }
1035     catch (ShibTargetException &e) {
1036         if (application) {
1037             ShibMLP markupProcessor;
1038             markupProcessor.insert("requestURL", targeturl.c_str());
1039             markupProcessor.insert("errorType", "Session Creation Service Error");
1040             markupProcessor.insert("errorText", e.what());
1041             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1042             return WriteClientError(sn,rq,application,"shire",markupProcessor);
1043         }
1044     }
1045 #ifndef _DEBUG
1046     catch (...) {
1047         if (application) {
1048             ShibMLP markupProcessor;
1049             markupProcessor.insert("requestURL", targeturl.c_str());
1050             markupProcessor.insert("errorType", "Session Creation Service Error");
1051             markupProcessor.insert("errorText", "Unexpected Exception");
1052             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1053             return WriteClientError(sn,rq,application,"shire",markupProcessor);
1054         }
1055     }
1056 #endif    
1057     return REQ_EXIT;
1058 }
1059
1060 #endif