Keyserver search and get basically works again.
[gnupg.git] / g10 / call-dirmngr.c
index a18eb64..1816a72 100644 (file)
 #include "options.h"
 #include "i18n.h"
 #include "asshelp.h"
+#include "keyserver.h"
 #include "call-dirmngr.h"
 
 
+/* Parameter structure used with the KS_SEARCH command.  */
+struct ks_search_parm_s
+{
+  gpg_error_t lasterr;  /* Last error code.  */
+  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.  */
+  void *data_cb_value;  /* First argument for DATA_CB.  */
+};
+
+
+/* Parameter structure used with the KS_GET command.  */
+struct ks_get_parm_s
+{
+  estream_t memfp;
+};
+
+
 /* 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
@@ -53,7 +73,7 @@ struct dirmngr_local_s
   struct dirmngr_local_s *next;
 
   /* The active Assuan context. */
-  static assuan_context_t ctx;
+  assuan_context_t ctx;
 
   /* Flag set to true while an operation is running on CTX.  */
   int is_active;
@@ -106,12 +126,12 @@ create_context (ctrl_t ctrl, assuan_context_t *r_ctx)
       /* Set all configured keyservers.  We clear existing keyservers
          so that any keyserver configured in GPG overrides keyservers
          possibly configured in Dirmngr. */
-      if (ksi = opt.keyservers; !err && ksi; ksi = ksi->next)
+      for (ksi = opt.keyserver; !err && ksi; ksi = ksi->next)
         {
           char *line;
           
           line = xtryasprintf ("KEYSERVER%s %s",
-                               ksi == opt.keyservers? " --clear":"", ksi->uri);
+                               ksi == opt.keyserver? " --clear":"", ksi->uri);
           if (!line)
             err = gpg_error_from_syserror ();
           else
@@ -156,7 +176,8 @@ open_context (ctrl_t ctrl, assuan_context_t *r_ctx)
           /* Found an inactive local session - return that.  */
           assert (!dml->is_active);
           dml->is_active = 1;
-          return dml;
+          *r_ctx = dml->ctx;
+          return 0;
         }
       
       dml = xtrycalloc (1, sizeof *dml);
@@ -192,9 +213,9 @@ close_context (ctrl_t ctrl, assuan_context_t ctx)
     {
       if (dml->ctx == ctx)
         {
-          if (!ctx->is_active)
+          if (!dml->is_active)
             log_fatal ("closing inactive dirmngr context %p\n", ctx);
-          ctx->is_active = 0;
+          dml->is_active = 0;
           return;
         }
     }
@@ -203,54 +224,215 @@ close_context (ctrl_t ctrl, assuan_context_t ctx)
 
 
 \f
+/* Data callback for the KS_SEARCH command. */
+static gpg_error_t
+ks_search_data_cb (void *opaque, const void *data, size_t datalen)
+{
+  gpg_error_t err = 0;
+  struct ks_search_parm_s *parm = opaque;
+  const char *line, *s;
+  size_t rawlen, linelen;
+  char fixedbuf[256];
+
+  if (parm->lasterr)
+    return 0;
+
+  if (!data)
+    return 0;  /* Ignore END commands.  */
 
-int 
-gpg_dirmngr_ks_search (ctrl_t ctrl, strlist_t names,
-                       void (*cb)(void*, ksba_cert_t), void *cb_value)
+  put_membuf (&parm->saveddata, data, datalen);
+
+ again:
+  line = peek_membuf (&parm->saveddata, &rawlen);
+  if (!line)
+    {
+      parm->lasterr = gpg_error_from_syserror ();
+      return parm->lasterr; /* Tell the server about our problem.  */
+    }
+  if ((s = memchr (line, '\n', rawlen)))
+    {
+      linelen = s - line;  /* That is the length excluding the LF.  */
+      if (linelen + 1 < sizeof fixedbuf)
+        {
+          /* We can use the static buffer.  */
+          memcpy (fixedbuf, line, linelen);
+          fixedbuf[linelen] = 0;
+          if (linelen && fixedbuf[linelen-1] == '\r')
+            fixedbuf[linelen-1] = 0;
+          err = parm->data_cb (parm->data_cb_value, fixedbuf);
+        }
+      else 
+        {
+          if (linelen + 1 >= parm->helpbufsize)
+            {
+              xfree (parm->helpbuf);
+              parm->helpbufsize = linelen + 1 + 1024;
+              parm->helpbuf = xtrymalloc (parm->helpbufsize);
+              if (!parm->helpbuf)
+                {
+                  parm->lasterr = gpg_error_from_syserror ();
+                  return parm->lasterr;
+                }
+            }
+          memcpy (parm->helpbuf, line, linelen);
+          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);
+        }
+      if (err)
+        parm->lasterr = err;
+      else
+        {
+          clear_membuf (&parm->saveddata, linelen+1);
+          goto again;  /* There might be another complete line.  */
+        }
+    }
+
+  return err;
+}
+
+
+/* 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.  */
+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 err;
   assuan_context_t ctx;
