gpg: Add support for unwrapping the outer level of encryption.
[gnupg.git] / sm / call-dirmngr.c
index 0e8f67f..bfb80fb 100644 (file)
@@ -1,11 +1,12 @@
-/* call-dirmngr.c - communication with the dromngr 
- *     Copyright (C) 2002, 2003 Free Software Foundation, Inc.
+/* call-dirmngr.c - Communication with the dirmngr
+ * Copyright (C) 2002, 2003, 2005, 2007, 2008,
+ *               2010  Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  *
  * This file is part of GnuPG.
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * GnuPG is distributed in the hope that it will be useful,
  * (at your option) any later version.
  *
  * GnuPG is distributed in the hope that it will be useful,
@@ -14,8 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * GNU General Public License for more details.
  *
  * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
  */
 
 #include <config.h>
@@ -23,7 +23,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
-#include <unistd.h> 
+#include <unistd.h>
 #include <time.h>
 #include <assert.h>
 #include <ctype.h>
 #include <time.h>
 #include <assert.h>
 #include <ctype.h>
@@ -34,6 +34,7 @@
 
 #include "i18n.h"
 #include "keydb.h"
 
 #include "i18n.h"
 #include "keydb.h"
+#include "asshelp.h"
 
 
 struct membuf {
 
 
 struct membuf {
@@ -45,24 +46,31 @@ struct membuf {
 
 
 
 
 
 
-static ASSUAN_CONTEXT dirmngr_ctx = NULL;
-static int force_pipe_server = 0;
+/* fixme: We need a context for each thread or serialize the access to
+   the dirmngr.  */
+static assuan_context_t dirmngr_ctx = NULL;
+static assuan_context_t dirmngr2_ctx = NULL;
+
+static int dirmngr_ctx_locked;
+static int dirmngr2_ctx_locked;
 
 struct inq_certificate_parm_s {
 
 struct inq_certificate_parm_s {
-  ASSUAN_CONTEXT ctx;
+  ctrl_t ctrl;
+  assuan_context_t ctx;
   ksba_cert_t cert;
   ksba_cert_t issuer_cert;
 };
 
 struct isvalid_status_parm_s {
   ksba_cert_t cert;
   ksba_cert_t issuer_cert;
 };
 
 struct isvalid_status_parm_s {
+  ctrl_t ctrl;
   int seen;
   unsigned char fpr[20];
 };
 
 
 struct lookup_parm_s {
   int seen;
   unsigned char fpr[20];
 };
 
 
 struct lookup_parm_s {
-  CTRL ctrl;
-  ASSUAN_CONTEXT ctx;
+  ctrl_t ctrl;
+  assuan_context_t ctx;
   void (*cb)(void *, ksba_cert_t);
   void *cb_value;
   struct membuf data;
   void (*cb)(void *, ksba_cert_t);
   void *cb_value;
   struct membuf data;
@@ -70,10 +78,17 @@ struct lookup_parm_s {
 };
 
 struct run_command_parm_s {
 };
 
 struct run_command_parm_s {
-  ASSUAN_CONTEXT ctx;
+  assuan_context_t ctx;
 };
 
 
 };
 
 
+
+static gpg_error_t get_cached_cert (assuan_context_t ctx,
+                                    const unsigned char *fpr,
+                                    ksba_cert_t *r_cert);
+
+
+\f
 /* A simple implementation of a dynamic buffer.  Use init_membuf() to
    create a buffer, put_membuf to append bytes and get_membuf to
    release and return the buffer.  Allocation errors are detected but
 /* A simple implementation of a dynamic buffer.  Use init_membuf() to
    create a buffer, put_membuf to append bytes and get_membuf to
    release and return the buffer.  Allocation errors are detected but
@@ -100,7 +115,7 @@ put_membuf (struct membuf *mb, const void *buf, size_t len)
   if (mb->len + len >= mb->size)
     {
       char *p;
   if (mb->len + len >= mb->size)
     {
       char *p;
-      
+
       mb->size += len + 1024;
       p = xtryrealloc (mb->buf, mb->size);
       if (!p)
       mb->size += len + 1024;
       p = xtryrealloc (mb->buf, mb->size);
       if (!p)
@@ -134,136 +149,202 @@ get_membuf (struct membuf *mb, size_t *len)
 }
 
 
 }
 
 
+/* This function prepares the dirmngr for a new session.  The
+   audit-events option is used so that other dirmngr clients won't get
+   disturbed by such events.  */
+static void
+prepare_dirmngr (ctrl_t ctrl, assuan_context_t ctx, gpg_error_t err)
+{
+  struct keyserver_spec *server;
+
+  if (!err)
+    {
+      err = assuan_transact (ctx, "OPTION audit-events=1",
+                            NULL, NULL, NULL, NULL, NULL, NULL);
+      if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
+       err = 0;  /* Allow the use of old dirmngr versions.  */
+    }
+  audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err);
+
+  if (!ctx || err)
+    return;
+
+  server = opt.keyserver;
+  while (server)
+    {
+      char line[ASSUAN_LINELENGTH];
+      char *user = server->user ? server->user : "";
+      char *pass = server->pass ? server->pass : "";
+      char *base = server->base ? server->base : "";
+
+      snprintf (line, DIM (line) - 1, "LDAPSERVER %s:%i:%s:%s:%s",
+               server->host, server->port, user, pass, base);
+      line[DIM (line) - 1] = 0;
+
+      err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+      if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
+       err = 0;  /* Allow the use of old dirmngr versions.  */
+
+      server = server->next;
+    }
+}
 
 
 \f
 
 
 \f
-/* Try to connect to the agent via socket or fork it off and work by
-   pipes.  Handle the server's initial greeting */
-static int
-start_dirmngr (void)
+/* Return a new assuan context for a Dirmngr connection.  */
+static gpg_error_t
+start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r)
 {
 {
-  int rc;
-  char *infostr, *p;
-  ASSUAN_CONTEXT ctx;
+  gpg_error_t err;
+  assuan_context_t ctx;
+
+  if (opt.disable_dirmngr || ctrl->offline)
+    return gpg_error (GPG_ERR_NO_DIRMNGR);
+
+  if (*ctx_r)
+    return 0;
 
 
-  if (dirmngr_ctx)
-    return 0; /* fixme: We need a context for each thread or serialize
-                 the access to the dirmngr */
+  /* Note: if you change this to multiple connections, you also need
+     to take care of the implicit option sending caching. */
 
 
-  infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO");
-  if (!infostr || !*infostr)
+  err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT,
+                           opt.homedir, opt.dirmngr_program,
+                           opt.autostart, opt.verbose, DBG_IPC,
+                           gpgsm_status2, ctrl);
+  if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR)
     {
     {
-      const char *pgmname;
-      const char *argv[3];
-      int no_close_list[3];
-      int i;
+      static int shown;
 
 
-      if (opt.verbose)
-        log_info (_("no running dirmngr - starting one\n"));
-      
-      if (fflush (NULL))
+      if (!shown)
         {
         {
-          gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-          log_error ("error flushing pending output: %s\n", strerror (errno));
-          return tmperr;
+          shown = 1;
+          log_info (_("no dirmngr running in this session\n"));
         }
         }
+    }
+  prepare_dirmngr (ctrl, ctx, err);
+  if (err)
+    return err;
 
 
-      if (!opt.dirmngr_program || !*opt.dirmngr_program)
-        opt.dirmngr_program = GNUPG_DEFAULT_DIRMNGR;
-      if ( !(pgmname = strrchr (opt.dirmngr_program, '/')))
-        pgmname = opt.dirmngr_program;
-      else
-        pgmname++;
+  *ctx_r = ctx;
+  return 0;
+}
 
 
-      argv[0] = pgmname;
-      argv[1] = "--server";
-      argv[2] = NULL;
 
 
-      i=0;
-      if (log_get_fd () != -1)
-        no_close_list[i++] = log_get_fd ();
-      no_close_list[i++] = fileno (stderr);
-      no_close_list[i] = -1;
+static int
+start_dirmngr (ctrl_t ctrl)
+{
+  gpg_error_t err;
+
+  assert (! dirmngr_ctx_locked);
+  dirmngr_ctx_locked = 1;
+
+  err = start_dirmngr_ext (ctrl, &dirmngr_ctx);
+  /* We do not check ERR but the existance of a context because the
+     error might come from a failed command send to the dirmngr.
+     Fixme: Why don't we close the drimngr context if we encountered
+     an error in prepare_dirmngr?  */
+  if (!dirmngr_ctx)
+    dirmngr_ctx_locked = 0;
+  return err;
+}
 
 
-      /* connect to the agent and perform initial handshaking */
-      rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv,
-                                no_close_list);
-    }
-  else
-    {
-      int prot;
-      int pid;
 
 
-      infostr = xstrdup (infostr);
-      if ( !(p = strchr (infostr, ':')) || p == infostr)
-        {
-          log_error (_("malformed DIRMNGR_INFO environment variable\n"));
-          xfree (infostr);
-          force_pipe_server = 1;
-          return start_dirmngr ();
-        }
-      *p++ = 0;
-      pid = atoi (p);
-      while (*p && *p != ':')
-        p++;
-      prot = *p? atoi (p+1) : 0;
-      if (prot != 1)
-        {
-          log_error (_("dirmngr protocol version %d is not supported\n"),
-                     prot);
-          xfree (infostr);
-          force_pipe_server = 1;
-          return start_dirmngr ();
-        }
+static void
+release_dirmngr (ctrl_t ctrl)
+{
+  (void)ctrl;
 
 
-      rc = assuan_socket_connect (&ctx, infostr, pid);
-      xfree (infostr);
-      if (rc == ASSUAN_Connect_Failed)
-        {
-          log_error (_("can't connect to the dirmngr - trying fall back\n"));
-          force_pipe_server = 1;
-          return start_dirmngr ();
-        }
-    }
+  if (!dirmngr_ctx_locked)
+    log_error ("WARNING: trying to release a non-locked dirmngr ctx\n");
+  dirmngr_ctx_locked = 0;
+}
 
 
-  if (rc)
-    {
-      log_error ("can't connect to the dirmngr: %s\n", assuan_strerror (rc));
-      return gpg_error (GPG_ERR_NO_DIRMNGR);
-    }
-  dirmngr_ctx = ctx;
 
 
-  if (DBG_ASSUAN)
-    log_debug ("connection to dirmngr established\n");
-  return 0;
+static int
+start_dirmngr2 (ctrl_t ctrl)
+{
+  gpg_error_t err;
+
+  assert (! dirmngr2_ctx_locked);
+  dirmngr2_ctx_locked = 1;
+
+  err = start_dirmngr_ext (ctrl, &dirmngr2_ctx);
+  if (!dirmngr2_ctx)
+    dirmngr2_ctx_locked = 0;
+  return err;
+}
+
+
+static void
+release_dirmngr2 (ctrl_t ctrl)
+{
+  (void)ctrl;
+
+  if (!dirmngr2_ctx_locked)
+    log_error ("WARNING: trying to release a non-locked dirmngr2 ctx\n");
+  dirmngr2_ctx_locked = 0;
 }
 
 
 \f
 /* Handle a SENDCERT inquiry. */
 }
 
 
 \f
 /* Handle a SENDCERT inquiry. */
