Updated FSF's address.
[gnupg.git] / agent / cache.c
index 9621371..2f46839 100644 (file)
@@ -15,7 +15,8 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
  */
 
 #include <config.h>
@@ -39,8 +40,10 @@ struct cache_item_s {
   ITEM next;
   time_t created;
   time_t accessed;
-  int  ttl;  /* max. lifetime given in seonds */
+  int ttl;  /* max. lifetime given in seconds, -1 one means infinite */
+  int lockcount;
   struct secret_data_s *pw;
+  cache_mode_t cache_mode;
   char key[1];
 };
 
@@ -66,7 +69,7 @@ new_data (const void *data, size_t length)
      secure storage provider*/
   total = length + 32 - (length % 32);
 
-  d = gcry_malloc_secure (sizeof d + total - 1);
+  d = gcry_malloc_secure (sizeof *d + total - 1);
   if (d)
     {
       d->totallen = total;
@@ -77,49 +80,78 @@ new_data (const void *data, size_t length)
 }
 
 
+
 /* check whether there are items to expire */
 static void
 housekeeping (void)
 {
   ITEM r, rprev;
-  time_t current = time (NULL);
+  time_t current = gnupg_get_time ();
 
-  /* first expire the actual data */
+  /* First expire the actual data */
   for (r=thecache; r; r = r->next)
     {
-      if (r->pw && r->accessed + r->ttl < current)
+      if (!r->lockcount && r->pw
+         && r->ttl >= 0 && r->accessed + r->ttl < current)
         {
+          if (DBG_CACHE)
+            log_debug ("  expired `%s' (%ds after last access)\n",
+                       r->key, r->ttl);
           release_data (r->pw);
           r->pw = NULL;
           r->accessed = current;
         }
     }
 
-  /* second, make sure that we also remove them based on the created stamp so
-     that the used has to enter it from time to time.  We do this every hour */
+  /* Second, make sure that we also remove them based on the created stamp so
+     that the user has to enter it from time to time. */
   for (r=thecache; r; r = r->next)
     {
-      if (r->pw && r->created + 60*60 < current)
+      unsigned long maxttl;
+      
+      switch (r->cache_mode)
+        {
+        case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break;
+        default: maxttl = opt.max_cache_ttl; break;
+        }
+      if (!r->lockcount && r->pw && r->created + maxttl < current)
         {
+          if (DBG_CACHE)
+            log_debug ("  expired `%s' (%lus after creation)\n",
+                       r->key, opt.max_cache_ttl);
           release_data (r->pw);
           r->pw = NULL;
           r->accessed = current;
         }
     }
 
-  /* third, make sure that we don't have too many items in the list.
+  /* Third, make sure that we don't have too many items in the list.
      Expire old and unused entries after 30 minutes */
   for (rprev=NULL, r=thecache; r; )
     {
-      if (!r->pw && r->accessed + 60*30 < current)
+      if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current)
         {
-          ITEM r2 = r->next;
-          xfree (r);
-          if (!rprev)
-            thecache = r2;
+          if (r->lockcount)
+            {
+              log_error ("can't remove unused cache entry `%s' due to"
+                         " lockcount=%d\n",
+                         r->key, r->lockcount);
+              r->accessed += 60*10; /* next error message in 10 minutes */
+              rprev = r;
+              r = r->next;
+            }
           else
-            rprev = r2;
-          r = r2;
+            {
+              ITEM r2 = r->next;
+              if (DBG_CACHE)
+                log_debug ("  removed `%s' (slot not used for 30m)\n", r->key);
+              xfree (r);
+              if (!rprev)
+                thecache = r2;
+              else
+                rprev->next = r2;
+              r = r2;
+            }
         }
       else
         {
@@ -130,24 +162,67 @@ housekeeping (void)
 }
 
 
+void
+agent_flush_cache (void)
+{
+  ITEM r;
+
+  if (DBG_CACHE)
+    log_debug ("agent_flush_cache\n");
+
+  for (r=thecache; r; r = r->next)
+    {
+      if (!r->lockcount && r->pw)
+        {
+          if (DBG_CACHE)
+            log_debug ("  flushing `%s'\n", r->key);
+          release_data (r->pw);
+          r->pw = NULL;
+          r->accessed = 0;
+        }
+      else if (r->lockcount && r->pw)
+        {
+          if (DBG_CACHE)
+            log_debug ("    marked `%s' for flushing\n", r->key);
+          r->accessed = 0;
+          r->ttl = 0;
+        }
+    }
+}
+
+
 
 /* Store DATA of length DATALEN in the cache under KEY and mark it
-   with a maxiumum lifetime of TTL seconds.  If tehre is already data
+   with a maximum lifetime of TTL seconds.  If there is already data
    under this key, it will be replaced.  Using a DATA of NULL deletes
-   the entry */
+   the entry.  A TTL of 0 is replaced by the default TTL and a TTL of
+   -1 set infinite timeout. CACHE_MODE is stored with the cache entry
+   and used t select different timeouts. */
 int
-agent_put_cache (const char *key, const char *data, int ttl)
+agent_put_cache (const char *key, cache_mode_t cache_mode,
+                 const char *data, int ttl)
 {
   ITEM r;
 
+  if (DBG_CACHE)
+    log_debug ("agent_put_cache `%s' requested ttl=%d mode=%d\n",
+               key, ttl, cache_mode);
   housekeeping ();
 
-  if (ttl < 1)
-    ttl = 60*5; /* default is 5 minutes */
+  if (!ttl)
+    {
+      switch(cache_mode)
+        {
+        case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break;
+        default: ttl = opt.def_cache_ttl; break;
+        }
+    }
+  if (!ttl || cache_mode == CACHE_MODE_IGNORE)
+    return 0;
 
   for (r=thecache; r; r = r->next)
     {
-      if ( !strcmp (r->key, key))
+      if (!r->lockcount && !strcmp (r->key, key))
         break;
     }
   if (r)
@@ -159,6 +234,9 @@ agent_put_cache (const char *key, const char *data, int ttl)
         }
       if (data)
         {
+          r->created = r->accessed = gnupg_get_time (); 
+          r->ttl = ttl;
+          r->cache_mode = cache_mode;
           r->pw = new_data (data, strlen (data)+1);
           if (!r->pw)
             log_error ("out of core while allocating new cache item\n");
@@ -172,8 +250,9 @@ agent_put_cache (const char *key, const char *data, int ttl)
       else
         {
           strcpy (r->key, key);
-          r->created = r->accessed = time (NULL); 
+          r->created = r->accessed = gnupg_get_time (); 
           r->ttl = ttl;
+          r->cache_mode = cache_mode;
           r->pw = new_data (data, strlen (data)+1);
           if (!r->pw)
             {
@@ -191,30 +270,73 @@ agent_put_cache (const char *key, const char *data, int ttl)
 }
 
 
-/* Try to find an item in the cache */
+/* Try to find an item in the cache.  Note that we currently don't
+   make use of CACHE_MODE.  */
 const char *
-agent_get_cache (const char *key)
+agent_get_cache (const char *key, cache_mode_t cache_mode, void **cache_id)
 {
   ITEM r;
-  int count = 0;
 
+  if (cache_mode == CACHE_MODE_IGNORE)
+    return NULL;
+
+  if (DBG_CACHE)
+    log_debug ("agent_get_cache `%s'...\n", key);
   housekeeping ();
 
-  /* FIXME: Returning pointers is not thread safe - add a referencense
-     counter */
-  for (r=thecache; r; r = r->next, count++)
+  /* first try to find one with no locks - this is an updated cache
+     entry: We might have entries with a lockcount and without a
+     lockcount. */
+  for (r=thecache; r; r = r->next)
     {
-      if (r->pw && !strcmp (r->key, key))
+      if (!r->lockcount && r->pw && !strcmp (r->key, key))
         {
-          /* put_cache does onlu put strings into the cache, so we
+          /* put_cache does only put strings into the cache, so we
              don't need the lengths */
-          r->accessed = time (NULL);
+          r->accessed = gnupg_get_time ();
+          if (DBG_CACHE)
+            log_debug ("... hit\n");
+          r->lockcount++;
+          *cache_id = r;
+          return r->pw->data;
+        }
+    }
+  /* again, but this time get even one with a lockcount set */
+  for (r=thecache; r; r = r->next)
+    {
+      if (r->pw && !strcmp (r->key, key))
+        {
+          r->accessed = gnupg_get_time ();
+          if (DBG_CACHE)
+            log_debug ("... hit (locked)\n");
+          r->lockcount++;
+          *cache_id = r;
           return r->pw->data;
         }
     }
+  if (DBG_CACHE)
+    log_debug ("... miss\n");
 
+  *cache_id = NULL;
   return NULL;
 }
 
 
+void
+agent_unlock_cache_entry (void **cache_id)
+{
+  ITEM r;
 
+  for (r=thecache; r; r = r->next)
+    {
+      if (r == *cache_id)
+        {
+          if (!r->lockcount)
+            log_error ("trying to unlock non-locked cache entry `%s'\n",
+                       r->key);
+          else
+            r->lockcount--;
+          return;
+        }
+    }
+}