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