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