gpg: Consistent use of preprocessor conditionals.
[gnupg.git] / g10 / call-dirmngr.c
index 09ade4e..aa39155 100644 (file)
@@ -1,5 +1,6 @@
 /* call-dirmngr.c - GPG operations to the Dirmngr.
  * Copyright (C) 2011 Free Software Foundation, Inc.
+ * Copyright (C) 2015  g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
@@ -14,7 +15,7 @@
  * 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, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <errno.h>
 #include <unistd.h>
 #include <time.h>
-#include <assert.h>
 #ifdef HAVE_LOCALE_H
 # include <locale.h>
 #endif
 
 #include "gpg.h"
 #include <assuan.h>
-#include "util.h"
-#include "membuf.h"
+#include "../common/util.h"
+#include "../common/membuf.h"
 #include "options.h"
-#include "i18n.h"
-#include "asshelp.h"
-#include "keyserver.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "../common/keyserver.h"
+#include "../common/status.h"
 #include "call-dirmngr.h"
 
 
+/* Parameter structure used to gather status info.  */
+struct ks_status_parm_s
+{
+  const char *keyword; /* Look for this keyword or NULL for "SOURCE". */
+  char *source;
+};
+
+
 /* Parameter structure used with the KS_SEARCH command.  */
 struct ks_search_parm_s
 {
@@ -47,8 +56,9 @@ struct ks_search_parm_s
   membuf_t saveddata;   /* Buffer to build complete lines.  */
   char *helpbuf;        /* NULL or malloced buffer.  */
   size_t helpbufsize;   /* Allocated size of HELPBUF.  */
-  gpg_error_t (*data_cb)(void*, char*);  /* Callback.  */
+  gpg_error_t (*data_cb)(void*, int, char*);  /* Callback.  */
   void *data_cb_value;  /* First argument for DATA_CB.  */
+  struct ks_status_parm_s *stparm; /* Link to the status parameter.  */
 };
 
 
@@ -69,12 +79,22 @@ struct ks_put_parm_s
 };
 
 
+/* Parameter structure used with the DNS_CERT command.  */
+struct dns_cert_parm_s
+{
+  estream_t memfp;
+  unsigned char *fpr;
+  size_t fprlen;
+  char *url;
+};
+
+
 /* Data used to associate an session with dirmngr contexts.  We can't
    use a simple one to one mapping because we sometimes need two
-   connection s to the dirmngr; for example while doing a listing and
+   connections to the dirmngr; for example while doing a listing and
    being in a data callback we may want to retrieve a key.  The local
    dirmngr data takes care of this.  At the end of the session the
-   function dirmngr_deinit_session_data is called bu gpg.c to cleanup
+   function dirmngr_deinit_session_data is called by gpg.c to cleanup
    these resources.  Note that gpg.h defines a typedef dirmngr_local_t
    for this structure. */
 struct dirmngr_local_s
@@ -85,6 +105,9 @@ struct dirmngr_local_s
   /* The active Assuan context. */
   assuan_context_t ctx;
 
+  /* Flag set when the keyserver names have been send.  */
+  int set_keyservers_done;
+
   /* Flag set to true while an operation is running on CTX.  */
   int is_active;
 };
@@ -109,9 +132,42 @@ gpg_dirmngr_deinit_session_data (ctrl_t ctrl)
 }
 
 
