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