Issue 1447: Pass proper Host header and SNI when SRV is used with curl.
[gnupg.git] / keyserver / gpgkeys_hkp.c
index a15cbf6..382bee5 100644 (file)
@@ -1,6 +1,6 @@
 /* gpgkeys_hkp.c - talk to an HKP keyserver
  * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
- *               2009 Free Software Foundation, Inc.
+ *               2009, 2012 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #include <errno.h>
 #include <unistd.h>
 #ifdef HAVE_GETOPT_H
-#include <getopt.h>
+# include <getopt.h>
 #endif
 #ifdef HAVE_LIBCURL
-#include <curl/curl.h>
+# include <curl/curl.h>
+/* This #define rigamarole is to enable a hack to fake DNS SRV using
+   libcurl.  It only works if we have getaddrinfo(), inet_ntop(), and
+   a modern enough version of libcurl (7.21.3) so we can use
+   CURLOPT_RESOLVE to feed the resolver from the outside to force
+   libcurl to pass the right SNI. */
+#if (defined(HAVE_GETADDRINFO) && defined(HAVE_INET_NTOP)      \
+     && LIBCURL_VERNUM >= 0x071503)
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <netdb.h>
+# include <arpa/inet.h>
 #else
-#include "curl-shim.h"
+# undef USE_DNS_SRV
+#endif
+#else
+# include "curl-shim.h"
 #endif
 #ifdef USE_DNS_SRV
-#include "srv.h"
+# include "srv.h"
 #endif
 #include "compat.h"
 #include "keyserver.h"
