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