Move assertions to separate storage records, improve error handling.
[shibboleth/sp.git] / shibsp / impl / RemotedSessionCache.cpp
1 /*\r
2  *  Copyright 2001-2007 Internet2\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * RemotedSessionCache.cpp\r
19  * \r
20  * SessionCache implementation that delegates to a remoted version.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "Application.h"\r
25 #include "exceptions.h"\r
26 #include "ServiceProvider.h"\r
27 #include "SessionCache.h"\r
28 #include "TransactionLog.h"\r
29 #include "attribute/Attribute.h"\r
30 #include "remoting/ListenerService.h"\r
31 #include "util/SPConstants.h"\r
32 \r
33 #include <sstream>\r
34 #include <log4cpp/Category.hh>\r
35 #include <xmltooling/XMLToolingConfig.h>\r
36 #include <xmltooling/util/NDC.h>\r
37 #include <xmltooling/util/XMLHelper.h>\r
38 \r
39 using namespace shibsp;\r
40 using namespace opensaml::saml2md;\r
41 using namespace opensaml;\r
42 using namespace xmltooling;\r
43 using namespace log4cpp;\r
44 using namespace std;\r
45 \r
46 namespace shibsp {\r
47 \r
48     class RemotedCache;\r
49     class RemotedSession : public virtual Session\r
50     {\r
51     public:\r
52         RemotedSession(RemotedCache* cache, DDF& obj) : m_version(obj["version"].integer()), m_obj(obj),\r
53                 m_nameid(NULL), m_expires(0), m_lastAccess(time(NULL)), m_cache(cache), m_lock(NULL) {\r
54             const char* nameid = obj["nameid"].string();\r
55             if (!nameid)\r
56                 throw FatalProfileException("NameID missing from remotely cached session.");\r
57             \r
58             // Parse and bind the NameID into an XMLObject.\r
59             istringstream instr(nameid);\r
60             DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
61             XercesJanitor<DOMDocument> janitor(doc);\r
62             auto_ptr<saml2::NameID> n(saml2::NameIDBuilder::buildNameID());\r
63             n->unmarshall(doc->getDocumentElement(), true);\r
64             janitor.release();\r
65             \r
66             try {\r
67                 DDF attrs = m_obj["attributes"];\r
68                 unmarshallAttributes(attrs);\r
69             }\r
70             catch (...) {\r
71                 for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
72                 throw;\r
73             }\r
74 \r
75             auto_ptr_XMLCh exp(m_obj["expires"].string());\r
76             if (exp.get()) {\r
77                 DateTime iso(exp.get());\r
78                 iso.parseDateTime();\r
79                 m_expires = iso.getEpoch();\r
80             }\r
81 \r
82             m_nameid = n.release();\r
83             m_lock = Mutex::create();\r
84         }\r
85         \r
86         ~RemotedSession() {\r
87             delete m_lock;\r
88             m_obj.destroy();\r
89             delete m_nameid;\r
90             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
91             for_each(m_tokens.begin(), m_tokens.end(), xmltooling::cleanup_pair<string,RootObject>());\r
92         }\r
93         \r
94         Lockable* lock() {\r
95             m_lock->lock();\r
96             return this;\r
97         }\r
98         void unlock() {\r
99             m_lock->unlock();\r
100         }\r
101 \r
102         const char* getApplicationID() const {\r
103             return m_obj["application_id"].string();\r
104         }\r
105         const char* getClientAddress() const {\r
106             return m_obj["client_addr"].string();\r
107         }\r
108         const char* getEntityID() const {\r
109             return m_obj["entity_id"].string();\r
110         }\r
111         const char* getAuthnInstant() const {\r
112             return m_obj["authn_instant"].string();\r
113         }\r
114         const opensaml::saml2::NameID& getNameID() const {\r
115             return *m_nameid;\r
116         }\r
117         const char* getSessionIndex() const {\r
118             return m_obj["session_index"].string();\r
119         }\r
120         const char* getAuthnContextClassRef() const {\r
121             return m_obj["authncontext_class"].string();\r
122         }\r
123         const char* getAuthnContextDeclRef() const {\r
124             return m_obj["authncontext_decl"].string();\r
125         }\r
126         const vector<const Attribute*>& getAttributes() const {\r
127             return m_attributes;\r
128         }\r
129         const vector<const char*>& getAssertionIDs() const {\r
130             if (m_ids.empty()) {\r
131                 DDF id = m_obj["assertions"].first();\r
132                 while (id.isstring()) {\r
133                     m_ids.push_back(id.string());\r
134                     id = id.next();\r
135                 }\r
136             }\r
137             return m_ids;\r
138         }\r
139         \r
140         const RootObject* getAssertion(const char* id) const;\r
141 \r
142         void addAttributes(const vector<Attribute*>& attributes) {\r
143             throw ConfigurationException("addAttributes method not implemented by this session cache plugin.");\r
144         }\r
145         void addAssertion(RootObject* assertion) {\r
146             throw ConfigurationException("addAssertion method not implemented by this session cache plugin.");\r
147         }\r
148 \r
149         time_t expires() const { return m_expires; }\r
150         time_t lastAccess() const { return m_lastAccess; }\r
151         void validate(const Application& application, const char* client_addr, time_t timeout, bool local=true);\r
152 \r
153     private:\r
154         void unmarshallAttributes(DDF& in);\r
155 \r
156         int m_version;\r
157         mutable DDF m_obj;\r
158         saml2::NameID* m_nameid;\r
159         vector<const Attribute*> m_attributes;\r
160         mutable vector<const char*> m_ids;\r
161         mutable map<string,RootObject*> m_tokens;\r
162         time_t m_expires,m_lastAccess;\r
163         RemotedCache* m_cache;\r
164         Mutex* m_lock;\r
165     };\r
166     \r
167     class RemotedCache : public SessionCache\r
168     {\r
169     public:\r
170         RemotedCache(const DOMElement* e);\r
171         ~RemotedCache();\r
172     \r
173         string insert(\r
174             time_t expires,\r
175             const Application& application,\r
176             const char* client_addr,\r
177             const saml2md::EntityDescriptor* issuer,\r
178             const saml2::NameID& nameid,\r
179             const char* authn_instant=NULL,\r
180             const char* session_index=NULL,\r
181             const char* authncontext_class=NULL,\r
182             const char* authncontext_decl=NULL,\r
183             const RootObject* ssoToken=NULL,\r
184             const vector<Attribute*>* attributes=NULL\r
185             );\r
186         Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t timeout=0);\r
187         void remove(const char* key, const Application& application, const char* client_addr);\r
188         \r
189         void cleanup();\r
190     \r
191         Category& m_log;\r
192     private:\r
193         const DOMElement* m_root;         // Only valid during initialization\r
194         RWLock* m_lock;\r
195         map<string,RemotedSession*> m_hashtable;\r
196     \r
197         void dormant(const char* key);\r
198         static void* cleanup_fn(void*);\r
199         bool shutdown;\r
200         CondWait* shutdown_wait;\r
201         Thread* cleanup_thread;\r
202     };\r
203 \r
204     SessionCache* SHIBSP_DLLLOCAL RemotedCacheFactory(const DOMElement* const & e)\r
205     {\r
206         return new RemotedCache(e);\r
207     }\r
208 }\r
209 \r
210 void RemotedSession::unmarshallAttributes(DDF& in)\r
211 {\r
212     DDF attr = in.first();\r
213     while (!attr.isnull()) {\r
214         try {\r
215             m_attributes.push_back(Attribute::unmarshall(attr));\r
216             if (m_cache->m_log.isDebugEnabled())\r
217                 m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",\r
218                     attr.first().name(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");\r
219         }\r
220         catch (AttributeException& ex) {\r
221             const char* id = attr.first().name();\r
222             m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());\r
223         }\r
224         attr = attr.next();\r
225     }\r
226 }\r
227 \r
228 const RootObject* RemotedSession::getAssertion(const char* id) const\r
229 {\r
230     map<string,RootObject*>::const_iterator i = m_tokens.find(id);\r
231     if (i!=m_tokens.end())\r
232         return i->second;\r
233 \r
234     // Fetch from remoted cache.\r
235     DDF in("getAssertion::"REMOTED_SESSION_CACHE);\r
236     DDFJanitor jin(in);\r
237     in.structure();\r
238     in.addmember("key").string(m_obj.name());\r
239     in.addmember("id").string(id);\r
240 \r
241     DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
242     DDFJanitor jout(out);\r
243     \r
244     // Parse and bind the document into an XMLObject.\r
245     istringstream instr(out.string());\r
246     DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
247     XercesJanitor<DOMDocument> janitor(doc);\r
248     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));\r
249     janitor.release();\r
250     \r
251     RootObject* token = dynamic_cast<RootObject*>(xmlObject.get());\r
252     if (!token || !token->isAssertion())\r
253         throw FatalProfileException("Cached assertion was of an unknown object type.");\r
254 \r
255     // Transfer ownership to us.\r
256     xmlObject.release();\r
257     m_tokens[id]=token;\r
258     return token;\r
259 }\r
260 \r
261 void RemotedSession::validate(const Application& application, const char* client_addr, time_t timeout, bool local)\r
262 {\r
263     // Basic expiration?\r
264     time_t now = time(NULL);\r
265     if (now > m_expires) {\r
266         m_cache->m_log.info("session expired (ID: %s)", m_obj.name());\r
267         RetryableProfileException ex("Your session has expired, and you must re-authenticate.");\r
268         if (!getEntityID())\r
269             throw ex;\r
270         MetadataProvider* m=application.getMetadataProvider();\r
271         Locker locker(m);\r
272         annotateException(&ex,m->getEntityDescriptor(getEntityID(),false)); // throws it\r
273     }\r
274 \r
275     // Address check?\r
276     if (client_addr) {\r
277         if (m_cache->m_log.isDebugEnabled())\r
278             m_cache->m_log.debug("comparing client address %s against %s", client_addr, getClientAddress());\r
279         if (strcmp(getClientAddress(),client_addr)) {\r
280             m_cache->m_log.warn("client address mismatch");\r
281             RetryableProfileException ex(\r
282                 "Your IP address ($1) does not match the address recorded at the time the session was established.",\r
283                 params(1,client_addr)\r
284                 );\r
285             if (!getEntityID())\r
286                 throw ex;\r
287             MetadataProvider* m=application.getMetadataProvider();\r
288             Locker locker(m);\r
289             annotateException(&ex,m->getEntityDescriptor(getEntityID(),false)); // throws it\r
290         }\r
291     }\r
292 \r
293     if (local)\r
294         return;\r
295     \r
296     DDF in("touch::"REMOTED_SESSION_CACHE), out;\r
297     DDFJanitor jin(in);\r
298     in.structure();\r
299     in.addmember("key").string(m_obj.name());\r
300     in.addmember("version").integer(m_obj["version"].integer());\r
301     if (timeout) {\r
302         // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  \r
303 #ifndef HAVE_GMTIME_R\r
304         struct tm* ptime=gmtime(&timeout);\r
305 #else\r
306         struct tm res;\r
307         struct tm* ptime=gmtime_r(&timeout,&res);\r
308 #endif\r
309         char timebuf[32];\r
310         strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
311         in.addmember("timeout").string(timebuf);\r
312     }\r
313 \r
314     try {\r
315         out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
316     }\r
317     catch (...) {\r
318         out.destroy();\r
319         throw;\r
320     }\r
321 \r
322     if (out.isstruct()) {\r
323         // We got an updated record back.\r
324         m_ids.clear();\r
325         for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
326         m_attributes.clear();\r
327         m_obj.destroy();\r
328         m_obj = out;\r
329         DDF attrs = m_obj["attributes"];\r
330         unmarshallAttributes(attrs);\r
331     }\r
332 \r
333     m_lastAccess = now;\r
334 }\r
335 \r
336 RemotedCache::RemotedCache(const DOMElement* e)\r
337     : SessionCache(e), m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), m_root(e), m_lock(NULL), shutdown(false)\r
338 {\r
339     if (!SPConfig::getConfig().getServiceProvider()->getListenerService())\r
340         throw ConfigurationException("RemotedCacheService requires a ListenerService, but none available.");\r
341         \r
342     m_lock = RWLock::create();\r
343     shutdown_wait = CondWait::create();\r
344     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
345 }\r
346 \r
347 RemotedCache::~RemotedCache()\r
348 {\r
349     // Shut down the cleanup thread and let it know...\r
350     shutdown = true;\r
351     shutdown_wait->signal();\r
352     cleanup_thread->join(NULL);\r
353 \r
354     for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,RemotedSession>());\r
355     delete m_lock;\r
356     delete shutdown_wait;\r
357 }\r
358 \r
359 string RemotedCache::insert(\r
360     time_t expires,\r
361     const Application& application,\r
362     const char* client_addr,\r
363     const saml2md::EntityDescriptor* issuer,\r
364     const saml2::NameID& nameid,\r
365     const char* authn_instant,\r
366     const char* session_index,\r
367     const char* authncontext_class,\r
368     const char* authncontext_decl,\r
369     const RootObject* ssoToken,\r
370     const vector<Attribute*>* attributes\r
371     )\r
372 {\r
373     DDF in("insert::"REMOTED_SESSION_CACHE);\r
374     DDFJanitor jin(in);\r
375     in.structure();\r
376     if (expires) {\r
377 #ifndef HAVE_GMTIME_R\r
378         struct tm* ptime=gmtime(&expires);\r
379 #else\r
380         struct tm res;\r
381         struct tm* ptime=gmtime_r(&expires,&res);\r
382 #endif\r
383         char timebuf[32];\r
384         strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
385         in.addmember("expires").string(timebuf);\r
386     }\r
387     in.addmember("application_id").string(application.getId());\r
388     in.addmember("client_addr").string(client_addr);\r
389     if (issuer) {\r
390         auto_ptr_char provid(issuer->getEntityID());\r
391         in.addmember("entity_id").string(provid.get());\r
392     }\r
393     if (authn_instant)\r
394         in.addmember("authn_instant").string(authn_instant);\r
395     if (session_index)\r
396         in.addmember("session_index").string(session_index);\r
397     if (authncontext_class)\r
398         in.addmember("authncontext_class").string(authncontext_class);\r
399     if (authncontext_decl)\r
400         in.addmember("authncontext_decl").string(authncontext_decl);\r
401     \r
402     ostringstream namestr;\r
403     namestr << nameid;\r
404     in.addmember("nameid").string(namestr.str().c_str());\r
405 \r
406     if (ssoToken) {\r
407         ostringstream tstr;\r
408         tstr << *ssoToken;\r
409         auto_ptr_char tokenid(ssoToken->getID());\r
410         DDF tokid = DDF(NULL).string(tokenid.get());\r
411         in.addmember("assertions").list().add(tokid);\r
412         in.addmember("token").string(tstr.str().c_str());\r
413     }\r
414     \r
415     if (attributes) {\r
416         DDF attr;\r
417         DDF attrs = in.addmember("attributes").list();\r
418         for (vector<Attribute*>::const_iterator a=attributes->begin(); a!=attributes->end(); ++a) {\r
419             attr = (*a)->marshall();\r
420             attrs.add(attr);\r
421         }\r
422     }\r
423 \r
424     DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
425     DDFJanitor jout(out);\r
426     if (out["key"].isstring()) {\r
427         for_each(attributes->begin(), attributes->end(), xmltooling::cleanup<Attribute>());\r
428 \r
429         // Transaction Logging\r
430         auto_ptr_char name(nameid.getName());\r
431         const char* pid = in["entity_id"].string();\r
432         TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
433         Locker locker(xlog);\r
434         xlog->log.infoStream() <<\r
435             "New session (ID: " <<\r
436                 out["key"].string() <<\r
437             ") with (applicationId: " <<\r
438                 application.getId() <<\r
439             ") for principal from (IdP: " <<\r
440                 (pid ? pid : "none") <<\r
441             ") at (ClientAddress: " <<\r
442                 client_addr <<\r
443             ") with (NameIdentifier: " <<\r
444                 name.get() <<\r
445             ")";\r
446 \r
447         return out["key"].string();\r
448     }\r
449     throw RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");\r
450 }\r
451 \r
452 Session* RemotedCache::find(const char* key, const Application& application, const char* client_addr, time_t timeout)\r
453 {\r
454 #ifdef _DEBUG\r
455     xmltooling::NDC ndc("find");\r
456 #endif\r
457 \r
458     bool localValidation = false;\r
459     RemotedSession* session=NULL;\r
460     m_log.debug("searching local cache for session (%s)", key);\r
461     m_lock->rdlock();\r
462     map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
463     if (i==m_hashtable.end()) {\r
464         m_lock->unlock();\r
465         m_log.debug("session not found locally, searching remote cache");\r
466 \r
467         DDF in("find::"REMOTED_SESSION_CACHE), out;\r
468         DDFJanitor jin(in);\r
469         in.structure();\r
470         in.addmember("key").string(key);\r
471         if (timeout) {\r
472             // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  \r
473 #ifndef HAVE_GMTIME_R\r
474             struct tm* ptime=gmtime(&timeout);\r
475 #else\r
476             struct tm res;\r
477             struct tm* ptime=gmtime_r(&timeout,&res);\r
478 #endif\r
479             char timebuf[32];\r
480             strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
481             in.addmember("timeout").string(timebuf);\r
482         }\r
483         \r
484         try {\r
485             out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
486             if (!out.isstruct()) {\r
487                 out.destroy();\r
488                 m_log.debug("session not found in remote cache");\r
489                 return NULL;\r
490             }\r
491             \r
492             // Wrap the results in a local entry and save it.\r
493             session = new RemotedSession(this, out);\r
494             // The remote end has handled timeout issues, we handle address and expiration checks.\r
495             localValidation = true;\r
496         }\r
497         catch (...) {\r
498             out.destroy();\r
499             throw;\r
500         }\r
501 \r
502         // Lock for writing and repeat the search to avoid duplication.\r
503         m_lock->wrlock();\r
504         SharedLock shared(m_lock, false);\r
505         if (m_hashtable.count(key)) {\r
506             delete session;\r
507             // We're using an existing session entry, so we have to switch back to full validation.\r
508             localValidation = false;\r
509             session = m_hashtable[key];\r
510             session->lock();\r
511         }\r
512         else {\r
513             m_hashtable[key]=session;\r
514             session->lock();\r
515         }\r
516     }\r
517     else {\r
518         // Save off and lock the session.\r
519         session = i->second;\r
520         session->lock();\r
521         m_lock->unlock();\r
522         \r
523         m_log.debug("session found locally, validating it for use");\r
524     }\r
525 \r
526     if (!XMLString::equals(session->getApplicationID(), application.getId())) {\r
527         m_log.error("an application (%s) tried to access another application's session", application.getId());\r
528         session->unlock();\r
529         return NULL;\r
530     }\r
531 \r
532     // Verify currency and update the timestamp.\r
533     // If the local switch is false, we also update the access time.\r
534     try {\r
535         session->validate(application, client_addr, timeout, localValidation);\r
536     }\r
537     catch (...) {\r
538         session->unlock();\r
539         remove(key, application, client_addr);\r
540         throw;\r
541     }\r
542     \r
543     return session;\r
544 }\r
545 \r
546 void RemotedCache::remove(const char* key, const Application& application, const char* client_addr)\r
547 {\r
548     // Take care of local copy.\r
549     dormant(key);\r
550     \r
551     // Now remote...\r
552     DDF in("remove::"REMOTED_SESSION_CACHE);\r
553     DDFJanitor jin(in);\r
554     in.structure();\r
555     in.addmember("key").string(key);\r
556     in.addmember("application_id").string(application.getId());\r
557     in.addmember("client_addr").string(client_addr);\r
558     \r
559     DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
560     out.destroy();\r
561 }\r
562 \r
563 void RemotedCache::dormant(const char* key)\r
564 {\r
565 #ifdef _DEBUG\r
566     xmltooling::NDC ndc("dormant");\r
567 #endif\r
568 \r
569     m_log.debug("deleting local copy of session (%s)", key);\r
570 \r
571     // lock the cache for writing, which means we know nobody is sitting in find()\r
572     m_lock->wrlock();\r
573 \r
574     // grab the entry from the table\r
575     map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
576     if (i==m_hashtable.end()) {\r
577         m_lock->unlock();\r
578         return;\r
579     }\r
580 \r
581     // ok, remove the entry and lock it\r
582     RemotedSession* entry=i->second;\r
583     m_hashtable.erase(key);\r
584     entry->lock();\r
585     \r
586     // unlock the cache\r
587     m_lock->unlock();\r
588 \r
589     // we can release the cache entry lock because we know we're not in the cache anymore\r
590     entry->unlock();\r
591 \r
592     delete entry;\r
593 }\r
594 \r
595 void RemotedCache::cleanup()\r
596 {\r
597 #ifdef _DEBUG\r
598     xmltooling::NDC ndc("cleanup");\r
599 #endif\r
600 \r
601     int rerun_timer = 0;\r
602     Mutex* mutex = Mutex::create();\r
603   \r
604     // Load our configuration details...\r
605     static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
606     const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);\r
607     if (tag && *tag)\r
608         rerun_timer = XMLString::parseInt(tag);\r
609 \r
610     if (rerun_timer <= 0)\r
611         rerun_timer = 300;        // rerun every 5 minutes\r
612 \r
613     mutex->lock();\r
614 \r
615     m_log.info("cleanup thread started...run every %d secs; timeout after %d secs", rerun_timer, m_cacheTimeout);\r
616 \r
617     while (!shutdown) {\r
618         shutdown_wait->timedwait(mutex,rerun_timer);\r
619         if (shutdown)\r
620             break;\r
621 \r
622         // Ok, let's run through the cleanup process and clean out\r
623         // really old sessions.  This is a two-pass process.  The\r
624         // first pass is done holding a read-lock while we iterate over\r
625         // the cache.  The second pass doesn't need a lock because\r
626         // the 'deletes' will lock the cache.\r
627     \r
628         // Pass 1: iterate over the map and find all entries that have not been\r
629         // used in X hours\r
630         vector<string> stale_keys;\r
631         time_t stale = time(NULL) - m_cacheTimeout;\r
632     \r
633         m_lock->rdlock();\r
634         for (map<string,RemotedSession*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); ++i) {\r
635             // If the last access was BEFORE the stale timeout...\r
636             i->second->lock();\r
637             time_t last=i->second->lastAccess();\r
638             i->second->unlock();\r
639             if (last < stale)\r
640                 stale_keys.push_back(i->first);\r
641         }\r
642         m_lock->unlock();\r
643     \r
644         if (!stale_keys.empty()) {\r
645             m_log.info("purging %d old sessions", stale_keys.size());\r
646     \r
647             // Pass 2: walk through the list of stale entries and remove them from the cache\r
648             for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); ++j)\r
649                 dormant(j->c_str());\r
650         }\r
651     }\r
652 \r
653     m_log.info("cleanup thread exiting");\r
654 \r
655     mutex->unlock();\r
656     delete mutex;\r
657     Thread::exit(NULL);\r
658 }\r
659 \r
660 void* RemotedCache::cleanup_fn(void* cache_p)\r
661 {\r
662     RemotedCache* cache = reinterpret_cast<RemotedCache*>(cache_p);\r
663 \r
664 #ifndef WIN32\r
665     // First, let's block all signals \r
666     Thread::mask_all_signals();\r
667 #endif\r
668 \r
669     // Now run the cleanup process.\r
670     cache->cleanup();\r
671     return NULL;\r
672 }\r
673 \r
674 /* These are currently unimplemented.\r
675 \r
676 void RemotedSession::addAttributes(const vector<Attribute*>& attributes)\r
677 {\r
678     DDF in("addAttributes::"REMOTED_SESSION_CACHE);\r
679     DDFJanitor jin(in);\r
680     in.structure();\r
681     in.addmember("key").string(m_key.c_str());\r
682     in.addmember("application_id").string(m_appId.c_str());\r
683 \r
684     DDF attr;\r
685     DDF attrs = in.addmember("attributes").list();\r
686     for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {\r
687         attr = (*a)->marshall();\r
688         attrs.add(attr);\r
689     }\r
690 \r
691     attr=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
692     DDFJanitor jout(attr);\r
693     \r
694     // Transfer ownership to us.\r
695     m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
696 }\r
697 \r
698 void RemotedSession::addAssertion(RootObject* assertion)\r
699 {\r
700     if (!assertion || !assertion->isAssertion())\r
701         throw FatalProfileException("Unknown object type passed to session cache for storage.");\r
702 \r
703     DDF in("addAssertion::"REMOTED_SESSION_CACHE);\r
704     DDFJanitor jin(in);\r
705     in.structure();\r
706     in.addmember("key").string(m_key.c_str());\r
707     in.addmember("application_id").string(m_appId.c_str());\r
708     \r
709     ostringstream os;\r
710     os << *assertion;\r
711     string token(os.str());\r
712     auto_ptr_char tokenid(assertion->getID());\r
713     in.addmember("assertion_id").string(tokenid.get());\r
714     in.addmember("assertion").string(token.c_str());\r
715 \r
716     DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
717     out.destroy();\r
718     \r
719     // Add to local record and token map.\r
720     // Next attempt to find and lock session will refresh from remote store anyway.\r
721     m_obj["assertions"].addmember(tokenid.get()).string(token.c_str());\r
722     m_ids.clear();\r
723     m_tokens[tokenid.get()] = assertion;\r
724 }\r
725 \r
726 */