2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * shib-ccache.cpp -- in-memory session cache plugin
29 #include <log4cpp/Category.hh>
30 #include <shibsp/SPConfig.h>
37 #ifdef HAVE_LIBDMALLOCXX
41 using namespace shibsp;
42 using namespace shibtarget;
43 using namespace shibboleth;
45 using namespace xmltooling;
46 using namespace log4cpp;
49 static const XMLCh cleanupInterval[] =
50 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
51 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
53 static const XMLCh cacheTimeout[] =
54 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
55 chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
57 static const XMLCh AAConnectTimeout[] =
58 { chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
59 chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
61 static const XMLCh AATimeout[] =
62 { chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
64 static const XMLCh defaultLifetime[] =
65 { chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
66 chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
68 static const XMLCh retryInterval[] =
69 { chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
70 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
72 static const XMLCh strictValidity[] =
73 { chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
74 chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
76 static const XMLCh propagateErrors[] =
77 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
78 chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
80 static const XMLCh writeThrough[] =
81 { chLatin_w, chLatin_r, chLatin_i, chLatin_t, chLatin_e,
82 chLatin_T, chLatin_h, chLatin_r, chLatin_o, chLatin_u, chLatin_g, chLatin_h, chNull
87 * Stubbed out, inproc version of an ISessionCacheEntry
89 class StubCacheEntry : public virtual ISessionCacheEntry
92 StubCacheEntry(Category* log) : m_log(log), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
93 StubCacheEntry(DDF& obj, Category* log)
94 : m_log(log), m_obj(obj), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
95 ~StubCacheEntry() { m_obj.destroy(); delete m_pSubject; delete m_pUnfiltered; delete m_pFiltered; }
97 void unlock() { delete this; }
98 const char* getClientAddress() const { return m_obj["client_address"].string(); }
99 const char* getProviderId() const { return m_obj["provider_id"].string(); }
100 const char* getAuthnContext() const { return m_obj["authn_context"].string(); }
101 pair<const char*,const SAMLSubject*> getSubject(bool xml=true, bool obj=false) const;
102 pair<const char*,const SAMLResponse*> getTokens(bool xml=true, bool obj=false) const;
103 pair<const char*,const SAMLResponse*> getFilteredTokens(bool xml=true, bool obj=false) const;
108 mutable SAMLSubject* m_pSubject;
109 mutable SAMLResponse* m_pUnfiltered;
110 mutable SAMLResponse* m_pFiltered;
113 pair<const char*,const SAMLSubject*> StubCacheEntry::getSubject(bool xml, bool obj) const
115 const char* raw=m_obj["subject"].string();
116 pair<const char*,const SAMLSubject*> ret=pair<const char*,const SAMLSubject*>(NULL,NULL);
121 istringstream in(raw);
122 m_log->debugStream() << "decoding subject: " << (raw ? raw : "(none)") << CategoryStream::ENDLINE;
123 m_pSubject=raw ? new SAMLSubject(in) : NULL;
125 ret.second=m_pSubject;
130 pair<const char*,const SAMLResponse*> StubCacheEntry::getTokens(bool xml, bool obj) const
132 const char* unfiltered=m_obj["tokens.unfiltered"].string();
133 pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
135 ret.first=unfiltered;
137 if (!m_pUnfiltered) {
139 istringstream in(unfiltered);
140 m_log->debugStream() << "decoding unfiltered tokens: " << unfiltered << CategoryStream::ENDLINE;
141 m_pUnfiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
144 ret.second=m_pUnfiltered;
149 pair<const char*,const SAMLResponse*> StubCacheEntry::getFilteredTokens(bool xml, bool obj) const
151 const char* filtered=m_obj["tokens.filtered"].string();
153 return getTokens(xml,obj);
154 pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
159 istringstream in(filtered);
160 m_log->debugStream() << "decoding filtered tokens: " << filtered << CategoryStream::ENDLINE;
161 m_pFiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
163 ret.second=m_pFiltered;
169 * Remoting front-half of session cache, drops out in single process deployments.
170 * TODO: Add buffering of frequently-used entries.
172 class StubCache : public virtual ISessionCache
175 StubCache(const DOMElement* e);
178 const IApplication* application,
179 const IEntityDescriptor* source,
180 const char* client_addr,
181 const SAMLSubject* subject,
182 const char* authnContext,
183 const SAMLResponse* tokens
185 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
186 void remove(const char* key, const IApplication* application, const char* client_addr);
188 bool setBackingStore(ISessionCacheStore*) { return false; }
194 StubCache::StubCache(const DOMElement* e) : m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")) {}
197 * The public methods are remoted using the message passing system.
198 * In practice, insert is unlikely to be used remotely, but just in case...
201 string StubCache::insert(
202 const IApplication* application,
203 const IEntityDescriptor* source,
204 const char* client_addr,
205 const SAMLSubject* subject,
206 const char* authnContext,
207 const SAMLResponse* tokens
210 DDF in("SessionCache::insert"),out;
211 DDFJanitor jin(in),jout(out);
213 in.addmember("application_id").string(application->getId());
214 in.addmember("client_address").string(client_addr);
215 auto_ptr_char provid(source->getId());
216 in.addmember("provider_id").string(provid.get());
217 in.addmember("major_version").integer(1);
218 in.addmember("minor_version").integer(tokens->getMinorVersion());
219 in.addmember("authn_context").string(authnContext);
223 in.addmember("subject").string(os.str().c_str());
226 in.addmember("tokens.unfiltered").string(os.str().c_str());
228 out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
229 if (out["key"].isstring())
230 return out["key"].string();
231 throw InvalidSessionException("A remoted cache insertion operation did not return a usable session key.");
234 ISessionCacheEntry* StubCache::find(const char* key, const IApplication* application, const char* client_addr)
236 DDF in("SessionCache::find"),out;
239 in.addmember("key").string(key);
240 in.addmember("application_id").string(application->getId());
241 in.addmember("client_address").string(client_addr);
244 out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
245 if (!out.isstruct()) {
250 // Wrap the results in a stub entry and return it to the caller.
251 return new StubCacheEntry(out,m_log);
259 void StubCache::remove(const char* key, const IApplication* application, const char* client_addr)
261 DDF in("SessionCache::remove");
264 in.addmember("key").string(key);
265 in.addmember("application_id").string(application->getId());
266 in.addmember("client_address").string(client_addr);
268 ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
272 * Long-lived cache entries that store the actual sessions and
273 * wrap attribute query/refresh/filtering
275 class MemorySessionCache;
276 class MemorySessionCacheEntry : public virtual ISessionCacheEntry, public virtual StubCacheEntry
279 MemorySessionCacheEntry(
280 MemorySessionCache* cache,
282 const IApplication* application,
283 const IEntityDescriptor* source,
284 const char* client_addr,
285 const SAMLSubject* subject,
286 const char* authnContext,
287 const SAMLResponse* tokens
289 MemorySessionCacheEntry(
290 MemorySessionCache* cache,
292 const IApplication* application,
293 const IEntityDescriptor* source,
294 const char* client_addr,
296 const char* authnContext,
303 ~MemorySessionCacheEntry();
305 void lock() { m_lock->lock(); }
306 void unlock() { m_lock->unlock(); }
308 HRESULT isValid(const IApplication* application, const char* client_addr) const;
309 void populate(const IApplication* application, const IEntityDescriptor* source, bool initial=false) const;
310 bool checkApplication(const IApplication* application) { return (m_obj["application_id"]==application->getId()); }
311 time_t created() const { return m_sessionCreated; }
312 time_t lastAccess() const { return m_lastAccess; }
313 const DDF& getDDF() const { return m_obj; }
316 bool hasAttributes(const SAMLResponse& r) const;
317 time_t calculateExpiration(const SAMLResponse& r) const;
318 pair<SAMLResponse*,SAMLResponse*> getNewResponse(const IApplication* application, const IEntityDescriptor* source) const;
319 SAMLResponse* filter(const SAMLResponse* r, const IApplication* application, const IEntityDescriptor* source) const;
321 time_t m_sessionCreated;
322 mutable time_t m_responseExpiration, m_lastAccess, m_lastRetry;
324 MemorySessionCache* m_cache;
329 * The actual in-memory session cache implementation.
331 class MemorySessionCache : public virtual ISessionCache, public virtual Remoted
334 MemorySessionCache(const DOMElement* e);
335 virtual ~MemorySessionCache();
337 DDF receive(const DDF& in);
340 const IApplication* application,
341 const IEntityDescriptor* source,
342 const char* client_addr,
343 const SAMLSubject* subject,
344 const char* authnContext,
345 const SAMLResponse* tokens
347 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
348 void remove(const char* key, const IApplication* application, const char* client_addr);
352 bool setBackingStore(ISessionCacheStore* store);
355 const DOMElement* m_root; // Only valid during initialization
357 map<string,MemorySessionCacheEntry*> m_hashtable;
360 Remoted* restoreInsert;
361 Remoted* restoreFind;
362 Remoted* restoreRemove;
363 ISessionCacheStore* m_sink;
365 void dormant(const char* key);
366 static void* cleanup_fcn(void*);
368 CondWait* shutdown_wait;
369 Thread* cleanup_thread;
371 // extracted config settings
372 unsigned int m_AATimeout,m_AAConnectTimeout;
373 unsigned int m_defaultLifetime,m_retryInterval;
374 bool m_strictValidity,m_propagateErrors,m_writeThrough;
375 friend class MemorySessionCacheEntry;
378 MemorySessionCacheEntry::MemorySessionCacheEntry(
379 MemorySessionCache* cache,
381 const IApplication* application,
382 const IEntityDescriptor* source,
383 const char* client_addr,
384 const SAMLSubject* subject,
385 const char* authnContext,
386 const SAMLResponse* tokens
387 ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
389 m_sessionCreated = m_lastAccess = time(NULL);
391 // Store session properties in DDF.
392 m_obj=DDF(NULL).structure();
393 m_obj.addmember("key").string(key);
394 m_obj.addmember("client_address").string(client_addr);
395 m_obj.addmember("application_id").string(application->getId());
396 auto_ptr_char pid(source->getId());
397 m_obj.addmember("provider_id").string(pid.get());
398 m_obj.addmember("major_version").integer(1);
399 m_obj.addmember("minor_version").integer(tokens->getMinorVersion());
401 // Save the subject as XML.
404 m_obj.addmember("subject").string(os.str().c_str());
406 // Save the authn method.
407 m_obj.addmember("authn_context").string(authnContext);
409 // Serialize unfiltered assertions.
412 m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
414 if (hasAttributes(*tokens)) {
415 // Filter attributes in the response.
416 auto_ptr<SAMLResponse> filtered(filter(tokens, application, source));
418 // Calculate expiration.
419 m_responseExpiration=calculateExpiration(*(filtered.get()));
421 // Serialize filtered assertions (if changes were made).
423 os << *(filtered.get());
424 string fstr=os.str();
425 if (fstr.length() != m_obj["tokens.unfiltered"].strlen())
426 m_obj.addmember("tokens.filtered").string(fstr.c_str());
428 // Save actual objects only if we're running inprocess. The subject needs to be
429 // owned by the entry, so we'll defer creation of a cloned copy.
430 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
431 if (m_obj["tokens.filtered"].isstring())
432 m_pFiltered=filtered.release();
436 m_lock = Mutex::create();
438 if (m_log->isDebugEnabled()) {
439 m_log->debug("new cache entry created: SessionID (%s) IdP (%s) Address (%s)", key, pid.get(), client_addr);
442 // Transaction Logging
443 auto_ptr_char hname(subject->getNameIdentifier()->getName());
444 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
445 stc.getTransactionLog().infoStream() <<
446 "New session (ID: " <<
448 ") with (applicationId: " <<
449 application->getId() <<
450 ") for principal from (IdP: " <<
452 ") at (ClientAddress: " <<
454 ") with (NameIdentifier: " <<
457 stc.releaseTransactionLog();
460 MemorySessionCacheEntry::MemorySessionCacheEntry(
461 MemorySessionCache* cache,
463 const IApplication* application,
464 const IEntityDescriptor* source,
465 const char* client_addr,
467 const char* authnContext,
473 ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
475 m_sessionCreated = created;
476 m_lastAccess = accessed;
478 // Reconstitute the tokens for filtering.
479 istringstream is(tokens);
480 auto_ptr<SAMLResponse> unfiltered(new SAMLResponse(is,minorVersion));
482 // Store session properties in DDF.
483 m_obj=DDF(NULL).structure();
484 m_obj.addmember("key").string(key);
485 m_obj.addmember("client_address").string(client_addr);
486 m_obj.addmember("application_id").string(application->getId());
487 auto_ptr_char pid(source->getId());
488 m_obj.addmember("provider_id").string(pid.get());
489 m_obj.addmember("subject").string(subject);
490 m_obj.addmember("authn_context").string(authnContext);
491 m_obj.addmember("tokens.unfiltered").string(tokens);
492 m_obj.addmember("major_version").integer(majorVersion);
493 m_obj.addmember("minor_version").integer(minorVersion);
495 if (hasAttributes(*(unfiltered.get()))) {
496 auto_ptr<SAMLResponse> filtered(filter(unfiltered.get(), application, source));
498 // Calculate expiration.
499 m_responseExpiration=calculateExpiration(*(filtered.get()));
501 // Serialize filtered assertions (if changes were made).
503 os << *(filtered.get());
504 string fstr=os.str();
505 if (fstr.length() != strlen(tokens))
506 m_obj.addmember("tokens.filtered").string(fstr.c_str());
508 // Save actual objects only if we're running inprocess.
509 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
510 m_pUnfiltered=unfiltered.release();
511 if (m_obj["tokens.filtered"].isstring())
512 m_pFiltered=filtered.release();
516 m_lock = Mutex::create();
518 if (m_log->isDebugEnabled())
519 m_log->debug("session loaded from secondary cache (ID: %s)", key);
523 MemorySessionCacheEntry::~MemorySessionCacheEntry()
528 HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* client_addr) const
531 saml::NDC ndc("isValid");
534 // Obtain validation rules from application settings.
535 bool consistentIPAddress=true;
536 int lifetime=0,timeout=0;
537 const PropertySet* props=app->getPropertySet("Sessions");
539 pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
542 p=props->getUnsignedInt("timeout");
545 pair<bool,bool> pcheck=props->getBool("consistentIPAddress");
547 consistentIPAddress = pcheck.second;
550 if (m_log->isDebugEnabled())
551 m_log->debug("checking validity of session (ID: %s)", m_obj["key"].string());
553 time_t now=time(NULL);
554 if (lifetime > 0 && now > m_sessionCreated+lifetime) {
555 if (m_log->isInfoEnabled())
556 m_log->info("session expired (ID: %s)", m_obj["key"].string());
557 return SESSION_E_EXPIRED;
560 if (timeout > 0 && now-m_lastAccess >= timeout) {
561 // May need to query sink first to find out if another cluster member has been used.
562 if (m_cache->m_sink && m_cache->m_writeThrough) {
563 if (NOERROR!=m_cache->m_sink->onRead(m_obj["key"].string(),m_lastAccess))
564 m_log->error("cache store failed to return last access timestamp");
565 if (now-m_lastAccess >= timeout) {
566 m_log->info("session timed out (ID: %s)", m_obj["key"].string());
567 return SESSION_E_EXPIRED;
571 m_log->info("session timed out (ID: %s)", m_obj["key"].string());
572 return SESSION_E_EXPIRED;
576 if (consistentIPAddress) {
577 if (m_log->isDebugEnabled())
578 m_log->debug("comparing client address %s against %s", client_addr, getClientAddress());
579 if (strcmp(client_addr, getClientAddress())) {
580 m_log->debug("client address mismatch");
581 return SESSION_E_ADDRESSMISMATCH;
587 if (m_cache->m_sink && m_cache->m_writeThrough && timeout > 0) {
588 // Update sink with last access data, if possible.
589 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),NULL,m_lastAccess)))
590 m_log->error("cache store failed to update last access timestamp");
596 bool MemorySessionCacheEntry::hasAttributes(const SAMLResponse& r) const
598 Iterator<SAMLAssertion*> assertions=r.getAssertions();
599 while (assertions.hasNext()) {
600 Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
601 while (statements.hasNext()) {
602 if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
609 time_t MemorySessionCacheEntry::calculateExpiration(const SAMLResponse& r) const
612 Iterator<SAMLAssertion*> assertions = r.getAssertions();
613 while (assertions.hasNext()) {
614 SAMLAssertion* assertion = assertions.next();
616 // Only examine this assertion if it contains an attribute statement.
617 // We know at least one such statement exists, or this is a query response.
618 Iterator<SAMLStatement*> statements = assertion->getStatements();
619 while (statements.hasNext()) {
620 if (dynamic_cast<SAMLAttributeStatement*>(statements.next())) {
621 const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
623 // If there is no time, then just continue and ignore this assertion.
625 // If this is a tighter expiration, cache it.
626 if (expiration == 0 || thistime->getEpoch() < expiration)
627 expiration = thistime->getEpoch();
630 // No need to continue with this assertion.
636 // If we didn't find any assertions with times, then use the default.
638 expiration = time(NULL) + m_cache->m_defaultLifetime;
643 void MemorySessionCacheEntry::populate(const IApplication* application, const IEntityDescriptor* source, bool initial) const
646 saml::NDC ndc("populate");
649 // Do we have any attribute data cached?
650 if (m_responseExpiration > 0) {
651 // Can we use what we have?
652 if (time(NULL) < m_responseExpiration)
655 // Possibly check the sink in case another cluster member already refreshed it.
656 if (m_cache->m_sink && m_cache->m_writeThrough) {
657 string tokensFromSink;
658 HRESULT hr=m_cache->m_sink->onRead(m_obj["key"].string(),tokensFromSink);
660 m_log->error("cache store failed to return updated tokens");
661 else if (hr==NOERROR && tokensFromSink!=m_obj["tokens.unfiltered"].string()) {
662 // The tokens in the sink were different.
663 istringstream is(tokensFromSink);
664 auto_ptr<SAMLResponse> respFromSink(new SAMLResponse(is,m_obj["minor_version"].integer()));
665 auto_ptr<SAMLResponse> filteredFromSink(filter(respFromSink.get(),application,source));
666 time_t expFromSink=calculateExpiration(*(filteredFromSink.get()));
668 // Recheck to see if the new tokens are valid.
669 if (expFromSink < time(NULL)) {
670 m_log->info("loading replacement tokens into memory from cache store");
671 m_obj["tokens"].destroy();
672 delete m_pUnfiltered;
674 m_pUnfiltered=m_pFiltered=NULL;
675 m_obj.addmember("tokens.unfiltered").string(tokensFromSink.c_str());
677 // Serialize filtered assertions (if changes were made).
679 os << *(filteredFromSink.get());
680 string fstr=os.str();
681 if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
682 m_obj.addmember("tokens.filtered").string(fstr.c_str());
684 // Save actual objects only if we're running inprocess.
685 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
686 m_pUnfiltered=respFromSink.release();
687 if (m_obj["tokens.filtered"].isstring())
688 m_pFiltered=filteredFromSink.release();
691 m_responseExpiration=expFromSink;
698 // If we're being strict, dump what we have and reset timestamps.
699 if (m_cache->m_strictValidity) {
700 m_log->info("strictly enforcing attribute validity, dumping expired data");
701 m_obj["tokens"].destroy();
702 delete m_pUnfiltered;
704 m_pUnfiltered=m_pFiltered=NULL;
705 m_responseExpiration=0;
707 if (m_cache->m_sink) {
708 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),"")))
709 m_log->error("cache store returned failure while clearing tokens from entry");
715 pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse(application,source);
716 auto_ptr<SAMLResponse> r1(new_responses.first),r2(new_responses.second);
717 if (new_responses.first) {
718 m_obj["tokens"].destroy();
719 delete m_pUnfiltered;
721 m_pUnfiltered=m_pFiltered=NULL;
722 m_responseExpiration=0;
724 // Serialize unfiltered assertions.
726 os << *new_responses.first;
727 m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
729 // Serialize filtered assertions (if changes were made).
731 os << *new_responses.second;
732 string fstr=os.str();
733 if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
734 m_obj.addmember("tokens.filtered").string(fstr.c_str());
736 // Update expiration.
737 m_responseExpiration=calculateExpiration(*new_responses.second);
739 // Save actual objects only if we're running inprocess.
740 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
741 m_pUnfiltered=r1.release();
742 if (m_obj["tokens.filtered"].isstring())
743 m_pFiltered=r2.release();
746 // Update backing store.
747 if (!initial && m_cache->m_sink) {
748 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),m_obj["tokens.unfiltered"].string())))
749 m_log->error("cache store returned failure while updating tokens in entry");
753 m_log->debug("fetched and stored new response");
754 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
755 stc.getTransactionLog().infoStream() << "Successful attribute query for session (ID: " << m_obj["key"].string() << ")";
756 stc.releaseTransactionLog();
759 catch (SAMLException&) {
760 if (m_cache->m_propagateErrors)
762 m_log->warn("suppressed SAML exception caught while trying to fetch attributes");
766 if (m_cache->m_propagateErrors)
768 m_log->warn("suppressed unknown exception caught while trying to fetch attributes");
773 pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse(
774 const IApplication* application, const IEntityDescriptor* source
778 saml::NDC ndc("getNewResponse");
781 // The retryInterval determines how often to poll an AA that might be down.
782 time_t now=time(NULL);
783 if ((now - m_lastRetry) < m_cache->m_retryInterval)
784 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
786 m_log->debug("retry interval exceeded, trying for attributes again");
789 m_log->info("trying to get new attributes for session (ID: %s)", m_obj["key"].string());
791 // Transaction Logging
792 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
793 stc.getTransactionLog().infoStream() <<
794 "Making attribute query for session (ID: " <<
795 m_obj["key"].string() <<
796 ") on (applicationId: " <<
797 m_obj["application_id"].string() <<
798 ") for principal from (IdP: " <<
799 m_obj["provider_id"].string() <<
801 stc.releaseTransactionLog();
804 pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
805 if (!providerID.first) {
806 m_log->crit("unable to determine ProviderID for application, not set?");
807 throw SAMLException("Unable to determine ProviderID for application, not set?");
810 // Try to locate an AA role.
811 const IAttributeAuthorityDescriptor* AA=source->getAttributeAuthorityDescriptor(
812 m_obj["minor_version"].integer()==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
815 m_log->warn("unable to locate metadata for identity provider's Attribute Authority");
816 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
819 // Get protocol signing policy.
820 const PropertySet* credUse=application->getCredentialUse(source);
821 pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
822 pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
823 if (!signatureAlg.first)
824 signatureAlg.second=URI_ID_RSA_SHA1;
825 pair<bool,const char*> digestAlg=credUse ? credUse->getString("digestAlg") : pair<bool,const char*>(false,NULL);
826 if (!digestAlg.first)
827 digestAlg.second=URI_ID_SHA1;
828 pair<bool,bool> signedResponse=credUse ? credUse->getBool("signedResponse") : make_pair(false,false);
829 pair<bool,const char*> signingCred=credUse ? credUse->getString("Signing") : pair<bool,const char*>(false,NULL);
831 SAMLResponse* response = NULL;
833 // Copy NameID from subject (may need to reconstitute it).
834 SAMLNameIdentifier* nameid=NULL;
836 nameid=static_cast<SAMLNameIdentifier*>(m_pSubject->getNameIdentifier()->clone());
838 istringstream instr(m_obj["subject"].string());
839 auto_ptr<SAMLSubject> sub(new SAMLSubject(instr));
840 nameid=static_cast<SAMLNameIdentifier*>(sub->getNameIdentifier()->clone());
843 // Build a SAML Request....
844 SAMLAttributeQuery* q=new SAMLAttributeQuery(
845 new SAMLSubject(nameid),
847 application->getAttributeDesignators().clone()
849 auto_ptr<SAMLRequest> req(new SAMLRequest(q));
850 req->setMinorVersion(m_obj["minor_version"].integer());
853 if (signRequest.first && signRequest.second && signingCred.first) {
854 if (req->getMinorVersion()==1) {
855 Credentials creds(ShibTargetConfig::getConfig().getINI()->getCredentialsProviders());
856 const ICredResolver* cr=creds.lookup(signingCred.second);
858 req->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
860 m_log->error("unable to sign attribute query, specified credential (%s) was not found",signingCred.second);
863 m_log->error("unable to sign SAML 1.0 attribute query, only SAML 1.1 defines signing adequately");
866 m_log->debug("trying to query an AA...");
868 // Call context object
869 ShibHTTPHook::ShibHTTPHookCallContext ctx(credUse,AA);
870 Trust t(application->getTrustProviders());
872 // Use metadata to locate endpoints.
873 Iterator<const IEndpoint*> endpoints=AA->getAttributeServiceManager()->getEndpoints();
874 while (!response && endpoints.hasNext()) {
875 const IEndpoint* ep=endpoints.next();
877 // Get a binding object for this protocol.
878 const SAMLBinding* binding = application->getBinding(ep->getBinding());
880 auto_ptr_char prot(ep->getBinding());
881 m_log->warn("skipping binding on unsupported protocol (%s)", prot.get());
884 static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
885 auto_ptr<SAMLResponse> r(binding->send(ep->getLocation(), *(req.get()), &ctx));
887 if (!t.validate(*r,AA))
888 throw TrustException("Unable to verify signed response message.");
890 else if (!ctx.isAuthenticated() || XMLString::compareNString(ep->getLocation(),https,6))
891 throw TrustException("Response message was unauthenticated.");
892 response = r.release();
894 catch (SAMLException& e) {
895 m_log->error("caught SAML exception during SAML attribute query: %s", e.what());
896 // Check for shib:InvalidHandle error and propagate it out.
897 Iterator<saml::QName> codes=e.getCodes();
898 if (codes.size()>1) {
899 const saml::QName& code=codes[1];
900 if (!XMLString::compareString(code.getNamespaceURI(),shibboleth::Constants::SHIB_NS) &&
901 !XMLString::compareString(code.getLocalName(), shibboleth::Constants::InvalidHandle)) {
903 throw InvalidHandleException(e.what(),saml::params(),codes);
910 if (signedResponse.first && signedResponse.second && !response->isSigned()) {
912 m_log->error("unsigned response obtained, but we were told it must be signed.");
913 throw TrustException("Unable to obtain a signed response message.");
916 // Iterate over the tokens and apply basic validation.
917 time_t now=time(NULL);
918 Iterator<SAMLAssertion*> assertions=response->getAssertions();
919 for (unsigned int a=0; a<assertions.size();) {
920 // Discard any assertions not issued by the right entity.
921 if (XMLString::compareString(source->getId(),assertions[a]->getIssuer())) {
922 auto_ptr_char bad(assertions[a]->getIssuer());
923 m_log->warn("discarding assertion not issued by (%s), instead by (%s)",m_obj["provider_id"].string(),bad.get());
924 response->removeAssertion(a);
928 // Validate the token.
930 application->validateToken(assertions[a],now,AA,application->getTrustProviders());
933 catch (SAMLException&) {
934 m_log->warn("assertion failed to validate, removing it from response");
935 response->removeAssertion(a);
939 // Run it through the filter.
940 return make_pair(response,filter(response,application,source));
943 catch (SAMLException& e) {
944 m_log->error("caught SAML exception during query to AA: %s", e.what());
945 annotateException(&e,AA);
948 m_log->error("no response obtained");
949 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
952 SAMLResponse* MemorySessionCacheEntry::filter(
953 const SAMLResponse* r, const IApplication* application, const IEntityDescriptor* source
957 saml::NDC ndc("filter");
960 // Make a copy of the original and process that against the AAP.
961 auto_ptr<SAMLResponse> copy(static_cast<SAMLResponse*>(r->clone()));
964 Iterator<SAMLAssertion*> copies=copy->getAssertions();
965 for (unsigned long j=0; j < copies.size();) {
967 // Finally, filter the content.
968 AAP::apply(application->getAAPProviders(),*(copies[j]),source);
972 catch (SAMLException&) {
973 m_log->info("no statements remain after AAP, removing assertion");
974 copy->removeAssertion(j);
978 // Audit the results.
979 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
980 Category& tran=stc.getTransactionLog();
981 if (tran.isInfoEnabled()) {
983 "Caching the following attributes after AAP applied for session (ID: " <<
984 m_obj["key"].string() <<
985 ") on (applicationId: " <<
986 m_obj["application_id"].string() <<
987 ") for principal from (IdP: " <<
988 m_obj["provider_id"].string() <<
991 Iterator<SAMLAssertion*> loggies=copy->getAssertions();
992 while (loggies.hasNext()) {
993 SAMLAssertion* logit=loggies.next();
994 Iterator<SAMLStatement*> states=logit->getStatements();
995 while (states.hasNext()) {
996 SAMLAttributeStatement* state=dynamic_cast<SAMLAttributeStatement*>(states.next());
997 Iterator<SAMLAttribute*> attrs=state ? state->getAttributes() : EMPTY(SAMLAttribute*);
998 while (attrs.hasNext()) {
999 SAMLAttribute* attr=attrs.next();
1000 auto_ptr_char attrname(attr->getName());
1001 tran.infoStream() << "\t" << attrname.get() << " (" << attr->getValues().size() << " values)";
1007 stc.releaseTransactionLog();
1009 return copy.release();
1012 MemorySessionCache::MemorySessionCache(const DOMElement* e)
1013 : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
1014 m_strictValidity(true), m_propagateErrors(false), m_writeThrough(false), m_lock(RWLock::create()),
1015 m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")),
1016 restoreInsert(NULL), restoreFind(NULL), restoreRemove(NULL), m_sink(NULL)
1019 const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
1021 m_AATimeout = XMLString::parseInt(tag);
1026 tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
1028 m_AAConnectTimeout = XMLString::parseInt(tag);
1029 if (!m_AAConnectTimeout)
1030 m_AAConnectTimeout=15;
1033 tag=m_root->getAttributeNS(NULL,defaultLifetime);
1035 m_defaultLifetime = XMLString::parseInt(tag);
1036 if (!m_defaultLifetime)
1037 m_defaultLifetime=1800;
1040 tag=m_root->getAttributeNS(NULL,retryInterval);
1042 m_retryInterval = XMLString::parseInt(tag);
1043 if (!m_retryInterval)
1044 m_retryInterval=300;
1047 tag=m_root->getAttributeNS(NULL,strictValidity);
1048 if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
1049 m_strictValidity=false;
1051 tag=m_root->getAttributeNS(NULL,propagateErrors);
1052 if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
1053 m_propagateErrors=true;
1055 tag=m_root->getAttributeNS(NULL,writeThrough);
1056 if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
1057 m_writeThrough=true;
1060 SAMLConfig::getConfig().timeout = m_AATimeout;
1061 SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
1063 // Register for remoted messages.
1064 ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListener();
1065 if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
1066 restoreInsert=listener->regListener("SessionCache::insert",this);
1067 restoreFind=listener->regListener("SessionCache::find",this);
1068 restoreRemove=listener->regListener("SessionCache::remove",this);
1071 m_log->info("no listener interface available, cache remoting is disabled");
1073 shutdown_wait = CondWait::create();
1075 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
1078 MemorySessionCache::~MemorySessionCache()
1080 // Shut down the cleanup thread and let it know...
1082 shutdown_wait->signal();
1083 cleanup_thread->join(NULL);
1085 // Unregister remoted messages.
1086 ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListener();
1087 if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
1088 listener->unregListener("SessionCache::insert",this,restoreInsert);
1089 listener->unregListener("SessionCache::find",this,restoreFind);
1090 listener->unregListener("SessionCache::remove",this,restoreRemove);
1093 for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,MemorySessionCacheEntry>());
1095 delete shutdown_wait;
1098 bool MemorySessionCache::setBackingStore(ISessionCacheStore* store)
1100 if (m_sink && store!=m_sink)
1107 * IPC message definitions:
1109 * SessionCache::insert
1124 * SessionCache::find
1141 * SessionCache::remove
1149 DDF MemorySessionCache::receive(const DDF& in)
1152 saml::NDC ndc("receive");
1155 // Find application.
1156 const char* aid=in["application_id"].string();
1157 const IApplication* app=aid ? ShibTargetConfig::getConfig().getINI()->getApplication(aid) : NULL;
1159 // Something's horribly wrong.
1160 m_log->error("couldn't find application (%s) for session", aid ? aid : "(missing)");
1161 throw SAMLException("Unable to locate application for session, deleted?");
1164 if (!strcmp(in.name(),"SessionCache::find")) {
1165 // Check required parameters.
1166 const char* key=in["key"].string();
1167 const char* client_address=in["client_address"].string();
1168 if (!key || !client_address)
1169 throw SAMLException("Required parameters missing in call to SessionCache::find");
1172 // Lookup the session and cast down to the internal type.
1173 MemorySessionCacheEntry* entry=dynamic_cast<MemorySessionCacheEntry*>(find(key,app,client_address));
1176 DDF dup=entry->getDDF().copy();
1180 catch (SAMLException&) {
1181 remove(key,app,client_address);
1185 else if (!strcmp(in.name(),"SessionCache::remove")) {
1186 // Check required parameters.
1187 const char* key=in["key"].string();
1188 const char* client_address=in["client_address"].string();
1189 if (!key || !client_address)
1190 throw SAMLException("Required parameters missing in call to SessionCache::remove");
1192 remove(key,app,client_address);
1195 else if (!strcmp(in.name(),"SessionCache::insert")) {
1196 // Check required parameters.
1197 const char* client_address=in["client_address"].string();
1198 const char* provider_id=in["provider_id"].string();
1199 const char* authn_context=in["authn_context"].string();
1200 const char* subject=in["subject"].string();
1201 const char* tokens=in["tokens.unfiltered"].string();
1202 if (!client_address || !provider_id || !authn_context || !subject || !tokens)
1203 throw SAMLException("Required parameters missing in call to SessionCache::insert");
1204 int minor=in["minor_version"].integer();
1206 // Locate role descriptor to use in filtering.
1207 Metadata m(app->getMetadataProviders());
1208 const IEntityDescriptor* site=m.lookup(provider_id);
1210 m_log->error("unable to locate issuing identity provider's metadata");
1211 throw MetadataException("Unable to locate identity provider's metadata.");
1213 // Deserialize XML for insert method.
1214 istringstream subis(subject);
1215 auto_ptr<SAMLSubject> pSubject(new SAMLSubject(subis));
1216 istringstream tokis(tokens);
1217 auto_ptr<SAMLResponse> pTokens(new SAMLResponse(tokis,minor));
1219 // Insert the data and return the cache key.
1220 string key=insert(app,site,client_address,pSubject.get(),authn_context,pTokens.get());
1224 out.addmember("key").string(key.c_str());
1227 throw ListenerException("Unsupported operation ($1)",saml::params(1,in.name()));
1230 string MemorySessionCache::insert(
1231 const IApplication* application,
1232 const IEntityDescriptor* source,
1233 const char* client_addr,
1234 const SAMLSubject* subject,
1235 const char* authnContext,
1236 const SAMLResponse* tokens
1240 saml::NDC ndc("insert");
1244 auto_ptr_char key(id);
1246 if (m_log->isDebugEnabled())
1247 m_log->debug("creating new cache entry for application %s: \"%s\"", application->getId(), key.get());
1249 auto_ptr<MemorySessionCacheEntry> entry(
1250 new MemorySessionCacheEntry(
1261 entry->populate(application,source,true);
1264 HRESULT hr=m_sink->onCreate(key.get(),application,entry.get(),1,tokens->getMinorVersion(),entry->created());
1266 m_log->error("cache store returned failure while storing new entry");
1267 throw SAMLException(hr,"Unable to record new session in cache store.");
1272 m_hashtable[key.get()]=entry.release();
1278 ISessionCacheEntry* MemorySessionCache::find(const char* key, const IApplication* application, const char* client_addr)
1281 saml::NDC ndc("find");
1284 m_log->debug("searching memory cache for key (%s)", key);
1287 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1288 if (i==m_hashtable.end()) {
1290 m_log->debug("no match found");
1292 return NULL; // no backing store to search
1294 m_log->debug("searching backing store");
1295 string appid,addr,pid,sub,ac,tokens;
1297 time_t created,accessed;
1298 HRESULT hr=m_sink->onRead(key,appid,addr,pid,sub,ac,tokens,major,minor,created,accessed);
1301 else if (FAILED(hr)) {
1302 m_log->error("cache store returned failure during search");
1305 const IApplication* eapp=ShibTargetConfig::getConfig().getINI()->getApplication(appid.c_str());
1307 // Something's horribly wrong.
1308 m_log->error("couldn't find application (%s) for session", appid.c_str());
1309 if (FAILED(m_sink->onDelete(key)))
1310 m_log->error("cache store returned failure during delete");
1313 if (m_log->isDebugEnabled())
1314 m_log->debug("loading cache entry (ID: %s) back into memory for application (%s)", key, appid.c_str());
1316 // Locate role descriptor to use in filtering.
1317 Metadata m(eapp->getMetadataProviders());
1318 const IEntityDescriptor* site=m.lookup(pid.c_str());
1320 m_log->error("unable to locate issuing identity provider's metadata");
1321 if (FAILED(m_sink->onDelete(key)))
1322 m_log->error("cache store returned failure during delete");
1325 MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
1340 m_hashtable[key]=entry;
1343 // Downgrade to a read lock and repeat the initial search.
1345 i=m_hashtable.find(key);
1346 if (i==m_hashtable.end()) {
1348 m_log->warn("cache entry was loaded from backing store, but disappeared after lock downgrade");
1353 m_log->debug("match found");
1355 // Check for application mismatch (could also do this with partitioned caches by application ID)
1356 if (!i->second->checkApplication(application)) {
1358 m_log->crit("An application (%s) attempted to access another application's session!", application->getId());
1362 // Check for timeouts, expiration, address mismatch, etc (also updates last access)
1363 // Use the return code to assign specific error messages.
1365 HRESULT hr=i->second->isValid(application, client_addr);
1367 Metadata m(application->getMetadataProviders());
1369 case SESSION_E_EXPIRED: {
1370 InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
1371 annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1374 case SESSION_E_ADDRESSMISMATCH: {
1375 InvalidSessionException ex(
1376 SESSION_E_ADDRESSMISMATCH,
1377 "Your IP address ($1) does not match the address recorded at the time the session was established.",
1378 saml::params(1,client_addr)
1380 annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1384 InvalidSessionException ex(hr, "Your session is invalid.");
1385 annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1395 // Lock the cache entry for the caller -- they have to unlock it.
1400 // Make sure the entry has valid tokens.
1401 Metadata m(application->getMetadataProviders());
1402 i->second->populate(application,m.lookup(i->second->getProviderId()));
1405 i->second->unlock();
1412 void MemorySessionCache::remove(const char* key, const IApplication* application, const char* client_addr)
1415 saml::NDC ndc("remove");
1418 m_log->debug("removing cache entry with key (%s)", key);
1420 // lock the cache for writing, which means we know nobody is sitting in find()
1423 // grab the entry from the database.
1424 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1425 if (i==m_hashtable.end()) {
1430 // ok, remove the entry and lock it
1431 MemorySessionCacheEntry* entry=i->second;
1432 m_hashtable.erase(key);
1440 // Notify sink. Smart ptr will make sure entry gets deleted.
1441 auto_ptr<ISessionCacheEntry> entrywrap(entry);
1443 if (FAILED(m_sink->onDelete(key)))
1444 m_log->error("cache store failed to delete entry");
1447 // Transaction Logging
1448 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
1449 stc.getTransactionLog().infoStream() << "Destroyed session (ID: " << key << ")";
1450 stc.releaseTransactionLog();
1453 void MemorySessionCache::dormant(const char* key)
1456 saml::NDC ndc("dormant");
1459 m_log->debug("purging old cache entry with key (%s)", key);
1461 // lock the cache for writing, which means we know nobody is sitting in find()
1464 // grab the entry from the database.
1465 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1466 if (i==m_hashtable.end()) {
1471 // ok, remove the entry and lock it
1472 MemorySessionCacheEntry* entry=i->second;
1473 m_hashtable.erase(key);
1479 // we can release the cache entry lock because we know we're not in the cache anymore
1482 auto_ptr<ISessionCacheEntry> entrywrap(entry);
1483 if (m_sink && !m_writeThrough) {
1484 // Update sink with last access data. Wrapper will make sure entry gets deleted.
1485 if (FAILED(m_sink->onUpdate(key,NULL,entry->lastAccess())))
1486 m_log->error("cache store failed to update last access timestamp");
1490 void MemorySessionCache::cleanup()
1493 saml::NDC ndc("cleanup()");
1496 int rerun_timer = 0;
1497 int timeout_life = 0;
1498 Mutex* mutex = Mutex::create();
1500 // Load our configuration details...
1501 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
1503 rerun_timer = XMLString::parseInt(tag);
1505 tag=m_root->getAttributeNS(NULL,cacheTimeout);
1507 timeout_life = XMLString::parseInt(tag);
1509 if (rerun_timer <= 0)
1510 rerun_timer = 300; // rerun every 5 minutes
1512 if (timeout_life <= 0)
1513 timeout_life = 28800; // timeout after 8 hours
1517 m_log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
1520 shutdown_wait->timedwait(mutex,rerun_timer);
1524 // Ok, let's run through the cleanup process and clean out
1525 // really old sessions. This is a two-pass process. The
1526 // first pass is done holding a read-lock while we iterate over
1527 // the cache. The second pass doesn't need a lock because
1528 // the 'deletes' will lock the cache.
1530 // Pass 1: iterate over the map and find all entries that have not been
1532 vector<string> stale_keys;
1533 time_t stale = time(NULL) - timeout_life;
1536 for (map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
1538 // If the last access was BEFORE the stale timeout...
1540 time_t last=i->second->lastAccess();
1541 i->second->unlock();
1543 stale_keys.push_back(i->first);
1547 if (!stale_keys.empty()) {
1548 m_log->info("purging %d old sessions", stale_keys.size());
1550 // Pass 2: walk through the list of stale entries and remove them from the cache
1551 for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); j++)
1552 dormant(j->c_str());
1556 m_log->info("cleanup thread finished.");
1563 void* MemorySessionCache::cleanup_fcn(void* cache_p)
1565 MemorySessionCache* cache = reinterpret_cast<MemorySessionCache*>(cache_p);
1568 // First, let's block all signals
1569 Thread::mask_all_signals();
1572 // Now run the cleanup process.
1577 IPlugIn* MemoryCacheFactory(const DOMElement* e)
1579 // If this is a long-lived process, we return the "real" cache.
1580 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
1581 return new MemorySessionCache(e);
1582 // Otherwise, we return a stubbed front-end that remotes calls to the real cache.
1583 return new StubCache(e);