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