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