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