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