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