-static AssuanError
+static gpg_error_t
 inq_certificate (void *opaque, const char *line)
 {
   struct inq_certificate_parm_s *parm = opaque;
 inq_certificate (void *opaque, const char *line)
 {
   struct inq_certificate_parm_s *parm = opaque;
-  AssuanError rc;
+  const char *s;
+  int rc;
+  size_t n;
   const unsigned char *der;
   size_t derlen;
   int issuer_mode = 0;
   const unsigned char *der;
   size_t derlen;
   int issuer_mode = 0;
+  ksba_sexp_t ski = NULL;
 
 
-  if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]))
+  if ((s = has_leading_keyword (line, "SENDCERT")))
     {
     {
-      line += 8;
+      line = s;
     }
     }
-  else if (!strncmp (line, "SENDISSUERCERT", 14)
-           && (line[14] == ' ' || !line[14]))
+  else if ((s = has_leading_keyword (line, "SENDCERT_SKI")))
     {
     {
-      line += 14;
+      /* Send a certificate where a sourceKeyIdentifier is included. */
+      line = s;
+      ski = make_simple_sexp_from_hexstr (line, &n);
+      line += n;
+      while (*line == ' ')
+        line++;
+    }
+  else if ((s = has_leading_keyword (line, "SENDISSUERCERT")))
+    {
+      line = s;
       issuer_mode = 1;
     }
       issuer_mode = 1;
     }
