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