Clone statement before deleting response, append null to serialized object
[shibboleth/sp.git] / shib-target / shib-ccache.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
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.
22  *
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
28  *
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.
33  *
34  *
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.
48  */
49
50
51 /*
52  * shib-ccache.cpp -- SHAR Credential Cache
53  *
54  * Originally from mod_shib
55  * Modified by: Derek Atkins <derek@ihtfp.com>
56  *
57  * $Id$
58  */
59
60 #if HAVE_UNISTD_H
61 # include <unistd.h>
62 #endif
63
64 #include "shib-target.h"
65 #include "ccache-utils.h"
66 #include <shib/shib-threads.h>
67
68 #include <log4cpp/Category.hh>
69
70 #include <sstream>
71 #include <stdexcept>
72
73 #ifdef HAVE_LIBDMALLOCXX
74 #include <dmalloc.h>
75 #endif
76
77 using namespace std;
78 using namespace saml;
79 using namespace shibboleth;
80 using namespace shibtarget;
81
82 class InternalCCache;
83 class InternalCCacheEntry : public CCacheEntry
84 {
85 public:
86   InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
87   ~InternalCCacheEntry();
88
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(); }
96
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(); }
101
102   static vector<SAMLAssertion*> g_emptyVector;
103
104 private:
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);
109
110   string m_statement;
111   string m_originSite;
112   string m_handle;
113   string m_clientAddress;
114   time_t m_sessionCreated;
115   time_t m_lastAccess;
116   bool m_hasbinding;
117
118   const SAMLSubject* m_subject;
119   SAMLAuthenticationStatement* p_auth;
120   InternalCCache *m_cache;
121
122   map<string,ResourceEntry*> m_resources;
123
124   log4cpp::Category* log;
125
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;
131
132   Mutex*        access_lock;
133   RWLock*       resource_lock;
134   RWLock*       cacheitem_lock;
135
136   class ResourceLock
137   {
138   public:
139     ResourceLock(InternalCCacheEntry* entry, string resource);
140     ~ResourceLock();
141
142   private:
143     Mutex*                      find(string& resource);
144     InternalCCacheEntry*        entry;
145     string                      resource;
146   };
147
148   friend class ResourceLock;
149 };
150
151 class InternalCCache : public CCache
152 {
153 public:
154   InternalCCache();
155   virtual ~InternalCCache();
156
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);
162
163   InternalCCacheEntry* findi(const char* key);
164   void  cleanup();
165
166 private:
167   RWLock *lock;
168
169   SAMLBinding* m_SAMLBinding;
170   map<string,InternalCCacheEntry*> m_hashtable;
171
172   log4cpp::Category* log;
173
174   static void*  cleanup_fcn(void*); // XXX Assumed an InternalCCache
175   bool          shutdown;
176   CondWait*     shutdown_wait;
177   Thread*       cleanup_thread;
178 };
179
180 namespace {
181   map<string,CCache::CCacheFactory> g_ccacheFactoryDB;
182 };
183
184 // Global Constructors & Destructors
185 CCache::~CCache() { }
186
187 void CCache::registerFactory(const char* name, CCache::CCacheFactory factory)
188 {
189   string ctx = "shibtarget.CCache";
190   log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
191   saml::NDC ndc("registerFactory");
192
193   log.info ("Registered factory %p for CCache %s", factory, name);
194   g_ccacheFactoryDB[name] = factory;
195 }
196
197 CCache* CCache::getInstance(const char* type)
198 {
199   string ctx = "shibtarget.CCache";
200   log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
201   saml::NDC ndc("getInstance");
202
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)());
207   }
208
209   log.info ("Loading default memory CCache");
210   return (CCache*) new InternalCCache();
211 }
212
213 // static members
214 vector<SAMLAssertion*> InternalCCacheEntry::g_emptyVector;
215
216
217 /******************************************************************************/
218 /* InternalCCache:  A Credential Cache                                        */
219 /******************************************************************************/
220
221 InternalCCache::InternalCCache()
222 {
223   m_SAMLBinding=SAMLBindingFactory::getInstance();
224   string ctx="shibtarget.InternalCCache";
225   log = &(log4cpp::Category::getInstance(ctx));
226   lock = RWLock::create();
227
228   shutdown_wait = CondWait::create();
229   shutdown = false;
230   cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
231 }
232
233 InternalCCache::~InternalCCache()
234 {
235   // Shut down the cleanup thread and let it know...
236   shutdown = true;
237   shutdown_wait->signal();
238   cleanup_thread->join(NULL);
239
240   delete m_SAMLBinding;
241   for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
242     delete i->second;
243   delete lock;
244   delete shutdown_wait;
245 }
246
247 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
248 {
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;
253   }
254   return NULL;
255 }
256
257 // assumed a lock is held..
258 InternalCCacheEntry* InternalCCache::findi(const char* key)
259 {
260   log->debug("FindI: \"%s\"", key);
261
262   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
263   if (i==m_hashtable.end()) {
264     log->debug("No Match found");
265     return NULL;
266   }
267   log->debug("Match Found.");
268
269   return i->second;
270 }
271
272 CCacheEntry* InternalCCache::find(const char* key)
273 {
274   log->debug("Find: \"%s\"", key);
275   ReadLock rwlock(lock);
276
277   InternalCCacheEntry* entry = findi(key);
278   if (!entry) return NULL;
279
280   // Lock the database for the caller -- they have to release the item.
281   entry->rdlock();
282   return dynamic_cast<CCacheEntry*>(entry);
283 }
284
285 void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
286                             const char *client_addr)
287 {
288   log->debug("caching new entry for \"%s\"", key);
289
290   InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
291   entry->setCache(this);
292
293   lock->wrlock();
294   m_hashtable[key]=entry;
295   lock->unlock();
296 }
297
298 // remove the entry from the database and then destroy the cacheentry
299 void InternalCCache::remove(const char* key)
300 {
301   log->debug("removing cache entry \"key\"", key);
302
303   // grab the entry from the database.  We'll have a readlock on it.
304   CCacheEntry* entry = findi(key);
305
306   if (!entry)
307     return;
308
309   // grab the cache write lock
310   lock->wrlock();
311
312   // verify we've still got the same entry.
313   if (entry != findi(key)) {
314     // Nope -- must've already been removed.
315     lock->unlock();
316     return;
317   }
318
319   // ok, remove the entry.
320   m_hashtable.erase(key);
321   lock->unlock();
322
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);
326   ientry->wrlock();
327
328   // we can release immediately because we know we're not in the database!
329   ientry->release();
330
331   // Now delete the entry
332   delete ientry;
333 }
334
335 void InternalCCache::cleanup()
336 {
337   Mutex* mutex = Mutex::create();
338   saml::NDC ndc("InternalCCache::cleanup()");
339
340   ShibTargetConfig& config = ShibTargetConfig::getConfig();
341   ShibINI& ini = config.getINI();
342
343   int rerun_timer = 0;
344   int timeout_life = 0;
345
346   string tag;
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());
351
352   if (rerun_timer <= 0)
353     rerun_timer = 300;          // rerun every 5 minutes
354
355   if (timeout_life <= 0)
356     timeout_life = 28800;       // timeout after 8 hours
357
358   mutex->lock();
359
360   log->debug("Cleanup thread started...  Run every %d secs; timeout after %d secs",
361              rerun_timer, timeout_life);
362
363   while (shutdown == false) {
364     shutdown_wait->timedwait(mutex,rerun_timer);
365
366     if (shutdown == true)
367       break;
368
369     log->info("Cleanup thread running...");
370
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.
376
377     // Pass 1: iterate over the map and find all entries that have not been
378     // used in X hours
379     vector<string> stale_keys;
380     time_t stale = time(NULL) - timeout_life;
381
382     lock->rdlock();
383     for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
384          i != m_hashtable.end(); i++)
385     {
386       // If the last access was BEFORE the stale timeout...
387       time_t last=i->second->lastAccess();
388       if (last < stale)
389         stale_keys.push_back(i->first);
390     }
391     lock->unlock();
392
393     log->info("deleting %d old items.", stale_keys.size());
394
395     // Pass 2: walk through the list of stale entries and remove them from
396     // the database
397     for (vector<string>::iterator j = stale_keys.begin();
398          j != stale_keys.end(); j++)
399     {
400       remove (j->c_str());
401     }
402
403   }
404
405   log->debug("Cleanup thread finished.");
406
407   mutex->unlock();
408   delete mutex;
409   Thread::exit(NULL);
410 }
411
412 void* InternalCCache::cleanup_fcn(void* cache_p)
413 {
414   InternalCCache* cache = (InternalCCache*)cache_p;
415
416   // First, let's block all signals
417   sigset_t sigmask;
418   sigfillset(&sigmask);
419   Thread::mask_signals(SIG_BLOCK, &sigmask, NULL);
420
421   // Now run the cleanup process.
422   cache->cleanup();
423 }
424
425 /******************************************************************************/
426 /* InternalCCacheEntry:  A Credential Cache Entry                             */
427 /******************************************************************************/
428
429 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
430   : m_hasbinding(false)
431 {
432   string ctx = "shibtarget::InternalCCacheEntry";
433   log = &(log4cpp::Category::getInstance(ctx));
434   pop_locks_lock = Mutex::create();
435   access_lock = Mutex::create();
436   resource_lock = RWLock::create();
437   cacheitem_lock = RWLock::create();
438
439   if (s == NULL) {
440     log->error("NULL auth statement");
441     throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
442   }
443
444   m_subject = s->getSubject();
445
446   xstring name = m_subject->getName();
447   xstring qual = m_subject->getNameQualifier();
448
449   auto_ptr<char> h(XMLString::transcode(name.c_str()));
450   auto_ptr<char> d(XMLString::transcode(qual.c_str()));
451
452   m_handle = h.get();
453   m_originSite = d.get();
454
455   Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
456   if (bindings.hasNext())
457     m_hasbinding = true;
458
459   m_clientAddress = client_addr;
460   m_sessionCreated = m_lastAccess = time(NULL);
461
462   // Save for later.
463   p_auth = static_cast<SAMLAuthenticationStatement*>(s->clone());
464   
465   // Save the serialized version of the auth statement
466   ostringstream os;
467   os << *s << '\0';
468   m_statement = os.str();
469
470   log->info("New Session Created...");
471   log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
472              client_addr);
473 }
474
475 InternalCCacheEntry::~InternalCCacheEntry()
476 {
477   log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
478   delete p_auth;
479   for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
480        i!=m_resources.end(); i++)
481     delete i->second;
482
483   for (map<string,Mutex*>::iterator j=populate_locks.begin();
484        j!=populate_locks.end(); j++)
485     delete j->second;
486
487   delete pop_locks_lock;
488   delete cacheitem_lock;
489   delete resource_lock;
490   delete access_lock;
491 }
492
493 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
494 {
495   saml::NDC ndc("isSessionValid");
496   log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
497              m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
498   time_t now=time(NULL);
499   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
500     log->debug("session beyond lifetime");
501     return false;
502   }
503
504   // Lock the access-time from here until we return
505   Lock lock(access_lock);
506   if (timeout > 0 && now-m_lastAccess >= timeout) {
507     log->debug("session timed out");
508     return false;
509   }
510   m_lastAccess=now;
511   return true;
512 }
513
514 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
515 {
516   saml::NDC ndc("getAssertions");
517   ResourceEntry* entry = populate(resource, 0);
518   if (entry)
519     return entry->getAssertions();
520   return Iterator<SAMLAssertion*>(InternalCCacheEntry::g_emptyVector);
521 }
522
523 void InternalCCacheEntry::preFetch(Resource& resource, int prefetch_window)
524 {
525   saml::NDC ndc("preFetch");
526   ResourceEntry* entry = populate(resource, prefetch_window);
527 }
528
529 ResourceEntry* InternalCCacheEntry::populate(Resource& resource, int slop)
530 {
531   saml::NDC ndc("populate");
532   log->debug("populating entry for %s (%s)",
533              resource.getResource(), resource.getURL());
534
535   // Lock the resource within this entry...
536   InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
537
538   // Can we use what we have?
539   ResourceEntry *entry = find(resource.getResource());
540   if (entry) {
541     log->debug("found resource");
542     if (entry->isValid(slop))
543       return entry;
544
545     // entry is invalid (expired) -- go fetch a new one.
546     log->debug("removing resource cache; assertion is invalid");
547     remove (resource.getResource());
548     delete entry;
549   }
550
551   // Nope, no entry.. Create a new resource entry
552
553   if (!m_hasbinding) {
554     log->error("No binding!");
555     return NULL;
556   }
557
558   log->info("trying to request attributes for %s@%s -> %s",
559             m_handle.c_str(), m_originSite.c_str(), resource.getURL());
560
561   try {
562     entry = new ResourceEntry(resource, *m_subject, m_cache, p_auth->getBindings());
563   } catch (ShibTargetException &e) {
564     return NULL;
565   }
566   insert (resource.getResource(), entry);
567
568   log->info("fetched and stored SAML response");
569   return entry;
570 }
571
572 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
573 {
574   ReadLock rwlock(resource_lock);
575
576   log->debug("find: %s", resource_url);
577   map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
578   if (i==m_resources.end()) {
579     log->debug("no match found");
580     return NULL;
581   }
582   log->debug("match found");
583   return i->second;
584 }
585
586 void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
587 {
588   log->debug("inserting %s", resource);
589
590   resource_lock->wrlock();
591   m_resources[resource]=entry;
592   resource_lock->unlock();
593 }
594
595 // caller will delete the entry.. don't worry about that here.
596 void InternalCCacheEntry::remove(const char* resource)
597 {
598   log->debug("removing %s", resource);
599
600   resource_lock->wrlock();
601   m_resources.erase(resource);
602   resource_lock->unlock();
603 }
604
605
606 // a lock on a resource.  This is a specific "table of locks" that
607 // will provide a mutex on a particular resource within a Cache Entry.
608 // Just instantiate a ResourceLock within scope of the function and it
609 // will obtain and hold the proper lock until it goes out of scope and
610 // deconstructs.
611
612 InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
613                                                 string resource) :
614   entry(entry), resource(resource)
615 {
616   Mutex *mutex = find(resource);
617   mutex->lock();
618 }
619
620 InternalCCacheEntry::ResourceLock::~ResourceLock()
621 {
622   Mutex *mutex = find(resource);
623   mutex->unlock();
624 }
625
626 Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
627 {
628   Lock(entry->pop_locks_lock);
629   
630   map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
631   if (i==entry->populate_locks.end()) {
632     Mutex* mutex = Mutex::create();
633     entry->populate_locks[resource] = mutex;
634     return mutex;
635   }
636   return i->second;
637 }