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 void release() { cacheitem_lock->unlock(); }
96 void setCache(InternalCCache *cache) { m_cache = cache; }
97 time_t lastAccess() { Lock lock(access_lock); return m_lastAccess; }
98 void rdlock() { cacheitem_lock->rdlock(); }
99 void wrlock() { cacheitem_lock->wrlock(); }
101 static vector<SAMLAssertion*> g_emptyVector;
104 ResourceEntry* populate(Resource& resource, int slop);
105 ResourceEntry* find(const char* resource);
106 void insert(const char* resource, ResourceEntry* entry);
107 void remove(const char* resource);
112 string m_clientAddress;
113 time_t m_sessionCreated;
117 const SAMLSubject* m_subject;
118 SAMLAuthenticationStatement* p_auth;
119 InternalCCache *m_cache;
121 map<string,ResourceEntry*> m_resources;
123 log4cpp::Category* log;
125 // This is used to keep track of in-process "populate()" calls,
126 // to make sure that we don't try to populate the same resource
127 // in multiple threads.
128 map<string,Mutex*> populate_locks;
129 Mutex* pop_locks_lock;
132 RWLock* resource_lock;
133 RWLock* cacheitem_lock;
138 ResourceLock(InternalCCacheEntry* entry, string resource);
142 Mutex* find(string& resource);
143 InternalCCacheEntry* entry;
147 friend class ResourceLock;
150 class InternalCCache : public CCache
154 virtual ~InternalCCache();
156 virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
157 virtual CCacheEntry* find(const char* key);
158 virtual void insert(const char* key, SAMLAuthenticationStatement *s,
159 const char *client_addr);
160 virtual void remove(const char* key);
162 InternalCCacheEntry* findi(const char* key);
168 SAMLBinding* m_SAMLBinding;
169 map<string,InternalCCacheEntry*> m_hashtable;
171 log4cpp::Category* log;
173 static void* cleanup_fcn(void*); // XXX Assumed an InternalCCache
175 CondWait* shutdown_wait;
176 Thread* cleanup_thread;
180 map<string,CCache::CCacheFactory> g_ccacheFactoryDB;
183 // Global Constructors & Destructors
184 CCache::~CCache() { }
186 void CCache::registerFactory(const char* name, CCache::CCacheFactory factory)
188 string ctx = "shibtarget.CCache";
189 log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
190 saml::NDC ndc("registerFactory");
192 log.info ("Registered factory %p for CCache %s", factory, name);
193 g_ccacheFactoryDB[name] = factory;
196 CCache* CCache::getInstance(const char* type)
198 string ctx = "shibtarget.CCache";
199 log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
200 saml::NDC ndc("getInstance");
202 map<string,CCache::CCacheFactory>::const_iterator i=g_ccacheFactoryDB.find(type);
203 if (i!=g_ccacheFactoryDB.end()) {
204 log.info ("Loading CCache: %s at %p", type, i->second);
205 return ((i->second)());
208 log.info ("Loading default memory CCache");
209 return (CCache*) new InternalCCache();
213 vector<SAMLAssertion*> InternalCCacheEntry::g_emptyVector;
216 /******************************************************************************/
217 /* InternalCCache: A Credential Cache */
218 /******************************************************************************/
220 InternalCCache::InternalCCache()
222 m_SAMLBinding=SAMLBindingFactory::getInstance();
223 string ctx="shibtarget.InternalCCache";
224 log = &(log4cpp::Category::getInstance(ctx));
225 lock = RWLock::create();
227 shutdown_wait = CondWait::create();
229 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
232 InternalCCache::~InternalCCache()
234 // Shut down the cleanup thread and let it know...
236 shutdown_wait->signal();
237 cleanup_thread->join(NULL);
239 delete m_SAMLBinding;
240 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
243 delete shutdown_wait;
246 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
248 log->debug("looking for binding...");
249 if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
250 log->debug("https binding found");
251 return m_SAMLBinding;
256 // assumed a lock is held..
257 InternalCCacheEntry* InternalCCache::findi(const char* key)
259 log->debug("FindI: \"%s\"", key);
261 map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
262 if (i==m_hashtable.end()) {
263 log->debug("No Match found");
266 log->debug("Match Found.");
271 CCacheEntry* InternalCCache::find(const char* key)
273 log->debug("Find: \"%s\"", key);
274 ReadLock rwlock(lock);
276 InternalCCacheEntry* entry = findi(key);
277 if (!entry) return NULL;
279 // Lock the database for the caller -- they have to release the item.
281 return dynamic_cast<CCacheEntry*>(entry);
284 void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
285 const char *client_addr)
287 log->debug("caching new entry for \"%s\"", key);
289 InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
290 entry->setCache(this);
293 m_hashtable[key]=entry;
297 // remove the entry from the database and then destroy the cacheentry
298 void InternalCCache::remove(const char* key)
300 log->debug("removing cache entry \"key\"", key);
302 // grab the entry from the database. We'll have a readlock on it.
303 CCacheEntry* entry = findi(key);
308 // grab the cache write lock
311 // verify we've still got the same entry.
312 if (entry != findi(key)) {
313 // Nope -- must've already been removed.
318 // ok, remove the entry.
319 m_hashtable.erase(key);
322 // now grab the write lock on the cacheitem.
323 // This will make sure all other threads have released this item.
324 InternalCCacheEntry* ientry = dynamic_cast<InternalCCacheEntry*>(entry);
327 // we can release immediately because we know we're not in the database!
330 // Now delete the entry
334 void InternalCCache::cleanup()
336 Mutex* mutex = Mutex::create();
337 saml::NDC ndc("InternalCCache::cleanup()");
339 ShibTargetConfig& config = ShibTargetConfig::getConfig();
340 ShibINI& ini = config.getINI();
343 int timeout_life = 0;
346 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHECLEAN, true, &tag))
347 rerun_timer = atoi(tag.c_str());
348 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHETIMEOUT, true, &tag))
349 timeout_life = atoi(tag.c_str());
351 if (rerun_timer <= 0)
352 rerun_timer = 300; // rerun every 5 minutes
354 if (timeout_life <= 0)
355 timeout_life = 28800; // timeout after 8 hours
359 log->debug("Cleanup thread started... Run every %d secs; timeout after %d secs",
360 rerun_timer, timeout_life);
362 while (shutdown == false) {
364 memset (&ts, 0, sizeof(ts));
365 ts.tv_sec = time(NULL) + rerun_timer;
367 shutdown_wait->timedwait(mutex, &ts);
369 if (shutdown == true)
372 log->info("Cleanup thread running...");
374 // Ok, let's run through the cleanup process and clean out
375 // really old sessions. This is a two-pass process. The
376 // first pass is done holding a read-lock while we iterate over
377 // the database. The second pass doesn't need a lock because
378 // the 'deletes' will lock the database.
380 // Pass 1: iterate over the map and find all entries that have not been
382 vector<string> stale_keys;
383 time_t stale = time(NULL) - timeout_life;
386 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
387 i != m_hashtable.end(); i++)
389 // If the last access was BEFORE the stale timeout...
390 time_t last=i->second->lastAccess();
392 stale_keys.push_back(i->first);
396 log->info("deleting %d old items.", stale_keys.size());
398 // Pass 2: walk through the list of stale entries and remove them from
400 for (vector<string>::iterator j = stale_keys.begin();
401 j != stale_keys.end(); j++)
408 log->debug("Cleanup thread finished.");
415 void* InternalCCache::cleanup_fcn(void* cache_p)
417 InternalCCache* cache = (InternalCCache*)cache_p;
419 // First, let's block all signals
421 sigfillset(&sigmask);
422 Thread::mask_signals(SIG_BLOCK, &sigmask, NULL);
424 // Now run the cleanup process.
428 /******************************************************************************/
429 /* InternalCCacheEntry: A Credential Cache Entry */
430 /******************************************************************************/
432 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
433 : m_hasbinding(false)
435 string ctx = "shibtarget::InternalCCacheEntry";
436 log = &(log4cpp::Category::getInstance(ctx));
437 pop_locks_lock = Mutex::create();
438 access_lock = Mutex::create();
439 resource_lock = RWLock::create();
440 cacheitem_lock = RWLock::create();
443 log->error("NULL auth statement");
444 throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
447 m_subject = s->getSubject();
449 xstring name = m_subject->getName();
450 xstring qual = m_subject->getNameQualifier();
452 auto_ptr<char> h(XMLString::transcode(name.c_str()));
453 auto_ptr<char> d(XMLString::transcode(qual.c_str()));
456 m_originSite = d.get();
458 Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
459 if (bindings.hasNext())
462 m_clientAddress = client_addr;
463 m_sessionCreated = m_lastAccess = time(NULL);
468 // Save the serialized version of the auth statement
471 m_statement = os.str();
473 log->info("New Session Created...");
474 log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
478 InternalCCacheEntry::~InternalCCacheEntry()
480 log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
482 for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
483 i!=m_resources.end(); i++)
486 for (map<string,Mutex*>::iterator j=populate_locks.begin();
487 j!=populate_locks.end(); j++)
490 delete pop_locks_lock;
491 delete cacheitem_lock;
492 delete resource_lock;
496 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
498 saml::NDC ndc("isSessionValid");
499 log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
500 m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
501 time_t now=time(NULL);
502 if (lifetime > 0 && now > m_sessionCreated+lifetime) {
503 log->debug("session beyond lifetime");
507 // Lock the access-time from here until we return
508 Lock lock(access_lock);
509 if (timeout > 0 && now-m_lastAccess >= timeout) {
510 log->debug("session timed out");
517 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
519 saml::NDC ndc("getAssertions");
520 ResourceEntry* entry = populate(resource, 0);
522 return entry->getAssertions();
523 return Iterator<SAMLAssertion*>(InternalCCacheEntry::g_emptyVector);
526 void InternalCCacheEntry::preFetch(Resource& resource, int prefetch_window)
528 saml::NDC ndc("preFetch");
529 ResourceEntry* entry = populate(resource, prefetch_window);
532 ResourceEntry* InternalCCacheEntry::populate(Resource& resource, int slop)
534 saml::NDC ndc("populate");
535 log->debug("populating entry for %s (%s)",
536 resource.getResource(), resource.getURL());
538 // Lock the resource within this entry...
539 InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
541 // Can we use what we have?
542 ResourceEntry *entry = find(resource.getResource());
544 log->debug("found resource");
545 if (entry->isValid(slop))
548 // entry is invalid (expired) -- go fetch a new one.
549 log->debug("removing resource cache; assertion is invalid");
550 remove (resource.getResource());
554 // Nope, no entry.. Create a new resource entry
557 log->error("No binding!");
561 log->info("trying to request attributes for %s@%s -> %s",
562 m_handle.c_str(), m_originSite.c_str(), resource.getURL());
565 entry = new ResourceEntry(resource, *m_subject, m_cache, p_auth->getBindings());
566 } catch (ShibTargetException &e) {
569 insert (resource.getResource(), entry);
571 log->info("fetched and stored SAML response");
575 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
577 ReadLock rwlock(resource_lock);
579 log->debug("find: %s", resource_url);
580 map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
581 if (i==m_resources.end()) {
582 log->debug("no match found");
585 log->debug("match found");
589 void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
591 log->debug("inserting %s", resource);
593 resource_lock->wrlock();
594 m_resources[resource]=entry;
595 resource_lock->unlock();
598 // caller will delete the entry.. don't worry about that here.
599 void InternalCCacheEntry::remove(const char* resource)
601 log->debug("removing %s", resource);
603 resource_lock->wrlock();
604 m_resources.erase(resource);
605 resource_lock->unlock();
609 // a lock on a resource. This is a specific "table of locks" that
610 // will provide a mutex on a particular resource within a Cache Entry.
611 // Just instantiate a ResourceLock within scope of the function and it
612 // will obtain and hold the proper lock until it goes out of scope and
615 InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
617 entry(entry), resource(resource)
619 Mutex *mutex = find(resource);
623 InternalCCacheEntry::ResourceLock::~ResourceLock()
625 Mutex *mutex = find(resource);
629 Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
631 Lock(entry->pop_locks_lock);
633 map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
634 if (i==entry->populate_locks.end()) {
635 Mutex* mutex = Mutex::create();
636 entry->populate_locks[resource] = mutex;