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