0074ea6596aabfae1422a4a822bbe9d8a7d8c2d0
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  *  Copyright 2001-2005 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 /* isapi_shib.cpp - Shibboleth ISAPI filter
18
19    Scott Cantor
20    8/23/02
21 */
22
23 #include "config_win32.h"
24
25 #define _CRT_NONSTDC_NO_DEPRECATE 1
26 #define _CRT_SECURE_NO_DEPRECATE 1
27
28 // SAML Runtime
29 #include <saml/saml.h>
30 #include <shib/shib.h>
31 #include <shib-target/shib-target.h>
32
33 #include <ctime>
34 #include <fstream>
35 #include <sstream>
36 #include <stdexcept>
37 #include <process.h>
38
39 #include <httpfilt.h>
40 #include <httpext.h>
41
42 using namespace shibtarget;
43 using namespace saml;
44 using namespace xmltooling;
45 using namespace std;
46
47 // globals
48 namespace {
49     static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
50     static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
51     static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
52     static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
53     static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
54     static const XMLCh Implementation[] =
55     { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
56     static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
57     static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
58     static const XMLCh normalizeRequest[] =
59     { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
60       chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
61     };
62     static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
63
64     struct site_t {
65         site_t(const DOMElement* e)
66         {
67             auto_ptr_char n(e->getAttributeNS(NULL,name));
68             auto_ptr_char s(e->getAttributeNS(NULL,scheme));
69             auto_ptr_char p(e->getAttributeNS(NULL,port));
70             auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
71             if (n.get()) m_name=n.get();
72             if (s.get()) m_scheme=s.get();
73             if (p.get()) m_port=p.get();
74             if (p2.get()) m_sslport=p2.get();
75             DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
76             for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
77                 if (nlist->item(i)->hasChildNodes()) {
78                     auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
79                     m_aliases.insert(alias.get());
80                 }
81             }
82         }
83         string m_scheme,m_port,m_sslport,m_name;
84         set<string> m_aliases;
85     };
86     
87     HINSTANCE g_hinstDLL;
88     ShibTargetConfig* g_Config = NULL;
89     map<string,site_t> g_Sites;
90     bool g_bNormalizeRequest = true;
91 }
92
93 BOOL LogEvent(
94     LPCSTR  lpUNCServerName,
95     WORD  wType,
96     DWORD  dwEventID,
97     PSID  lpUserSid,
98     LPCSTR  message)
99 {
100     LPCSTR  messages[] = {message, NULL};
101     
102     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
103     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
104     return (DeregisterEventSource(hElog) && res);
105 }
106
107 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
108 {
109     if (fdwReason==DLL_PROCESS_ATTACH)
110         g_hinstDLL=hinstDLL;
111     return TRUE;
112 }
113
114 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
115 {
116     if (!pVer)
117         return FALSE;
118         
119     if (!g_Config)
120     {
121         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
122                 "Extension mode startup not possible, is the DLL loaded as a filter?");
123         return FALSE;
124     }
125
126     pVer->dwExtensionVersion=HSE_VERSION;
127     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
128     return TRUE;
129 }
130
131 extern "C" BOOL WINAPI TerminateExtension(DWORD)
132 {
133     return TRUE;    // cleanup should happen when filter unloads
134 }
135
136 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
137 {
138     if (!pVer)
139         return FALSE;
140     else if (g_Config) {
141         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
142                 "Reentrant filter initialization, ignoring...");
143         return TRUE;
144     }
145
146 #ifndef _DEBUG
147     try
148     {
149 #endif
150         LPCSTR schemadir=getenv("SHIBSCHEMAS");
151         if (!schemadir)
152             schemadir=SHIB_SCHEMAS;
153         LPCSTR config=getenv("SHIBCONFIG");
154         if (!config)
155             config=SHIB_CONFIG;
156         g_Config=&ShibTargetConfig::getConfig();
157         g_Config->setFeatures(
158             ShibTargetConfig::Listener |
159             ShibTargetConfig::Caching |
160             ShibTargetConfig::Metadata |
161             ShibTargetConfig::AAP |
162             ShibTargetConfig::RequestMapper |
163             ShibTargetConfig::InProcess |
164             ShibTargetConfig::Logging
165             );
166         if (!g_Config->init(schemadir)) {
167             g_Config=NULL;
168             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
169                     "Filter startup failed during library initialization, check native log for help.");
170             return FALSE;
171         }
172         else if (!g_Config->load(config)) {
173             g_Config=NULL;
174             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
175                     "Filter startup failed to load configuration, check native log for help.");
176             return FALSE;
177         }
178         
179         // Access the implementation-specifics for site mappings.
180         IConfig* conf=g_Config->getINI();
181         Locker locker(conf);
182         const IPropertySet* props=conf->getPropertySet("Local");
183         if (props) {
184             const DOMElement* impl=saml::XML::getFirstChildElement(
185                 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
186                 );
187             if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
188                 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
189                 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
190                 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
191                 while (impl) {
192                     auto_ptr_char id(impl->getAttributeNS(NULL,id));
193                     if (id.get())
194                         g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
195                     impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
196                 }
197             }
198         }
199 #ifndef _DEBUG
200     }
201     catch (...)
202     {
203         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
204         return FALSE;
205     }
206 #endif
207
208     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
209     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
210     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
211                    SF_NOTIFY_SECURE_PORT |
212                    SF_NOTIFY_NONSECURE_PORT |
213                    SF_NOTIFY_PREPROC_HEADERS |
214                    SF_NOTIFY_LOG);
215     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
216     return TRUE;
217 }
218
219 extern "C" BOOL WINAPI TerminateFilter(DWORD)
220 {
221     if (g_Config)
222         g_Config->shutdown();
223     g_Config = NULL;
224     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
225     return TRUE;
226 }
227
228 /* Next up, some suck-free versions of various APIs.
229
230    You DON'T require people to guess the buffer size and THEN tell them the right size.
231    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
232    constant strings aren't typed as such, making it just that much harder. These versions
233    are now updated to use a special growable buffer object, modeled after the standard
234    string class. The standard string won't work because they left out the option to
235    pre-allocate a non-constant buffer.
236 */
237
238 class dynabuf
239 {
240 public:
241     dynabuf() { bufptr=NULL; buflen=0; }
242     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
243     ~dynabuf() { delete[] bufptr; }
244     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
245     size_t size() const { return buflen; }
246     bool empty() const { return length()==0; }
247     void reserve(size_t s, bool keep=false);
248     void erase() { if (bufptr) memset(bufptr,0,buflen); }
249     operator char*() { return bufptr; }
250     bool operator ==(const char* s) const;
251     bool operator !=(const char* s) const { return !(*this==s); }
252 private:
253     char* bufptr;
254     size_t buflen;
255 };
256
257 void dynabuf::reserve(size_t s, bool keep)
258 {
259     if (s<=buflen)
260         return;
261     char* p=new char[s];
262     if (keep)
263         while (buflen--)
264             p[buflen]=bufptr[buflen];
265     buflen=s;
266     delete[] bufptr;
267     bufptr=p;
268 }
269
270 bool dynabuf::operator==(const char* s) const
271 {
272     if (buflen==NULL || s==NULL)
273         return (buflen==NULL && s==NULL);
274     else
275         return strcmp(bufptr,s)==0;
276 }
277
278 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
279     throw (bad_alloc, DWORD)
280 {
281     s.reserve(size);
282     s.erase();
283     size=s.size();
284
285     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
286     {
287         // Grumble. Check the error.
288         DWORD e=GetLastError();
289         if (e==ERROR_INSUFFICIENT_BUFFER)
290             s.reserve(size);
291         else
292             break;
293     }
294     if (bRequired && s.empty())
295         throw ERROR_NO_DATA;
296 }
297
298 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
299     throw (bad_alloc, DWORD)
300 {
301     s.reserve(size);
302     s.erase();
303     size=s.size();
304
305     while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
306     {
307         // Grumble. Check the error.
308         DWORD e=GetLastError();
309         if (e==ERROR_INSUFFICIENT_BUFFER)
310             s.reserve(size);
311         else
312             break;
313     }
314     if (bRequired && s.empty())
315         throw ERROR_NO_DATA;
316 }
317
318 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
319                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
320     throw (bad_alloc, DWORD)
321 {
322     s.reserve(size);
323     s.erase();
324     size=s.size();
325
326     while (!pn->GetHeader(pfc,lpszName,s,&size))
327     {
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 ShibTarget
343 {
344   PHTTP_FILTER_CONTEXT m_pfc;
345   PHTTP_FILTER_PREPROC_HEADERS m_pn;
346   string m_cookie;
347 public:
348   ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
349
350     // URL path always come from IIS.
351     dynabuf url(256);
352     GetHeader(pn,pfc,"url",url,256,false);
353
354     // Port may come from IIS or from site def.
355     dynabuf port(11);
356     if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
357         GetServerVariable(pfc,"SERVER_PORT",port,10);
358     else if (pfc->fIsSecurePort) {
359         strncpy(port,site.m_sslport.c_str(),10);
360         static_cast<char*>(port)[10]=0;
361     }
362     else {
363         strncpy(port,site.m_port.c_str(),10);
364         static_cast<char*>(port)[10]=0;
365     }
366     
367     // Scheme may come from site def or be derived from IIS.
368     const char* scheme=site.m_scheme.c_str();
369     if (!scheme || !*scheme || !g_bNormalizeRequest)
370         scheme=pfc->fIsSecurePort ? "https" : "http";
371
372     // Get the rest of the server variables.
373     dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
374     GetServerVariable(pfc,"SERVER_NAME",hostname,32);
375     GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
376     GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
377     GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
378
379     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
380     const char* host=hostname;
381     if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
382         host=site.m_name.c_str();
383
384     init(scheme, host, atoi(port), url, content_type, remote_addr, method); 
385
386     m_pfc = pfc;
387     m_pn = pn;
388   }
389   ~ShibTargetIsapiF() { }
390
391   virtual void log(ShibLogLevel level, const string &msg) {
392     ShibTarget::log(level,msg);
393     if (level == LogLevelError)
394         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
395   }
396   virtual string getCookies() const {
397     dynabuf buf(128);
398     GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
399     return buf.empty() ? "" : buf;
400   }
401   
402   virtual void clearHeader(const string &name) {
403     string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
404     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
405   }
406   virtual void setHeader(const string &name, const string &value) {
407     string hdr = name + ":";
408     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
409                     const_cast<char*>(value.c_str()));
410   }
411   virtual string getHeader(const string &name) {
412     string hdr = name + ":";
413     dynabuf buf(1024);
414     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
415     return string(buf);
416   }
417   virtual void setRemoteUser(const string &user) {
418     setHeader(string("remote-user"), user);
419   }
420   virtual string getRemoteUser(void) {
421     return getHeader(string("remote-user"));
422   }
423   virtual void* sendPage(
424     const string& msg,
425     int code=200,
426     const string& content_type="text/html",
427     const Iterator<header_t>& headers=EMPTY(header_t)) {
428     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
429     while (headers.hasNext()) {
430         const header_t& h=headers.next();
431         hdr += h.first + ": " + h.second + "\r\n";
432     }
433     hdr += "\r\n";
434     const char* codestr="200 OK";
435     switch (code) {
436         case 403:   codestr="403 Forbidden"; break;
437         case 404:   codestr="404 Not Found"; break;
438         case 500:   codestr="500 Server Error"; break;
439     }
440     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
441     DWORD resplen = msg.size();
442     m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
443     return (void*)SF_STATUS_REQ_FINISHED;
444   }
445   virtual void* sendRedirect(const string& url) {
446     // XXX: Don't support the httpRedirect option, yet.
447     string hdrs=m_cookie + string("Location: ") + url + "\r\n"
448       "Content-Type: text/html\r\n"
449       "Content-Length: 40\r\n"
450       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
451       "Cache-Control: private,no-store,no-cache\r\n\r\n";
452     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
453                                  "302 Please Wait", (DWORD)hdrs.c_str(), 0);
454     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
455     DWORD resplen=40;
456     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
457     return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
458   }
459   // XXX: We might not ever hit the 'decline' status in this filter.
460   //virtual void* returnDecline(void) { }
461   virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
462
463   // The filter never processes the POST, so stub these methods.
464   virtual void setCookie(const string &name, const string &value) {
465     // Set the cookie for later.  Use it during the redirect.
466     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
467   }
468   virtual const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
469   virtual const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
470 };
471
472 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
473 {
474     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
475     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
476     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
477     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
478                             "<H1>Shibboleth Filter Error</H1>";
479     DWORD resplen=strlen(xmsg);
480     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
481     resplen=strlen(msg);
482     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
483     static const char* xmsg2="</BODY></HTML>";
484     resplen=strlen(xmsg2);
485     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
486     return SF_STATUS_REQ_FINISHED;
487 }
488
489 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
490 {
491     // Is this a log notification?
492     if (notificationType==SF_NOTIFY_LOG)
493     {
494         if (pfc->pFilterContext)
495             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
496         return SF_STATUS_REQ_NEXT_NOTIFICATION;
497     }
498
499     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
500     try
501     {
502         // Determine web site number. This can't really fail, I don't think.
503         dynabuf buf(128);
504         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
505
506         // Match site instance to host name, skip if no match.
507         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
508         if (map_i==g_Sites.end())
509             return SF_STATUS_REQ_NEXT_NOTIFICATION;
510             
511         ostringstream threadid;
512         threadid << "[" << getpid() << "] isapi_shib" << '\0';
513         saml::NDC ndc(threadid.str().c_str());
514
515         ShibTargetIsapiF stf(pfc, pn, map_i->second);
516
517         // "false" because we don't override the Shib settings
518         pair<bool,void*> res = stf.doCheckAuthN();
519         if (res.first) return (DWORD)res.second;
520
521         // "false" because we don't override the Shib settings
522         res = stf.doExportAssertions();
523         if (res.first) return (DWORD)res.second;
524
525         res = stf.doCheckAuthZ();
526         if (res.first) return (DWORD)res.second;
527
528         return SF_STATUS_REQ_NEXT_NOTIFICATION;
529     }
530     catch(bad_alloc) {
531         return WriteClientError(pfc,"Out of Memory");
532     }
533     catch(long e) {
534         if (e==ERROR_NO_DATA)
535             return WriteClientError(pfc,"A required variable or header was empty.");
536         else
537             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
538     }
539     catch (SAMLException& e) {
540         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
541         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
542     }
543 #ifndef _DEBUG
544     catch(...) {
545         return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
546     }
547 #endif
548
549     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
550 }
551         
552
553 /****************************************************************************/
554 // ISAPI Extension
555
556 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
557 {
558     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
559     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
560     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
561     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
562     DWORD resplen=strlen(xmsg);
563     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
564     resplen=strlen(msg);
565     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
566     static const char* xmsg2="</BODY></HTML>";
567     resplen=strlen(xmsg2);
568     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
569     return HSE_STATUS_SUCCESS;
570 }
571
572
573 class ShibTargetIsapiE : public ShibTarget
574 {
575   LPEXTENSION_CONTROL_BLOCK m_lpECB;
576   string m_cookie;
577   mutable string m_body;
578   mutable bool m_gotBody;
579   
580 public:
581   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_gotBody(false) {
582     dynabuf ssl(5);
583     GetServerVariable(lpECB,"HTTPS",ssl,5);
584     bool SSL=(ssl=="on" || ssl=="ON");
585
586     // URL path always come from IIS.
587     dynabuf url(256);
588     GetServerVariable(lpECB,"URL",url,255);
589
590     // Port may come from IIS or from site def.
591     dynabuf port(11);
592     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
593         GetServerVariable(lpECB,"SERVER_PORT",port,10);
594     else if (SSL) {
595         strncpy(port,site.m_sslport.c_str(),10);
596         static_cast<char*>(port)[10]=0;
597     }
598     else {
599         strncpy(port,site.m_port.c_str(),10);
600         static_cast<char*>(port)[10]=0;
601     }
602
603     // Scheme may come from site def or be derived from IIS.
604     const char* scheme=site.m_scheme.c_str();
605     if (!scheme || !*scheme || !g_bNormalizeRequest) {
606         scheme = SSL ? "https" : "http";
607     }
608
609     // Get the other server variables.
610     dynabuf remote_addr(16),hostname(32);
611     GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
612     GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
613
614     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
615     const char* host=hostname;
616     if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
617         host=site.m_name.c_str();
618
619     /*
620      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
621      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
622      * which is the default. No perfect way to tell, but we can take a good guess by checking
623      * whether the URL is a substring of the PATH_INFO:
624      * 
625      * e.g. for /Shibboleth.sso/SAML/POST
626      * 
627      *  Bad mode (default):
628      *      URL:        /Shibboleth.sso
629      *      PathInfo:   /Shibboleth.sso/SAML/POST
630      * 
631      *  Good mode:
632      *      URL:        /Shibboleth.sso
633      *      PathInfo:   /SAML/POST
634      */
635     
636     string fullurl;
637     
638     // Clearly we're only in bad mode if path info exists at all.
639     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
640         if (strstr(lpECB->lpszPathInfo,url))
641             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
642             fullurl=lpECB->lpszPathInfo;
643         else {
644             fullurl+=url;
645             fullurl+=lpECB->lpszPathInfo;
646         }
647     }
648     
649     // For consistency with Apache, let's add the query string.
650     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
651         fullurl+='?';
652         fullurl+=lpECB->lpszQueryString;
653     }
654     init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
655
656     m_lpECB = lpECB;
657   }
658   ~ShibTargetIsapiE() { }
659
660   virtual void log(ShibLogLevel level, const string &msg) {
661       ShibTarget::log(level,msg);
662       if (level == LogLevelError)
663           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
664   }
665   virtual string getCookies() const {
666     dynabuf buf(128);
667     GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
668     return buf.empty() ? "" : buf;
669   }
670   virtual void setCookie(const string &name, const string &value) {
671     // Set the cookie for later.  Use it during the redirect.
672     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
673   }
674   virtual const char* getQueryString() const {
675     return m_lpECB->lpszQueryString;
676   }
677   virtual const char* getRequestBody() const {
678     if (m_gotBody)
679         return m_body.c_str();
680     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
681       throw SAMLException("Size of POST request body exceeded limit.");
682     else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
683       m_gotBody=true;
684       char buf[8192];
685       DWORD datalen=m_lpECB->cbTotalBytes;
686       while (datalen) {
687         DWORD buflen=8192;
688         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
689         if (!ret || !buflen)
690           throw SAMLException("Error reading POST request body from browser.");
691         m_body.append(buf, buflen);
692         datalen-=buflen;
693       }
694     }
695     else {
696         m_gotBody=true;
697         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
698     }
699     return m_body.c_str();
700   }
701   virtual void* sendPage(
702     const string &msg,
703     int code=200,
704     const string& content_type="text/html",
705     const Iterator<header_t>& headers=EMPTY(header_t)) {
706     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
707     for (unsigned int k = 0; k < headers.size(); k++) {
708       hdr += headers[k].first + ": " + headers[k].second + "\r\n";
709     }
710     hdr += "\r\n";
711     const char* codestr="200 OK";
712     switch (code) {
713         case 403:   codestr="403 Forbidden"; break;
714         case 404:   codestr="404 Not Found"; break;
715         case 500:   codestr="500 Server Error"; break;
716     }
717     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
718     DWORD resplen = msg.size();
719     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
720     return (void*)HSE_STATUS_SUCCESS;
721   }
722   virtual void* sendRedirect(const string& url) {
723     // XXX: Don't support the httpRedirect option, yet.
724     string hdrs = m_cookie + "Location: " + url + "\r\n"
725       "Content-Type: text/html\r\n"
726       "Content-Length: 40\r\n"
727       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
728       "Cache-Control: private,no-store,no-cache\r\n\r\n";
729     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
730                                  "302 Moved", 0, (LPDWORD)hdrs.c_str());
731     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
732     DWORD resplen=40;
733     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
734     return (void*)HSE_STATUS_SUCCESS;
735   }
736   // Decline happens in the POST processor if this isn't the shire url
737   // Note that it can also happen with HTAccess, but we don't support that, yet.
738   virtual void* returnDecline(void) {
739     return (void*)
740       WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
741                        "Make sure the mapped file extension doesn't match actual content.");
742   }
743   virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
744
745   // Not used in the extension.
746   virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
747   virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
748   virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
749   virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
750   virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
751 };
752
753 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
754 {
755     string targeturl;
756     const IApplication* application=NULL;
757     try {
758         ostringstream threadid;
759         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
760         saml::NDC ndc(threadid.str().c_str());
761
762         // Determine web site number. This can't really fail, I don't think.
763         dynabuf buf(128);
764         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
765
766         // Match site instance to host name, skip if no match.
767         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
768         if (map_i==g_Sites.end())
769             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
770
771         ShibTargetIsapiE ste(lpECB, map_i->second);
772         pair<bool,void*> res = ste.doHandler();
773         if (res.first) return (DWORD)res.second;
774         
775         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
776
777     }
778     catch(bad_alloc) {
779         return WriteClientError(lpECB,"Out of Memory");
780     }
781     catch(long e) {
782         if (e==ERROR_NO_DATA)
783             return WriteClientError(lpECB,"A required variable or header was empty.");
784         else
785             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
786     }
787     catch (SAMLException& e) {
788         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
789         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
790     }
791 #ifndef _DEBUG
792     catch(...) {
793         return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
794     }
795 #endif
796
797     // If we get here we've got an error.
798     return HSE_STATUS_ERROR;
799 }