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