Port up context changes to prevent false spoofing error.
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * isapi_shib.cpp
19  * 
20  * Shibboleth ISAPI filter
21  */
22
23 #define SHIBSP_LITE
24 #include "config_win32.h"
25
26 #define _CRT_NONSTDC_NO_DEPRECATE 1
27 #define _CRT_SECURE_NO_DEPRECATE 1
28
29 #include <shibsp/AbstractSPRequest.h>
30 #include <shibsp/SPConfig.h>
31 #include <shibsp/ServiceProvider.h>
32 #include <xmltooling/unicode.h>
33 #include <xmltooling/XMLToolingConfig.h>
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/XMLConstants.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xercesc/util/Base64.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
39
40 #include <set>
41 #include <sstream>
42 #include <fstream>
43 #include <process.h>
44
45 #include <windows.h>
46 #include <httpfilt.h>
47 #include <httpext.h>
48
49 using namespace shibsp;
50 using namespace xmltooling;
51 using namespace xercesc;
52 using namespace std;
53
54 // globals
55 namespace {
56     static const XMLCh path[] =             UNICODE_LITERAL_4(p,a,t,h);
57     static const XMLCh validate[] =         UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
58     static const XMLCh name[] =             UNICODE_LITERAL_4(n,a,m,e);
59     static const XMLCh port[] =             UNICODE_LITERAL_4(p,o,r,t);
60     static const XMLCh sslport[] =          UNICODE_LITERAL_7(s,s,l,p,o,r,t);
61     static const XMLCh scheme[] =           UNICODE_LITERAL_6(s,c,h,e,m,e);
62     static const XMLCh id[] =               UNICODE_LITERAL_2(i,d);
63     static const XMLCh ISAPI[] =            UNICODE_LITERAL_5(I,S,A,P,I);
64     static const XMLCh Alias[] =            UNICODE_LITERAL_5(A,l,i,a,s);
65     static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
66     static const XMLCh Site[] =             UNICODE_LITERAL_4(S,i,t,e);
67
68     struct site_t {
69         site_t(const DOMElement* e)
70         {
71             auto_ptr_char n(e->getAttributeNS(NULL,name));
72             auto_ptr_char s(e->getAttributeNS(NULL,scheme));
73             auto_ptr_char p(e->getAttributeNS(NULL,port));
74             auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
75             if (n.get()) m_name=n.get();
76             if (s.get()) m_scheme=s.get();
77             if (p.get()) m_port=p.get();
78             if (p2.get()) m_sslport=p2.get();
79             e = XMLHelper::getFirstChildElement(e, Alias);
80             while (e) {
81                 if (e->hasChildNodes()) {
82                     auto_ptr_char alias(e->getFirstChild()->getNodeValue());
83                     m_aliases.insert(alias.get());
84                 }
85                 e = XMLHelper::getNextSiblingElement(e, Alias);
86             }
87         }
88         string m_scheme,m_port,m_sslport,m_name;
89         set<string> m_aliases;
90     };
91
92     struct context_t {
93         char* m_user;
94         bool m_checked;
95     };
96     
97     HINSTANCE g_hinstDLL;
98     SPConfig* g_Config = NULL;
99     map<string,site_t> g_Sites;
100     bool g_bNormalizeRequest = true;
101     string g_unsetHeaderValue;
102     bool g_checkSpoofing = true;
103     bool g_catchAll = false;
104     vector<string> g_NoCerts;
105 }
106
107 BOOL LogEvent(
108     LPCSTR  lpUNCServerName,
109     WORD  wType,
110     DWORD  dwEventID,
111     PSID  lpUserSid,
112     LPCSTR  message)
113 {
114     LPCSTR  messages[] = {message, NULL};
115     
116     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
117     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
118     return (DeregisterEventSource(hElog) && res);
119 }
120
121 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
122 {
123     if (fdwReason==DLL_PROCESS_ATTACH)
124         g_hinstDLL=hinstDLL;
125     return TRUE;
126 }
127
128 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
129 {
130     if (!pVer)
131         return FALSE;
132         
133     if (!g_Config) {
134         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
135                 "Extension mode startup not possible, is the DLL loaded as a filter?");
136         return FALSE;
137     }
138
139     pVer->dwExtensionVersion=HSE_VERSION;
140     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
141     return TRUE;
142 }
143
144 extern "C" BOOL WINAPI TerminateExtension(DWORD)
145 {
146     return TRUE;    // cleanup should happen when filter unloads
147 }
148
149 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
150 {
151     if (!pVer)
152         return FALSE;
153     else if (g_Config) {
154         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
155                 "Reentrant filter initialization, ignoring...");
156         return TRUE;
157     }
158
159     g_Config=&SPConfig::getConfig();
160     g_Config->setFeatures(
161         SPConfig::Listener |
162         SPConfig::Caching |
163         SPConfig::RequestMapping |
164         SPConfig::InProcess |
165         SPConfig::Logging |
166         SPConfig::Handlers
167         );
168     if (!g_Config->init()) {
169         g_Config=NULL;
170         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
171                 "Filter startup failed during library initialization, check native log for help.");
172         return FALSE;
173     }
174
175     LPCSTR config=getenv("SHIBSP_CONFIG");
176     if (!config)
177         config=SHIBSP_CONFIG;
178
179     try {
180         DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
181         XercesJanitor<DOMDocument> docjanitor(dummydoc);
182         DOMElement* dummy = dummydoc->createElementNS(NULL,path);
183         auto_ptr_XMLCh src(config);
184         dummy->setAttributeNS(NULL,path,src.get());
185         dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
186
187         g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
188         g_Config->getServiceProvider()->init();
189     }
190     catch (exception& ex) {
191         g_Config->term();
192         g_Config=NULL;
193         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
194         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
195                 "Filter startup failed to load configuration, check native log for details.");
196         return FALSE;
197     }
198     
199     // Access implementation-specifics and site mappings.
200     ServiceProvider* sp=g_Config->getServiceProvider();
201     Locker locker(sp);
202     const PropertySet* props=sp->getPropertySet("InProcess");
203     if (props) {
204         pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
205         if (unsetValue.first)
206             g_unsetHeaderValue = unsetValue.second;
207         pair<bool,bool> flag=props->getBool("checkSpoofing");
208         g_checkSpoofing = !flag.first || flag.second;
209         flag=props->getBool("catchAll");
210         g_catchAll = flag.first && flag.second;
211
212         props = props->getPropertySet("ISAPI");
213         if (props) {
214             flag = props->getBool("normalizeRequest");
215             g_bNormalizeRequest = !flag.first || flag.second;
216             const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
217             while (child) {
218                 auto_ptr_char id(child->getAttributeNS(NULL,id));
219                 if (id.get())
220                     g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
221                 child=XMLHelper::getNextSiblingElement(child,Site);
222             }
223         }
224     }
225
226     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
227     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
228     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
229                    SF_NOTIFY_SECURE_PORT |
230                    SF_NOTIFY_NONSECURE_PORT |
231                    SF_NOTIFY_PREPROC_HEADERS |
232                    SF_NOTIFY_LOG);
233     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
234     return TRUE;
235 }
236
237 extern "C" BOOL WINAPI TerminateFilter(DWORD)
238 {
239     if (g_Config)
240         g_Config->term();
241     g_Config = NULL;
242     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
243     return TRUE;
244 }
245
246 /* Next up, some suck-free versions of various APIs.
247
248    You DON'T require people to guess the buffer size and THEN tell them the right size.
249    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
250    constant strings aren't typed as such, making it just that much harder. These versions
251    are now updated to use a special growable buffer object, modeled after the standard
252    string class. The standard string won't work because they left out the option to
253    pre-allocate a non-constant buffer.
254 */
255
256 class dynabuf
257 {
258 public:
259     dynabuf() { bufptr=NULL; buflen=0; }
260     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
261     ~dynabuf() { delete[] bufptr; }
262     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
263     size_t size() const { return buflen; }
264     bool empty() const { return length()==0; }
265     void reserve(size_t s, bool keep=false);
266     void erase() { if (bufptr) memset(bufptr,0,buflen); }
267     operator char*() { return bufptr; }
268     bool operator ==(const char* s) const;
269     bool operator !=(const char* s) const { return !(*this==s); }
270 private:
271     char* bufptr;
272     size_t buflen;
273 };
274
275 void dynabuf::reserve(size_t s, bool keep)
276 {
277     if (s<=buflen)
278         return;
279     char* p=new char[s];
280     if (keep)
281         while (buflen--)
282             p[buflen]=bufptr[buflen];
283     buflen=s;
284     delete[] bufptr;
285     bufptr=p;
286 }
287
288 bool dynabuf::operator==(const char* s) const
289 {
290     if (buflen==NULL || s==NULL)
291         return (buflen==NULL && s==NULL);
292     else
293         return strcmp(bufptr,s)==0;
294 }
295
296 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
297 {
298     s.reserve(size);
299     s.erase();
300     size=s.size();
301
302     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
303         // Grumble. Check the error.
304         DWORD e=GetLastError();
305         if (e==ERROR_INSUFFICIENT_BUFFER)
306             s.reserve(size);
307         else
308             break;
309     }
310     if (bRequired && s.empty())
311         throw ERROR_NO_DATA;
312 }
313
314 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
315 {
316     s.reserve(size);
317     s.erase();
318     size=s.size();
319
320     while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
321         // Grumble. Check the error.
322         DWORD e=GetLastError();
323         if (e==ERROR_INSUFFICIENT_BUFFER)
324             s.reserve(size);
325         else
326             break;
327     }
328     if (bRequired && s.empty())
329         throw ERROR_NO_DATA;
330 }
331
332 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
333                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
334 {
335     s.reserve(size);
336     s.erase();
337     size=s.size();
338
339     while (!pn->GetHeader(pfc,lpszName,s,&size)) {
340         // Grumble. Check the error.
341         DWORD e=GetLastError();
342         if (e==ERROR_INSUFFICIENT_BUFFER)
343             s.reserve(size);
344         else
345             break;
346     }
347     if (bRequired && s.empty())
348         throw ERROR_NO_DATA;
349 }
350
351 /****************************************************************************/
352 // ISAPI Filter
353
354 class ShibTargetIsapiF : public AbstractSPRequest
355 {
356   PHTTP_FILTER_CONTEXT m_pfc;
357   PHTTP_FILTER_PREPROC_HEADERS m_pn;
358   multimap<string,string> m_headers;
359   int m_port;
360   string m_scheme,m_hostname;
361   mutable string m_remote_addr,m_content_type,m_method;
362   dynabuf m_allhttp;
363
364 public:
365   ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
366       : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
367
368     // URL path always come from IIS.
369     dynabuf var(256);
370     GetHeader(pn,pfc,"url",var,256,false);
371     setRequestURI(var);
372
373     // Port may come from IIS or from site def.
374     if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
375         GetServerVariable(pfc,"SERVER_PORT",var,10);
376         m_port = atoi(var);
377     }
378     else if (pfc->fIsSecurePort) {
379         m_port = atoi(site.m_sslport.c_str());
380     }
381     else {
382         m_port = atoi(site.m_port.c_str());
383     }
384     
385     // Scheme may come from site def or be derived from IIS.
386     m_scheme=site.m_scheme;
387     if (m_scheme.empty() || !g_bNormalizeRequest)
388         m_scheme=pfc->fIsSecurePort ? "https" : "http";
389
390     GetServerVariable(pfc,"SERVER_NAME",var,32);
391
392     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
393     m_hostname = var;
394     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
395         m_hostname=site.m_name;
396     
397     if (!pfc->pFilterContext) {
398         pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
399         if (static_cast<context_t*>(pfc->pFilterContext)) {
400             static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
401             static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
402         }
403     }    
404   }
405   ~ShibTargetIsapiF() { }
406
407   const char* getScheme() const {
408     return m_scheme.c_str();
409   }
410   const char* getHostname() const {
411     return m_hostname.c_str();
412   }
413   int getPort() const {
414     return m_port;
415   }
416   const char* getMethod() const {
417     if (m_method.empty()) {
418         dynabuf var(5);
419         GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
420         if (!var.empty())
421             m_method = var;
422     }
423     return m_method.c_str();
424   }
425   string getContentType() const {
426     if (m_content_type.empty()) {
427         dynabuf var(32);
428         GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
429         if (!var.empty())
430             m_content_type = var;
431     }
432     return m_content_type;
433   }
434   long getContentLength() const {
435       return 0;
436   }
437   string getRemoteAddr() const {
438     if (m_remote_addr.empty()) {
439         dynabuf var(16);
440         GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
441         if (!var.empty())
442             m_remote_addr = var;
443     }
444     return m_remote_addr;
445   }
446   void log(SPLogLevel level, const string& msg) {
447     AbstractSPRequest::log(level,msg);
448     if (level >= SPError)
449         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
450   }
451   void clearHeader(const char* rawname, const char* cginame) {
452         if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
453         if (m_allhttp.empty())
454                 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
455         if (strstr(m_allhttp, cginame))
456             throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
457     }
458     string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
459     hdr += ':';
460     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
461   }
462   void setHeader(const char* name, const char* value) {
463     string hdr(name);
464     hdr += ':';
465     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
466   }
467   string getHeader(const char* name) const {
468     string hdr(name);
469     hdr += ':';
470     dynabuf buf(256);
471     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
472     return string(buf);
473   }
474   void setRemoteUser(const char* user) {
475     setHeader("remote-user", user);
476     if (m_pfc->pFilterContext) {
477         if (!user || !*user)
478             static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
479         else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
480             strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
481     }
482   }
483   string getRemoteUser() const {
484     return getHeader("remote-user");
485   }
486   void setResponseHeader(const char* name, const char* value) {
487     // Set for later.
488     if (value)
489         m_headers.insert(make_pair(name,value));
490     else
491         m_headers.erase(name);
492   }
493   long sendResponse(istream& in, long status) {
494     string hdr = string("Connection: close\r\n");
495     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
496         hdr += i->first + ": " + i->second + "\r\n";
497     hdr += "\r\n";
498     const char* codestr="200 OK";
499     switch (status) {
500         case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="401 Authorization Required"; break;
501         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="403 Forbidden"; break;
502         case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="404 Not Found"; break;
503         case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="500 Server Error"; break;
504     }
505     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
506     char buf[1024];
507     while (in) {
508         in.read(buf,1024);
509         DWORD resplen = in.gcount();
510         m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
511     }
512     return SF_STATUS_REQ_FINISHED;
513   }
514   long sendRedirect(const char* url) {
515     // XXX: Don't support the httpRedirect option, yet.
516     string hdr=string("Location: ") + url + "\r\n"
517       "Content-Type: text/html\r\n"
518       "Content-Length: 40\r\n"
519       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
520       "Cache-Control: private,no-store,no-cache\r\n";
521     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
522         hdr += i->first + ": " + i->second + "\r\n";
523     hdr += "\r\n";
524     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
525     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
526     DWORD resplen=40;
527     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
528     return SF_STATUS_REQ_FINISHED;
529   }
530   long returnDecline() {
531       return SF_STATUS_REQ_NEXT_NOTIFICATION;
532   }
533   long returnOK() {
534     return SF_STATUS_REQ_NEXT_NOTIFICATION;
535   }
536
537   const vector<string>& getClientCertificates() const {
538       return g_NoCerts;
539   }
540   
541   // The filter never processes the POST, so stub these methods.
542   const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
543   const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
544 };
545
546 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
547 {
548     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
549     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
550     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
551     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
552                             "<H1>Shibboleth Filter Error</H1>";
553     DWORD resplen=strlen(xmsg);
554     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
555     resplen=strlen(msg);
556     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
557     static const char* xmsg2="</BODY></HTML>";
558     resplen=strlen(xmsg2);
559     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
560     return SF_STATUS_REQ_FINISHED;
561 }
562
563 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
564 {
565     // Is this a log notification?
566     if (notificationType==SF_NOTIFY_LOG)
567     {
568         if (pfc->pFilterContext)
569                 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
570         return SF_STATUS_REQ_NEXT_NOTIFICATION;
571     }
572
573     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
574     try
575     {
576         // Determine web site number. This can't really fail, I don't think.
577         dynabuf buf(128);
578         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
579
580         // Match site instance to host name, skip if no match.
581         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
582         if (map_i==g_Sites.end())
583             return SF_STATUS_REQ_NEXT_NOTIFICATION;
584             
585         ostringstream threadid;
586         threadid << "[" << getpid() << "] isapi_shib" << '\0';
587         xmltooling::NDC ndc(threadid.str().c_str());
588
589         ShibTargetIsapiF stf(pfc, pn, map_i->second);
590
591         // "false" because we don't override the Shib settings
592         pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
593         if (pfc->pFilterContext)
594             static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
595         if (res.first) return res.second;
596
597         // "false" because we don't override the Shib settings
598         res = stf.getServiceProvider().doExport(stf);
599         if (res.first) return res.second;
600
601         res = stf.getServiceProvider().doAuthorization(stf);
602         if (res.first) return res.second;
603
604         return SF_STATUS_REQ_NEXT_NOTIFICATION;
605     }
606     catch(bad_alloc) {
607         return WriteClientError(pfc,"Out of Memory");
608     }
609     catch(long e) {
610         if (e==ERROR_NO_DATA)
611             return WriteClientError(pfc,"A required variable or header was empty.");
612         else
613             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
614     }
615     catch (exception& e) {
616         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
617         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
618     }
619     catch(...) {
620         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
621         if (g_catchAll)
622             return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
623         throw;
624     }
625
626     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
627 }
628         
629
630 /****************************************************************************/
631 // ISAPI Extension
632
633 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
634 {
635     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
636     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
637     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
638     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
639     DWORD resplen=strlen(xmsg);
640     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
641     resplen=strlen(msg);
642     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
643     static const char* xmsg2="</BODY></HTML>";
644     resplen=strlen(xmsg2);
645     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
646     return HSE_STATUS_SUCCESS;
647 }
648
649
650 class ShibTargetIsapiE : public AbstractSPRequest
651 {
652   LPEXTENSION_CONTROL_BLOCK m_lpECB;
653   multimap<string,string> m_headers;
654   mutable vector<string> m_certs;
655   mutable string m_body;
656   mutable bool m_gotBody;
657   int m_port;
658   string m_scheme,m_hostname,m_uri;
659   mutable string m_remote_addr,m_remote_user;
660   
661 public:
662   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
663       : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
664     dynabuf ssl(5);
665     GetServerVariable(lpECB,"HTTPS",ssl,5);
666     bool SSL=(ssl=="on" || ssl=="ON");
667
668     // Scheme may come from site def or be derived from IIS.
669     m_scheme=site.m_scheme;
670     if (m_scheme.empty() || !g_bNormalizeRequest)
671         m_scheme = SSL ? "https" : "http";
672
673     // URL path always come from IIS.
674     dynabuf url(256);
675     GetServerVariable(lpECB,"URL",url,255);
676
677     // Port may come from IIS or from site def.
678     dynabuf port(11);
679     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
680         GetServerVariable(lpECB,"SERVER_PORT",port,10);
681     else if (SSL) {
682         strncpy(port,site.m_sslport.c_str(),10);
683         static_cast<char*>(port)[10]=0;
684     }
685     else {
686         strncpy(port,site.m_port.c_str(),10);
687         static_cast<char*>(port)[10]=0;
688     }
689     m_port = atoi(port);
690
691     dynabuf var(32);
692     GetServerVariable(lpECB, "SERVER_NAME", var, 32);
693
694     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
695     m_hostname=var;
696     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
697         m_hostname=site.m_name;
698
699     /*
700      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
701      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
702      * which is the default. No perfect way to tell, but we can take a good guess by checking
703      * whether the URL is a substring of the PATH_INFO:
704      * 
705      * e.g. for /Shibboleth.sso/SAML/POST
706      * 
707      *  Bad mode (default):
708      *      URL:        /Shibboleth.sso
709      *      PathInfo:   /Shibboleth.sso/SAML/POST
710      * 
711      *  Good mode:
712      *      URL:        /Shibboleth.sso
713      *      PathInfo:   /SAML/POST
714      */
715     
716     string uri;
717
718     // Clearly we're only in bad mode if path info exists at all.
719     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
720         if (strstr(lpECB->lpszPathInfo,url))
721             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
722             uri = lpECB->lpszPathInfo;
723         else {
724             uri = url;
725             uri += lpECB->lpszPathInfo;
726         }
727     }
728     else {
729         uri = url;
730     }
731     
732     // For consistency with Apache, let's add the query string.
733     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
734         uri += '?';
735         uri += lpECB->lpszQueryString;
736     }
737
738     setRequestURI(uri.c_str());
739   }
740   ~ShibTargetIsapiE() { }
741
742   const char* getScheme() const {
743     return m_scheme.c_str();
744   }
745   const char* getHostname() const {
746     return m_hostname.c_str();
747   }
748   int getPort() const {
749     return m_port;
750   }
751   const char* getMethod() const {
752     return m_lpECB->lpszMethod;
753   }
754   string getContentType() const {
755     return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
756   }
757   long getContentLength() const {
758       return m_lpECB->cbTotalBytes;
759   }
760   string getRemoteUser() const {
761     if (m_remote_user.empty()) {
762         dynabuf var(16);
763         GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
764         if (!var.empty())
765             m_remote_user = var;
766     }
767     return m_remote_user;
768   }
769   string getRemoteAddr() const {
770     if (m_remote_addr.empty()) {
771         dynabuf var(16);
772         GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
773         if (!var.empty())
774             m_remote_addr = var;
775     }
776     return m_remote_addr;
777   }
778   void log(SPLogLevel level, const string& msg) const {
779       AbstractSPRequest::log(level,msg);
780       if (level >= SPError)
781           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
782   }
783   string getHeader(const char* name) const {
784     string hdr("HTTP_");
785     for (; *name; ++name) {
786         if (*name=='-')
787             hdr += '_';
788         else
789             hdr += toupper(*name);
790     }
791     dynabuf buf(128);
792     GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
793     return buf.empty() ? "" : buf;
794   }
795   void setResponseHeader(const char* name, const char* value) {
796     // Set for later.
797     if (value)
798         m_headers.insert(make_pair(name,value));
799     else
800         m_headers.erase(name);
801   }
802   const char* getQueryString() const {
803     return m_lpECB->lpszQueryString;
804   }
805   const char* getRequestBody() const {
806     if (m_gotBody)
807         return m_body.c_str();
808     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
809         throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
810     else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
811       m_gotBody=true;
812       char buf[8192];
813       DWORD datalen=m_lpECB->cbTotalBytes;
814       while (datalen) {
815         DWORD buflen=8192;
816         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
817         if (!ret || !buflen)
818             throw IOException("Error reading request body from browser.");
819         m_body.append(buf, buflen);
820         datalen-=buflen;
821       }
822     }
823     else if (m_lpECB->cbAvailable) {
824         m_gotBody=true;
825         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
826     }
827     return m_body.c_str();
828   }
829   long sendResponse(istream& in, long status) {
830     string hdr = string("Connection: close\r\n");
831     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
832         hdr += i->first + ": " + i->second + "\r\n";
833     hdr += "\r\n";
834     const char* codestr="200 OK";
835     switch (status) {
836         case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="401 Authorization Required"; break;
837         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="403 Forbidden"; break;
838         case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="404 Not Found"; break;
839         case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="500 Server Error"; break;
840     }
841     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
842     char buf[1024];
843     while (in) {
844         in.read(buf,1024);
845         DWORD resplen = in.gcount();
846         m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
847     }
848     return HSE_STATUS_SUCCESS;
849   }
850   long sendRedirect(const char* url) {
851     string hdr=string("Location: ") + url + "\r\n"
852       "Content-Type: text/html\r\n"
853       "Content-Length: 40\r\n"
854       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
855       "Cache-Control: private,no-store,no-cache\r\n";
856     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
857         hdr += i->first + ": " + i->second + "\r\n";
858     hdr += "\r\n";
859     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
860     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
861     DWORD resplen=40;
862     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
863     return HSE_STATUS_SUCCESS;
864   }
865   // Decline happens in the POST processor if this isn't the shire url
866   // Note that it can also happen with HTAccess, but we don't support that, yet.
867   long returnDecline() {
868     return WriteClientError(
869         m_lpECB,
870         "ISAPI extension can only be invoked to process Shibboleth protocol requests."
871                 "Make sure the mapped file extension doesn't match actual content."
872         );
873   }
874   long returnOK() {
875       return HSE_STATUS_SUCCESS;
876   }
877
878   const vector<string>& getClientCertificates() const {
879       if (m_certs.empty()) {
880         char CertificateBuf[8192];
881         CERT_CONTEXT_EX ccex;
882         ccex.cbAllocated = sizeof(CertificateBuf);
883         ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
884         DWORD dwSize = sizeof(ccex);
885
886         if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
887             if (ccex.CertContext.cbCertEncoded) {
888                 unsigned int outlen;
889                 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
890                 m_certs.push_back(reinterpret_cast<char*>(serialized));
891                 XMLString::release(&serialized);
892             }
893         }
894       }
895       return m_certs;
896   }
897
898   // Not used in the extension.
899   void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
900   void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
901   void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
902 };
903
904 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
905 {
906     try {
907         ostringstream threadid;
908         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
909         xmltooling::NDC ndc(threadid.str().c_str());
910
911         // Determine web site number. This can't really fail, I don't think.
912         dynabuf buf(128);
913         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
914
915         // Match site instance to host name, skip if no match.
916         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
917         if (map_i==g_Sites.end())
918             return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
919
920         ShibTargetIsapiE ste(lpECB, map_i->second);
921         pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
922         if (res.first) return res.second;
923         
924         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
925
926     }
927     catch(bad_alloc) {
928         return WriteClientError(lpECB,"Out of Memory");
929     }
930     catch(long e) {
931         if (e==ERROR_NO_DATA)
932             return WriteClientError(lpECB,"A required variable or header was empty.");
933         else
934             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
935     }
936     catch (exception& e) {
937         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
938         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
939     }
940     catch(...) {
941         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
942         if (g_catchAll)
943             return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
944         throw;
945     }
946
947     // If we get here we've got an error.
948     return HSE_STATUS_ERROR;
949 }