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