2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52 * shib-ccache.cpp -- SHAR Credential Cache
54 * Originally from mod_shib
55 * Modified by: Derek Atkins <derek@ihtfp.com>
64 #include "shib-target.h"
65 #include "ccache-utils.h"
66 #include <shib/shib-threads.h>
68 #include <log4cpp/Category.hh>
73 #ifdef HAVE_LIBDMALLOCXX
79 using namespace shibboleth;
80 using namespace shibtarget;
83 class InternalCCacheEntry : public CCacheEntry
86 InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
87 ~InternalCCacheEntry();
89 virtual Iterator<SAMLAssertion*> getAssertions(Resource& resource);
90 virtual void preFetch(Resource& resource, int prefetch_window);
91 virtual bool isSessionValid(time_t lifetime, time_t timeout);
92 virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
93 virtual const char* getSerializedStatement() { return m_statement.c_str(); }
94 virtual const SAMLAuthenticationStatement* getStatement() { return p_auth; }
95 virtual void release() { cacheitem_lock->unlock(); }
97 void setCache(InternalCCache *cache) { m_cache = cache; }
98 time_t lastAccess() { Lock lock(access_lock); return m_lastAccess; }
99 void rdlock() { cacheitem_lock->rdlock(); }
100 void wrlock() { cacheitem_lock->wrlock(); }
102 static vector<SAMLAssertion*> g_emptyVector;
105 ResourceEntry* populate(Resource& resource, int slop);
106 ResourceEntry* find(const char* resource);
107 void insert(const char* resource, ResourceEntry* entry);
108 void remove(const char* resource);
113 string m_clientAddress;
114 time_t m_sessionCreated;
118 const SAMLSubject* m_subject;
119 SAMLAuthenticationStatement* p_auth;
120 InternalCCache *m_cache;
122 map<string,ResourceEntry*> m_resources;
124 log4cpp::Category* log;
126 // This is used to keep track of in-process "populate()" calls,
127 // to make sure that we don't try to populate the same resource
128 // in multiple threads.
129 map<string,Mutex*> populate_locks;
130 Mutex* pop_locks_lock;
133 RWLock* resource_lock;
134 RWLock* cacheitem_lock;
139 ResourceLock(InternalCCacheEntry* entry, string resource);
143 Mutex* find(string& resource);
144 InternalCCacheEntry* entry;
148 friend class ResourceLock;
151 class InternalCCache : public CCache
155 virtual ~InternalCCache();
157 virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
158 virtual CCacheEntry* find(const char* key);
159 virtual void insert(const char* key, SAMLAuthenticationStatement *s,
160 const char *client_addr);
161 virtual void remove(const char* key);
163 InternalCCacheEntry* findi(const char* key);
169 SAMLBinding* m_SAMLBinding;
170 map<string,InternalCCacheEntry*> m_hashtable;
172 log4cpp::Category* log;
174 static void* cleanup_fcn(void*); // XXX Assumed an InternalCCache
176 CondWait* shutdown_wait;
177 Thread* cleanup_thread;
181 map<string,CCache::CCacheFactory> g_ccacheFactoryDB;
184 // Global Constructors & Destructors
185 CCache::~CCache() { }
187 void CCache::registerFactory(const char* name, CCache::CCacheFactory factory)
189 string ctx = "shibtarget.CCache";
190 log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
191 saml::NDC ndc("registerFactory");
193 log.info ("Registered factory %p for CCache %s", factory, name);
194 g_ccacheFactoryDB[name] = factory;
197 CCache* CCache::getInstance(const char* type)
199 string ctx = "shibtarget.CCache";
200 log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
201 saml::NDC ndc("getInstance");
203 map<string,CCache::CCacheFactory>::const_iterator i=g_ccacheFactoryDB.find(type);
204 if (i!=g_ccacheFactoryDB.end()) {
205 log.info ("Loading CCache: %s at %p", type, i->second);
206 return ((i->second)());
209 log.info ("Loading default memory CCache");
210 return (CCache*) new InternalCCache();
214 vector<SAMLAssertion*> InternalCCacheEntry::g_emptyVector;
217 /******************************************************************************/
218 /* InternalCCache: A Credential Cache */
219 /******************************************************************************/
221 InternalCCache::InternalCCache()
223 m_SAMLBinding=SAMLBindingFactory::getInstance();
224 string ctx="shibtarget.InternalCCache";
225 log = &(log4cpp::Category::getInstance(ctx));
226 lock = RWLock::create();
228 shutdown_wait = CondWait::create();
230 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
233 InternalCCache::~InternalCCache()
235 // Shut down the cleanup thread and let it know...
237 shutdown_wait->signal();
238 cleanup_thread->join(NULL);
240 delete m_SAMLBinding;
241 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
244 delete shutdown_wait;
247 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
249 log->debug("looking for binding...");
250 if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
251 log->debug("https binding found");
252 return m_SAMLBinding;
257 // assumed a lock is held..
258 InternalCCacheEntry* InternalCCache::findi(const char* key)
260 log->debug("FindI: \"%s\"", key);
262 map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
263 if (i==m_hashtable.end()) {
264 log->debug("No Match found");
267 log->debug("Match Found.");
272 CCacheEntry* InternalCCache::find(const char* key)
274 log->debug("Find: \"%s\"", key);
275 ReadLock rwlock(lock);
277 InternalCCacheEntry* entry = findi(key);
278 if (!entry) return NULL;
280 // Lock the database for the caller -- they have to release the item.
282 return dynamic_cast<CCacheEntry*>(entry);
285 void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
286 const char *client_addr)
288 log->debug("caching new entry for \"%s\"", key);
290 InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
291 entry->setCache(this);
294 m_hashtable[key]=entry;
298 // remove the entry from the database and then destroy the cacheentry
299 void InternalCCache::remove(const char* key)
301 log->debug("removing cache entry \"key\"", key);
303 // grab the entry from the database. We'll have a readlock on it.
304 CCacheEntry* entry = findi(key);
309 // grab the cache write lock
312 // verify we've still got the same entry.
313 if (entry != findi(key)) {
314 // Nope -- must've already been removed.
319 // ok, remove the entry.
320 m_hashtable.erase(key);
323 // now grab the write lock on the cacheitem.
324 // This will make sure all other threads have released this item.
325 InternalCCacheEntry* ientry = dynamic_cast<InternalCCacheEntry*>(entry);
328 // we can release immediately because we know we're not in the database!
331 // Now delete the entry
335 void InternalCCache::cleanup()
337 Mutex* mutex = Mutex::create();
338 saml::NDC ndc("InternalCCache::cleanup()");
340 ShibTargetConfig& config = ShibTargetConfig::getConfig();
341 ShibINI& ini = config.getINI();
344 int timeout_life = 0;
347 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHECLEAN, true, &tag))
348 rerun_timer = atoi(tag.c_str());
349 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHETIMEOUT, true, &tag))
350 timeout_life = atoi(tag.c_str());
352 if (rerun_timer <= 0)
353 rerun_timer = 300; // rerun every 5 minutes
355 if (timeout_life <= 0)
356 timeout_life = 28800; // timeout after 8 hours
360 log->debug("Cleanup thread started... Run every %d secs; timeout after %d secs",
361 rerun_timer, timeout_life);
363 while (shutdown == false) {
364 shutdown_wait->timedwait(mutex,rerun_timer);
366 if (shutdown == true)
369 log->info("Cleanup thread running...");
371 // Ok, let's run through the cleanup process and clean out
372 // really old sessions. This is a two-pass process. The
373 // first pass is done holding a read-lock while we iterate over
374 // the database. The second pass doesn't need a lock because
375 // the 'deletes' will lock the database.
377 // Pass 1: iterate over the map and find all entries that have not been
379 vector<string> stale_keys;
380 time_t stale = time(NULL) - timeout_life;
383 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
384 i != m_hashtable.end(); i++)
386 // If the last access was BEFORE the stale timeout...
387 time_t last=i->second->lastAccess();
389 stale_keys.push_back(i->first);
393 log->info("deleting %d old items.", stale_keys.size());
395 // Pass 2: walk through the list of stale entries and remove them from
397 for (vector<string>::iterator j = stale_keys.begin();
398 j != stale_keys.end(); j++)
405 log->debug("Cleanup thread finished.");
412 void* InternalCCache::cleanup_fcn(void* cache_p)
414 InternalCCache* cache = (InternalCCache*)cache_p;
416 // First, let's block all signals
417 Thread::mask_all_signals();
419 // Now run the cleanup process.
423 /******************************************************************************/
424 /* InternalCCacheEntry: A Credential Cache Entry */
425 /******************************************************************************/
427 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
428 : m_hasbinding(false)
430 string ctx = "shibtarget::InternalCCacheEntry";
431 log = &(log4cpp::Category::getInstance(ctx));
432 pop_locks_lock = Mutex::create();
433 access_lock = Mutex::create();
434 resource_lock = RWLock::create();
435 cacheitem_lock = RWLock::create();
438 log->error("NULL auth statement");
439 throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
442 m_subject = s->getSubject();
444 xstring name = m_subject->getName();
445 xstring qual = m_subject->getNameQualifier();
447 auto_ptr<char> h(XMLString::transcode(name.c_str()));
448 auto_ptr<char> d(XMLString::transcode(qual.c_str()));
451 m_originSite = d.get();
453 Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
454 if (bindings.hasNext())
457 m_clientAddress = client_addr;
458 m_sessionCreated = m_lastAccess = time(NULL);
463 // Save the serialized version of the auth statement
466 m_statement = os.str();
468 log->info("New Session Created...");
469 log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
473 InternalCCacheEntry::~InternalCCacheEntry()
475 log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
477 for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
478 i!=m_resources.end(); i++)
481 for (map<string,Mutex*>::iterator j=populate_locks.begin();
482 j!=populate_locks.end(); j++)
485 delete pop_locks_lock;
486 delete cacheitem_lock;
487 delete resource_lock;
491 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
493 saml::NDC ndc("isSessionValid");
494 log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
495 m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
496 time_t now=time(NULL);
497 if (lifetime > 0 && now > m_sessionCreated+lifetime) {
498 log->debug("session beyond lifetime");
502 // Lock the access-time from here until we return
503 Lock lock(access_lock);
504 if (timeout > 0 && now-m_lastAccess >= timeout) {
505 log->debug("session timed out");
512 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
514 saml::NDC ndc("getAssertions");
515 ResourceEntry* entry = populate(resource, 0);
517 return entry->getAssertions();
518 return Iterator<SAMLAssertion*>(InternalCCacheEntry::g_emptyVector);
521 void InternalCCacheEntry::preFetch(Resource& resource, int prefetch_window)
523 saml::NDC ndc("preFetch");
524 ResourceEntry* entry = populate(resource, prefetch_window);
527 ResourceEntry* InternalCCacheEntry::populate(Resource& resource, int slop)
529 saml::NDC ndc("populate");
530 log->debug("populating entry for %s (%s)",
531 resource.getResource(), resource.getURL());
533 // Lock the resource within this entry...
534 InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
536 // Can we use what we have?
537 ResourceEntry *entry = find(resource.getResource());
539 log->debug("found resource");
540 if (entry->isValid(slop))
543 // entry is invalid (expired) -- go fetch a new one.
544 log->debug("removing resource cache; assertion is invalid");
545 remove (resource.getResource());
549 // Nope, no entry.. Create a new resource entry
552 log->error("No binding!");
556 log->info("trying to request attributes for %s@%s -> %s",
557 m_handle.c_str(), m_originSite.c_str(), resource.getURL());
560 entry = new ResourceEntry(resource, *m_subject, m_cache, p_auth->getBindings());
561 } catch (ShibTargetException &e) {
564 insert (resource.getResource(), entry);
566 log->info("fetched and stored SAML response");
570 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
572 ReadLock rwlock(resource_lock);
574 log->debug("find: %s", resource_url);
575 map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
576 if (i==m_resources.end()) {
577 log->debug("no match found");
580 log->debug("match found");
584 void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
586 log->debug("inserting %s", resource);
588 resource_lock->wrlock();
589 m_resources[resource]=entry;
590 resource_lock->unlock();
593 // caller will delete the entry.. don't worry about that here.
594 void InternalCCacheEntry::remove(const char* resource)
596 log->debug("removing %s", resource);
598 resource_lock->wrlock();
599 m_resources.erase(resource);
600 resource_lock->unlock();
604 // a lock on a resource. This is a specific "table of locks" that
605 // will provide a mutex on a particular resource within a Cache Entry.
606 // Just instantiate a ResourceLock within scope of the function and it
607 // will obtain and hold the proper lock until it goes out of scope and
610 InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
612 entry(entry), resource(resource)
614 Mutex *mutex = find(resource);
618 InternalCCacheEntry::ResourceLock::~ResourceLock()
620 Mutex *mutex = find(resource);
624 Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
626 Lock(entry->pop_locks_lock);
628 map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
629 if (i==entry->populate_locks.end()) {
630 Mutex* mutex = Mutex::create();
631 entry->populate_locks[resource] = mutex;