Keyserver search and get basically works again.
[gnupg.git] / dirmngr / ks-engine-hkp.c
index 356f643..662e9e4 100644 (file)
 #define MAX_REDIRECTS 2
 
 
+/* Send an HTTP request.  On success returns an estream object at
+   R_FP.  HOSTPORTSTR is only used for diagnostics. */
+static gpg_error_t
+send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
+              estream_t *r_fp)
+{
+  gpg_error_t err;
+  http_t http = NULL;
+  int redirects_left = MAX_REDIRECTS;
+  estream_t fp = NULL;
+  char *request_buffer = NULL;
+
+  *r_fp = NULL;
+ once_more:
+  err = http_open (&http, HTTP_REQ_GET, request,
+                   /* fixme: AUTH */ NULL,
+                   0,
+                   /* fixme: proxy*/ NULL,
+                   NULL, NULL,
+                   /*FIXME curl->srvtag*/NULL);
+  if (!err)
+    {
+      fp = http_get_write_ptr (http);
+      /* Avoid caches to get the most recent copy of the key.  We set
+         both the Pragma and Cache-Control versions of the header, so
+         we're good with both HTTP 1.0 and 1.1.  */
+      es_fputs ("Pragma: no-cache\r\n"
+                "Cache-Control: no-cache\r\n", fp);
+      http_start_data (http);
+      if (es_ferror (fp))
+        err = gpg_error_from_syserror ();
+    }
+  if (err)
+    {
+      /* Fixme: After a redirection we show the old host name.  */
+      log_error (_("error connecting to `%s': %s\n"),
+                 hostportstr, gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Wait for the response.  */
+  dirmngr_tick (ctrl);
+  err = http_wait_response (http);
+  if (err)
+    {
+      log_error (_("error reading HTTP response for `%s': %s\n"),
+                 hostportstr, gpg_strerror (err));
+      goto leave;
+    }
+
+  switch (http_get_status_code (http))
+    {
+    case 200:
+      err = 0;
+      break; /* Success.  */
+
+    case 301:
+    case 302:
+      {
+        const char *s = http_get_header (http, "Location");
+        
+        log_info (_("URL `%s' redirected to `%s' (%u)\n"),
+                  request, s?s:"[none]", http_get_status_code (http));
+        if (s && *s && redirects_left-- )
+          {
+            xfree (request_buffer);
+            request_buffer = xtrystrdup (s);
+            if (request_buffer)
+              {
+                request = request_buffer;
+                http_close (http, 0);
+                http = NULL;
+                goto once_more;
+              }
+            err = gpg_error_from_syserror ();
+          }
+        else
+          err = gpg_error (GPG_ERR_NO_DATA);
+        log_error (_("too many redirections\n"));
+      }
+      goto leave;
+
+    default:
+      log_error (_("error accessing `%s': http status %u\n"),
+                 request, http_get_status_code (http));
+      err = gpg_error (GPG_ERR_NO_DATA);
+      goto leave;
+    }
+
+  fp = http_get_read_ptr (http);
+  if (!fp)
+    {
+      err = gpg_error (GPG_ERR_BUG);
+      goto leave;
+    }
+
+  /* Return the read stream and close the HTTP context.  */
+  *r_fp = fp;
+  fp = NULL;
+  http_close (http, 1);
+  http = NULL;
+
+ leave:
+  es_fclose (fp);
+  http_close (http, 0);
+  xfree (request_buffer);
+  return err;
+}
+
+
+
 /* Search the keyserver identified by URI for keys matching PATTERN.
    On success R_FP has an open stream to read the data.  */
 gpg_error_t
@@ -48,10 +159,8 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
   char fprbuf[2+40+1];
   const char *scheme;
   char portstr[10];
-  http_t http = NULL;
   char *hostport = NULL;
   char *request = NULL;
-  int redirects_left = MAX_REDIRECTS;
   estream_t fp = NULL;
 
   *r_fp = NULL;
@@ -142,87 +251,11 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
   }
   
   /* Send the request.  */
- once_more:
-  err = http_open (&http, HTTP_REQ_GET, request,
-                   /* fixme: AUTH */ NULL,
-                   0,
-                   /* fixme: proxy*/ NULL,
-                   NULL, NULL,
-                   /*FIXME curl->srvtag*/NULL);
-  if (!err)
-    {
-      fp = http_get_write_ptr (http);
-      /* Avoid caches to get the most recent copy of the key.  We set
-         both the Pragma and Cache-Control versions of the header, so
-         we're good with both HTTP 1.0 and 1.1.  */
-      es_fputs ("Pragma: no-cache\r\n"
-                "Cache-Control: no-cache\r\n", fp);
-      http_start_data (http);
-      if (es_ferror (fp))
-        err = gpg_error_from_syserror ();
-    }
+  err = send_request (ctrl, request, hostport, &fp);
   if (err)
-    {
-      /* Fixme: After a redirection we show the old host name.  */
-      log_error (_("error connecting to `%s': %s\n"),
-                 hostport, gpg_strerror (err));
-      goto leave;
-    }
-
-  /* Wait for the response.  */
-  dirmngr_tick (ctrl);
-  err = http_wait_response (http);
-  if (err)
-    {
-      log_error (_("error reading HTTP response for `%s': %s\n"),
-                 hostport, gpg_strerror (err));
-      goto leave;
-    }
-
-  switch (http_get_status_code (http))
-    {
-    case 200:
-      break; /* Success.  */
-
-    case 301:
-    case 302:
-      {
-        const char *s = http_get_header (http, "Location");
-        
-        log_info (_("URL `%s' redirected to `%s' (%u)\n"),
-                  request, s?s:"[none]", http_get_status_code (http));
-        if (s && *s && redirects_left-- )
-          {
-            xfree (request);
-            request = xtrystrdup (s);
-            if (request)
-              {
-                http_close (http, 0);
-                http = NULL;
-                goto once_more;
-              }
-            err = gpg_error_from_syserror ();
-          }
-        else
-          err = gpg_error (GPG_ERR_NO_DATA);
-        log_error (_("too many redirections\n"));
-      }
-      goto leave;
-
-    default:
-      log_error (_("error accessing `%s': http status %u\n"),
-                 request, http_get_status_code (http));
-      err = gpg_error (GPG_ERR_NO_DATA);
-      goto leave;
-    }
+    goto leave;
 
   /* Start reading the response.  */
-  fp = http_get_read_ptr (http);
-  if (!fp)
-    {
-      err = gpg_error (GPG_ERR_BUG);
-      goto leave;
-    }
   {
     int c = es_getc (fp);
     if (c == -1)
@@ -241,15 +274,110 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
     es_ungetc (c, fp);
   }
 
+  /* Return the read stream.  */
+  *r_fp = fp;
+  fp = NULL;
+
+ leave:
+  es_fclose (fp);
+  xfree (request);
+  xfree (hostport);
+  return err;
+}
+
+
+/* Get the key described key the KEYSPEC string from the keyserver
+   identified by URI.  On success R_FP has an open stream to read the
+   data.  */
+gpg_error_t
+ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
+{
+  gpg_error_t err;
+  KEYDB_SEARCH_DESC desc;
+  char kidbuf[8+1];
+  const char *scheme;
+  char portstr[10];
+  char *hostport = NULL;
+  char *request = NULL;
+  estream_t fp = NULL;
+
+  *r_fp = NULL;
+
+  /* Remove search type indicator and adjust PATTERN accordingly.
+     Note that HKP keyservers like the 0x to be present when searching
+     by keyid.  We need to re-format the fingerprint and keyids so to
+     remove the gpg specific force-use-of-this-key flag ("!").  */
+  err = classify_user_id (keyspec, &desc);
+  if (err)
+    return err;
+  switch (desc.mode)
+    {
+    case KEYDB_SEARCH_MODE_SHORT_KID:
+    case KEYDB_SEARCH_MODE_LONG_KID:
+      snprintf (kidbuf, sizeof kidbuf, "%08lX", (ulong)desc.u.kid[1]);
+      break;
+    case KEYDB_SEARCH_MODE_FPR20:
+    case KEYDB_SEARCH_MODE_FPR:
+      /* This is a v4 fingerprint.  Take the last 8 hex digits from
+         the fingerprint which is the expected short keyid.  */
+      bin2hex (desc.u.fpr+16, 4, kidbuf);
+      break;
+
+    case KEYDB_SEARCH_MODE_FPR16:
+      log_error ("HKP keyserver do not support v3 fingerprints\n");
+    default:
+      return gpg_error (GPG_ERR_INV_USER_ID);
+    }
+  
+  /* Map scheme and port.  */
+  if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https"))
+    {
+      scheme = "https";
+      strcpy (portstr, "443");
+    }
+  else /* HKP or HTTP.  */
+    {
+      scheme = "http";
+      strcpy (portstr, "11371");
+    }
+  if (uri->port)
+    snprintf (portstr, sizeof portstr, "%hu", uri->port);
+  else
+    {} /*fixme_do_srv_lookup ()*/
+
+  /* Build the request string.  */
+  {
+    hostport = strconcat (scheme, "://", 
+                          *uri->host? uri->host: "localhost",
+                          ":", portstr, NULL);
+    if (!hostport)
+      {
+        err = gpg_error_from_syserror ();
+        goto leave;
+      }
+
+    request = strconcat (hostport,
+                         "/pks/lookup?op=get&options=mr&search=0x",
+                         kidbuf,
+                         NULL);
+    if (!request)
+      {
+        err = gpg_error_from_syserror ();
+        goto leave;
+      }
+  }
+  
+  /* Send the request.  */
+  err = send_request (ctrl, request, hostport, &fp);
+  if (err)
+    goto leave;
+
   /* Return the read stream and close the HTTP context.  */
   *r_fp = fp;
   fp = NULL;
-  http_close (http, 1);
-  http = NULL;
 
  leave:
   es_fclose (fp);
-  http_close (http, 0);
   xfree (request);
   xfree (hostport);
   return err;