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