Fix backslashes in SHIBSP_PREFIX variable by manually creating it during the script...
[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         if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
568                 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
569         return SF_STATUS_REQ_NEXT_NOTIFICATION;
570     }
571
572     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
573     try
574     {
575         // Determine web site number. This can't really fail, I don't think.
576         dynabuf buf(128);
577         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
578
579         // Match site instance to host name, skip if no match.
580         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
581         if (map_i==g_Sites.end())
582             return SF_STATUS_REQ_NEXT_NOTIFICATION;
583             
584         ostringstream threadid;
585         threadid << "[" << getpid() << "] isapi_shib" << '\0';
586         xmltooling::NDC ndc(threadid.str().c_str());
587
588         ShibTargetIsapiF stf(pfc, pn, map_i->second);
589
590         // "false" because we don't override the Shib settings
591         pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
592         if (pfc->pFilterContext)
593             static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
594         if (res.first) return res.second;
595
596         // "false" because we don't override the Shib settings
597         res = stf.getServiceProvider().doExport(stf);
598         if (res.first) return res.second;
599
600         res = stf.getServiceProvider().doAuthorization(stf);
601         if (res.first) return res.second;
602
603         return SF_STATUS_REQ_NEXT_NOTIFICATION;
604     }
605     catch(bad_alloc) {
606         return WriteClientError(pfc,"Out of Memory");
607     }
608     catch(long e) {
609         if (e==ERROR_NO_DATA)
610             return WriteClientError(pfc,"A required variable or header was empty.");
611         else
612             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
613     }
614     catch (exception& e) {
615         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
616         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
617     }
618     catch(...) {
619         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
620         if (g_catchAll)
621             return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
622         throw;
623     }
624
625     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
626 }
627         
628
629 /****************************************************************************/
630 // ISAPI Extension
631
632 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
633 {
634     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
635     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
636     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
637     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
638     DWORD resplen=strlen(xmsg);
639     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
640     resplen=strlen(msg);
641     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
642     static const char* xmsg2="</BODY></HTML>";
643     resplen=strlen(xmsg2);
644     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
645     return HSE_STATUS_SUCCESS;
646 }
647
648
649 class ShibTargetIsapiE : public AbstractSPRequest
650 {
651   LPEXTENSION_CONTROL_BLOCK m_lpECB;
652   multimap<string,string> m_headers;
653   mutable vector<string> m_certs;
654   mutable string m_body;
655   mutable bool m_gotBody;
656   int m_port;
657   string m_scheme,m_hostname,m_uri;
658   mutable string m_remote_addr,m_remote_user;
659   
660 public:
661   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
662       : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
663     dynabuf ssl(5);
664     GetServerVariable(lpECB,"HTTPS",ssl,5);
665     bool SSL=(ssl=="on" || ssl=="ON");
666
667     // Scheme may come from site def or be derived from IIS.
668     m_scheme=site.m_scheme;
669     if (m_scheme.empty() || !g_bNormalizeRequest)
670         m_scheme = SSL ? "https" : "http";
671
672     // URL path always come from IIS.
673     dynabuf url(256);
674     GetServerVariable(lpECB,"URL",url,255);
675
676     // Port may come from IIS or from site def.
677     dynabuf port(11);
678     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
679         GetServerVariable(lpECB,"SERVER_PORT",port,10);
680     else if (SSL) {
681         strncpy(port,site.m_sslport.c_str(),10);
682         static_cast<char*>(port)[10]=0;
683     }
684     else {
685         strncpy(port,site.m_port.c_str(),10);
686         static_cast<char*>(port)[10]=0;
687     }
688     m_port = atoi(port);
689
690     dynabuf var(32);
691     GetServerVariable(lpECB, "SERVER_NAME", var, 32);
692
693     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
694     m_hostname=var;
695     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
696         m_hostname=site.m_name;
697
698     /*
699      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
700      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
701      * which is the default. No perfect way to tell, but we can take a good guess by checking
702      * whether the URL is a substring of the PATH_INFO:
703      * 
704      * e.g. for /Shibboleth.sso/SAML/POST
705      * 
706      *  Bad mode (default):
707      *      URL:        /Shibboleth.sso
708      *      PathInfo:   /Shibboleth.sso/SAML/POST
709      * 
710      *  Good mode:
711      *      URL:        /Shibboleth.sso
712      *      PathInfo:   /SAML/POST
713      */
714     
715     string uri;
716
717     // Clearly we're only in bad mode if path info exists at all.
718     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
719         if (strstr(lpECB->lpszPathInfo,url))
720             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
721             uri = lpECB->lpszPathInfo;
722         else {
723             uri = url;
724             uri += lpECB->lpszPathInfo;
725         }
726     }
727     else {
728         uri = url;
729     }
730     
731     // For consistency with Apache, let's add the query string.
732     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
733         uri += '?';
734         uri += lpECB->lpszQueryString;
735     }
736
737     setRequestURI(uri.c_str());
738   }
739   ~ShibTargetIsapiE() { }
740
741   const char* getScheme() const {
742     return m_scheme.c_str();
743   }
744   const char* getHostname() const {
745     return m_hostname.c_str();
746   }
747   int getPort() const {
748     return m_port;
749   }
750   const char* getMethod() const {
751     return m_lpECB->lpszMethod;
752   }
753   string getContentType() const {
754     return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
755   }
756   long getContentLength() const {
757       return m_lpECB->cbTotalBytes;
758   }
759   string getRemoteUser() const {
760     if (m_remote_user.empty()) {
761         dynabuf var(16);
762         GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
763         if (!var.empty())
764             m_remote_user = var;
765     }
766     return m_remote_user;
767   }
768   string getRemoteAddr() const {
769     if (m_remote_addr.empty()) {
770         dynabuf var(16);
771         GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
772         if (!var.empty())
773             m_remote_addr = var;
774     }
775     return m_remote_addr;
776   }
777   void log(SPLogLevel level, const string& msg) const {
778       AbstractSPRequest::log(level,msg);
779       if (level >= SPError)
780           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
781   }
782   string getHeader(const char* name) const {
783     string hdr("HTTP_");
784     for (; *name; ++name) {
785         if (*name=='-')
786             hdr += '_';
787         else
788             hdr += toupper(*name);
789     }
790     dynabuf buf(128);
791     GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
792     return buf.empty() ? "" : buf;
793   }
794   void setResponseHeader(const char* name, const char* value) {
795     // Set for later.
796     if (value)
797         m_headers.insert(make_pair(name,value));
798     else
799         m_headers.erase(name);
800   }
801   const char* getQueryString() const {
802     return m_lpECB->lpszQueryString;
803   }
804   const char* getRequestBody() const {
805     if (m_gotBody)
806         return m_body.c_str();
807     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
808         throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
809     else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
810       m_gotBody=true;
811       char buf[8192];
812       DWORD datalen=m_lpECB->cbTotalBytes;
813       while (datalen) {
814         DWORD buflen=8192;
815         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
816         if (!ret || !buflen)
817             throw IOException("Error reading request body from browser.");
818         m_body.append(buf, buflen);
819         datalen-=buflen;
820       }
821     }
822     else if (m_lpECB->cbAvailable) {
823         m_gotBody=true;
824         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
825     }
826     return m_body.c_str();
827   }
828   long sendResponse(istream& in, long status) {
829     string hdr = string("Connection: close\r\n");
830     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
831         hdr += i->first + ": " + i->second + "\r\n";
832     hdr += "\r\n";
833     const char* codestr="200 OK";
834     switch (status) {
835         case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="401 Authorization Required"; break;
836         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="403 Forbidden"; break;
837         case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="404 Not Found"; break;
838         case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="500 Server Error"; break;
839     }
840     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
841     char buf[1024];
842     while (in) {
843         in.read(buf,1024);
844         DWORD resplen = in.gcount();
845         m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
846     }
847     return HSE_STATUS_SUCCESS;
848   }
849   long sendRedirect(const char* url) {
850     string hdr=string("Location: ") + url + "\r\n"
851       "Content-Type: text/html\r\n"
852       "Content-Length: 40\r\n"
853       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
854       "Cache-Control: private,no-store,no-cache\r\n";
855     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
856         hdr += i->first + ": " + i->second + "\r\n";
857     hdr += "\r\n";
858     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
859     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
860     DWORD resplen=40;
861     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
862     return HSE_STATUS_SUCCESS;
863   }
864   // Decline happens in the POST processor if this isn't the shire url
865   // Note that it can also happen with HTAccess, but we don't support that, yet.
866   long returnDecline() {
867     return WriteClientError(
868         m_lpECB,
869         "ISAPI extension can only be invoked to process Shibboleth protocol requests."
870                 "Make sure the mapped file extension doesn't match actual content."
871         );
872   }
873   long returnOK() {
874       return HSE_STATUS_SUCCESS;
875   }
876
877   const vector<string>& getClientCertificates() const {
878       if (m_certs.empty()) {
879         char CertificateBuf[8192];
880         CERT_CONTEXT_EX ccex;
881         ccex.cbAllocated = sizeof(CertificateBuf);
882         ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
883         DWORD dwSize = sizeof(ccex);
884
885         if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
886             if (ccex.CertContext.cbCertEncoded) {
887                 unsigned int outlen;
888                 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
889                 m_certs.push_back(reinterpret_cast<char*>(serialized));
890                 XMLString::release(&serialized);
891             }
892         }
893       }
894       return m_certs;
895   }
896
897   // Not used in the extension.
898   void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
899   void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
900   void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
901 };
902
903 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
904 {
905     try {
906         ostringstream threadid;
907         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
908         xmltooling::NDC ndc(threadid.str().c_str());
909
910         // Determine web site number. This can't really fail, I don't think.
911         dynabuf buf(128);
912         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
913
914         // Match site instance to host name, skip if no match.
915         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
916         if (map_i==g_Sites.end())
917             return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
918
919         ShibTargetIsapiE ste(lpECB, map_i->second);
920         pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
921         if (res.first) return res.second;
922         
923         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
924
925     }
926     catch(bad_alloc) {
927         return WriteClientError(lpECB,"Out of Memory");
928     }
929     catch(long e) {
930         if (e==ERROR_NO_DATA)
931             return WriteClientError(lpECB,"A required variable or header was empty.");
932         else
933             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
934     }
935     catch (exception& e) {
936         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
937         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
938     }
939     catch(...) {
940         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
941         if (g_catchAll)
942             return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
943         throw;
944     }
945
946     // If we get here we've got an error.
947     return HSE_STATUS_ERROR;
948 }