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