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>
66 #include <shib/shib-threads.h>
68 #include <log4cpp/Category.hh>
73 #ifdef HAVE_LIBDMALLOCXX
78 class InternalCCacheEntry : public CCacheEntry
81 InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
82 ~InternalCCacheEntry();
84 virtual Iterator<SAMLAssertion*> getAssertions(Resource& resource);
85 virtual void preFetch(Resource& resource, int prefetch_window);
86 virtual bool isSessionValid(time_t lifetime, time_t timeout);
87 virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
88 virtual const char* getSerializedStatement() { return m_statement.c_str(); }
89 virtual const SAMLAuthenticationStatement* getStatement() { return p_auth; }
90 virtual void release() { cacheitem_lock->unlock(); }
92 void setCache(InternalCCache *cache) { m_cache = cache; }
93 time_t lastAccess() { Lock lock(access_lock); return m_lastAccess; }
94 void rdlock() { cacheitem_lock->rdlock(); }
95 void wrlock() { cacheitem_lock->wrlock(); }
97 static vector<SAMLAssertion*> g_emptyVector;
100 ResourceEntry* populate(Resource& resource, int slop);
101 ResourceEntry* find(const char* resource);
102 void insert(const char* resource, ResourceEntry* entry);
103 void remove(const char* resource);
108 string m_clientAddress;
109 time_t m_sessionCreated;
113 const SAMLSubject* m_subject;
114 SAMLAuthenticationStatement* p_auth;
115 InternalCCache *m_cache;
117 map<string,ResourceEntry*> m_resources;
119 log4cpp::Category* log;
121 // This is used to keep track of in-process "populate()" calls,
122 // to make sure that we don't try to populate the same resource
123 // in multiple threads.
124 map<string,Mutex*> populate_locks;
125 Mutex* pop_locks_lock;
128 RWLock* resource_lock;
129 RWLock* cacheitem_lock;
134 ResourceLock(InternalCCacheEntry* entry, string resource);
138 Mutex* find(string& resource);
139 InternalCCacheEntry* entry;
143 friend class ResourceLock;
146 class InternalCCache : public CCache
150 virtual ~InternalCCache();
152 virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
153 virtual CCacheEntry* find(const char* key);
154 virtual void insert(const char* key, SAMLAuthenticationStatement *s,
155 const char *client_addr);
156 virtual void remove(const char* key);
158 InternalCCacheEntry* findi(const char* key);
164 SAMLBinding* m_SAMLBinding;
165 map<string,InternalCCacheEntry*> m_hashtable;
167 log4cpp::Category* log;
169 static void* cleanup_fcn(void*); // XXX Assumed an InternalCCache
171 CondWait* shutdown_wait;
172 Thread* cleanup_thread;
176 map<string,CCache::CCacheFactory> g_ccacheFactoryDB;
179 // Global Constructors & Destructors
180 CCache::~CCache() { }
182 void CCache::registerFactory(const char* name, CCache::CCacheFactory factory)
184 string ctx = "shibtarget.CCache";
185 log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
186 saml::NDC ndc("registerFactory");
188 log.info ("Registered factory %p for CCache %s", factory, name);
189 g_ccacheFactoryDB[name] = factory;
192 CCache* CCache::getInstance(const char* type)
194 string ctx = "shibtarget.CCache";
195 log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
196 saml::NDC ndc("getInstance");
198 map<string,CCache::CCacheFactory>::const_iterator i=g_ccacheFactoryDB.find(type);
199 if (i!=g_ccacheFactoryDB.end()) {
200 log.info ("Loading CCache: %s at %p", type, i->second);
201 return ((i->second)());
204 log.info ("Loading default memory CCache");
205 return (CCache*) new InternalCCache();
209 vector<SAMLAssertion*> InternalCCacheEntry::g_emptyVector;
212 /******************************************************************************/
213 /* InternalCCache: A Credential Cache */
214 /******************************************************************************/
216 InternalCCache::InternalCCache()
218 m_SAMLBinding=SAMLBindingFactory::getInstance();
219 string ctx="shibtarget.InternalCCache";
220 log = &(log4cpp::Category::getInstance(ctx));
221 lock = RWLock::create();
223 shutdown_wait = CondWait::create();
225 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
228 InternalCCache::~InternalCCache()
230 // Shut down the cleanup thread and let it know...
232 shutdown_wait->signal();
233 cleanup_thread->join(NULL);
235 delete m_SAMLBinding;
236 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
239 delete shutdown_wait;
242 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
244 log->debug("looking for binding...");
245 if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
246 log->debug("https binding found");
247 return m_SAMLBinding;
252 // assumed a lock is held..
253 InternalCCacheEntry* InternalCCache::findi(const char* key)
255 log->debug("FindI: \"%s\"", key);
257 map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
258 if (i==m_hashtable.end()) {
259 log->debug("No Match found");
262 log->debug("Match Found.");
267 CCacheEntry* InternalCCache::find(const char* key)
269 log->debug("Find: \"%s\"", key);
270 ReadLock rwlock(lock);
272 InternalCCacheEntry* entry = findi(key);
273 if (!entry) return NULL;
275 // Lock the database for the caller -- they have to release the item.
277 return dynamic_cast<CCacheEntry*>(entry);
280 void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
281 const char *client_addr)
283 log->debug("caching new entry for \"%s\"", key);
285 InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
286 entry->setCache(this);
289 m_hashtable[key]=entry;
293 // remove the entry from the database and then destroy the cacheentry
294 void InternalCCache::remove(const char* key)
296 log->debug("removing cache entry \"key\"", key);
298 // grab the entry from the database. We'll have a readlock on it.
299 CCacheEntry* entry = findi(key);
304 // grab the cache write lock
307 // verify we've still got the same entry.
308 if (entry != findi(key)) {
309 // Nope -- must've already been removed.
314 // ok, remove the entry.
315 m_hashtable.erase(key);
318 // now grab the write lock on the cacheitem.
319 // This will make sure all other threads have released this item.
320 InternalCCacheEntry* ientry = dynamic_cast<InternalCCacheEntry*>(entry);
323 // we can release immediately because we know we're not in the database!
326 // Now delete the entry
330 void InternalCCache::cleanup()
332 Mutex* mutex = Mutex::create();
333 saml::NDC ndc("InternalCCache::cleanup()");
335 ShibTargetConfig& config = ShibTargetConfig::getConfig();
336 ShibINI& ini = config.getINI();
339 int timeout_life = 0;
342 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHECLEAN, true, &tag))
343 rerun_timer = atoi(tag.c_str());
344 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHETIMEOUT, true, &tag))
345 timeout_life = atoi(tag.c_str());
347 if (rerun_timer <= 0)
348 rerun_timer = 300; // rerun every 5 minutes
350 if (timeout_life <= 0)
351 timeout_life = 28800; // timeout after 8 hours
355 log->debug("Cleanup thread started... Run every %d secs; timeout after %d secs",
356 rerun_timer, timeout_life);
358 while (shutdown == false) {
359 shutdown_wait->timedwait(mutex,rerun_timer);
361 if (shutdown == true)
364 log->info("Cleanup thread running...");
366 // Ok, let's run through the cleanup process and clean out
367 // really old sessions. This is a two-pass process. The
368 // first pass is done holding a read-lock while we iterate over
369 // the database. The second pass doesn't need a lock because
370 // the 'deletes' will lock the database.
372 // Pass 1: iterate over the map and find all entries that have not been
374 vector<string> stale_keys;
375 time_t stale = time(NULL) - timeout_life;
378 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
379 i != m_hashtable.end(); i++)
381 // If the last access was BEFORE the stale timeout...
382 time_t last=i->second->lastAccess();
384 stale_keys.push_back(i->first);
388 log->info("deleting %d old items.", stale_keys.size());
390 // Pass 2: walk through the list of stale entries and remove them from
392 for (vector<string>::iterator j = stale_keys.begin();
393 j != stale_keys.end(); j++)
400 log->debug("Cleanup thread finished.");
407 void* InternalCCache::cleanup_fcn(void* cache_p)
409 InternalCCache* cache = (InternalCCache*)cache_p;
411 // First, let's block all signals
412 Thread::mask_all_signals();
414 // Now run the cleanup process.
419 /******************************************************************************/
420 /* InternalCCacheEntry: A Credential Cache Entry */
421 /******************************************************************************/
423 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
424 : m_hasbinding(false)
426 string ctx = "shibtarget::InternalCCacheEntry";
427 log = &(log4cpp::Category::getInstance(ctx));
428 pop_locks_lock = Mutex::create();
429 access_lock = Mutex::create();
430 resource_lock = RWLock::create();
431 cacheitem_lock = RWLock::create();
434 log->error("NULL auth statement");
435 throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
438 m_subject = s->getSubject();
440 xstring name = m_subject->getName();
441 xstring qual = m_subject->getNameQualifier();
443 auto_ptr<char> h(XMLString::transcode(name.c_str()));
444 auto_ptr<char> d(XMLString::transcode(qual.c_str()));
447 m_originSite = d.get();
449 Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
450 if (bindings.hasNext())
453 m_clientAddress = client_addr;
454 m_sessionCreated = m_lastAccess = time(NULL);
459 // Save the serialized version of the auth statement
462 m_statement = os.str();
464 log->info("New Session Created...");
465 log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
469 InternalCCacheEntry::~InternalCCacheEntry()
471 log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
473 for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
474 i!=m_resources.end(); i++)
477 for (map<string,Mutex*>::iterator j=populate_locks.begin();
478 j!=populate_locks.end(); j++)
481 delete pop_locks_lock;
482 delete cacheitem_lock;
483 delete resource_lock;
487 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
489 saml::NDC ndc("isSessionValid");
490 log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
491 m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
492 time_t now=time(NULL);
493 if (lifetime > 0 && now > m_sessionCreated+lifetime) {
494 log->debug("session beyond lifetime");
498 // Lock the access-time from here until we return
499 Lock lock(access_lock);
500 if (timeout > 0 && now-m_lastAccess >= timeout) {
501 log->debug("session timed out");
508 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
510 saml::NDC ndc("getAssertions");
511 ResourceEntry* entry = populate(resource, 0);
513 return entry->getAssertions();
514 return Iterator<SAMLAssertion*>(InternalCCacheEntry::g_emptyVector);
517 void InternalCCacheEntry::preFetch(Resource& resource, int prefetch_window)
519 saml::NDC ndc("preFetch");
520 ResourceEntry* entry = populate(resource, prefetch_window);
523 ResourceEntry* InternalCCacheEntry::populate(Resource& resource, int slop)
525 saml::NDC ndc("populate");
526 log->debug("populating entry for %s (%s)",
527 resource.getResource(), resource.getURL());
529 // Lock the resource within this entry...
530 InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
532 // Can we use what we have?
533 ResourceEntry *entry = find(resource.getResource());
535 log->debug("found resource");
536 if (entry->isValid(slop))
539 // entry is invalid (expired) -- go fetch a new one.
540 log->debug("removing resource cache; assertion is invalid");
541 remove (resource.getResource());
545 // Nope, no entry.. Create a new resource entry
548 log->error("No binding!");
552 log->info("trying to request attributes for %s@%s -> %s",
553 m_handle.c_str(), m_originSite.c_str(), resource.getURL());
556 entry = new ResourceEntry(resource, *m_subject, m_cache, p_auth->getBindings());
557 } catch (ShibTargetException&) {
560 insert (resource.getResource(), entry);
562 log->info("fetched and stored SAML response");
566 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
568 ReadLock rwlock(resource_lock);
570 log->debug("find: %s", resource_url);
571 map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
572 if (i==m_resources.end()) {
573 log->debug("no match found");
576 log->debug("match found");
580 void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
582 log->debug("inserting %s", resource);
584 resource_lock->wrlock();
585 m_resources[resource]=entry;
586 resource_lock->unlock();
589 // caller will delete the entry.. don't worry about that here.
590 void InternalCCacheEntry::remove(const char* resource)
592 log->debug("removing %s", resource);
594 resource_lock->wrlock();
595 m_resources.erase(resource);
596 resource_lock->unlock();
600 // a lock on a resource. This is a specific "table of locks" that
601 // will provide a mutex on a particular resource within a Cache Entry.
602 // Just instantiate a ResourceLock within scope of the function and it
603 // will obtain and hold the proper lock until it goes out of scope and
606 InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
608 entry(entry), resource(resource)
610 Mutex *mutex = find(resource);
614 InternalCCacheEntry::ResourceLock::~ResourceLock()
616 Mutex *mutex = find(resource);
620 Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
622 Lock(entry->pop_locks_lock);
624 map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
625 if (i==entry->populate_locks.end()) {
626 Mutex* mutex = Mutex::create();
627 entry->populate_locks[resource] = mutex;