Factor out some of the cookie name handling.
[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         if (!application)
437             return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
438         
439         // Declare SHIRE object for this request.
440         SHIRE shire(application);
441
442         // If the user is accessing the SHIRE acceptance point, pass it on.
443         if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
444             return SF_STATUS_REQ_NEXT_NOTIFICATION;
445
446         // Now check the policy for this request.
447         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
448         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
449
450         // Check for session cookie.
451         const char* session_id=NULL;
452         GetHeader(pn,pfc,"Cookie:",buf,128,false);
453         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
454         if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
455             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
456             char* cookieend=strchr(session_id,';');
457             if (cookieend)
458                 *cookieend = '\0';    /* Ignore anyting after a ; */
459         }
460         
461         if (!session_id || !*session_id) {
462             // If no session required, bail now.
463             if (!requireSession.second)
464                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
465     
466             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
467             string loc("Location: ");
468             loc+=shire.getAuthnRequest(targeturl.c_str());
469             pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
470             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
471             return SF_STATUS_REQ_FINISHED;
472         }
473
474         // Make sure this session is still valid.
475         RPCError* status = NULL;
476         ShibMLP markupProcessor;
477         markupProcessor.insert("requestURL", targeturl);
478     
479         dynabuf abuf(16);
480         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
481         try {
482             status = shire.sessionIsValid(session_id, abuf);
483         }
484         catch (ShibTargetException &e) {
485             markupProcessor.insert("errorType", "Session Processing Error");
486             markupProcessor.insert("errorText", e.what());
487             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
488             return WriteClientError(pfc, application, "shire", markupProcessor);
489         }
490 #ifndef _DEBUG
491         catch (...) {
492             markupProcessor.insert("errorType", "Session Processing Error");
493             markupProcessor.insert("errorText", "Unexpected Exception");
494             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
495             return WriteClientError(pfc, application, "shire", markupProcessor);
496         }
497 #endif
498
499         // Check the status
500         if (status->isError()) {
501             if (!requireSession.second)
502                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
503             else if (status->isRetryable()) {
504                 // Oops, session is invalid. Generate AuthnRequest.
505                 delete status;
506                 string loc("Location: ");
507                 loc+=shire.getAuthnRequest(targeturl.c_str());
508                 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
509                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
510                 return SF_STATUS_REQ_FINISHED;
511             }
512             else {
513                 // return the error page to the user
514                 markupProcessor.insert(*status);
515                 delete status;
516                 return WriteClientError(pfc, application, "shire", markupProcessor);
517             }
518         }
519         delete status;
520     
521         // Move to RM phase.
522         RM rm(application);
523         vector<SAMLAssertion*> assertions;
524         SAMLAuthenticationStatement* sso_statement=NULL;
525
526         try {
527             status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
528         }
529         catch (ShibTargetException &e) {
530             markupProcessor.insert("errorType", "Attribute Processing Error");
531             markupProcessor.insert("errorText", e.what());
532             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
533             return WriteClientError(pfc, application, "rm", markupProcessor);
534         }
535     #ifndef _DEBUG
536         catch (...) {
537             markupProcessor.insert("errorType", "Attribute Processing Error");
538             markupProcessor.insert("errorText", "Unexpected Exception");
539             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
540             return WriteClientError(pfc, application, "rm", markupProcessor);
541         }
542     #endif
543     
544         if (status->isError()) {
545             markupProcessor.insert(*status);
546             delete status;
547             return WriteClientError(pfc, application, "rm", markupProcessor);
548         }
549         delete status;
550
551         // Do we have an access control plugin?
552         if (settings.second) {
553             Locker acllock(settings.second);
554             if (!settings.second->authorized(assertions)) {
555                 for (int k = 0; k < assertions.size(); k++)
556                     delete assertions[k];
557                 delete sso_statement;
558                 return WriteClientError(pfc, application, "access", markupProcessor);
559             }
560         }
561
562         // Get the AAP providers, which contain the attribute policy info.
563         Iterator<IAAP*> provs=application->getAAPProviders();
564     
565         // Clear out the list of mapped attributes
566         while (provs.hasNext()) {
567             IAAP* aap=provs.next();
568             aap->lock();
569             try {
570                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
571                 while (rules.hasNext()) {
572                     const char* header=rules.next()->getHeader();
573                     if (header) {
574                         string hname=string(header) + ':';
575                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
576                     }
577                 }
578             }
579             catch(...) {
580                 aap->unlock();
581                 for (int k = 0; k < assertions.size(); k++)
582                   delete assertions[k];
583                 delete sso_statement;
584                 markupProcessor.insert("errorType", "Attribute Processing Error");
585                 markupProcessor.insert("errorText", "Unexpected Exception");
586                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
587                 return WriteClientError(pfc, application, "rm", markupProcessor);
588             }
589             aap->unlock();
590         }
591         provs.reset();
592
593         // Maybe export the first assertion.
594         pn->SetHeader(pfc,"remote-user:","");
595         pn->SetHeader(pfc,"Shib-Attributes:","");
596         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
597         if (exp.first && exp.second && assertions.size()) {
598             string assertion;
599             RM::serialize(*(assertions[0]), assertion);
600             string::size_type lfeed;
601             while ((lfeed=assertion.find('\n'))!=string::npos)
602                 assertion.erase(lfeed,1);
603             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
604         }
605         
606         pn->SetHeader(pfc,"Shib-Origin-Site:","");
607         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
608         pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
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             // Export NameID?
618             AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
619             if (!wrapper.fail() && wrapper->getHeader()) {
620                 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
621                 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
622                 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
623                 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
624                     char* principal=const_cast<char*>(nameid.get());
625                     pn->SetHeader(pfc,"remote-user:",principal);
626                     pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
627                     if (pfc->pFilterContext)
628                         strcpy(static_cast<char*>(pfc->pFilterContext),principal);
629                 }
630                 else {
631                     string hname=string(wrapper->getHeader()) + ':';
632                     pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
633                 }
634             }
635         }
636
637         pn->SetHeader(pfc,"Shib-Application-ID:","");
638         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
639
640         // Export the attributes.
641         Iterator<SAMLAssertion*> a_iter(assertions);
642         while (a_iter.hasNext()) {
643             SAMLAssertion* assert=a_iter.next();
644             Iterator<SAMLStatement*> statements=assert->getStatements();
645             while (statements.hasNext()) {
646                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
647                 if (!astate)
648                     continue;
649                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
650                 while (attrs.hasNext()) {
651                     SAMLAttribute* attr=attrs.next();
652         
653                     // Are we supposed to export it?
654                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
655                     if (wrapper.fail() || !wrapper->getHeader())
656                         continue;
657                 
658                     Iterator<string> vals=attr->getSingleByteValues();
659                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
660                         char* principal=const_cast<char*>(vals.next().c_str());
661                         pn->SetHeader(pfc,"remote-user:",principal);
662                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
663                         if (pfc->pFilterContext)
664                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
665                     }
666                     else {
667                         int it=0;
668                         string header;
669                         string hname=string(wrapper->getHeader()) + ':';
670                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
671                         if (!buf.empty()) {
672                             header=buf;
673                             it++;
674                         }
675                         for (; vals.hasNext(); it++) {
676                             string value = vals.next();
677                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
678                                     pos != string::npos;
679                                     pos = value.find_first_of(";", pos)) {
680                                 value.insert(pos, "\\");
681                                 pos += 2;
682                             }
683                             if (it == 0)
684                                 header=value;
685                             else
686                                 header=header + ';' + value;
687                         }
688                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
689                         }
690                 }
691             }
692         }
693     
694         // clean up memory
695         for (int k = 0; k < assertions.size(); k++)
696           delete assertions[k];
697         delete sso_statement;
698
699         return SF_STATUS_REQ_NEXT_NOTIFICATION;
700     }
701     catch(bad_alloc) {
702         return WriteClientError(pfc,"Out of Memory");
703     }
704     catch(DWORD e) {
705         if (e==ERROR_NO_DATA)
706             return WriteClientError(pfc,"A required variable or header was empty.");
707         else
708             return WriteClientError(pfc,"Server detected unexpected IIS error.");
709     }
710 #ifndef _DEBUG
711     catch(...) {
712         return WriteClientError(pfc,"Server caught an unknown exception.");
713     }
714 #endif
715
716     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
717 }
718
719 IRequestMapper::Settings map_request(
720     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
721     )
722 {
723     dynabuf ssl(5);
724     dynabuf port(10);
725     dynabuf url(256);
726     GetServerVariable(lpECB,"HTTPS",ssl,5);
727     GetServerVariable(lpECB,"SERVER_PORT",port,10);
728     GetServerVariable(lpECB,"URL",url,255);
729     bool SSL=(ssl=="on");
730     
731     if (!url.empty())
732         target=static_cast<char*>(url);
733     if (port!=(SSL ? "443" : "80"))
734         target = ':' + static_cast<char*>(port) + target;
735
736     if (g_bNormalizeRequest) {
737         target = string(SSL ? "https://" : "http://") + hostname + target;
738         return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
739     }
740     else {
741         dynabuf name(64);
742         GetServerVariable(lpECB,"SERVER_NAME",name,64);
743         target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
744         return mapper->getSettingsFromParsedURL((SSL ? "https" : "http"),name,strtoul(port,NULL,10),url);
745     }
746 }
747
748 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
749 {
750     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
751     static const char* ctype="Content-Type: text/html\r\n";
752     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
753     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
754     DWORD resplen=strlen(xmsg);
755     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
756     resplen=strlen(msg);
757     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
758     static const char* xmsg2="</BODY></HTML>";
759     resplen=strlen(xmsg2);
760     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
761     return HSE_STATUS_SUCCESS;
762 }
763
764 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
765 {
766     const IPropertySet* props=app->getPropertySet("Errors");
767     if (props) {
768         pair<bool,const char*> p=props->getString(page);
769         if (p.first) {
770             ifstream infile(p.second);
771             if (!infile.fail()) {
772                 const char* res = mlp.run(infile,props);
773                 if (res) {
774                     static const char* ctype="Content-Type: text/html\r\n";
775                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
776                     DWORD resplen=strlen(res);
777                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
778                     return HSE_STATUS_SUCCESS;
779                 }
780             }
781         }
782     }
783     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
784     return WriteClientError(lpECB,"Unable to open error template, check settings.");
785 }
786
787 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
788 {
789     string targeturl;
790     const IApplication* application=NULL;
791     try
792     {
793         ostringstream threadid;
794         threadid << "[" << getpid() << "] shire_handler" << '\0';
795         saml::NDC ndc(threadid.str().c_str());
796
797         // Determine web site number. This can't really fail, I don't think.
798         dynabuf buf(128);
799         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
800
801         // Match site instance to host name, skip if no match.
802         map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
803         if (map_i==g_Sites.end())
804             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
805             
806         const string& site=map_i->second;
807
808         // We lock the configuration system for the duration.
809         IConfig* conf=g_Config->getINI();
810         Locker locker(conf);
811         
812         // Map request to application and content settings.
813         string targeturl;
814         IRequestMapper* mapper=conf->getRequestMapper();
815         Locker locker2(mapper);
816         IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
817         pair<bool,const char*> application_id=settings.first->getString("applicationId");
818         application=conf->getApplication(application_id.second);
819         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
820         if (!application || !sessionProps)
821             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
822
823         SHIRE shire(application);
824
825         // Make sure we only process the SHIRE requests.
826         if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
827             return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
828
829         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
830
831         // Make sure this is SSL, if it should be
832         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
833         if (!shireSSL.first || shireSSL.second) {
834             GetServerVariable(lpECB,"HTTPS",buf,10);
835             if (buf!="on")
836                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
837         }
838         
839         // If this is a GET, we manufacture an AuthnRequest.
840         if (!stricmp(lpECB->lpszMethod,"GET")) {
841             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
842             if (!areq)
843                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
844             targeturl = string("Location: ") + areq + "\r\n"
845                 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
846                 "Cache-Control: private,no-store,no-cache\r\n"
847                 "Connection: close\r\n";
848             HSE_SEND_HEADER_EX_INFO hinfo;
849             hinfo.pszStatus="302 Moved";
850             hinfo.pszHeader=targeturl.c_str();
851             hinfo.cchStatus=9;
852             hinfo.cchHeader=targeturl.length();
853             hinfo.fKeepConn=FALSE;
854             if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
855                 return HSE_STATUS_SUCCESS;
856             return HSE_STATUS_ERROR;
857         }
858         else if (stricmp(lpECB->lpszMethod,"POST"))
859             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
860
861         // Sure sure this POST is an appropriate content type
862         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
863             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
864     
865         // Read the data.
866         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
867         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
868             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
869         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
870             string cgistr;
871             char buf[8192];
872             DWORD datalen=lpECB->cbTotalBytes;
873             while (datalen) {
874                 DWORD buflen=8192;
875                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
876                 if (!ret || !buflen)
877                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
878                 cgistr.append(buf,buflen);
879                 datalen-=buflen;
880             }
881             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
882         }
883         else
884             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
885     
886         // Make sure the SAML Response parameter exists
887         if (!elements.first || !*elements.first)
888             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
889     
890         // Make sure the target parameter exists
891         if (!elements.second || !*elements.second)
892             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
893             
894         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
895
896         // Process the post.
897         string cookie;
898         RPCError* status=NULL;
899         ShibMLP markupProcessor;
900         markupProcessor.insert("requestURL", targeturl.c_str());
901         try {
902             status = shire.sessionCreate(elements.first,buf,cookie);
903         }
904         catch (ShibTargetException &e) {
905             markupProcessor.insert("errorType", "Session Creation Service Error");
906             markupProcessor.insert("errorText", e.what());
907             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
908             return WriteClientError(lpECB, application, "shire", markupProcessor);
909         }
910 #ifndef _DEBUG
911         catch (...) {
912             markupProcessor.insert("errorType", "Session Creation Service Error");
913             markupProcessor.insert("errorText", "Unexpected Exception");
914             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
915             return WriteClientError(lpECB, application, "shire", markupProcessor);
916         }
917 #endif
918
919         if (status->isError()) {
920             if (status->isRetryable()) {
921                 delete status;
922                 const char* loc=shire.getAuthnRequest(elements.second);
923                 DWORD len=strlen(loc);
924                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
925                     return HSE_STATUS_SUCCESS;
926                 return HSE_STATUS_ERROR;
927             }
928     
929             // Return this error to the user.
930             markupProcessor.insert(*status);
931             delete status;
932             return WriteClientError(lpECB,application,"shire",markupProcessor);
933         }
934         delete status;
935     
936         // We've got a good session, set the cookie and redirect to target.
937         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
938             "Location: " + elements.second + "\r\n"
939             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
940             "Cache-Control: private,no-store,no-cache\r\n"
941             "Connection: close\r\n";
942         HSE_SEND_HEADER_EX_INFO hinfo;
943         hinfo.pszStatus="302 Moved";
944         hinfo.pszHeader=cookie.c_str();
945         hinfo.cchStatus=9;
946         hinfo.cchHeader=cookie.length();
947         hinfo.fKeepConn=FALSE;
948         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
949             return HSE_STATUS_SUCCESS;
950     }
951     catch (ShibTargetException &e) {
952         if (application) {
953             ShibMLP markupProcessor;
954             markupProcessor.insert("requestURL", targeturl.c_str());
955             markupProcessor.insert("errorType", "Session Creation Service Error");
956             markupProcessor.insert("errorText", e.what());
957             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
958             return WriteClientError(lpECB,application,"shire",markupProcessor);
959         }
960     }
961 #ifndef _DEBUG
962     catch (...) {
963         if (application) {
964             ShibMLP markupProcessor;
965             markupProcessor.insert("requestURL", targeturl.c_str());
966             markupProcessor.insert("errorType", "Session Creation Service Error");
967             markupProcessor.insert("errorText", "Unexpected Exception");
968             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
969             return WriteClientError(lpECB,application,"shire",markupProcessor);
970         }
971     }
972 #endif
973     
974     return HSE_STATUS_ERROR;
975 }