+  else if ((s = has_leading_keyword (line, "ISTRUSTED")))
+    {
+      /* The server is asking us whether the certificate is a trusted
+         root certificate.  */
+      char fpr[41];
+      struct rootca_flags_s rootca_flags;
+
+      line = s;
+
+      for (s=line,n=0; hexdigitp (s); s++, n++)
+        ;
+      if (*s || n != 40)
+        return gpg_error (GPG_ERR_ASS_PARAMETER);
+      for (s=line, n=0; n < 40; s++, n++)
+        fpr[n] = (*s >= 'a')? (*s & 0xdf): *s;
+      fpr[n] = 0;
+
+      if (!gpgsm_agent_istrusted (parm->ctrl, NULL, fpr, &rootca_flags))
+        rc = assuan_send_data (parm->ctx, "1", 1);
+      else
+        rc = 0;
+      return rc;
+    }
   else
     {
   else
     {
-      log_error ("unsupported inquiry `%s'\n", line);
-      return ASSUAN_Inquire_Unknown;
+      log_error ("unsupported inquiry '%s'\n", line);
+      return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
     }
 
   if (!*line)
     }
 
   if (!*line)
@@ -271,7 +352,7 @@ inq_certificate (void *opaque, const char *line)
       der = ksba_cert_get_image (issuer_mode? parm->issuer_cert : parm->cert,
                                  &derlen);
       if (!der)
       der = ksba_cert_get_image (issuer_mode? parm->issuer_cert : parm->cert,
                                  &derlen);
       if (!der)
-        rc = ASSUAN_Inquire_Error;
+        rc = gpg_error (GPG_ERR_INV_CERT_OBJ);
       else
         rc = assuan_send_data (parm->ctx, der, derlen);
     }
       else
         rc = assuan_send_data (parm->ctx, der, derlen);
     }
@@ -279,32 +360,33 @@ inq_certificate (void *opaque, const char *line)
     {
       log_error ("sending specific issuer certificate back "
                  "is not yet implemented\n");
     {
       log_error ("sending specific issuer certificate back "
                  "is not yet implemented\n");
-      rc = ASSUAN_Inquire_Error;
+      rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
     }
     }