-/* Try to connect to the Dirmngr via a socket or fork it off if
-   possible.  Handle the server's initial greeting and set global
-   options.  */
+/* Print a warning if the server's version number is less than our
+   version number.  Returns an error code on a connection problem.  */
+static gpg_error_t
+warn_version_mismatch (assuan_context_t ctx, const char *servername)
+{
+  gpg_error_t err;
+  char *serverversion;
+  const char *myversion = strusage (13);
+
+  err = get_assuan_server_version (ctx, 0, &serverversion);
+  if (err)
+    log_error (_("error getting version from '%s': %s\n"),
+               servername, gpg_strerror (err));
+  else if (compare_version_strings (serverversion, myversion) < 0)
+    {
+      char *warn;
+
+      warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
+                           servername, serverversion, myversion);
+      if (!warn)
+        err = gpg_error_from_syserror ();
+      else
+        {
+          log_info (_("WARNING: %s\n"), warn);
+          write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
+                                " ", warn, NULL);
+          xfree (warn);
+        }
+    }
+  xfree (serverversion);
+  return err;
+}
+
+
+/* Try to connect to the Dirmngr via a socket or spawn it if possible.
+   Handle the server's initial greeting and set global options.  */
 static gpg_error_t
 create_context (ctrl_t ctrl, assuan_context_t *r_ctx)
 {
@@ -121,36 +177,55 @@ create_context (ctrl_t ctrl, assuan_context_t *r_ctx)
   *r_ctx = NULL;
   err = start_new_dirmngr (&ctx,
                            GPG_ERR_SOURCE_DEFAULT,
-                           opt.homedir,
-                           NULL,
-                           opt.verbose, DBG_ASSUAN,
+                           opt.dirmngr_program,
+                           opt.autostart, opt.verbose, DBG_IPC,
                            NULL /*gpg_status2*/, ctrl);
-  if (!err)
+  if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR)
+    {
+      static int shown;
+
+      if (!shown)
+        {
+          shown = 1;
+          log_info (_("no dirmngr running in this session\n"));
+        }
+    }
+  else if (!err && !(err = warn_version_mismatch (ctx, DIRMNGR_NAME)))
     {
-      keyserver_spec_t ksi;
+      char *line;
 
       /* Tell the dirmngr that we want to collect audit event. */
       /* err = assuan_transact (agent_ctx, "OPTION audit-events=1", */
       /*                        NULL, NULL, NULL, NULL, NULL, NULL); */
-
-      /* Set all configured keyservers.  We clear existing keyservers
-         so that any keyserver configured in GPG overrides keyservers
-         possibly configured in Dirmngr. */
-      for (ksi = opt.keyserver; !err && ksi; ksi = ksi->next)
+      if (opt.keyserver_options.http_proxy)
         {
-          char *line;
-
-          line = xtryasprintf ("KEYSERVER%s %s",
-                               ksi == opt.keyserver? " --clear":"", ksi->uri);
+          line = xtryasprintf ("OPTION http-proxy=%s",
+                               opt.keyserver_options.http_proxy);
           if (!line)
             err = gpg_error_from_syserror ();
           else
             {
-              err = assuan_transact (ctx, line,
-                                     NULL, NULL, NULL, NULL, NULL, NULL);
+              err = assuan_transact (ctx, line, NULL, NULL, NULL,
+                                     NULL, NULL, NULL);
               xfree (line);
             }
         }
+
+      if (err)
+        ;
+      else if ((opt.keyserver_options.options & KEYSERVER_HONOR_KEYSERVER_URL))
+        {
+          /* Tell the dirmngr that this possibly privacy invading
+             option is in use.  If Dirmngr is running in Tor mode, it
+             will return an error.  */
+          err = assuan_transact (ctx, "OPTION honor-keyserver-url-used",
+                                 NULL, NULL, NULL, NULL, NULL, NULL);
+          if (gpg_err_code (err) == GPG_ERR_FORBIDDEN)
+            log_error (_("keyserver option \"honor-keyserver-url\""
+                         " may not be used in Tor mode\n"));
+          else if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
+            err = 0; /* Old dirmngr versions do not support this option.  */
+        }
     }
 
   if (err)
@@ -166,8 +241,8 @@ create_context (ctrl_t ctrl, assuan_context_t *r_ctx)
 
 
 /* Get a context for accessing dirmngr.  If no context is available a
-   new one is created and - if requred - dirmngr started.  On success
-   an assuan context is stored at R_CTX.  This Context may only be
+   new one is created and - if required - dirmngr started.  On success
+   an assuan context is stored at R_CTX.  This context may only be
    released by means of close_context.  Note that NULL is stored at
    R_CTX on error.  */
 static gpg_error_t
@@ -184,8 +259,43 @@ open_context (ctrl_t ctrl, assuan_context_t *r_ctx)
       if (dml)
         {
           /* Found an inactive local session - return that.  */
-          assert (!dml->is_active);
+          log_assert (!dml->is_active);
+
+          /* But first do the per session init if not yet done.  */
+          if (!dml->set_keyservers_done)
+            {
+              keyserver_spec_t ksi;
+
+              /* Set all configured keyservers.  We clear existing
+                 keyservers so that any keyserver configured in GPG
+                 overrides keyservers possibly still configured in Dirmngr
+                 for the session (Note that the keyserver list of a
+                 session in Dirmngr survives a RESET. */
+              for (ksi = opt.keyserver; ksi; ksi = ksi->next)
+                {
+                  char *line;
+
+                  line = xtryasprintf
+                    ("KEYSERVER%s %s",
+                     ksi == opt.keyserver? " --clear":"", ksi->uri);
+                  if (!line)
+                    err = gpg_error_from_syserror ();
+                  else
+                    {
+                      err = assuan_transact (dml->ctx, line, NULL, NULL, NULL,
+                                             NULL, NULL, NULL);
+                      xfree (line);
+                    }
+
+                  if (err)
+                    return err;
+                }
+
+              dml->set_keyservers_done = 1;
+            }
+
           dml->is_active = 1;
+
           *r_ctx = dml->ctx;
           return 0;
         }
