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