-  else 
+  else
     { /* Send the given certificate. */
       int err;
       ksba_cert_t cert;
 
 
     { /* Send the given certificate. */
       int err;
       ksba_cert_t cert;
 
 
-      err = gpgsm_find_cert (line, &cert);
+      err = gpgsm_find_cert (line, ski, &cert);
       if (err)
         {
           log_error ("certificate not found: %s\n", gpg_strerror (err));
       if (err)
         {
           log_error ("certificate not found: %s\n", gpg_strerror (err));
-          rc = ASSUAN_Inquire_Error;
+          rc = gpg_error (GPG_ERR_NOT_FOUND);
         }
       else
         {
           der = ksba_cert_get_image (cert, &derlen);
           if (!der)
         }
       else
         {
           der = ksba_cert_get_image (cert, &derlen);
           if (!der)
-            rc = ASSUAN_Inquire_Error;
+            rc = gpg_error (GPG_ERR_INV_CERT_OBJ);
           else
             rc = assuan_send_data (parm->ctx, der, derlen);
           ksba_cert_release (cert);
         }
     }
 
           else
             rc = assuan_send_data (parm->ctx, der, derlen);
           ksba_cert_release (cert);
         }
     }
 
-  return rc; 
+  xfree (ski);
+  return rc;
 }
 
 
 }
 
 
@@ -327,16 +409,25 @@ unhexify_fpr (const char *hexstr, unsigned char *fpr)
 }
 
 
 }
 
 
-static assuan_error_t
+static gpg_error_t
 isvalid_status_cb (void *opaque, const char *line)
 {
   struct isvalid_status_parm_s *parm = opaque;
 isvalid_status_cb (void *opaque, const char *line)
 {
   struct isvalid_status_parm_s *parm = opaque;
+  const char *s;
 
 
-  if (!strncmp (line, "ONLY_VALID_IF_CERT_VALID", 24)
-      && (line[24]==' ' || !line[24]))
+  if ((s = has_leading_keyword (line, "PROGRESS")))
+    {
+      if (parm->ctrl)
+        {
+          line = s;
+          if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line))
+            return gpg_error (GPG_ERR_ASS_CANCELED);
+        }
+    }
+  else if ((s = has_leading_keyword (line, "ONLY_VALID_IF_CERT_VALID")))
     {
       parm->seen++;
     {
       parm->seen++;
-      if (!line[24] || !unhexify_fpr (line+25, parm->fpr))
+      if (!*s || !unhexify_fpr (s, parm->fpr))
         parm->seen++; /* Bumb it to indicate an error. */
     }
   return 0;
         parm->seen++; /* Bumb it to indicate an error. */
     }
   return 0;
@@ -352,20 +443,23 @@ isvalid_status_cb (void *opaque, const char *line)
   GPG_ERR_NO_CRL_KNOWN
   GPG_ERR_CRL_TOO_OLD
 
   GPG_ERR_NO_CRL_KNOWN
   GPG_ERR_CRL_TOO_OLD
 
-  With USE_OCSP set to true, the dirmngr is asked to do an OCSP
-  request first.
+  Values for USE_OCSP:
+     0 = Do CRL check.
+     1 = Do an OCSP check.
+     2 = Do an OCSP check using only the default responder.
  */
 int
 gpgsm_dirmngr_isvalid (ctrl_t ctrl,
                        ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp)
 {
  */
 int
 gpgsm_dirmngr_isvalid (ctrl_t ctrl,
                        ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp)
 {
+  static int did_options;
   int rc;
   char *certid;
   char line[ASSUAN_LINELENGTH];
   struct inq_certificate_parm_s parm;
   struct isvalid_status_parm_s stparm;
 
   int rc;
   char *certid;
   char line[ASSUAN_LINELENGTH];
   struct inq_certificate_parm_s parm;
   struct isvalid_status_parm_s stparm;
 
-  rc = start_dirmngr ();
+  rc = start_dirmngr (ctrl);
   if (rc)
     return rc;
 
   if (rc)
     return rc;
 
@@ -379,6 +473,7 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
       if (!certid)
         {
           log_error ("error getting the certificate ID\n");
       if (!certid)
         {
           log_error ("error getting the certificate ID\n");
+         release_dirmngr (ctrl);
           return gpg_error (GPG_ERR_GENERAL);
         }
     }
           return gpg_error (GPG_ERR_GENERAL);
         }
     }
@@ -392,17 +487,31 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
     }
 
   parm.ctx = dirmngr_ctx;
     }
 
   parm.ctx = dirmngr_ctx;
+  parm.ctrl = ctrl;
   parm.cert = cert;
   parm.issuer_cert = issuer_cert;
 
   parm.cert = cert;
   parm.issuer_cert = issuer_cert;
 