@@ -199,7 +309,8 @@ open_context (ctrl_t ctrl, assuan_context_t *r_ctx)
           xfree (dml);
           return err;
         }
-      /* To be on the Pth thread safe site we need to add it to a
+
+      /* To be on the nPth thread safe site we need to add it to a
          list; this is far easier than to have a lock for this
          function.  It should not happen anyway but the code is free
          because we need it for the is_active check above.  */
@@ -233,6 +344,118 @@ close_context (ctrl_t ctrl, assuan_context_t ctx)
 }
 
 
+/* Clear the set_keyservers_done flag on context CTX.  */
+static void
+clear_context_flags (ctrl_t ctrl, assuan_context_t ctx)
+{
+  dirmngr_local_t dml;
+
+  if (!ctx)
+    return;
+
+  for (dml = ctrl->dirmngr_local; dml; dml = dml->next)
+    {
+      if (dml->ctx == ctx)
+        {
+          if (!dml->is_active)
+            log_fatal ("clear_context_flags on inactive dirmngr ctx %p\n", ctx);
+          dml->set_keyservers_done = 0;
+          return;
+        }
+    }
+  log_fatal ("clear_context_flags on unknown dirmngr ctx %p\n", ctx);
+}
+
+
+\f
+/* Status callback for ks_list, ks_get and ks_search.  */
+static gpg_error_t
+ks_status_cb (void *opaque, const char *line)
+{
+  struct ks_status_parm_s *parm = opaque;
+  gpg_error_t err = 0;
+  const char *s, *s2;
+  const char *warn;
+
+  if ((s = has_leading_keyword (line, parm->keyword? parm->keyword : "SOURCE")))
+    {
+      if (!parm->source)
+        {
+          parm->source = xtrystrdup (s);
+          if (!parm->source)
+            err = gpg_error_from_syserror ();
+        }
+    }
+  else if ((s = has_leading_keyword (line, "WARNING")))
+    {
+      if ((s2 = has_leading_keyword (s, "tor_not_running")))
+        warn = _("Tor is not running");
+      else if ((s2 = has_leading_keyword (s, "tor_config_problem")))
+        warn = _("Tor is not properly configured");
+      else
+        warn = NULL;
+
+      if (warn)
+        {
+          log_info (_("WARNING: %s\n"), warn);
+          if (s2)
+            {
+              while (*s2 && !spacep (s2))
+                s2++;
+              while (*s2 && spacep (s2))
+                s2++;
+              if (*s2)
+                print_further_info ("%s", s2);
+            }
+        }
+    }
+
+  return err;
+}
+
+
+\f
+/* Run the "KEYSERVER" command to return the name of the used
+   keyserver at R_KEYSERVER.  */
+gpg_error_t
+gpg_dirmngr_ks_list (ctrl_t ctrl, char **r_keyserver)
+{
+  gpg_error_t err;
+  assuan_context_t ctx;
+  struct ks_status_parm_s stparm;
+
+  memset (&stparm, 0, sizeof stparm);
+  stparm.keyword = "KEYSERVER";
+  if (r_keyserver)
+    *r_keyserver = NULL;
+
+  err = open_context (ctrl, &ctx);
+  if (err)
+    return err;
+
+  err = assuan_transact (ctx, "KEYSERVER", NULL, NULL,
+                         NULL, NULL, ks_status_cb, &stparm);
+  if (err)
+    goto leave;
+  if (!stparm.source)
+    {
+      err = gpg_error (GPG_ERR_NO_KEYSERVER);
+      goto leave;
+    }
+
+  if (r_keyserver)
+    *r_keyserver = stparm.source;
+  else
+    xfree (stparm.source);
+  stparm.source = NULL;
+
+ leave:
+  xfree (stparm.source);
+  close_context (ctrl, ctx);
+  return err;
+}
+
+
 \f
 /* Data callback for the KS_SEARCH command. */
 static gpg_error_t
@@ -247,6 +470,22 @@ ks_search_data_cb (void *opaque, const void *data, size_t datalen)
   if (parm->lasterr)
     return 0;
 
+  if (parm->stparm->source)
+    {
+      err = parm->data_cb (parm->data_cb_value, 1, parm->stparm->source);
+      if (err)
+        {
+          parm->lasterr = err;
+          return err;
+        }
+      /* Clear it so that we won't get back here unless the server
+         accidentally sends a second source status line.  Note that
+         will not see all accidentally sent source lines because it
+         depends on whether data lines have been send in between.  */
+      xfree (parm->stparm->source);
+      parm->stparm->source = NULL;
+    }
+
   if (!data)
     return 0;  /* Ignore END commands.  */
 
