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