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