e75f5782aa9c6744f3addf02a0f22e9cb59f1a72
[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" || 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 = SSL ? "https" : "http";
787     }
788
789     // Start with scheme and hostname.
790     if (g_bNormalizeRequest) {
791         target = string(scheme) + "://" + site.m_name;
792     }
793     else {
794         dynabuf name(64);
795         GetServerVariable(lpECB,"SERVER_NAME",name,64);
796         target = string(scheme) + "://" + static_cast<char*>(name);
797     }
798     
799     // If port is non-default, append it.
800     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
801         target = target + ':' + static_cast<char*>(port);
802
803     // Append path.
804     if (!url.empty())
805         target+=static_cast<char*>(url);
806     
807     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
808 }
809
810 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
811 {
812     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
813     static const char* ctype="Content-Type: text/html\r\n";
814     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
815     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
816     DWORD resplen=strlen(xmsg);
817     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
818     resplen=strlen(msg);
819     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
820     static const char* xmsg2="</BODY></HTML>";
821     resplen=strlen(xmsg2);
822     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
823     return HSE_STATUS_SUCCESS;
824 }
825
826 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
827 {
828     const IPropertySet* props=app->getPropertySet("Errors");
829     if (props) {
830         pair<bool,const char*> p=props->getString(page);
831         if (p.first) {
832             ifstream infile(p.second);
833             if (!infile.fail()) {
834                 const char* res = mlp.run(infile,props);
835                 if (res) {
836                     static const char* ctype="Content-Type: text/html\r\n";
837                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
838                     DWORD resplen=strlen(res);
839                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
840                     return HSE_STATUS_SUCCESS;
841                 }
842             }
843         }
844     }
845     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
846     return WriteClientError(lpECB,"Unable to open error template, check settings.");
847 }
848
849 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
850 {
851     string targeturl;
852     const IApplication* application=NULL;
853     try
854     {
855         ostringstream threadid;
856         threadid << "[" << getpid() << "] shire_handler" << '\0';
857         saml::NDC ndc(threadid.str().c_str());
858
859         // Determine web site number. This can't really fail, I don't think.
860         dynabuf buf(128);
861         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
862
863         // Match site instance to host name, skip if no match.
864         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
865         if (map_i==g_Sites.end())
866             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
867             
868         // We lock the configuration system for the duration.
869         IConfig* conf=g_Config->getINI();
870         Locker locker(conf);
871         
872         // Map request to application and content settings.
873         string targeturl;
874         IRequestMapper* mapper=conf->getRequestMapper();
875         Locker locker2(mapper);
876         IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
877         pair<bool,const char*> application_id=settings.first->getString("applicationId");
878         application=conf->getApplication(application_id.second);
879         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
880         if (!application || !sessionProps)
881             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
882
883         SHIRE shire(application);
884         
885         const char* shireURL=shire.getShireURL(targeturl.c_str());
886         if (!shireURL)
887             return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
888
889         // Make sure we only process the SHIRE requests.
890         if (!strstr(targeturl.c_str(),shireURL))
891             return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
892                 "Make sure the mapped file extension doesn't match actual content.");
893
894         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
895
896         // Make sure this is SSL, if it should be
897         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
898         if (!shireSSL.first || shireSSL.second) {
899             GetServerVariable(lpECB,"HTTPS",buf,10);
900             if (buf!="on")
901                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
902         }
903         
904         // If this is a GET, we manufacture an AuthnRequest.
905         if (!stricmp(lpECB->lpszMethod,"GET")) {
906             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
907             if (!areq)
908                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
909             targeturl = string("Location: ") + areq + "\r\n"
910                 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
911                 "Cache-Control: private,no-store,no-cache\r\n"
912                 "Connection: close\r\n";
913             HSE_SEND_HEADER_EX_INFO hinfo;
914             hinfo.pszStatus="302 Moved";
915             hinfo.pszHeader=targeturl.c_str();
916             hinfo.cchStatus=9;
917             hinfo.cchHeader=targeturl.length();
918             hinfo.fKeepConn=FALSE;
919             if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
920                 return HSE_STATUS_SUCCESS;
921             return HSE_STATUS_ERROR;
922         }
923         else if (stricmp(lpECB->lpszMethod,"POST"))
924             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
925
926         // Sure sure this POST is an appropriate content type
927         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
928             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
929     
930         // Read the data.
931         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
932         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
933             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
934         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
935             string cgistr;
936             char buf[8192];
937             DWORD datalen=lpECB->cbTotalBytes;
938             while (datalen) {
939                 DWORD buflen=8192;
940                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
941                 if (!ret || !buflen)
942                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
943                 cgistr.append(buf,buflen);
944                 datalen-=buflen;
945             }
946             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
947         }
948         else
949             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
950     
951         // Make sure the SAML Response parameter exists
952         if (!elements.first || !*elements.first)
953             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
954     
955         // Make sure the target parameter exists
956         if (!elements.second || !*elements.second)
957             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
958             
959         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
960
961         // Process the post.
962         string cookie;
963         RPCError* status=NULL;
964         ShibMLP markupProcessor;
965         markupProcessor.insert("requestURL", targeturl.c_str());
966         try {
967             status = shire.sessionCreate(elements.first,buf,cookie);
968         }
969         catch (ShibTargetException &e) {
970             markupProcessor.insert("errorType", "Session Creation Service Error");
971             markupProcessor.insert("errorText", e.what());
972             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
973             return WriteClientError(lpECB, application, "shire", markupProcessor);
974         }
975 #ifndef _DEBUG
976         catch (...) {
977             markupProcessor.insert("errorType", "Session Creation Service Error");
978             markupProcessor.insert("errorText", "Unexpected Exception");
979             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
980             return WriteClientError(lpECB, application, "shire", markupProcessor);
981         }
982 #endif
983
984         if (status->isError()) {
985             if (status->isRetryable()) {
986                 delete status;
987                 const char* loc=shire.getAuthnRequest(elements.second);
988                 DWORD len=strlen(loc);
989                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
990                     return HSE_STATUS_SUCCESS;
991                 return HSE_STATUS_ERROR;
992             }
993     
994             // Return this error to the user.
995             markupProcessor.insert(*status);
996             delete status;
997             return WriteClientError(lpECB,application,"shire",markupProcessor);
998         }
999         delete status;
1000     
1001         // We've got a good session, set the cookie and redirect to target.
1002         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1003             "Location: " + elements.second + "\r\n"
1004             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1005             "Cache-Control: private,no-store,no-cache\r\n"
1006             "Connection: close\r\n";
1007         HSE_SEND_HEADER_EX_INFO hinfo;
1008         hinfo.pszStatus="302 Moved";
1009         hinfo.pszHeader=cookie.c_str();
1010         hinfo.cchStatus=9;
1011         hinfo.cchHeader=cookie.length();
1012         hinfo.fKeepConn=FALSE;
1013         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
1014             return HSE_STATUS_SUCCESS;
1015     }
1016     catch (ShibTargetException &e) {
1017         if (application) {
1018             ShibMLP markupProcessor;
1019             markupProcessor.insert("requestURL", targeturl.c_str());
1020             markupProcessor.insert("errorType", "Session Creation Service Error");
1021             markupProcessor.insert("errorText", e.what());
1022             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1023             return WriteClientError(lpECB,application,"shire",markupProcessor);
1024         }
1025     }
1026 #ifndef _DEBUG
1027     catch (...) {
1028         if (application) {
1029             ShibMLP markupProcessor;
1030             markupProcessor.insert("requestURL", targeturl.c_str());
1031             markupProcessor.insert("errorType", "Session Creation Service Error");
1032             markupProcessor.insert("errorText", "Unexpected Exception");
1033             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1034             return WriteClientError(lpECB,application,"shire",markupProcessor);
1035         }
1036     }
1037 #endif
1038     
1039     return HSE_STATUS_ERROR;
1040 }