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