Print status of CRL checks in the audit log.
[gnupg.git] / sm / call-dirmngr.c
index a4b6fbc..33aebdf 100644 (file)
@@ -1,5 +1,5 @@
 /* call-dirmngr.c - communication with the dromngr 
- *     Copyright (C) 2002, 2003, 2005, 2007 Free Software Foundation, Inc.
+ * Copyright (C) 2002, 2003, 2005, 2007, 2008 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -44,10 +44,18 @@ struct membuf {
 
 
 
+/* 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;
+
 static int force_pipe_server = 0;
 
 struct inq_certificate_parm_s {
+  ctrl_t ctrl;
   assuan_context_t ctx;
   ksba_cert_t cert;
   ksba_cert_t issuer_cert;
@@ -134,23 +142,40 @@ get_membuf (struct membuf *mb, size_t *len)
 }
 
 
-/* This fucntion prepares the dirmngr for a new session.  The
+/* 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)
 {
-  if (!ctrl->dirmngr_seen)
+  struct keyserver_spec *server;
+
+  if (!err)
     {
-      ctrl->dirmngr_seen = 1;
-      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);
+      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);
+
+  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;
     }
 }
 
@@ -159,19 +184,19 @@ prepare_dirmngr (ctrl_t ctrl, assuan_context_t ctx, gpg_error_t err)
 /* 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 (ctrl_t ctrl)
+start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r)
 {
   int rc;
   char *infostr, *p;
   assuan_context_t ctx;
   int try_default = 0;
 
-  if (dirmngr_ctx)
-    {
-      prepare_dirmngr (ctrl, dirmngr_ctx, 0);
-      return 0; /* fixme: We need a context for each thread or serialize
-                   the access to the dirmngr */
-    }
+  if (opt.disable_dirmngr)
+    return gpg_error (GPG_ERR_NO_DIRMNGR);
+
+  if (*ctx_r)
+    return 0;
+
   /* Note: if you change this to multiple connections, you also need
      to take care of the implicit option sending caching. */
 
@@ -242,7 +267,7 @@ start_dirmngr (ctrl_t ctrl)
               log_error (_("malformed DIRMNGR_INFO environment variable\n"));
               xfree (infostr);
               force_pipe_server = 1;
-              return start_dirmngr (ctrl);
+              return start_dirmngr_ext (ctrl, ctx_r);
             }
           *p++ = 0;
           pid = atoi (p);
@@ -255,7 +280,7 @@ start_dirmngr (ctrl_t ctrl)
                          prot);
               xfree (infostr);
               force_pipe_server = 1;
-              return start_dirmngr (ctrl);
+              return start_dirmngr_ext (ctrl, ctx_r);
             }
         }
       else
@@ -271,9 +296,9 @@ start_dirmngr (ctrl_t ctrl)
 #ifndef HAVE_W32_SYSTEM
       if (gpg_err_code (rc) == GPG_ERR_ASS_CONNECT_FAILED)
         {
-          log_error (_("can't connect to the dirmngr - trying fall back\n"));
+          log_info (_("can't connect to the dirmngr - trying fall back\n"));
           force_pipe_server = 1;
-          return start_dirmngr (ctrl);
+          return start_dirmngr_ext (ctrl, ctx_r);
         }
 #endif /*!HAVE_W32_SYSTEM*/
     }
@@ -285,7 +310,7 @@ start_dirmngr (ctrl_t ctrl)
       log_error ("can't connect to the dirmngr: %s\n", gpg_strerror (rc));
       return gpg_error (GPG_ERR_NO_DIRMNGR);
     }
-  dirmngr_ctx = ctx;
+  *ctx_r = ctx;
 
   if (DBG_ASSUAN)
     log_debug ("connection to dirmngr established\n");
@@ -293,6 +318,62 @@ start_dirmngr (ctrl_t ctrl)
 }
 
 
+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;  
+}
+
+
+static void
+release_dirmngr (ctrl_t ctrl)
+{
+  (void)ctrl;
+
+  if (!dirmngr_ctx_locked)
+    log_error ("WARNING: trying to release a non-locked dirmngr ctx\n");
+  dirmngr_ctx_locked = 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. */
 static int
@@ -328,6 +409,33 @@ inq_certificate (void *opaque, const char *line)
       line += 14;
       issuer_mode = 1;
     }
+  else if (!strncmp (line, "ISTRUSTED", 9) && (line[9]==' ' || !line[9]))
+    {
+      /* The server is asking us whether the certificate is a trusted
+         root certificate.  */
+      const char *s;
+      size_t n;
+      char fpr[41];
+      struct rootca_flags_s rootca_flags;
+
+      line += 9;
+      while (*line == ' ')
+        line++;
+
+      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
     {
       log_error ("unsupported inquiry `%s'\n", line);
@@ -447,7 +555,6 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
   struct inq_certificate_parm_s parm;
   struct isvalid_status_parm_s stparm;
 
-
   rc = start_dirmngr (ctrl);
   if (rc)
     return rc;
@@ -462,6 +569,7 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
       if (!certid)
         {
           log_error ("error getting the certificate ID\n");
+         release_dirmngr (ctrl);
           return gpg_error (GPG_ERR_GENERAL);
         }
     }
@@ -475,6 +583,7 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
     }
 
   parm.ctx = dirmngr_ctx;
+  parm.ctrl = ctrl;
   parm.cert = cert;
   parm.issuer_cert = issuer_cert;
 
@@ -561,6 +670,7 @@ gpgsm_dirmngr_isvalid (ctrl_t ctrl,
           ksba_cert_release (rspcert);
         }
     }
+  release_dirmngr (ctrl);
   return rc;
 }
 
@@ -637,7 +747,7 @@ pattern_from_strlist (strlist_t names)
   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++)
         {
@@ -701,12 +811,13 @@ 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
-   is optional. */
+   is optional.  With CACHE_ONLY the dirmngr will search only its own
+   key cache. */
 int 
-gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names,
+gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, int cache_only,
                       void (*cb)(void*, ksba_cert_t), void *cb_value)
 { 
   int rc;
@@ -714,30 +825,62 @@ gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names,
   char line[ASSUAN_LINELENGTH];
   struct lookup_parm_s parm;
   size_t len;
+  assuan_context_t ctx;
 
-  rc = start_dirmngr (ctrl);
-  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)
-    return out_of_core ();
-  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;
-  parm.ctx = dirmngr_ctx;
+  parm.ctx = ctx;
   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));
+
+  if (ctx == dirmngr_ctx)
+    release_dirmngr (ctrl);
+  else
+    release_dirmngr2 (ctrl);
+
   if (rc)
-    return rc;
+      return rc;
   return parm.error;
 }
 
@@ -749,6 +892,8 @@ gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names,
 static int
 run_command_cb (void *opaque, const void *buffer, size_t length)
 {
+  (void)opaque;
+
   if (buffer)
     {
       if ( fwrite (buffer, length, 1, stdout) != 1 )
@@ -856,7 +1001,10 @@ gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command,
     len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */
   line = xtrymalloc (len);
   if (!line)
-    return out_of_core ();
+    {
+      release_dirmngr (ctrl);
+      return out_of_core ();
+    }
 
   p = stpcpy (line, command);
   for (i=0; i < argc; i++)
@@ -885,5 +1033,6 @@ gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command,
                         run_command_status_cb, ctrl);
   xfree (line);
   log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay");
+  release_dirmngr (ctrl);
   return rc;
 }