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