From 84a4260cf6ab47b41b227256edaa5103c6f528a6 Mon Sep 17 00:00:00 2001 From: cantor Date: Sun, 6 Jul 2003 05:35:19 +0000 Subject: [PATCH] Rewritten filter based on 1.0 APIs, still missing POST handler. git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@619 cb58f699-b61c-0410-a6fe-9272a202ed29 --- isapi_shib/isapi_shib.cpp | 1170 +++++++++++++-------------------------------- 1 file changed, 337 insertions(+), 833 deletions(-) diff --git a/isapi_shib/isapi_shib.cpp b/isapi_shib/isapi_shib.cpp index 726798a..8628b5f 100644 --- a/isapi_shib/isapi_shib.cpp +++ b/isapi_shib/isapi_shib.cpp @@ -53,373 +53,63 @@ 8/23/02 */ -#include -#include - // SAML Runtime -#include -#include -#include +#include +#include +#include +#include #include -#include -#include #include -#include +#include +#include #include +#include + using namespace std; using namespace log4cpp; using namespace saml; using namespace shibboleth; -using namespace eduPerson; - -class CCacheEntry; -class CCache -{ -public: - CCache(); - ~CCache(); - - SAMLBinding* getBinding(const XMLCh* bindingProt); - CCacheEntry* find(const char* key); - void insert(const char* key, CCacheEntry* entry); - void remove(const char* key); - void sweep(time_t lifetime); - - bool lock() { EnterCriticalSection(&m_lock); return true; } - void unlock() { LeaveCriticalSection(&m_lock); } +using namespace shibtarget; -private: - SAMLBinding* m_SAMLBinding; - map m_hashtable; - CRITICAL_SECTION m_lock; -}; - -// Per-website global structure struct settings_t { - settings_t(); - string g_CookieName; // name of authentication token - string g_WAYFLocation; // URL of WAYF service - string g_GarbageCollector; // URL of cache garbage collection service - string g_SHIRELocation; // URL of SHIRE acceptance point - string g_SHIRESessionPath; // path to storage for sessions - vector g_MustContain; // simple URL matching string array - bool g_bSSLOnly; // only over SSL? - time_t g_Lifetime; // maximum token lifetime - time_t g_Timeout; // maximum time between uses - bool g_bCheckAddress; // validate IP addresses? - bool g_bExportAssertion; // export SAML assertion to header? - CCache g_AuthCache; // local auth cache -}; - -settings_t::settings_t() -{ - g_bSSLOnly=true; - g_Lifetime=7200; - g_Timeout=3600; - g_bCheckAddress=true; - g_bExportAssertion=false; -} - -class CCacheEntry -{ -public: - CCacheEntry(const char* sessionFile); - ~CCacheEntry(); - - SAMLAuthorityBinding* getBinding() { return m_binding; } - Iterator getAttributes(const char* resource_url, settings_t* pSite); - const XMLByte* getSerializedAssertion(const char* resource_url, settings_t* pSite); - bool isSessionValid(time_t lifetime, time_t timeout); - const XMLCh* getHandle() { return m_handle.c_str(); } - const XMLCh* getOriginSite() { return m_originSite.c_str(); } - const char* getClientAddress() { return m_clientAddress.c_str(); } - -private: - void populate(const char* resource_url, settings_t* pSite); - - xstring m_originSite; - xstring m_handle; - SAMLAuthorityBinding* m_binding; - string m_clientAddress; - SAMLResponse* m_response; - SAMLAssertion* m_assertion; - time_t m_sessionCreated; - time_t m_lastAccess; - XMLByte* m_serialized; - - static saml::QName g_authorityKind; - static saml::QName g_respondWith; - friend class CCache; + settings_t() {} + settings_t(string& name) : m_name(name) {} + + string m_name; + vector m_mustContain; }; -// static members -saml::QName CCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery)); -saml::QName CCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement)); - -CCache::CCache() -{ - m_SAMLBinding=SAMLBindingFactory::getInstance(); - InitializeCriticalSection(&m_lock); -} - -CCache::~CCache() -{ - DeleteCriticalSection(&m_lock); - delete m_SAMLBinding; - for (map::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++) - delete i->second; -} - -SAMLBinding* CCache::getBinding(const XMLCh* bindingProt) -{ - if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) - return m_SAMLBinding; - return NULL; -} - -CCacheEntry* CCache::find(const char* key) -{ - map::const_iterator i=m_hashtable.find(key); - if (i==m_hashtable.end()) - return NULL; - return i->second; -} - -void CCache::insert(const char* key, CCacheEntry* entry) -{ - m_hashtable[key]=entry; -} - -void CCache::remove(const char* key) -{ - m_hashtable.erase(key); +// globals +namespace { + HINSTANCE g_hinstDLL; + ThreadKey* rpc_handle_key = NULL; + ShibTargetConfig* g_Config = NULL; + vector g_Sites; } -void CCache::sweep(time_t lifetime) +void destroy_handle(void* data) { - time_t now=time(NULL); - for (map::iterator i=m_hashtable.begin(); i!=m_hashtable.end();) - { - if (lifetime > 0 && now > i->second->m_sessionCreated+lifetime) - { - delete i->second; - i=m_hashtable.erase(i); - } - else - i++; - } + delete (RPCHandle*)data; } -CCacheEntry::CCacheEntry(const char* sessionFile) - : m_binding(NULL), m_assertion(NULL), m_response(NULL), m_lastAccess(0), m_sessionCreated(0), m_serialized(NULL) +BOOL LogEvent( + LPCSTR lpUNCServerName, + WORD wType, + DWORD dwEventID, + PSID lpUserSid, + LPCSTR message) { - FILE* f; - char line[1024]; - const char* token = NULL; - char* w = NULL; - auto_ptr binding,location; - - if (!(f=fopen(sessionFile,"r"))) - { - fprintf(stderr,"CCacheEntry() could not open session file: %s",sessionFile); - throw runtime_error("CCacheEntry() could not open session file"); - } - - while (fgets(line,1024,f)) - { - if ((*line=='#') || (!*line)) - continue; - token = line; - w=strchr(token,'='); - if (!w) - continue; - *w++=0; - if (w[strlen(w)-1]=='\n') - w[strlen(w)-1]=0; - - if (!strcmp("Domain",token)) - { - auto_ptr origin(XMLString::transcode(w)); - m_originSite=origin.get(); - } - else if (!strcmp("Handle",token)) - { - auto_ptr handle(XMLString::transcode(w)); - m_handle=handle.get(); - } - else if (!strcmp("PBinding0",token)) - binding=auto_ptr(XMLString::transcode(w)); - else if (!strcmp("LBinding0",token)) - location=auto_ptr(XMLString::transcode(w)); - else if (!strcmp("Time",token)) - m_sessionCreated=atoi(w); - else if (!strcmp("ClientAddress",token)) - m_clientAddress=w; - else if (!strcmp("EOF",token)) - break; - } - fclose(f); + LPCSTR messages[] = {message, NULL}; - if (binding.get()!=NULL && location.get()!=NULL) - m_binding=new SAMLAuthorityBinding(g_authorityKind,binding.get(),location.get()); - - m_lastAccess=time(NULL); - if (!m_sessionCreated) - m_sessionCreated=m_lastAccess; -} - -CCacheEntry::~CCacheEntry() -{ - delete m_binding; - delete m_response; - delete[] m_serialized; -} - -bool CCacheEntry::isSessionValid(time_t lifetime, time_t timeout) -{ - time_t now=time(NULL); - if (lifetime > 0 && now > m_sessionCreated+lifetime) - return false; - if (timeout > 0 && now-m_lastAccess >= timeout) - return false; - m_lastAccess=now; - return true; + HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter"); + BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL); + return (DeregisterEventSource(hElog) && res); } -Iterator CCacheEntry::getAttributes(const char* resource_url, settings_t* pSite) -{ - populate(resource_url,pSite); - if (m_assertion) - { - Iterator i=m_assertion->getStatements(); - if (i.hasNext()) - { - SAMLAttributeStatement* s=dynamic_cast(i.next()); - if (s) - return s->getAttributes(); - } - } - return Iterator(); -} - -const XMLByte* CCacheEntry::getSerializedAssertion(const char* resource_url, settings_t* pSite) -{ - populate(resource_url,pSite); - if (m_serialized) - return m_serialized; - if (!m_assertion) - return NULL; - ostrstream os; - os << *m_assertion; - unsigned int outlen; - return m_serialized=Base64::encode(reinterpret_cast(os.str()),os.pcount(),&outlen); -} - -void CCacheEntry::populate(const char* resource_url, settings_t* pSite) -{ -#undef FUNC -#define FUNC populate - Category& log=Category::getInstance("isapi_shib.CCacheEntry"); - - // Can we use what we have? - if (m_assertion && m_assertion->getNotOnOrAfter()) - { - // This is awful, but the XMLDateTime class is truly horrible. - time_t now=time(NULL); - struct tm* ptime=gmtime(&now); - char timebuf[32]; - strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime); - auto_ptr timeptr(XMLString::transcode(timebuf)); - XMLDateTime curDateTime(timeptr.get()); - int result=XMLDateTime::compareOrder(&curDateTime,m_assertion->getNotOnOrAfter()); - if (XMLDateTime::LESS_THAN) - return; - - delete m_response; - delete[] m_serialized; - m_assertion=NULL; - m_response=NULL; - m_serialized=NULL; - - log.info("%s: cached attributes have expired",FUNC); - } - - if (!m_binding) - return; - - auto_ptr resource(XMLString::transcode(resource_url)); - - // Build a SAML Request and send it to the AA. - SAMLSubject* subject=new SAMLSubject(m_handle.c_str(),m_originSite.c_str()); - SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resource.get()); - SAMLRequest* req=new SAMLRequest(q,ArrayIterator(&g_respondWith)); - SAMLBinding* pBinding=pSite->g_AuthCache.getBinding(m_binding->getBinding()); - m_response=pBinding->send(*m_binding,*req); - delete req; - - // Store off the assertion for quick access. Memory mgmt is based on the response pointer. - Iterator i=m_response->getAssertions(); - if (i.hasNext()) - m_assertion=i.next(); - - auto_ptr h(XMLString::transcode(m_handle.c_str())); - auto_ptr d(XMLString::transcode(m_originSite.c_str())); - log.info("%s: fetched and stored SAML response for %s@%s",FUNC,h.get(),d.get()); -} - -class DummyMapper : public IOriginSiteMapper -{ -public: - DummyMapper() { InitializeCriticalSection(&m_lock); } - ~DummyMapper(); - virtual Iterator getHandleServiceNames(const XMLCh* originSite) { return Iterator(); } - virtual Key* getHandleServiceKey(const XMLCh* handleService) { return NULL; } - virtual Iterator getSecurityDomains(const XMLCh* originSite); - virtual Iterator getTrustedRoots() { return Iterator(); } - -private: - typedef map*> domains_t; - domains_t m_domains; - CRITICAL_SECTION m_lock; -}; - -Iterator DummyMapper::getSecurityDomains(const XMLCh* originSite) -{ - EnterCriticalSection(&m_lock); - vector* pv=NULL; - domains_t::iterator i=m_domains.find(originSite); - if (i==m_domains.end()) - { - pv=new vector(); - pv->push_back(originSite); - pair p=m_domains.insert(domains_t::value_type(originSite,pv)); - i=p.first; - } - else - pv=i->second; - LeaveCriticalSection(&m_lock); - return Iterator(*pv); -} - -DummyMapper::~DummyMapper() -{ - for (domains_t::iterator i=m_domains.begin(); i!=m_domains.end(); i++) - delete i->second; - DeleteCriticalSection(&m_lock); -} - -// globals -HINSTANCE g_hinstDLL; -ULONG g_ulMaxSite=1; // max IIS site instance to handle -settings_t* g_Sites=NULL; // array of site settings -map g_mapAttribNameToHeader; // attribute mapping -map g_mapAttribNames; - - extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID) { if (fdwReason==DLL_PROCESS_ATTACH) @@ -432,248 +122,64 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) if (!pVer) return FALSE; - // Get module pathname and replace file name with ini file name. - char inifile[MAX_PATH+1]; - if (GetModuleFileName(g_hinstDLL,inifile,MAX_PATH+1)==0) - return FALSE; - char* pch=strrchr(inifile,'\\'); - if (pch==NULL) - return FALSE; - pch++; - *pch=0; - strcat(inifile,"isapi_shib.ini"); - - // Read system-wide parameters from isapi_shib.ini. - char buf[1024]; - char buf3[48]; - try { - SAMLConfig& SAMLconf=SAMLConfig::getConfig(); - - GetPrivateProfileString("shibboleth","ShibLogConfig","",buf,sizeof(buf),inifile); - if (*buf) - PropertyConfigurator::configure(buf); - Category& log=Category::getInstance("isapi_shib.GetFilterVersion"); - log.info("using INI file: %s",inifile); - - GetPrivateProfileString("shibboleth","ShibSchemaPath","",buf,sizeof(buf),inifile); - if (!*buf) + g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG"))); + ShibINI& ini = g_Config->getINI(); + + // Create the RPC Handle TLS key. + rpc_handle_key=ThreadKey::create(destroy_handle); + + // Read site-specific settings for each instance ID we can find. + unsigned short i=1; + char iid[8]; + sprintf(iid,"%u",i++); + string hostname; + while (ini.get_tag("isapi",iid,false,&hostname)) { - log.fatal("ShibSchemaPath missing"); - return FALSE; - } - SAMLconf.schema_dir=buf; - if (*SAMLconf.schema_dir.end()!='\\') - SAMLconf.schema_dir+='\\'; - - GetPrivateProfileString("shibboleth","ShibSSLCertFile","",buf,sizeof(buf),inifile); - if (!*buf) - { - log.fatal("ShibSSLCertFile missing"); - return FALSE; - } - SAMLconf.ssl_certfile=buf; - - GetPrivateProfileString("shibboleth","ShibSSLKeyFile","",buf,sizeof(buf),inifile); - if (!*buf) - { - log.fatal("ShibSSLKeyFile missing"); - return FALSE; - } - SAMLconf.ssl_keyfile=buf; - - GetPrivateProfileString("shibboleth","ShibSSLKeyPass","",buf,sizeof(buf),inifile); - SAMLconf.ssl_keypass=buf; - - GetPrivateProfileString("shibboleth","ShibSSLCAList","",buf,sizeof(buf),inifile); - SAMLconf.ssl_calist=buf; - - // Read site count and allocate site array. - g_ulMaxSite=GetPrivateProfileInt("shibboleth","max-site",0,inifile); - if (g_ulMaxSite==0) - { - log.fatal("max-site was 0 or invalid"); - return FALSE; - } - log.debug("max-site is %d",g_ulMaxSite); - g_Sites=new settings_t[g_ulMaxSite]; - - // Read site-specific settings for each site. - for (ULONG i=0; i::const_iterator j=g_mapAttribNameToHeader.begin(); - j!=g_mapAttribNameToHeader.end(); j++) - { - auto_ptr temp(XMLString::transcode(j->first.c_str())); - g_mapAttribNames[temp.get()]=j->first; - } - - res=GetPrivateProfileSection("ShibExtensions",buf2,sizeof(buf2),inifile); - if (res==sizeof(buf2)-2) - { - delete[] g_Sites; - log.fatal("ShibExtensions INI section was larger than 32k"); - return FALSE; - } - - for (char* libpath=buf2; *libpath; libpath+=strlen(libpath)+1) - SAMLconf.saml_register_extension(libpath); - - log.info("completed loading of extension libraries"); } - catch (bad_alloc) + catch (SAMLException&) { - delete[] g_Sites; - Category::getInstance("isapi_shib.GetFilterVersion").fatal("out of memory"); + LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, + "Filter startup failed with SAML exception, check shire log for help."); return FALSE; } - catch (log4cpp::ConfigureFailure& ex) + catch (...) { - delete[] g_Sites; - WritePrivateProfileString("startlog","bailed-at","log4cpp exception caught",inifile); - WritePrivateProfileString("startlog","log4cpp",ex.what(),inifile); - return FALSE; - } - catch (SAMLException& ex) - { - delete[] g_Sites; - Category::getInstance("isapi_shib.GetFilterVersion").fatal("caught SAML exception: %s",ex.what()); + LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, + "Filter startup failed with unexpected exception, check shire log for help."); return FALSE; } @@ -684,17 +190,17 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) SF_NOTIFY_NONSECURE_PORT | SF_NOTIFY_PREPROC_HEADERS | SF_NOTIFY_LOG); + LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized..."); return TRUE; } extern "C" BOOL WINAPI TerminateFilter(DWORD dwFlags) { - Category::getInstance("isapi_shib.TerminateFilter").info("shutting down..."); - delete[] g_Sites; - g_Sites=NULL; - ShibConfig::getConfig().term(); - SAMLConfig::getConfig().term(); - Category::getInstance("isapi_shib.TerminateFilter").info("shut down complete"); + delete rpc_handle_key; + if (g_Config) + g_Config->shutdown(); + g_Config = NULL; + LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down..."); return TRUE; } @@ -748,8 +254,7 @@ bool dynabuf::operator==(const char* s) const return strcmp(bufptr,s)==0; } -void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, - LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true) +void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true) throw (bad_alloc, DWORD) { s.erase(); @@ -809,7 +314,7 @@ string url_encode(const char* url) throw (bad_alloc) return s; } -string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t* pSite) +string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t& site) { // Reconstructing the requested URL is not fun. Apparently, the PREPROC_HEADERS // event means way pre. As in, none of the usual CGI headers are in place yet. @@ -822,9 +327,18 @@ string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, set else s="http://"; + // We use the "normalizeRequest" tag to decide how to obtain the server's name. dynabuf buf(256); - GetServerVariable(pfc,"SERVER_NAME",buf); - s+=buf; + string tag; + if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag)) + { + s+=site.m_name; + } + else + { + GetServerVariable(pfc,"SERVER_NAME",buf); + s+=buf; + } GetServerVariable(pfc,"SERVER_PORT",buf,10); if (buf!=(pfc->fIsSecurePort ? "443" : "80")) @@ -836,21 +350,25 @@ string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, set return s; } -string get_shire_location(PHTTP_FILTER_CONTEXT pfc, settings_t* pSite, const char* target) +string get_shire_location(PHTTP_FILTER_CONTEXT pfc, settings_t& site, const char* target) { - if (pSite->g_SHIRELocation[0]!='/') - return url_encode(pSite->g_SHIRELocation.c_str()); - const char* colon=strchr(target,':'); - const char* slash=strchr(colon+3,'/'); - string s(target,slash-target); - s+=pSite->g_SHIRELocation; - return url_encode(s.c_str()); + string shireURL; + if (g_Config->getINI().get_tag(site.m_name,"shireURL",true,&shireURL) && !shireURL.empty()) + { + if (shireURL[0]!='/') + return shireURL; + const char* colon=strchr(target,':'); + const char* slash=strchr(colon+3,'/'); + string s(target,slash-target); + s+=shireURL; + return s; + } + return shireURL; } DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg) { - Category::getInstance("isapi_shib.WriteClientError").error("sending error page to browser: %s",msg); - + LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg); pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0); static const char* xmsg="Shibboleth Filter Error" "

