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