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