Convert more strings to references.
[shibboleth/cpp-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(const string& msg, const string& content_type,
449       const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
450     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
451     while (headers.hasNext()) {
452         const header_t& h=headers.next();
453         hdr += h.first + ": " + h.second + "\r\n";
454     }
455     hdr += "\r\n";
456     // XXX Need to handle "code"
457     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK", (DWORD)hdr.c_str(), 0);
458     DWORD resplen = msg.size();
459     m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
460     return (void*)SF_STATUS_REQ_FINISHED;
461   }
462   virtual void* sendRedirect(const string& url) {
463     // XXX: Don't support the httpRedirect option, yet.
464     string hdrs=m_cookie + string("Location: ") + url + "\r\n"
465       "Content-Type: text/html\r\n"
466       "Content-Length: 40\r\n"
467       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
468       "Cache-Control: private,no-store,no-cache\r\n\r\n";
469     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
470                                  "302 Please Wait", (DWORD)hdrs.c_str(), 0);
471     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
472     DWORD resplen=40;
473     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
474     return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
475   }
476   // XXX: We might not ever hit the 'decline' status in this filter.
477   //virtual void* returnDecline(void) { }
478   virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
479
480   // The filter never processes the POST, so stub these methods.
481   virtual void setCookie(const string &name, const string &value) {
482     // Set the cookie for later.  Use it during the redirect.
483     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
484   }
485   virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
486   virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
487   
488   PHTTP_FILTER_CONTEXT m_pfc;
489   PHTTP_FILTER_PREPROC_HEADERS m_pn;
490   string m_cookie;
491 };
492
493 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
494 {
495     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
496     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
497     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
498     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
499                             "<H1>Shibboleth Filter Error</H1>";
500     DWORD resplen=strlen(xmsg);
501     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
502     resplen=strlen(msg);
503     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
504     static const char* xmsg2="</BODY></HTML>";
505     resplen=strlen(xmsg2);
506     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
507     return SF_STATUS_REQ_FINISHED;
508 }
509
510 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
511 {
512     // Is this a log notification?
513     if (notificationType==SF_NOTIFY_LOG)
514     {
515         if (pfc->pFilterContext)
516             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
517         return SF_STATUS_REQ_NEXT_NOTIFICATION;
518     }
519
520     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
521     try
522     {
523         // Determine web site number. This can't really fail, I don't think.
524         dynabuf buf(128);
525         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
526
527         // Match site instance to host name, skip if no match.
528         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
529         if (map_i==g_Sites.end())
530             return SF_STATUS_REQ_NEXT_NOTIFICATION;
531             
532         ostringstream threadid;
533         threadid << "[" << getpid() << "] isapi_shib" << '\0';
534         saml::NDC ndc(threadid.str().c_str());
535
536         ShibTargetIsapiF stf(pfc, pn, map_i->second);
537
538         // "false" because we don't override the Shib settings
539         pair<bool,void*> res = stf.doCheckAuthN();
540         if (res.first) return (DWORD)res.second;
541
542         // "false" because we don't override the Shib settings
543         res = stf.doExportAssertions();
544         if (res.first) return (DWORD)res.second;
545
546         res = stf.doCheckAuthZ();
547         if (res.first) return (DWORD)res.second;
548
549         return SF_STATUS_REQ_NEXT_NOTIFICATION;
550     }
551     catch(bad_alloc) {
552         return WriteClientError(pfc,"Out of Memory");
553     }
554     catch(long e) {
555         if (e==ERROR_NO_DATA)
556             return WriteClientError(pfc,"A required variable or header was empty.");
557         else
558             return WriteClientError(pfc,"Server detected unexpected IIS error.");
559     }
560 #ifndef _DEBUG
561     catch(...) {
562         return WriteClientError(pfc,"Server caught an unknown exception.");
563     }
564 #endif
565
566     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
567 }
568         
569
570 #if 0
571 IRequestMapper::Settings map_request(
572     PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
573     )
574 {
575     // URL path always come from IIS.
576     dynabuf url(256);
577     GetHeader(pn,pfc,"url",url,256,false);
578
579     // Port may come from IIS or from site def.
580     dynabuf port(11);
581     if (site.m_port.empty() || !g_bNormalizeRequest)
582         GetServerVariable(pfc,"SERVER_PORT",port,10);
583     else {
584         strncpy(port,site.m_port.c_str(),10);
585         static_cast<char*>(port)[10]=0;
586     }
587     
588     // Scheme may come from site def or be derived from IIS.
589     const char* scheme=site.m_scheme.c_str();
590     if (!scheme || !*scheme || !g_bNormalizeRequest)
591         scheme=pfc->fIsSecurePort ? "https" : "http";
592
593     // Start with scheme and hostname.
594     if (g_bNormalizeRequest) {
595         target = string(scheme) + "://" + site.m_name;
596     }
597     else {
598         dynabuf name(64);
599         GetServerVariable(pfc,"SERVER_NAME",name,64);
600         target = string(scheme) + "://" + static_cast<char*>(name);
601     }
602     
603     // If port is non-default, append it.
604     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
605         target = target + ':' + static_cast<char*>(port);
606
607     // Append path.
608     if (!url.empty())
609         target+=static_cast<char*>(url);
610     
611     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
612 }
613
614 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
615 {
616     const IPropertySet* props=app->getPropertySet("Errors");
617     if (props) {
618         pair<bool,const char*> p=props->getString(page);
619         if (p.first) {
620             ifstream infile(p.second);
621             if (!infile.fail()) {
622                 const char* res = mlp.run(infile,props);
623                 if (res) {
624                     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
625                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
626                     DWORD resplen=strlen(res);
627                     pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
628                     return SF_STATUS_REQ_FINISHED;
629                 }
630             }
631         }
632     }
633
634     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
635     return WriteClientError(pfc,"Unable to open error template, check settings.");
636 }
637
638 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
639 {
640     ifstream infile(file);
641     if (!infile.fail()) {
642         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
643         if (res) {
644             char buf[255];
645             sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
646             if (headers) {
647                 string h(headers);
648                 h+=buf;
649                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
650             }
651             else
652                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
653             DWORD resplen=strlen(res);
654             pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
655             return SF_STATUS_REQ_FINISHED;
656         }
657     }
658     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
659     return WriteClientError(pfc,"Unable to open redirect template, check settings.");
660 }
661
662 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
663 {
664     // Is this a log notification?
665     if (notificationType==SF_NOTIFY_LOG)
666     {
667         if (pfc->pFilterContext)
668             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
669         return SF_STATUS_REQ_NEXT_NOTIFICATION;
670     }
671
672     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
673     try
674     {
675         // Determine web site number. This can't really fail, I don't think.
676         dynabuf buf(128);
677         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
678
679         // Match site instance to host name, skip if no match.
680         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
681         if (map_i==g_Sites.end())
682             return SF_STATUS_REQ_NEXT_NOTIFICATION;
683             
684         ostringstream threadid;
685         threadid << "[" << getpid() << "] isapi_shib" << '\0';
686         saml::NDC ndc(threadid.str().c_str());
687         
688         // We lock the configuration system for the duration.
689         IConfig* conf=g_Config->getINI();
690         Locker locker(conf);
691         
692         // Map request to application and content settings.
693         string targeturl;
694         IRequestMapper* mapper=conf->getRequestMapper();
695         Locker locker2(mapper);
696         IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
697         pair<bool,const char*> application_id=settings.first->getString("applicationId");
698         const IApplication* application=conf->getApplication(application_id.second);
699         if (!application)
700             return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
701         
702         // Declare SHIRE object for this request.
703         SHIRE shire(application);
704         
705         const char* shireURL=shire.getShireURL(targeturl.c_str());
706         if (!shireURL)
707             return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
708
709         // If the user is accessing the SHIRE acceptance point, pass it on.
710         if (targeturl.find(shireURL)!=string::npos)
711             return SF_STATUS_REQ_NEXT_NOTIFICATION;
712
713         // Now check the policy for this request.
714         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
715         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
716         pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
717         pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
718         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
719             return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
720
721         // Check for session cookie.
722         const char* session_id=NULL;
723         GetHeader(pn,pfc,"Cookie:",buf,128,false);
724         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
725         if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
726             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
727             char* cookieend=strchr(session_id,';');
728             if (cookieend)
729                 *cookieend = '\0';    /* Ignore anyting after a ; */
730         }
731         
732         if (!session_id || !*session_id) {
733             // If no session required, bail now.
734             if (!requireSession.second)
735                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
736     
737             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
738             const char* areq = shire.getAuthnRequest(targeturl.c_str());
739             if (!httpRedirects.first || httpRedirects.second) {
740                 string hdrs=string("Location: ") + areq + "\r\n"
741                     "Content-Type: text/html\r\n"
742                     "Content-Length: 40\r\n"
743                     "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
744                     "Cache-Control: private,no-store,no-cache\r\n\r\n";
745                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
746                 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
747                 DWORD resplen=40;
748                 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
749                 return SF_STATUS_REQ_FINISHED;
750             }
751             else {
752                 ShibMLP markupProcessor;
753                 markupProcessor.insert("requestURL",areq);
754                 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
755             }
756         }
757
758         // Make sure this session is still valid.
759         RPCError* status = NULL;
760         ShibMLP markupProcessor;
761         markupProcessor.insert("requestURL", targeturl);
762     
763         dynabuf abuf(16);
764         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
765         try {
766             status = shire.sessionIsValid(session_id, abuf);
767         }
768         catch (ShibTargetException &e) {
769             markupProcessor.insert("errorType", "Session Processing Error");
770             markupProcessor.insert("errorText", e.what());
771             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
772             return WriteClientError(pfc, application, "shire", markupProcessor);
773         }
774 #ifndef _DEBUG
775         catch (...) {
776             markupProcessor.insert("errorType", "Session Processing Error");
777             markupProcessor.insert("errorText", "Unexpected Exception");
778             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
779             return WriteClientError(pfc, application, "shire", markupProcessor);
780         }
781 #endif
782
783         // Check the status
784         if (status->isError()) {
785             if (!requireSession.second)
786                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
787             else if (status->isRetryable()) {
788                 // Oops, session is invalid. Generate AuthnRequest.
789                 delete status;
790                 const char* areq = shire.getAuthnRequest(targeturl.c_str());
791                 if (!httpRedirects.first || httpRedirects.second) {
792                     string hdrs=string("Location: ") + areq + "\r\n"
793                         "Content-Type: text/html\r\n"
794                         "Content-Length: 40\r\n"
795                         "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
796                         "Cache-Control: private,no-store,no-cache\r\n\r\n";
797                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
798                     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
799                     DWORD resplen=40;
800                     pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
801                     return SF_STATUS_REQ_FINISHED;
802                 }
803                 else {
804                     markupProcessor.insert("requestURL",areq);
805                     return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
806                 }
807             }
808             else {
809                 // return the error page to the user
810                 markupProcessor.insert(*status);
811                 delete status;
812                 return WriteClientError(pfc, application, "shire", markupProcessor);
813             }
814         }
815         delete status;
816     
817         // Move to RM phase.
818         RM rm(application);
819         vector<SAMLAssertion*> assertions;
820         SAMLAuthenticationStatement* sso_statement=NULL;
821
822         try {
823             status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
824         }
825         catch (ShibTargetException &e) {
826             markupProcessor.insert("errorType", "Attribute Processing Error");
827             markupProcessor.insert("errorText", e.what());
828             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
829             return WriteClientError(pfc, application, "rm", markupProcessor);
830         }
831     #ifndef _DEBUG
832         catch (...) {
833             markupProcessor.insert("errorType", "Attribute Processing Error");
834             markupProcessor.insert("errorText", "Unexpected Exception");
835             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
836             return WriteClientError(pfc, application, "rm", markupProcessor);
837         }
838     #endif
839     
840         if (status->isError()) {
841             markupProcessor.insert(*status);
842             delete status;
843             return WriteClientError(pfc, application, "rm", markupProcessor);
844         }
845         delete status;
846
847         // Do we have an access control plugin?
848         if (settings.second) {
849             Locker acllock(settings.second);
850             if (!settings.second->authorized(*sso_statement,assertions)) {
851                 for (int k = 0; k < assertions.size(); k++)
852                     delete assertions[k];
853                 delete sso_statement;
854                 return WriteClientError(pfc, application, "access", markupProcessor);
855             }
856         }
857
858         // Get the AAP providers, which contain the attribute policy info.
859         Iterator<IAAP*> provs=application->getAAPProviders();
860     
861         // Clear out the list of mapped attributes
862         while (provs.hasNext()) {
863             IAAP* aap=provs.next();
864             aap->lock();
865             try {
866                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
867                 while (rules.hasNext()) {
868                     const char* header=rules.next()->getHeader();
869                     if (header) {
870                         string hname=string(header) + ':';
871                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
872                     }
873                 }
874             }
875             catch(...) {
876                 aap->unlock();
877                 for (int k = 0; k < assertions.size(); k++)
878                   delete assertions[k];
879                 delete sso_statement;
880                 markupProcessor.insert("errorType", "Attribute Processing Error");
881                 markupProcessor.insert("errorText", "Unexpected Exception");
882                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
883                 return WriteClientError(pfc, application, "rm", markupProcessor);
884             }
885             aap->unlock();
886         }
887         provs.reset();
888
889         // Maybe export the first assertion.
890         pn->SetHeader(pfc,"remote-user:","");
891         pn->SetHeader(pfc,"Shib-Attributes:","");
892         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
893         if (exp.first && exp.second && assertions.size()) {
894             string assertion;
895             RM::serialize(*(assertions[0]), assertion);
896             string::size_type lfeed;
897             while ((lfeed=assertion.find('\n'))!=string::npos)
898                 assertion.erase(lfeed,1);
899             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
900         }
901         
902         pn->SetHeader(pfc,"Shib-Origin-Site:","");
903         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
904         pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
905
906         // Export the SAML AuthnMethod and the origin site name.
907         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
908         auto_ptr_char am(sso_statement->getAuthMethod());
909         pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
910         pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
911
912         // Export NameID?
913         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
914         if (!wrapper.fail() && wrapper->getHeader()) {
915             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
916             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
917             pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
918             if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
919                 char* principal=const_cast<char*>(nameid.get());
920                 pn->SetHeader(pfc,"remote-user:",principal);
921                 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
922                 if (pfc->pFilterContext)
923                     strcpy(static_cast<char*>(pfc->pFilterContext),principal);
924             }
925             else {
926                 string hname=string(wrapper->getHeader()) + ':';
927                 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
928             }
929         }
930
931         pn->SetHeader(pfc,"Shib-Application-ID:","");
932         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
933
934         // Export the attributes.
935         Iterator<SAMLAssertion*> a_iter(assertions);
936         while (a_iter.hasNext()) {
937             SAMLAssertion* assert=a_iter.next();
938             Iterator<SAMLStatement*> statements=assert->getStatements();
939             while (statements.hasNext()) {
940                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
941                 if (!astate)
942                     continue;
943                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
944                 while (attrs.hasNext()) {
945                     SAMLAttribute* attr=attrs.next();
946         
947                     // Are we supposed to export it?
948                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
949                     if (wrapper.fail() || !wrapper->getHeader())
950                         continue;
951                 
952                     Iterator<string> vals=attr->getSingleByteValues();
953                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
954                         char* principal=const_cast<char*>(vals.next().c_str());
955                         pn->SetHeader(pfc,"remote-user:",principal);
956                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
957                         if (pfc->pFilterContext)
958                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
959                     }
960                     else {
961                         int it=0;
962                         string header;
963                         string hname=string(wrapper->getHeader()) + ':';
964                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
965                         if (!buf.empty()) {
966                             header=buf;
967                             it++;
968                         }
969                         for (; vals.hasNext(); it++) {
970                             string value = vals.next();
971                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
972                                     pos != string::npos;
973                                     pos = value.find_first_of(";", pos)) {
974                                 value.insert(pos, "\\");
975                                 pos += 2;
976                             }
977                             if (it == 0)
978                                 header=value;
979                             else
980                                 header=header + ';' + value;
981                         }
982                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
983                         }
984                 }
985             }
986         }
987     
988         // clean up memory
989         for (int k = 0; k < assertions.size(); k++)
990           delete assertions[k];
991         delete sso_statement;
992
993         return SF_STATUS_REQ_NEXT_NOTIFICATION;
994     }
995     catch(bad_alloc) {
996         return WriteClientError(pfc,"Out of Memory");
997     }
998     catch(DWORD e) {
999         if (e==ERROR_NO_DATA)
1000             return WriteClientError(pfc,"A required variable or header was empty.");
1001         else
1002             return WriteClientError(pfc,"Server detected unexpected IIS error.");
1003     }
1004 #ifndef _DEBUG
1005     catch(...) {
1006         return WriteClientError(pfc,"Server caught an unknown exception.");
1007     }
1008 #endif
1009
1010     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1011 }
1012 #endif // 0
1013
1014 /****************************************************************************/
1015 // ISAPI Extension
1016
1017 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1018 {
1019     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1020     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1021     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1022     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1023     DWORD resplen=strlen(xmsg);
1024     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1025     resplen=strlen(msg);
1026     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1027     static const char* xmsg2="</BODY></HTML>";
1028     resplen=strlen(xmsg2);
1029     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1030     return HSE_STATUS_SUCCESS;
1031 }
1032
1033
1034 class ShibTargetIsapiE : public ShibTarget
1035 {
1036 public:
1037   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
1038   {
1039     dynabuf ssl(5);
1040     GetServerVariable(lpECB,"HTTPS",ssl,5);
1041     bool SSL=(ssl=="on" || ssl=="ON");
1042
1043     // URL path always come from IIS.
1044     dynabuf url(256);
1045     GetServerVariable(lpECB,"URL",url,255);
1046
1047     // Port may come from IIS or from site def.
1048     dynabuf port(11);
1049     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1050         GetServerVariable(lpECB,"SERVER_PORT",port,10);
1051     else if (SSL) {
1052         strncpy(port,site.m_sslport.c_str(),10);
1053         static_cast<char*>(port)[10]=0;
1054     }
1055     else {
1056         strncpy(port,site.m_port.c_str(),10);
1057         static_cast<char*>(port)[10]=0;
1058     }
1059
1060     // Scheme may come from site def or be derived from IIS.
1061     const char* scheme=site.m_scheme.c_str();
1062     if (!scheme || !*scheme || !g_bNormalizeRequest) {
1063         scheme = SSL ? "https" : "http";
1064     }
1065
1066     // Get the other server variables.
1067     dynabuf remote_addr(16),hostname(32);
1068     GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1069     GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1070
1071     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1072     const char* host=hostname;
1073     if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1074         host=site.m_name.c_str();
1075
1076     init(g_Config, scheme, host, atoi(port), url, lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1077
1078     m_lpECB = lpECB;
1079   }
1080   ~ShibTargetIsapiE() { }
1081
1082   virtual void log(ShibLogLevel level, const string &msg) {
1083       LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1084                         (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1085                         (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1086              2100, NULL, msg.c_str());
1087   }
1088   virtual void setCookie(const string &name, const string &value) {
1089     // Set the cookie for later.  Use it during the redirect.
1090     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1091   }
1092   virtual string getArgs(void) {
1093     return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1094   }
1095   virtual string getPostData(void) {
1096     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1097       throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1098     else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1099       string cgistr;
1100       char buf[8192];
1101       DWORD datalen=m_lpECB->cbTotalBytes;
1102       while (datalen) {
1103         DWORD buflen=8192;
1104         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1105         if (!ret || !buflen)
1106           throw FatalProfileException("Error reading profile submission from browser.");
1107         cgistr.append(buf, buflen);
1108         datalen-=buflen;
1109       }
1110       return cgistr;
1111     }
1112     else
1113       return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1114   }
1115   virtual void* sendPage(const string &msg, const string& content_type,
1116                          const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
1117     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1118     for (int k = 0; k < headers.size(); k++) {
1119       hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1120     }
1121     hdr += "\r\n";
1122     // XXX Need to handle "code"
1123     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1124                                    "200 OK", 0, (LPDWORD)hdr.c_str());
1125     DWORD resplen = msg.size();
1126     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1127     return (void*)HSE_STATUS_SUCCESS;
1128   }
1129   virtual void* sendRedirect(const string& url) {
1130     // XXX: Don't support the httpRedirect option, yet.
1131     string hdrs = m_cookie + "Location: " + url + "\r\n"
1132       "Content-Type: text/html\r\n"
1133       "Content-Length: 40\r\n"
1134       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1135       "Cache-Control: private,no-store,no-cache\r\n\r\n";
1136     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1137                                  "302 Moved", 0, (LPDWORD)hdrs.c_str());
1138     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1139     DWORD resplen=40;
1140     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1141     return (void*)HSE_STATUS_SUCCESS;
1142   }
1143   // Decline happens in the POST processor if this isn't the shire url
1144   // Note that it can also happen with HTAccess, but we don't support that, yet.
1145   virtual void* returnDecline(void) {
1146     return (void*)
1147       WriteClientError(m_lpECB, "ISAPA extension can only be invoked to process incoming sessions."
1148                        "Make sure the mapped file extension doesn't match actual content.");
1149   }
1150   virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1151
1152   virtual string getCookies(void) {
1153     dynabuf buf(128);
1154     GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1155     return buf.empty() ? "" : buf;
1156   }
1157
1158   // Not used in the extension.
1159   virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1160   virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1161   virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1162   virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1163   virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1164
1165   LPEXTENSION_CONTROL_BLOCK m_lpECB;
1166   string m_cookie;
1167 };
1168
1169 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1170 {
1171     string targeturl;
1172     const IApplication* application=NULL;
1173     try {
1174         ostringstream threadid;
1175         threadid << "[" << getpid() << "] shire_handler" << '\0';
1176         saml::NDC ndc(threadid.str().c_str());
1177
1178         // Determine web site number. This can't really fail, I don't think.
1179         dynabuf buf(128);
1180         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1181
1182         // Match site instance to host name, skip if no match.
1183         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1184         if (map_i==g_Sites.end())
1185             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1186
1187         ShibTargetIsapiE ste(lpECB, map_i->second);
1188         pair<bool,void*> res = ste.doHandleProfile();
1189         if (res.first) return (DWORD)res.second;
1190         
1191         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1192
1193     }
1194     catch(bad_alloc) {
1195         return WriteClientError(lpECB,"Out of Memory");
1196     }
1197     catch(long e) {
1198         if (e==ERROR_NO_DATA)
1199             return WriteClientError(lpECB,"A required variable or header was empty.");
1200         else
1201             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1202     }
1203 #ifndef _DEBUG
1204     catch(...) {
1205         return WriteClientError(lpECB,"Server caught an unknown exception.");
1206     }
1207 #endif
1208
1209     // If we get here we've got an error.
1210     return HSE_STATUS_ERROR;
1211 }
1212
1213 #if 0
1214 IRequestMapper::Settings map_request(
1215     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1216     )
1217 {
1218     dynabuf ssl(5);
1219     GetServerVariable(lpECB,"HTTPS",ssl,5);
1220     bool SSL=(ssl=="on" || ssl=="ON");
1221
1222     // URL path always come from IIS.
1223     dynabuf url(256);
1224     GetServerVariable(lpECB,"URL",url,255);
1225
1226     // Port may come from IIS or from site def.
1227     dynabuf port(11);
1228     if (site.m_port.empty() || !g_bNormalizeRequest)
1229         GetServerVariable(lpECB,"SERVER_PORT",port,10);
1230     else {
1231         strncpy(port,site.m_port.c_str(),10);
1232         static_cast<char*>(port)[10]=0;
1233     }
1234
1235     // Scheme may come from site def or be derived from IIS.
1236     const char* scheme=site.m_scheme.c_str();
1237     if (!scheme || !*scheme || !g_bNormalizeRequest) {
1238         scheme = SSL ? "https" : "http";
1239     }
1240
1241     // Start with scheme and hostname.
1242     if (g_bNormalizeRequest) {
1243         target = string(scheme) + "://" + site.m_name;
1244     }
1245     else {
1246         dynabuf name(64);
1247         GetServerVariable(lpECB,"SERVER_NAME",name,64);
1248         target = string(scheme) + "://" + static_cast<char*>(name);
1249     }
1250     
1251     // If port is non-default, append it.
1252     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1253         target = target + ':' + static_cast<char*>(port);
1254
1255     // Append path.
1256     if (!url.empty())
1257         target+=static_cast<char*>(url);
1258     
1259     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1260 }
1261
1262 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1263 {
1264     const IPropertySet* props=app->getPropertySet("Errors");
1265     if (props) {
1266         pair<bool,const char*> p=props->getString(page);
1267         if (p.first) {
1268             ifstream infile(p.second);
1269             if (!infile.fail()) {
1270                 const char* res = mlp.run(infile,props);
1271                 if (res) {
1272                     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1273                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1274                     DWORD resplen=strlen(res);
1275                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1276                     return HSE_STATUS_SUCCESS;
1277                 }
1278             }
1279         }
1280     }
1281     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1282     return WriteClientError(lpECB,"Unable to open error template, check settings.");
1283 }
1284
1285 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1286 {
1287     ifstream infile(file);
1288     if (!infile.fail()) {
1289         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1290         if (res) {
1291             char buf[255];
1292             sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1293             if (headers) {
1294                 string h(headers);
1295                 h+=buf;
1296                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1297             }
1298             else
1299                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1300             DWORD resplen=strlen(res);
1301             lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1302             return HSE_STATUS_SUCCESS;
1303         }
1304     }
1305     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1306     return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1307 }
1308
1309 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1310 {
1311     string targeturl;
1312     const IApplication* application=NULL;
1313     try
1314     {
1315         ostringstream threadid;
1316         threadid << "[" << getpid() << "] shire_handler" << '\0';
1317         saml::NDC ndc(threadid.str().c_str());
1318
1319         // Determine web site number. This can't really fail, I don't think.
1320         dynabuf buf(128);
1321         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1322
1323         // Match site instance to host name, skip if no match.
1324         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1325         if (map_i==g_Sites.end())
1326             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1327             
1328         // We lock the configuration system for the duration.
1329         IConfig* conf=g_Config->getINI();
1330         Locker locker(conf);
1331         
1332         // Map request to application and content settings.
1333         IRequestMapper* mapper=conf->getRequestMapper();
1334         Locker locker2(mapper);
1335         IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1336         pair<bool,const char*> application_id=settings.first->getString("applicationId");
1337         application=conf->getApplication(application_id.second);
1338         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1339         if (!application || !sessionProps)
1340             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1341
1342         SHIRE shire(application);
1343         
1344         const char* shireURL=shire.getShireURL(targeturl.c_str());
1345         if (!shireURL)
1346             return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1347
1348         // Make sure we only process the SHIRE requests.
1349         if (!strstr(targeturl.c_str(),shireURL))
1350             return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1351                 "Make sure the mapped file extension doesn't match actual content.");
1352
1353         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1354
1355         // Make sure this is SSL, if it should be
1356         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1357         if (!shireSSL.first || shireSSL.second) {
1358             GetServerVariable(lpECB,"HTTPS",buf,10);
1359             if (buf!="on")
1360                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1361         }
1362         
1363         pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1364         pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1365         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1366             return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1367         
1368         // Check for Mac web browser
1369         /*
1370         bool bSafari=false;
1371         dynabuf agent(64);
1372         GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1373         if (strstr(agent,"AppleWebKit/"))
1374             bSafari=true;
1375         */
1376         
1377         // If this is a GET, we manufacture an AuthnRequest.
1378         if (!stricmp(lpECB->lpszMethod,"GET")) {
1379             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1380             if (!areq)
1381                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1382             if (!httpRedirects.first || httpRedirects.second) {
1383                 string hdrs=string("Location: ") + areq + "\r\n"
1384                     "Content-Type: text/html\r\n"
1385                     "Content-Length: 40\r\n"
1386                     "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1387                     "Cache-Control: private,no-store,no-cache\r\n\r\n";
1388                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1389                 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1390                 DWORD resplen=40;
1391                 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1392                 return HSE_STATUS_SUCCESS;
1393             }
1394             else {
1395                 ShibMLP markupProcessor;
1396                 markupProcessor.insert("requestURL",areq);
1397                 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1398             }
1399         }
1400         else if (stricmp(lpECB->lpszMethod,"POST"))
1401             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1402
1403         // Sure sure this POST is an appropriate content type
1404         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1405             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1406     
1407         // Read the data.
1408         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1409         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1410             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1411         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1412             string cgistr;
1413             char buf[8192];
1414             DWORD datalen=lpECB->cbTotalBytes;
1415             while (datalen) {
1416                 DWORD buflen=8192;
1417                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1418                 if (!ret || !buflen)
1419                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1420                 cgistr.append(buf,buflen);
1421                 datalen-=buflen;
1422             }
1423             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1424         }
1425         else
1426             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1427     
1428         // Make sure the SAML Response parameter exists
1429         if (!elements.first || !*elements.first)
1430             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1431     
1432         // Make sure the target parameter exists
1433         if (!elements.second || !*elements.second)
1434             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1435             
1436         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1437
1438         // Process the post.
1439         string cookie;
1440         RPCError* status=NULL;
1441         ShibMLP markupProcessor;
1442         markupProcessor.insert("requestURL", targeturl.c_str());
1443         try {
1444             status = shire.sessionCreate(elements.first,buf,cookie);
1445         }
1446         catch (ShibTargetException &e) {
1447             markupProcessor.insert("errorType", "Session Creation Service Error");
1448             markupProcessor.insert("errorText", e.what());
1449             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1450             return WriteClientError(lpECB, application, "shire", markupProcessor);
1451         }
1452 #ifndef _DEBUG
1453         catch (...) {
1454             markupProcessor.insert("errorType", "Session Creation Service Error");
1455             markupProcessor.insert("errorText", "Unexpected Exception");
1456             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1457             return WriteClientError(lpECB, application, "shire", markupProcessor);
1458         }
1459 #endif
1460
1461         if (status->isError()) {
1462             if (status->isRetryable()) {
1463                 delete status;
1464                 const char* loc=shire.getAuthnRequest(elements.second);
1465                 if (!httpRedirects.first || httpRedirects.second) {
1466                     string hdrs=string("Location: ") + loc + "\r\n"
1467                         "Content-Type: text/html\r\n"
1468                         "Content-Length: 40\r\n"
1469                         "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1470                         "Cache-Control: private,no-store,no-cache\r\n\r\n";
1471                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1472                     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1473                     DWORD resplen=40;
1474                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1475                     return HSE_STATUS_SUCCESS;
1476                 }
1477                 else {
1478                     markupProcessor.insert("requestURL",loc);
1479                     return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1480                 }
1481             }
1482     
1483             // Return this error to the user.
1484             markupProcessor.insert(*status);
1485             delete status;
1486             return WriteClientError(lpECB,application,"shire",markupProcessor);
1487         }
1488         delete status;
1489     
1490         // We've got a good session, set the cookie and redirect to target.
1491         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1492             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1493             "Cache-Control: private,no-store,no-cache\r\n";
1494         if (!httpRedirects.first || httpRedirects.second) {
1495             cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1496             lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1497             static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1498             DWORD resplen=40;
1499             lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1500             return HSE_STATUS_SUCCESS;
1501         }
1502         else {
1503             markupProcessor.insert("requestURL",elements.second);
1504             return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1505         }
1506     }
1507     catch (ShibTargetException &e) {
1508         if (application) {
1509             ShibMLP markupProcessor;
1510             markupProcessor.insert("requestURL", targeturl.c_str());
1511             markupProcessor.insert("errorType", "Session Creation Service Error");
1512             markupProcessor.insert("errorText", e.what());
1513             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1514             return WriteClientError(lpECB,application,"shire",markupProcessor);
1515         }
1516     }
1517 #ifndef _DEBUG
1518     catch (...) {
1519         if (application) {
1520             ShibMLP markupProcessor;
1521             markupProcessor.insert("requestURL", targeturl.c_str());
1522             markupProcessor.insert("errorType", "Session Creation Service Error");
1523             markupProcessor.insert("errorText", "Unexpected Exception");
1524             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1525             return WriteClientError(lpECB,application,"shire",markupProcessor);
1526         }
1527     }
1528 #endif
1529
1530     return HSE_STATUS_ERROR;
1531 }
1532 #endif // 0