Move Shib constants to new lib, fixed symbol conflicts.
[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 #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 #include <shibsp/SPConfig.h>
33
34 #include <ctime>
35 #include <fstream>
36 #include <sstream>
37 #include <stdexcept>
38 #include <process.h>
39
40 #include <httpfilt.h>
41 #include <httpext.h>
42
43 using namespace shibsp;
44 using namespace shibtarget;
45 using namespace xmltooling;
46 using namespace std;
47
48 // globals
49 namespace {
50     static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
51     static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
52     static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
53     static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
54     static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
55     static const XMLCh Implementation[] =
56     { 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 };
57     static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
58     static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
59     static const XMLCh normalizeRequest[] =
60     { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
61       chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
62     };
63     static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
64
65     struct site_t {
66         site_t(const DOMElement* e)
67         {
68             auto_ptr_char n(e->getAttributeNS(NULL,name));
69             auto_ptr_char s(e->getAttributeNS(NULL,scheme));
70             auto_ptr_char p(e->getAttributeNS(NULL,port));
71             auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
72             if (n.get()) m_name=n.get();
73             if (s.get()) m_scheme=s.get();
74             if (p.get()) m_port=p.get();
75             if (p2.get()) m_sslport=p2.get();
76             DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
77             for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
78                 if (nlist->item(i)->hasChildNodes()) {
79                     auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
80                     m_aliases.insert(alias.get());
81                 }
82             }
83         }
84         string m_scheme,m_port,m_sslport,m_name;
85         set<string> m_aliases;
86     };
87     
88     HINSTANCE g_hinstDLL;
89     ShibTargetConfig* g_Config = NULL;
90     map<string,site_t> g_Sites;
91     bool g_bNormalizeRequest = true;
92 }
93
94 BOOL LogEvent(
95     LPCSTR  lpUNCServerName,
96     WORD  wType,
97     DWORD  dwEventID,
98     PSID  lpUserSid,
99     LPCSTR  message)
100 {
101     LPCSTR  messages[] = {message, NULL};
102     
103     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
104     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
105     return (DeregisterEventSource(hElog) && res);
106 }
107
108 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
109 {
110     if (fdwReason==DLL_PROCESS_ATTACH)
111         g_hinstDLL=hinstDLL;
112     return TRUE;
113 }
114
115 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
116 {
117     if (!pVer)
118         return FALSE;
119         
120     if (!g_Config)
121     {
122         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
123                 "Extension mode startup not possible, is the DLL loaded as a filter?");
124         return FALSE;
125     }
126
127     pVer->dwExtensionVersion=HSE_VERSION;
128     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
129     return TRUE;
130 }
131
132 extern "C" BOOL WINAPI TerminateExtension(DWORD)
133 {
134     return TRUE;    // cleanup should happen when filter unloads
135 }
136
137 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
138 {
139     if (!pVer)
140         return FALSE;
141     else if (g_Config) {
142         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
143                 "Reentrant filter initialization, ignoring...");
144         return TRUE;
145     }
146
147 #ifndef _DEBUG
148     try
149     {
150 #endif
151         LPCSTR schemadir=getenv("SHIBSCHEMAS");
152         if (!schemadir)
153             schemadir=SHIB_SCHEMAS;
154         LPCSTR config=getenv("SHIBCONFIG");
155         if (!config)
156             config=SHIB_CONFIG;
157         g_Config=&ShibTargetConfig::getConfig();
158         SPConfig::getConfig().setFeatures(
159             SPConfig::Listener |
160             SPConfig::Caching |
161             SPConfig::Metadata |
162             SPConfig::AAP |
163             SPConfig::RequestMapper |
164             SPConfig::InProcess |
165             SPConfig::Logging
166             );
167         if (!g_Config->init(schemadir)) {
168             g_Config=NULL;
169             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
170                     "Filter startup failed during library initialization, check native log for help.");
171             return FALSE;
172         }
173         else if (!g_Config->load(config)) {
174             g_Config=NULL;
175             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
176                     "Filter startup failed to load configuration, check native log for help.");
177             return FALSE;
178         }
179         
180         // Access the implementation-specifics for site mappings.
181         IConfig* conf=g_Config->getINI();
182         saml::Locker locker(conf);
183         const PropertySet* props=conf->getPropertySet("Local");
184         if (props) {
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     if (level == LogLevelError)
395         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
396   }
397   virtual string getCookies() const {
398     dynabuf buf(128);
399     GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
400     return buf.empty() ? "" : buf;
401   }
402   
403   virtual void clearHeader(const string &name) {
404     string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
405     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
406   }
407   virtual void setHeader(const string &name, const string &value) {
408     string hdr = name + ":";
409     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
410                     const_cast<char*>(value.c_str()));
411   }
412   virtual string getHeader(const string &name) {
413     string hdr = name + ":";
414     dynabuf buf(1024);
415     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
416     return string(buf);
417   }
418   virtual void setRemoteUser(const string &user) {
419     setHeader(string("remote-user"), user);
420   }
421   virtual string getRemoteUser(void) {
422     return getHeader(string("remote-user"));
423   }
424   virtual void* sendPage(
425     const string& msg,
426     int code=200,
427     const string& content_type="text/html",
428     const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
429     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
430     while (headers.hasNext()) {
431         const header_t& h=headers.next();
432         hdr += h.first + ": " + h.second + "\r\n";
433     }
434     hdr += "\r\n";
435     const char* codestr="200 OK";
436     switch (code) {
437         case 403:   codestr="403 Forbidden"; break;
438         case 404:   codestr="404 Not Found"; break;
439         case 500:   codestr="500 Server Error"; break;
440     }
441     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
442     DWORD resplen = msg.size();
443     m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
444     return (void*)SF_STATUS_REQ_FINISHED;
445   }
446   virtual void* sendRedirect(const string& url) {
447     // XXX: Don't support the httpRedirect option, yet.
448     string hdrs=m_cookie + string("Location: ") + url + "\r\n"
449       "Content-Type: text/html\r\n"
450       "Content-Length: 40\r\n"
451       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
452       "Cache-Control: private,no-store,no-cache\r\n\r\n";
453     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
454                                  "302 Please Wait", (DWORD)hdrs.c_str(), 0);
455     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
456     DWORD resplen=40;
457     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
458     return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
459   }
460   // XXX: We might not ever hit the 'decline' status in this filter.
461   //virtual void* returnDecline(void) { }
462   virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
463
464   // The filter never processes the POST, so stub these methods.
465   virtual void setCookie(const string &name, const string &value) {
466     // Set the cookie for later.  Use it during the redirect.
467     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
468   }
469   virtual const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
470   virtual const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
471 };
472
473 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
474 {
475     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
476     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
477     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
478     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
479                             "<H1>Shibboleth Filter Error</H1>";
480     DWORD resplen=strlen(xmsg);
481     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
482     resplen=strlen(msg);
483     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
484     static const char* xmsg2="</BODY></HTML>";
485     resplen=strlen(xmsg2);
486     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
487     return SF_STATUS_REQ_FINISHED;
488 }
489
490 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
491 {
492     // Is this a log notification?
493     if (notificationType==SF_NOTIFY_LOG)
494     {
495         if (pfc->pFilterContext)
496             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
497         return SF_STATUS_REQ_NEXT_NOTIFICATION;
498     }
499
500     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
501     try
502     {
503         // Determine web site number. This can't really fail, I don't think.
504         dynabuf buf(128);
505         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
506
507         // Match site instance to host name, skip if no match.
508         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
509         if (map_i==g_Sites.end())
510             return SF_STATUS_REQ_NEXT_NOTIFICATION;
511             
512         ostringstream threadid;
513         threadid << "[" << getpid() << "] isapi_shib" << '\0';
514         saml::NDC ndc(threadid.str().c_str());
515
516         ShibTargetIsapiF stf(pfc, pn, map_i->second);
517
518         // "false" because we don't override the Shib settings
519         pair<bool,void*> res = stf.doCheckAuthN();
520         if (res.first) return (DWORD)res.second;
521
522         // "false" because we don't override the Shib settings
523         res = stf.doExportAssertions();
524         if (res.first) return (DWORD)res.second;
525
526         res = stf.doCheckAuthZ();
527         if (res.first) return (DWORD)res.second;
528
529         return SF_STATUS_REQ_NEXT_NOTIFICATION;
530     }
531     catch(bad_alloc) {
532         return WriteClientError(pfc,"Out of Memory");
533     }
534     catch(long e) {
535         if (e==ERROR_NO_DATA)
536             return WriteClientError(pfc,"A required variable or header was empty.");
537         else
538             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
539     }
540     catch (saml::SAMLException& e) {
541         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
542         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
543     }
544 #ifndef _DEBUG
545     catch(...) {
546         return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
547     }
548 #endif
549
550     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
551 }
552         
553
554 /****************************************************************************/
555 // ISAPI Extension
556
557 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
558 {
559     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
560     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
561     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
562     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
563     DWORD resplen=strlen(xmsg);
564     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
565     resplen=strlen(msg);
566     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
567     static const char* xmsg2="</BODY></HTML>";
568     resplen=strlen(xmsg2);
569     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
570     return HSE_STATUS_SUCCESS;
571 }
572
573
574 class ShibTargetIsapiE : public ShibTarget
575 {
576   LPEXTENSION_CONTROL_BLOCK m_lpECB;
577   string m_cookie;
578   mutable string m_body;
579   mutable bool m_gotBody;
580   
581 public:
582   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_gotBody(false) {
583     dynabuf ssl(5);
584     GetServerVariable(lpECB,"HTTPS",ssl,5);
585     bool SSL=(ssl=="on" || ssl=="ON");
586
587     // URL path always come from IIS.
588     dynabuf url(256);
589     GetServerVariable(lpECB,"URL",url,255);
590
591     // Port may come from IIS or from site def.
592     dynabuf port(11);
593     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
594         GetServerVariable(lpECB,"SERVER_PORT",port,10);
595     else if (SSL) {
596         strncpy(port,site.m_sslport.c_str(),10);
597         static_cast<char*>(port)[10]=0;
598     }
599     else {
600         strncpy(port,site.m_port.c_str(),10);
601         static_cast<char*>(port)[10]=0;
602     }
603
604     // Scheme may come from site def or be derived from IIS.
605     const char* scheme=site.m_scheme.c_str();
606     if (!scheme || !*scheme || !g_bNormalizeRequest) {
607         scheme = SSL ? "https" : "http";
608     }
609
610     // Get the other server variables.
611     dynabuf remote_addr(16),hostname(32);
612     GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
613     GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
614
615     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
616     const char* host=hostname;
617     if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
618         host=site.m_name.c_str();
619
620     /*
621      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
622      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
623      * which is the default. No perfect way to tell, but we can take a good guess by checking
624      * whether the URL is a substring of the PATH_INFO:
625      * 
626      * e.g. for /Shibboleth.sso/SAML/POST
627      * 
628      *  Bad mode (default):
629      *      URL:        /Shibboleth.sso
630      *      PathInfo:   /Shibboleth.sso/SAML/POST
631      * 
632      *  Good mode:
633      *      URL:        /Shibboleth.sso
634      *      PathInfo:   /SAML/POST
635      */
636     
637     string fullurl;
638     
639     // Clearly we're only in bad mode if path info exists at all.
640     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
641         if (strstr(lpECB->lpszPathInfo,url))
642             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
643             fullurl=lpECB->lpszPathInfo;
644         else {
645             fullurl+=url;
646             fullurl+=lpECB->lpszPathInfo;
647         }
648     }
649     
650     // For consistency with Apache, let's add the query string.
651     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
652         fullurl+='?';
653         fullurl+=lpECB->lpszQueryString;
654     }
655     init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
656
657     m_lpECB = lpECB;
658   }
659   ~ShibTargetIsapiE() { }
660
661   virtual void log(ShibLogLevel level, const string &msg) {
662       ShibTarget::log(level,msg);
663       if (level == LogLevelError)
664           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
665   }
666   virtual string getCookies() const {
667     dynabuf buf(128);
668     GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
669     return buf.empty() ? "" : buf;
670   }
671   virtual void setCookie(const string &name, const string &value) {
672     // Set the cookie for later.  Use it during the redirect.
673     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
674   }
675   virtual const char* getQueryString() const {
676     return m_lpECB->lpszQueryString;
677   }
678   virtual const char* getRequestBody() const {
679     if (m_gotBody)
680         return m_body.c_str();
681     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
682         throw saml::SAMLException("Size of POST request body exceeded limit.");
683     else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
684       m_gotBody=true;
685       char buf[8192];
686       DWORD datalen=m_lpECB->cbTotalBytes;
687       while (datalen) {
688         DWORD buflen=8192;
689         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
690         if (!ret || !buflen)
691             throw saml::SAMLException("Error reading POST request body from browser.");
692         m_body.append(buf, buflen);
693         datalen-=buflen;
694       }
695     }
696     else {
697         m_gotBody=true;
698         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
699     }
700     return m_body.c_str();
701   }
702   virtual void* sendPage(
703     const string &msg,
704     int code=200,
705     const string& content_type="text/html",
706     const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
707     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
708     for (unsigned int k = 0; k < headers.size(); k++) {
709       hdr += headers[k].first + ": " + headers[k].second + "\r\n";
710     }
711     hdr += "\r\n";
712     const char* codestr="200 OK";
713     switch (code) {
714         case 403:   codestr="403 Forbidden"; break;
715         case 404:   codestr="404 Not Found"; break;
716         case 500:   codestr="500 Server Error"; break;
717     }
718     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
719     DWORD resplen = msg.size();
720     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
721     return (void*)HSE_STATUS_SUCCESS;
722   }
723   virtual void* sendRedirect(const string& url) {
724     // XXX: Don't support the httpRedirect option, yet.
725     string hdrs = m_cookie + "Location: " + url + "\r\n"
726       "Content-Type: text/html\r\n"
727       "Content-Length: 40\r\n"
728       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
729       "Cache-Control: private,no-store,no-cache\r\n\r\n";
730     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
731                                  "302 Moved", 0, (LPDWORD)hdrs.c_str());
732     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
733     DWORD resplen=40;
734     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
735     return (void*)HSE_STATUS_SUCCESS;
736   }
737   // Decline happens in the POST processor if this isn't the shire url
738   // Note that it can also happen with HTAccess, but we don't support that, yet.
739   virtual void* returnDecline(void) {
740     return (void*)
741       WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
742                        "Make sure the mapped file extension doesn't match actual content.");
743   }
744   virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
745
746   // Not used in the extension.
747   virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
748   virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
749   virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
750   virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
751   virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
752 };
753
754 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
755 {
756     string targeturl;
757     const IApplication* application=NULL;
758     try {
759         ostringstream threadid;
760         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
761         saml::NDC ndc(threadid.str().c_str());
762
763         // Determine web site number. This can't really fail, I don't think.
764         dynabuf buf(128);
765         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
766
767         // Match site instance to host name, skip if no match.
768         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
769         if (map_i==g_Sites.end())
770             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
771
772         ShibTargetIsapiE ste(lpECB, map_i->second);
773         pair<bool,void*> res = ste.doHandler();
774         if (res.first) return (DWORD)res.second;
775         
776         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
777
778     }
779     catch(bad_alloc) {
780         return WriteClientError(lpECB,"Out of Memory");
781     }
782     catch(long e) {
783         if (e==ERROR_NO_DATA)
784             return WriteClientError(lpECB,"A required variable or header was empty.");
785         else
786             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
787     }
788     catch (saml::SAMLException& e) {
789         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
790         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
791     }
792 #ifndef _DEBUG
793     catch(...) {
794         return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
795     }
796 #endif
797
798     // If we get here we've got an error.
799     return HSE_STATUS_ERROR;
800 }