Shibboleth Filter Error

"; @@ -864,40 +382,16 @@ DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg) return SF_STATUS_REQ_FINISHED; } -DWORD shib_shar_error(PHTTP_FILTER_CONTEXT pfc, SAMLException& e) +DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp) { - Category::getInstance("isapi_shib.shib_shar_error").errorStream() - << "exception during SHAR request: " << e; + ifstream infile(filename); + if (!infile) + return WriteClientError(pfc,"Unable to open error template, check settings."); + string res = mlp.run(infile); pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0); - - static const char* msg="Shibboleth Attribute Exchange Failed\n" - "

Shibboleth Attribute Exchange Failed

\n" - "While attempting to securely contact your origin site to obtain " - "information about you, an error occurred:
"; - DWORD resplen=strlen(msg); - pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0); - - const char* msg2=e.what(); - resplen=strlen(msg2); - pfc->WriteClient(pfc,(LPVOID)msg2,&resplen,0); - - bool origin=true; - Iterator i=e.getCodes(); - if (i.hasNext() && XMLString::compareString(L(Responder),i.next().getLocalName())) - origin=false; - - const char* msg4=(origin ? "

The error appears to be located at your origin site.
" : - "

The error appears to be located at the resource provider's site.
"); - resplen=strlen(msg4); - pfc->WriteClient(pfc,(LPVOID)msg4,&resplen,0); - - static const char* msg5="

