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