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