@@ -269,7 +508,7 @@ ks_search_data_cb (void *opaque, const void *data, size_t datalen)
           fixedbuf[linelen] = 0;
           if (linelen && fixedbuf[linelen-1] == '\r')
             fixedbuf[linelen-1] = 0;
-          err = parm->data_cb (parm->data_cb_value, fixedbuf);
+          err = parm->data_cb (parm->data_cb_value, 0, fixedbuf);
         }
       else
         {
@@ -288,7 +527,7 @@ ks_search_data_cb (void *opaque, const void *data, size_t datalen)
           parm->helpbuf[linelen] = 0;
           if (linelen && parm->helpbuf[linelen-1] == '\r')
             parm->helpbuf[linelen-1] = 0;
-          err = parm->data_cb (parm->data_cb_value, parm->helpbuf);
+          err = parm->data_cb (parm->data_cb_value, 0, parm->helpbuf);
         }
       if (err)
         parm->lasterr = err;
@@ -305,17 +544,18 @@ ks_search_data_cb (void *opaque, const void *data, size_t datalen)
 
 /* Run the KS_SEARCH command using the search string SEARCHSTR.  All
    data lines are passed to the CB function.  That function is called
-   with CB_VALUE as its first argument and the decoded data line as
-   second argument.  The callback function may modify the data line
-   and it is guaranteed that this data line is a complete line with a
-   terminating 0 character but without the linefeed.  NULL is passed
-   to the callback to indicate EOF.  */
+   with CB_VALUE as its first argument, a 0 as second argument, and
+   the decoded data line as third argument.  The callback function may
+   modify the data line and it is guaranteed that this data line is a
+   complete line with a terminating 0 character but without the
+   linefeed.  NULL is passed to the callback to indicate EOF.  */
 gpg_error_t
 gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr,
-                       gpg_error_t (*cb)(void*, char *), void *cb_value)
+                       gpg_error_t (*cb)(void*, int, char *), void *cb_value)
 {
   gpg_error_t err;
   assuan_context_t ctx;
+  struct ks_status_parm_s stparm;
   struct ks_search_parm_s parm;
   char line[ASSUAN_LINELENGTH];
 
@@ -335,18 +575,21 @@ gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr,
     xfree (escsearchstr);
   }
 
+  memset (&stparm, 0, sizeof stparm);
   memset (&parm, 0, sizeof parm);
   init_membuf (&parm.saveddata, 1024);
   parm.data_cb = cb;
   parm.data_cb_value = cb_value;
+  parm.stparm = &stparm;
 
   err = assuan_transact (ctx, line, ks_search_data_cb, &parm,
-                        NULL, NULL, NULL, NULL);
+                        NULL, NULL, ks_status_cb, &stparm);
   if (!err)
-    err = cb (cb_value, NULL);  /* Send EOF.  */
+    err = cb (cb_value, 0, NULL);  /* Send EOF.  */
 
   xfree (get_membuf (&parm.saveddata, NULL));
   xfree (parm.helpbuf);
+  xfree (stparm.source);
 
   close_context (ctrl, ctx);
   return err;
@@ -377,36 +620,68 @@ ks_get_data_cb (void *opaque, const void *data, size_t datalen)
    error an error code is returned and NULL stored at R_FP.
 
    The pattern may only use search specification which a keyserver can
-   use to retriev keys.  Because we know the format of the pattern we
+   use to retrieve keys.  Because we know the format of the pattern we
    don't need to escape the patterns before sending them to the
    server.
 
+   If QUICK is set the dirmngr is advised to use a shorter timeout.
+
+   If R_SOURCE is not NULL the source of the data is stored as a
+   malloced string there.  If a source is not known NULL is stored.
+
    If there are too many patterns the function returns an error.  That
    could be fixed by issuing several search commands or by
    implementing a different interface.  However with long keyids we
    are able to ask for (1000-10-1)/(2+8+1) = 90 keys at once.  */
 gpg_error_t
-gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, estream_t *r_fp)
+gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern,
+                    keyserver_spec_t override_keyserver, int quick,
+                    estream_t *r_fp, char **r_source)
 {
   gpg_error_t err;
   assuan_context_t ctx;
+  struct ks_status_parm_s stparm;
   struct ks_get_parm_s parm;
   char *line = NULL;
   size_t linelen;
   membuf_t mb;
   int idx;
 
+  memset (&stparm, 0, sizeof stparm);
   memset (&parm, 0, sizeof parm);
 
   *r_fp = NULL;
+  if (r_source)
+    *r_source = NULL;
 
   err = open_context (ctrl, &ctx);
   if (err)
     return err;
 
+  /* If we have an override keyserver we first indicate that the next
+     user of the context needs to again setup the global keyservers and
+     them we send the override keyserver.  */
+  if (override_keyserver)
+    {
+      clear_context_flags (ctrl, ctx);
+      line = xtryasprintf ("KEYSERVER --clear %s", override_keyserver->uri);
+      if (!line)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      err = assuan_transact (ctx, line, NULL, NULL, NULL,
+                             NULL, NULL, NULL);
+      if (err)
+        goto leave;
+
+      xfree (line);
+      line = NULL;
+    }
+
   /* Lump all patterns into one string.  */
   init_membuf (&mb, 1024);
-  put_membuf_str (&mb, "KS_GET --");
+  put_membuf_str (&mb, quick? "KS_GET --quick --" : "KS_GET --");
   for (idx=0; pattern[idx]; idx++)
     {
       put_membuf (&mb, " ", 1); /* Append Delimiter.  */
@@ -432,7 +707,7 @@ gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, estream_t *r_fp)
       goto leave;
     }
   err = assuan_transact (ctx, line, ks_get_data_cb, &parm,
-                         NULL, NULL, NULL, NULL);
+                         NULL, NULL, ks_status_cb, &stparm);
   if (err)
     goto leave;
 
@@ -440,8 +715,15 @@ gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, estream_t *r_fp)
   *r_fp = parm.memfp;
   parm.memfp = NULL;
 
+  if (r_source)
+    {
+      *r_source = stparm.source;
+      stparm.source = NULL;
+    }
+
  leave:
   es_fclose (parm.memfp);
+  xfree (stparm.source);
   xfree (line);
   close_context (ctrl, ctx);
   return err;
@@ -508,6 +790,117 @@ gpg_dirmngr_ks_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
 
 
 \f
+static void
+record_output (estream_t output,
+              pkttype_t type,
+              const char *validity,
+              /* The public key length or -1.  */
+              int pub_key_length,
+              /* The public key algo or -1.  */
+              int pub_key_algo,
+              /* 2 ulongs or NULL.  */
+              const u32 *keyid,
+              /* The creation / expiration date or 0.  */
+              u32 creation_date,
+              u32 expiration_date,
+              const char *userid)
+{
+  const char *type_str = NULL;
+  char *pub_key_length_str = NULL;
+  char *pub_key_algo_str = NULL;
+  char *keyid_str = NULL;
+  char *creation_date_str = NULL;
+  char *expiration_date_str = NULL;
+  char *userid_escaped = NULL;
+
+  switch (type)
+    {
+    case PKT_PUBLIC_KEY:
+      type_str = "pub";
+      break;
+    case PKT_PUBLIC_SUBKEY:
+      type_str = "sub";
+      break;
+    case PKT_USER_ID:
+      type_str = "uid";
+      break;
+    case PKT_SIGNATURE:
+      type_str = "sig";
+      break;
+    default:
+      log_assert (! "Unhandled type.");
+    }
+
+  if (pub_key_length > 0)
+    pub_key_length_str = xasprintf ("%d", pub_key_length);
+
+  if (pub_key_algo != -1)
+    pub_key_algo_str = xasprintf ("%d", pub_key_algo);
+
+  if (keyid)
+    keyid_str = xasprintf ("%08lX%08lX", (ulong) keyid[0], (ulong) keyid[1]);
+
+  if (creation_date)
+    creation_date_str = xstrdup (colon_strtime (creation_date));
+
+  if (expiration_date)
+    expiration_date_str = xstrdup (colon_strtime (expiration_date));
+
+  /* Quote ':', '%', and any 8-bit characters.  */
+  if (userid)
+    {
+      int r;
+      int w = 0;
+
+      int len = strlen (userid);
+      /* A 100k character limit on the uid should be way more than
+        enough.  */
+      if (len > 100 * 1024)
+       len = 100 * 1024;
+
+      /* The minimum amount of space that we need.  */
+      userid_escaped = xmalloc (len * 3 + 1);
+
+      for (r = 0; r < len; r++)
+       {
+         if (userid[r] == ':' || userid[r]== '%' || (userid[r] & 0x80))
+           {
+             sprintf (&userid_escaped[w], "%%%02X", (byte) userid[r]);
+             w += 3;
+           }
+         else
+           userid_escaped[w ++] = userid[r];
+       }
+      userid_escaped[w] = '\0';
+    }
+
+  es_fprintf (output, "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s\n",
+             type_str,
+             validity ?: "",
+             pub_key_length_str ?: "",
+             pub_key_algo_str ?: "",
+             keyid_str ?: "",
+             creation_date_str ?: "",
+             expiration_date_str ?: "",
+             "" /* Certificate S/N */,
+             "" /* Ownertrust.  */,
+             userid_escaped ?: "",
+             "" /* Signature class.  */,
+             "" /* Key capabilities.  */,
+             "" /* Issuer certificate fingerprint.  */,
+             "" /* Flag field.  */,
+             "" /* S/N of a token.  */,
+             "" /* Hash algo.  */,
+             "" /* Curve name.  */);
+
+  xfree (userid_escaped);
+  xfree (expiration_date_str);
+  xfree (creation_date_str);
+  xfree (keyid_str);
+  xfree (pub_key_algo_str);
+  xfree (pub_key_length_str);
+}
+
 /* Handle the KS_PUT inquiries. */
 static gpg_error_t
 ks_put_inq_cb (void *opaque, const char *line)
