g10: Eliminate the redundant function get_keyblock_byfprint.
[gnupg.git] / agent / cache.c
index f19e97d..49402e4 100644 (file)
@@ -24,7 +24,7 @@
 #include <string.h>
 #include <time.h>
 #include <assert.h>
-#include <pth.h>
+#include <npth.h>
 
 #include "agent.h"
 
@@ -33,7 +33,7 @@
 
 /* A mutex used to protect the encryption.  This is required because
    we use one context to do all encryption and decryption.  */
-static pth_mutex_t encryption_lock;
+static npth_mutex_t encryption_lock;
 /* The encryption context.  This is the only place where the
    encryption key for all cached entries is available.  It would be nice
    to keep this (or just the key) in some hardware device, for example
@@ -65,21 +65,54 @@ struct cache_item_s {
 /* The cache himself.  */
 static ITEM thecache;
 
+/* NULL or the last cache key stored by agent_store_cache_hit.  */
+static char *last_stored_cache_key;
+
 
 /* This function must be called once to initialize this module. It
    has to be done before a second thread is spawned.  */
 void
 initialize_module_cache (void)
 {
-  static int initialized;
+  int err;
+
+  err = npth_mutex_init (&encryption_lock, NULL);
+
+  if (err)
+    log_fatal ("error initializing cache module: %s\n", strerror (err));
+}
+
+
+void
+deinitialize_module_cache (void)
+{
+  gcry_cipher_close (encryption_handle);
+  encryption_handle = NULL;
+}
+
+
+/* We do the encryption init on the fly.  We can't do it in the module
+   init code because that is run before we listen for connections and
+   in case we are started on demand by gpg etc. it will only wait for
+   a few seconds to decide whether the agent may now accept
+   connections.  Thus we should get into listen state as soon as
+   possible.  */
+static gpg_error_t
+init_encryption (void)
+{
   gpg_error_t err;
   void *key;
+  int res;
+
+  if (encryption_handle)
+    return 0; /* Shortcut - Already initialized.  */
+
+  res = npth_mutex_lock (&encryption_lock);
+  if (res)
+    log_fatal ("failed to acquire cache encryption mutex: %s\n", strerror (res));
 
-  if (!pth_mutex_init (&encryption_lock))
-    err = gpg_error_from_syserror ();
-  else
-    err = gcry_cipher_open (&encryption_handle, GCRY_CIPHER_AES128,
-                            GCRY_CIPHER_MODE_AESWRAP, GCRY_CIPHER_SECURE);
+  err = gcry_cipher_open (&encryption_handle, GCRY_CIPHER_AES128,
+                          GCRY_CIPHER_MODE_AESWRAP, GCRY_CIPHER_SECURE);
   if (!err)
     {
       key = gcry_random_bytes (ENCRYPTION_KEYSIZE, GCRY_STRONG_RANDOM);
@@ -90,22 +123,25 @@ initialize_module_cache (void)
           err = gcry_cipher_setkey (encryption_handle, key, ENCRYPTION_KEYSIZE);
           xfree (key);
         }
+      if (err)
+        {
+          gcry_cipher_close (encryption_handle);
+          encryption_handle = NULL;
+        }
     }
   if (err)
-    log_fatal ("error initializing cache encryption context: %s\n",
-             gpg_strerror (err));
-  initialized = 1;
-}
+    log_error ("error initializing cache encryption context: %s\n",
+               gpg_strerror (err));
 
+  res = npth_mutex_unlock (&encryption_lock);
+  if (res)
+    log_fatal ("failed to release cache encryption mutex: %s\n", strerror (res));
 
-void
-deinitialize_module_cache (void)
-{
-  gcry_cipher_close (encryption_handle);
-  encryption_handle = NULL;
+  return err? gpg_error (GPG_ERR_NOT_INITIALIZED) : 0;
 }
 
 
+
 static void
 release_data (struct secret_data_s *data)
 {
@@ -119,11 +155,13 @@ new_data (const char *string, struct secret_data_s **r_data)
   struct secret_data_s *d, *d_enc;
   size_t length;
   int total;
-  
+  int res;
+
   *r_data = NULL;
 
-  if (!encryption_handle)
-    return gpg_error (GPG_ERR_NOT_INITIALIZED);
+  err = init_encryption ();
+  if (err)
+    return err;
 
   length = strlen (string) + 1;
 
@@ -148,13 +186,17 @@ new_data (const char *string, struct secret_data_s **r_data)
     }
 
   d_enc->totallen = total;
-  if (!pth_mutex_acquire (&encryption_lock, 0, NULL))
-    log_fatal ("failed to acquire cache encryption mutex\n");
+  res = npth_mutex_lock (&encryption_lock);
+  if (res)
+    log_fatal ("failed to acquire cache encryption mutex: %s\n",
+               strerror (res));
+
   err = gcry_cipher_encrypt (encryption_handle, d_enc->data, total,
                              d->data, total - 8);
   xfree (d);
