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