@@ -515,68 +908,95 @@ ks_put_inq_cb (void *opaque, const char *line)
   struct ks_put_parm_s *parm = opaque;
   gpg_error_t err = 0;
 
-  if (!strncmp (line, "KEYBLOCK", 8) && (line[8] == ' ' || !line[8]))
+  if (has_leading_keyword (line, "KEYBLOCK"))
     {
       if (parm->data)
         err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
     }
-  else if (!strncmp (line, "KEYBLOCK_INFO", 13) && (line[13]==' ' || !line[13]))
+  else if (has_leading_keyword (line, "KEYBLOCK_INFO"))
     {
       kbnode_t node;
       estream_t fp;
 
       /* Parse the keyblock and send info lines back to the server.  */
-      fp = es_fopenmem (0, "rw");
+      fp = es_fopenmem (0, "rw,samethread");
       if (!fp)
         err = gpg_error_from_syserror ();
 
+      /* Note: the output format for the INFO block follows the colon
+        format as described in doc/DETAILS.  We don't actually reuse
+        the functionality from g10/keylist.c to produce the output,
+        because we don't need all of it and some of it is quite
+        expensive to generate.
+
+        The fields are (the starred fields are the ones we need):
+
+          * Field 1 - Type of record
+           * Field 2 - Validity
+           * Field 3 - Key length
+           * Field 4 - Public key algorithm
+           * Field 5 - KeyID
+           * Field 6 - Creation date
+           * Field 7 - Expiration date
+             Field 8 - Certificate S/N, UID hash, trust signature info
+             Field 9 -  Ownertrust
+          * Field 10 - User-ID
+             Field 11 - Signature class
+             Field 12 - Key capabilities
+             Field 13 - Issuer certificate fingerprint or other info
+             Field 14 - Flag field
+             Field 15 - S/N of a token
+             Field 16 - Hash algorithm
+             Field 17 - Curve name
+       */
       for (node = parm->keyblock; !err && node; node=node->next)
         {
-          switch(node->pkt->pkttype)
+          switch (node->pkt->pkttype)
             {
             case PKT_PUBLIC_KEY:
             case PKT_PUBLIC_SUBKEY:
               {
                 PKT_public_key *pk = node->pkt->pkt.public_key;
 
+               char validity[3];
+               int i;
+
+               i = 0;
+               if (pk->flags.revoked)
+                 validity[i ++] = 'r';
+               if (pk->has_expired)
+                 validity[i ++] = 'e';
+               validity[i] = '\0';
+
                 keyid_from_pk (pk, NULL);
 
-                es_fprintf (fp, "%s:%08lX%08lX:%u:%u:%u:%u:%s%s:\n",
-                            node->pkt->pkttype==PKT_PUBLIC_KEY? "pub" : "sub",
-                            (ulong)pk->keyid[0], (ulong)pk->keyid[1],
-                            pk->pubkey_algo,
-                            nbits_from_pk (pk),
-                            pk->timestamp,
-                            pk->expiredate,
-                            pk->flags.revoked? "r":"",
-                            pk->has_expired? "e":"");
+               record_output (fp, node->pkt->pkttype, validity,
+                              nbits_from_pk (pk), pk->pubkey_algo,
+                              pk->keyid, pk->timestamp, pk->expiredate,
+                              NULL);
               }
               break;
 
             case PKT_USER_ID:
               {
                 PKT_user_id *uid = node->pkt->pkt.user_id;
-                int r;
 
                 if (!uid->attrib_data)
                   {
-                    es_fprintf (fp, "uid:");
-
-                    /* Quote ':', '%', and any 8-bit characters.  */
-                    for (r=0; r < uid->len; r++)
-                      {
-                        if (uid->name[r] == ':'
-                            || uid->name[r]== '%'
-                            || (uid->name[r]&0x80))
-                          es_fprintf (fp, "%%%02X", (byte)uid->name[r]);
-                        else
-                          es_putc (uid->name[r], fp);
-                      }
-
-                    es_fprintf (fp, ":%u:%u:%s%s:\n",
-                                uid->created,uid->expiredate,
-                                uid->is_revoked? "r":"",
-                                uid->is_expired? "e":"");
+                   char validity[3];
+                   int i;
+
+                   i = 0;
+                   if (uid->flags.revoked)
+                     validity[i ++] = 'r';
+                   if (uid->flags.expired)
+                     validity[i ++] = 'e';
+                   validity[i] = '\0';
+
+                   record_output (fp, node->pkt->pkttype, validity,
+                                  -1, -1, NULL,
+                                  uid->created, uid->expiredate,
+                                  uid->name);
                   }
               }
               break;
@@ -590,12 +1010,9 @@ ks_put_inq_cb (void *opaque, const char *line)
                 PKT_signature *sig = node->pkt->pkt.signature;
 
                 if (IS_UID_SIG (sig))
-                  {
-                    es_fprintf (fp, "sig:%08lX%08lX:%X:%u:%u:\n",
-                                (ulong)sig->keyid[0],(ulong)sig->keyid[1],
-                                sig->sig_class, sig->timestamp,
-                                sig->expiredate);
-                  }
+                 record_output (fp, node->pkt->pkttype, NULL,
+                                -1, -1, sig->keyid,
+                                sig->timestamp, sig->expiredate, NULL);
               }
               break;
 
@@ -668,3 +1085,288 @@ gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock)
   close_context (ctrl, ctx);
   return err;
 }
