2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
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.
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
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.
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.
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
62 #include <eduPerson.h>
64 #include <xercesc/util/Base64.hpp>
71 using namespace shibboleth;
72 using namespace eduPerson;
81 SAMLBinding* getBinding(const XMLCh* bindingProt);
82 CCacheEntry* find(const char* key);
83 void insert(const char* key, CCacheEntry* entry);
84 void remove(const char* key);
85 void sweep(time_t lifetime);
87 bool lock() { EnterCriticalSection(&m_lock); return true; }
88 void unlock() { LeaveCriticalSection(&m_lock); }
91 SAMLBinding* m_SAMLBinding;
92 map<string,CCacheEntry*> m_hashtable;
93 CRITICAL_SECTION m_lock;
96 // Per-website global structure
100 string g_CookieName; // name of authentication token
101 string g_WAYFLocation; // URL of WAYF service
102 string g_GarbageCollector; // URL of cache garbage collection service
103 string g_SHIRELocation; // URL of SHIRE acceptance point
104 string g_SHIRESessionPath; // path to storage for sessions
105 vector<string> g_MustContain; // simple URL matching string array
106 bool g_bSSLOnly; // only over SSL?
107 time_t g_Lifetime; // maximum token lifetime
108 time_t g_Timeout; // maximum time between uses
109 bool g_bCheckAddress; // validate IP addresses?
110 bool g_bExportAssertion; // export SAML assertion to header?
111 CCache g_AuthCache; // local auth cache
114 settings_t::settings_t()
119 g_bCheckAddress=true;
120 g_bExportAssertion=false;
126 CCacheEntry(const char* sessionFile);
129 SAMLAuthorityBinding* getBinding() { return m_binding; }
130 Iterator<SAMLAttribute*> getAttributes(const char* resource_url, settings_t* pSite);
131 const XMLByte* getSerializedAssertion(const char* resource_url, settings_t* pSite);
132 bool isSessionValid(time_t lifetime, time_t timeout);
133 const XMLCh* getHandle() { return m_handle.c_str(); }
134 const XMLCh* getOriginSite() { return m_originSite.c_str(); }
135 const char* getClientAddress() { return m_clientAddress.c_str(); }
138 void populate(const char* resource_url, settings_t* pSite);
140 xstring m_originSite;
142 SAMLAuthorityBinding* m_binding;
143 string m_clientAddress;
144 SAMLResponse* m_response;
145 SAMLAssertion* m_assertion;
146 time_t m_sessionCreated;
148 XMLByte* m_serialized;
150 static saml::QName g_authorityKind;
151 static saml::QName g_respondWith;
156 saml::QName CCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery));
157 saml::QName CCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement));
161 m_SAMLBinding=SAMLBindingFactory::getInstance();
162 InitializeCriticalSection(&m_lock);
167 DeleteCriticalSection(&m_lock);
168 delete m_SAMLBinding;
169 for (map<string,CCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
173 SAMLBinding* CCache::getBinding(const XMLCh* bindingProt)
175 if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS))
176 return m_SAMLBinding;
180 CCacheEntry* CCache::find(const char* key)
182 map<string,CCacheEntry*>::const_iterator i=m_hashtable.find(key);
183 if (i==m_hashtable.end())
188 void CCache::insert(const char* key, CCacheEntry* entry)
190 m_hashtable[key]=entry;
193 void CCache::remove(const char* key)
195 m_hashtable.erase(key);
198 void CCache::sweep(time_t lifetime)
200 time_t now=time(NULL);
201 for (map<string,CCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end();)
203 if (lifetime > 0 && now > i->second->m_sessionCreated+lifetime)
206 i=m_hashtable.erase(i);
213 CCacheEntry::CCacheEntry(const char* sessionFile)
214 : m_binding(NULL), m_assertion(NULL), m_response(NULL), m_lastAccess(0), m_sessionCreated(0), m_serialized(NULL)
218 const char* token = NULL;
220 auto_ptr<XMLCh> binding,location;
222 if (!(f=fopen(sessionFile,"r")))
224 fprintf(stderr,"CCacheEntry() could not open session file: %s",sessionFile);
225 throw runtime_error("CCacheEntry() could not open session file");
228 while (fgets(line,1024,f))
230 if ((*line=='#') || (!*line))
237 if (w[strlen(w)-1]=='\n')
240 if (!strcmp("Domain",token))
242 auto_ptr<XMLCh> origin(XMLString::transcode(w));
243 m_originSite=origin.get();
245 else if (!strcmp("Handle",token))
247 auto_ptr<XMLCh> handle(XMLString::transcode(w));
248 m_handle=handle.get();
250 else if (!strcmp("PBinding0",token))
251 binding=auto_ptr<XMLCh>(XMLString::transcode(w));
252 else if (!strcmp("LBinding0",token))
253 location=auto_ptr<XMLCh>(XMLString::transcode(w));
254 else if (!strcmp("Time",token))
255 m_sessionCreated=atoi(w);
256 else if (!strcmp("ClientAddress",token))
258 else if (!strcmp("EOF",token))
263 if (binding.get()!=NULL && location.get()!=NULL)
264 m_binding=new SAMLAuthorityBinding(g_authorityKind,binding.get(),location.get());
266 m_lastAccess=time(NULL);
267 if (!m_sessionCreated)
268 m_sessionCreated=m_lastAccess;
271 CCacheEntry::~CCacheEntry()
275 delete[] m_serialized;
278 bool CCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
280 time_t now=time(NULL);
281 if (lifetime > 0 && now > m_sessionCreated+lifetime)
283 if (timeout > 0 && now-m_lastAccess >= timeout)
289 Iterator<SAMLAttribute*> CCacheEntry::getAttributes(const char* resource_url, settings_t* pSite)
291 populate(resource_url,pSite);
294 Iterator<SAMLStatement*> i=m_assertion->getStatements();
297 SAMLAttributeStatement* s=dynamic_cast<SAMLAttributeStatement*>(i.next());
299 return s->getAttributes();
302 return Iterator<SAMLAttribute*>();
305 const XMLByte* CCacheEntry::getSerializedAssertion(const char* resource_url, settings_t* pSite)
307 populate(resource_url,pSite);
315 return m_serialized=Base64::encode(reinterpret_cast<XMLByte*>(os.str()),os.pcount(),&outlen);
318 void CCacheEntry::populate(const char* resource_url, settings_t* pSite)
320 // Can we use what we have?
321 if (m_assertion && m_assertion->getNotOnOrAfter())
323 // This is awful, but the XMLDateTime class is truly horrible.
324 time_t now=time(NULL);
325 struct tm* ptime=gmtime(&now);
327 strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
328 auto_ptr<XMLCh> timeptr(XMLString::transcode(timebuf));
329 XMLDateTime curDateTime(timeptr.get());
330 int result=XMLDateTime::compareOrder(&curDateTime,m_assertion->getNotOnOrAfter());
331 if (XMLDateTime::LESS_THAN)
335 delete[] m_serialized;
344 auto_ptr<XMLCh> resource(XMLString::transcode(resource_url));
346 // Build a SAML Request and send it to the AA.
347 SAMLSubject* subject=new SAMLSubject(m_handle.c_str(),m_originSite.c_str());
348 SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resource.get());
349 SAMLRequest* req=new SAMLRequest(q,ArrayIterator<saml::QName>(&g_respondWith));
350 SAMLBinding* pBinding=pSite->g_AuthCache.getBinding(m_binding->getBinding());
351 m_response=pBinding->send(*m_binding,*req);
354 // Store off the assertion for quick access. Memory mgmt is based on the response pointer.
355 Iterator<SAMLAssertion*> i=m_response->getAssertions();
357 m_assertion=i.next();
359 auto_ptr<char> h(XMLString::transcode(m_handle.c_str()));
360 auto_ptr<char> d(XMLString::transcode(m_originSite.c_str()));
361 fprintf(stderr,"CCacheEntry::populate() fetched and stored SAML response for %s@%s\n",h.get(),d.get());
364 class DummyMapper : public IOriginSiteMapper
369 virtual Iterator<xstring> getHandleServiceNames(const XMLCh* originSite) { return Iterator<xstring>(); }
370 virtual Key* getHandleServiceKey(const XMLCh* handleService) { return NULL; }
371 virtual Iterator<xstring> getSecurityDomains(const XMLCh* originSite);
372 virtual Iterator<X509Certificate*> getTrustedRoots() { return Iterator<X509Certificate*>(); }
375 typedef map<xstring,vector<xstring>*> domains_t;
379 Iterator<xstring> DummyMapper::getSecurityDomains(const XMLCh* originSite)
381 SAMLConfig::getConfig()->saml_lock();
382 vector<xstring>* pv=NULL;
383 domains_t::iterator i=m_domains.find(originSite);
384 if (i==m_domains.end())
386 pv=new vector<xstring>();
387 pv->push_back(originSite);
388 pair<domains_t::iterator,bool> p=m_domains.insert(domains_t::value_type(originSite,pv));
393 SAMLConfig::getConfig()->saml_unlock();
394 return Iterator<xstring>(*pv);
397 DummyMapper::~DummyMapper()
399 for (domains_t::iterator i=m_domains.begin(); i!=m_domains.end(); i++)
404 HINSTANCE g_hinstDLL;
405 ULONG g_ulMaxSite=1; // max IIS site instance to handle
406 settings_t* g_Sites=NULL; // array of site settings
407 string g_SchemaPath; // location of XML schemas
408 string g_SSLCertFile; // PKI components for SHAR
412 map<string,string> g_mapAttribNameToHeader; // attribute mapping
413 map<xstring,string> g_mapAttribNames;
416 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
418 if (fdwReason==DLL_PROCESS_ATTACH)
423 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
428 // Get module pathname and replace file name with ini file name.
429 char inifile[MAX_PATH+1];
430 if (GetModuleFileName(g_hinstDLL,inifile,MAX_PATH+1)==0)
432 char* pch=strrchr(inifile,'\\');
437 strcat(inifile,"isapi_shib.ini");
439 // Read system-wide parameters from isapi_shib.ini.
445 GetPrivateProfileString("shibboleth","ShibSchemaPath","",buf,sizeof(buf),inifile);
448 WritePrivateProfileString("startlog","bailed-at","ShibSchemaPath",inifile);
452 if (*g_SchemaPath.end()!='\\')
455 GetPrivateProfileString("shibboleth","ShibSSLCertFile","",buf,sizeof(buf),inifile);
458 WritePrivateProfileString("startlog","bailed-at","ShibSSLCertFile",inifile);
463 GetPrivateProfileString("shibboleth","ShibSSLKeyFile","",buf,sizeof(buf),inifile);
466 WritePrivateProfileString("startlog","bailed-at","ShibSSLKeyFile",inifile);
471 GetPrivateProfileString("shibboleth","ShibSSLKeyPass","",buf,sizeof(buf),inifile);
474 GetPrivateProfileString("shibboleth","ShibSSLCAList","",buf,sizeof(buf),inifile);
477 // Read site count and allocate site array.
478 g_ulMaxSite=GetPrivateProfileInt("shibboleth","max-site",0,inifile);
481 WritePrivateProfileString("startlog","bailed-at","max-site check",inifile);
484 g_Sites=new settings_t[g_ulMaxSite];
486 // Read site-specific settings for each site.
487 for (ULONG i=0; i<g_ulMaxSite; i++)
490 GetPrivateProfileString(buf3,"ShibSiteName","X",buf,sizeof(buf),inifile);
491 if (!strcmp(buf,"X"))
494 GetPrivateProfileString(buf3,"ShibCookieName","",buf,sizeof(buf),inifile);
498 WritePrivateProfileString("startlog","bailed-at","ShibCookieName",inifile);
501 g_Sites[i].g_CookieName=buf;
503 GetPrivateProfileString(buf3,"WAYFLocation","",buf,sizeof(buf),inifile);
507 WritePrivateProfileString("startlog","bailed-at","WAYFLocation",inifile);
510 g_Sites[i].g_WAYFLocation=buf;
512 GetPrivateProfileString(buf3,"GarbageCollector","",buf,sizeof(buf),inifile);
516 WritePrivateProfileString("startlog","bailed-at","GarbageCollector",inifile);
519 g_Sites[i].g_GarbageCollector=buf;
521 GetPrivateProfileString(buf3,"SHIRELocation","",buf,sizeof(buf),inifile);
525 WritePrivateProfileString("startlog","bailed-at","SHIRELocation",inifile);
528 g_Sites[i].g_SHIRELocation=buf;
530 GetPrivateProfileString(buf3,"SHIRESessionPath","",buf,sizeof(buf),inifile);
534 WritePrivateProfileString("startlog","bailed-at","SHIRESessionPath",inifile);
537 g_Sites[i].g_SHIRESessionPath=buf;
538 if (g_Sites[i].g_SHIRESessionPath[g_Sites[i].g_SHIRESessionPath.length()]!='\\')
539 g_Sites[i].g_SHIRESessionPath+='\\';
541 // Old-style matching string.
542 GetPrivateProfileString(buf3,"ShibMustContain","",buf,sizeof(buf),inifile);
545 while (char* sep=strchr(start,';'))
549 g_Sites[i].g_MustContain.push_back(start);
553 g_Sites[i].g_MustContain.push_back(start);
555 if (GetPrivateProfileInt(buf3,"ShibSSLOnly",1,inifile)==0)
556 g_Sites[i].g_bSSLOnly=false;
557 if (GetPrivateProfileInt(buf3,"ShibCheckAddress",1,inifile)==0)
558 g_Sites[i].g_bCheckAddress=false;
559 if (GetPrivateProfileInt(buf3,"ShibExportAssertion",0,inifile)==1)
560 g_Sites[i].g_bExportAssertion=true;
561 g_Sites[i].g_Lifetime=GetPrivateProfileInt(buf3,"ShibAuthLifetime",7200,inifile);
562 if (g_Sites[i].g_Lifetime<=0)
563 g_Sites[i].g_Lifetime=7200;
564 g_Sites[i].g_Timeout=GetPrivateProfileInt(buf3,"ShibAuthTimeout",3600,inifile);
565 if (g_Sites[i].g_Timeout<=0)
566 g_Sites[i].g_Timeout=3600;
567 WritePrivateProfileString("startlog","site-complete",buf3,inifile);
570 static SAMLConfig SAMLconf;
571 static ShibConfig Shibconf;
572 static DummyMapper mapper;
574 SAMLconf.schema_dir=g_SchemaPath;
575 SAMLconf.ssl_certfile=g_SSLCertFile;
576 SAMLconf.ssl_keyfile=g_SSLKeyFile;
577 SAMLconf.ssl_keypass=g_SSLKeyPass;
578 SAMLconf.ssl_calist=g_SSLCAList;
580 SAMLconf.bVerbose=true;
582 SAMLconf.bVerbose=false;
584 if (!SAMLConfig::init(&SAMLconf))
587 WritePrivateProfileString("startlog","bailed-at","SAML init failed",inifile);
591 Shibconf.origin_mapper=&mapper;
592 if (!ShibConfig::init(&Shibconf))
595 WritePrivateProfileString("startlog","bailed-at","Shib init failed",inifile);
600 DWORD res=GetPrivateProfileSection("ShibMapAttributes",buf2,sizeof(buf2),inifile);
601 if (res==sizeof(buf2)-2)
604 WritePrivateProfileString("startlog","bailed-at","ShibMapAttributes too big",inifile);
608 for (char* attr=buf2; *attr; attr++)
610 char* delim=strchr(attr,'=');
614 WritePrivateProfileString("startlog","bailed-at","ShibMapAttributes bad key format",inifile);
618 g_mapAttribNameToHeader[attr]=(string(delim) + ':');
619 attr=delim + strlen(delim);
622 WritePrivateProfileString("startlog","attributes","complete",inifile);
624 // Transcode the attribute names we know about for quick handling map access.
625 for (map<string,string>::const_iterator j=g_mapAttribNameToHeader.begin();
626 j!=g_mapAttribNameToHeader.end(); j++)
628 auto_ptr<XMLCh> temp(XMLString::transcode(j->first.c_str()));
629 g_mapAttribNames[temp.get()]=j->first;
632 res=GetPrivateProfileSection("ShibExtensions",buf2,sizeof(buf2),inifile);
633 if (res==sizeof(buf2)-2)
636 WritePrivateProfileString("startlog","bailed-at","ShibExtensions too big",inifile);
640 for (char* libpath=buf2; *libpath; libpath+=strlen(libpath)+1)
641 SAMLConfig::getConfig()->saml_register_extension(libpath);
643 WritePrivateProfileString("startlog","extensions","complete",inifile);
648 WritePrivateProfileString("startlog","bailed-at","bad_alloc caught",inifile);
651 catch (SAMLException& ex)
654 WritePrivateProfileString("startlog","bailed-at","SAML Exception caught",inifile);
655 WritePrivateProfileString("startlog","SAMLException",ex.what(),inifile);
659 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
660 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
661 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
662 SF_NOTIFY_SECURE_PORT |
663 SF_NOTIFY_NONSECURE_PORT |
664 SF_NOTIFY_PREPROC_HEADERS |
669 extern "C" BOOL WINAPI TerminateFilter(DWORD dwFlags)
678 /* Next up, some suck-free versions of various APIs.
680 You DON'T require people to guess the buffer size and THEN tell them the right size.
681 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
682 constant strings aren't typed as such, making it just that much harder. These versions
683 are now updated to use a special growable buffer object, modeled after the standard
684 string class. The standard string won't work because they left out the option to
685 pre-allocate a non-constant buffer.
691 dynabuf() { bufptr=NULL; buflen=0; }
692 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
693 ~dynabuf() { delete[] bufptr; }
694 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
695 size_t size() const { return buflen; }
696 bool empty() const { return length()==0; }
697 void reserve(size_t s, bool keep=false);
698 void erase() { if (bufptr) *bufptr=0; }
699 operator char*() { return bufptr; }
700 bool operator ==(const char* s) const;
701 bool operator !=(const char* s) const { return !(*this==s); }
707 void dynabuf::reserve(size_t s, bool keep)
714 p[buflen]=bufptr[buflen];
720 bool dynabuf::operator==(const char* s) const
722 if (buflen==NULL || s==NULL)
723 return (buflen==NULL && s==NULL);
725 return strcmp(bufptr,s)==0;
728 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc,
729 LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
730 throw (bad_alloc, DWORD)
736 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
738 // Grumble. Check the error.
739 DWORD e=GetLastError();
740 if (e==ERROR_INSUFFICIENT_BUFFER)
745 if (bRequired && s.empty())
749 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
750 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
751 throw (bad_alloc, DWORD)
757 while (!pn->GetHeader(pfc,lpszName,s,&size))
759 // Grumble. Check the error.
760 DWORD e=GetLastError();
761 if (e==ERROR_INSUFFICIENT_BUFFER)
766 if (bRequired && s.empty())
770 inline char hexchar(unsigned short s)
772 return (s<=9) ? ('0' + s) : ('A' + s - 10);
775 string url_encode(const char* url) throw (bad_alloc)
777 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
779 for (const char* pch=url; *pch; pch++)
781 if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
782 s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
789 string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t* pSite)
791 // Reconstructing the requested URL is not fun. Apparently, the PREPROC_HEADERS
792 // event means way pre. As in, none of the usual CGI headers are in place yet.
793 // It's actually almost easier, in a way, because all the path-info and query
794 // stuff is in one place, the requested URL, which we can get. But we have to
795 // reconstruct the protocol/host pair using tweezers.
797 if (pfc->fIsSecurePort)
803 GetServerVariable(pfc,"SERVER_NAME",buf);
806 GetServerVariable(pfc,"SERVER_PORT",buf,10);
807 if (buf!=(pfc->fIsSecurePort ? "443" : "80"))
808 s=s + ':' + static_cast<char*>(buf);
810 GetHeader(pn,pfc,"url",buf,256,false);
816 string get_shire_location(PHTTP_FILTER_CONTEXT pfc, settings_t* pSite, const char* target)
818 if (pSite->g_SHIRELocation[0]!='/')
819 return url_encode(pSite->g_SHIRELocation.c_str());
820 const char* colon=strchr(target,':');
821 const char* slash=strchr(colon+3,'/');
822 string s(target,slash-target);
823 s+=pSite->g_SHIRELocation;
824 return url_encode(s.c_str());
827 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
829 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
830 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
831 "<H1>Shibboleth Filter Error</H1>";
832 DWORD resplen=strlen(xmsg);
833 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
835 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
836 static const char* xmsg2="</BODY></HTML>";
837 resplen=strlen(xmsg2);
838 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
839 return SF_STATUS_REQ_FINISHED;
842 DWORD shib_shar_error(PHTTP_FILTER_CONTEXT pfc, SAMLException& e)
844 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
846 static const char* msg="<HTML><HEAD><TITLE>Shibboleth Attribute Exchange Failed</TITLE></HEAD>\n"
847 "<BODY><H3>Shibboleth Attribute Exchange Failed</H3>\n"
848 "While attempting to securely contact your origin site to obtain "
849 "information about you, an error occurred:<BR><BLOCKQUOTE>";
850 DWORD resplen=strlen(msg);
851 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
853 const char* msg2=e.what();
854 resplen=strlen(msg2);
855 pfc->WriteClient(pfc,(LPVOID)msg2,&resplen,0);
858 Iterator<saml::QName> i=e.getCodes();
859 if (i.hasNext() && XMLString::compareString(L(Responder),i.next().getLocalName()))
862 const char* msg4=(origin ? "</BLOCKQUOTE><P>The error appears to be located at your origin site.<BR>" :
863 "</BLOCKQUOTE><P>The error appears to be located at the resource provider's site.<BR>");
864 resplen=strlen(msg4);
865 pfc->WriteClient(pfc,(LPVOID)msg4,&resplen,0);
867 static const char* msg5="<P>Try restarting your browser and accessing the site again to make "
868 "sure the problem isn't temporary. Please contact the administrator "
869 "of that site if this problem recurs. If possible, provide him/her "
870 "with the error message shown above.</BODY></HTML>";
871 resplen=strlen(msg5);
872 pfc->WriteClient(pfc,(LPVOID)msg5,&resplen,0);
873 return SF_STATUS_REQ_FINISHED;
876 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
878 // Is this a log notification?
879 if (notificationType==SF_NOTIFY_LOG)
881 if (pfc->pFilterContext)
882 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
883 return SF_STATUS_REQ_NEXT_NOTIFICATION;
887 settings_t* pSite=NULL;
889 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
892 // Determine web site number.
895 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
896 if ((site_id=strtoul(buf,NULL,10))==0)
897 return WriteClientError(pfc,"IIS site instance appears to be invalid.");
899 // Match site instance to site settings pointer.
900 if (site_id>g_ulMaxSite || g_Sites[site_id-1].g_CookieName.empty())
901 return SF_STATUS_REQ_NEXT_NOTIFICATION;
902 pSite=&g_Sites[site_id-1];
904 string targeturl=get_target(pfc,pn,pSite);
906 // If the user is accessing the SHIRE acceptance point, pass on.
907 if (targeturl.find(pSite->g_SHIRELocation)!=string::npos)
908 return SF_STATUS_REQ_NEXT_NOTIFICATION;
910 // If this is the garbage collection service, do a cache sweep.
911 if (targeturl==pSite->g_GarbageCollector)
913 pSite->g_AuthCache.lock();
915 pSite->g_AuthCache.sweep(pSite->g_Lifetime);
916 pSite->g_AuthCache.unlock();
918 return WriteClientError(pfc,"The cache was swept for expired sessions.");
921 // Get the url request and scan for the must-contain string.
922 if (!pSite->g_MustContain.empty())
924 char* upcased=new char[targeturl.length()+1];
925 strcpy(upcased,targeturl.c_str());
927 for (vector<string>::const_iterator index=pSite->g_MustContain.begin(); index!=pSite->g_MustContain.end(); index++)
928 if (strstr(upcased,index->c_str()))
931 if (index==pSite->g_MustContain.end())
932 return SF_STATUS_REQ_NEXT_NOTIFICATION;
936 if (pSite->g_bSSLOnly && !pfc->fIsSecurePort)
938 xmsg="<HTML><HEAD><TITLE>Access Denied</TITLE></HEAD><BODY>"
939 "<H1>Access Denied</H1>"
940 "This server is configured to deny non-SSL requests for secure resources. "
941 "Try your request again using https instead of http."
943 DWORD resplen=strlen(xmsg);
944 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
945 pfc->WriteClient(pfc,xmsg,&resplen,0);
946 return SF_STATUS_REQ_FINISHED;
949 // Check for authentication cookie.
950 const char* session_id=NULL;
951 GetHeader(pn,pfc,"Cookie:",buf,128,false);
952 if (buf.empty() || !(session_id=strstr(buf,pSite->g_CookieName.c_str())) ||
953 *(session_id+pSite->g_CookieName.length())!='=')
956 string wayf("Location: ");
957 wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
958 "&target=" + url_encode(targeturl.c_str()) + "\r\n";
959 // Insert the headers.
960 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
961 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
962 return SF_STATUS_REQ_FINISHED;
965 session_id+=pSite->g_CookieName.length() + 1; /* Skip over the '=' */
966 char* cookieend=strchr(session_id,';');
968 *cookieend = '\0'; /* Ignore anyting after a ; */
970 pSite->g_AuthCache.lock(); // ---> Get cache lock
973 // The caching logic is the heart of the "SHAR".
974 CCacheEntry* entry=pSite->g_AuthCache.find(session_id);
979 pSite->g_AuthCache.unlock(); // ---> Release cache lock
982 // Construct the path to the session file
983 string sessionFile=pSite->g_SHIRESessionPath + session_id;
986 entry=new CCacheEntry(sessionFile.c_str());
988 catch (runtime_error e)
991 string wayf("Location: ");
992 wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
993 "&target=" + url_encode(targeturl.c_str()) + "\r\n";
994 // Insert the headers.
995 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
996 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
997 return SF_STATUS_REQ_FINISHED;
999 pSite->g_AuthCache.lock(); // ---> Get cache lock
1001 pSite->g_AuthCache.insert(session_id,entry);
1004 if (!entry->isSessionValid(pSite->g_Lifetime,pSite->g_Timeout))
1006 pSite->g_AuthCache.remove(session_id);
1007 pSite->g_AuthCache.unlock(); // ---> Release cache lock
1011 // Redirect to WAYF.
1012 string wayf("Location: ");
1013 wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
1014 "&target=" + url_encode(targeturl.c_str()) + "\r\n";
1015 // Insert the headers.
1016 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
1017 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
1018 return SF_STATUS_REQ_FINISHED;
1021 if (pSite->g_bCheckAddress && entry->getClientAddress())
1023 GetServerVariable(pfc,"REMOTE_ADDR",buf,16);
1024 if (strcmp(entry->getClientAddress(),buf))
1026 pSite->g_AuthCache.remove(session_id);
1028 pSite->g_AuthCache.unlock(); // ---> Release cache lock
1031 return WriteClientError(pfc,
1032 "Your session was terminated because the network address associated "
1033 "with it does not match your current address. This is usually caused "
1034 "by a firewall or proxy of some sort.");
1038 // Clear relevant headers.
1039 pn->SetHeader(pfc,"Shib-Attributes:","");
1040 pn->SetHeader(pfc,"remote-user:","");
1041 for (map<string,string>::const_iterator h_iter=g_mapAttribNameToHeader.begin(); h_iter!=g_mapAttribNameToHeader.end(); h_iter++)
1042 if (h_iter->second!="REMOTE_USER:")
1043 pn->SetHeader(pfc,const_cast<char*>(h_iter->second.c_str()),"");
1045 if (pSite->g_bExportAssertion)
1047 string exp((char*)entry->getSerializedAssertion(targeturl.c_str(),pSite));
1048 string::size_type lfeed;
1049 while ((lfeed=exp.find('\n'))!=string::npos)
1051 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(exp.c_str()));
1053 Iterator<SAMLAttribute*> i=entry->getAttributes(targeturl.c_str(),pSite);
1057 SAMLAttribute* attr=i.next();
1059 // Are we supposed to export it?
1060 map<xstring,string>::const_iterator iname=g_mapAttribNames.find(attr->getName());
1061 if (iname!=g_mapAttribNames.end())
1063 string hname=g_mapAttribNameToHeader[iname->second];
1064 Iterator<string> vals=attr->getSingleByteValues();
1065 if (hname=="REMOTE_USER:" && vals.hasNext())
1067 char* principal=const_cast<char*>(vals.next().c_str());
1068 pn->SetHeader(pfc,"remote-user:",principal);
1069 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
1070 if (pfc->pFilterContext)
1071 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
1076 while (vals.hasNext())
1077 header+=vals.next() + " ";
1078 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
1083 pSite->g_AuthCache.unlock(); // ---> Release cache lock
1085 return SF_STATUS_REQ_NEXT_NOTIFICATION;
1087 catch (SAMLException& e)
1089 Iterator<saml::QName> i=e.getCodes();
1094 saml::QName q=i.next();
1095 if (c==1 && !XMLString::compareString(q.getNamespaceURI(),saml::XML::SAMLP_NS) &&
1096 !XMLString::compareString(q.getLocalName(),L(Requester)))
1098 else if (c==2 && !XMLString::compareString(q.getNamespaceURI(),shibboleth::XML::SHIB_NS) &&
1099 !XMLString::compareString(q.getLocalName(),shibboleth::XML::Literals::InvalidHandle))
1102 pSite->g_AuthCache.lock(); // ---> Grab cache lock
1103 pSite->g_AuthCache.remove(session_id);
1104 pSite->g_AuthCache.unlock(); // ---> Release cache lock
1107 // Redirect to WAYF.
1108 string wayf("Location: ");
1109 wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) +
1110 "&target=" + url_encode(targeturl.c_str()) + "\r\n";
1111 // Insert the headers.
1112 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
1113 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
1114 return SF_STATUS_REQ_FINISHED;
1118 return shib_shar_error(pfc,e);
1120 catch (XMLException& e)
1123 pSite->g_AuthCache.unlock();
1124 auto_ptr<char> msg(XMLString::transcode(e.getMessage()));
1125 SAMLException ex(SAMLException::RESPONDER,msg.get());
1126 return shib_shar_error(pfc,ex);
1131 xmsg="Out of memory.";
1135 if (e==ERROR_NO_DATA)
1136 xmsg="A required variable or header was empty.";
1138 xmsg="Server detected unexpected IIS error.";
1142 xmsg="Server caught an unknown exception.";
1145 // If we drop here, the exception handler set the proper message.
1147 pSite->g_AuthCache.unlock();
1148 return WriteClientError(pfc,xmsg);