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