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