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