Try restarting your browser and accessing the site again to make " - "sure the problem isn't temporary. Please contact the administrator " - "of that site if this problem recurs. If possible, provide him/her " - "with the error message shown above."; - resplen=strlen(msg5); - pfc->WriteClient(pfc,(LPVOID)msg5,&resplen,0); + DWORD resplen=res.length(); + pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0); return SF_STATUS_REQ_FINISHED; } @@ -911,296 +405,306 @@ extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificat return SF_STATUS_REQ_NEXT_NOTIFICATION; } - char* xmsg=NULL; - settings_t* pSite=NULL; - bool bLocked=false; PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification; - Category& log=Category::getInstance("isapi_shib.HttpFilterProc"); try { - // Determine web site number. + // Determine web site number. This can't really fail, I don't think. dynabuf buf(128); ULONG site_id=0; GetServerVariable(pfc,"INSTANCE_ID",buf,10); if ((site_id=strtoul(buf,NULL,10))==0) return WriteClientError(pfc,"IIS site instance appears to be invalid."); - // Match site instance to site settings pointer. - if (site_id>g_ulMaxSite || g_Sites[site_id-1].g_CookieName.empty()) + // Match site instance to site settings. + if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0) return SF_STATUS_REQ_NEXT_NOTIFICATION; - pSite=&g_Sites[site_id-1]; + settings_t& site=g_Sites[site_id-1]; - string targeturl=get_target(pfc,pn,pSite); + string target_url=get_target(pfc,pn,site); + string shire_url=get_shire_location(pfc,site,target_url.c_str()); - // If the user is accessing the SHIRE acceptance point, pass on. - if (targeturl.find(pSite->g_SHIRELocation)!=string::npos) - { - log.debug("passing on SHIRE acceptance request"); + // If the user is accessing the SHIRE acceptance point, pass it on. + if (target_url.find(shire_url)!=string::npos) return SF_STATUS_REQ_NEXT_NOTIFICATION; - } - - // If this is the garbage collection service, do a cache sweep. - if (targeturl==pSite->g_GarbageCollector) - { - log.notice("garbage collector triggered"); - pSite->g_AuthCache.lock(); - bLocked=true; - pSite->g_AuthCache.sweep(pSite->g_Lifetime); - pSite->g_AuthCache.unlock(); - bLocked=false; - return WriteClientError(pfc,"The cache was swept for expired sessions."); - } // Get the url request and scan for the must-contain string. - if (!pSite->g_MustContain.empty()) + if (!site.m_mustContain.empty()) { - char* upcased=new char[targeturl.length()+1]; - strcpy(upcased,targeturl.c_str()); + char* upcased=new char[target_url.length()+1]; + strcpy(upcased,target_url.c_str()); _strupr(upcased); - for (vector::const_iterator index=pSite->g_MustContain.begin(); index!=pSite->g_MustContain.end(); index++) + for (vector::const_iterator index=site.m_mustContain.begin(); index!=site.m_mustContain.end(); index++) if (strstr(upcased,index->c_str())) break; delete[] upcased; - if (index==pSite->g_MustContain.end()) + if (index==site.m_mustContain.end()) return SF_STATUS_REQ_NEXT_NOTIFICATION; } - // SSL check. - if (pSite->g_bSSLOnly && !pfc->fIsSecurePort) + // SSL content check. + ShibINI& ini=g_Config->getINI(); + string tag; + if (ini.get_tag(site.m_name,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort) { - log.warn("blocking non-SSL request"); - xmsg="Access Denied" - "

