Add configuration options for the cacheType, cacheClean, cacheTimeout,
[shibboleth/cpp-sp.git] / shib-target / shib-ccache.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50
51 /*
52  * shib-ccache.cpp -- SHAR Credential Cache
53  *
54  * Originally from mod_shib
55  * Modified by: Derek Atkins <derek@ihtfp.com>
56  *
57  * $Id$
58  */
59
60 #ifndef WIN32
61 # include <unistd.h>
62 #endif
63
64 #include "shib-target.h"
65 #include <shib/shib-threads.h>
66
67 #include <log4cpp/Category.hh>
68
69 #include <sstream>
70 #include <stdexcept>
71
72 #ifdef HAVE_LIBDMALLOCXX
73 #include <dmalloc.h>
74 #endif
75
76 using namespace std;
77 using namespace saml;
78 using namespace shibboleth;
79 using namespace shibtarget;
80
81 class ResourceEntry
82 {
83 public:
84   ResourceEntry(SAMLResponse* response);
85   ~ResourceEntry();
86
87   bool isValid(int slop);
88   Iterator<SAMLAssertion*> getAssertions();
89
90   static vector<SAMLAssertion*> g_emptyVector;
91
92 private:
93   SAMLResponse* m_response;
94
95   log4cpp::Category* log;
96 };
97
98 class InternalCCache;
99 class InternalCCacheEntry : public CCacheEntry
100 {
101 public:
102   InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
103   ~InternalCCacheEntry();
104
105   virtual Iterator<SAMLAssertion*> getAssertions(Resource& resource);
106   virtual void preFetch(Resource& resource, int prefetch_window);
107   virtual bool isSessionValid(time_t lifetime, time_t timeout);
108   virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
109   virtual void release() { cacheitem_lock->unlock(); }
110
111   void setCache(InternalCCache *cache) { m_cache = cache; }
112   time_t lastAccess() { Lock lock(access_lock); return m_lastAccess; }
113   void rdlock() { cacheitem_lock->rdlock(); }
114   void wrlock() { cacheitem_lock->wrlock(); }
115
116 private:
117   ResourceEntry* populate(Resource& resource, int slop);
118   ResourceEntry* find(const char* resource);
119   void insert(const char* resource, ResourceEntry* entry);
120   void remove(const char* resource);
121
122   string m_originSite;
123   string m_handle;
124   string m_clientAddress;
125   time_t m_sessionCreated;
126   time_t m_lastAccess;
127   bool m_hasbinding;
128
129   const SAMLSubject* m_subject;
130   SAMLAuthenticationStatement* p_auth;
131   InternalCCache *m_cache;
132
133   map<string,ResourceEntry*> m_resources;
134
135   static saml::QName g_authorityKind;
136   static saml::QName g_respondWith;
137
138   log4cpp::Category* log;
139
140   // This is used to keep track of in-process "populate()" calls,
141   // to make sure that we don't try to populate the same resource
142   // in multiple threads.
143   map<string,Mutex*>    populate_locks;
144   Mutex*        pop_locks_lock;
145
146   Mutex*        access_lock;
147   RWLock*       resource_lock;
148   RWLock*       cacheitem_lock;
149
150   class ResourceLock
151   {
152   public:
153     ResourceLock(InternalCCacheEntry* entry, string resource);
154     ~ResourceLock();
155
156   private:
157     Mutex*                      find(string& resource);
158     InternalCCacheEntry*        entry;
159     string                      resource;
160   };
161 };
162
163 class InternalCCache : public CCache
164 {
165 public:
166   InternalCCache();
167   virtual ~InternalCCache();
168
169   virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
170   virtual CCacheEntry* find(const char* key);
171   virtual void insert(const char* key, SAMLAuthenticationStatement *s,
172                       const char *client_addr);
173   virtual void remove(const char* key);
174
175   InternalCCacheEntry* findi(const char* key);
176   void  cleanup();
177
178 private:
179   RWLock *lock;
180
181   SAMLBinding* m_SAMLBinding;
182   map<string,InternalCCacheEntry*> m_hashtable;
183
184   log4cpp::Category* log;
185
186   static void*  cleanup_fcn(void*); // XXX Assumed an InternalCCache
187   bool          shutdown;
188   CondWait*     shutdown_wait;
189   Thread*       cleanup_thread;
190 };
191
192 // Global Constructors & Destructors
193 CCache::~CCache() { }
194
195 CCache* CCache::getInstance(const char* type)
196 {
197   return (CCache*) new InternalCCache();
198 }
199
200 // static members
201 saml::QName InternalCCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery));
202 saml::QName InternalCCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement));
203 vector<SAMLAssertion*> ResourceEntry::g_emptyVector;
204
205
206 /******************************************************************************/
207 /* InternalCCache:  A Credential Cache                                        */
208 /******************************************************************************/
209
210 InternalCCache::InternalCCache()
211 {
212   m_SAMLBinding=SAMLBindingFactory::getInstance();
213   string ctx="shibtarget.InternalCCache";
214   log = &(log4cpp::Category::getInstance(ctx));
215   lock = RWLock::create();
216
217   shutdown_wait = CondWait::create();
218   shutdown = false;
219   cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
220 }
221
222 InternalCCache::~InternalCCache()
223 {
224   // Shut down the cleanup thread and let it know...
225   shutdown = true;
226   shutdown_wait->signal();
227   cleanup_thread->join(NULL);
228
229   delete m_SAMLBinding;
230   for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
231     delete i->second;
232   delete lock;
233   delete shutdown_wait;
234 }
235
236 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
237 {
238   log->debug("looking for binding...");
239   if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
240     log->debug("https binding found");
241     return m_SAMLBinding;
242   }
243   return NULL;
244 }
245
246 // assumed a lock is held..
247 InternalCCacheEntry* InternalCCache::findi(const char* key)
248 {
249   log->debug("FindI: \"%s\"", key);
250
251   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
252   if (i==m_hashtable.end()) {
253     log->debug("No Match found");
254     return NULL;
255   }
256   log->debug("Match Found.");
257
258   return i->second;
259 }
260
261 CCacheEntry* InternalCCache::find(const char* key)
262 {
263   log->debug("Find: \"%s\"", key);
264   ReadLock rwlock(lock);
265
266   InternalCCacheEntry* entry = findi(key);
267   if (!entry) return NULL;
268
269   // Lock the database for the caller -- they have to release the item.
270   entry->rdlock();
271   return dynamic_cast<CCacheEntry*>(entry);
272 }
273
274 void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
275                             const char *client_addr)
276 {
277   log->debug("caching new entry for \"%s\"", key);
278
279   InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
280   entry->setCache(this);
281
282   lock->wrlock();
283   m_hashtable[key]=entry;
284   lock->unlock();
285 }
286
287 // remove the entry from the database and then destroy the cacheentry
288 void InternalCCache::remove(const char* key)
289 {
290   log->debug("removing cache entry \"key\"", key);
291
292   // grab the entry from the database.  We'll have a readlock on it.
293   CCacheEntry* entry = findi(key);
294
295   if (!entry)
296     return;
297
298   // grab the cache write lock
299   lock->wrlock();
300
301   // verify we've still got the same entry.
302   if (entry != findi(key)) {
303     // Nope -- must've already been removed.
304     lock->unlock();
305     return;
306   }
307
308   // ok, remove the entry.
309   m_hashtable.erase(key);
310   lock->unlock();
311
312   // now grab the write lock on the cacheitem.
313   // This will make sure all other threads have released this item.
314   InternalCCacheEntry* ientry = dynamic_cast<InternalCCacheEntry*>(entry);
315   ientry->wrlock();
316
317   // we can release immediately because we know we're not in the database!
318   ientry->release();
319
320   // Now delete the entry
321   delete ientry;
322 }
323
324 void InternalCCache::cleanup()
325 {
326   Mutex* mutex = Mutex::create();
327   saml::NDC ndc("InternalCCache::cleanup()");
328
329   ShibTargetConfig& config = ShibTargetConfig::getConfig();
330   ShibINI& ini = config.getINI();
331
332   int rerun_timer = 0;
333   int timeout_life = 0;
334
335   string tag;
336   if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHECLEAN, true, &tag))
337     rerun_timer = atoi(tag.c_str());
338   if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHETIMEOUT, true, &tag))
339     timeout_life = atoi(tag.c_str());
340
341   if (rerun_timer <= 0)
342     rerun_timer = 300;          // rerun every 5 minutes
343
344   if (timeout_life <= 0)
345     timeout_life = 28800;       // timeout after 8 hours
346
347   mutex->lock();
348
349   log->debug("Cleanup thread started...  Run every %d secs; timeout after %d secs",
350              rerun_timer, timeout_life);
351
352   while (shutdown == false) {
353     struct timespec ts;
354     memset (&ts, 0, sizeof(ts));
355     ts.tv_sec = time(NULL) + rerun_timer;
356
357     shutdown_wait->timedwait(mutex, &ts);
358
359     if (shutdown == true)
360       break;
361
362     log->info("Cleanup thread running...");
363
364     // Ok, let's run through the cleanup process and clean out
365     // really old sessions.  This is a two-pass process.  The
366     // first pass is done holding a read-lock while we iterate over
367     // the database.  The second pass doesn't need a lock because
368     // the 'deletes' will lock the database.
369
370     // Pass 1: iterate over the map and find all entries that have not been
371     // used in X hours
372     vector<string> stale_keys;
373     time_t stale = time(NULL) - timeout_life;
374
375     lock->rdlock();
376     for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
377          i != m_hashtable.end(); i++)
378     {
379       // If the last access was BEFORE the stale timeout...
380       if (i->second->lastAccess() < stale)
381         stale_keys.push_back(i->first);
382     }
383     lock->unlock();
384
385     log->info("deleting %d old items.", stale_keys.size());
386
387     // Pass 2: walk through the list of stale entries and remove them from
388     // the database
389     for (vector<string>::iterator i = stale_keys.begin();
390          i != stale_keys.end(); i++)
391     {
392       remove (i->c_str());
393     }
394
395   }
396
397   log->debug("Cleanup thread finished.");
398
399   mutex->unlock();
400   delete mutex;
401   Thread::exit(NULL);
402 }
403
404 void* InternalCCache::cleanup_fcn(void* cache_p)
405 {
406   InternalCCache* cache = (InternalCCache*)cache_p;
407
408   // First, let's block all signals
409   sigset_t sigmask;
410   sigfillset(&sigmask);
411   Thread::mask_signals(SIG_BLOCK, &sigmask, NULL);
412
413   // Now run the cleanup process.
414   cache->cleanup();
415 }
416
417 /******************************************************************************/
418 /* InternalCCacheEntry:  A Credential Cache Entry                             */
419 /******************************************************************************/
420
421 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
422   : m_hasbinding(false)
423 {
424   string ctx = "shibtarget::InternalCCacheEntry";
425   log = &(log4cpp::Category::getInstance(ctx));
426   pop_locks_lock = Mutex::create();
427   access_lock = Mutex::create();
428   resource_lock = RWLock::create();
429   cacheitem_lock = RWLock::create();
430
431   if (s == NULL) {
432     log->error("NULL auth statement");
433     throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
434   }
435
436   m_subject = s->getSubject();
437
438   xstring name = m_subject->getName();
439   xstring qual = m_subject->getNameQualifier();
440
441   auto_ptr<char> h(XMLString::transcode(name.c_str()));
442   auto_ptr<char> d(XMLString::transcode(qual.c_str()));
443
444   m_handle = h.get();
445   m_originSite = d.get();
446
447   Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
448   if (bindings.hasNext())
449     m_hasbinding = true;
450
451   m_clientAddress = client_addr;
452   m_sessionCreated = m_lastAccess = time(NULL);
453
454   // Save for later.
455   p_auth = s;
456
457   log->info("New Session Created...");
458   log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
459              client_addr);
460 }
461
462 InternalCCacheEntry::~InternalCCacheEntry()
463 {
464   log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
465   delete p_auth;
466   for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
467        i!=m_resources.end(); i++)
468     delete i->second;
469
470   for (map<string,Mutex*>::iterator i=populate_locks.begin();
471        i!=populate_locks.end(); i++)
472     delete i->second;
473
474   delete pop_locks_lock;
475   delete cacheitem_lock;
476   delete resource_lock;
477   delete access_lock;
478 }
479
480 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
481 {
482   saml::NDC ndc("isSessionValid");
483   log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
484              m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
485   time_t now=time(NULL);
486   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
487     log->debug("session beyond lifetime");
488     return false;
489   }
490
491   // Lock the access-time from here until we return
492   Lock lock(access_lock);
493   if (timeout > 0 && now-m_lastAccess >= timeout) {
494     log->debug("session timed out");
495     return false;
496   }
497   m_lastAccess=now;
498   return true;
499 }
500
501 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
502 {
503   saml::NDC ndc("getAssertions");
504   ResourceEntry* entry = populate(resource, 0);
505   if (entry)
506     return entry->getAssertions();
507   return Iterator<SAMLAssertion*>(ResourceEntry::g_emptyVector);
508 }
509
510 void InternalCCacheEntry::preFetch(Resource& resource, int prefetch_window)
511 {
512   saml::NDC ndc("preFetch");
513   ResourceEntry* entry = populate(resource, prefetch_window);
514 }
515
516 ResourceEntry* InternalCCacheEntry::populate(Resource& resource, int slop)
517 {
518   saml::NDC ndc("populate");
519   log->debug("populating entry for %s (%s)",
520              resource.getResource(), resource.getURL());
521
522   // Lock the resource within this entry...
523   InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
524
525   // Can we use what we have?
526   ResourceEntry *entry = find(resource.getResource());
527   if (entry) {
528     log->debug("found resource");
529     if (entry->isValid(slop))
530       return entry;
531
532     // entry is invalid (expired) -- go fetch a new one.
533     log->debug("removing resource cache; assertion is invalid");
534     remove (resource.getResource());
535     delete entry;
536   }
537
538   // Nope, no entry.. Create a new resource entry
539
540   if (!m_hasbinding) {
541     log->error("No binding!");
542     return NULL;
543   }
544
545   log->info("trying to request attributes for %s@%s -> %s",
546             m_handle.c_str(), m_originSite.c_str(), resource.getURL());
547
548   auto_ptr<XMLCh> resourceURL(XMLString::transcode(resource.getURL()));
549   Iterator<saml::QName> respond_withs = ArrayIterator<saml::QName>(&g_respondWith);
550
551   // Clone the subject...
552   // 1) I know the static_cast is safe from clone()
553   // 2) the AttributeQuery will destroy this new subject.
554   SAMLSubject* subject=static_cast<SAMLSubject*>(m_subject->clone());
555
556   // Build a SAML Request....
557   SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resourceURL.get(),
558                                                resource.getDesignators());
559   SAMLRequest* req=new SAMLRequest(respond_withs,q);
560
561   // Try this request against all the bindings in the AuthenticationStatement
562   // (i.e. send it to each AA in the list of bindings)
563   Iterator<SAMLAuthorityBinding*> bindings = p_auth->getBindings();
564   SAMLResponse* response = NULL;
565
566   while (!response && bindings.hasNext()) {
567     SAMLAuthorityBinding* binding = bindings.next();
568
569     log->debug("Trying binding...");
570     SAMLBinding* pBinding=m_cache->getBinding(binding->getBinding());
571     log->debug("Sending request");
572     response=pBinding->send(*binding,*req);
573   }
574
575   // ok, we can delete the request now.
576   delete req;
577
578   // Make sure we got a response
579   if (!response) {
580     log->info ("No Response");
581     return NULL;
582   }
583
584   entry = new ResourceEntry(response);
585   insert (resource.getResource(), entry);
586
587   log->info("fetched and stored SAML response");
588   return entry;
589 }
590
591 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
592 {
593   ReadLock rwlock(resource_lock);
594
595   log->debug("find: %s", resource_url);
596   map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
597   if (i==m_resources.end()) {
598     log->debug("no match found");
599     return NULL;
600   }
601   log->debug("match found");
602   return i->second;
603 }
604
605 void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
606 {
607   log->debug("inserting %s", resource);
608
609   resource_lock->wrlock();
610   m_resources[resource]=entry;
611   resource_lock->unlock();
612 }
613
614 // caller will delete the entry.. don't worry about that here.
615 void InternalCCacheEntry::remove(const char* resource)
616 {
617   log->debug("removing %s", resource);
618
619   resource_lock->wrlock();
620   m_resources.erase(resource);
621   resource_lock->unlock();
622 }
623
624
625 // a lock on a resource.  This is a specific "table of locks" that
626 // will provide a mutex on a particular resource within a Cache Entry.
627 // Just instantiate a ResourceLock within scope of the function and it
628 // will obtain and hold the proper lock until it goes out of scope and
629 // deconstructs.
630
631 InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
632                                                 string resource) :
633   entry(entry), resource(resource)
634 {
635   Mutex *mutex = find(resource);
636   mutex->lock();
637 }
638
639 InternalCCacheEntry::ResourceLock::~ResourceLock()
640 {
641   Mutex *mutex = find(resource);
642   mutex->unlock();
643 }
644
645 Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
646 {
647   Lock(entry->pop_locks_lock);
648   
649   map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
650   if (i==entry->populate_locks.end()) {
651     Mutex* mutex = Mutex::create();
652     entry->populate_locks[resource] = mutex;
653     return mutex;
654   }
655   return i->second;
656 }
657
658 /******************************************************************************/
659 /* ResourceEntry:  A Credential Cache Entry for a particular Resource URL     */
660 /******************************************************************************/
661
662 ResourceEntry::ResourceEntry(SAMLResponse* response)
663 {
664   string ctx = "shibtarget::ResourceEntry";
665   log = &(log4cpp::Category::getInstance(ctx));
666
667   log->info("caching resource entry");
668
669   m_response = response;
670 }
671
672 ResourceEntry::~ResourceEntry()
673 {
674   delete m_response;
675 }
676
677 Iterator<SAMLAssertion*> ResourceEntry::getAssertions()
678 {
679   saml::NDC ndc("getAssertions");
680   return m_response->getAssertions();
681 }
682
683 bool ResourceEntry::isValid(int slop)
684 {
685   saml::NDC ndc("isValid");
686
687   log->info("checking validity");
688
689   // This is awful, but the XMLDateTime class is truly horrible.
690   time_t now=time(NULL)+slop;
691 #ifdef WIN32
692   struct tm* ptime=gmtime(&now);
693 #else
694   struct tm res;
695   struct tm* ptime=gmtime_r(&now,&res);
696 #endif
697   char timebuf[32];
698   strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
699   auto_ptr<XMLCh> timeptr(XMLString::transcode(timebuf));
700   XMLDateTime curDateTime(timeptr.get());
701   curDateTime.parseDateTime();
702
703   Iterator<SAMLAssertion*> iter = getAssertions();
704
705   while (iter.hasNext()) {
706     SAMLAssertion* assertion = iter.next();
707
708     log->debug ("testing assertion...");
709
710     const XMLDateTime* thistime = assertion->getNotOnOrAfter();
711
712     if (! thistime) {
713       log->debug ("getNotOnOrAfter failed.");
714       return false;
715     }
716
717     auto_ptr<char> nowptr(XMLString::transcode(curDateTime.toString()));
718     auto_ptr<char> assnptr(XMLString::transcode(thistime->toString()));
719
720     log->debug ("comparing now (%s) to %s", nowptr.get(), assnptr.get());
721     int result=XMLDateTime::compareOrder(&curDateTime, thistime);
722
723     if (result != XMLDateTime::LESS_THAN) {
724       log->debug("nope, not still valid");
725       return false;
726     }
727   } // while
728
729   log->debug("yep, all still valid");
730   return true;
731 }