+
+
+\f
+/* Data callback for the DNS_CERT and WKD_GET commands. */
+static gpg_error_t
+dns_cert_data_cb (void *opaque, const void *data, size_t datalen)
+{
+  struct dns_cert_parm_s *parm = opaque;
+  gpg_error_t err = 0;
+  size_t nwritten;
+
+  if (!data)
+    return 0;  /* Ignore END commands.  */
+  if (!parm->memfp)
+    return 0;  /* Data is not required.  */
+
+  if (es_write (parm->memfp, data, datalen, &nwritten))
+    err = gpg_error_from_syserror ();
+
+  return err;
+}
+
+
+/* Status callback for the DNS_CERT command.  */
+static gpg_error_t
+dns_cert_status_cb (void *opaque, const char *line)
+{
+  struct dns_cert_parm_s *parm = opaque;
+  gpg_error_t err = 0;
+  const char *s;
+  size_t nbytes;
+
+  if ((s = has_leading_keyword (line, "FPR")))
+    {
+      char *buf;
+
+      if (!(buf = xtrystrdup (s)))
+        err = gpg_error_from_syserror ();
+      else if (parm->fpr)
+        err = gpg_error (GPG_ERR_DUP_KEY);
+      else if (!hex2str (buf, buf, strlen (buf)+1, &nbytes))
+        err = gpg_error_from_syserror ();
+      else if (nbytes < 20)
+        err = gpg_error (GPG_ERR_TOO_SHORT);
+      else
+        {
+          parm->fpr = xtrymalloc (nbytes);
+          if (!parm->fpr)
+            err = gpg_error_from_syserror ();
+          else
+            memcpy (parm->fpr, buf, (parm->fprlen = nbytes));
+        }
+      xfree (buf);
+    }
+  else if ((s = has_leading_keyword (line, "URL")) && *s)
+    {
+      if (parm->url)
+        err = gpg_error (GPG_ERR_DUP_KEY);
+      else if (!(parm->url = xtrystrdup (s)))
+        err = gpg_error_from_syserror ();
+    }
+
+  return err;
+}
+
+/* Ask the dirmngr for a DNS CERT record.  Depending on the found
+   subtypes different return values are set:
+
+   - For a PGP subtype a new estream with that key will be returned at
+     R_KEY and the other return parameters are set to NULL/0.
+
+   - For an IPGP subtype the fingerprint is stored as a malloced block
+     at (R_FPR,R_FPRLEN).  If an URL is available it is stored as a
+     malloced string at R_URL; NULL is stored if there is no URL.
+
+   If CERTTYPE is DNS_CERTTYPE_ANY this function returns the first
+   CERT record found with a supported type; it is expected that only
+   one CERT record is used.  If CERTTYPE is one of the supported
+   certtypes, only records with this certtype are considered and the
+   first one found is returned.  All R_* args are optional.
+
+   If CERTTYPE is NULL the DANE method is used to fetch the key.
+ */
+gpg_error_t
+gpg_dirmngr_dns_cert (ctrl_t ctrl, const char *name, const char *certtype,
+                      estream_t *r_key,
+                      unsigned char **r_fpr, size_t *r_fprlen,
+                      char **r_url)
+{
+  gpg_error_t err;
+  assuan_context_t ctx;
+  struct dns_cert_parm_s parm;
+  char *line = NULL;
+
+  memset (&parm, 0, sizeof parm);
+  if (r_key)
+    *r_key = NULL;
+  if (r_fpr)
+    *r_fpr = NULL;
+  if (r_fprlen)
+    *r_fprlen = 0;
+  if (r_url)
+    *r_url = NULL;
+
+  err = open_context (ctrl, &ctx);
+  if (err)
+    return err;
+
+  line = es_bsprintf ("DNS_CERT %s %s", certtype? certtype : "--dane", name);
+  if (!line)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
+    {
+      err = gpg_error (GPG_ERR_TOO_LARGE);
+      goto leave;
+    }
+
+  parm.memfp = es_fopenmem (0, "rwb");
+  if (!parm.memfp)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
+                         NULL, NULL, dns_cert_status_cb, &parm);
+  if (err)
+    goto leave;
+
+  if (r_key)
+    {
+      es_rewind (parm.memfp);
+      *r_key = parm.memfp;
+      parm.memfp = NULL;
+    }
+
+  if (r_fpr && parm.fpr)
+    {
+      *r_fpr = parm.fpr;
+      parm.fpr = NULL;
+    }
+  if (r_fprlen)
+    *r_fprlen = parm.fprlen;
+
+  if (r_url && parm.url)
+    {
+      *r_url = parm.url;
+      parm.url = NULL;
+    }
+
+ leave:
+  xfree (parm.fpr);
+  xfree (parm.url);
+  es_fclose (parm.memfp);
+  xfree (line);
+  close_context (ctrl, ctx);
+  return err;
+}
+
+
+/* Ask the dirmngr for PKA info.  On success the retrieved fingerprint
+   is returned in a malloced buffer at R_FPR and its length is stored
+   at R_FPRLEN.  If an URL is available it is stored as a malloced
+   string at R_URL.  On error all return values are set to NULL/0.  */
+gpg_error_t
+gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid,
+                     unsigned char **r_fpr, size_t *r_fprlen,
+                     char **r_url)
+{
+  gpg_error_t err;
+  assuan_context_t ctx;
+  struct dns_cert_parm_s parm;
+  char *line = NULL;
+
+  memset (&parm, 0, sizeof parm);
+  if (r_fpr)
+    *r_fpr = NULL;
+  if (r_fprlen)
+    *r_fprlen = 0;
+  if (r_url)
+    *r_url = NULL;
+
+  err = open_context (ctrl, &ctx);
+  if (err)
+    return err;
+
+  line = es_bsprintf ("DNS_CERT --pka -- %s", userid);
+  if (!line)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
+    {
+      err = gpg_error (GPG_ERR_TOO_LARGE);
+      goto leave;
+    }
+
+  err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
+                         NULL, NULL, dns_cert_status_cb, &parm);
+  if (err)
+    goto leave;
+
+  if (r_fpr && parm.fpr)
+    {
+      *r_fpr = parm.fpr;
+      parm.fpr = NULL;
+    }
+  if (r_fprlen)
+    *r_fprlen = parm.fprlen;
+
+  if (r_url && parm.url)
+    {
+      *r_url = parm.url;
+      parm.url = NULL;
+    }
+
+ leave:
+  xfree (parm.fpr);
+  xfree (parm.url);
+  xfree (line);
+  close_context (ctrl, ctx);
+  return err;
+}
+
+
+\f
+/* Ask the dirmngr to retrieve a key via the Web Key Directory
+ * protocol.  If QUICK is set the dirmngr is advised to use a shorter
+ * timeout.  On success a new estream with the key is stored at R_KEY.
+ */
+gpg_error_t
+gpg_dirmngr_wkd_get (ctrl_t ctrl, const char *name, int quick, estream_t *r_key)
+{
+  gpg_error_t err;
+  assuan_context_t ctx;
+  struct dns_cert_parm_s parm;
+  char *line = NULL;
+
+  memset (&parm, 0, sizeof parm);
+
+  err = open_context (ctrl, &ctx);
+  if (err)
+    return err;
+
+  line = es_bsprintf ("WKD_GET%s -- %s", quick?" --quick":"", name);
+  if (!line)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
+    {
+      err = gpg_error (GPG_ERR_TOO_LARGE);
+      goto leave;
+    }
+
+  parm.memfp = es_fopenmem (0, "rwb");
+  if (!parm.memfp)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
+                         NULL, NULL, NULL, &parm);
+  if (err)
+    goto leave;
+
+  if (r_key)
+    {
+      es_rewind (parm.memfp);
+      *r_key = parm.memfp;
+      parm.memfp = NULL;
+    }
+
+ leave:
+  xfree (parm.fpr);
+  xfree (parm.url);
+  es_fclose (parm.memfp);
+  xfree (line);
+  close_context (ctrl, ctx);
+  return err;
+}