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