New backing store API for cache.
[shibboleth/sp.git] / shib-target / shib-ccache.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * shib-ccache.cpp -- in-memory session cache plugin
19  *
20  * $Id$
21  */
22
23 #include "internal.h"
24
25 #if HAVE_UNISTD_H
26 # include <unistd.h>
27 #endif
28
29 #include <shib/shib-threads.h>
30
31 #include <log4cpp/Category.hh>
32
33 #include <algorithm>
34 #include <sstream>
35 #include <stdexcept>
36
37 #ifdef HAVE_LIBDMALLOCXX
38 #include <dmalloc.h>
39 #endif
40
41 using namespace std;
42 using namespace log4cpp;
43 using namespace saml;
44 using namespace shibboleth;
45 using namespace shibtarget;
46
47 static const XMLCh cleanupInterval[] =
48 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
49   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
50 };
51 static const XMLCh cacheTimeout[] =
52 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
53   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
54 };
55 static const XMLCh AAConnectTimeout[] =
56 { chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
57   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
58 };
59 static const XMLCh AATimeout[] =
60 { chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
61
62 static const XMLCh defaultLifetime[] =
63 { chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
64   chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
65 };
66 static const XMLCh retryInterval[] =
67 { chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
68   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
69 };
70 static const XMLCh strictValidity[] =
71 { chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
72   chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
73 };
74 static const XMLCh propagateErrors[] =
75 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
76   chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
77 };
78
79
80 /*
81  * Stubbed out, inproc version of an ISessionCacheEntry
82  */
83 class StubCacheEntry : public virtual ISessionCacheEntry
84 {
85 public:
86     StubCacheEntry(Category* log) : m_log(log), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
87     StubCacheEntry(DDF& obj, Category* log)
88         : m_log(log), m_obj(obj), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
89     ~StubCacheEntry() { m_obj.destroy(); delete m_pSubject; delete m_pUnfiltered; delete m_pFiltered; }
90     void lock() {}
91     void unlock() { delete this; }
92     const char* getClientAddress() const { return m_obj["client_address"].string(); }
93     const char* getProviderId() const { return m_obj["provider_id"].string(); }
94     const char* getAuthnContext() const { return m_obj["authn_context"].string(); }
95     pair<const char*,const SAMLSubject*> getSubject(bool xml=true, bool obj=false) const;
96     pair<const char*,const SAMLResponse*> getTokens(bool xml=true, bool obj=false) const;
97     pair<const char*,const SAMLResponse*> getFilteredTokens(bool xml=true, bool obj=false) const;
98
99 protected:
100     Category* m_log;
101     mutable DDF m_obj;
102     mutable SAMLSubject* m_pSubject;
103     mutable SAMLResponse* m_pUnfiltered;
104     mutable SAMLResponse* m_pFiltered;
105 };
106
107 pair<const char*,const SAMLSubject*> StubCacheEntry::getSubject(bool xml, bool obj) const
108 {
109     const char* raw=m_obj["subject"].string();
110     pair<const char*,const SAMLSubject*> ret=pair<const char*,const SAMLSubject*>(NULL,NULL);
111     if (xml)
112         ret.first=raw;
113     if (obj) {
114         if (!m_pSubject) {
115             istringstream in(raw);
116             m_log->debugStream() << "decoding subject: " << (raw ? raw : "(none)") << CategoryStream::ENDLINE;
117             m_pSubject=raw ? new SAMLSubject(in) : NULL;
118         }
119         ret.second=m_pSubject;
120     }
121     return ret;
122 }
123
124 pair<const char*,const SAMLResponse*> StubCacheEntry::getTokens(bool xml, bool obj) const
125 {
126     const char* unfiltered=m_obj["tokens.unfiltered"].string();
127     pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
128     if (xml)
129         ret.first=unfiltered;
130     if (obj) {
131         if (!m_pUnfiltered) {
132             if (unfiltered) {
133                 istringstream in(unfiltered);
134                 m_log->debugStream() << "decoding unfiltered tokens: " << unfiltered << CategoryStream::ENDLINE;
135                 m_pUnfiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
136             }
137         }
138         ret.second=m_pUnfiltered;
139     }
140     return ret;
141 }
142
143 pair<const char*,const SAMLResponse*> StubCacheEntry::getFilteredTokens(bool xml, bool obj) const
144 {
145     const char* filtered=m_obj["tokens.filtered"].string();
146     if (!filtered)
147         return getTokens(xml,obj);
148     pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
149     if (xml)
150         ret.first=filtered;
151     if (obj) {
152         if (!m_pFiltered) {
153             istringstream in(filtered);
154             m_log->debugStream() << "decoding filtered tokens: " << filtered << CategoryStream::ENDLINE;
155             m_pFiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
156         }
157         ret.second=m_pFiltered;
158     }
159     return ret;
160 }
161
162 /*
163  * Remoting front-half of session cache, drops out in single process deployments.
164  *  TODO: Add buffering of frequently-used entries.
165  */
166 class StubCache : public virtual ISessionCache
167 {
168 public:
169     StubCache(const DOMElement* e);
170
171     string insert(
172         const IApplication* application,
173         const IEntityDescriptor* source,
174         const char* client_addr,
175         const SAMLSubject* subject,
176         const char* authnContext,
177         const SAMLResponse* tokens
178     );
179     ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
180     void remove(const char* key, const IApplication* application, const char* client_addr);
181
182     bool setBackingStore(ISessionCacheStore*) { return false; }
183
184 private:
185     Category* m_log;
186 };
187
188 StubCache::StubCache(const DOMElement* e) : m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")) {}
189
190 /*
191  * The public methods are remoted using the message passing system.
192  * In practice, insert is unlikely to be used remotely, but just in case...
193  */
194
195 string StubCache::insert(
196     const IApplication* application,
197     const IEntityDescriptor* source,
198     const char* client_addr,
199     const SAMLSubject* subject,
200     const char* authnContext,
201     const SAMLResponse* tokens
202     )
203 {
204     DDF in("SessionCache::insert"),out;
205     DDFJanitor jin(in),jout(out);
206     in.structure();
207     in.addmember("application_id").string(application->getId());
208     in.addmember("client_address").string(client_addr);
209     auto_ptr_char provid(source->getId());
210     in.addmember("provider_id").string(provid.get());
211     in.addmember("major_version").integer(1);
212     in.addmember("minor_version").integer(tokens->getMinorVersion());
213     in.addmember("authn_context").string(authnContext);
214     
215     ostringstream os;
216     os << *subject;
217     in.addmember("subject").string(os.str().c_str());
218     os.str("");
219     os << *tokens;
220     in.addmember("tokens.unfiltered").string(os.str().c_str());
221
222     out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
223     if (out["key"].isstring())
224         return out["key"].string();
225     throw InvalidSessionException("A remoted cache insertion operation did not return a usable session key.");
226 }
227
228 ISessionCacheEntry* StubCache::find(const char* key, const IApplication* application, const char* client_addr)
229 {
230     DDF in("SessionCache::find"),out;
231     DDFJanitor jin(in);
232     in.structure();
233     in.addmember("key").string(key);
234     in.addmember("application_id").string(application->getId());
235     in.addmember("client_address").string(client_addr);
236     
237     try {
238         out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
239         if (!out.isstruct()) {
240             out.destroy();
241             return NULL;
242         }
243         
244         // Wrap the results in a stub entry and return it to the caller.
245         return new StubCacheEntry(out,m_log);
246     }
247     catch (...) {
248         out.destroy();
249         throw;
250     }
251 }
252
253 void StubCache::remove(const char* key, const IApplication* application, const char* client_addr)
254 {
255     DDF in("SessionCache::remove");
256     DDFJanitor jin(in);
257     in.structure();
258     in.addmember("key").string(key);
259     in.addmember("application_id").string(application->getId());
260     in.addmember("client_address").string(client_addr);
261     
262     ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
263 }
264
265 /*
266  * Long-lived cache entries that store the actual sessions and
267  * wrap attribute query/refresh/filtering
268  */
269 class MemorySessionCache;
270 class MemorySessionCacheEntry : public virtual ISessionCacheEntry, public virtual StubCacheEntry
271 {
272 public:
273     MemorySessionCacheEntry(
274         MemorySessionCache* cache,
275         const char* key,
276         const IApplication* application,
277         const IEntityDescriptor* source,
278         const char* client_addr,
279         const SAMLSubject* subject,
280         const char* authnContext,
281         const SAMLResponse* tokens
282         );
283     MemorySessionCacheEntry(
284         MemorySessionCache* cache,
285         const char* key,
286         const IApplication* application,
287         const IEntityDescriptor* source,
288         const char* client_addr,
289         const char* subject,
290         const char* authnContext,
291         const char* tokens,
292         int majorVersion,
293         int minorVersion,
294         time_t created,
295         time_t accessed
296         );
297     ~MemorySessionCacheEntry();
298
299     void lock() { m_lock->lock(); }
300     void unlock() { m_lock->unlock(); }
301     
302     HRESULT isValid(const IApplication* application, const char* client_addr) const;
303     void populate() const;
304     bool checkApplication(const IApplication* application) { return (m_obj["application_id"]==application->getId()); }
305     time_t created() const { return m_sessionCreated; }
306     time_t lastAccess() const { return m_lastAccess; }
307     const DDF& getDDF() const { return m_obj; }
308   
309 private:
310     bool hasAttributes(const SAMLResponse& r) const;
311     time_t calculateExpiration(const SAMLResponse& r) const;
312     pair<SAMLResponse*,SAMLResponse*> getNewResponse() const;   // wraps an actual query
313     SAMLResponse* filter(const SAMLResponse* r, const IApplication* application, const IEntityDescriptor* source) const;
314   
315     time_t m_sessionCreated;
316     mutable time_t m_responseExpiration, m_lastAccess, m_lastRetry;
317
318     MemorySessionCache* m_cache;
319     Mutex* m_lock;
320 };
321
322 /*
323  * The actual in-memory session cache implementation.
324  */
325 class MemorySessionCache : public virtual ISessionCache, public virtual IRemoted
326 {
327 public:
328     MemorySessionCache(const DOMElement* e);
329     virtual ~MemorySessionCache();
330
331     DDF receive(const DDF& in);
332
333     string insert(
334         const IApplication* application,
335         const IEntityDescriptor* source,
336         const char* client_addr,
337         const SAMLSubject* subject,
338         const char* authnContext,
339         const SAMLResponse* tokens
340     );
341     ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
342     void remove(const char* key, const IApplication* application, const char* client_addr);
343
344     void cleanup();
345
346     bool setBackingStore(ISessionCacheStore* store);
347
348 private:
349     const DOMElement* m_root;         // Only valid during initialization
350     RWLock* m_lock;
351     map<string,MemorySessionCacheEntry*> m_hashtable;
352
353     Category* m_log;
354     IRemoted* restoreInsert;
355     IRemoted* restoreFind;
356     IRemoted* restoreRemove;
357     ISessionCacheStore* m_sink;
358
359     void dormant(const char* key);
360     static void* cleanup_fcn(void*);
361     bool shutdown;
362     CondWait* shutdown_wait;
363     Thread* cleanup_thread;
364   
365     // extracted config settings
366     unsigned int m_AATimeout,m_AAConnectTimeout;
367     unsigned int m_defaultLifetime,m_retryInterval;
368     bool m_strictValidity,m_propagateErrors;
369     friend class MemorySessionCacheEntry;
370 };
371
372 MemorySessionCacheEntry::MemorySessionCacheEntry(
373     MemorySessionCache* cache,
374     const char* key,
375     const IApplication* application,
376     const IEntityDescriptor* source,
377     const char* client_addr,
378     const SAMLSubject* subject,
379     const char* authnContext,
380     const SAMLResponse* tokens
381     ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
382 {
383     m_sessionCreated = m_lastAccess = time(NULL);
384
385     // Store session properties in DDF.
386     m_obj=DDF(NULL).structure();
387     m_obj.addmember("key").string(key);
388     m_obj.addmember("client_address").string(client_addr);
389     m_obj.addmember("application_id").string(application->getId());
390     auto_ptr_char pid(source->getId());
391     m_obj.addmember("provider_id").string(pid.get());
392     m_obj.addmember("major_version").integer(1);
393     m_obj.addmember("minor_version").integer(tokens->getMinorVersion());
394
395     // Save the subject as XML.
396     ostringstream os;
397     os << *subject;
398     m_obj.addmember("subject").string(os.str().c_str());
399     
400     // Save the authn method.
401     m_obj.addmember("authn_context").string(authnContext);
402
403     // Serialize unfiltered assertions.
404     os.str("");
405     os << *tokens;
406     m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
407
408     if (hasAttributes(*tokens)) {
409         // Filter attributes in the response.
410         auto_ptr<SAMLResponse> filtered(filter(tokens, application, source));
411         
412         // Calculate expiration.
413         m_responseExpiration=calculateExpiration(*(filtered.get()));
414         
415         // Serialize filtered assertions (if changes were made).
416         os.str("");
417         os << *(filtered.get());
418         string fstr=os.str();
419         if (fstr.length() != m_obj["tokens.unfiltered"].strlen())
420             m_obj.addmember("tokens.filtered").string(fstr.c_str());
421
422         // Save actual objects only if we're running inprocess. The subject needs to be
423         // owned by the entry, so we'll defer creation of a cloned copy.
424         if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
425             if (m_obj["tokens.filtered"].isstring())
426                 m_pFiltered=filtered.release();
427         }
428     }
429     
430     m_lock = Mutex::create();
431
432     if (m_log->isDebugEnabled()) {
433         m_log->debug("new cache entry created: SessionID (%s) IdP (%s) Address (%s)", key, pid.get(), client_addr);
434     }
435
436     // Transaction Logging
437     auto_ptr_char hname(subject->getNameIdentifier()->getName());
438     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
439     stc.getTransactionLog().infoStream() <<
440         "New session (ID: " <<
441             key <<
442         ") with (applicationId: " <<
443             application->getId() <<
444         ") for principal from (IdP: " <<
445             pid.get() <<
446         ") at (ClientAddress: " <<
447             client_addr <<
448         ") with (NameIdentifier: " <<
449             hname.get() <<
450         ")";
451     stc.releaseTransactionLog();
452 }
453
454 MemorySessionCacheEntry::MemorySessionCacheEntry(
455     MemorySessionCache* cache,
456     const char* key,
457     const IApplication* application,
458     const IEntityDescriptor* source,
459     const char* client_addr,
460     const char* subject,
461     const char* authnContext,
462     const char* tokens,
463     int majorVersion,
464     int minorVersion,
465     time_t created,
466     time_t accessed
467     ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
468 {
469     m_sessionCreated = created;
470     m_lastAccess = accessed;
471
472     // Reconstitute the tokens for filtering.
473     istringstream is(tokens);
474     auto_ptr<SAMLResponse> unfiltered(new SAMLResponse(is,minorVersion));
475
476     // Store session properties in DDF.
477     m_obj=DDF(NULL).structure();
478     m_obj.addmember("key").string(key);
479     m_obj.addmember("client_address").string(client_addr);
480     m_obj.addmember("application_id").string(application->getId());
481     auto_ptr_char pid(source->getId());
482     m_obj.addmember("provider_id").string(pid.get());
483     m_obj.addmember("subject").string(subject);
484     m_obj.addmember("authn_context").string(authnContext);
485     m_obj.addmember("tokens.unfiltered").string(tokens);
486     m_obj.addmember("major_version").integer(majorVersion);
487     m_obj.addmember("minor_version").integer(minorVersion);
488
489     if (hasAttributes(*(unfiltered.get()))) {
490         auto_ptr<SAMLResponse> filtered(filter(unfiltered.get(), application, source));
491     
492         // Calculate expiration.
493         m_responseExpiration=calculateExpiration(*(filtered.get()));
494     
495         // Serialize filtered assertions (if changes were made).
496         ostringstream os;
497         os << *(filtered.get());
498         string fstr=os.str();
499         if (fstr.length() != strlen(tokens))
500             m_obj.addmember("tokens.filtered").string(fstr.c_str());
501
502         // Save actual objects only if we're running inprocess.
503         if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
504             m_pUnfiltered=unfiltered.release();
505             if (m_obj["tokens.filtered"].isstring())
506                 m_pFiltered=filtered.release();
507         }
508     }
509     
510     m_lock = Mutex::create();
511
512     if (m_log->isDebugEnabled())
513         m_log->debug("session loaded from secondary cache (ID: %s)", key);
514 }
515
516
517 MemorySessionCacheEntry::~MemorySessionCacheEntry()
518 {
519     delete m_lock;
520 }
521
522 HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* client_addr) const
523 {
524 #ifdef _DEBUG
525     saml::NDC ndc("isValid");
526 #endif
527
528     // Obtain validation rules from application settings.
529     bool consistentIPAddress=true;
530     int lifetime=0,timeout=0;
531     const IPropertySet* props=app->getPropertySet("Sessions");
532     if (props) {
533         pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
534         if (p.first)
535             lifetime = p.second;
536         p=props->getUnsignedInt("timeout");
537         if (p.first)
538             timeout = p.second;
539         pair<bool,bool> pcheck=props->getBool("consistentIPAddress");
540         if (pcheck.first)
541             consistentIPAddress = pcheck.second;
542     }
543     
544     if (m_log->isDebugEnabled())
545         m_log->debug("checking validity of session (ID: %s)", m_obj["key"].string());
546     
547     time_t now=time(NULL);
548     if (lifetime > 0 && now > m_sessionCreated+lifetime) {
549         if (m_log->isInfoEnabled())
550             m_log->info("session expired (ID: %s)", m_obj["key"].string());
551         return SESSION_E_EXPIRED;
552     }
553
554     if (timeout > 0 && now-m_lastAccess >= timeout) {
555         m_log->info("session timed out (ID: %s)", m_obj["key"].string());
556         return SESSION_E_EXPIRED;
557     }
558
559     if (consistentIPAddress) {
560         if (m_log->isDebugEnabled())
561             m_log->debug("comparing client address %s against %s", client_addr, getClientAddress());
562         if (strcmp(client_addr, getClientAddress())) {
563             m_log->debug("client address mismatch");
564             return SESSION_E_ADDRESSMISMATCH;
565         }
566     }
567
568     m_lastAccess=now;
569     return NOERROR;
570 }
571
572 bool MemorySessionCacheEntry::hasAttributes(const SAMLResponse& r) const
573 {
574     Iterator<SAMLAssertion*> assertions=r.getAssertions();
575     while (assertions.hasNext()) {
576         Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
577         while (statements.hasNext()) {
578             if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
579                 return true;
580         }
581     }
582     return false;
583 }
584
585 time_t MemorySessionCacheEntry::calculateExpiration(const SAMLResponse& r) const
586 {
587     time_t expiration=0;
588     Iterator<SAMLAssertion*> assertions = r.getAssertions();
589     while (assertions.hasNext()) {
590         SAMLAssertion* assertion = assertions.next();
591         
592         // Only examine this assertion if it contains an attribute statement.
593         // We know at least one such statement exists, or this is a query response.
594         Iterator<SAMLStatement*> statements = assertion->getStatements();
595         while (statements.hasNext()) {
596             if (dynamic_cast<SAMLAttributeStatement*>(statements.next())) {
597                 const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
598         
599                 // If there is no time, then just continue and ignore this assertion.
600                 if (thistime) {    
601                     // If this is a tighter expiration, cache it.   
602                     if (expiration == 0 || thistime->getEpoch() < expiration)
603                         expiration = thistime->getEpoch();
604                 }
605
606                 // No need to continue with this assertion.
607                 break;
608             }
609         }
610     }
611
612     // If we didn't find any assertions with times, then use the default.
613     if (expiration == 0)
614         expiration = time(NULL) + m_cache->m_defaultLifetime;
615   
616     return expiration;
617 }
618
619 void MemorySessionCacheEntry::populate() const
620 {
621 #ifdef _DEBUG
622     saml::NDC ndc("populate");
623 #endif
624
625     // Do we have any attribute data cached?
626     if (m_responseExpiration > 0) {
627         // Can we use what we have?
628         if (time(NULL) < m_responseExpiration)
629             return;
630       
631         // If we're being strict, dump what we have and reset timestamps.
632         if (m_cache->m_strictValidity) {
633             m_log->info("strictly enforcing attribute validity, dumping expired data");
634             m_obj["tokens"].destroy();
635             delete m_pUnfiltered;
636             delete m_pFiltered;
637             m_pUnfiltered=m_pFiltered=NULL;
638             m_responseExpiration=0;
639             m_lastRetry=0;
640             if (m_cache->m_sink) {
641                 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),"")))
642                     m_log->error("cache store returned failure while clearing tokens from entry");
643             }
644         }
645     }
646
647     try {
648         pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse();
649         auto_ptr<SAMLResponse> r1(new_responses.first),r2(new_responses.second);
650         if (new_responses.first) {
651             m_obj["tokens"].destroy();
652             delete m_pUnfiltered;
653             delete m_pFiltered;
654             m_pUnfiltered=m_pFiltered=NULL;
655             m_responseExpiration=0;
656             
657             // Serialize unfiltered assertions.
658             ostringstream os;
659             os << *new_responses.first;
660             m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
661             
662             // Serialize filtered assertions (if changes were made).
663             os.str("");
664             os << *new_responses.second;
665             string fstr=os.str();
666             if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
667                 m_obj.addmember("tokens.filtered").string(fstr.c_str());
668             
669             // Update expiration.
670             m_responseExpiration=calculateExpiration(*new_responses.second);
671
672             // Save actual objects only if we're running inprocess.
673             if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
674                 m_pUnfiltered=r1.release();
675                 if (m_obj["tokens.filtered"].isstring())
676                     m_pFiltered=r2.release();
677             }
678
679             // Update backing store.
680             if (m_cache->m_sink) {
681                 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),m_obj["tokens.unfiltered"].string())))
682                     m_log->error("cache store returned failure while updating tokens in entry");
683             }
684
685             m_lastRetry=0;
686             m_log->debug("fetched and stored new response");
687             STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
688             stc.getTransactionLog().infoStream() <<  "Successful attribute query for session (ID: " << m_obj["key"].string() << ")";
689             stc.releaseTransactionLog();
690         }
691     }
692     catch (SAMLException&) {
693         if (m_cache->m_propagateErrors)
694             throw;
695         m_log->warn("suppressed SAML exception caught while trying to fetch attributes");
696     }
697 #ifndef _DEBUG
698     catch (...) {
699         if (m_cache->m_propagateErrors)
700             throw;
701         m_log->warn("suppressed unknown exception caught while trying to fetch attributes");
702     }
703 #endif
704 }
705
706 pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() const
707 {
708 #ifdef _DEBUG
709     saml::NDC ndc("getNewResponse");
710 #endif
711
712     // The retryInterval determines how often to poll an AA that might be down.
713     time_t now=time(NULL);
714     if ((now - m_lastRetry) < m_cache->m_retryInterval)
715         return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
716     if (m_lastRetry)
717         m_log->debug("retry interval exceeded, trying for attributes again");
718     m_lastRetry=now;
719
720     m_log->info("trying to get new attributes for session (ID: %s)", m_obj["key"].string());
721     
722     // Transaction Logging
723     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
724     stc.getTransactionLog().infoStream() <<
725         "Making attribute query for session (ID: " <<
726             m_obj["key"].string() <<
727         ") on (applicationId: " <<
728             m_obj["application_id"].string() <<
729         ") for principal from (IdP: " <<
730             m_obj["provider_id"].string() <<
731         ")";
732     stc.releaseTransactionLog();
733
734
735     // Caller must be holding the config lock.
736     // Lookup application for session to get providerId and attributes to request.
737     IConfig* conf=ShibTargetConfig::getConfig().getINI();
738     const IApplication* application=conf->getApplication(m_obj["application_id"].string());
739     if (!application) {
740         m_log->crit("unable to locate application for session, deleted?");
741         throw SAMLException("Unable to locate application for session, deleted?");
742     }
743     pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
744     if (!providerID.first) {
745         m_log->crit("unable to determine ProviderID for application, not set?");
746         throw SAMLException("Unable to determine ProviderID for application, not set?");
747     }
748
749     // Try this request.
750     Metadata m(application->getMetadataProviders());
751     const IEntityDescriptor* site=m.lookup(m_obj["provider_id"].string());
752     if (!site) {
753         m_log->error("unable to locate identity provider's metadata for attribute query");
754         throw MetadataException("Unable to locate identity provider's metadata for attribute query.");
755     }
756
757     // Try to locate an AA role.
758     const IAttributeAuthorityDescriptor* AA=site->getAttributeAuthorityDescriptor(
759         m_obj["minor_version"].integer()==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
760         );
761     if (!AA) {
762         m_log->warn("unable to locate metadata for identity provider's Attribute Authority");
763         return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
764     }
765
766     // Get protocol signing policy.
767     const IPropertySet* credUse=application->getCredentialUse(site);
768     pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
769     pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
770     if (!signatureAlg.first)
771         signatureAlg.second=URI_ID_RSA_SHA1;
772     pair<bool,const char*> digestAlg=credUse ? credUse->getString("digestAlg") : pair<bool,const char*>(false,NULL);
773     if (!digestAlg.first)
774         digestAlg.second=URI_ID_SHA1;
775     pair<bool,bool> signedResponse=credUse ? credUse->getBool("signedResponse") : make_pair(false,false);
776     pair<bool,const char*> signingCred=credUse ? credUse->getString("Signing") : pair<bool,const char*>(false,NULL);
777     
778     SAMLResponse* response = NULL;
779     try {
780         // Copy NameID from subject (may need to reconstitute it).
781         SAMLNameIdentifier* nameid=NULL;
782         if (m_pSubject)
783             nameid=static_cast<SAMLNameIdentifier*>(m_pSubject->getNameIdentifier()->clone());
784         else {
785             istringstream instr(m_obj["subject"].string());
786             auto_ptr<SAMLSubject> sub(new SAMLSubject(instr));
787             nameid=static_cast<SAMLNameIdentifier*>(sub->getNameIdentifier()->clone());
788         }
789
790         // Build a SAML Request....
791         SAMLAttributeQuery* q=new SAMLAttributeQuery(
792             new SAMLSubject(nameid),
793             providerID.second,
794             application->getAttributeDesignators().clone()
795             );
796         auto_ptr<SAMLRequest> req(new SAMLRequest(q));
797         req->setMinorVersion(m_obj["minor_version"].integer());
798         
799         // Sign it?
800         if (signRequest.first && signRequest.second && signingCred.first) {
801             if (req->getMinorVersion()==1) {
802                 Credentials creds(conf->getCredentialsProviders());
803                 const ICredResolver* cr=creds.lookup(signingCred.second);
804                 if (cr)
805                     req->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
806                 else
807                     m_log->error("unable to sign attribute query, specified credential (%s) was not found",signingCred.second);
808             }
809             else
810                 m_log->error("unable to sign SAML 1.0 attribute query, only SAML 1.1 defines signing adequately");
811         }
812             
813         m_log->debug("trying to query an AA...");
814
815         // Call context object
816         ShibHTTPHook::ShibHTTPHookCallContext ctx(credUse,AA);
817         Trust t(application->getTrustProviders());
818         
819         // Use metadata to locate endpoints.
820         Iterator<const IEndpoint*> endpoints=AA->getAttributeServiceManager()->getEndpoints();
821         while (!response && endpoints.hasNext()) {
822             const IEndpoint* ep=endpoints.next();
823             try {
824                 // Get a binding object for this protocol.
825                 const SAMLBinding* binding = application->getBinding(ep->getBinding());
826                 if (!binding) {
827                     auto_ptr_char prot(ep->getBinding());
828                     m_log->warn("skipping binding on unsupported protocol (%s)", prot.get());
829                     continue;
830                 }
831                 static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
832                 auto_ptr<SAMLResponse> r(binding->send(ep->getLocation(), *(req.get()), &ctx));
833                 if (r->isSigned()) {
834                     if (!t.validate(*r,AA))
835                         throw TrustException("Unable to verify signed response message.");
836                 }
837                 else if (!ctx.isAuthenticated() || XMLString::compareNString(ep->getLocation(),https,6))
838                     throw TrustException("Response message was unauthenticated.");
839                 response = r.release();
840             }
841             catch (SAMLException& e) {
842                 m_log->error("caught SAML exception during SAML attribute query: %s", e.what());
843                 // Check for shib:InvalidHandle error and propagate it out.
844                 Iterator<saml::QName> codes=e.getCodes();
845                 if (codes.size()>1) {
846                     const saml::QName& code=codes[1];
847                     if (!XMLString::compareString(code.getNamespaceURI(),shibboleth::Constants::SHIB_NS) &&
848                         !XMLString::compareString(code.getLocalName(), shibboleth::Constants::InvalidHandle)) {
849                         codes.reset();
850                         throw InvalidHandleException(e.what(),params(),codes);
851                     }
852                 }
853             }
854         }
855
856         if (response) {
857             if (signedResponse.first && signedResponse.second && !response->isSigned()) {
858                 delete response;
859                 m_log->error("unsigned response obtained, but we were told it must be signed.");
860                 throw TrustException("Unable to obtain a signed response message.");
861             }
862             
863             // Iterate over the tokens and apply basic validation.
864             time_t now=time(NULL);
865             Iterator<SAMLAssertion*> assertions=response->getAssertions();
866             for (unsigned int a=0; a<assertions.size();) {
867                 // Discard any assertions not issued by the right entity.
868                 if (XMLString::compareString(site->getId(),assertions[a]->getIssuer())) {
869                     auto_ptr_char bad(assertions[a]->getIssuer());
870                     m_log->warn("discarding assertion not issued by (%s), instead by (%s)",m_obj["provider_id"].string(),bad.get());
871                     response->removeAssertion(a);
872                     continue;
873                 }
874
875                 // Validate the token.
876                 try {
877                     application->validateToken(assertions[a],now,AA,application->getTrustProviders());
878                     a++;
879                 }
880                 catch (SAMLException&) {
881                     m_log->warn("assertion failed to validate, removing it from response");
882                     response->removeAssertion(a);
883                 }
884             }
885
886             // Run it through the filter.
887             return make_pair(response,filter(response,application,site));
888         }
889     }
890     catch (SAMLException& e) {
891         m_log->error("caught SAML exception during query to AA: %s", e.what());
892         annotateException(&e,AA);
893     }
894     
895     m_log->error("no response obtained");
896     return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
897 }
898
899 SAMLResponse* MemorySessionCacheEntry::filter(
900     const SAMLResponse* r, const IApplication* application, const IEntityDescriptor* source
901     ) const
902 {
903 #ifdef _DEBUG
904     saml::NDC ndc("filter");
905 #endif
906
907     // Make a copy of the original and process that against the AAP.
908     auto_ptr<SAMLResponse> copy(static_cast<SAMLResponse*>(r->clone()));
909     copy->toDOM();
910
911     Iterator<SAMLAssertion*> copies=copy->getAssertions();
912     for (unsigned long j=0; j < copies.size();) {
913         try {
914             // Finally, filter the content.
915             AAP::apply(application->getAAPProviders(),*(copies[j]),source);
916             j++;
917
918         }
919         catch (SAMLException&) {
920             m_log->info("no statements remain after AAP, removing assertion");
921             copy->removeAssertion(j);
922         }
923     }
924
925     // Audit the results.    
926     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
927     Category& tran=stc.getTransactionLog();
928     if (tran.isInfoEnabled()) {
929         tran.infoStream() <<
930             "Caching the following attributes after AAP applied for session (ID: " <<
931                 m_obj["key"].string() <<
932             ") on (applicationId: " <<
933                 m_obj["application_id"].string() <<
934             ") for principal from (IdP: " <<
935                 m_obj["provider_id"].string() <<
936             ") {";
937
938         Iterator<SAMLAssertion*> loggies=copy->getAssertions();
939         while (loggies.hasNext()) {
940             SAMLAssertion* logit=loggies.next();
941             Iterator<SAMLStatement*> states=logit->getStatements();
942             while (states.hasNext()) {
943                 SAMLAttributeStatement* state=dynamic_cast<SAMLAttributeStatement*>(states.next());
944                 Iterator<SAMLAttribute*> attrs=state ? state->getAttributes() : EMPTY(SAMLAttribute*);
945                 while (attrs.hasNext()) {
946                     SAMLAttribute* attr=attrs.next();
947                     auto_ptr_char attrname(attr->getName());
948                     tran.infoStream() << "\t" << attrname.get() << " (" << attr->getValues().size() << " values)";
949                 }
950             }
951         }
952         tran.info("}");
953     }
954     stc.releaseTransactionLog();
955     
956     return copy.release();
957 }
958
959 MemorySessionCache::MemorySessionCache(const DOMElement* e)
960     : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
961         m_strictValidity(true), m_propagateErrors(false), m_lock(RWLock::create()),
962         m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")),
963         restoreInsert(NULL), restoreFind(NULL), restoreRemove(NULL), m_sink(NULL)
964 {
965     const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
966     if (tag && *tag) {
967         m_AATimeout = XMLString::parseInt(tag);
968         if (!m_AATimeout)
969             m_AATimeout=30;
970     }
971     SAMLConfig::getConfig().timeout = m_AATimeout;
972
973     tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
974     if (tag && *tag) {
975         m_AAConnectTimeout = XMLString::parseInt(tag);
976         if (!m_AAConnectTimeout)
977             m_AAConnectTimeout=15;
978     }
979     SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
980     
981     tag=m_root->getAttributeNS(NULL,defaultLifetime);
982     if (tag && *tag) {
983         m_defaultLifetime = XMLString::parseInt(tag);
984         if (!m_defaultLifetime)
985             m_defaultLifetime=1800;
986     }
987
988     tag=m_root->getAttributeNS(NULL,retryInterval);
989     if (tag && *tag) {
990         m_retryInterval = XMLString::parseInt(tag);
991         if (!m_retryInterval)
992             m_retryInterval=300;
993     }
994     
995     tag=m_root->getAttributeNS(NULL,strictValidity);
996     if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
997         m_strictValidity=false;
998         
999     tag=m_root->getAttributeNS(NULL,propagateErrors);
1000     if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
1001         m_propagateErrors=true;
1002
1003     // Register for remoted messages.
1004     IListener* listener=ShibTargetConfig::getConfig().getINI()->getListener();
1005     if (listener && ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess)) {
1006         restoreInsert=listener->regListener("SessionCache::insert",this);
1007         restoreFind=listener->regListener("SessionCache::find",this);
1008         restoreRemove=listener->regListener("SessionCache::remove",this);
1009     }
1010     else
1011         m_log->info("no listener interface available, cache remoting is disabled");
1012
1013     shutdown_wait = CondWait::create();
1014     shutdown = false;
1015     cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
1016 }
1017
1018 MemorySessionCache::~MemorySessionCache()
1019 {
1020     // Shut down the cleanup thread and let it know...
1021     shutdown = true;
1022     shutdown_wait->signal();
1023     cleanup_thread->join(NULL);
1024
1025     // Unregister remoted messages.
1026     IListener* listener=ShibTargetConfig::getConfig().getINI()->getListener();
1027     if (listener && ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess)) {
1028         listener->unregListener("SessionCache::insert",this,restoreInsert);
1029         listener->unregListener("SessionCache::find",this,restoreFind);
1030         listener->unregListener("SessionCache::remove",this,restoreRemove);
1031     }
1032
1033     for_each(m_hashtable.begin(),m_hashtable.end(),shibtarget::cleanup<string,MemorySessionCacheEntry>);
1034     delete m_lock;
1035     delete shutdown_wait;
1036 }
1037
1038 bool MemorySessionCache::setBackingStore(ISessionCacheStore* store)
1039 {
1040     if (m_sink && store!=m_sink)
1041         return false;
1042     m_sink=store;
1043     return true;
1044 }
1045
1046 /*
1047  * IPC message definitions:
1048  * 
1049  *  SessionCache::insert
1050  * 
1051  *      IN
1052  *      application_id
1053  *      client_address
1054  *      provider_id
1055  *      major_version
1056  *      minor_version
1057  *      authn_context
1058  *      subject
1059  *      tokens.unfiltered
1060  * 
1061  *      OUT
1062  *      key
1063  * 
1064  *  SessionCache::find
1065  * 
1066  *      IN
1067  *      key
1068  *      application_id
1069  *      client_address
1070  * 
1071  *      OUT
1072  *      client_address
1073  *      provider_id
1074  *      major_version
1075  *      minor_version
1076  *      authn_context
1077  *      subject
1078  *      tokens.unfiltered
1079  *      tokens.filtered
1080  * 
1081  *  SessionCache::remove
1082  * 
1083  *      IN
1084  *      key
1085  *      application_id
1086  *      client_address
1087  */
1088
1089 DDF MemorySessionCache::receive(const DDF& in)
1090 {
1091 #ifdef _DEBUG
1092     saml::NDC ndc("receive");
1093 #endif
1094
1095     // Find application.
1096     const char* aid=in["application_id"].string();
1097     const IApplication* app=aid ? ShibTargetConfig::getConfig().getINI()->getApplication(aid) : NULL;
1098     if (!app) {
1099         // Something's horribly wrong.
1100         m_log->error("couldn't find application (%s) for session", aid ? aid : "(missing)");
1101         throw SAMLException("Unable to locate application for session, deleted?");
1102     }
1103
1104     if (!strcmp(in.name(),"SessionCache::find")) {
1105         // Check required parameters.
1106         const char* key=in["key"].string();
1107         const char* client_address=in["client_address"].string();
1108         if (!key || !client_address)
1109             throw SAMLException("Required parameters missing in call to SessionCache::find");
1110         
1111         try {        
1112             // Lookup the session and cast down to the internal type.
1113             MemorySessionCacheEntry* entry=dynamic_cast<MemorySessionCacheEntry*>(find(key,app,client_address));
1114             if (!entry)
1115                 return DDF();
1116             DDF dup=entry->getDDF().copy();
1117             entry->unlock();
1118             return dup;
1119         }
1120         catch (SAMLException&) {
1121             remove(key,app,client_address);
1122             throw;
1123         }
1124     }
1125     else if (!strcmp(in.name(),"SessionCache::remove")) {
1126         // Check required parameters.
1127         const char* key=in["key"].string();
1128         const char* client_address=in["client_address"].string();
1129         if (!key || !client_address)
1130             throw SAMLException("Required parameters missing in call to SessionCache::remove");
1131         
1132         remove(key,app,client_address);
1133         return DDF();
1134     }
1135     else if (!strcmp(in.name(),"SessionCache::insert")) {
1136         // Check required parameters.
1137         const char* client_address=in["client_address"].string();
1138         const char* provider_id=in["provider_id"].string();
1139         const char* authn_context=in["authn_context"].string();
1140         const char* subject=in["subject"].string();
1141         const char* tokens=in["tokens.unfiltered"].string();
1142         if (!client_address || !provider_id || !authn_context || !subject || !tokens)
1143             throw SAMLException("Required parameters missing in call to SessionCache::insert");
1144         int minor=in["minor_version"].integer();
1145         
1146         // Locate role descriptor to use in filtering.
1147         Metadata m(app->getMetadataProviders());
1148         const IEntityDescriptor* site=m.lookup(provider_id);
1149         if (!site) {
1150             m_log->error("unable to locate issuing identity provider's metadata");
1151             throw MetadataException("Unable to locate identity provider's metadata.");
1152         }
1153         // Deserialize XML for insert method.
1154         istringstream subis(subject);
1155         auto_ptr<SAMLSubject> pSubject(new SAMLSubject(subis));
1156         istringstream tokis(tokens);
1157         auto_ptr<SAMLResponse> pTokens=new SAMLResponse(tokis,minor);
1158         
1159         // Insert the data and return the cache key.
1160         string key=insert(app,site,client_address,pSubject.get(),authn_context,pTokens.get());
1161         
1162         DDF out(NULL);
1163         out.structure();
1164         out.addmember("key").string(key.c_str());
1165         return out;
1166     }
1167     throw ListenerException("Unsupported operation ($1)",params(1,in.name()));
1168 }
1169
1170 string MemorySessionCache::insert(
1171     const IApplication* application,
1172     const IEntityDescriptor* source,
1173     const char* client_addr,
1174     const SAMLSubject* subject,
1175     const char* authnContext,
1176     const SAMLResponse* tokens
1177     )
1178 {
1179 #ifdef _DEBUG
1180     saml::NDC ndc("insert");
1181 #endif
1182
1183     SAMLIdentifier id;
1184     auto_ptr_char key(id);
1185
1186     if (m_log->isDebugEnabled())
1187         m_log->debug("creating new cache entry for application %s: \"%s\"", application->getId(), key.get());
1188
1189     auto_ptr<MemorySessionCacheEntry> entry(
1190         new MemorySessionCacheEntry(
1191             this,
1192             key.get(),
1193             application,
1194             source,
1195             client_addr,
1196             subject,
1197             authnContext,
1198             tokens
1199             )
1200         );
1201     entry->populate();
1202
1203     if (m_sink) {
1204         HRESULT hr=m_sink->onCreate(key.get(),application,entry.get(),1,tokens->getMinorVersion(),entry->created());
1205         if (FAILED(hr)) {
1206             m_log->error("cache store returned failure while storing new entry");
1207             throw SAMLException(hr,"Unable to record new session in cache store.");
1208         }
1209     }
1210
1211     m_lock->wrlock();
1212     m_hashtable[key.get()]=entry.release();
1213     m_lock->unlock();
1214
1215     return key.get();
1216 }
1217
1218 ISessionCacheEntry* MemorySessionCache::find(const char* key, const IApplication* application, const char* client_addr)
1219 {
1220 #ifdef _DEBUG
1221     saml::NDC ndc("find");
1222 #endif
1223
1224     m_log->debug("searching memory cache for key (%s)", key);
1225     m_lock->rdlock();
1226
1227     map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1228     if (i==m_hashtable.end()) {
1229         m_lock->unlock();
1230         m_log->debug("no match found");
1231         if (!m_sink)
1232             return NULL;    // no backing store to search
1233
1234         m_log->debug("searching backing store");
1235         string appid,addr,pid,sub,ac,tokens;
1236         int major,minor;
1237         time_t created,accessed;
1238         HRESULT hr=m_sink->onRead(key,appid,addr,pid,sub,ac,tokens,major,minor,created,accessed);
1239         if (hr==S_FALSE)
1240             return NULL;
1241         else if (FAILED(hr)) {
1242             m_log->error("cache store returned failure during search");
1243             return NULL;
1244         }
1245         const IApplication* eapp=ShibTargetConfig::getConfig().getINI()->getApplication(appid.c_str());
1246         if (!eapp) {
1247             // Something's horribly wrong.
1248             m_log->error("couldn't find application (%s) for session", appid.c_str());
1249             if (FAILED(m_sink->onDelete(key)))
1250                 m_log->error("cache store returned failure during delete");
1251             return NULL;
1252         }
1253         if (m_log->isDebugEnabled())
1254             m_log->debug("loading cache entry (ID: %s) back into memory for application (%s)", key, appid.c_str());
1255
1256         // Locate role descriptor to use in filtering.
1257         Metadata m(eapp->getMetadataProviders());
1258         const IEntityDescriptor* site=m.lookup(pid.c_str());
1259         if (!site) {
1260             m_log->error("unable to locate issuing identity provider's metadata");
1261             if (FAILED(m_sink->onDelete(key)))
1262                 m_log->error("cache store returned failure during delete");
1263             return NULL;
1264         }
1265         MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
1266             this,
1267             key,
1268             eapp,
1269             site,
1270             addr.c_str(),
1271             sub.c_str(),
1272             ac.c_str(),
1273             tokens.c_str(),
1274             major,
1275             minor,
1276             created,
1277             accessed
1278             );
1279         m_lock->wrlock();
1280         m_hashtable[key]=entry;
1281         m_lock->unlock();
1282
1283         // Downgrade to a read lock and repeat the initial search.
1284         m_lock->rdlock();
1285         i=m_hashtable.find(key);
1286         if (i==m_hashtable.end()) {
1287             m_lock->unlock();
1288             m_log->warn("cache entry was loaded from backing store, but disappeared after lock downgrade");
1289             return NULL;
1290         }
1291     }
1292     else
1293         m_log->debug("match found");
1294
1295     // Check for application mismatch (could also do this with partitioned caches by application ID)
1296     if (!i->second->checkApplication(application)) {
1297         m_lock->unlock();
1298         m_log->crit("An application (%s) attempted to access another application's session!", application->getId());
1299         return NULL;
1300     }
1301     
1302     // Check for timeouts, expiration, address mismatch, etc (also updates last access)
1303     // Use the return code to assign specific error messages.
1304     try {
1305         HRESULT hr=i->second->isValid(application, client_addr);
1306         if (FAILED(hr)) {
1307             Metadata m(application->getMetadataProviders());
1308             switch (hr) {
1309                 case SESSION_E_EXPIRED: {
1310                     InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
1311                     annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1312                 }
1313                 
1314                 case SESSION_E_ADDRESSMISMATCH: {
1315                     InvalidSessionException ex(
1316                         SESSION_E_ADDRESSMISMATCH,
1317                         "Your IP address (%1) does not match the address recorded at the time the session was established.",
1318                         params(1,client_addr)
1319                         );
1320                     annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1321                 }
1322                 
1323                 default: {
1324                     InvalidSessionException ex(hr, "Your session is invalid.");
1325                     annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1326                 }
1327             }
1328         }
1329     }
1330     catch (...) {
1331         m_lock->unlock();
1332         throw;
1333     }
1334
1335     // Lock the cache entry for the caller -- they have to unlock it.
1336     i->second->lock();
1337     m_lock->unlock();
1338     return i->second;
1339 }
1340
1341 void MemorySessionCache::remove(const char* key, const IApplication* application, const char* client_addr)
1342 {
1343 #ifdef _DEBUG
1344     saml::NDC ndc("remove");
1345 #endif
1346
1347     m_log->debug("removing cache entry with key (%s)", key);
1348
1349     // lock the cache for writing, which means we know nobody is sitting in find()
1350     m_lock->wrlock();
1351
1352     // grab the entry from the database.
1353     map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1354     if (i==m_hashtable.end()) {
1355         m_lock->unlock();
1356         return;
1357     }
1358
1359     // ok, remove the entry and lock it
1360     MemorySessionCacheEntry* entry=i->second;
1361     m_hashtable.erase(key);
1362     entry->lock();
1363     
1364     // unlock the cache
1365     m_lock->unlock();
1366
1367     entry->unlock();
1368
1369     // Notify sink. Smart ptr will make sure entry gets deleted.
1370     auto_ptr<ISessionCacheEntry> entrywrap(entry);
1371     if (m_sink) {
1372         if (FAILED(m_sink->onDelete(key)))
1373             m_log->error("cache store failed to delete entry");
1374     }
1375
1376     // Transaction Logging
1377     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
1378     stc.getTransactionLog().infoStream() << "Destroyed session (ID: " << key << ")";
1379     stc.releaseTransactionLog();
1380 }
1381
1382 void MemorySessionCache::dormant(const char* key)
1383 {
1384 #ifdef _DEBUG
1385     saml::NDC ndc("dormant");
1386 #endif
1387
1388     m_log->debug("purging old cache entry with key (%s)", key);
1389
1390     // lock the cache for writing, which means we know nobody is sitting in find()
1391     m_lock->wrlock();
1392
1393     // grab the entry from the database.
1394     map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1395     if (i==m_hashtable.end()) {
1396         m_lock->unlock();
1397         return;
1398     }
1399
1400     // ok, remove the entry and lock it
1401     MemorySessionCacheEntry* entry=i->second;
1402     m_hashtable.erase(key);
1403     entry->lock();
1404     
1405     // unlock the cache
1406     m_lock->unlock();
1407
1408     // we can release the cache entry lock because we know we're not in the cache anymore
1409     entry->unlock();
1410
1411     // Update sink with last access data. Wrapper will make sure entry gets deleted.
1412     auto_ptr<ISessionCacheEntry> entrywrap(entry);
1413     if (m_sink) {
1414         if (FAILED(m_sink->onUpdate(key,NULL,entry->lastAccess())))
1415             m_log->error("cache store failed to update last access timestamp");
1416     }
1417 }
1418
1419 void MemorySessionCache::cleanup()
1420 {
1421 #ifdef _DEBUG
1422     saml::NDC ndc("cleanup()");
1423 #endif
1424
1425     int rerun_timer = 0;
1426     int timeout_life = 0;
1427     Mutex* mutex = Mutex::create();
1428   
1429     // Load our configuration details...
1430     const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
1431     if (tag && *tag)
1432         rerun_timer = XMLString::parseInt(tag);
1433
1434     tag=m_root->getAttributeNS(NULL,cacheTimeout);
1435     if (tag && *tag)
1436         timeout_life = XMLString::parseInt(tag);
1437   
1438     if (rerun_timer <= 0)
1439         rerun_timer = 300;        // rerun every 5 minutes
1440
1441     if (timeout_life <= 0)
1442         timeout_life = 28800; // timeout after 8 hours
1443
1444     mutex->lock();
1445
1446     m_log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
1447
1448     while (!shutdown) {
1449         shutdown_wait->timedwait(mutex,rerun_timer);
1450         if (shutdown)
1451             break;
1452
1453         // Ok, let's run through the cleanup process and clean out
1454         // really old sessions.  This is a two-pass process.  The
1455         // first pass is done holding a read-lock while we iterate over
1456         // the cache.  The second pass doesn't need a lock because
1457         // the 'deletes' will lock the cache.
1458     
1459         // Pass 1: iterate over the map and find all entries that have not been
1460         // used in X hours
1461         vector<string> stale_keys;
1462         time_t stale = time(NULL) - timeout_life;
1463     
1464         m_lock->rdlock();
1465         for (map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
1466         {
1467             // If the last access was BEFORE the stale timeout...
1468             i->second->lock();
1469             time_t last=i->second->lastAccess();
1470             i->second->unlock();
1471             if (last < stale)
1472                 stale_keys.push_back(i->first);
1473         }
1474         m_lock->unlock();
1475     
1476         m_log->info("purging %d old sessions", stale_keys.size());
1477     
1478         // Pass 2: walk through the list of stale entries and remove them from the cache
1479         for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); j++)
1480             dormant(j->c_str());
1481     }
1482
1483     m_log->info("cleanup thread finished.");
1484
1485     mutex->unlock();
1486     delete mutex;
1487     Thread::exit(NULL);
1488 }
1489
1490 void* MemorySessionCache::cleanup_fcn(void* cache_p)
1491 {
1492     MemorySessionCache* cache = reinterpret_cast<MemorySessionCache*>(cache_p);
1493
1494     // First, let's block all signals 
1495     Thread::mask_all_signals();
1496
1497     // Now run the cleanup process.
1498     cache->cleanup();
1499     return NULL;
1500 }
1501
1502 IPlugIn* MemoryCacheFactory(const DOMElement* e)
1503 {
1504     // If this is a long-lived process, we return the "real" cache.
1505     if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess))
1506         return new MemorySessionCache(e);
1507     // Otherwise, we return a stubbed front-end that remotes calls to the real cache.
1508     return new StubCache(e);
1509 }