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