New profile API, consolidated RPCs, simplified cache.
[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 <log4cpp/Category.hh>
65
66 #include <ctime>
67 #include <fstream>
68 #include <sstream>
69 #include <stdexcept>
70
71 #include <httpfilt.h>
72 #include <httpext.h>
73
74 using namespace std;
75 using namespace log4cpp;
76 using namespace saml;
77 using namespace shibboleth;
78 using namespace shibtarget;
79
80 // globals
81 namespace {
82     static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
83     static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
84     static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
85     static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
86     static const XMLCh Implementation[] =
87     { 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 };
88     static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, 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             if (n.get()) m_name=n.get();
102             if (s.get()) m_scheme=s.get();
103             if (p.get()) m_port=p.get();
104         }
105         string m_scheme,m_name,m_port;
106     };
107     
108     HINSTANCE g_hinstDLL;
109     ShibTargetConfig* g_Config = NULL;
110     map<string,site_t> g_Sites;
111     bool g_bNormalizeRequest = true;
112 }
113
114 BOOL LogEvent(
115     LPCSTR  lpUNCServerName,
116     WORD  wType,
117     DWORD  dwEventID,
118     PSID  lpUserSid,
119     LPCSTR  message)
120 {
121     LPCSTR  messages[] = {message, NULL};
122     
123     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
124     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
125     return (DeregisterEventSource(hElog) && res);
126 }
127
128 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
129 {
130     if (fdwReason==DLL_PROCESS_ATTACH)
131         g_hinstDLL=hinstDLL;
132     return TRUE;
133 }
134
135 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
136 {
137     if (!pVer)
138         return FALSE;
139         
140     if (!g_Config)
141     {
142         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
143                 "Extension mode startup not possible, is the DLL loaded as a filter?");
144         return FALSE;
145     }
146
147     pVer->dwExtensionVersion=HSE_VERSION;
148     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
149     return TRUE;
150 }
151
152 extern "C" BOOL WINAPI TerminateExtension(DWORD)
153 {
154     return TRUE;    // cleanup should happen when filter unloads
155 }
156
157 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
158 {
159     if (!pVer)
160         return FALSE;
161     else if (g_Config) {
162         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
163                 "Reentrant filter initialization, ignoring...");
164         return TRUE;
165     }
166
167     try
168     {
169         LPCSTR schemadir=getenv("SHIBSCHEMAS");
170         if (!schemadir)
171             schemadir=SHIB_SCHEMAS;
172         LPCSTR config=getenv("SHIBCONFIG");
173         if (!config)
174             config=SHIB_CONFIG;
175         g_Config=&ShibTargetConfig::getConfig();
176         g_Config->setFeatures(
177             ShibTargetConfig::Listener |
178             ShibTargetConfig::Metadata |
179             ShibTargetConfig::AAP |
180             ShibTargetConfig::RequestMapper |
181             ShibTargetConfig::SHIREExtensions |
182             ShibTargetConfig::Logging
183             );
184         if (!g_Config->init(schemadir,config)) {
185             g_Config=NULL;
186             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
187                     "Filter startup failed during initialization, check shire log for help.");
188             return FALSE;
189         }
190         
191         // Access the implementation-specifics for site mappings.
192         IConfig* conf=g_Config->getINI();
193         Locker locker(conf);
194         const IPropertySet* props=conf->getPropertySet("SHIRE");
195         if (props) {
196             const DOMElement* impl=saml::XML::getFirstChildElement(
197                 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
198                 );
199             if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
200                 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
201                 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
202                 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
203                 while (impl) {
204                     auto_ptr_char id(impl->getAttributeNS(NULL,id));
205                     if (id.get())
206                         g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
207                     impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
208                 }
209             }
210         }
211     }
212     catch (...)
213     {
214         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
215 #ifdef _DEBUG
216         throw;
217 #endif
218         return FALSE;
219     }
220
221     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
222     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
223     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
224                    SF_NOTIFY_SECURE_PORT |
225                    SF_NOTIFY_NONSECURE_PORT |
226                    SF_NOTIFY_PREPROC_HEADERS |
227                    SF_NOTIFY_LOG);
228     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
229     return TRUE;
230 }
231
232 extern "C" BOOL WINAPI TerminateFilter(DWORD)
233 {
234     if (g_Config)
235         g_Config->shutdown();
236     g_Config = NULL;
237     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
238     return TRUE;
239 }
240
241 /* Next up, some suck-free versions of various APIs.
242
243    You DON'T require people to guess the buffer size and THEN tell them the right size.
244    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
245    constant strings aren't typed as such, making it just that much harder. These versions
246    are now updated to use a special growable buffer object, modeled after the standard
247    string class. The standard string won't work because they left out the option to
248    pre-allocate a non-constant buffer.
249 */
250
251 class dynabuf
252 {
253 public:
254     dynabuf() { bufptr=NULL; buflen=0; }
255     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
256     ~dynabuf() { delete[] bufptr; }
257     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
258     size_t size() const { return buflen; }
259     bool empty() const { return length()==0; }
260     void reserve(size_t s, bool keep=false);
261     void erase() { if (bufptr) memset(bufptr,0,buflen); }
262     operator char*() { return bufptr; }
263     bool operator ==(const char* s) const;
264     bool operator !=(const char* s) const { return !(*this==s); }
265 private:
266     char* bufptr;
267     size_t buflen;
268 };
269
270 void dynabuf::reserve(size_t s, bool keep)
271 {
272     if (s<=buflen)
273         return;
274     char* p=new char[s];
275     if (keep)
276         while (buflen--)
277             p[buflen]=bufptr[buflen];
278     buflen=s;
279     delete[] bufptr;
280     bufptr=p;
281 }
282
283 bool dynabuf::operator==(const char* s) const
284 {
285     if (buflen==NULL || s==NULL)
286         return (buflen==NULL && s==NULL);
287     else
288         return strcmp(bufptr,s)==0;
289 }
290
291 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
292     throw (bad_alloc, DWORD)
293 {
294     s.reserve(size);
295     s.erase();
296     size=s.size();
297
298     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
299     {
300         // Grumble. Check the error.
301         DWORD e=GetLastError();
302         if (e==ERROR_INSUFFICIENT_BUFFER)
303             s.reserve(size);
304         else
305             break;
306     }
307     if (bRequired && s.empty())
308         throw ERROR_NO_DATA;
309 }
310
311 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
312     throw (bad_alloc, DWORD)
313 {
314     s.reserve(size);
315     s.erase();
316     size=s.size();
317
318     while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
319     {
320         // Grumble. Check the error.
321         DWORD e=GetLastError();
322         if (e==ERROR_INSUFFICIENT_BUFFER)
323             s.reserve(size);
324         else
325             break;
326     }
327     if (bRequired && s.empty())
328         throw ERROR_NO_DATA;
329 }
330
331 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
332                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
333     throw (bad_alloc, DWORD)
334 {
335     s.reserve(size);
336     s.erase();
337     size=s.size();
338
339     while (!pn->GetHeader(pfc,lpszName,s,&size))
340     {
341         // Grumble. Check the error.
342         DWORD e=GetLastError();
343         if (e==ERROR_INSUFFICIENT_BUFFER)
344             s.reserve(size);
345         else
346             break;
347     }
348     if (bRequired && s.empty())
349         throw ERROR_NO_DATA;
350 }
351
352 /****************************************************************************/
353 // ISAPI Filter
354
355 class ShibTargetIsapiF : public ShibTarget
356 {
357 public:
358   ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn,
359                    const site_t& site) {
360
361     // URL path always come from IIS.
362     dynabuf url(256);
363     GetHeader(pn,pfc,"url",url,256,false);
364
365     // Port may come from IIS or from site def.
366     dynabuf port(11);
367     if (site.m_port.empty() || !g_bNormalizeRequest)
368         GetServerVariable(pfc,"SERVER_PORT",port,10);
369     else {
370         strncpy(port,site.m_port.c_str(),10);
371         static_cast<char*>(port)[10]=0;
372     }
373     
374     // Scheme may come from site def or be derived from IIS.
375     const char* scheme=site.m_scheme.c_str();
376     if (!scheme || !*scheme || !g_bNormalizeRequest)
377         scheme=pfc->fIsSecurePort ? "https" : "http";
378
379     // Get the remote address
380     dynabuf remote_addr(16);
381     GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
382
383     // XXX: How do I get the content type and HTTP Method from this context?
384
385     init(g_Config, string(scheme), site.m_name, atoi(port),
386          string(url), string(""), // XXX: content type
387          string(remote_addr), string("") // XXX: http method
388          ); 
389
390     m_pfc = pfc;
391     m_pn = pn;
392   }
393   ~ShibTargetIsapiF() { }
394
395   virtual void log(ShibLogLevel level, const string &msg) {
396       LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
397                       (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
398                       (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
399              2100, NULL, msg.c_str());
400   }
401   virtual string getCookies(void) {
402     dynabuf buf(128);
403     GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
404     return buf.empty() ? "" : buf;
405   }
406   
407   virtual void clearHeader(const string &name) {
408     string hdr = name + ":";
409     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
410   }
411   virtual void setHeader(const string &name, const string &value) {
412     string hdr = name + ":";
413     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
414                     const_cast<char*>(value.c_str()));
415   }
416   virtual string getHeader(const string &name) {
417     string hdr = name + ":";
418     dynabuf buf(1024);
419     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
420     return string(buf);
421   }
422   virtual void setRemoteUser(const string &user) {
423     setHeader(string("remote-user"), user);
424   }
425   virtual string getRemoteUser(void) {
426     return getHeader(string("remote-user"));
427   }
428   virtual void* sendPage(const string &msg, const string content_type,
429       const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
430     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
431     while (headers.hasNext()) {
432         const header_t& h=headers.next();
433         hdr += h.first + ": " + h.second + "\r\n";
434     }
435     hdr += "\r\n";
436     // XXX Need to handle "code"
437     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK", (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=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) { throw runtime_error("setCookie not implemented"); }
462   virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
463   virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
464   
465   PHTTP_FILTER_CONTEXT m_pfc;
466   PHTTP_FILTER_PREPROC_HEADERS m_pn;
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(DWORD 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 #if 0
547 IRequestMapper::Settings map_request(
548     PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
549     )
550 {
551     // URL path always come from IIS.
552     dynabuf url(256);
553     GetHeader(pn,pfc,"url",url,256,false);
554
555     // Port may come from IIS or from site def.
556     dynabuf port(11);
557     if (site.m_port.empty() || !g_bNormalizeRequest)
558         GetServerVariable(pfc,"SERVER_PORT",port,10);
559     else {
560         strncpy(port,site.m_port.c_str(),10);
561         static_cast<char*>(port)[10]=0;
562     }
563     
564     // Scheme may come from site def or be derived from IIS.
565     const char* scheme=site.m_scheme.c_str();
566     if (!scheme || !*scheme || !g_bNormalizeRequest)
567         scheme=pfc->fIsSecurePort ? "https" : "http";
568
569     // Start with scheme and hostname.
570     if (g_bNormalizeRequest) {
571         target = string(scheme) + "://" + site.m_name;
572     }
573     else {
574         dynabuf name(64);
575         GetServerVariable(pfc,"SERVER_NAME",name,64);
576         target = string(scheme) + "://" + static_cast<char*>(name);
577     }
578     
579     // If port is non-default, append it.
580     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
581         target = target + ':' + static_cast<char*>(port);
582
583     // Append path.
584     if (!url.empty())
585         target+=static_cast<char*>(url);
586     
587     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
588 }
589
590 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
591 {
592     const IPropertySet* props=app->getPropertySet("Errors");
593     if (props) {
594         pair<bool,const char*> p=props->getString(page);
595         if (p.first) {
596             ifstream infile(p.second);
597             if (!infile.fail()) {
598                 const char* res = mlp.run(infile,props);
599                 if (res) {
600                     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
601                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
602                     DWORD resplen=strlen(res);
603                     pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
604                     return SF_STATUS_REQ_FINISHED;
605                 }
606             }
607         }
608     }
609
610     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
611     return WriteClientError(pfc,"Unable to open error template, check settings.");
612 }
613
614 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
615 {
616     ifstream infile(file);
617     if (!infile.fail()) {
618         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
619         if (res) {
620             char buf[255];
621             sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
622             if (headers) {
623                 string h(headers);
624                 h+=buf;
625                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
626             }
627             else
628                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
629             DWORD resplen=strlen(res);
630             pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
631             return SF_STATUS_REQ_FINISHED;
632         }
633     }
634     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
635     return WriteClientError(pfc,"Unable to open redirect template, check settings.");
636 }
637
638 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
639 {
640     // Is this a log notification?
641     if (notificationType==SF_NOTIFY_LOG)
642     {
643         if (pfc->pFilterContext)
644             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
645         return SF_STATUS_REQ_NEXT_NOTIFICATION;
646     }
647
648     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
649     try
650     {
651         // Determine web site number. This can't really fail, I don't think.
652         dynabuf buf(128);
653         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
654
655         // Match site instance to host name, skip if no match.
656         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
657         if (map_i==g_Sites.end())
658             return SF_STATUS_REQ_NEXT_NOTIFICATION;
659             
660         ostringstream threadid;
661         threadid << "[" << getpid() << "] isapi_shib" << '\0';
662         saml::NDC ndc(threadid.str().c_str());
663         
664         // We lock the configuration system for the duration.
665         IConfig* conf=g_Config->getINI();
666         Locker locker(conf);
667         
668         // Map request to application and content settings.
669         string targeturl;
670         IRequestMapper* mapper=conf->getRequestMapper();
671         Locker locker2(mapper);
672         IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
673         pair<bool,const char*> application_id=settings.first->getString("applicationId");
674         const IApplication* application=conf->getApplication(application_id.second);
675         if (!application)
676             return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
677         
678         // Declare SHIRE object for this request.
679         SHIRE shire(application);
680         
681         const char* shireURL=shire.getShireURL(targeturl.c_str());
682         if (!shireURL)
683             return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
684
685         // If the user is accessing the SHIRE acceptance point, pass it on.
686         if (targeturl.find(shireURL)!=string::npos)
687             return SF_STATUS_REQ_NEXT_NOTIFICATION;
688
689         // Now check the policy for this request.
690         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
691         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
692         pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
693         pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
694         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
695             return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
696
697         // Check for session cookie.
698         const char* session_id=NULL;
699         GetHeader(pn,pfc,"Cookie:",buf,128,false);
700         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
701         if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
702             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
703             char* cookieend=strchr(session_id,';');
704             if (cookieend)
705                 *cookieend = '\0';    /* Ignore anyting after a ; */
706         }
707         
708         if (!session_id || !*session_id) {
709             // If no session required, bail now.
710             if (!requireSession.second)
711                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
712     
713             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
714             const char* areq = shire.getAuthnRequest(targeturl.c_str());
715             if (!httpRedirects.first || httpRedirects.second) {
716                 string hdrs=string("Location: ") + areq + "\r\n"
717                     "Content-Type: text/html\r\n"
718                     "Content-Length: 40\r\n"
719                     "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
720                     "Cache-Control: private,no-store,no-cache\r\n\r\n";
721                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
722                 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
723                 DWORD resplen=40;
724                 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
725                 return SF_STATUS_REQ_FINISHED;
726             }
727             else {
728                 ShibMLP markupProcessor;
729                 markupProcessor.insert("requestURL",areq);
730                 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
731             }
732         }
733
734         // Make sure this session is still valid.
735         RPCError* status = NULL;
736         ShibMLP markupProcessor;
737         markupProcessor.insert("requestURL", targeturl);
738     
739         dynabuf abuf(16);
740         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
741         try {
742             status = shire.sessionIsValid(session_id, abuf);
743         }
744         catch (ShibTargetException &e) {
745             markupProcessor.insert("errorType", "Session Processing Error");
746             markupProcessor.insert("errorText", e.what());
747             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
748             return WriteClientError(pfc, application, "shire", markupProcessor);
749         }
750 #ifndef _DEBUG
751         catch (...) {
752             markupProcessor.insert("errorType", "Session Processing Error");
753             markupProcessor.insert("errorText", "Unexpected Exception");
754             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
755             return WriteClientError(pfc, application, "shire", markupProcessor);
756         }
757 #endif
758
759         // Check the status
760         if (status->isError()) {
761             if (!requireSession.second)
762                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
763             else if (status->isRetryable()) {
764                 // Oops, session is invalid. Generate AuthnRequest.
765                 delete status;
766                 const char* areq = shire.getAuthnRequest(targeturl.c_str());
767                 if (!httpRedirects.first || httpRedirects.second) {
768                     string hdrs=string("Location: ") + areq + "\r\n"
769                         "Content-Type: text/html\r\n"
770                         "Content-Length: 40\r\n"
771                         "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
772                         "Cache-Control: private,no-store,no-cache\r\n\r\n";
773                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
774                     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
775                     DWORD resplen=40;
776                     pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
777                     return SF_STATUS_REQ_FINISHED;
778                 }
779                 else {
780                     markupProcessor.insert("requestURL",areq);
781                     return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
782                 }
783             }
784             else {
785                 // return the error page to the user
786                 markupProcessor.insert(*status);
787                 delete status;
788                 return WriteClientError(pfc, application, "shire", markupProcessor);
789             }
790         }
791         delete status;
792     
793         // Move to RM phase.
794         RM rm(application);
795         vector<SAMLAssertion*> assertions;
796         SAMLAuthenticationStatement* sso_statement=NULL;
797
798         try {
799             status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
800         }
801         catch (ShibTargetException &e) {
802             markupProcessor.insert("errorType", "Attribute Processing Error");
803             markupProcessor.insert("errorText", e.what());
804             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
805             return WriteClientError(pfc, application, "rm", markupProcessor);
806         }
807     #ifndef _DEBUG
808         catch (...) {
809             markupProcessor.insert("errorType", "Attribute Processing Error");
810             markupProcessor.insert("errorText", "Unexpected Exception");
811             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
812             return WriteClientError(pfc, application, "rm", markupProcessor);
813         }
814     #endif
815     
816         if (status->isError()) {
817             markupProcessor.insert(*status);
818             delete status;
819             return WriteClientError(pfc, application, "rm", markupProcessor);
820         }
821         delete status;
822
823         // Do we have an access control plugin?
824         if (settings.second) {
825             Locker acllock(settings.second);
826             if (!settings.second->authorized(*sso_statement,assertions)) {
827                 for (int k = 0; k < assertions.size(); k++)
828                     delete assertions[k];
829                 delete sso_statement;
830                 return WriteClientError(pfc, application, "access", markupProcessor);
831             }
832         }
833
834         // Get the AAP providers, which contain the attribute policy info.
835         Iterator<IAAP*> provs=application->getAAPProviders();
836     
837         // Clear out the list of mapped attributes
838         while (provs.hasNext()) {
839             IAAP* aap=provs.next();
840             aap->lock();
841             try {
842                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
843                 while (rules.hasNext()) {
844                     const char* header=rules.next()->getHeader();
845                     if (header) {
846                         string hname=string(header) + ':';
847                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
848                     }
849                 }
850             }
851             catch(...) {
852                 aap->unlock();
853                 for (int k = 0; k < assertions.size(); k++)
854                   delete assertions[k];
855                 delete sso_statement;
856                 markupProcessor.insert("errorType", "Attribute Processing Error");
857                 markupProcessor.insert("errorText", "Unexpected Exception");
858                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
859                 return WriteClientError(pfc, application, "rm", markupProcessor);
860             }
861             aap->unlock();
862         }
863         provs.reset();
864
865         // Maybe export the first assertion.
866         pn->SetHeader(pfc,"remote-user:","");
867         pn->SetHeader(pfc,"Shib-Attributes:","");
868         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
869         if (exp.first && exp.second && assertions.size()) {
870             string assertion;
871             RM::serialize(*(assertions[0]), assertion);
872             string::size_type lfeed;
873             while ((lfeed=assertion.find('\n'))!=string::npos)
874                 assertion.erase(lfeed,1);
875             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
876         }
877         
878         pn->SetHeader(pfc,"Shib-Origin-Site:","");
879         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
880         pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
881
882         // Export the SAML AuthnMethod and the origin site name.
883         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
884         auto_ptr_char am(sso_statement->getAuthMethod());
885         pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
886         pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
887
888         // Export NameID?
889         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
890         if (!wrapper.fail() && wrapper->getHeader()) {
891             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
892             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
893             pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
894             if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
895                 char* principal=const_cast<char*>(nameid.get());
896                 pn->SetHeader(pfc,"remote-user:",principal);
897                 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
898                 if (pfc->pFilterContext)
899                     strcpy(static_cast<char*>(pfc->pFilterContext),principal);
900             }
901             else {
902                 string hname=string(wrapper->getHeader()) + ':';
903                 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
904             }
905         }
906
907         pn->SetHeader(pfc,"Shib-Application-ID:","");
908         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
909
910         // Export the attributes.
911         Iterator<SAMLAssertion*> a_iter(assertions);
912         while (a_iter.hasNext()) {
913             SAMLAssertion* assert=a_iter.next();
914             Iterator<SAMLStatement*> statements=assert->getStatements();
915             while (statements.hasNext()) {
916                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
917                 if (!astate)
918                     continue;
919                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
920                 while (attrs.hasNext()) {
921                     SAMLAttribute* attr=attrs.next();
922         
923                     // Are we supposed to export it?
924                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
925                     if (wrapper.fail() || !wrapper->getHeader())
926                         continue;
927                 
928                     Iterator<string> vals=attr->getSingleByteValues();
929                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
930                         char* principal=const_cast<char*>(vals.next().c_str());
931                         pn->SetHeader(pfc,"remote-user:",principal);
932                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
933                         if (pfc->pFilterContext)
934                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
935                     }
936                     else {
937                         int it=0;
938                         string header;
939                         string hname=string(wrapper->getHeader()) + ':';
940                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
941                         if (!buf.empty()) {
942                             header=buf;
943                             it++;
944                         }
945                         for (; vals.hasNext(); it++) {
946                             string value = vals.next();
947                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
948                                     pos != string::npos;
949                                     pos = value.find_first_of(";", pos)) {
950                                 value.insert(pos, "\\");
951                                 pos += 2;
952                             }
953                             if (it == 0)
954                                 header=value;
955                             else
956                                 header=header + ';' + value;
957                         }
958                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
959                         }
960                 }
961             }
962         }
963     
964         // clean up memory
965         for (int k = 0; k < assertions.size(); k++)
966           delete assertions[k];
967         delete sso_statement;
968
969         return SF_STATUS_REQ_NEXT_NOTIFICATION;
970     }
971     catch(bad_alloc) {
972         return WriteClientError(pfc,"Out of Memory");
973     }
974     catch(DWORD e) {
975         if (e==ERROR_NO_DATA)
976             return WriteClientError(pfc,"A required variable or header was empty.");
977         else
978             return WriteClientError(pfc,"Server detected unexpected IIS error.");
979     }
980 #ifndef _DEBUG
981     catch(...) {
982         return WriteClientError(pfc,"Server caught an unknown exception.");
983     }
984 #endif
985
986     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
987 }
988 #endif // 0
989
990 /****************************************************************************/
991 // ISAPI Extension
992
993 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
994 {
995     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
996     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
997     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
998     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
999     DWORD resplen=strlen(xmsg);
1000     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1001     resplen=strlen(msg);
1002     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1003     static const char* xmsg2="</BODY></HTML>";
1004     resplen=strlen(xmsg2);
1005     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1006     return HSE_STATUS_SUCCESS;
1007 }
1008
1009
1010 class ShibTargetIsapiE : public ShibTarget
1011 {
1012 public:
1013   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) :
1014     m_cookie(NULL)
1015   {
1016     dynabuf ssl(5);
1017     GetServerVariable(lpECB,"HTTPS",ssl,5);
1018     bool SSL=(ssl=="on" || ssl=="ON");
1019
1020     // URL path always come from IIS.
1021     dynabuf url(256);
1022     GetServerVariable(lpECB,"URL",url,255);
1023
1024     // Port may come from IIS or from site def.
1025     dynabuf port(11);
1026     if (site.m_port.empty() || !g_bNormalizeRequest)
1027         GetServerVariable(lpECB,"SERVER_PORT",port,10);
1028     else {
1029         strncpy(port,site.m_port.c_str(),10);
1030         static_cast<char*>(port)[10]=0;
1031     }
1032
1033     // Scheme may come from site def or be derived from IIS.
1034     const char* scheme=site.m_scheme.c_str();
1035     if (!scheme || !*scheme || !g_bNormalizeRequest) {
1036         scheme = SSL ? "https" : "http";
1037     }
1038
1039     // Get the remote address
1040     dynabuf remote_addr(16);
1041     GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1042
1043     init(g_Config, string(scheme), site.m_name, atoi(port),
1044          string(url), string(lpECB->lpszContentType ? lpECB->lpszContentType : ""),
1045          string(remote_addr), string(lpECB->lpszMethod)
1046          ); 
1047
1048     m_lpECB = lpECB;
1049   }
1050   ~ShibTargetIsapiE() { }
1051
1052   virtual void log(ShibLogLevel level, const string &msg) {
1053       LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1054                         (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1055                         (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1056              2100, NULL, msg.c_str());
1057   }
1058   virtual void setCookie(const string &name, const string &value) {
1059     // Set the cookie for later.  Use it during the redirect.
1060     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1061   }
1062   virtual string getArgs(void) {
1063     return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1064   }
1065   virtual string getPostData(void) {
1066     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1067       throw ShibTargetException(SHIBRPC_OK,
1068                                 "blocked too-large a post to SHIRE POST processor");
1069     else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1070       string cgistr;
1071       char buf[8192];
1072       DWORD datalen=m_lpECB->cbTotalBytes;
1073       while (datalen) {
1074         DWORD buflen=8192;
1075         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1076         if (!ret || !buflen)
1077           throw ShibTargetException(SHIBRPC_OK,
1078                                     "error reading POST data from browser");
1079         cgistr.append(buf, buflen);
1080         datalen-=buflen;
1081       }
1082       return cgistr;
1083     }
1084     else
1085       return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1086   }
1087   virtual void* sendPage(const string &msg, const string content_type,
1088                          const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
1089     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1090     for (int k = 0; k < headers.size(); k++) {
1091       hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1092     }
1093     hdr += "\r\n";
1094     // XXX Need to handle "code"
1095     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1096                                    "200 OK", 0, (LPDWORD)hdr.c_str());
1097     DWORD resplen = msg.size();
1098     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1099     return (void*)HSE_STATUS_SUCCESS;
1100   }
1101   virtual void* sendRedirect(const string url) {
1102     // XXX: Don't support the httpRedirect option, yet.
1103     string hdrs = m_cookie + "Location: " + url + "\r\n"
1104       "Content-Type: text/html\r\n"
1105       "Content-Length: 40\r\n"
1106       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1107       "Cache-Control: private,no-store,no-cache\r\n\r\n";
1108     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1109                                  "302 Moved", 0, (LPDWORD)hdrs.c_str());
1110     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1111     DWORD resplen=40;
1112     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1113     return (void*)HSE_STATUS_SUCCESS;
1114   }
1115   // Decline happens in the POST processor if this isn't the shire url
1116   // Note that it can also happen with HTAccess, but we don't suppor that, yet.
1117   virtual void* returnDecline(void) {
1118     return (void*)
1119       WriteClientError(m_lpECB, "UISAPA extension can only be unvoked to process incoming sessions."
1120                        "Make sure the mapped file extension doesn't match actual content.");
1121   }
1122   virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1123
1124   // Not used in the extension.
1125   virtual string getCookies(void) { throw runtime_error("getCookies not implemented"); }
1126   virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1127   virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1128   virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1129   virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1130   virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1131
1132   LPEXTENSION_CONTROL_BLOCK m_lpECB;
1133   string m_cookie;
1134 };
1135
1136 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1137 {
1138     string targeturl;
1139     const IApplication* application=NULL;
1140     try
1141     {
1142         ostringstream threadid;
1143         threadid << "[" << getpid() << "] shire_handler" << '\0';
1144         saml::NDC ndc(threadid.str().c_str());
1145
1146         // Determine web site number. This can't really fail, I don't think.
1147         dynabuf buf(128);
1148         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1149
1150         // Match site instance to host name, skip if no match.
1151         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1152         if (map_i==g_Sites.end())
1153             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1154
1155         ShibTargetIsapiE ste(lpECB, map_i->second);
1156         pair<bool,void*> res = ste.doHandleProfile();
1157         if (res.first) return (DWORD)res.second;
1158
1159         return WriteClientError(lpECB, "Shibboleth Extension failed to process POST");
1160
1161     } catch (...) {
1162       return WriteClientError(lpECB,
1163                               "Shibboleth Extension caught an unknown error. "
1164                               "Memory Failure?");
1165     }
1166
1167     // If we get here we've got an error.
1168     return HSE_STATUS_ERROR;
1169 }
1170
1171 #if 0
1172 IRequestMapper::Settings map_request(
1173     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1174     )
1175 {
1176     dynabuf ssl(5);
1177     GetServerVariable(lpECB,"HTTPS",ssl,5);
1178     bool SSL=(ssl=="on" || ssl=="ON");
1179
1180     // URL path always come from IIS.
1181     dynabuf url(256);
1182     GetServerVariable(lpECB,"URL",url,255);
1183
1184     // Port may come from IIS or from site def.
1185     dynabuf port(11);
1186     if (site.m_port.empty() || !g_bNormalizeRequest)
1187         GetServerVariable(lpECB,"SERVER_PORT",port,10);
1188     else {
1189         strncpy(port,site.m_port.c_str(),10);
1190         static_cast<char*>(port)[10]=0;
1191     }
1192
1193     // Scheme may come from site def or be derived from IIS.
1194     const char* scheme=site.m_scheme.c_str();
1195     if (!scheme || !*scheme || !g_bNormalizeRequest) {
1196         scheme = SSL ? "https" : "http";
1197     }
1198
1199     // Start with scheme and hostname.
1200     if (g_bNormalizeRequest) {
1201         target = string(scheme) + "://" + site.m_name;
1202     }
1203     else {
1204         dynabuf name(64);
1205         GetServerVariable(lpECB,"SERVER_NAME",name,64);
1206         target = string(scheme) + "://" + static_cast<char*>(name);
1207     }
1208     
1209     // If port is non-default, append it.
1210     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1211         target = target + ':' + static_cast<char*>(port);
1212
1213     // Append path.
1214     if (!url.empty())
1215         target+=static_cast<char*>(url);
1216     
1217     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1218 }
1219
1220 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1221 {
1222     const IPropertySet* props=app->getPropertySet("Errors");
1223     if (props) {
1224         pair<bool,const char*> p=props->getString(page);
1225         if (p.first) {
1226             ifstream infile(p.second);
1227             if (!infile.fail()) {
1228                 const char* res = mlp.run(infile,props);
1229                 if (res) {
1230                     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1231                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1232                     DWORD resplen=strlen(res);
1233                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1234                     return HSE_STATUS_SUCCESS;
1235                 }
1236             }
1237         }
1238     }
1239     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1240     return WriteClientError(lpECB,"Unable to open error template, check settings.");
1241 }
1242
1243 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1244 {
1245     ifstream infile(file);
1246     if (!infile.fail()) {
1247         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1248         if (res) {
1249             char buf[255];
1250             sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1251             if (headers) {
1252                 string h(headers);
1253                 h+=buf;
1254                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1255             }
1256             else
1257                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1258             DWORD resplen=strlen(res);
1259             lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1260             return HSE_STATUS_SUCCESS;
1261         }
1262     }
1263     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1264     return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1265 }
1266
1267 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1268 {
1269     string targeturl;
1270     const IApplication* application=NULL;
1271     try
1272     {
1273         ostringstream threadid;
1274         threadid << "[" << getpid() << "] shire_handler" << '\0';
1275         saml::NDC ndc(threadid.str().c_str());
1276
1277         // Determine web site number. This can't really fail, I don't think.
1278         dynabuf buf(128);
1279         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1280
1281         // Match site instance to host name, skip if no match.
1282         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1283         if (map_i==g_Sites.end())
1284             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1285             
1286         // We lock the configuration system for the duration.
1287         IConfig* conf=g_Config->getINI();
1288         Locker locker(conf);
1289         
1290         // Map request to application and content settings.
1291         IRequestMapper* mapper=conf->getRequestMapper();
1292         Locker locker2(mapper);
1293         IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1294         pair<bool,const char*> application_id=settings.first->getString("applicationId");
1295         application=conf->getApplication(application_id.second);
1296         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1297         if (!application || !sessionProps)
1298             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1299
1300         SHIRE shire(application);
1301         
1302         const char* shireURL=shire.getShireURL(targeturl.c_str());
1303         if (!shireURL)
1304             return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1305
1306         // Make sure we only process the SHIRE requests.
1307         if (!strstr(targeturl.c_str(),shireURL))
1308             return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1309                 "Make sure the mapped file extension doesn't match actual content.");
1310
1311         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1312
1313         // Make sure this is SSL, if it should be
1314         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1315         if (!shireSSL.first || shireSSL.second) {
1316             GetServerVariable(lpECB,"HTTPS",buf,10);
1317             if (buf!="on")
1318                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1319         }
1320         
1321         pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1322         pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1323         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1324             return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1325         
1326         // Check for Mac web browser
1327         /*
1328         bool bSafari=false;
1329         dynabuf agent(64);
1330         GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1331         if (strstr(agent,"AppleWebKit/"))
1332             bSafari=true;
1333         */
1334         
1335         // If this is a GET, we manufacture an AuthnRequest.
1336         if (!stricmp(lpECB->lpszMethod,"GET")) {
1337             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1338             if (!areq)
1339                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1340             if (!httpRedirects.first || httpRedirects.second) {
1341                 string hdrs=string("Location: ") + areq + "\r\n"
1342                     "Content-Type: text/html\r\n"
1343                     "Content-Length: 40\r\n"
1344                     "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1345                     "Cache-Control: private,no-store,no-cache\r\n\r\n";
1346                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1347                 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1348                 DWORD resplen=40;
1349                 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1350                 return HSE_STATUS_SUCCESS;
1351             }
1352             else {
1353                 ShibMLP markupProcessor;
1354                 markupProcessor.insert("requestURL",areq);
1355                 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1356             }
1357         }
1358         else if (stricmp(lpECB->lpszMethod,"POST"))
1359             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1360
1361         // Sure sure this POST is an appropriate content type
1362         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1363             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1364     
1365         // Read the data.
1366         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1367         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1368             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1369         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1370             string cgistr;
1371             char buf[8192];
1372             DWORD datalen=lpECB->cbTotalBytes;
1373             while (datalen) {
1374                 DWORD buflen=8192;
1375                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1376                 if (!ret || !buflen)
1377                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1378                 cgistr.append(buf,buflen);
1379                 datalen-=buflen;
1380             }
1381             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1382         }
1383         else
1384             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1385     
1386         // Make sure the SAML Response parameter exists
1387         if (!elements.first || !*elements.first)
1388             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1389     
1390         // Make sure the target parameter exists
1391         if (!elements.second || !*elements.second)
1392             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1393             
1394         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1395
1396         // Process the post.
1397         string cookie;
1398         RPCError* status=NULL;
1399         ShibMLP markupProcessor;
1400         markupProcessor.insert("requestURL", targeturl.c_str());
1401         try {
1402             status = shire.sessionCreate(elements.first,buf,cookie);
1403         }
1404         catch (ShibTargetException &e) {
1405             markupProcessor.insert("errorType", "Session Creation Service Error");
1406             markupProcessor.insert("errorText", e.what());
1407             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1408             return WriteClientError(lpECB, application, "shire", markupProcessor);
1409         }
1410 #ifndef _DEBUG
1411         catch (...) {
1412             markupProcessor.insert("errorType", "Session Creation Service Error");
1413             markupProcessor.insert("errorText", "Unexpected Exception");
1414             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1415             return WriteClientError(lpECB, application, "shire", markupProcessor);
1416         }
1417 #endif
1418
1419         if (status->isError()) {
1420             if (status->isRetryable()) {
1421                 delete status;
1422                 const char* loc=shire.getAuthnRequest(elements.second);
1423                 if (!httpRedirects.first || httpRedirects.second) {
1424                     string hdrs=string("Location: ") + loc + "\r\n"
1425                         "Content-Type: text/html\r\n"
1426                         "Content-Length: 40\r\n"
1427                         "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1428                         "Cache-Control: private,no-store,no-cache\r\n\r\n";
1429                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1430                     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1431                     DWORD resplen=40;
1432                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1433                     return HSE_STATUS_SUCCESS;
1434                 }
1435                 else {
1436                     markupProcessor.insert("requestURL",loc);
1437                     return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1438                 }
1439             }
1440     
1441             // Return this error to the user.
1442             markupProcessor.insert(*status);
1443             delete status;
1444             return WriteClientError(lpECB,application,"shire",markupProcessor);
1445         }
1446         delete status;
1447     
1448         // We've got a good session, set the cookie and redirect to target.
1449         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1450             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1451             "Cache-Control: private,no-store,no-cache\r\n";
1452         if (!httpRedirects.first || httpRedirects.second) {
1453             cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1454             lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1455             static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1456             DWORD resplen=40;
1457             lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1458             return HSE_STATUS_SUCCESS;
1459         }
1460         else {
1461             markupProcessor.insert("requestURL",elements.second);
1462             return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1463         }
1464     }
1465     catch (ShibTargetException &e) {
1466         if (application) {
1467             ShibMLP markupProcessor;
1468             markupProcessor.insert("requestURL", targeturl.c_str());
1469             markupProcessor.insert("errorType", "Session Creation Service Error");
1470             markupProcessor.insert("errorText", e.what());
1471             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1472             return WriteClientError(lpECB,application,"shire",markupProcessor);
1473         }
1474     }
1475 #ifndef _DEBUG
1476     catch (...) {
1477         if (application) {
1478             ShibMLP markupProcessor;
1479             markupProcessor.insert("requestURL", targeturl.c_str());
1480             markupProcessor.insert("errorType", "Session Creation Service Error");
1481             markupProcessor.insert("errorText", "Unexpected Exception");
1482             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1483             return WriteClientError(lpECB,application,"shire",markupProcessor);
1484         }
1485     }
1486 #endif
1487
1488     return HSE_STATUS_ERROR;
1489 }
1490 #endif // 0