Extended MLP to support arbitrary tag subbing from XML config
[shibboleth/sp.git] / isapi_shib / isapi_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 /* isapi_shib.cpp - Shibboleth ISAPI filter
51
52    Scott Cantor
53    8/23/02
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 #include <httpfilt.h>
72 #include <httpext.h>
73
74 using namespace std;
75 using namespace log4cpp;
76 using namespace saml;
77 using namespace shibboleth;
78 using namespace shibtarget;
79
80 // globals
81 namespace {
82     HINSTANCE g_hinstDLL;
83     ShibTargetConfig* g_Config = NULL;
84     map<string,string> g_Sites;
85     bool g_bNormalizeRequest = true;
86 }
87
88 BOOL LogEvent(
89     LPCSTR  lpUNCServerName,
90     WORD  wType,
91     DWORD  dwEventID,
92     PSID  lpUserSid,
93     LPCSTR  message)
94 {
95     LPCSTR  messages[] = {message, NULL};
96     
97     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
98     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
99     return (DeregisterEventSource(hElog) && res);
100 }
101
102 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
103 {
104     if (fdwReason==DLL_PROCESS_ATTACH)
105         g_hinstDLL=hinstDLL;
106     return TRUE;
107 }
108
109 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
110 {
111     if (!pVer)
112         return FALSE;
113         
114     if (!g_Config)
115     {
116         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
117                 "Extension mode startup not possible, is the DLL loaded as a filter?");
118         return FALSE;
119     }
120
121     pVer->dwExtensionVersion=HSE_VERSION;
122     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
123     return TRUE;
124 }
125
126 extern "C" BOOL WINAPI TerminateExtension(DWORD)
127 {
128     return TRUE;    // cleanup should happen when filter unloads
129 }
130
131 static const XMLCh host[] = { chLatin_h, chLatin_o, chLatin_s, chLatin_t, chNull };
132 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
133 static const XMLCh Implementation[] =
134 { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
135 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
136 static const XMLCh normalizeRequest[] =
137 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
138   chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
139 };
140 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
141
142 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
143 {
144     if (!pVer)
145         return FALSE;
146
147     try
148     {
149         LPCSTR schemadir=getenv("SHIBSCHEMAS");
150         if (!schemadir)
151             schemadir=SHIB_SCHEMAS;
152         LPCSTR config=getenv("SHIBCONFIG");
153         if (!config)
154             config=SHIB_CONFIG;
155         g_Config=&ShibTargetConfig::getConfig();
156         g_Config->setFeatures(
157             ShibTargetConfig::Listener |
158             ShibTargetConfig::Metadata |
159             ShibTargetConfig::AAP |
160             ShibTargetConfig::RequestMapper |
161             ShibTargetConfig::SHIREExtensions
162             );
163         if (!g_Config->init(schemadir,config)) {
164             g_Config=NULL;
165             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
166                     "Filter startup failed during initialization, check shire log for help.");
167             return FALSE;
168         }
169         
170         // Access the implementation-specifics for site mappings.
171         IConfig* conf=g_Config->getINI();
172         Locker locker(conf);
173         const IPropertySet* props=conf->getPropertySet("SHIRE");
174         if (props) {
175             const DOMElement* impl=saml::XML::getFirstChildElement(
176                 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
177                 );
178             if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
179                 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
180                 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
181                 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
182                 while (impl) {
183                     auto_ptr_char id(impl->getAttributeNS(NULL,id));
184                     auto_ptr_char host(impl->getAttributeNS(NULL,host));
185                     if (id.get() && host.get())
186                         g_Sites[id.get()]=host.get();
187                     impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
188                 }
189             }
190         }
191     }
192     catch (...)
193     {
194         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
195         return FALSE;
196     }
197
198     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
199     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
200     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
201                    SF_NOTIFY_SECURE_PORT |
202                    SF_NOTIFY_NONSECURE_PORT |
203                    SF_NOTIFY_PREPROC_HEADERS |
204                    SF_NOTIFY_LOG);
205     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
206     return TRUE;
207 }
208
209 extern "C" BOOL WINAPI TerminateFilter(DWORD)
210 {
211     if (g_Config)
212         g_Config->shutdown();
213     g_Config = NULL;
214     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
215     return TRUE;
216 }
217
218 /* Next up, some suck-free versions of various APIs.
219
220    You DON'T require people to guess the buffer size and THEN tell them the right size.
221    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
222    constant strings aren't typed as such, making it just that much harder. These versions
223    are now updated to use a special growable buffer object, modeled after the standard
224    string class. The standard string won't work because they left out the option to
225    pre-allocate a non-constant buffer.
226 */
227
228 class dynabuf
229 {
230 public:
231     dynabuf() { bufptr=NULL; buflen=0; }
232     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
233     ~dynabuf() { delete[] bufptr; }
234     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
235     size_t size() const { return buflen; }
236     bool empty() const { return length()==0; }
237     void reserve(size_t s, bool keep=false);
238     void erase() { if (bufptr) memset(bufptr,0,buflen); }
239     operator char*() { return bufptr; }
240     bool operator ==(const char* s) const;
241     bool operator !=(const char* s) const { return !(*this==s); }
242 private:
243     char* bufptr;
244     size_t buflen;
245 };
246
247 void dynabuf::reserve(size_t s, bool keep)
248 {
249     if (s<=buflen)
250         return;
251     char* p=new char[s];
252     if (keep)
253         while (buflen--)
254             p[buflen]=bufptr[buflen];
255     buflen=s;
256     delete[] bufptr;
257     bufptr=p;
258 }
259
260 bool dynabuf::operator==(const char* s) const
261 {
262     if (buflen==NULL || s==NULL)
263         return (buflen==NULL && s==NULL);
264     else
265         return strcmp(bufptr,s)==0;
266 }
267
268 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
269     throw (bad_alloc, DWORD)
270 {
271     s.reserve(size);
272     s.erase();
273     size=s.size();
274
275     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
276     {
277         // Grumble. Check the error.
278         DWORD e=GetLastError();
279         if (e==ERROR_INSUFFICIENT_BUFFER)
280             s.reserve(size);
281         else
282             break;
283     }
284     if (bRequired && s.empty())
285         throw ERROR_NO_DATA;
286 }
287
288 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
289     throw (bad_alloc, DWORD)
290 {
291     s.reserve(size);
292     s.erase();
293     size=s.size();
294
295     while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
296     {
297         // Grumble. Check the error.
298         DWORD e=GetLastError();
299         if (e==ERROR_INSUFFICIENT_BUFFER)
300             s.reserve(size);
301         else
302             break;
303     }
304     if (bRequired && s.empty())
305         throw ERROR_NO_DATA;
306 }
307
308 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
309                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
310     throw (bad_alloc, DWORD)
311 {
312     s.reserve(size);
313     s.erase();
314     size=s.size();
315
316     while (!pn->GetHeader(pfc,lpszName,s,&size))
317     {
318         // Grumble. Check the error.
319         DWORD e=GetLastError();
320         if (e==ERROR_INSUFFICIENT_BUFFER)
321             s.reserve(size);
322         else
323             break;
324     }
325     if (bRequired && s.empty())
326         throw ERROR_NO_DATA;
327 }
328
329 IRequestMapper::Settings map_request(
330     PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const char* hostname, string& target
331     )
332 {
333     dynabuf port(10);
334     dynabuf url(256);
335     GetServerVariable(pfc,"SERVER_PORT",port,10);
336     GetHeader(pn,pfc,"url",url,256,false);
337     
338     if (!url.empty())
339         target=static_cast<char*>(url);
340     if (port!=(pfc->fIsSecurePort ? "443" : "80"))
341         target = ':' + static_cast<char*>(port) + target;
342
343     if (g_bNormalizeRequest) {
344         target = string(pfc->fIsSecurePort ? "https://" : "http://") + hostname + target;
345     }
346     else {
347         dynabuf name(64);
348         GetServerVariable(pfc,"SERVER_NAME",name,64);
349         target = string(pfc->fIsSecurePort ? "https://" : "http://") + static_cast<char*>(name) + target;
350     }
351     return mapper->getSettingsFromParsedURL((pfc->fIsSecurePort ? "https" : "http"),hostname,strtoul(port,NULL,10),url);
352 }
353
354 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
355 {
356     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
357     static const char* ctype="Content-Type: text/html\r\n";
358     pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
359     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
360     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
361                             "<H1>Shibboleth Filter Error</H1>";
362     DWORD resplen=strlen(xmsg);
363     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
364     resplen=strlen(msg);
365     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
366     static const char* xmsg2="</BODY></HTML>";
367     resplen=strlen(xmsg2);
368     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
369     return SF_STATUS_REQ_FINISHED;
370 }
371
372 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
373 {
374     const IPropertySet* props=app->getPropertySet("Errors");
375     if (props) {
376         pair<bool,const char*> p=props->getString(page);
377         if (p.first) {
378             ifstream infile(p.second);
379             if (!infile.fail()) {
380                 const char* res = mlp.run(infile,props);
381                 if (res) {
382                     static const char* ctype="Content-Type: text/html\r\n";
383                     pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
384                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
385                     DWORD resplen=strlen(res);
386                     pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
387                     return SF_STATUS_REQ_FINISHED;
388                 }
389             }
390         }
391     }
392
393     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
394     return WriteClientError(pfc,"Unable to open error template, check settings.");
395 }
396
397 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
398 {
399     // Is this a log notification?
400     if (notificationType==SF_NOTIFY_LOG)
401     {
402         if (pfc->pFilterContext)
403             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
404         return SF_STATUS_REQ_NEXT_NOTIFICATION;
405     }
406
407     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
408     try
409     {
410         // Determine web site number. This can't really fail, I don't think.
411         dynabuf buf(128);
412         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
413
414         // Match site instance to host name, skip if no match.
415         map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
416         if (map_i==g_Sites.end())
417             return SF_STATUS_REQ_NEXT_NOTIFICATION;
418             
419         const string& site=map_i->second;
420
421         ostringstream threadid;
422         threadid << "[" << getpid() << "] isapi_shib" << '\0';
423         saml::NDC ndc(threadid.str().c_str());
424         
425         // We lock the configuration system for the duration.
426         IConfig* conf=g_Config->getINI();
427         Locker locker(conf);
428         
429         // Map request to application and content settings.
430         string targeturl;
431         IRequestMapper* mapper=conf->getRequestMapper();
432         Locker locker2(mapper);
433         IRequestMapper::Settings settings=map_request(pfc,pn,mapper,site.c_str(),targeturl);
434         pair<bool,const char*> application_id=settings.first->getString("applicationId");
435         const IApplication* application=conf->getApplication(application_id.second);
436         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
437         if (!application || !sessionProps)
438             return WriteClientError(pfc,"Unable to map request to application session settings, check configuration.");
439         
440         // Declare SHIRE object for this request.
441         SHIRE shire(application);
442
443         // If the user is accessing the SHIRE acceptance point, pass it on.
444         if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
445             return SF_STATUS_REQ_NEXT_NOTIFICATION;
446
447         // Now check the policy for this request.
448         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
449         pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
450         if (!shib_cookie.first)
451             return WriteClientError(pfc,"No session cookie name defined for this application, check configuration.");
452
453         // Check for session cookie.
454         const char* session_id=NULL;
455         GetHeader(pn,pfc,"Cookie:",buf,128,false);
456         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
457         if (!buf.empty() && (session_id=strstr(buf,shib_cookie.second))) {
458             session_id+=strlen(shib_cookie.second) + 1;   /* Skip over the '=' */
459             char* cookieend=strchr(session_id,';');
460             if (cookieend)
461                 *cookieend = '\0';    /* Ignore anyting after a ; */
462         }
463         
464         if (!session_id || !*session_id) {
465             // If no session required, bail now.
466             if (!requireSession.second)
467                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
468     
469             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
470             string loc("Location: ");
471             loc+=shire.getAuthnRequest(targeturl.c_str());
472             pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
473             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
474             return SF_STATUS_REQ_FINISHED;
475         }
476
477         // Make sure this session is still valid.
478         RPCError* status = NULL;
479         ShibMLP markupProcessor;
480         markupProcessor.insert("requestURL", targeturl);
481     
482         dynabuf abuf(16);
483         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
484         try {
485             status = shire.sessionIsValid(session_id, abuf);
486         }
487         catch (ShibTargetException &e) {
488             markupProcessor.insert("errorType", "Session Processing Error");
489             markupProcessor.insert("errorText", e.what());
490             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
491             return WriteClientError(pfc, application, "shire", markupProcessor);
492         }
493 #ifndef _DEBUG
494         catch (...) {
495             markupProcessor.insert("errorType", "Session Processing Error");
496             markupProcessor.insert("errorText", "Unexpected Exception");
497             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
498             return WriteClientError(pfc, application, "shire", markupProcessor);
499         }
500 #endif
501
502         // Check the status
503         if (status->isError()) {
504             if (!requireSession.second)
505                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
506             else if (status->isRetryable()) {
507                 // Oops, session is invalid. Generate AuthnRequest.
508                 delete status;
509                 string loc("Location: ");
510                 loc+=shire.getAuthnRequest(targeturl.c_str());
511                 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
512                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
513                 return SF_STATUS_REQ_FINISHED;
514             }
515             else {
516                 // return the error page to the user
517                 markupProcessor.insert(*status);
518                 delete status;
519                 return WriteClientError(pfc, application, "shire", markupProcessor);
520             }
521         }
522         delete status;
523     
524         // Move to RM phase.
525         RM rm(application);
526         vector<SAMLAssertion*> assertions;
527         SAMLAuthenticationStatement* sso_statement=NULL;
528
529         try {
530             status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
531         }
532         catch (ShibTargetException &e) {
533             markupProcessor.insert("errorType", "Attribute Processing Error");
534             markupProcessor.insert("errorText", e.what());
535             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
536             return WriteClientError(pfc, application, "rm", markupProcessor);
537         }
538     #ifndef _DEBUG
539         catch (...) {
540             markupProcessor.insert("errorType", "Attribute Processing Error");
541             markupProcessor.insert("errorText", "Unexpected Exception");
542             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
543             return WriteClientError(pfc, application, "rm", markupProcessor);
544         }
545     #endif
546     
547         if (status->isError()) {
548             markupProcessor.insert(*status);
549             delete status;
550             return WriteClientError(pfc, application, "rm", markupProcessor);
551         }
552         delete status;
553
554         // Do we have an access control plugin?
555         if (settings.second) {
556             Locker acllock(settings.second);
557             if (!settings.second->authorized(assertions)) {
558                 for (int k = 0; k < assertions.size(); k++)
559                     delete assertions[k];
560                 delete sso_statement;
561                 return WriteClientError(pfc, application, "access", markupProcessor);
562             }
563         }
564
565         // Get the AAP providers, which contain the attribute policy info.
566         Iterator<IAAP*> provs=application->getAAPProviders();
567     
568         // Clear out the list of mapped attributes
569         while (provs.hasNext()) {
570             IAAP* aap=provs.next();
571             aap->lock();
572             try {
573                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
574                 while (rules.hasNext()) {
575                     const char* header=rules.next()->getHeader();
576                     if (header)
577                         pn->SetHeader(pfc,const_cast<char*>(header),"");
578                 }
579             }
580             catch(...) {
581                 aap->unlock();
582                 for (int k = 0; k < assertions.size(); k++)
583                   delete assertions[k];
584                 delete sso_statement;
585                 markupProcessor.insert("errorType", "Attribute Processing Error");
586                 markupProcessor.insert("errorText", "Unexpected Exception");
587                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
588                 return WriteClientError(pfc, application, "rm", markupProcessor);
589             }
590             aap->unlock();
591         }
592         provs.reset();
593
594         // Maybe export the first assertion.
595         pn->SetHeader(pfc,"remote-user:","");
596         pn->SetHeader(pfc,"Shib-Attributes:","");
597         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
598         if (exp.first && exp.second && assertions.size()) {
599             string assertion;
600             RM::serialize(*(assertions[0]), assertion);
601             string::size_type lfeed;
602             while ((lfeed=assertion.find('\n'))!=string::npos)
603                 assertion.erase(lfeed,1);
604             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
605         }
606         
607         pn->SetHeader(pfc,"Shib-Origin-Site:","");
608         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
609
610         // Export the SAML AuthnMethod and the origin site name.
611         if (sso_statement) {
612             auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
613             auto_ptr_char am(sso_statement->getAuthMethod());
614             pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
615             pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
616         }
617
618         pn->SetHeader(pfc,"Shib-Application-ID:","");
619         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
620
621         // Export the attributes.
622         Iterator<SAMLAssertion*> a_iter(assertions);
623         while (a_iter.hasNext()) {
624             SAMLAssertion* assert=a_iter.next();
625             Iterator<SAMLStatement*> statements=assert->getStatements();
626             while (statements.hasNext()) {
627                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
628                 if (!astate)
629                     continue;
630                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
631                 while (attrs.hasNext()) {
632                     SAMLAttribute* attr=attrs.next();
633         
634                     // Are we supposed to export it?
635                     AAP wrapper(application->getAAPProviders(),attr->getName(),attr->getNamespace());
636                     if (wrapper.fail())
637                         continue;
638                 
639                     Iterator<string> vals=attr->getSingleByteValues();
640                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
641                         char* principal=const_cast<char*>(vals.next().c_str());
642                         pn->SetHeader(pfc,"remote-user:",principal);
643                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
644                         if (pfc->pFilterContext)
645                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
646                     }
647                     else {
648                         int it=0;
649                         string header;
650                         string hname=string(wrapper->getHeader()) + ':';
651                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
652                         if (!buf.empty()) {
653                             header=buf;
654                             it++;
655                         }
656                         for (; vals.hasNext(); it++) {
657                             string value = vals.next();
658                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
659                                     pos != string::npos;
660                                     pos = value.find_first_of(";", pos)) {
661                                 value.insert(pos, "\\");
662                                 pos += 2;
663                             }
664                             if (it == 0)
665                                 header=value;
666                             else
667                                 header=header + ';' + value;
668                         }
669                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
670                         }
671                 }
672             }
673         }
674     
675         // clean up memory
676         for (int k = 0; k < assertions.size(); k++)
677           delete assertions[k];
678         delete sso_statement;
679
680         return SF_STATUS_REQ_NEXT_NOTIFICATION;
681     }
682     catch(bad_alloc) {
683         return WriteClientError(pfc,"Out of Memory");
684     }
685     catch(DWORD e) {
686         if (e==ERROR_NO_DATA)
687             return WriteClientError(pfc,"A required variable or header was empty.");
688         else
689             return WriteClientError(pfc,"Server detected unexpected IIS error.");
690     }
691 #ifndef _DEBUG
692     catch(...) {
693         return WriteClientError(pfc,"Server caught an unknown exception.");
694     }
695 #endif
696
697     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
698 }
699
700 IRequestMapper::Settings map_request(
701     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
702     )
703 {
704     dynabuf ssl(5);
705     dynabuf port(10);
706     dynabuf url(256);
707     GetServerVariable(lpECB,"HTTPS",ssl,5);
708     GetServerVariable(lpECB,"SERVER_PORT",port,10);
709     GetServerVariable(lpECB,"URL",url,255);
710     bool SSL=(ssl=="on");
711     
712     if (!url.empty())
713         target=static_cast<char*>(url);
714     if (port!=(SSL ? "443" : "80"))
715         target = ':' + static_cast<char*>(port) + target;
716
717     if (g_bNormalizeRequest) {
718         target = string(SSL ? "https://" : "http://") + hostname + target;
719         return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
720     }
721     else {
722         dynabuf name(64);
723         GetServerVariable(lpECB,"SERVER_NAME",name,64);
724         target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
725         return mapper->getSettingsFromParsedURL((SSL ? "https" : "http"),name,strtoul(port,NULL,10),url);
726     }
727 }
728
729 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
730 {
731     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
732     static const char* ctype="Content-Type: text/html\r\n";
733     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
734     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
735     DWORD resplen=strlen(xmsg);
736     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
737     resplen=strlen(msg);
738     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
739     static const char* xmsg2="</BODY></HTML>";
740     resplen=strlen(xmsg2);
741     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
742     return HSE_STATUS_SUCCESS;
743 }
744
745 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
746 {
747     const IPropertySet* props=app->getPropertySet("Errors");
748     if (props) {
749         pair<bool,const char*> p=props->getString(page);
750         if (p.first) {
751             ifstream infile(p.second);
752             if (!infile.fail()) {
753                 const char* res = mlp.run(infile,props);
754                 if (res) {
755                     static const char* ctype="Content-Type: text/html\r\n";
756                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
757                     DWORD resplen=strlen(res);
758                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
759                     return HSE_STATUS_SUCCESS;
760                 }
761             }
762         }
763     }
764     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
765     return WriteClientError(lpECB,"Unable to open error template, check settings.");
766 }
767
768 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
769 {
770     string targeturl;
771     const IApplication* application=NULL;
772     try
773     {
774         ostringstream threadid;
775         threadid << "[" << getpid() << "] shire_handler" << '\0';
776         saml::NDC ndc(threadid.str().c_str());
777
778         // Determine web site number. This can't really fail, I don't think.
779         dynabuf buf(128);
780         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
781
782         // Match site instance to host name, skip if no match.
783         map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
784         if (map_i==g_Sites.end())
785             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
786             
787         const string& site=map_i->second;
788
789         // We lock the configuration system for the duration.
790         IConfig* conf=g_Config->getINI();
791         Locker locker(conf);
792         
793         // Map request to application and content settings.
794         string targeturl;
795         IRequestMapper* mapper=conf->getRequestMapper();
796         Locker locker2(mapper);
797         IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
798         pair<bool,const char*> application_id=settings.first->getString("applicationId");
799         application=conf->getApplication(application_id.second);
800         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
801         if (!application || !sessionProps)
802             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
803
804         SHIRE shire(application);
805
806         // Make sure we only process the SHIRE requests.
807         if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
808             return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
809
810         pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
811         pair<bool,const char*> shib_cookie_props=sessionProps->getString("cookieProps");
812         if (!shib_cookie.first)
813             return WriteClientError(lpECB,"No session cookie name defined for this application, check configuration.");
814
815         // Make sure this is SSL, if it should be
816         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
817         if (!shireSSL.first || shireSSL.second) {
818             GetServerVariable(lpECB,"HTTPS",buf,10);
819             if (buf!="on")
820                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
821         }
822         
823         // If this is a GET, we manufacture an AuthnRequest.
824         if (!stricmp(lpECB->lpszMethod,"GET")) {
825             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
826             if (!areq)
827                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
828             targeturl = string("Location: ") + areq + "\r\n"
829                 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
830                 "Cache-Control: private,no-store,no-cache\r\n"
831                 "Connection: close\r\n";
832             HSE_SEND_HEADER_EX_INFO hinfo;
833             hinfo.pszStatus="302 Moved";
834             hinfo.pszHeader=targeturl.c_str();
835             hinfo.cchStatus=9;
836             hinfo.cchHeader=targeturl.length();
837             hinfo.fKeepConn=FALSE;
838             if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
839                 return HSE_STATUS_SUCCESS;
840             return HSE_STATUS_ERROR;
841         }
842         else if (stricmp(lpECB->lpszMethod,"POST"))
843             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
844
845         // Sure sure this POST is an appropriate content type
846         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
847             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
848     
849         // Read the data.
850         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
851         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
852             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
853         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
854             string cgistr;
855             char buf[8192];
856             DWORD datalen=lpECB->cbTotalBytes;
857             while (datalen) {
858                 DWORD buflen=8192;
859                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
860                 if (!ret || !buflen)
861                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
862                 cgistr.append(buf,buflen);
863                 datalen-=buflen;
864             }
865             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
866         }
867         else
868             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
869     
870         // Make sure the SAML Response parameter exists
871         if (!elements.first || !*elements.first)
872             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
873     
874         // Make sure the target parameter exists
875         if (!elements.second || !*elements.second)
876             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
877             
878         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
879
880         // Process the post.
881         string cookie;
882         RPCError* status=NULL;
883         ShibMLP markupProcessor;
884         markupProcessor.insert("requestURL", targeturl.c_str());
885         try {
886             status = shire.sessionCreate(elements.first,buf,cookie);
887         }
888         catch (ShibTargetException &e) {
889             markupProcessor.insert("errorType", "Session Creation Service Error");
890             markupProcessor.insert("errorText", e.what());
891             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
892             return WriteClientError(lpECB, application, "shire", markupProcessor);
893         }
894 #ifndef _DEBUG
895         catch (...) {
896             markupProcessor.insert("errorType", "Session Creation Service Error");
897             markupProcessor.insert("errorText", "Unexpected Exception");
898             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
899             return WriteClientError(lpECB, application, "shire", markupProcessor);
900         }
901 #endif
902
903         if (status->isError()) {
904             if (status->isRetryable()) {
905                 delete status;
906                 const char* loc=shire.getAuthnRequest(elements.second);
907                 DWORD len=strlen(loc);
908                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
909                     return HSE_STATUS_SUCCESS;
910                 return HSE_STATUS_ERROR;
911             }
912     
913             // Return this error to the user.
914             markupProcessor.insert(*status);
915             delete status;
916             return WriteClientError(lpECB,application,"shire",markupProcessor);
917         }
918         delete status;
919     
920         // We've got a good session, set the cookie and redirect to target.
921         cookie = string("Set-Cookie: ") + shib_cookie.second + '=' + cookie +
922             (shib_cookie_props.first ? shib_cookie_props.second : "; path=/") + "\r\n" 
923             "Location: " + elements.second + "\r\n"
924             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
925             "Cache-Control: private,no-store,no-cache\r\n"
926             "Connection: close\r\n";
927         HSE_SEND_HEADER_EX_INFO hinfo;
928         hinfo.pszStatus="302 Moved";
929         hinfo.pszHeader=cookie.c_str();
930         hinfo.cchStatus=9;
931         hinfo.cchHeader=cookie.length();
932         hinfo.fKeepConn=FALSE;
933         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
934             return HSE_STATUS_SUCCESS;
935     }
936     catch (ShibTargetException &e) {
937         if (application) {
938             ShibMLP markupProcessor;
939             markupProcessor.insert("requestURL", targeturl.c_str());
940             markupProcessor.insert("errorType", "Session Creation Service Error");
941             markupProcessor.insert("errorText", e.what());
942             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
943             return WriteClientError(lpECB,application,"shire",markupProcessor);
944         }
945     }
946 #ifndef _DEBUG
947     catch (...) {
948         if (application) {
949             ShibMLP markupProcessor;
950             markupProcessor.insert("requestURL", targeturl.c_str());
951             markupProcessor.insert("errorType", "Session Creation Service Error");
952             markupProcessor.insert("errorText", "Unexpected Exception");
953             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
954             return WriteClientError(lpECB,application,"shire",markupProcessor);
955         }
956     }
957 #endif
958     
959     return HSE_STATUS_ERROR;
960 }