Added extensive logging, mapper now uses an internal lock, misc. bug fixes.
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
51
52    Scott Cantor
53    8/23/02
54 */
55
56 #include <windows.h>
57 #include <httpfilt.h>
58
59 // SAML Runtime
60 #include <saml.h>
61 #include <shib.h>
62 #include <eduPerson.h>
63
64 #include <log4cpp/Category.hh>
65 #include <log4cpp/PropertyConfigurator.hh>
66 #include <xercesc/util/Base64.hpp>
67
68 #include <ctime>
69 #include <strstream>
70 #include <stdexcept>
71
72 using namespace std;
73 using namespace log4cpp;
74 using namespace saml;
75 using namespace shibboleth;
76 using namespace eduPerson;
77
78 class CCacheEntry;
79 class CCache
80 {
81 public:
82     CCache();
83     ~CCache();
84
85     SAMLBinding* getBinding(const XMLCh* bindingProt);
86     CCacheEntry* find(const char* key);
87     void insert(const char* key, CCacheEntry* entry);
88     void remove(const char* key);
89     void sweep(time_t lifetime);
90
91     bool lock() { EnterCriticalSection(&m_lock); return true; }
92     void unlock() { LeaveCriticalSection(&m_lock); }
93
94 private:
95     SAMLBinding* m_SAMLBinding;
96     map<string,CCacheEntry*> m_hashtable;
97     CRITICAL_SECTION m_lock;
98 };
99
100 // Per-website global structure
101 struct settings_t
102 {
103     settings_t();
104     string g_CookieName;                    // name of authentication token
105     string g_WAYFLocation;                  // URL of WAYF service
106     string g_GarbageCollector;              // URL of cache garbage collection service
107     string g_SHIRELocation;                 // URL of SHIRE acceptance point
108     string g_SHIRESessionPath;              // path to storage for sessions
109     vector<string> g_MustContain;           // simple URL matching string array
110     bool g_bSSLOnly;                        // only over SSL?
111     time_t g_Lifetime;                      // maximum token lifetime
112     time_t g_Timeout;                       // maximum time between uses
113     bool g_bCheckAddress;                   // validate IP addresses?
114     bool g_bExportAssertion;                // export SAML assertion to header?
115     CCache g_AuthCache;                     // local auth cache
116 };
117
118 settings_t::settings_t()
119 {
120     g_bSSLOnly=true;
121     g_Lifetime=7200;
122     g_Timeout=3600;
123     g_bCheckAddress=true;
124     g_bExportAssertion=false;
125 }
126
127 class CCacheEntry
128 {
129 public:
130     CCacheEntry(const char* sessionFile);
131     ~CCacheEntry();
132
133     SAMLAuthorityBinding* getBinding() { return m_binding; }
134     Iterator<SAMLAttribute*> getAttributes(const char* resource_url, settings_t* pSite);
135     const XMLByte* getSerializedAssertion(const char* resource_url, settings_t* pSite);
136     bool isSessionValid(time_t lifetime, time_t timeout);
137     const XMLCh* getHandle() { return m_handle.c_str(); }
138     const XMLCh* getOriginSite() { return m_originSite.c_str(); }
139     const char* getClientAddress() { return m_clientAddress.c_str(); }
140
141 private:
142     void populate(const char* resource_url, settings_t* pSite);
143
144     xstring m_originSite;
145     xstring m_handle;
146     SAMLAuthorityBinding* m_binding;
147     string m_clientAddress;
148     SAMLResponse* m_response;
149     SAMLAssertion* m_assertion;
150     time_t m_sessionCreated;
151     time_t m_lastAccess;
152     XMLByte* m_serialized;
153
154     static saml::QName g_authorityKind;
155     static saml::QName g_respondWith;
156     friend class CCache;
157 };
158
159 // static members
160 saml::QName CCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery));
161 saml::QName CCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement));
162
163 CCache::CCache()
164 {
165     m_SAMLBinding=SAMLBindingFactory::getInstance();
166     InitializeCriticalSection(&m_lock);
167 }
168
169 CCache::~CCache()
170 {
171     DeleteCriticalSection(&m_lock);
172     delete m_SAMLBinding;
173     for (map<string,CCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
174         delete i->second;
175 }
176
177 SAMLBinding* CCache::getBinding(const XMLCh* bindingProt)
178 {
179     if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS))
180         return m_SAMLBinding;
181     return NULL;
182 }
183
184 CCacheEntry* CCache::find(const char* key)
185 {
186     map<string,CCacheEntry*>::const_iterator i=m_hashtable.find(key);
187     if (i==m_hashtable.end())
188         return NULL;
189     return i->second;
190 }
191
192 void CCache::insert(const char* key, CCacheEntry* entry)
193 {
194     m_hashtable[key]=entry;
195 }
196
197 void CCache::remove(const char* key)
198 {
199     m_hashtable.erase(key);
200 }
201
202 void CCache::sweep(time_t lifetime)
203 {
204     time_t now=time(NULL);
205     for (map<string,CCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end();)
206     {
207         if (lifetime > 0 && now > i->second->m_sessionCreated+lifetime)
208         {
209             delete i->second;
210             i=m_hashtable.erase(i);
211         }
212         else
213             i++;
214     }
215 }
216
217 CCacheEntry::CCacheEntry(const char* sessionFile)
218   : m_binding(NULL), m_assertion(NULL), m_response(NULL), m_lastAccess(0), m_sessionCreated(0), m_serialized(NULL)
219 {
220     FILE* f;
221     char line[1024];
222     const char* token = NULL;
223     char* w = NULL;
224     auto_ptr<XMLCh> binding,location;
225
226     if (!(f=fopen(sessionFile,"r")))
227     {
228         fprintf(stderr,"CCacheEntry() could not open session file: %s",sessionFile);
229         throw runtime_error("CCacheEntry() could not open session file");
230     }
231
232     while (fgets(line,1024,f))
233     {
234         if ((*line=='#') || (!*line))
235             continue;
236         token = line;
237         w=strchr(token,'=');
238         if (!w)
239             continue;
240         *w++=0;
241         if (w[strlen(w)-1]=='\n')
242             w[strlen(w)-1]=0;
243
244         if (!strcmp("Domain",token))
245         {
246                 auto_ptr<XMLCh> origin(XMLString::transcode(w));
247                 m_originSite=origin.get();
248         }
249         else if (!strcmp("Handle",token))
250         {
251                 auto_ptr<XMLCh> handle(XMLString::transcode(w));
252                 m_handle=handle.get();
253         }
254         else if (!strcmp("PBinding0",token))
255                 binding=auto_ptr<XMLCh>(XMLString::transcode(w));
256         else if (!strcmp("LBinding0",token))
257                 location=auto_ptr<XMLCh>(XMLString::transcode(w));
258         else if (!strcmp("Time",token))
259                 m_sessionCreated=atoi(w);
260         else if (!strcmp("ClientAddress",token))
261                 m_clientAddress=w;
262         else if (!strcmp("EOF",token))
263                 break;
264     }
265     fclose(f);
266     
267     if (binding.get()!=NULL && location.get()!=NULL)
268         m_binding=new SAMLAuthorityBinding(g_authorityKind,binding.get(),location.get());
269
270     m_lastAccess=time(NULL);
271     if (!m_sessionCreated)
272         m_sessionCreated=m_lastAccess;
273 }
274
275 CCacheEntry::~CCacheEntry()
276 {
277     delete m_binding;
278     delete m_response;
279     delete[] m_serialized;
280 }
281
282 bool CCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
283 {
284     time_t now=time(NULL);
285     if (lifetime > 0 && now > m_sessionCreated+lifetime)
286         return false;
287     if (timeout > 0 && now-m_lastAccess >= timeout)
288         return false;
289     m_lastAccess=now;
290     return true;
291 }
292
293 Iterator<SAMLAttribute*> CCacheEntry::getAttributes(const char* resource_url, settings_t* pSite)
294 {
295     populate(resource_url,pSite);
296     if (m_assertion)
297     {
298         Iterator<SAMLStatement*> i=m_assertion->getStatements();
299         if (i.hasNext())
300         {
301             SAMLAttributeStatement* s=dynamic_cast<SAMLAttributeStatement*>(i.next());
302             if (s)
303                 return s->getAttributes();
304         }
305     }
306     return Iterator<SAMLAttribute*>();
307 }
308
309 const XMLByte* CCacheEntry::getSerializedAssertion(const char* resource_url, settings_t* pSite)
310 {
311     populate(resource_url,pSite);
312     if (m_serialized)
313         return m_serialized;
314     if (!m_assertion)
315         return NULL;
316     ostrstream os;
317     os << *m_assertion;
318     unsigned int outlen;
319     return m_serialized=Base64::encode(reinterpret_cast<XMLByte*>(os.str()),os.pcount(),&outlen);
320 }
321
322 void CCacheEntry::populate(const char* resource_url, settings_t* pSite)
323 {
324 #undef FUNC
325 #define FUNC populate
326     Category& log=Category::getInstance("isapi_shib.CCacheEntry");
327
328     // Can we use what we have?
329     if (m_assertion && m_assertion->getNotOnOrAfter())
330     {
331         // This is awful, but the XMLDateTime class is truly horrible.
332         time_t now=time(NULL);
333         struct tm* ptime=gmtime(&now);
334         char timebuf[32];
335         strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
336         auto_ptr<XMLCh> timeptr(XMLString::transcode(timebuf));
337         XMLDateTime curDateTime(timeptr.get());
338         int result=XMLDateTime::compareOrder(&curDateTime,m_assertion->getNotOnOrAfter());
339         if (XMLDateTime::LESS_THAN)
340             return;
341
342         delete m_response;
343         delete[] m_serialized;
344         m_assertion=NULL;
345         m_response=NULL;
346         m_serialized=NULL;
347
348         log.info("%s: cached attributes have expired",FUNC);
349     }
350
351     if (!m_binding)
352         return;
353
354     auto_ptr<XMLCh> resource(XMLString::transcode(resource_url));
355
356     // Build a SAML Request and send it to the AA.
357     SAMLSubject* subject=new SAMLSubject(m_handle.c_str(),m_originSite.c_str());
358     SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resource.get());
359     SAMLRequest* req=new SAMLRequest(q,ArrayIterator<saml::QName>(&g_respondWith));
360     SAMLBinding* pBinding=pSite->g_AuthCache.getBinding(m_binding->getBinding());
361     m_response=pBinding->send(*m_binding,*req);
362     delete req;
363
364     // Store off the assertion for quick access. Memory mgmt is based on the response pointer.
365     Iterator<SAMLAssertion*> i=m_response->getAssertions();
366     if (i.hasNext())
367         m_assertion=i.next();
368
369     auto_ptr<char> h(XMLString::transcode(m_handle.c_str()));
370     auto_ptr<char> d(XMLString::transcode(m_originSite.c_str()));
371     log.info("%s: fetched and stored SAML response for %s@%s",FUNC,h.get(),d.get());
372 }
373
374 class DummyMapper : public IOriginSiteMapper
375 {
376 public:
377     DummyMapper() { InitializeCriticalSection(&m_lock); }
378     ~DummyMapper();
379     virtual Iterator<xstring> getHandleServiceNames(const XMLCh* originSite) { return Iterator<xstring>(); }
380     virtual Key* getHandleServiceKey(const XMLCh* handleService) { return NULL; }
381     virtual Iterator<xstring> getSecurityDomains(const XMLCh* originSite);
382     virtual Iterator<X509Certificate*> getTrustedRoots() { return Iterator<X509Certificate*>(); }
383
384 private:
385     typedef map<xstring,vector<xstring>*> domains_t;
386     domains_t m_domains;
387     CRITICAL_SECTION m_lock;
388 };
389
390 Iterator<xstring> DummyMapper::getSecurityDomains(const XMLCh* originSite)
391 {
392     EnterCriticalSection(&m_lock);
393     vector<xstring>* pv=NULL;
394     domains_t::iterator i=m_domains.find(originSite);
395     if (i==m_domains.end())
396     {
397         pv=new vector<xstring>();
398         pv->push_back(originSite);
399         pair<domains_t::iterator,bool> p=m_domains.insert(domains_t::value_type(originSite,pv));
400             i=p.first;
401     }
402     else
403         pv=i->second;
404     LeaveCriticalSection(&m_lock);
405     return Iterator<xstring>(*pv);
406 }
407
408 DummyMapper::~DummyMapper()
409 {
410     for (domains_t::iterator i=m_domains.begin(); i!=m_domains.end(); i++)
411         delete i->second;
412     DeleteCriticalSection(&m_lock);
413 }
414
415 // globals
416 HINSTANCE g_hinstDLL;
417 ULONG g_ulMaxSite=1;                        // max IIS site instance to handle
418 settings_t* g_Sites=NULL;                   // array of site settings
419 map<string,string> g_mapAttribNameToHeader; // attribute mapping
420 map<xstring,string> g_mapAttribNames;
421
422
423 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
424 {
425     if (fdwReason==DLL_PROCESS_ATTACH)
426         g_hinstDLL=hinstDLL;
427     return TRUE;
428 }
429
430 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
431 {
432     if (!pVer)
433         return FALSE;
434
435     // Get module pathname and replace file name with ini file name.
436     char inifile[MAX_PATH+1];
437     if (GetModuleFileName(g_hinstDLL,inifile,MAX_PATH+1)==0)
438         return FALSE;
439     char* pch=strrchr(inifile,'\\');
440     if (pch==NULL)
441         return FALSE;
442     pch++;
443     *pch=0;
444     strcat(inifile,"isapi_shib.ini");
445
446     // Read system-wide parameters from isapi_shib.ini.
447     char buf[1024];
448     char buf3[48];
449
450     try
451     {
452         SAMLConfig& SAMLconf=SAMLConfig::getConfig();
453         
454         GetPrivateProfileString("shibboleth","ShibLogConfig","",buf,sizeof(buf),inifile);
455         if (*buf)
456             PropertyConfigurator::configure(buf);
457         Category& log=Category::getInstance("isapi_shib.GetFilterVersion");
458         log.info("using INI file: %s",inifile);
459
460         GetPrivateProfileString("shibboleth","ShibSchemaPath","",buf,sizeof(buf),inifile);
461         if (!*buf)
462         {
463             log.fatal("ShibSchemaPath missing");
464             return FALSE;
465         }
466         SAMLconf.schema_dir=buf;
467         if (*SAMLconf.schema_dir.end()!='\\')
468             SAMLconf.schema_dir+='\\';
469
470         GetPrivateProfileString("shibboleth","ShibSSLCertFile","",buf,sizeof(buf),inifile);
471         if (!*buf)
472         {
473             log.fatal("ShibSSLCertFile missing");
474             return FALSE;
475         }
476         SAMLconf.ssl_certfile=buf;
477
478         GetPrivateProfileString("shibboleth","ShibSSLKeyFile","",buf,sizeof(buf),inifile);
479         if (!*buf)
480         {
481             log.fatal("ShibSSLKeyFile missing");
482             return FALSE;
483         }
484         SAMLconf.ssl_keyfile=buf;
485
486         GetPrivateProfileString("shibboleth","ShibSSLKeyPass","",buf,sizeof(buf),inifile);
487         SAMLconf.ssl_keypass=buf;
488
489         GetPrivateProfileString("shibboleth","ShibSSLCAList","",buf,sizeof(buf),inifile);
490         SAMLconf.ssl_calist=buf;
491
492         // Read site count and allocate site array.
493         g_ulMaxSite=GetPrivateProfileInt("shibboleth","max-site",0,inifile);
494         if (g_ulMaxSite==0)
495         {
496             log.fatal("max-site was 0 or invalid");
497             return FALSE;
498         }
499         log.debug("max-site is %d",g_ulMaxSite);
500         g_Sites=new settings_t[g_ulMaxSite];
501
502         // Read site-specific settings for each site.
503         for (ULONG i=0; i<g_ulMaxSite; i++)
504         {
505             ultoa(i+1,buf3,10);
506             GetPrivateProfileString(buf3,"ShibSiteName","X",buf,sizeof(buf),inifile);
507             if (!strcmp(buf,"X"))
508             {
509                 log.info("skipping site %d (no ShibSiteName)",i);
510                 continue;
511             }
512
513             GetPrivateProfileString(buf3,"ShibCookieName","",buf,sizeof(buf),inifile);
514             if (!*buf)
515             {
516                 delete[] g_Sites;
517                 log.fatal("ShibCookieName missing in site %d",i);
518                 return FALSE;
519             }
520             g_Sites[i].g_CookieName=buf;
521
522             GetPrivateProfileString(buf3,"WAYFLocation","",buf,sizeof(buf),inifile);
523             if (!*buf)
524             {
525                 delete[] g_Sites;
526                 log.fatal("WAYFLocation missing in site %d",i);
527                 return FALSE;
528             }
529             g_Sites[i].g_WAYFLocation=buf;
530
531             GetPrivateProfileString(buf3,"GarbageCollector","",buf,sizeof(buf),inifile);
532             if (!*buf)
533             {
534                 delete[] g_Sites;
535                 log.fatal("GarbageCollector missing in site %d",i);
536                 return FALSE;
537             }
538             g_Sites[i].g_GarbageCollector=buf;
539
540             GetPrivateProfileString(buf3,"SHIRELocation","",buf,sizeof(buf),inifile);
541             if (!*buf)
542             {
543                 delete[] g_Sites;
544                 log.fatal("SHIRELocation missing in site %d",i);
545                 return FALSE;
546             }
547             g_Sites[i].g_SHIRELocation=buf;
548
549             GetPrivateProfileString(buf3,"SHIRESessionPath","",buf,sizeof(buf),inifile);
550             if (!*buf)
551             {
552                 delete[] g_Sites;
553                 log.fatal("SHIRESessionPath missing in site %d",i);
554                 return FALSE;
555             }
556             g_Sites[i].g_SHIRESessionPath=buf;
557             if (g_Sites[i].g_SHIRESessionPath[g_Sites[i].g_SHIRESessionPath.length()]!='\\')
558                 g_Sites[i].g_SHIRESessionPath+='\\';
559
560             // Old-style matching string.
561             GetPrivateProfileString(buf3,"ShibMustContain","",buf,sizeof(buf),inifile);
562             _strupr(buf);
563             char* start=buf;
564             while (char* sep=strchr(start,';'))
565             {
566                 *sep='\0';
567                 if (*start)
568                 {
569                     g_Sites[i].g_MustContain.push_back(start);
570                     log.info("site %d told to match against %s",i,start);
571                 }
572                 start=sep+1;
573             }
574             if (*start)
575             {
576                 g_Sites[i].g_MustContain.push_back(start);
577                 log.info("site %d told to match against %s",i,start);
578             }
579             
580             if (GetPrivateProfileInt(buf3,"ShibSSLOnly",1,inifile)==0)
581                 g_Sites[i].g_bSSLOnly=false;
582             if (GetPrivateProfileInt(buf3,"ShibCheckAddress",1,inifile)==0)
583                 g_Sites[i].g_bCheckAddress=false;
584             if (GetPrivateProfileInt(buf3,"ShibExportAssertion",0,inifile)==1)
585                 g_Sites[i].g_bExportAssertion=true;
586             g_Sites[i].g_Lifetime=GetPrivateProfileInt(buf3,"ShibAuthLifetime",7200,inifile);
587             if (g_Sites[i].g_Lifetime<=0)
588                 g_Sites[i].g_Lifetime=7200;
589             g_Sites[i].g_Timeout=GetPrivateProfileInt(buf3,"ShibAuthTimeout",3600,inifile);
590             if (g_Sites[i].g_Timeout<=0)
591                 g_Sites[i].g_Timeout=3600;
592             log.info("configuration of site %d complete",i);
593         }
594
595         ShibConfig& Shibconf=ShibConfig::getConfig();
596         static DummyMapper mapper;
597
598         if (!SAMLconf.init())
599         {
600             delete[] g_Sites;
601             log.fatal("SAML initialization failed");
602             return FALSE;
603         }
604
605         Shibconf.origin_mapper=&mapper;
606         if (!Shibconf.init())
607         {
608             delete[] g_Sites;
609             log.fatal("Shibboleth initialization failed");
610             return FALSE;
611         }
612
613         char buf2[32767];
614         DWORD res=GetPrivateProfileSection("ShibMapAttributes",buf2,sizeof(buf2),inifile);
615         if (res==sizeof(buf2)-2)
616         {
617             delete[] g_Sites;
618             log.fatal("ShibMapAttributes INI section was larger than 32k");
619             return FALSE;
620         }
621
622         for (char* attr=buf2; *attr; attr++)
623         {
624             char* delim=strchr(attr,'=');
625             if (!delim)
626             {
627                 delete[] g_Sites;
628                 log.fatal("unrecognizable ShibMapAttributes directive: %s",attr);
629                 return FALSE;
630             }
631             *delim++=0;
632             g_mapAttribNameToHeader[attr]=(string(delim) + ':');
633             log.info("mapping attribute %s to request header %s",attr,delim);
634             attr=delim + strlen(delim);
635         }
636
637         log.info("configuration of attributes complete");
638
639         // Transcode the attribute names we know about for quick handling map access.
640         for (map<string,string>::const_iterator j=g_mapAttribNameToHeader.begin();
641              j!=g_mapAttribNameToHeader.end(); j++)
642         {
643             auto_ptr<XMLCh> temp(XMLString::transcode(j->first.c_str()));
644             g_mapAttribNames[temp.get()]=j->first;
645         }
646
647         res=GetPrivateProfileSection("ShibExtensions",buf2,sizeof(buf2),inifile);
648         if (res==sizeof(buf2)-2)
649         {
650             delete[] g_Sites;
651             log.fatal("ShibExtensions INI section was larger than 32k");
652             return FALSE;
653         }
654
655         for (char* libpath=buf2; *libpath; libpath+=strlen(libpath)+1)
656             SAMLconf.saml_register_extension(libpath);
657
658         log.info("completed loading of extension libraries");
659     }
660     catch (bad_alloc)
661     {
662         delete[] g_Sites;
663         Category::getInstance("isapi_shib.GetFilterVersion").fatal("out of memory");
664         return FALSE;
665     }
666     catch (log4cpp::ConfigureFailure& ex)
667     {
668         delete[] g_Sites;
669         WritePrivateProfileString("startlog","bailed-at","log4cpp exception caught",inifile);
670         WritePrivateProfileString("startlog","log4cpp",ex.what(),inifile);
671         return FALSE;
672     }
673     catch (SAMLException& ex)
674     {
675         delete[] g_Sites;
676         Category::getInstance("isapi_shib.GetFilterVersion").fatal("caught SAML exception: %s",ex.what());
677         return FALSE;
678     }
679
680     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
681     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
682     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
683                    SF_NOTIFY_SECURE_PORT |
684                    SF_NOTIFY_NONSECURE_PORT |
685                    SF_NOTIFY_PREPROC_HEADERS |
686                    SF_NOTIFY_LOG);
687     return TRUE;
688 }
689
690 extern "C" BOOL WINAPI TerminateFilter(DWORD dwFlags)
691 {
692     Category::getInstance("isapi_shib.TerminateFilter").info("shutting down...");
693     delete[] g_Sites;
694     g_Sites=NULL;
695     ShibConfig::getConfig().term();
696     SAMLConfig::getConfig().term();
697     Category::getInstance("isapi_shib.TerminateFilter").info("shut down complete");
698     return TRUE;
699 }
700
701 /* Next up, some suck-free versions of various APIs.
702
703    You DON'T require people to guess the buffer size and THEN tell them the right size.
704    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
705    constant strings aren't typed as such, making it just that much harder. These versions
706    are now updated to use a special growable buffer object, modeled after the standard
707    string class. The standard string won't work because they left out the option to
708    pre-allocate a non-constant buffer.
709 */
710
711 class dynabuf
712 {
713 public:
714     dynabuf() { bufptr=NULL; buflen=0; }
715     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
716     ~dynabuf() { delete[] bufptr; }
717     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
718     size_t size() const { return buflen; }
719     bool empty() const { return length()==0; }
720     void reserve(size_t s, bool keep=false);
721     void erase() { if (bufptr) *bufptr=0; }
722     operator char*() { return bufptr; }
723     bool operator ==(const char* s) const;
724     bool operator !=(const char* s) const { return !(*this==s); }
725 private:
726     char* bufptr;
727     size_t buflen;
728 };
729
730 void dynabuf::reserve(size_t s, bool keep)
731 {
732     if (s<=buflen)
733         return;
734     char* p=new char[s];
735     if (keep)
736         while (buflen--)
737             p[buflen]=bufptr[buflen];
738     buflen=s;
739     delete[] bufptr;
740     bufptr=p;
741 }
742
743 bool dynabuf::operator==(const char* s) const
744 {
745     if (buflen==NULL || s==NULL)
746         return (buflen==NULL && s==NULL);
747     else
748         return strcmp(bufptr,s)==0;
749 }
750
751 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc,
752                        LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
753     throw (bad_alloc, DWORD)
754 {
755     s.erase();
756     s.reserve(size);
757     size=s.size();
758
759     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
760     {
761         // Grumble. Check the error.
762         DWORD e=GetLastError();
763         if (e==ERROR_INSUFFICIENT_BUFFER)
764             s.reserve(size);
765         else
766             break;
767     }
768     if (bRequired && s.empty())
769         throw ERROR_NO_DATA;
770 }
771
772 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
773                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
774     throw (bad_alloc, DWORD)
775 {
776     s.erase();
777     s.reserve(size);
778     size=s.size();
779
780     while (!pn->GetHeader(pfc,lpszName,s,&size))
781     {
782         // Grumble. Check the error.
783         DWORD e=GetLastError();
784         if (e==ERROR_INSUFFICIENT_BUFFER)
785             s.reserve(size);
786         else
787             break;
788     }
789     if (bRequired && s.empty())
790         throw ERROR_NO_DATA;
791 }
792
793 inline char hexchar(unsigned short s)
794 {
795     return (s<=9) ? ('0' + s) : ('A' + s - 10);
796 }
797
798 string url_encode(const char* url) throw (bad_alloc)
799 {
800     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
801     string s;
802     for (const char* pch=url; *pch; pch++)
803     {
804         if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
805             s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
806         else
807             s+=*pch;
808     }
809     return s;
810 }
811
812 string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t* pSite)
813 {
814     // Reconstructing the requested URL is not fun. Apparently, the PREPROC_HEADERS
815     // event means way pre. As in, none of the usual CGI headers are in place yet.
816     // It's actually almost easier, in a way, because all the path-info and query
817     // stuff is in one place, the requested URL, which we can get. But we have to
818     // reconstruct the protocol/host pair using tweezers.
819     string s;
820     if (pfc->fIsSecurePort)
821         s="https://";
822     else
823         s="http://";
824
825     dynabuf buf(256);
826     GetServerVariable(pfc,"SERVER_NAME",buf);
827     s+=buf;
828
829     GetServerVariable(pfc,"SERVER_PORT",buf,10);
830     if (buf!=(pfc->fIsSecurePort ? "443" : "80"))
831         s=s + ':' + static_cast<char*>(buf);
832
833     GetHeader(pn,pfc,"url",buf,256,false);
834     s+=buf;
835
836     return s;
837 }
838
839 string get_shire_location(PHTTP_FILTER_CONTEXT pfc, settings_t* pSite, const char* target)
840 {
841     if (pSite->g_SHIRELocation[0]!='/')
842         return url_encode(pSite->g_SHIRELocation.c_str());
843     const char* colon=strchr(target,':');
844     const char* slash=strchr(colon+3,'/');
845     string s(target,slash-target);
846     s+=pSite->g_SHIRELocation;
847     return url_encode(s.c_str());
848 }
849
850 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
851 {
852     Category::getInstance("isapi_shib.WriteClientError").error("sending error page to browser: %s",msg);
853
854     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
855     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
856                             "<H1>Shibboleth Filter Error</H1>";
857     DWORD resplen=strlen(xmsg);
858     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
859     resplen=strlen(msg);
860     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
861     static const char* xmsg2="</BODY></HTML>";
862     resplen=strlen(xmsg2);
863     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
864     return SF_STATUS_REQ_FINISHED;
865 }
866
867 DWORD shib_shar_error(PHTTP_FILTER_CONTEXT pfc, SAMLException& e)
868 {
869     Category::getInstance("isapi_shib.shib_shar_error").errorStream()
870         << "exception during SHAR request: " << e;
871
872     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
873     
874     static const char* msg="<HTML><HEAD><TITLE>Shibboleth Attribute Exchange Failed</TITLE></HEAD>\n"
875                            "<BODY><H3>Shibboleth Attribute Exchange Failed</H3>\n"
876                            "While attempting to securely contact your origin site to obtain "
877                            "information about you, an error occurred:<BR><BLOCKQUOTE>";
878     DWORD resplen=strlen(msg);
879     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
880
881     const char* msg2=e.what();
882     resplen=strlen(msg2);
883     pfc->WriteClient(pfc,(LPVOID)msg2,&resplen,0);
884
885     bool origin=true;
886     Iterator<saml::QName> i=e.getCodes();
887     if (i.hasNext() && XMLString::compareString(L(Responder),i.next().getLocalName()))
888         origin=false;
889
890     const char* msg4=(origin ? "</BLOCKQUOTE><P>The error appears to be located at your origin site.<BR>" :
891                                "</BLOCKQUOTE><P>The error appears to be located at the resource provider's site.<BR>");
892     resplen=strlen(msg4);
893     pfc->WriteClient(pfc,(LPVOID)msg4,&resplen,0);
894     
895     static const char* msg5="<P>Try restarting your browser and accessing the site again to make "
896                             "sure the problem isn't temporary. Please contact the administrator "
897                             "of that site if this problem recurs. If possible, provide him/her "
898                             "with the error message shown above.</BODY></HTML>";
899     resplen=strlen(msg5);
900     pfc->WriteClient(pfc,(LPVOID)msg5,&resplen,0);
901     return SF_STATUS_REQ_FINISHED;
902 }
903
904 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
905 {
906     // Is this a log notification?
907     if (notificationType==SF_NOTIFY_LOG)
908     {
909         if (pfc->pFilterContext)
910             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
911         return SF_STATUS_REQ_NEXT_NOTIFICATION;
912     }
913
914     char* xmsg=NULL;
915     settings_t* pSite=NULL;
916     bool bLocked=false;
917     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
918     Category& log=Category::getInstance("isapi_shib.HttpFilterProc");
919     try
920     {
921         // Determine web site number.
922         dynabuf buf(128);
923         ULONG site_id=0;
924         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
925         if ((site_id=strtoul(buf,NULL,10))==0)
926             return WriteClientError(pfc,"IIS site instance appears to be invalid.");
927
928         // Match site instance to site settings pointer.
929         if (site_id>g_ulMaxSite || g_Sites[site_id-1].g_CookieName.empty())
930             return SF_STATUS_REQ_NEXT_NOTIFICATION;
931         pSite=&g_Sites[site_id-1];
932
933         string targeturl=get_target(pfc,pn,pSite);
934
935         // If the user is accessing the SHIRE acceptance point, pass on.
936         if (targeturl.find(pSite->g_SHIRELocation)!=string::npos)
937         {
938             log.debug("passing on SHIRE acceptance request");
939             return SF_STATUS_REQ_NEXT_NOTIFICATION;
940         }
941
942         // If this is the garbage collection service, do a cache sweep.
943         if (targeturl==pSite->g_GarbageCollector)
944         {
945             log.notice("garbage collector triggered");
946             pSite->g_AuthCache.lock();
947             bLocked=true;
948             pSite->g_AuthCache.sweep(pSite->g_Lifetime);
949             pSite->g_AuthCache.unlock();
950             bLocked=false;
951             return WriteClientError(pfc,"The cache was swept for expired sessions.");
952         }
953
954         // Get the url request and scan for the must-contain string.
955         if (!pSite->g_MustContain.empty())
956         {
957             char* upcased=new char[targeturl.length()+1];
958             strcpy(upcased,targeturl.c_str());
959             _strupr(upcased);
960             for (vector<string>::const_iterator index=pSite->g_MustContain.begin(); index!=pSite->g_MustContain.end(); index++)
961                 if (strstr(upcased,index->c_str()))
962                     break;
963             delete[] upcased;
964             if (index==pSite->g_MustContain.end())
965                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
966         }
967
968         // SSL check.
969         if (pSite->g_bSSLOnly && !pfc->fIsSecurePort)
970         {
971             log.warn("blocking non-SSL request");
972             xmsg="<HTML><HEAD><TITLE>Access Denied</TITLE></HEAD><BODY>"
973                  "<H1>Access Denied</H1>"
974                  "This server is configured to deny non-SSL requests for secure resources. "
975                  "Try your request again using https instead of http."
976                  "</BODY></HTML>";
977             DWORD resplen=strlen(xmsg);
978             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
979             pfc->WriteClient(pfc,xmsg,&resplen,0);
980             return SF_STATUS_REQ_FINISHED;
981         }
982
983         // Check for authentication cookie.
984         const char* session_id=NULL;
985         GetHeader(pn,pfc,"Cookie:",buf,128,false);
986         if (buf.empty() || !(session_id=strstr(buf,pSite->g_CookieName.c_str())) ||
987             *(session_id+pSite->g_CookieName.length())!='=')
988         {
989             log.info("session cookie not found, redirecting to WAYF");
990
991             // Redirect to WAYF.
992             string wayf("Location: ");
993             wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
994                                           "&target=" + url_encode(targeturl.c_str()) + "\r\n";
995             // Insert the headers.
996             pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
997             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
998             return SF_STATUS_REQ_FINISHED;
999         }
1000
1001         session_id+=pSite->g_CookieName.length() + 1;   /* Skip over the '=' */
1002         char* cookieend=strchr(session_id,';');
1003         if (cookieend)
1004             *cookieend = '\0';  /* Ignore anyting after a ; */
1005   
1006         pSite->g_AuthCache.lock();    // ---> Get cache lock
1007         bLocked=true;
1008
1009         // The caching logic is the heart of the "SHAR".
1010         CCacheEntry* entry=pSite->g_AuthCache.find(session_id);
1011         try
1012         {
1013             if (!entry)
1014             {
1015                 pSite->g_AuthCache.unlock();    // ---> Release cache lock
1016                 bLocked=false;
1017
1018                 // Construct the path to the session file
1019                 string sessionFile=pSite->g_SHIRESessionPath + session_id;
1020                 try
1021                 {
1022                     entry=new CCacheEntry(sessionFile.c_str());
1023                 }
1024                 catch (runtime_error e)
1025                 {
1026                     log.info("unable to load session from file '%s', redirecting to WAYF",sessionFile.c_str());
1027
1028                     // Redirect to WAYF.
1029                     string wayf("Location: ");
1030                     wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
1031                                                   "&target=" + url_encode(targeturl.c_str()) + "\r\n";
1032                     wayf+="Set-Cookie: " + pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n";
1033
1034                     // Insert the headers.
1035                     pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
1036                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
1037                     return SF_STATUS_REQ_FINISHED;
1038                 }
1039                 pSite->g_AuthCache.lock();    // ---> Get cache lock
1040                 bLocked=true;
1041                 pSite->g_AuthCache.insert(session_id,entry);
1042                 log.info("new session established: %s",session_id);
1043             }
1044             
1045             if (!entry->isSessionValid(pSite->g_Lifetime,pSite->g_Timeout))
1046             {
1047                 pSite->g_AuthCache.remove(session_id);
1048                 pSite->g_AuthCache.unlock();    // ---> Release cache lock
1049                 bLocked=false;
1050                 delete entry;
1051
1052                 log.warn("invalidating session because of timeout, redirecting to WAYF");
1053
1054                 // Redirect to WAYF.
1055                 string wayf("Location: ");
1056                 wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
1057                                               "&target=" + url_encode(targeturl.c_str()) + "\r\n";
1058                 wayf+="Set-Cookie: " + pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n";
1059
1060                 // Insert the headers.
1061                 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
1062                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
1063                 return SF_STATUS_REQ_FINISHED;
1064             }
1065
1066             if (pSite->g_bCheckAddress && entry->getClientAddress())
1067             {
1068                 GetServerVariable(pfc,"REMOTE_ADDR",buf,16);
1069                 if (strcmp(entry->getClientAddress(),buf))
1070                 {
1071                     pSite->g_AuthCache.remove(session_id);
1072                     delete entry;
1073                     pSite->g_AuthCache.unlock();  // ---> Release cache lock
1074                     bLocked=false;
1075
1076                     log.warn("IP address mismatch detected, clearing session");
1077
1078                     string clearcookie("Set-Cookie: ");
1079                     clearcookie+=pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n";
1080                     pfc->AddResponseHeaders(pfc,const_cast<char*>(clearcookie.c_str()),0);
1081                     return WriteClientError(pfc,
1082                         "Your session was terminated because the network address associated "
1083                         "with it does not match your current address. This is usually caused "
1084                         "by a firewall or proxy of some sort.");
1085                 }
1086             }
1087
1088             // Clear relevant headers.
1089             pn->SetHeader(pfc,"Shib-Attributes:","");
1090             pn->SetHeader(pfc,"remote-user:","");
1091             for (map<string,string>::const_iterator h_iter=g_mapAttribNameToHeader.begin(); h_iter!=g_mapAttribNameToHeader.end(); h_iter++)
1092                 if (h_iter->second!="REMOTE_USER:")
1093                     pn->SetHeader(pfc,const_cast<char*>(h_iter->second.c_str()),"");
1094
1095             if (pSite->g_bExportAssertion)
1096             {
1097                 string exp((char*)entry->getSerializedAssertion(targeturl.c_str(),pSite));
1098                 string::size_type lfeed;
1099                 while ((lfeed=exp.find('\n'))!=string::npos)
1100                     exp.erase(lfeed,1);
1101                 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(exp.c_str()));
1102             }
1103             Iterator<SAMLAttribute*> i=entry->getAttributes(targeturl.c_str(),pSite);
1104             
1105             while (i.hasNext())
1106             {
1107                 SAMLAttribute* attr=i.next();
1108
1109                 // Are we supposed to export it?
1110                 map<xstring,string>::const_iterator iname=g_mapAttribNames.find(attr->getName());
1111                 if (iname!=g_mapAttribNames.end())
1112                 {
1113                     string hname=g_mapAttribNameToHeader[iname->second];
1114                     Iterator<string> vals=attr->getSingleByteValues();
1115                     if (hname=="REMOTE_USER:" && vals.hasNext())
1116                     {
1117                         char* principal=const_cast<char*>(vals.next().c_str());
1118                         pn->SetHeader(pfc,"remote-user:",principal);
1119                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
1120                         if (pfc->pFilterContext)
1121                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
1122                     }   
1123                     else
1124                     {
1125                         string header(" ");
1126                         while (vals.hasNext())
1127                             header+=vals.next() + " ";
1128                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
1129                     }
1130                 }
1131             }
1132
1133             pSite->g_AuthCache.unlock();  // ---> Release cache lock
1134             bLocked=false;
1135             return SF_STATUS_REQ_NEXT_NOTIFICATION;
1136         }
1137         catch (SAMLException& e)
1138         {
1139             Iterator<saml::QName> i=e.getCodes();
1140             int c=0;
1141             while (i.hasNext())
1142             {
1143                     c++;
1144                     saml::QName q=i.next();
1145                     if (c==1 && !XMLString::compareString(q.getNamespaceURI(),saml::XML::SAMLP_NS) &&
1146                     !XMLString::compareString(q.getLocalName(),L(Requester)))
1147                     continue;
1148                 else if (c==2 && !XMLString::compareString(q.getNamespaceURI(),shibboleth::XML::SHIB_NS) &&
1149                          !XMLString::compareString(q.getLocalName(),shibboleth::XML::Literals::InvalidHandle))
1150                 {
1151                     if (!bLocked)
1152                         pSite->g_AuthCache.lock();  // ---> Grab cache lock
1153                     pSite->g_AuthCache.remove(session_id);
1154                     pSite->g_AuthCache.unlock();  // ---> Release cache lock
1155                     delete entry;
1156
1157                     log.info("invaliding session due to shib:InvalidHandle code from AA");
1158
1159                     // Redirect to WAYF.
1160                     string wayf("Location: ");
1161                     wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
1162                                                   "&target=" + url_encode(targeturl.c_str()) + "\r\n";
1163                     wayf+="Set-Cookie: " + pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n";
1164
1165                     // Insert the headers.
1166                     pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
1167                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
1168                     return SF_STATUS_REQ_FINISHED;
1169                 }
1170                 break;
1171             }
1172             if (bLocked)
1173                 pSite->g_AuthCache.unlock();
1174                 return shib_shar_error(pfc,e);
1175         }
1176         catch (XMLException& e)
1177         {
1178             if (bLocked)
1179                 pSite->g_AuthCache.unlock();
1180             auto_ptr<char> msg(XMLString::transcode(e.getMessage()));
1181             SAMLException ex(SAMLException::RESPONDER,msg.get());
1182             return shib_shar_error(pfc,ex);
1183         }
1184     }
1185     catch(bad_alloc)
1186     {
1187         xmsg="Out of memory.";
1188         log.error("out of memory");
1189     }
1190     catch(DWORD e)
1191     {
1192         if (e==ERROR_NO_DATA)
1193             xmsg="A required variable or header was empty.";
1194         else
1195             xmsg="Server detected unexpected IIS error.";
1196     }
1197     catch(...)
1198     {
1199         xmsg="Server caught an unknown exception.";
1200     }
1201
1202     // If we drop here, the exception handler set the proper message.
1203     if (bLocked)
1204         pSite->g_AuthCache.unlock();
1205     return WriteClientError(pfc,xmsg);
1206 }