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