Access Denied

" - "This server is configured to deny non-SSL requests for secure resources. " - "Try your request again using https instead of http." - ""; - DWORD resplen=strlen(xmsg); - pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0); - pfc->WriteClient(pfc,xmsg,&resplen,0); - return SF_STATUS_REQ_FINISHED; + return WriteClientError(pfc, + "This server is configured to deny non-SSL requests for secure resources. " + "Try your request again using https instead of http."); } + ostringstream threadid; + threadid << "[" << getpid() << "] shire" << '\0'; + saml::NDC ndc(threadid.str().c_str()); + + // Set SHIRE policies. + SHIREConfig config; + config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag)); + config.lifetime=config.timeout=0; + tag.erase(); + if (ini.get_tag(site.m_name, "authLifetime", true, &tag)) + config.lifetime=strtoul(tag.c_str(),NULL,10); + tag.erase(); + if (ini.get_tag(site.m_name, "authTimeout", true, &tag)) + config.timeout=strtoul(tag.c_str(),NULL,10); + + // Pull the config data we need to handle the various possible conditions. + string shib_cookie; + if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie)) + return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration."); + + string wayfLocation; + if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation)) + return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration."); + + string shireError; + if (!ini.get_tag(site.m_name, "shireError", true, &shireError)) + return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration."); + + string accessError; + if (!ini.get_tag(site.m_name, "accessError", true, &shireError)) + return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration."); + + // Get an RPC handle and build the SHIRE object. + RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData(); + if (!rpc_handle) + { + rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1); + rpc_handle_key->setData(rpc_handle); + } + SHIRE shire(rpc_handle, config, shire_url); + // Check for authentication cookie. const char* session_id=NULL; GetHeader(pn,pfc,"Cookie:",buf,128,false); - if (buf.empty() || !(session_id=strstr(buf,pSite->g_CookieName.c_str())) || - *(session_id+pSite->g_CookieName.length())!='=') + if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=') { - log.info("session cookie not found, redirecting to WAYF"); - // Redirect to WAYF. string wayf("Location: "); - wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) + - "&target=" + url_encode(targeturl.c_str()) + "\r\n"; + wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n"; // Insert the headers. pfc->AddResponseHeaders(pfc,const_cast(wayf.c_str()),0); pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0); return SF_STATUS_REQ_FINISHED; } - session_id+=pSite->g_CookieName.length() + 1; /* Skip over the '=' */ + session_id+=shib_cookie.length() + 1; /* Skip over the '=' */ char* cookieend=strchr(session_id,';'); if (cookieend) *cookieend = '\0'; /* Ignore anyting after a ; */ - pSite->g_AuthCache.lock(); // ---> Get cache lock - bLocked=true; - - // The caching logic is the heart of the "SHAR". - CCacheEntry* entry=pSite->g_AuthCache.find(session_id); - try - { - if (!entry) - { - pSite->g_AuthCache.unlock(); // ---> Release cache lock - bLocked=false; - - // Construct the path to the session file - string sessionFile=pSite->g_SHIRESessionPath + session_id; - try - { - entry=new CCacheEntry(sessionFile.c_str()); - } - catch (runtime_error e) - { - log.info("unable to load session from file '%s', redirecting to WAYF",sessionFile.c_str()); - - // Redirect to WAYF. - string wayf("Location: "); - wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) + - "&target=" + url_encode(targeturl.c_str()) + "\r\n"; - wayf+="Set-Cookie: " + pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n"; - - // Insert the headers. - pfc->AddResponseHeaders(pfc,const_cast(wayf.c_str()),0); - pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0); - return SF_STATUS_REQ_FINISHED; - } - pSite->g_AuthCache.lock(); // ---> Get cache lock - bLocked=true; - pSite->g_AuthCache.insert(session_id,entry); - log.info("new session established: %s",session_id); - } - - if (!entry->isSessionValid(pSite->g_Lifetime,pSite->g_Timeout)) - { - pSite->g_AuthCache.remove(session_id); - pSite->g_AuthCache.unlock(); // ---> Release cache lock - bLocked=false; - delete entry; - - log.warn("invalidating session because of timeout, redirecting to WAYF"); - + // Make sure this session is still valid. + RPCError* status = NULL; + ShibMLP markupProcessor; + bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag); + markupProcessor.insert("supportContact", has_tag ? tag : ""); + has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag); + markupProcessor.insert("logoLocation", has_tag ? tag : ""); + markupProcessor.insert("requestURL", target_url); + + GetServerVariable(pfc,"REMOTE_ADDR",buf,16); + try { + status = shire.sessionIsValid(session_id, buf, target_url.c_str()); + } + catch (ShibTargetException &e) { + markupProcessor.insert("errorType", "SHIRE Processing Error"); + markupProcessor.insert("errorText", e.what()); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(pfc, shireError.c_str(), markupProcessor); + } + catch (...) { + markupProcessor.insert("errorType", "SHIRE Processing Error"); + markupProcessor.insert("errorText", "Unexpected Exception"); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(pfc, shireError.c_str(), markupProcessor); + } + + // Check the status + if (status->isError()) { + if (status->isRetryable()) { // Redirect to WAYF. + delete status; string wayf("Location: "); - wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) + - "&target=" + url_encode(targeturl.c_str()) + "\r\n"; - wayf+="Set-Cookie: " + pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n"; - + wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n"; // Insert the headers. pfc->AddResponseHeaders(pfc,const_cast(wayf.c_str()),0); pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0); return SF_STATUS_REQ_FINISHED; } + else { + // return the error page to the user + markupProcessor.insert(*status); + delete status; + return WriteClientError(pfc, shireError.c_str(), markupProcessor); + } + } + delete status; + + // Move to RM phase. + RMConfig rm_config; + rm_config.checkIPAddress = config.checkIPAddress; + RM rm(rpc_handle,rm_config); + + // Get the attributes. + vector assertions; + SAMLAuthenticationStatement* sso_statement=NULL; + status = rm.getAssertions(session_id, buf, target_url.c_str(), assertions, &sso_statement); + + if (status->isError()) { + string rmError; + if (!ini.get_tag(site.m_name, "rmError", true, &shireError)) + return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration."); + + markupProcessor.insert(*status); + delete status; + return WriteClientError(pfc, rmError.c_str(), markupProcessor); + } + delete status; + + // Only allow a single assertion... + if (assertions.size() > 1) { + for (int k = 0; k < assertions.size(); k++) + delete assertions[k]; + delete sso_statement; + return WriteClientError(pfc, accessError.c_str(), markupProcessor); + } - if (pSite->g_bCheckAddress && entry->getClientAddress()) + // Get the AAP providers, which contain the attribute policy info. + Iterator provs=ShibConfig::getConfig().getAAPProviders(); + + // Clear out the list of mapped attributes + while (provs.hasNext()) + { + IAAP* aap=provs.next(); + aap->lock(); + try { - GetServerVariable(pfc,"REMOTE_ADDR",buf,16); - if (strcmp(entry->getClientAddress(),buf)) + Iterator rules=aap->getAttributeRules(); + while (rules.hasNext()) { - pSite->g_AuthCache.remove(session_id); - delete entry; - pSite->g_AuthCache.unlock(); // ---> Release cache lock - bLocked=false; - - log.warn("IP address mismatch detected, clearing session"); - - string clearcookie("Set-Cookie: "); - clearcookie+=pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n"; - pfc->AddResponseHeaders(pfc,const_cast(clearcookie.c_str()),0); - return WriteClientError(pfc, - "Your session was terminated because the network address associated " - "with it does not match your current address. This is usually caused " - "by a firewall or proxy of some sort."); + const char* header=rules.next()->getHeader(); + if (header) + pn->SetHeader(pfc,const_cast(header),""); } } - - // Clear relevant headers. - pn->SetHeader(pfc,"Shib-Attributes:",""); - pn->SetHeader(pfc,"remote-user:",""); - for (map::const_iterator h_iter=g_mapAttribNameToHeader.begin(); h_iter!=g_mapAttribNameToHeader.end(); h_iter++) - if (h_iter->second!="REMOTE_USER:") - pn->SetHeader(pfc,const_cast(h_iter->second.c_str()),""); - - if (pSite->g_bExportAssertion) + catch(...) { - string exp((char*)entry->getSerializedAssertion(targeturl.c_str(),pSite)); - string::size_type lfeed; - while ((lfeed=exp.find('\n'))!=string::npos) - exp.erase(lfeed,1); - pn->SetHeader(pfc,"Shib-Attributes:",const_cast(exp.c_str())); + aap->unlock(); + for (int k = 0; k < assertions.size(); k++) + delete assertions[k]; + delete sso_statement; + throw; } - Iterator i=entry->getAttributes(targeturl.c_str(),pSite); - - while (i.hasNext()) - { - SAMLAttribute* attr=i.next(); + aap->unlock(); + } + provs.reset(); - // Are we supposed to export it? - map::const_iterator iname=g_mapAttribNames.find(attr->getName()); - if (iname!=g_mapAttribNames.end()) - { - string hname=g_mapAttribNameToHeader[iname->second]; - Iterator vals=attr->getSingleByteValues(); - if (hname=="REMOTE_USER:" && vals.hasNext()) - { - char* principal=const_cast(vals.next().c_str()); - pn->SetHeader(pfc,"remote-user:",principal); - pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0); - if (pfc->pFilterContext) - strcpy(static_cast(pfc->pFilterContext),principal); - } - else - { - string header(" "); - while (vals.hasNext()) - header+=vals.next() + " "; - pn->SetHeader(pfc,const_cast(hname.c_str()),const_cast(header.c_str())); - } - } - } + // Clear relevant headers. + pn->SetHeader(pfc,"remote-user:",""); + pn->SetHeader(pfc,"Shib-Attributes:",""); + pn->SetHeader(pfc,"Shib-Origin-Site:",""); + pn->SetHeader(pfc,"Shib-Authentication-Method:",""); - pSite->g_AuthCache.unlock(); // ---> Release cache lock - bLocked=false; - return SF_STATUS_REQ_NEXT_NOTIFICATION; + // Maybe export the assertion. + if (ini.get_tag(site.m_name,"exportAssertion",true,&tag) && ShibINI::boolean(tag)) + { + string assertion; + RM::serialize(*(assertions[0]), assertion); +// string::size_type lfeed; +// while ((lfeed=exp.find('\n'))!=string::npos) +// exp.erase(lfeed,1); + pn->SetHeader(pfc,"Shib-Attributes:",const_cast(assertion.c_str())); + } + + if (sso_statement) + { + auto_ptr os(XMLString::transcode(sso_statement->getSubject()->getNameQualifier())); + auto_ptr am(XMLString::transcode(sso_statement->getAuthMethod())); + pn->SetHeader(pfc,"Shib-Origin-Site:", os.get()); + pn->SetHeader(pfc,"Shib-Authentication-Method:", am.get()); } - catch (SAMLException& e) + + // Export the attributes. Only supports a single statement. + Iterator j = assertions.size()==1 ? RM::getAttributes(*(assertions[0])) : EMPTY(SAMLAttribute*); + while (j.hasNext()) { - Iterator i=e.getCodes(); - int c=0; - while (i.hasNext()) + SAMLAttribute* attr=j.next(); + + // Are we supposed to export it? + const char* hname=NULL; + AAP wrapper(attr->getName(),attr->getNamespace()); + if (!wrapper.fail()) + hname=wrapper->getHeader(); + if (hname) { - c++; - saml::QName q=i.next(); - if (c==1 && !XMLString::compareString(q.getNamespaceURI(),saml::XML::SAMLP_NS) && - !XMLString::compareString(q.getLocalName(),L(Requester))) - continue; - else if (c==2 && !XMLString::compareString(q.getNamespaceURI(),shibboleth::XML::SHIB_NS) && - !XMLString::compareString(q.getLocalName(),shibboleth::XML::Literals::InvalidHandle)) + Iterator vals=attr->getSingleByteValues(); + if (!strcmp(hname,"REMOTE_USER") && vals.hasNext()) { - if (!bLocked) - pSite->g_AuthCache.lock(); // ---> Grab cache lock - pSite->g_AuthCache.remove(session_id); - pSite->g_AuthCache.unlock(); // ---> Release cache lock - delete entry; - - log.info("invaliding session due to shib:InvalidHandle code from AA"); - - // Redirect to WAYF. - string wayf("Location: "); - wayf+=pSite->g_WAYFLocation + "?shire=" + get_shire_location(pfc,pSite,targeturl.c_str()) + - "&target=" + url_encode(targeturl.c_str()) + "\r\n"; - wayf+="Set-Cookie: " + pSite->g_CookieName + "=; path=/; expires=19-Mar-1971 08:23:00 GMT\r\n"; - - // Insert the headers. - pfc->AddResponseHeaders(pfc,const_cast(wayf.c_str()),0); - pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0); - return SF_STATUS_REQ_FINISHED; + char* principal=const_cast(vals.next().c_str()); + pn->SetHeader(pfc,"remote-user:",principal); + pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0); + if (pfc->pFilterContext) + strcpy(static_cast(pfc->pFilterContext),principal); + } + else + { + string header; + for (int it = 0; vals.hasNext(); it++) { + string value = vals.next(); + for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) { + value.insert(pos, "\\"); + pos += 2; + } + if (it == 0) + header=value; + else + header=header + ';' + value; + } + pn->SetHeader(pfc,const_cast(hname),const_cast(header.c_str())); } - break; } - if (bLocked) - pSite->g_AuthCache.unlock(); - return shib_shar_error(pfc,e); - } - catch (XMLException& e) - { - if (bLocked) - pSite->g_AuthCache.unlock(); - auto_ptr msg(XMLString::transcode(e.getMessage())); - SAMLException ex(SAMLException::RESPONDER,msg.get()); - return shib_shar_error(pfc,ex); } + + // clean up memory + for (int k = 0; k < assertions.size(); k++) + delete assertions[k]; + delete sso_statement; + + return SF_STATUS_REQ_NEXT_NOTIFICATION; } catch(bad_alloc) { - xmsg="Out of memory."; - log.error("out of memory"); + return WriteClientError(pfc,"Out of Memory"); } catch(DWORD e) { if (e==ERROR_NO_DATA) - xmsg="A required variable or header was empty."; + return WriteClientError(pfc,"A required variable or header was empty."); else - xmsg="Server detected unexpected IIS error."; + WriteClientError(pfc,"Server detected unexpected IIS error."); } catch(...) { - xmsg="Server caught an unknown exception."; + WriteClientError(pfc,"Server caught an unknown exception."); } - // If we drop here, the exception handler set the proper message. - if (bLocked) - pSite->g_AuthCache.unlock(); - return WriteClientError(pfc,xmsg); + return WriteClientError(pfc,"Server reached unreachable code!"); } -- 2.1.4