-  char *pattern;
+  struct ks_search_parm_s parm;
   char line[ASSUAN_LINELENGTH];
 
   err = open_context (ctrl, &ctx);
   if (err)
     return err;
 
-  pattern = pattern_from_strlist (names);
-  if (!pattern)
+  {
+    char *escsearchstr = percent_plus_escape (searchstr);
+    if (!escsearchstr)
+      {
+        err = gpg_error_from_syserror ();
+        close_context (ctrl, ctx);
+        return err;
+      }
+    snprintf (line, sizeof line, "KS_SEARCH -- %s", escsearchstr);
+    xfree (escsearchstr);
+  }
+
+  memset (&parm, 0, sizeof parm);
+  init_membuf (&parm.saveddata, 1024);
+  parm.data_cb = cb;
+  parm.data_cb_value = cb_value;
+
+  err = assuan_transact (ctx, line, ks_search_data_cb, &parm,
+                        NULL, NULL, NULL, NULL);
+  if (!err)
+    err = cb (cb_value, NULL);  /* Send EOF.  */
+
+  xfree (get_membuf (&parm.saveddata, NULL));
+  xfree (parm.helpbuf);
+
+  close_context (ctrl, ctx);
+  return err;
+}
+
+
+\f
+/* Data callback for the KS_GET command. */
+static gpg_error_t
+ks_get_data_cb (void *opaque, const void *data, size_t datalen)
+{
+  gpg_error_t err = 0;
+  struct ks_get_parm_s *parm = opaque;
+  size_t nwritten;
+
+  if (!data)
+    return 0;  /* Ignore END commands.  */
+
+  if (es_write (parm->memfp, data, datalen, &nwritten))
+    err = gpg_error_from_syserror ();
+
+  return err;
+}
+
+
+/* Run the KS_GET command using the patterns in the array PATTERN.  On
+   success an estream object is returned to retrieve the keys.  On
+   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
+   don't need to escape the patterns before sending them to the
+   server.
+
+   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_error_t err;
+  assuan_context_t ctx;
+  struct ks_get_parm_s parm;
+  char *line = NULL;
+  size_t linelen;
+  membuf_t mb;
+  int idx;
+
+  memset (&parm, 0, sizeof parm);
+
+  *r_fp = NULL;
+
+  err = open_context (ctrl, &ctx);
+  if (err)
+    return err;
+
+  /* Lump all patterns into one string.  */
+  init_membuf (&mb, 1024);
+  put_membuf_str (&mb, "KS_GET --");
+  for (idx=0; pattern[idx]; idx++)
     {
-      if (ctx == dirmngr_ctx)
-       release_dirmngr (ctrl);
-      else
-       release_dirmngr2 (ctrl);
+      put_membuf (&mb, " ", 1); /* Append Delimiter.  */
+      put_membuf_str (&mb, pattern[idx]);
+    }
+  put_membuf (&mb, "", 1); /* Append Nul.  */
+  line = get_membuf (&mb, &linelen);
+  if (!line)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  if (linelen + 2 >= ASSUAN_LINELENGTH)
+    {
+      err = gpg_error (GPG_ERR_TOO_MANY);
+      goto leave;
+    }
 
-      return out_of_core ();
+  parm.memfp = es_fopenmem (0, "rwb");
+  if (!parm.memfp)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
     }
-  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 = ctx;
-  parm.cb = cb;
-  parm.cb_value = cb_value;
-  parm.error = 0;
-  init_membuf (&parm.data, 4096);
-
-  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);
+  err = assuan_transact (ctx, line, ks_get_data_cb, &parm,
+                         NULL, NULL, NULL, NULL);
+  if (err)
+    goto leave;
 
-  if (rc)
-      return rc;
+  es_rewind (parm.memfp);
+  *r_fp = parm.memfp;
+  parm.memfp = NULL;
 
+ leave:
+  es_fclose (parm.memfp);
+  xfree (line);
   close_context (ctrl, ctx);
-  return parm.error;
+  return err;
 }