-/*\r
- * Copyright 2001-2007 Internet2\r
- * \r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-/**\r
- * RemotedSessionCache.cpp\r
- * \r
- * SessionCache implementation that delegates to a remoted version.\r
- */\r
-\r
-#include "internal.h"\r
-#include "Application.h"\r
-#include "exceptions.h"\r
-#include "ServiceProvider.h"\r
-#include "SessionCache.h"\r
-#include "attribute/Attribute.h"\r
-#include "remoting/ListenerService.h"\r
-#include "util/SPConstants.h"\r
-\r
-#include <ctime>\r
-#include <sstream>\r
-#include <xmltooling/XMLToolingConfig.h>\r
-#include <xmltooling/util/DateTime.h>\r
-#include <xmltooling/util/NDC.h>\r
-#include <xmltooling/util/XMLHelper.h>\r
-\r
-using namespace shibsp;\r
-using namespace xmltooling;\r
-using namespace std;\r
-\r
-namespace shibsp {\r
-\r
- class RemotedCache;\r
- class RemotedSession : public virtual Session\r
- {\r
- public:\r
- RemotedSession(RemotedCache* cache, DDF& obj) : m_version(obj["version"].integer()), m_obj(obj),\r
- m_expires(0), m_lastAccess(time(NULL)), m_cache(cache), m_lock(NULL) {\r
- auto_ptr_XMLCh exp(m_obj["expires"].string());\r
- if (exp.get()) {\r
- DateTime iso(exp.get());\r
- iso.parseDateTime();\r
- m_expires = iso.getEpoch();\r
- }\r
-\r
- m_lock = Mutex::create();\r
- }\r
- \r
- ~RemotedSession() {\r
- delete m_lock;\r
- m_obj.destroy();\r
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
- }\r
- \r
- Lockable* lock() {\r
- m_lock->lock();\r
- return this;\r
- }\r
- void unlock() {\r
- m_lock->unlock();\r
- }\r
-\r
- const char* getID() const {\r
- return m_obj.name();\r
- }\r
- const char* getApplicationID() const {\r
- return m_obj["application_id"].string();\r
- }\r
- const char* getClientAddress() const {\r
- return m_obj["client_addr"].string();\r
- }\r
- const char* getEntityID() const {\r
- return m_obj["entity_id"].string();\r
- }\r
- const char* getProtocol() const {\r
- return m_obj["protocol"].string();\r
- }\r
- const char* getAuthnInstant() const {\r
- return m_obj["authn_instant"].string();\r
- }\r
- const char* getSessionIndex() const {\r
- return m_obj["session_index"].string();\r
- }\r
- const char* getAuthnContextClassRef() const {\r
- return m_obj["authncontext_class"].string();\r
- }\r
- const char* getAuthnContextDeclRef() const {\r
- return m_obj["authncontext_decl"].string();\r
- }\r
- const vector<Attribute*>& getAttributes() const {\r
- if (m_attributes.empty())\r
- unmarshallAttributes();\r
- return m_attributes;\r
- }\r
- const multimap<string,const Attribute*>& getIndexedAttributes() const {\r
- if (m_attributeIndex.empty()) {\r
- if (m_attributes.empty())\r
- unmarshallAttributes();\r
- for (vector<Attribute*>::const_iterator a = m_attributes.begin(); a != m_attributes.end(); ++a) {\r
- const vector<string>& aliases = (*a)->getAliases();\r
- for (vector<string>::const_iterator alias = aliases.begin(); alias != aliases.end(); ++alias)\r
- m_attributeIndex.insert(make_pair(*alias, *a));\r
- }\r
- }\r
- return m_attributeIndex;\r
- }\r
- const vector<const char*>& getAssertionIDs() const {\r
- if (m_ids.empty()) {\r
- DDF ids = m_obj["assertions"];\r
- DDF id = ids.first();\r
- while (id.isstring()) {\r
- m_ids.push_back(id.string());\r
- id = ids.next();\r
- }\r
- }\r
- return m_ids;\r
- }\r
- \r
- time_t expires() const { return m_expires; }\r
- time_t lastAccess() const { return m_lastAccess; }\r
- void validate(const Application& application, const char* client_addr, time_t* timeout);\r
-\r
- private:\r
- void unmarshallAttributes() const;\r
-\r
- int m_version;\r
- mutable DDF m_obj;\r
- mutable vector<Attribute*> m_attributes;\r
- mutable multimap<string,const Attribute*> m_attributeIndex;\r
- mutable vector<const char*> m_ids;\r
- time_t m_expires,m_lastAccess;\r
- RemotedCache* m_cache;\r
- Mutex* m_lock;\r
- };\r
- \r
- class RemotedCache : public SessionCache\r
- {\r
- public:\r
- RemotedCache(const DOMElement* e);\r
- ~RemotedCache();\r
- \r
- Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL);\r
- void remove(const char* key, const Application& application);\r
- \r
- void cleanup();\r
- \r
- Category& m_log;\r
- private:\r
- const DOMElement* m_root; // Only valid during initialization\r
- RWLock* m_lock;\r
- map<string,RemotedSession*> m_hashtable;\r
- \r
- void dormant(const char* key);\r
- static void* cleanup_fn(void*);\r
- bool shutdown;\r
- CondWait* shutdown_wait;\r
- Thread* cleanup_thread;\r
- };\r
-\r
- SessionCache* SHIBSP_DLLLOCAL RemotedCacheFactory(const DOMElement* const & e)\r
- {\r
- return new RemotedCache(e);\r
- }\r
-}\r
-\r
-void RemotedSession::unmarshallAttributes() const\r
-{\r
- Attribute* attribute;\r
- DDF attrs = m_obj["attributes"];\r
- DDF attr = attrs.first();\r
- while (!attr.isnull()) {\r
- try {\r
- attribute = Attribute::unmarshall(attr);\r
- m_attributes.push_back(attribute);\r
- if (m_cache->m_log.isDebugEnabled())\r
- m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",\r
- attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");\r
- }\r
- catch (AttributeException& ex) {\r
- const char* id = attr.first().name();\r
- m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());\r
- }\r
- attr = attrs.next();\r
- }\r
-}\r
-\r
-void RemotedSession::validate(const Application& application, const char* client_addr, time_t* timeout)\r
-{\r
- // Basic expiration?\r
- time_t now = time(NULL);\r
- if (now > m_expires) {\r
- m_cache->m_log.info("session expired (ID: %s)", getID());\r
- throw opensaml::RetryableProfileException("Your session has expired, and you must re-authenticate.");\r
- }\r
-\r
- // Address check?\r
- if (client_addr) {\r
- if (m_cache->m_log.isDebugEnabled())\r
- m_cache->m_log.debug("comparing client address %s against %s", client_addr, getClientAddress());\r
- if (strcmp(getClientAddress(),client_addr)) {\r
- m_cache->m_log.warn("client address mismatch");\r
- throw opensaml::RetryableProfileException(\r
- "Your IP address ($1) does not match the address recorded at the time the session was established.",\r
- params(1,client_addr)\r
- );\r
- }\r
- }\r
-\r
- if (!timeout)\r
- return;\r
- \r
- DDF in("touch::"REMOTED_SESSION_CACHE"::SessionCache"), out;\r
- DDFJanitor jin(in);\r
- in.structure();\r
- in.addmember("key").string(getID());\r
- in.addmember("version").integer(m_obj["version"].integer());\r
- if (*timeout) {\r
- // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps. \r
-#ifndef HAVE_GMTIME_R\r
- struct tm* ptime=gmtime(timeout);\r
-#else\r
- struct tm res;\r
- struct tm* ptime=gmtime_r(timeout,&res);\r
-#endif\r
- char timebuf[32];\r
- strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
- in.addmember("timeout").string(timebuf);\r
- }\r
-\r
- try {\r
- out=application.getServiceProvider().getListenerService()->send(in);\r
- }\r
- catch (...) {\r
- out.destroy();\r
- throw;\r
- }\r
-\r
- if (out.isstruct()) {\r
- // We got an updated record back.\r
- m_ids.clear();\r
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
- m_attributes.clear();\r
- m_attributeIndex.clear();\r
- m_obj.destroy();\r
- m_obj = out;\r
- }\r
-\r
- m_lastAccess = now;\r
-}\r
-\r
-RemotedCache::RemotedCache(const DOMElement* e)\r
- : SessionCache(e, 900), m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), m_root(e), m_lock(NULL), shutdown(false)\r
-{\r
- if (!SPConfig::getConfig().getServiceProvider()->getListenerService())\r
- throw ConfigurationException("RemotedCacheService requires a ListenerService, but none available.");\r
- \r
- m_lock = RWLock::create();\r
- shutdown_wait = CondWait::create();\r
- cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
-}\r
-\r
-RemotedCache::~RemotedCache()\r
-{\r
- // Shut down the cleanup thread and let it know...\r
- shutdown = true;\r
- shutdown_wait->signal();\r
- cleanup_thread->join(NULL);\r
-\r
- for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,RemotedSession>());\r
- delete m_lock;\r
- delete shutdown_wait;\r
-}\r
-\r
-Session* RemotedCache::find(const char* key, const Application& application, const char* client_addr, time_t* timeout)\r
-{\r
-#ifdef _DEBUG\r
- xmltooling::NDC ndc("find");\r
-#endif\r
-\r
- RemotedSession* session=NULL;\r
- m_log.debug("searching local cache for session (%s)", key);\r
- m_lock->rdlock();\r
- map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
- if (i==m_hashtable.end()) {\r
- m_lock->unlock();\r
- m_log.debug("session not found locally, searching remote cache");\r
-\r
- DDF in("find::"REMOTED_SESSION_CACHE"::SessionCache"), out;\r
- DDFJanitor jin(in);\r
- in.structure();\r
- in.addmember("key").string(key);\r
- in.addmember("application_id").string(application.getId());\r
- if (timeout && *timeout) {\r
- // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps. \r
-#ifndef HAVE_GMTIME_R\r
- struct tm* ptime=gmtime(timeout);\r
-#else\r
- struct tm res;\r
- struct tm* ptime=gmtime_r(timeout,&res);\r
-#endif\r
- char timebuf[32];\r
- strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
- in.addmember("timeout").string(timebuf);\r
- }\r
- \r
- try {\r
- out=application.getServiceProvider().getListenerService()->send(in);\r
- if (!out.isstruct()) {\r
- out.destroy();\r
- m_log.debug("session not found in remote cache");\r
- return NULL;\r
- }\r
- \r
- // Wrap the results in a local entry and save it.\r
- session = new RemotedSession(this, out);\r
- // The remote end has handled timeout issues, we handle address and expiration checks.\r
- timeout = NULL;\r
- }\r
- catch (...) {\r
- out.destroy();\r
- throw;\r
- }\r
-\r
- // Lock for writing and repeat the search to avoid duplication.\r
- m_lock->wrlock();\r
- SharedLock shared(m_lock, false);\r
- if (m_hashtable.count(key)) {\r
- // We're using an existing session entry.\r
- delete session;\r
- session = m_hashtable[key];\r
- session->lock();\r
- }\r
- else {\r
- m_hashtable[key]=session;\r
- session->lock();\r
- }\r
- }\r
- else {\r
- // Save off and lock the session.\r
- session = i->second;\r
- session->lock();\r
- m_lock->unlock();\r
- \r
- m_log.debug("session found locally, validating it for use");\r
- }\r
-\r
- if (!XMLString::equals(session->getApplicationID(), application.getId())) {\r
- m_log.error("an application (%s) tried to access another application's session", application.getId());\r
- session->unlock();\r
- return NULL;\r
- }\r
-\r
- // Verify currency and update the timestamp if indicated by caller.\r
- try {\r
- session->validate(application, client_addr, timeout);\r
- }\r
- catch (...) {\r
- session->unlock();\r
- remove(key, application);\r
- throw;\r
- }\r
- \r
- return session;\r
-}\r
-\r
-void RemotedCache::remove(const char* key, const Application& application)\r
-{\r
- // Take care of local copy.\r
- dormant(key);\r
- \r
- // Now remote...\r
- DDF in("remove::"REMOTED_SESSION_CACHE"::SessionCache");\r
- DDFJanitor jin(in);\r
- in.structure();\r
- in.addmember("key").string(key);\r
- in.addmember("application_id").string(application.getId());\r
- \r
- DDF out = application.getServiceProvider().getListenerService()->send(in);\r
- out.destroy();\r
-}\r
-\r
-void RemotedCache::dormant(const char* key)\r
-{\r
-#ifdef _DEBUG\r
- xmltooling::NDC ndc("dormant");\r
-#endif\r
-\r
- m_log.debug("deleting local copy of session (%s)", key);\r
-\r
- // lock the cache for writing, which means we know nobody is sitting in find()\r
- m_lock->wrlock();\r
-\r
- // grab the entry from the table\r
- map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
- if (i==m_hashtable.end()) {\r
- m_lock->unlock();\r
- return;\r
- }\r
-\r
- // ok, remove the entry and lock it\r
- RemotedSession* entry=i->second;\r
- m_hashtable.erase(key);\r
- entry->lock();\r
- \r
- // unlock the cache\r
- m_lock->unlock();\r
-\r
- // we can release the cache entry lock because we know we're not in the cache anymore\r
- entry->unlock();\r
-\r
- delete entry;\r
-}\r
-\r
-void RemotedCache::cleanup()\r
-{\r
-#ifdef _DEBUG\r
- xmltooling::NDC ndc("cleanup");\r
-#endif\r
-\r
- Mutex* mutex = Mutex::create();\r
- \r
- // Load our configuration details...\r
- static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
- const XMLCh* tag=m_root ? m_root->getAttributeNS(NULL,cleanupInterval) : NULL;\r
- int rerun_timer = 900;\r
- if (tag && *tag)\r
- rerun_timer = XMLString::parseInt(tag);\r
- if (rerun_timer <= 0)\r
- rerun_timer = 900;\r
-\r
- mutex->lock();\r
-\r
- m_log.info("cleanup thread started...run every %d secs; timeout after %d secs", rerun_timer, m_cacheTimeout);\r
-\r
- while (!shutdown) {\r
- shutdown_wait->timedwait(mutex,rerun_timer);\r
- if (shutdown)\r
- break;\r
-\r
- // Ok, let's run through the cleanup process and clean out\r
- // really old sessions. This is a two-pass process. The\r
- // first pass is done holding a read-lock while we iterate over\r
- // the cache. The second pass doesn't need a lock because\r
- // the 'deletes' will lock the cache.\r
- \r
- // Pass 1: iterate over the map and find all entries that have not been\r
- // used in X hours\r
- vector<string> stale_keys;\r
- time_t stale = time(NULL) - m_cacheTimeout;\r
- \r
- m_log.debug("cleanup thread running");\r
-\r
- m_lock->rdlock();\r
- for (map<string,RemotedSession*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); ++i) {\r
- // If the last access was BEFORE the stale timeout...\r
- i->second->lock();\r
- time_t last=i->second->lastAccess();\r
- i->second->unlock();\r
- if (last < stale)\r
- stale_keys.push_back(i->first);\r
- }\r
- m_lock->unlock();\r
- \r
- if (!stale_keys.empty()) {\r
- m_log.info("purging %d old sessions", stale_keys.size());\r
- \r
- // Pass 2: walk through the list of stale entries and remove them from the cache\r
- for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); ++j)\r
- dormant(j->c_str());\r
- }\r
-\r
- m_log.debug("cleanup thread completed");\r
- }\r
-\r
- m_log.info("cleanup thread exiting");\r
-\r
- mutex->unlock();\r
- delete mutex;\r
- Thread::exit(NULL);\r
-}\r
-\r
-void* RemotedCache::cleanup_fn(void* cache_p)\r
-{\r
- RemotedCache* cache = reinterpret_cast<RemotedCache*>(cache_p);\r
-\r
-#ifndef WIN32\r
- // First, let's block all signals \r
- Thread::mask_all_signals();\r
-#endif\r
-\r
- // Now run the cleanup process.\r
- cache->cleanup();\r
- return NULL;\r
-}\r
+/*
+ * Copyright 2001-2007 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * RemotedSessionCache.cpp
+ *
+ * SessionCache implementation that delegates to a remoted version.
+ */
+
+#include "internal.h"
+#include "Application.h"
+#include "exceptions.h"
+#include "ServiceProvider.h"
+#include "SessionCache.h"
+#include "attribute/Attribute.h"
+#include "remoting/ListenerService.h"
+#include "util/SPConstants.h"
+
+#include <ctime>
+#include <sstream>
+#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/DateTime.h>
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/XMLHelper.h>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace std;
+
+namespace shibsp {
+
+ class RemotedCache;
+ class RemotedSession : public virtual Session
+ {
+ public:
+ RemotedSession(RemotedCache* cache, DDF& obj) : m_version(obj["version"].integer()), m_obj(obj),
+ m_expires(0), m_lastAccess(time(NULL)), m_cache(cache), m_lock(NULL) {
+ auto_ptr_XMLCh exp(m_obj["expires"].string());
+ if (exp.get()) {
+ DateTime iso(exp.get());
+ iso.parseDateTime();
+ m_expires = iso.getEpoch();
+ }
+
+ m_lock = Mutex::create();
+ }
+
+ ~RemotedSession() {
+ delete m_lock;
+ m_obj.destroy();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ }
+
+ Lockable* lock() {
+ m_lock->lock();
+ return this;
+ }
+ void unlock() {
+ m_lock->unlock();
+ }
+
+ const char* getID() const {
+ return m_obj.name();
+ }
+ const char* getApplicationID() const {
+ return m_obj["application_id"].string();
+ }
+ const char* getClientAddress() const {
+ return m_obj["client_addr"].string();
+ }
+ const char* getEntityID() const {
+ return m_obj["entity_id"].string();
+ }
+ const char* getProtocol() const {
+ return m_obj["protocol"].string();
+ }
+ const char* getAuthnInstant() const {
+ return m_obj["authn_instant"].string();
+ }
+ const char* getSessionIndex() const {
+ return m_obj["session_index"].string();
+ }
+ const char* getAuthnContextClassRef() const {
+ return m_obj["authncontext_class"].string();
+ }
+ const char* getAuthnContextDeclRef() const {
+ return m_obj["authncontext_decl"].string();
+ }
+ const vector<Attribute*>& getAttributes() const {
+ if (m_attributes.empty())
+ unmarshallAttributes();
+ return m_attributes;
+ }
+ const multimap<string,const Attribute*>& getIndexedAttributes() const {
+ if (m_attributeIndex.empty()) {
+ if (m_attributes.empty())
+ unmarshallAttributes();
+ for (vector<Attribute*>::const_iterator a = m_attributes.begin(); a != m_attributes.end(); ++a) {
+ const vector<string>& aliases = (*a)->getAliases();
+ for (vector<string>::const_iterator alias = aliases.begin(); alias != aliases.end(); ++alias)
+ m_attributeIndex.insert(multimap<string,const Attribute*>::value_type(*alias, *a));
+ }
+ }
+ return m_attributeIndex;
+ }
+ const vector<const char*>& getAssertionIDs() const {
+ if (m_ids.empty()) {
+ DDF ids = m_obj["assertions"];
+ DDF id = ids.first();
+ while (id.isstring()) {
+ m_ids.push_back(id.string());
+ id = ids.next();
+ }
+ }
+ return m_ids;
+ }
+
+ time_t expires() const { return m_expires; }
+ time_t lastAccess() const { return m_lastAccess; }
+ void validate(const Application& application, const char* client_addr, time_t* timeout);
+
+ private:
+ void unmarshallAttributes() const;
+
+ int m_version;
+ mutable DDF m_obj;
+ mutable vector<Attribute*> m_attributes;
+ mutable multimap<string,const Attribute*> m_attributeIndex;
+ mutable vector<const char*> m_ids;
+ time_t m_expires,m_lastAccess;
+ RemotedCache* m_cache;
+ Mutex* m_lock;
+ };
+
+ class RemotedCache : public SessionCache
+ {
+ public:
+ RemotedCache(const DOMElement* e);
+ ~RemotedCache();
+
+ Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL);
+ void remove(const char* key, const Application& application);
+
+ void cleanup();
+
+ Category& m_log;
+ private:
+ const DOMElement* m_root; // Only valid during initialization
+ RWLock* m_lock;
+ map<string,RemotedSession*> m_hashtable;
+
+ void dormant(const char* key);
+ static void* cleanup_fn(void*);
+ bool shutdown;
+ CondWait* shutdown_wait;
+ Thread* cleanup_thread;
+ };
+
+ SessionCache* SHIBSP_DLLLOCAL RemotedCacheFactory(const DOMElement* const & e)
+ {
+ return new RemotedCache(e);
+ }
+}
+
+void RemotedSession::unmarshallAttributes() const
+{
+ Attribute* attribute;
+ DDF attrs = m_obj["attributes"];
+ DDF attr = attrs.first();
+ while (!attr.isnull()) {
+ try {
+ attribute = Attribute::unmarshall(attr);
+ m_attributes.push_back(attribute);
+ if (m_cache->m_log.isDebugEnabled())
+ m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",
+ attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
+ }
+ catch (AttributeException& ex) {
+ const char* id = attr.first().name();
+ m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
+ }
+ attr = attrs.next();
+ }
+}
+
+void RemotedSession::validate(const Application& application, const char* client_addr, time_t* timeout)
+{
+ // Basic expiration?
+ time_t now = time(NULL);
+ if (now > m_expires) {
+ m_cache->m_log.info("session expired (ID: %s)", getID());
+ throw opensaml::RetryableProfileException("Your session has expired, and you must re-authenticate.");
+ }
+
+ // Address check?
+ if (client_addr) {
+ if (m_cache->m_log.isDebugEnabled())
+ m_cache->m_log.debug("comparing client address %s against %s", client_addr, getClientAddress());
+ if (strcmp(getClientAddress(),client_addr)) {
+ m_cache->m_log.warn("client address mismatch");
+ throw opensaml::RetryableProfileException(
+ "Your IP address ($1) does not match the address recorded at the time the session was established.",
+ params(1,client_addr)
+ );
+ }
+ }
+
+ if (!timeout)
+ return;
+
+ DDF in("touch::"REMOTED_SESSION_CACHE"::SessionCache"), out;
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(getID());
+ in.addmember("version").integer(m_obj["version"].integer());
+ if (*timeout) {
+ // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
+#ifndef HAVE_GMTIME_R
+ struct tm* ptime=gmtime(timeout);
+#else
+ struct tm res;
+ struct tm* ptime=gmtime_r(timeout,&res);
+#endif
+ char timebuf[32];
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+ in.addmember("timeout").string(timebuf);
+ }
+
+ try {
+ out=application.getServiceProvider().getListenerService()->send(in);
+ }
+ catch (...) {
+ out.destroy();
+ throw;
+ }
+
+ if (out.isstruct()) {
+ // We got an updated record back.
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ m_obj.destroy();
+ m_obj = out;
+ }
+
+ m_lastAccess = now;
+}
+
+RemotedCache::RemotedCache(const DOMElement* e)
+ : SessionCache(e, 900), m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), m_root(e), m_lock(NULL), shutdown(false)
+{
+ if (!SPConfig::getConfig().getServiceProvider()->getListenerService())
+ throw ConfigurationException("RemotedCacheService requires a ListenerService, but none available.");
+
+ m_lock = RWLock::create();
+ shutdown_wait = CondWait::create();
+ cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
+}
+
+RemotedCache::~RemotedCache()
+{
+ // Shut down the cleanup thread and let it know...
+ shutdown = true;
+ shutdown_wait->signal();
+ cleanup_thread->join(NULL);
+
+ for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,RemotedSession>());
+ delete m_lock;
+ delete shutdown_wait;
+}
+
+Session* RemotedCache::find(const char* key, const Application& application, const char* client_addr, time_t* timeout)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("find");
+#endif
+
+ RemotedSession* session=NULL;
+ m_log.debug("searching local cache for session (%s)", key);
+ m_lock->rdlock();
+ map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);
+ if (i==m_hashtable.end()) {
+ m_lock->unlock();
+ m_log.debug("session not found locally, searching remote cache");
+
+ DDF in("find::"REMOTED_SESSION_CACHE"::SessionCache"), out;
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(key);
+ in.addmember("application_id").string(application.getId());
+ if (timeout && *timeout) {
+ // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
+#ifndef HAVE_GMTIME_R
+ struct tm* ptime=gmtime(timeout);
+#else
+ struct tm res;
+ struct tm* ptime=gmtime_r(timeout,&res);
+#endif
+ char timebuf[32];
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+ in.addmember("timeout").string(timebuf);
+ }
+
+ try {
+ out=application.getServiceProvider().getListenerService()->send(in);
+ if (!out.isstruct()) {
+ out.destroy();
+ m_log.debug("session not found in remote cache");
+ return NULL;
+ }
+
+ // Wrap the results in a local entry and save it.
+ session = new RemotedSession(this, out);
+ // The remote end has handled timeout issues, we handle address and expiration checks.
+ timeout = NULL;
+ }
+ catch (...) {
+ out.destroy();
+ throw;
+ }
+
+ // Lock for writing and repeat the search to avoid duplication.
+ m_lock->wrlock();
+ SharedLock shared(m_lock, false);
+ if (m_hashtable.count(key)) {
+ // We're using an existing session entry.
+ delete session;
+ session = m_hashtable[key];
+ session->lock();
+ }
+ else {
+ m_hashtable[key]=session;
+ session->lock();
+ }
+ }
+ else {
+ // Save off and lock the session.
+ session = i->second;
+ session->lock();
+ m_lock->unlock();
+
+ m_log.debug("session found locally, validating it for use");
+ }
+
+ if (!XMLString::equals(session->getApplicationID(), application.getId())) {
+ m_log.error("an application (%s) tried to access another application's session", application.getId());
+ session->unlock();
+ return NULL;
+ }
+
+ // Verify currency and update the timestamp if indicated by caller.
+ try {
+ session->validate(application, client_addr, timeout);
+ }
+ catch (...) {
+ session->unlock();
+ remove(key, application);
+ throw;
+ }
+
+ return session;
+}
+
+void RemotedCache::remove(const char* key, const Application& application)
+{
+ // Take care of local copy.
+ dormant(key);
+
+ // Now remote...
+ DDF in("remove::"REMOTED_SESSION_CACHE"::SessionCache");
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(key);
+ in.addmember("application_id").string(application.getId());
+
+ DDF out = application.getServiceProvider().getListenerService()->send(in);
+ out.destroy();
+}
+
+void RemotedCache::dormant(const char* key)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("dormant");
+#endif
+
+ m_log.debug("deleting local copy of session (%s)", key);
+
+ // lock the cache for writing, which means we know nobody is sitting in find()
+ m_lock->wrlock();
+
+ // grab the entry from the table
+ map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);
+ if (i==m_hashtable.end()) {
+ m_lock->unlock();
+ return;
+ }
+
+ // ok, remove the entry and lock it
+ RemotedSession* entry=i->second;
+ m_hashtable.erase(key);
+ entry->lock();
+
+ // unlock the cache
+ m_lock->unlock();
+
+ // we can release the cache entry lock because we know we're not in the cache anymore
+ entry->unlock();
+
+ delete entry;
+}
+
+void RemotedCache::cleanup()
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("cleanup");
+#endif
+
+ Mutex* mutex = Mutex::create();
+
+ // Load our configuration details...
+ static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
+ const XMLCh* tag=m_root ? m_root->getAttributeNS(NULL,cleanupInterval) : NULL;
+ int rerun_timer = 900;
+ if (tag && *tag)
+ rerun_timer = XMLString::parseInt(tag);
+ if (rerun_timer <= 0)
+ rerun_timer = 900;
+
+ mutex->lock();
+
+ m_log.info("cleanup thread started...run every %d secs; timeout after %d secs", rerun_timer, m_cacheTimeout);
+
+ while (!shutdown) {
+ shutdown_wait->timedwait(mutex,rerun_timer);
+ if (shutdown)
+ break;
+
+ // Ok, let's run through the cleanup process and clean out
+ // really old sessions. This is a two-pass process. The
+ // first pass is done holding a read-lock while we iterate over
+ // the cache. The second pass doesn't need a lock because
+ // the 'deletes' will lock the cache.
+
+ // Pass 1: iterate over the map and find all entries that have not been
+ // used in X hours
+ vector<string> stale_keys;
+ time_t stale = time(NULL) - m_cacheTimeout;
+
+ m_log.debug("cleanup thread running");
+
+ m_lock->rdlock();
+ for (map<string,RemotedSession*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); ++i) {
+ // If the last access was BEFORE the stale timeout...
+ i->second->lock();
+ time_t last=i->second->lastAccess();
+ i->second->unlock();
+ if (last < stale)
+ stale_keys.push_back(i->first);
+ }
+ m_lock->unlock();
+
+ if (!stale_keys.empty()) {
+ m_log.info("purging %d old sessions", stale_keys.size());
+
+ // Pass 2: walk through the list of stale entries and remove them from the cache
+ for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); ++j)
+ dormant(j->c_str());
+ }
+
+ m_log.debug("cleanup thread completed");
+ }
+
+ m_log.info("cleanup thread exiting");
+
+ mutex->unlock();
+ delete mutex;
+ Thread::exit(NULL);
+}
+
+void* RemotedCache::cleanup_fn(void* cache_p)
+{
+ RemotedCache* cache = reinterpret_cast<RemotedCache*>(cache_p);
+
+#ifndef WIN32
+ // First, let's block all signals
+ Thread::mask_all_signals();
+#endif
+
+ // Now run the cleanup process.
+ cache->cleanup();
+ return NULL;
+}