-  if (!pth_mutex_release (&encryption_lock))
-    log_fatal ("failed to release cache encryption mutex\n");
+  res = npth_mutex_unlock (&encryption_lock);
+  if (res)
+    log_fatal ("failed to release cache encryption mutex: %s\n", strerror (res));
   if (err)
     {
       xfree (d_enc);
@@ -179,7 +221,7 @@ housekeeping (void)
       if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current)
         {
           if (DBG_CACHE)
-            log_debug ("  expired `%s' (%ds after last access)\n",
+            log_debug ("  expired '%s' (%ds after last access)\n",
                        r->key, r->ttl);
           release_data (r->pw);
           r->pw = NULL;
@@ -192,7 +234,7 @@ housekeeping (void)
   for (r=thecache; r; r = r->next)
     {
       unsigned long maxttl;
-      
+
       switch (r->cache_mode)
         {
         case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break;
@@ -201,7 +243,7 @@ housekeeping (void)
       if (r->pw && r->created + maxttl < current)
         {
           if (DBG_CACHE)
-            log_debug ("  expired `%s' (%lus after creation)\n",
+            log_debug ("  expired '%s' (%lus after creation)\n",
                        r->key, opt.max_cache_ttl);
           release_data (r->pw);
           r->pw = NULL;
@@ -217,7 +259,7 @@ housekeeping (void)
         {
           ITEM r2 = r->next;
           if (DBG_CACHE)
-            log_debug ("  removed `%s' (mode %d) (slot not used for 30m)\n",
+            log_debug ("  removed '%s' (mode %d) (slot not used for 30m)\n",
                        r->key, r->cache_mode);
           xfree (r);
           if (!rprev)
@@ -248,7 +290,7 @@ agent_flush_cache (void)
       if (r->pw)
         {
           if (DBG_CACHE)
-            log_debug ("  flushing `%s'\n", r->key);
+            log_debug ("  flushing '%s'\n", r->key);
           release_data (r->pw);
           r->pw = NULL;
           r->accessed = 0;
@@ -272,7 +314,7 @@ agent_put_cache (const char *key, cache_mode_t cache_mode,
   ITEM r;
 
   if (DBG_CACHE)
-    log_debug ("agent_put_cache `%s' (mode %d) requested ttl=%d\n",
+    log_debug ("agent_put_cache '%s' (mode %d) requested ttl=%d\n",
                key, cache_mode, ttl);
   housekeeping ();
 
@@ -304,7 +346,7 @@ agent_put_cache (const char *key, cache_mode_t cache_mode,
         }
       if (data)
         {
-          r->created = r->accessed = gnupg_get_time (); 
+          r->created = r->accessed = gnupg_get_time ();
           r->ttl = ttl;
           r->cache_mode = cache_mode;
           err = new_data (data, &r->pw);
@@ -320,7 +362,7 @@ agent_put_cache (const char *key, cache_mode_t cache_mode,
       else
         {
           strcpy (r->key, key);
-          r->created = r->accessed = gnupg_get_time (); 
+          r->created = r->accessed = gnupg_get_time ();
           r->ttl = ttl;
           r->cache_mode = cache_mode;
           err = new_data (data, &r->pw);
@@ -348,12 +390,25 @@ agent_get_cache (const char *key, cache_mode_t cache_mode)
   gpg_error_t err;
   ITEM r;
   char *value = NULL;
+  int res;
+  int last_stored = 0;
 
   if (cache_mode == CACHE_MODE_IGNORE)
     return NULL;
 
+  if (!key)
+    {
+      key = last_stored_cache_key;
+      if (!key)
+        return NULL;
+      last_stored = 1;
+    }
+
+
   if (DBG_CACHE)
-    log_debug ("agent_get_cache `%s' (mode %d) ...\n", key, cache_mode);
+    log_debug ("agent_get_cache '%s' (mode %d)%s ...\n",
+               key, cache_mode,
+               last_stored? " (stored cache key)":"");
   housekeeping ();
 
   for (r=thecache; r; r = r->next)
@@ -364,30 +419,35 @@ agent_get_cache (const char *key, cache_mode_t cache_mode)
               || r->cache_mode == cache_mode)
           && !strcmp (r->key, key))
         {
+          /* Note: To avoid races KEY may not be accessed anymore below.  */
           r->accessed = gnupg_get_time ();
           if (DBG_CACHE)
             log_debug ("... hit\n");
           if (r->pw->totallen < 32)
             err = gpg_error (GPG_ERR_INV_LENGTH);
-          else if (!encryption_handle)
-            err = gpg_error (GPG_ERR_NOT_INITIALIZED);
+          else if ((err = init_encryption ()))
+            ;
           else if (!(value = xtrymalloc_secure (r->pw->totallen - 8)))
             err = gpg_error_from_syserror ();
           else
             {
-              if (!pth_mutex_acquire (&encryption_lock, 0, NULL))
-                log_fatal ("failed to acquire cache encryption mutex\n");
+              res = npth_mutex_lock (&encryption_lock);
+              if (res)
+                log_fatal ("failed to acquire cache encryption mutex: %s\n",
+                          strerror (res));
               err = gcry_cipher_decrypt (encryption_handle,
                                          value, r->pw->totallen - 8,
                                          r->pw->data, r->pw->totallen);
-              if (!pth_mutex_release (&encryption_lock))
-                log_fatal ("failed to release cache encryption mutex\n");
+              res = npth_mutex_unlock (&encryption_lock);
+              if (res)
+                log_fatal ("failed to release cache encryption mutex: %s\n",
+                          strerror (res));
             }
           if (err)
             {
               xfree (value);
               value = NULL;
-              log_error ("retrieving cache entry `%s' failed: %s\n",
+              log_error ("retrieving cache entry '%s' failed: %s\n",
                          key, gpg_strerror (err));
             }
           return value;
@@ -399,3 +459,13 @@ agent_get_cache (const char *key, cache_mode_t cache_mode)
   return NULL;
 }
 
+
+/* Store the key for the last successful cache hit.  That value is
+   used by agent_get_cache if the requested KEY is given as NULL.
+   NULL may be used to remove that key. */
+void
+agent_store_cache_hit (const char *key)
+{
+  xfree (last_stored_cache_key);
+  last_stored_cache_key = key? xtrystrdup (key) : NULL;
+}