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