@@ -62,7 +76,8 @@ static char *proto,*port;
 static size_t
 curl_mrindex_writer(const void *ptr,size_t size,size_t nmemb,void *stream)
 {
-  static int checked=0,swallow=0;
+  static int checked=0;
+  static int swallow=0;
 
   if(!checked)
     {
@@ -234,9 +249,10 @@ static int
 get_key(char *getkey)
 {
   CURLcode res;
-  char request[MAX_URL+60];
+  char request[MAX_URL+92];
   char *offset;
   struct curl_writer_ctx ctx;
+  size_t keylen;
 
   memset(&ctx,0,sizeof(ctx));
 
@@ -262,14 +278,19 @@ get_key(char *getkey)
   strcat(request,port);
   strcat(request,opt->path);
   /* request is MAX_URL+55 bytes long - MAX_URL covers the whole URL,
-     including any supplied path.  The 60 overcovers this /pks/... etc
-     string plus the 8 bytes of key id */
+     including any supplied path.  The 92 overcovers this /pks/... etc
+     string plus the 8, 16, or 40 bytes of key id/fingerprint */
   append_path(request,"/pks/lookup?op=get&options=mr&search=0x");
 
-  /* fingerprint or long key id.  Take the last 8 characters and treat
-     it like a short key id */
-  if(strlen(getkey)>8)
-    offset=&getkey[strlen(getkey)-8];
+  /* send only fingerprint, long key id, or short keyid.  see:
+     https://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-3.1.1.1 */
+  keylen = strlen(getkey);
+  if(keylen >= 40)
+    offset=&getkey[keylen-40];
+  else if(keylen >= 16)
+    offset=&getkey[keylen-16];
+  else if(keylen >= 8)
+    offset=&getkey[keylen-8];
   else
     offset=getkey;
 
@@ -490,19 +511,29 @@ fail_all(struct keylist *keylist,int err)
       }
 }
 
-#ifdef HAVE_LIBCURL
+#if defined(HAVE_LIBCURL) && defined(USE_DNS_SRV)
 /* If there is a SRV record, take the highest ranked possibility.
-   This is a hack, as we don't proceed downwards. */
+   This is a hack, as we don't proceed downwards if we can't
+   connect(), but only if we can't getaddinfo().  All this should
+   ideally be replaced by actual SRV support in libcurl someday! */
+
+#define HOST_HEADER "Host:"
+
 static void
-srv_replace(const char *srvtag)
+srv_replace(const char *srvtag,
+           struct curl_slist **headers, struct curl_slist **resolve)
 {
-#ifdef USE_DNS_SRV
   struct srventry *srvlist=NULL;
-  int srvcount;
+  int srvcount, srvindex;
+  char *portstr;
 
   if(!srvtag)
     return;
 
+  portstr=malloc (MAX_PORT);
+  if(!portstr)
+    return;
+
   if(1+strlen(srvtag)+6+strlen(opt->host)+1<=MAXDNAME)
     {
       char srvname[MAXDNAME];
@@ -514,31 +545,79 @@ srv_replace(const char *srvtag)
       srvcount=getsrv(srvname,&srvlist);
     }
 
-  if(srvlist)
+  for(srvindex=0 ; srvindex<srvcount && portstr ; srvindex++)
     {
-      char *newname,*newport;
+      struct addrinfo hints, *res;
+
+      sprintf (portstr, "%hu", srvlist[srvindex].port);
+      memset (&hints, 0, sizeof (hints));
+      hints.ai_socktype = SOCK_STREAM;
 
-      newname=strdup(srvlist->target);
-      newport=xtrymalloc(MAX_PORT);
-      if(newname && newport)
+      if (getaddrinfo (srvlist[srvindex].target, portstr, &hints, &res) == 0)
        {
-         free(opt->host);
-         free(opt->port);
-         opt->host=newname;
-         snprintf(newport,MAX_PORT,"%u",srvlist->port);
-         opt->port=newport;
+         /* Very safe */
+         char ipaddr[INET_ADDRSTRLEN+INET6_ADDRSTRLEN];
+
+         if((res->ai_family==AF_INET
+             && inet_ntop (res->ai_family,
+                           &((struct sockaddr_in *)res->ai_addr)->sin_addr,
+                           ipaddr,sizeof(ipaddr)))
+            || (res->ai_family==AF_INET6
+                && inet_ntop (res->ai_family,
+                              &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
+                              ipaddr,sizeof(ipaddr))))
+           {
+             char *entry,*host;
+
+             entry=malloc (strlen(opt->host)+1
+                           +strlen(portstr)+1+strlen(ipaddr)+1);
+
+             host=malloc (strlen(HOST_HEADER)+1+strlen(opt->host)+1);
+
+             if(entry && host)
+               {
+                 sprintf (entry, "%s:%s:%s", opt->host, portstr, ipaddr);
+                 sprintf (host, "%s %s", HOST_HEADER, opt->host);
+
+                 *resolve=curl_slist_append (*resolve,entry);
+                 *headers=curl_slist_append (*headers,host);
+
+                 if(*resolve && *headers)
+                   {
+                     if(curl_easy_setopt (curl,
+                                          CURLOPT_RESOLVE,*resolve)==CURLE_OK)
+
+                       {
+                         if(opt->debug)
+                           fprintf (console, "gpgkeys: Faking %s SRV from"
+                                    " %s to %s:%u\n",
+                                    srvtag, opt->host,
+                                    srvlist[srvindex].target,
+                                    srvlist[srvindex].port);
+
+                         free (opt->port);
+                         opt->port=portstr;
+                         portstr=NULL;
+                       }
+                   }
+               }
+
+             free (entry);
+             free (host);
+           }
+
+         freeaddrinfo (res);
        }
       else
-       {
-         free(newname);
-         free(newport);
-       }
+       continue; /* Not found */
     }
-#endif
+
+  free (srvlist);
+  free (portstr);
 }
 #endif
 
-static void 
+static void
 show_help (FILE *fp)
 {
   fprintf (fp,"-h, --help\thelp\n");
@@ -550,12 +629,20 @@ show_help (FILE *fp)
 int
 main(int argc,char *argv[])
 {
-  int arg,ret=KEYSERVER_INTERNAL_ERROR,try_srv=1;
+  int arg,ret=KEYSERVER_INTERNAL_ERROR;
   char line[MAX_LINE];
   int failed=0;
   struct keylist *keylist=NULL,*keyptr=NULL;
   char *proxy=NULL;
   struct curl_slist *headers=NULL;
+  struct curl_slist *resolve=NULL;
+
+  /* Only default this to on if we have SRV support */
+#ifdef USE_DNS_SRV
+  int try_srv = 1;
+#else
+  int try_srv = 0;
+#endif
 
   console=stderr;
 
@@ -685,6 +772,7 @@ main(int argc,char *argv[])
       goto fail;
     }
 
+  /* Defaults */
   if(ascii_strcasecmp(opt->scheme,"hkps")==0)
     {
       proto="https";
@@ -717,11 +805,16 @@ main(int argc,char *argv[])
       goto fail;
     }
 
-  /* If the user gives a :port, then disable SRV.  The semantics of a
-     specified port and SRV do not play well together. */
-  if(opt->port)
-    port=opt->port;
-  else if(try_srv)
+  if(opt->debug)
+    {
+      fprintf(console,"gpgkeys: curl version = %s\n",curl_version());
+      curl_easy_setopt(curl,CURLOPT_STDERR,console);
+      curl_easy_setopt(curl,CURLOPT_VERBOSE,1L);
+    }
+
+  /* Only use SRV if the user does not provide a :port.  The semantics
+     of a specified port and SRV do not play well together. */
+  if(!opt->port && try_srv)
     {
       char *srvtag;
 
@@ -737,8 +830,12 @@ main(int argc,char *argv[])
         This isn't as good as true SRV support, as we do not try all
         possible targets at one particular level and work our way
         down the list, but it's better than nothing. */
-      srv_replace(srvtag);
+#ifdef USE_DNS_SRV
+      srv_replace(srvtag,&headers,&resolve);
 #else
+      fprintf(console,"gpgkeys: try-dns-srv was requested, but not SRV capable\n");
+#endif
+#else /* !HAVE_LIBCURL */
       /* We're using our internal curl shim, so we can use its (true)
         SRV support.  Obviously, CURLOPT_SRVTAG_GPG_HACK isn't a real
         libcurl option.  It's specific to our shim. */
@@ -746,18 +843,16 @@ main(int argc,char *argv[])
 #endif
     }
 
+  /* If the user provided a port (or it came in via SRV, above),
+     replace the default. */
+  if(opt->port)
+    port=opt->port;
+
   curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
 
   if(opt->auth)
     curl_easy_setopt(curl,CURLOPT_USERPWD,opt->auth);
 
-  if(opt->debug)
-    {
-      fprintf(console,"gpgkeys: curl version = %s\n",curl_version());
-      curl_easy_setopt(curl,CURLOPT_STDERR,console);
-      curl_easy_setopt(curl,CURLOPT_VERBOSE,1L);
-    }
-
   curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(long)opt->flags.check_cert);
   curl_easy_setopt(curl,CURLOPT_CAINFO,opt->ca_cert_file);
 
@@ -959,6 +1054,7 @@ main(int argc,char *argv[])
   free_ks_options(opt);
 
   curl_slist_free_all(headers);
+  curl_slist_free_all(resolve);
 
   if(curl)
     curl_easy_cleanup(curl);