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 <shib/shib-threads.h>
67 #include <log4cpp/Category.hh>
72 #ifdef HAVE_LIBDMALLOCXX
78 using namespace shibboleth;
79 using namespace shibtarget;
84 ResourceEntry(SAMLResponse* response);
87 bool isValid(int slop);
88 Iterator<SAMLAssertion*> getAssertions();
90 static vector<SAMLAssertion*> g_emptyVector;
93 SAMLResponse* m_response;
95 log4cpp::Category* log;
99 class InternalCCacheEntry : public CCacheEntry
102 InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
103 ~InternalCCacheEntry();
105 virtual Iterator<SAMLAssertion*> getAssertions(Resource& resource);
106 virtual void preFetch(Resource& resource, int prefetch_window);
107 virtual bool isSessionValid(time_t lifetime, time_t timeout);
108 virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
109 virtual void release() { cacheitem_lock->unlock(); }
111 void setCache(InternalCCache *cache) { m_cache = cache; }
112 time_t lastAccess() { Lock lock(access_lock); return m_lastAccess; }
113 void rdlock() { cacheitem_lock->rdlock(); }
114 void wrlock() { cacheitem_lock->wrlock(); }
117 ResourceEntry* populate(Resource& resource, int slop);
118 ResourceEntry* find(const char* resource);
119 void insert(const char* resource, ResourceEntry* entry);
120 void remove(const char* resource);
124 string m_clientAddress;
125 time_t m_sessionCreated;
129 const SAMLSubject* m_subject;
130 SAMLAuthenticationStatement* p_auth;
131 InternalCCache *m_cache;
133 map<string,ResourceEntry*> m_resources;
135 static saml::QName g_authorityKind;
136 static saml::QName g_respondWith;
138 log4cpp::Category* log;
140 // This is used to keep track of in-process "populate()" calls,
141 // to make sure that we don't try to populate the same resource
142 // in multiple threads.
143 map<string,Mutex*> populate_locks;
144 Mutex* pop_locks_lock;
147 RWLock* resource_lock;
148 RWLock* cacheitem_lock;
153 ResourceLock(InternalCCacheEntry* entry, string resource);
157 Mutex* find(string& resource);
158 InternalCCacheEntry* entry;
163 class InternalCCache : public CCache
167 virtual ~InternalCCache();
169 virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
170 virtual CCacheEntry* find(const char* key);
171 virtual void insert(const char* key, SAMLAuthenticationStatement *s,
172 const char *client_addr);
173 virtual void remove(const char* key);
175 InternalCCacheEntry* findi(const char* key);
181 SAMLBinding* m_SAMLBinding;
182 map<string,InternalCCacheEntry*> m_hashtable;
184 log4cpp::Category* log;
186 static void* cleanup_fcn(void*); // XXX Assumed an InternalCCache
188 CondWait* shutdown_wait;
189 Thread* cleanup_thread;
192 // Global Constructors & Destructors
193 CCache::~CCache() { }
195 CCache* CCache::getInstance(const char* type)
197 return (CCache*) new InternalCCache();
201 saml::QName InternalCCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery));
202 saml::QName InternalCCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement));
203 vector<SAMLAssertion*> ResourceEntry::g_emptyVector;
206 /******************************************************************************/
207 /* InternalCCache: A Credential Cache */
208 /******************************************************************************/
210 InternalCCache::InternalCCache()
212 m_SAMLBinding=SAMLBindingFactory::getInstance();
213 string ctx="shibtarget.InternalCCache";
214 log = &(log4cpp::Category::getInstance(ctx));
215 lock = RWLock::create();
217 shutdown_wait = CondWait::create();
219 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
222 InternalCCache::~InternalCCache()
224 // Shut down the cleanup thread and let it know...
226 shutdown_wait->signal();
227 cleanup_thread->join(NULL);
229 delete m_SAMLBinding;
230 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
233 delete shutdown_wait;
236 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
238 log->debug("looking for binding...");
239 if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
240 log->debug("https binding found");
241 return m_SAMLBinding;
246 // assumed a lock is held..
247 InternalCCacheEntry* InternalCCache::findi(const char* key)
249 log->debug("FindI: \"%s\"", key);
251 map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
252 if (i==m_hashtable.end()) {
253 log->debug("No Match found");
256 log->debug("Match Found.");
261 CCacheEntry* InternalCCache::find(const char* key)
263 log->debug("Find: \"%s\"", key);
264 ReadLock rwlock(lock);
266 InternalCCacheEntry* entry = findi(key);
267 if (!entry) return NULL;
269 // Lock the database for the caller -- they have to release the item.
271 return dynamic_cast<CCacheEntry*>(entry);
274 void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
275 const char *client_addr)
277 log->debug("caching new entry for \"%s\"", key);
279 InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
280 entry->setCache(this);
283 m_hashtable[key]=entry;
287 // remove the entry from the database and then destroy the cacheentry
288 void InternalCCache::remove(const char* key)
290 log->debug("removing cache entry \"key\"", key);
292 // grab the entry from the database. We'll have a readlock on it.
293 CCacheEntry* entry = findi(key);
298 // grab the cache write lock
301 // verify we've still got the same entry.
302 if (entry != findi(key)) {
303 // Nope -- must've already been removed.
308 // ok, remove the entry.
309 m_hashtable.erase(key);
312 // now grab the write lock on the cacheitem.
313 // This will make sure all other threads have released this item.
314 InternalCCacheEntry* ientry = dynamic_cast<InternalCCacheEntry*>(entry);
317 // we can release immediately because we know we're not in the database!
320 // Now delete the entry
324 void InternalCCache::cleanup()
326 Mutex* mutex = Mutex::create();
327 saml::NDC ndc("InternalCCache::cleanup()");
329 ShibTargetConfig& config = ShibTargetConfig::getConfig();
330 ShibINI& ini = config.getINI();
333 int timeout_life = 0;
336 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHECLEAN, true, &tag))
337 rerun_timer = atoi(tag.c_str());
338 if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHETIMEOUT, true, &tag))
339 timeout_life = atoi(tag.c_str());
341 if (rerun_timer <= 0)
342 rerun_timer = 300; // rerun every 5 minutes
344 if (timeout_life <= 0)
345 timeout_life = 28800; // timeout after 8 hours
349 log->debug("Cleanup thread started... Run every %d secs; timeout after %d secs",
350 rerun_timer, timeout_life);
352 while (shutdown == false) {
354 memset (&ts, 0, sizeof(ts));
355 ts.tv_sec = time(NULL) + rerun_timer;
357 shutdown_wait->timedwait(mutex, &ts);
359 if (shutdown == true)
362 log->info("Cleanup thread running...");
364 // Ok, let's run through the cleanup process and clean out
365 // really old sessions. This is a two-pass process. The
366 // first pass is done holding a read-lock while we iterate over
367 // the database. The second pass doesn't need a lock because
368 // the 'deletes' will lock the database.
370 // Pass 1: iterate over the map and find all entries that have not been
372 vector<string> stale_keys;
373 time_t stale = time(NULL) - timeout_life;
376 for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
377 i != m_hashtable.end(); i++)
379 // If the last access was BEFORE the stale timeout...
380 if (i->second->lastAccess() < stale)
381 stale_keys.push_back(i->first);
385 log->info("deleting %d old items.", stale_keys.size());
387 // Pass 2: walk through the list of stale entries and remove them from
389 for (vector<string>::iterator i = stale_keys.begin();
390 i != stale_keys.end(); i++)
397 log->debug("Cleanup thread finished.");
404 void* InternalCCache::cleanup_fcn(void* cache_p)
406 InternalCCache* cache = (InternalCCache*)cache_p;
408 // First, let's block all signals
410 sigfillset(&sigmask);
411 Thread::mask_signals(SIG_BLOCK, &sigmask, NULL);
413 // Now run the cleanup process.
417 /******************************************************************************/
418 /* InternalCCacheEntry: A Credential Cache Entry */
419 /******************************************************************************/
421 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
422 : m_hasbinding(false)
424 string ctx = "shibtarget::InternalCCacheEntry";
425 log = &(log4cpp::Category::getInstance(ctx));
426 pop_locks_lock = Mutex::create();
427 access_lock = Mutex::create();
428 resource_lock = RWLock::create();
429 cacheitem_lock = RWLock::create();
432 log->error("NULL auth statement");
433 throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
436 m_subject = s->getSubject();
438 xstring name = m_subject->getName();
439 xstring qual = m_subject->getNameQualifier();
441 auto_ptr<char> h(XMLString::transcode(name.c_str()));
442 auto_ptr<char> d(XMLString::transcode(qual.c_str()));
445 m_originSite = d.get();
447 Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
448 if (bindings.hasNext())
451 m_clientAddress = client_addr;
452 m_sessionCreated = m_lastAccess = time(NULL);
457 log->info("New Session Created...");
458 log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
462 InternalCCacheEntry::~InternalCCacheEntry()
464 log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
466 for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
467 i!=m_resources.end(); i++)
470 for (map<string,Mutex*>::iterator i=populate_locks.begin();
471 i!=populate_locks.end(); i++)
474 delete pop_locks_lock;
475 delete cacheitem_lock;
476 delete resource_lock;
480 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
482 saml::NDC ndc("isSessionValid");
483 log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
484 m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
485 time_t now=time(NULL);
486 if (lifetime > 0 && now > m_sessionCreated+lifetime) {
487 log->debug("session beyond lifetime");
491 // Lock the access-time from here until we return
492 Lock lock(access_lock);
493 if (timeout > 0 && now-m_lastAccess >= timeout) {
494 log->debug("session timed out");
501 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
503 saml::NDC ndc("getAssertions");
504 ResourceEntry* entry = populate(resource, 0);
506 return entry->getAssertions();
507 return Iterator<SAMLAssertion*>(ResourceEntry::g_emptyVector);
510 void InternalCCacheEntry::preFetch(Resource& resource, int prefetch_window)
512 saml::NDC ndc("preFetch");
513 ResourceEntry* entry = populate(resource, prefetch_window);
516 ResourceEntry* InternalCCacheEntry::populate(Resource& resource, int slop)
518 saml::NDC ndc("populate");
519 log->debug("populating entry for %s (%s)",
520 resource.getResource(), resource.getURL());
522 // Lock the resource within this entry...
523 InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
525 // Can we use what we have?
526 ResourceEntry *entry = find(resource.getResource());
528 log->debug("found resource");
529 if (entry->isValid(slop))
532 // entry is invalid (expired) -- go fetch a new one.
533 log->debug("removing resource cache; assertion is invalid");
534 remove (resource.getResource());
538 // Nope, no entry.. Create a new resource entry
541 log->error("No binding!");
545 log->info("trying to request attributes for %s@%s -> %s",
546 m_handle.c_str(), m_originSite.c_str(), resource.getURL());
548 auto_ptr<XMLCh> resourceURL(XMLString::transcode(resource.getURL()));
549 Iterator<saml::QName> respond_withs = ArrayIterator<saml::QName>(&g_respondWith);
551 // Clone the subject...
552 // 1) I know the static_cast is safe from clone()
553 // 2) the AttributeQuery will destroy this new subject.
554 SAMLSubject* subject=static_cast<SAMLSubject*>(m_subject->clone());
556 // Build a SAML Request....
557 SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resourceURL.get(),
558 resource.getDesignators());
559 SAMLRequest* req=new SAMLRequest(respond_withs,q);
561 // Try this request against all the bindings in the AuthenticationStatement
562 // (i.e. send it to each AA in the list of bindings)
563 Iterator<SAMLAuthorityBinding*> bindings = p_auth->getBindings();
564 SAMLResponse* response = NULL;
566 while (!response && bindings.hasNext()) {
567 SAMLAuthorityBinding* binding = bindings.next();
569 log->debug("Trying binding...");
570 SAMLBinding* pBinding=m_cache->getBinding(binding->getBinding());
571 log->debug("Sending request");
572 response=pBinding->send(*binding,*req);
575 // ok, we can delete the request now.
578 // Make sure we got a response
580 log->info ("No Response");
584 entry = new ResourceEntry(response);
585 insert (resource.getResource(), entry);
587 log->info("fetched and stored SAML response");
591 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
593 ReadLock rwlock(resource_lock);
595 log->debug("find: %s", resource_url);
596 map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
597 if (i==m_resources.end()) {
598 log->debug("no match found");
601 log->debug("match found");
605 void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
607 log->debug("inserting %s", resource);
609 resource_lock->wrlock();
610 m_resources[resource]=entry;
611 resource_lock->unlock();
614 // caller will delete the entry.. don't worry about that here.
615 void InternalCCacheEntry::remove(const char* resource)
617 log->debug("removing %s", resource);
619 resource_lock->wrlock();
620 m_resources.erase(resource);
621 resource_lock->unlock();
625 // a lock on a resource. This is a specific "table of locks" that
626 // will provide a mutex on a particular resource within a Cache Entry.
627 // Just instantiate a ResourceLock within scope of the function and it
628 // will obtain and hold the proper lock until it goes out of scope and
631 InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
633 entry(entry), resource(resource)
635 Mutex *mutex = find(resource);
639 InternalCCacheEntry::ResourceLock::~ResourceLock()
641 Mutex *mutex = find(resource);
645 Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
647 Lock(entry->pop_locks_lock);
649 map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
650 if (i==entry->populate_locks.end()) {
651 Mutex* mutex = Mutex::create();
652 entry->populate_locks[resource] = mutex;
658 /******************************************************************************/
659 /* ResourceEntry: A Credential Cache Entry for a particular Resource URL */
660 /******************************************************************************/
662 ResourceEntry::ResourceEntry(SAMLResponse* response)
664 string ctx = "shibtarget::ResourceEntry";
665 log = &(log4cpp::Category::getInstance(ctx));
667 log->info("caching resource entry");
669 m_response = response;
672 ResourceEntry::~ResourceEntry()
677 Iterator<SAMLAssertion*> ResourceEntry::getAssertions()
679 saml::NDC ndc("getAssertions");
680 return m_response->getAssertions();
683 bool ResourceEntry::isValid(int slop)
685 saml::NDC ndc("isValid");
687 log->info("checking validity");
689 // This is awful, but the XMLDateTime class is truly horrible.
690 time_t now=time(NULL)+slop;
692 struct tm* ptime=gmtime(&now);
695 struct tm* ptime=gmtime_r(&now,&res);
698 strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
699 auto_ptr<XMLCh> timeptr(XMLString::transcode(timebuf));
700 XMLDateTime curDateTime(timeptr.get());
701 curDateTime.parseDateTime();
703 Iterator<SAMLAssertion*> iter = getAssertions();
705 while (iter.hasNext()) {
706 SAMLAssertion* assertion = iter.next();
708 log->debug ("testing assertion...");
710 const XMLDateTime* thistime = assertion->getNotOnOrAfter();
713 log->debug ("getNotOnOrAfter failed.");
717 auto_ptr<char> nowptr(XMLString::transcode(curDateTime.toString()));
718 auto_ptr<char> assnptr(XMLString::transcode(thistime->toString()));
720 log->debug ("comparing now (%s) to %s", nowptr.get(), assnptr.get());
721 int result=XMLDateTime::compareOrder(&curDateTime, thistime);
723 if (result != XMLDateTime::LESS_THAN) {
724 log->debug("nope, not still valid");
729 log->debug("yep, all still valid");