Consolidated exception and status handling into a single class.
[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 #ifndef _DEBUG
168     try
169     {
170 #endif
171         LPCSTR schemadir=getenv("SHIBSCHEMAS");
172         if (!schemadir)
173             schemadir=SHIB_SCHEMAS;
174         LPCSTR config=getenv("SHIBCONFIG");
175         if (!config)
176             config=SHIB_CONFIG;
177         g_Config=&ShibTargetConfig::getConfig();
178         g_Config->setFeatures(
179             ShibTargetConfig::Listener |
180             ShibTargetConfig::Metadata |
181             ShibTargetConfig::AAP |
182             ShibTargetConfig::RequestMapper |
183             ShibTargetConfig::LocalExtensions |
184             ShibTargetConfig::Logging
185             );
186         if (!g_Config->init(schemadir,config)) {
187             g_Config=NULL;
188             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
189                     "Filter startup failed during initialization, check shire log for help.");
190             return FALSE;
191         }
192         
193         // Access the implementation-specifics for site mappings.
194         IConfig* conf=g_Config->getINI();
195         Locker locker(conf);
196         const IPropertySet* props=conf->getPropertySet("Local");
197         if (props) {
198             const DOMElement* impl=saml::XML::getFirstChildElement(
199                 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
200                 );
201             if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
202                 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
203                 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
204                 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
205                 while (impl) {
206                     auto_ptr_char id(impl->getAttributeNS(NULL,id));
207                     if (id.get())
208                         g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
209                     impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
210                 }
211             }
212         }
213 #ifndef _DEBUG
214     }
215     catch (...)
216     {
217         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
218         return FALSE;
219     }
220 #endif
221
222     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
223     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
224     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
225                    SF_NOTIFY_SECURE_PORT |
226                    SF_NOTIFY_NONSECURE_PORT |
227                    SF_NOTIFY_PREPROC_HEADERS |
228                    SF_NOTIFY_LOG);
229     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
230     return TRUE;
231 }
232
233 extern "C" BOOL WINAPI TerminateFilter(DWORD)
234 {
235     if (g_Config)
236         g_Config->shutdown();
237     g_Config = NULL;
238     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
239     return TRUE;
240 }
241
242 /* Next up, some suck-free versions of various APIs.
243
244    You DON'T require people to guess the buffer size and THEN tell them the right size.
245    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
246    constant strings aren't typed as such, making it just that much harder. These versions
247    are now updated to use a special growable buffer object, modeled after the standard
248    string class. The standard string won't work because they left out the option to
249    pre-allocate a non-constant buffer.
250 */
251
252 class dynabuf
253 {
254 public:
255     dynabuf() { bufptr=NULL; buflen=0; }
256     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
257     ~dynabuf() { delete[] bufptr; }
258     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
259     size_t size() const { return buflen; }
260     bool empty() const { return length()==0; }
261     void reserve(size_t s, bool keep=false);
262     void erase() { if (bufptr) memset(bufptr,0,buflen); }
263     operator char*() { return bufptr; }
264     bool operator ==(const char* s) const;
265     bool operator !=(const char* s) const { return !(*this==s); }
266 private:
267     char* bufptr;
268     size_t buflen;
269 };
270
271 void dynabuf::reserve(size_t s, bool keep)
272 {
273     if (s<=buflen)
274         return;
275     char* p=new char[s];
276     if (keep)
277         while (buflen--)
278             p[buflen]=bufptr[buflen];
279     buflen=s;
280     delete[] bufptr;
281     bufptr=p;
282 }
283
284 bool dynabuf::operator==(const char* s) const
285 {
286     if (buflen==NULL || s==NULL)
287         return (buflen==NULL && s==NULL);
288     else
289         return strcmp(bufptr,s)==0;
290 }
291
292 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
293     throw (bad_alloc, DWORD)
294 {
295     s.reserve(size);
296     s.erase();
297     size=s.size();
298
299     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
300     {
301         // Grumble. Check the error.
302         DWORD e=GetLastError();
303         if (e==ERROR_INSUFFICIENT_BUFFER)
304             s.reserve(size);
305         else
306             break;
307     }
308     if (bRequired && s.empty())
309         throw ERROR_NO_DATA;
310 }
311
312 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
313     throw (bad_alloc, DWORD)
314 {
315     s.reserve(size);
316     s.erase();
317     size=s.size();
318
319     while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
320     {
321         // Grumble. Check the error.
322         DWORD e=GetLastError();
323         if (e==ERROR_INSUFFICIENT_BUFFER)
324             s.reserve(size);
325         else
326             break;
327     }
328     if (bRequired && s.empty())
329         throw ERROR_NO_DATA;
330 }
331
332 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
333                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
334     throw (bad_alloc, DWORD)
335 {
336     s.reserve(size);
337     s.erase();
338     size=s.size();
339
340     while (!pn->GetHeader(pfc,lpszName,s,&size))
341     {
342         // Grumble. Check the error.
343         DWORD e=GetLastError();
344         if (e==ERROR_INSUFFICIENT_BUFFER)
345             s.reserve(size);
346         else
347             break;
348     }
349     if (bRequired && s.empty())
350         throw ERROR_NO_DATA;
351 }
352
353 /****************************************************************************/
354 // ISAPI Filter
355
356 class ShibTargetIsapiF : public ShibTarget
357 {
358 public:
359   ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn,
360                    const site_t& site) {
361
362     // URL path always come from IIS.
363     dynabuf url(256);
364     GetHeader(pn,pfc,"url",url,256,false);
365
366     // Port may come from IIS or from site def.
367     dynabuf port(11);
368     if (site.m_port.empty() || !g_bNormalizeRequest)
369         GetServerVariable(pfc,"SERVER_PORT",port,10);
370     else {
371         strncpy(port,site.m_port.c_str(),10);
372         static_cast<char*>(port)[10]=0;
373     }
374     
375     // Scheme may come from site def or be derived from IIS.
376     const char* scheme=site.m_scheme.c_str();
377     if (!scheme || !*scheme || !g_bNormalizeRequest)
378         scheme=pfc->fIsSecurePort ? "https" : "http";
379
380     // Get the remote address
381     dynabuf remote_addr(16);
382     GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
383
384     // XXX: How do I get the content type and HTTP Method from this context?
385
386     // TODO: Need to allow for use of SERVER_NAME
387
388     init(g_Config, string(scheme), site.m_name, atoi(port),
389          string(url), string(""), // XXX: content type
390          string(remote_addr), string("") // XXX: http method
391          ); 
392
393     m_pfc = pfc;
394     m_pn = pn;
395   }
396   ~ShibTargetIsapiF() { }
397
398   virtual void log(ShibLogLevel level, const string &msg) {
399       LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
400                       (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
401                       (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
402              2100, NULL, msg.c_str());
403   }
404   virtual string getCookies(void) {
405     dynabuf buf(128);
406     GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
407     return buf.empty() ? "" : buf;
408   }
409   
410   virtual void clearHeader(const string &name) {
411     string hdr = name + ":";
412     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
413   }
414   virtual void setHeader(const string &name, const string &value) {
415     string hdr = name + ":";
416     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
417                     const_cast<char*>(value.c_str()));
418   }
419   virtual string getHeader(const string &name) {
420     string hdr = name + ":";
421     dynabuf buf(1024);
422     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
423     return string(buf);
424   }
425   virtual void setRemoteUser(const string &user) {
426     setHeader(string("remote-user"), user);
427   }
428   virtual string getRemoteUser(void) {
429     return getHeader(string("remote-user"));
430   }
431   virtual void* sendPage(const string &msg, const string content_type,
432       const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
433     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
434     while (headers.hasNext()) {
435         const header_t& h=headers.next();
436         hdr += h.first + ": " + h.second + "\r\n";
437     }
438     hdr += "\r\n";
439     // XXX Need to handle "code"
440     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK", (DWORD)hdr.c_str(), 0);
441     DWORD resplen = msg.size();
442     m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
443     return (void*)SF_STATUS_REQ_FINISHED;
444   }
445   virtual void* sendRedirect(const string url) {
446     // XXX: Don't support the httpRedirect option, yet.
447     string hdrs=string("Location: ") + url + "\r\n"
448       "Content-Type: text/html\r\n"
449       "Content-Length: 40\r\n"
450       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
451       "Cache-Control: private,no-store,no-cache\r\n\r\n";
452     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
453                                  "302 Please Wait", (DWORD)hdrs.c_str(), 0);
454     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
455     DWORD resplen=40;
456     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
457     return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
458   }
459   // XXX: We might not ever hit the 'decline' status in this filter.
460   //virtual void* returnDecline(void) { }
461   virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
462
463   // The filter never processes the POST, so stub these methods.
464   virtual void setCookie(const string &name, const string &value) { throw runtime_error("setCookie not implemented"); }
465   virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
466   virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
467   
468   PHTTP_FILTER_CONTEXT m_pfc;
469   PHTTP_FILTER_PREPROC_HEADERS m_pn;
470 };
471
472 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
473 {
474     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
475     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
476     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
477     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
478                             "<H1>Shibboleth Filter Error</H1>";
479     DWORD resplen=strlen(xmsg);
480     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
481     resplen=strlen(msg);
482     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
483     static const char* xmsg2="</BODY></HTML>";
484     resplen=strlen(xmsg2);
485     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
486     return SF_STATUS_REQ_FINISHED;
487 }
488
489 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
490 {
491     // Is this a log notification?
492     if (notificationType==SF_NOTIFY_LOG)
493     {
494         if (pfc->pFilterContext)
495             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
496         return SF_STATUS_REQ_NEXT_NOTIFICATION;
497     }
498
499     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
500     try
501     {
502         // Determine web site number. This can't really fail, I don't think.
503         dynabuf buf(128);
504         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
505
506         // Match site instance to host name, skip if no match.
507         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
508         if (map_i==g_Sites.end())
509             return SF_STATUS_REQ_NEXT_NOTIFICATION;
510             
511         ostringstream threadid;
512         threadid << "[" << getpid() << "] isapi_shib" << '\0';
513         saml::NDC ndc(threadid.str().c_str());
514
515         ShibTargetIsapiF stf(pfc, pn, map_i->second);
516
517         // "false" because we don't override the Shib settings
518         pair<bool,void*> res = stf.doCheckAuthN();
519         if (res.first) return (DWORD)res.second;
520
521         // "false" because we don't override the Shib settings
522         res = stf.doExportAssertions();
523         if (res.first) return (DWORD)res.second;
524
525         res = stf.doCheckAuthZ();
526         if (res.first) return (DWORD)res.second;
527
528         return SF_STATUS_REQ_NEXT_NOTIFICATION;
529     }
530     catch(bad_alloc) {
531         return WriteClientError(pfc,"Out of Memory");
532     }
533     catch(DWORD e) {
534         if (e==ERROR_NO_DATA)
535             return WriteClientError(pfc,"A required variable or header was empty.");
536         else
537             return WriteClientError(pfc,"Server detected unexpected IIS error.");
538     }
539 #ifndef _DEBUG
540     catch(...) {
541         return WriteClientError(pfc,"Server caught an unknown exception.");
542     }
543 #endif
544
545     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
546 }
547         
548
549 #if 0
550 IRequestMapper::Settings map_request(
551     PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
552     )
553 {
554     // URL path always come from IIS.
555     dynabuf url(256);
556     GetHeader(pn,pfc,"url",url,256,false);
557
558     // Port may come from IIS or from site def.
559     dynabuf port(11);
560     if (site.m_port.empty() || !g_bNormalizeRequest)
561         GetServerVariable(pfc,"SERVER_PORT",port,10);
562     else {
563         strncpy(port,site.m_port.c_str(),10);
564         static_cast<char*>(port)[10]=0;
565     }
566     
567     // Scheme may come from site def or be derived from IIS.
568     const char* scheme=site.m_scheme.c_str();
569     if (!scheme || !*scheme || !g_bNormalizeRequest)
570         scheme=pfc->fIsSecurePort ? "https" : "http";
571
572     // Start with scheme and hostname.
573     if (g_bNormalizeRequest) {
574         target = string(scheme) + "://" + site.m_name;
575     }
576     else {
577         dynabuf name(64);
578         GetServerVariable(pfc,"SERVER_NAME",name,64);
579         target = string(scheme) + "://" + static_cast<char*>(name);
580     }
581     
582     // If port is non-default, append it.
583     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
584         target = target + ':' + static_cast<char*>(port);
585
586     // Append path.
587     if (!url.empty())
588         target+=static_cast<char*>(url);
589     
590     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
591 }
592
593 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
594 {
595     const IPropertySet* props=app->getPropertySet("Errors");
596     if (props) {
597         pair<bool,const char*> p=props->getString(page);
598         if (p.first) {
599             ifstream infile(p.second);
600             if (!infile.fail()) {
601                 const char* res = mlp.run(infile,props);
602                 if (res) {
603                     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
604                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
605                     DWORD resplen=strlen(res);
606                     pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
607                     return SF_STATUS_REQ_FINISHED;
608                 }
609             }
610         }
611     }
612
613     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
614     return WriteClientError(pfc,"Unable to open error template, check settings.");
615 }
616
617 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
618 {
619     ifstream infile(file);
620     if (!infile.fail()) {
621         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
622         if (res) {
623             char buf[255];
624             sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
625             if (headers) {
626                 string h(headers);
627                 h+=buf;
628                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
629             }
630             else
631                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
632             DWORD resplen=strlen(res);
633             pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
634             return SF_STATUS_REQ_FINISHED;
635         }
636     }
637     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
638     return WriteClientError(pfc,"Unable to open redirect template, check settings.");
639 }
640
641 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
642 {
643     // Is this a log notification?
644     if (notificationType==SF_NOTIFY_LOG)
645     {
646         if (pfc->pFilterContext)
647             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
648         return SF_STATUS_REQ_NEXT_NOTIFICATION;
649     }
650
651     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
652     try
653     {
654         // Determine web site number. This can't really fail, I don't think.
655         dynabuf buf(128);
656         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
657
658         // Match site instance to host name, skip if no match.
659         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
660         if (map_i==g_Sites.end())
661             return SF_STATUS_REQ_NEXT_NOTIFICATION;
662             
663         ostringstream threadid;
664         threadid << "[" << getpid() << "] isapi_shib" << '\0';
665         saml::NDC ndc(threadid.str().c_str());
666         
667         // We lock the configuration system for the duration.
668         IConfig* conf=g_Config->getINI();
669         Locker locker(conf);
670         
671         // Map request to application and content settings.
672         string targeturl;
673         IRequestMapper* mapper=conf->getRequestMapper();
674         Locker locker2(mapper);
675         IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
676         pair<bool,const char*> application_id=settings.first->getString("applicationId");
677         const IApplication* application=conf->getApplication(application_id.second);
678         if (!application)
679             return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
680         
681         // Declare SHIRE object for this request.
682         SHIRE shire(application);
683         
684         const char* shireURL=shire.getShireURL(targeturl.c_str());
685         if (!shireURL)
686             return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
687
688         // If the user is accessing the SHIRE acceptance point, pass it on.
689         if (targeturl.find(shireURL)!=string::npos)
690             return SF_STATUS_REQ_NEXT_NOTIFICATION;
691
692         // Now check the policy for this request.
693         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
694         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
695         pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
696         pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
697         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
698             return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
699
700         // Check for session cookie.
701         const char* session_id=NULL;
702         GetHeader(pn,pfc,"Cookie:",buf,128,false);
703         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
704         if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
705             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
706             char* cookieend=strchr(session_id,';');
707             if (cookieend)
708                 *cookieend = '\0';    /* Ignore anyting after a ; */
709         }
710         
711         if (!session_id || !*session_id) {
712             // If no session required, bail now.
713             if (!requireSession.second)
714                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
715     
716             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
717             const char* areq = shire.getAuthnRequest(targeturl.c_str());
718             if (!httpRedirects.first || httpRedirects.second) {
719                 string hdrs=string("Location: ") + areq + "\r\n"
720                     "Content-Type: text/html\r\n"
721                     "Content-Length: 40\r\n"
722                     "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
723                     "Cache-Control: private,no-store,no-cache\r\n\r\n";
724                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
725                 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
726                 DWORD resplen=40;
727                 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
728                 return SF_STATUS_REQ_FINISHED;
729             }
730             else {
731                 ShibMLP markupProcessor;
732                 markupProcessor.insert("requestURL",areq);
733                 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
734             }
735         }
736
737         // Make sure this session is still valid.
738         RPCError* status = NULL;
739         ShibMLP markupProcessor;
740         markupProcessor.insert("requestURL", targeturl);
741     
742         dynabuf abuf(16);
743         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
744         try {
745             status = shire.sessionIsValid(session_id, abuf);
746         }
747         catch (ShibTargetException &e) {
748             markupProcessor.insert("errorType", "Session Processing Error");
749             markupProcessor.insert("errorText", e.what());
750             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
751             return WriteClientError(pfc, application, "shire", markupProcessor);
752         }
753 #ifndef _DEBUG
754         catch (...) {
755             markupProcessor.insert("errorType", "Session Processing Error");
756             markupProcessor.insert("errorText", "Unexpected Exception");
757             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
758             return WriteClientError(pfc, application, "shire", markupProcessor);
759         }
760 #endif
761
762         // Check the status
763         if (status->isError()) {
764             if (!requireSession.second)
765                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
766             else if (status->isRetryable()) {
767                 // Oops, session is invalid. Generate AuthnRequest.
768                 delete status;
769                 const char* areq = shire.getAuthnRequest(targeturl.c_str());
770                 if (!httpRedirects.first || httpRedirects.second) {
771                     string hdrs=string("Location: ") + areq + "\r\n"
772                         "Content-Type: text/html\r\n"
773                         "Content-Length: 40\r\n"
774                         "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
775                         "Cache-Control: private,no-store,no-cache\r\n\r\n";
776                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
777                     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
778                     DWORD resplen=40;
779                     pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
780                     return SF_STATUS_REQ_FINISHED;
781                 }
782                 else {
783                     markupProcessor.insert("requestURL",areq);
784                     return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
785                 }
786             }
787             else {
788                 // return the error page to the user
789                 markupProcessor.insert(*status);
790                 delete status;
791                 return WriteClientError(pfc, application, "shire", markupProcessor);
792             }
793         }
794         delete status;
795     
796         // Move to RM phase.
797         RM rm(application);
798         vector<SAMLAssertion*> assertions;
799         SAMLAuthenticationStatement* sso_statement=NULL;
800
801         try {
802             status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
803         }
804         catch (ShibTargetException &e) {
805             markupProcessor.insert("errorType", "Attribute Processing Error");
806             markupProcessor.insert("errorText", e.what());
807             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
808             return WriteClientError(pfc, application, "rm", markupProcessor);
809         }
810     #ifndef _DEBUG
811         catch (...) {
812             markupProcessor.insert("errorType", "Attribute Processing Error");
813             markupProcessor.insert("errorText", "Unexpected Exception");
814             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
815             return WriteClientError(pfc, application, "rm", markupProcessor);
816         }
817     #endif
818     
819         if (status->isError()) {
820             markupProcessor.insert(*status);
821             delete status;
822             return WriteClientError(pfc, application, "rm", markupProcessor);
823         }
824         delete status;
825
826         // Do we have an access control plugin?
827         if (settings.second) {
828             Locker acllock(settings.second);
829             if (!settings.second->authorized(*sso_statement,assertions)) {
830                 for (int k = 0; k < assertions.size(); k++)
831                     delete assertions[k];
832                 delete sso_statement;
833                 return WriteClientError(pfc, application, "access", markupProcessor);
834             }
835         }
836
837         // Get the AAP providers, which contain the attribute policy info.
838         Iterator<IAAP*> provs=application->getAAPProviders();
839     
840         // Clear out the list of mapped attributes
841         while (provs.hasNext()) {
842             IAAP* aap=provs.next();
843             aap->lock();
844             try {
845                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
846                 while (rules.hasNext()) {
847                     const char* header=rules.next()->getHeader();
848                     if (header) {
849                         string hname=string(header) + ':';
850                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
851                     }
852                 }
853             }
854             catch(...) {
855                 aap->unlock();
856                 for (int k = 0; k < assertions.size(); k++)
857                   delete assertions[k];
858                 delete sso_statement;
859                 markupProcessor.insert("errorType", "Attribute Processing Error");
860                 markupProcessor.insert("errorText", "Unexpected Exception");
861                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
862                 return WriteClientError(pfc, application, "rm", markupProcessor);
863             }
864             aap->unlock();
865         }
866         provs.reset();
867
868         // Maybe export the first assertion.
869         pn->SetHeader(pfc,"remote-user:","");
870         pn->SetHeader(pfc,"Shib-Attributes:","");
871         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
872         if (exp.first && exp.second && assertions.size()) {
873             string assertion;
874             RM::serialize(*(assertions[0]), assertion);
875             string::size_type lfeed;
876             while ((lfeed=assertion.find('\n'))!=string::npos)
877                 assertion.erase(lfeed,1);
878             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
879         }
880         
881         pn->SetHeader(pfc,"Shib-Origin-Site:","");
882         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
883         pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
884
885         // Export the SAML AuthnMethod and the origin site name.
886         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
887         auto_ptr_char am(sso_statement->getAuthMethod());
888         pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
889         pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
890
891         // Export NameID?
892         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
893         if (!wrapper.fail() && wrapper->getHeader()) {
894             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
895             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
896             pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
897             if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
898                 char* principal=const_cast<char*>(nameid.get());
899                 pn->SetHeader(pfc,"remote-user:",principal);
900                 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
901                 if (pfc->pFilterContext)
902                     strcpy(static_cast<char*>(pfc->pFilterContext),principal);
903             }
904             else {
905                 string hname=string(wrapper->getHeader()) + ':';
906                 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
907             }
908         }
909
910         pn->SetHeader(pfc,"Shib-Application-ID:","");
911         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
912
913         // Export the attributes.
914         Iterator<SAMLAssertion*> a_iter(assertions);
915         while (a_iter.hasNext()) {
916             SAMLAssertion* assert=a_iter.next();
917             Iterator<SAMLStatement*> statements=assert->getStatements();
918             while (statements.hasNext()) {
919                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
920                 if (!astate)
921                     continue;
922                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
923                 while (attrs.hasNext()) {
924                     SAMLAttribute* attr=attrs.next();
925         
926                     // Are we supposed to export it?
927                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
928                     if (wrapper.fail() || !wrapper->getHeader())
929                         continue;
930                 
931                     Iterator<string> vals=attr->getSingleByteValues();
932                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
933                         char* principal=const_cast<char*>(vals.next().c_str());
934                         pn->SetHeader(pfc,"remote-user:",principal);
935                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
936                         if (pfc->pFilterContext)
937                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
938                     }
939                     else {
940                         int it=0;
941                         string header;
942                         string hname=string(wrapper->getHeader()) + ':';
943                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
944                         if (!buf.empty()) {
945                             header=buf;
946                             it++;
947                         }
948                         for (; vals.hasNext(); it++) {
949                             string value = vals.next();
950                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
951                                     pos != string::npos;
952                                     pos = value.find_first_of(";", pos)) {
953                                 value.insert(pos, "\\");
954                                 pos += 2;
955                             }
956                             if (it == 0)
957                                 header=value;
958                             else
959                                 header=header + ';' + value;
960                         }
961                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
962                         }
963                 }
964             }
965         }
966     
967         // clean up memory
968         for (int k = 0; k < assertions.size(); k++)
969           delete assertions[k];
970         delete sso_statement;
971
972         return SF_STATUS_REQ_NEXT_NOTIFICATION;
973     }
974     catch(bad_alloc) {
975         return WriteClientError(pfc,"Out of Memory");
976     }
977     catch(DWORD e) {
978         if (e==ERROR_NO_DATA)
979             return WriteClientError(pfc,"A required variable or header was empty.");
980         else
981             return WriteClientError(pfc,"Server detected unexpected IIS error.");
982     }
983 #ifndef _DEBUG
984     catch(...) {
985         return WriteClientError(pfc,"Server caught an unknown exception.");
986     }
987 #endif
988
989     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
990 }
991 #endif // 0
992
993 /****************************************************************************/
994 // ISAPI Extension
995
996 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
997 {
998     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
999     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1000     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1001     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1002     DWORD resplen=strlen(xmsg);
1003     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1004     resplen=strlen(msg);
1005     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1006     static const char* xmsg2="</BODY></HTML>";
1007     resplen=strlen(xmsg2);
1008     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1009     return HSE_STATUS_SUCCESS;
1010 }
1011
1012
1013 class ShibTargetIsapiE : public ShibTarget
1014 {
1015 public:
1016   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
1017   {
1018     dynabuf ssl(5);
1019     GetServerVariable(lpECB,"HTTPS",ssl,5);
1020     bool SSL=(ssl=="on" || ssl=="ON");
1021
1022     // URL path always come from IIS.
1023     dynabuf url(256);
1024     GetServerVariable(lpECB,"URL",url,255);
1025
1026     // Port may come from IIS or from site def.
1027     dynabuf port(11);
1028     if (site.m_port.empty() || !g_bNormalizeRequest)
1029         GetServerVariable(lpECB,"SERVER_PORT",port,10);
1030     else {
1031         strncpy(port,site.m_port.c_str(),10);
1032         static_cast<char*>(port)[10]=0;
1033     }
1034
1035     // Scheme may come from site def or be derived from IIS.
1036     const char* scheme=site.m_scheme.c_str();
1037     if (!scheme || !*scheme || !g_bNormalizeRequest) {
1038         scheme = SSL ? "https" : "http";
1039     }
1040
1041     // Get the remote address
1042     dynabuf remote_addr(16);
1043     GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1044
1045     init(g_Config, string(scheme), site.m_name, atoi(port),
1046          string(url), string(lpECB->lpszContentType ? lpECB->lpszContentType : ""),
1047          string(remote_addr), string(lpECB->lpszMethod)
1048          ); 
1049
1050     m_lpECB = lpECB;
1051   }
1052   ~ShibTargetIsapiE() { }
1053
1054   virtual void log(ShibLogLevel level, const string &msg) {
1055       LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1056                         (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1057                         (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1058              2100, NULL, msg.c_str());
1059   }
1060   virtual void setCookie(const string &name, const string &value) {
1061     // Set the cookie for later.  Use it during the redirect.
1062     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1063   }
1064   virtual string getArgs(void) {
1065     return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1066   }
1067   virtual string getPostData(void) {
1068     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1069       throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1070     else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1071       string cgistr;
1072       char buf[8192];
1073       DWORD datalen=m_lpECB->cbTotalBytes;
1074       while (datalen) {
1075         DWORD buflen=8192;
1076         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1077         if (!ret || !buflen)
1078           throw FatalProfileException("Error reading profile submission 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 request");
1160
1161     }
1162     catch (...) {
1163       return WriteClientError(lpECB,
1164                               "Shibboleth Extension caught an unknown error.");
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