+  stparm.ctrl = ctrl;
   stparm.seen = 0;
   memset (stparm.fpr, 0, 20);
 
   /* FIXME: If --disable-crl-checks has been set, we should pass an
      option to dirmngr, so that no fallback CRL check is done after an
   stparm.seen = 0;
   memset (stparm.fpr, 0, 20);
 
   /* FIXME: If --disable-crl-checks has been set, we should pass an
      option to dirmngr, so that no fallback CRL check is done after an
-     ocsp check. */
+     ocsp check.  It is not a problem right now as dirmngr does not
+     fallback to CRL checking.  */
 
 
-  snprintf (line, DIM(line)-1, "ISVALID %s", certid);
+  /* It is sufficient to send the options only once because we have
+     one connection per process only. */
+  if (!did_options)
+    {
+      if (opt.force_crl_refresh)
+        assuan_transact (dirmngr_ctx, "OPTION force-crl-refresh=1",
+                         NULL, NULL, NULL, NULL, NULL, NULL);
+      did_options = 1;
+    }
+  snprintf (line, DIM(line)-1, "ISVALID%s %s",
+            use_ocsp == 2? " --only-ocsp --force-default-responder":"",
+            certid);
   line[DIM(line)-1] = 0;
   xfree (certid);
 
   line[DIM(line)-1] = 0;
   xfree (certid);
 
@@ -410,8 +519,8 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
                         inq_certificate, &parm,
                         isvalid_status_cb, &stparm);
   if (opt.verbose > 1)
                         inq_certificate, &parm,
                         isvalid_status_cb, &stparm);
   if (opt.verbose > 1)
-    log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay");
-  rc = map_assuan_err (rc);
+    log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay");
+  rc = rc;
 
   if (!rc && stparm.seen)
     {
 
   if (!rc && stparm.seen)
     {
@@ -423,38 +532,41 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
         }
       else
         {
         }
       else
         {
-          KEYDB_HANDLE kh;
           ksba_cert_t rspcert = NULL;
 
           ksba_cert_t rspcert = NULL;
 
-          /* Fixme: First try to get the certificate from the
-             dirmngr's cache - it should be there. */
-          kh = keydb_new (0);
-          if (!kh)
-            rc = gpg_error (GPG_ERR_ENOMEM);
-          if (!rc)
-            rc = keydb_search_fpr (kh, stparm.fpr);
-          if (!rc)
-            rc = keydb_get_cert (kh, &rspcert);
-          if (rc)
+          if (get_cached_cert (dirmngr_ctx, stparm.fpr, &rspcert))
             {
             {
-              log_error ("unable to find the certificate used "
-                         "by the dirmngr: %s\n", gpg_strerror (rc));
-              rc = gpg_error (GPG_ERR_INV_CRL);
+              /* Ooops: Something went wrong getting the certificate
+                 from the dirmngr.  Try our own cert store now.  */
+              KEYDB_HANDLE kh;
+
+              kh = keydb_new (0);
+              if (!kh)
+                rc = gpg_error (GPG_ERR_ENOMEM);
+              if (!rc)
+                rc = keydb_search_fpr (kh, stparm.fpr);
+              if (!rc)
+                rc = keydb_get_cert (kh, &rspcert);
+              if (rc)
+                {
+                  log_error ("unable to find the certificate used "
+                             "by the dirmngr: %s\n", gpg_strerror (rc));
+                  rc = gpg_error (GPG_ERR_INV_CRL);
+                }
+              keydb_release (kh);
             }
             }
-          keydb_release (kh);
 
           if (!rc)
             {
 
           if (!rc)
             {
-              /* fixme: We should refine the check to check for
-                 certificates allowed for CRL/OCPS. */
-              rc = gpgsm_cert_use_verify_p (rspcert);
+              rc = gpgsm_cert_use_ocsp_p (rspcert);
               if (rc)
                 rc = gpg_error (GPG_ERR_INV_CRL);
               else
                 {
               if (rc)
                 rc = gpg_error (GPG_ERR_INV_CRL);
               else
                 {
-                  /* Note, the flag = 1: This avoids checking this
-                     certificate over and over again. */
-                  rc = gpgsm_validate_chain (ctrl, rspcert, NULL, 0, NULL, 1);
+                  /* Note the no_dirmngr flag: This avoids checking
+                     this certificate over and over again. */
+                  rc = gpgsm_validate_chain (ctrl, rspcert, "", NULL, 0, NULL,
+                                             VALIDATE_FLAG_NO_DIRMNGR, NULL);
                   if (rc)
                     {
                       log_error ("invalid certificate used for CRL/OCSP: %s\n",
                   if (rc)
                     {
                       log_error ("invalid certificate used for CRL/OCSP: %s\n",
@@ -466,13 +578,14 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
           ksba_cert_release (rspcert);
         }
     }
           ksba_cert_release (rspcert);
         }
     }
+  release_dirmngr (ctrl);
   return rc;
 }
 
 
 \f
 /* Lookup helpers*/
   return rc;
 }
 
 
 \f
 /* Lookup helpers*/
-static AssuanError
+static gpg_error_t
 lookup_cb (void *opaque, const void *buffer, size_t length)
 {
   struct lookup_parm_s *parm = opaque;
 lookup_cb (void *opaque, const void *buffer, size_t length)
 {
   struct lookup_parm_s *parm = opaque;
@@ -521,9 +634,9 @@ lookup_cb (void *opaque, const void *buffer, size_t length)
 /* Return a properly escaped pattern from NAMES.  The only error
    return is NULL to indicate a malloc failure. */
 static char *
 /* Return a properly escaped pattern from NAMES.  The only error
    return is NULL to indicate a malloc failure. */
 static char *
-pattern_from_strlist (STRLIST names)
+pattern_from_strlist (strlist_t names)
 {
 {
-  STRLIST sl;
+  strlist_t sl;
   int n;
   const char *s;
   char *pattern, *p;
   int n;
   const char *s;
   char *pattern, *p;
@@ -542,7 +655,7 @@ pattern_from_strlist (STRLIST names)
   if (!pattern)
     return NULL;
 
   if (!pattern)
     return NULL;
 
-  for (n=0, sl=names; sl; sl = sl->next)
+  for (sl=names; sl; sl = sl->next)
     {
       for (s=sl->d; *s; s++)
         {
     {
       for (s=sl->d; *s; s++)
         {
@@ -574,21 +687,30 @@ pattern_from_strlist (STRLIST names)
     *pattern = 0; /* is empty */
   else
     p[-1] = '\0'; /* remove trailing blank */
     *pattern = 0; /* is empty */
   else
     p[-1] = '\0'; /* remove trailing blank */
-  
+
   return pattern;
 }
 
   return pattern;
 }
 
-static AssuanError
+static gpg_error_t
 lookup_status_cb (void *opaque, const char *line)
 {
   struct lookup_parm_s *parm = opaque;
 lookup_status_cb (void *opaque, const char *line)
 {
   struct lookup_parm_s *parm = opaque;
+  const char *s;
 
 
-  if (!strncmp (line, "TRUNCATED", 9) && (line[9]==' ' || !line[9]))
+  if ((s = has_leading_keyword (line, "PROGRESS")))
     {
       if (parm->ctrl)
         {
     {
       if (parm->ctrl)
         {
-          for (line +=9; *line == ' '; line++)
-            ;
+          line = s;
+          if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line))
+            return gpg_error (GPG_ERR_ASS_CANCELED);
+        }
+    }
+  else if ((s = has_leading_keyword (line, "TRUNCATED")))
+    {
+      if (parm->ctrl)
+        {
+          line = s;
           gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line);
         }
     }
           gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line);
         }
     }
@@ -596,54 +718,154 @@ lookup_status_cb (void *opaque, const char *line)
 }
 
 
 }
 
 
-/* Run the Directroy Managers lookup command using the pattern
+/* Run the Directory Manager's lookup command using the pattern
    compiled from the strings given in NAMES.  The caller must provide
    the callback CB which will be passed cert by cert.  Note that CTRL
    compiled from the strings given in NAMES.  The caller must provide
    the callback CB which will be passed cert by cert.  Note that CTRL
-   is optional. */
-int 
-gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names,
+   is optional.  With CACHE_ONLY the dirmngr will search only its own
+   key cache. */
+int
+gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, int cache_only,
                       void (*cb)(void*, ksba_cert_t), void *cb_value)
                       void (*cb)(void*, ksba_cert_t), void *cb_value)
-{ 
+{
   int rc;
   char *pattern;
   char line[ASSUAN_LINELENGTH];
   struct lookup_parm_s parm;
   size_t len;
   int rc;
   char *pattern;
   char line[ASSUAN_LINELENGTH];
   struct lookup_parm_s parm;
   size_t len;
+  assuan_context_t ctx;
 
 
-  rc = start_dirmngr ();
-  if (rc)
-    return rc;
+  /* The lookup function can be invoked from the callback of a lookup
+     function, for example to walk the chain.  */
+  if (!dirmngr_ctx_locked)
+    {
+      rc = start_dirmngr (ctrl);
+      if (rc)
+       return rc;
+      ctx = dirmngr_ctx;
+    }
+  else if (!dirmngr2_ctx_locked)
+    {
+      rc = start_dirmngr2 (ctrl);
+      if (rc)
+       return rc;
+      ctx = dirmngr2_ctx;
+    }
+  else
+    {
+      log_fatal ("both dirmngr contexts are in use\n");
+    }
 
   pattern = pattern_from_strlist (names);
   if (!pattern)
 
   pattern = pattern_from_strlist (names);
   if (!pattern)
-    return OUT_OF_CORE (errno);
-  snprintf (line, DIM(line)-1, "LOOKUP %s", pattern);
+    {
+      if (ctx == dirmngr_ctx)
+       release_dirmngr (ctrl);
+      else
+       release_dirmngr2 (ctrl);
+
+      return out_of_core ();
+    }
+  snprintf (line, DIM(line)-1, "LOOKUP%s %s",
+            cache_only? " --cache-only":"", pattern);
   line[DIM(line)-1] = 0;
   xfree (pattern);
 
   parm.ctrl = ctrl;
   line[DIM(line)-1] = 0;
   xfree (pattern);
 
   parm.ctrl = ctrl;
-  parm.ctx = dirmngr_ctx;
+  parm.ctx = ctx;
   parm.cb = cb;
   parm.cb_value = cb_value;
   parm.error = 0;
   init_membuf (&parm.data, 4096);
 
   parm.cb = cb;
   parm.cb_value = cb_value;
   parm.error = 0;
   init_membuf (&parm.data, 4096);
 
-  rc = assuan_transact (dirmngr_ctx, line, lookup_cb, &parm,
+  rc = assuan_transact (ctx, line, lookup_cb, &parm,
                         NULL, NULL, lookup_status_cb, &parm);
   xfree (get_membuf (&parm.data, &len));
                         NULL, NULL, lookup_status_cb, &parm);
   xfree (get_membuf (&parm.data, &len));
+
+  if (ctx == dirmngr_ctx)
+    release_dirmngr (ctrl);
+  else
+    release_dirmngr2 (ctrl);
+
   if (rc)
   if (rc)
-    return map_assuan_err (rc);
+      return rc;
   return parm.error;
 }
 
 
 \f
   return parm.error;
 }
 
 
 \f
+static gpg_error_t
+get_cached_cert_data_cb (void *opaque, const void *buffer, size_t length)
+{
+  struct membuf *mb = opaque;
+
+  if (buffer)
+    put_membuf (mb, buffer, length);
+  return 0;
+}
+
+/* Return a certificate from the Directory Manager's cache.  This
+   function only returns one certificate which must be specified using
+   the fingerprint FPR and will be stored at R_CERT.  On error NULL is
+   stored at R_CERT and an error code returned.  Note that the caller
+   must provide the locked dirmngr context CTX. */
+static gpg_error_t
+get_cached_cert (assuan_context_t ctx,
+                 const unsigned char *fpr, ksba_cert_t *r_cert)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  char hexfpr[2*20+1];
+  struct membuf mb;
+  char *buf;
+  size_t buflen = 0;
+  ksba_cert_t cert;
+
+  *r_cert = NULL;
+
+  bin2hex (fpr, 20, hexfpr);
+  snprintf (line, DIM(line)-1, "LOOKUP --single --cache-only 0x%s", hexfpr);
+
+  init_membuf (&mb, 4096);
+  err = assuan_transact (ctx, line, get_cached_cert_data_cb, &mb,
+                         NULL, NULL, NULL, NULL);
+  buf = get_membuf (&mb, &buflen);
+  if (err)
+    {
+      xfree (buf);
+      return err;
+    }
+  if (!buf)
+    return gpg_error (GPG_ERR_ENOMEM);
+
+  err = ksba_cert_new (&cert);
+  if (err)
+    {
+      xfree (buf);
+      return err;
+    }
+  err = ksba_cert_init_from_mem (cert, buf, buflen);
+  xfree (buf);
+  if (err)
+    {
+      log_error ("failed to parse a certificate: %s\n", gpg_strerror (err));
+      ksba_cert_release (cert);
+      return err;
+    }
+
+  *r_cert = cert;
+  return 0;
+}
+
+
+\f
 /* Run Command helpers*/
 
 /* Fairly simple callback to write all output of dirmngr to stdout. */
 /* Run Command helpers*/
 
 /* Fairly simple callback to write all output of dirmngr to stdout. */
-static AssuanError
+static gpg_error_t
 run_command_cb (void *opaque, const void *buffer, size_t length)
 {
 run_command_cb (void *opaque, const void *buffer, size_t length)
 {
+  (void)opaque;
+
   if (buffer)
     {
       if ( fwrite (buffer, length, 1, stdout) != 1 )
   if (buffer)
     {
       if ( fwrite (buffer, length, 1, stdout) != 1 )
@@ -653,60 +875,73 @@ run_command_cb (void *opaque, const void *buffer, size_t length)
 }
 
 /* Handle inquiries from the dirmngr COMMAND. */
 }
 
 /* Handle inquiries from the dirmngr COMMAND. */
-static AssuanError
+static gpg_error_t
 run_command_inq_cb (void *opaque, const char *line)
 {
   struct run_command_parm_s *parm = opaque;
 run_command_inq_cb (void *opaque, const char *line)
 {
   struct run_command_parm_s *parm = opaque;
-  AssuanError rc = 0;
+  const char *s;
+  int rc = 0;
 
 
-  if ( !strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]) )
+  if ((s = has_leading_keyword (line, "SENDCERT")))
     { /* send the given certificate */
       int err;
       ksba_cert_t cert;
       const unsigned char *der;
       size_t derlen;
 
     { /* send the given certificate */
       int err;
       ksba_cert_t cert;
       const unsigned char *der;
       size_t derlen;
 
-      line += 8;
+      line = s;
       if (!*line)
       if (!*line)
-        return ASSUAN_Inquire_Error;
+        return gpg_error (GPG_ERR_ASS_PARAMETER);
 
 
-      err = gpgsm_find_cert (line, &cert);
+      err = gpgsm_find_cert (line, NULL, &cert);
       if (err)
         {
           log_error ("certificate not found: %s\n", gpg_strerror (err));
       if (err)
         {
           log_error ("certificate not found: %s\n", gpg_strerror (err));
-          rc = ASSUAN_Inquire_Error;
+          rc = gpg_error (GPG_ERR_NOT_FOUND);
         }
       else
         {
           der = ksba_cert_get_image (cert, &derlen);
           if (!der)
         }
       else
         {
           der = ksba_cert_get_image (cert, &derlen);
           if (!der)
-            rc = ASSUAN_Inquire_Error;
+            rc = gpg_error (GPG_ERR_INV_CERT_OBJ);
           else
             rc = assuan_send_data (parm->ctx, der, derlen);
           ksba_cert_release (cert);
         }
     }
           else
             rc = assuan_send_data (parm->ctx, der, derlen);
           ksba_cert_release (cert);
         }
     }
-  else if ( !strncmp (line, "PRINTINFO", 9) && (line[9] == ' ' || !line[9]) )
+  else if ((s = has_leading_keyword (line, "PRINTINFO")))
     { /* Simply show the message given in the argument. */
     { /* Simply show the message given in the argument. */
-      line += 9;
+      line = s;
       log_info ("dirmngr: %s\n", line);
     }
   else
     {
       log_info ("dirmngr: %s\n", line);
     }
   else
     {
-      log_error ("unsupported inquiry `%s'\n", line);
-      rc = ASSUAN_Inquire_Unknown;
+      log_error ("unsupported inquiry '%s'\n", line);
+      rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
     }
 
     }
 
-  return rc; 
+  return rc;
 }
 
 }
 
-static AssuanError
+static gpg_error_t
 run_command_status_cb (void *opaque, const char *line)
 {
 run_command_status_cb (void *opaque, const char *line)
 {
+  ctrl_t ctrl = opaque;
+  const char *s;
+
   if (opt.verbose)
     {
       log_info ("dirmngr status: %s\n", line);
     }
   if (opt.verbose)
     {
       log_info ("dirmngr status: %s\n", line);
     }
+  if ((s = has_leading_keyword (line, "PROGRESS")))
+    {
+      if (ctrl)
+        {
+          line = s;
+          if (gpgsm_status (ctrl, STATUS_PROGRESS, line))
+            return gpg_error (GPG_ERR_ASS_CANCELED);
+        }
+    }
   return 0;
 }
 
   return 0;
 }
 
@@ -718,9 +953,9 @@ run_command_status_cb (void *opaque, const char *line)
    percent characters within the argument strings are percent escaped
    so that blanks can act as delimiters. */
 int
    percent characters within the argument strings are percent escaped
    so that blanks can act as delimiters. */
 int
-gpgsm_dirmngr_run_command (CTRL ctrl, const char *command,
+gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command,
                            int argc, char **argv)
                            int argc, char **argv)
-{ 
+{
   int rc;
   int i;
   const char *s;
   int rc;
   int i;
   const char *s;
@@ -728,7 +963,7 @@ gpgsm_dirmngr_run_command (CTRL ctrl, const char *command,
   size_t len;
   struct run_command_parm_s parm;
 
   size_t len;
   struct run_command_parm_s parm;
 
-  rc = start_dirmngr ();
+  rc = start_dirmngr (ctrl);
   if (rc)
     return rc;
 
   if (rc)
     return rc;
 
@@ -739,7 +974,10 @@ gpgsm_dirmngr_run_command (CTRL ctrl, const char *command,
     len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */
   line = xtrymalloc (len);
   if (!line)
     len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */
   line = xtrymalloc (len);
   if (!line)
-    return OUT_OF_CORE (errno);
+    {
+      release_dirmngr (ctrl);
+      return out_of_core ();
+    }
 
   p = stpcpy (line, command);
   for (i=0; i < argc; i++)
 
   p = stpcpy (line, command);
   for (i=0; i < argc; i++)
@@ -753,7 +991,7 @@ gpgsm_dirmngr_run_command (CTRL ctrl, const char *command,
             *p++ = '+';
           else if (!isprint (*s) || *s == '+')
             {
             *p++ = '+';
           else if (!isprint (*s) || *s == '+')
             {
-              sprintf (p, "%%%02X", *s);
+              sprintf (p, "%%%02X", *(const unsigned char *)s);
               p += 3;
             }
           else
               p += 3;
             }
           else
@@ -765,8 +1003,9 @@ gpgsm_dirmngr_run_command (CTRL ctrl, const char *command,
   rc = assuan_transact (dirmngr_ctx, line,
                         run_command_cb, NULL,
                         run_command_inq_cb, &parm,
   rc = assuan_transact (dirmngr_ctx, line,
                         run_command_cb, NULL,
                         run_command_inq_cb, &parm,
-                        run_command_status_cb, NULL);
+                        run_command_status_cb, ctrl);
   xfree (line);
   xfree (line);
-  log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay");
-  return map_assuan_err (rc);
+  log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay");
+  release_dirmngr (ctrl);
+  return rc;
 }
 }