Issue 1447: Pass proper Host header and SNI when SRV is used with curl.
[gnupg.git] / keyserver / gpgkeys_hkp.c
index 843035b..382bee5 100644 (file)
@@ -1,11 +1,12 @@
 /* gpgkeys_hkp.c - talk to an HKP keyserver
- * Copyright (C) 2001, 2002 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
+ *               2009, 2012 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * GnuPG is distributed in the hope that it will be useful,
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the Free Software Foundation
+ * gives permission to link the code of the keyserver helper tools:
+ * gpgkeys_ldap, gpgkeys_curl and gpgkeys_hkp with the OpenSSL
+ * project's "OpenSSL" library (or with modified versions of it that
+ * use the same license as the "OpenSSL" library), and distribute the
+ * linked executables.  You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL".  If
+ * you modify this file, you may extend this exception to your version
+ * of the file, but you are not obligated to do so.  If you do not
+ * wish to do so, delete this exception statement from your version.
  */
 
 #include <config.h>
 #include <stdio.h>
 #include <string.h>
-#include <ctype.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
-#define INCLUDED_BY_MAIN_MODULE 1
-#include "util.h"
-#include "http.h"
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+#ifdef HAVE_LIBCURL
+# 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
+# undef USE_DNS_SRV
+#endif
+#else
+# include "curl-shim.h"
+#endif
+#ifdef USE_DNS_SRV
+# include "srv.h"
+#endif
+#include "compat.h"
 #include "keyserver.h"
+#include "ksutil.h"
 
-#define GET    0
-#define SEND   1
-#define SEARCH 2
-#define MAX_LINE 80
+extern char *optarg;
+extern int optind;
 
-int verbose=0,include_revoked=0;
-unsigned int http_flags=0;
-char host[80]={'\0'},port[10]={'\0'};
-FILE *input=NULL,*output=NULL,*console=NULL;
+static FILE *input,*output,*console;
+static CURL *curl;
+static struct ks_options *opt;
+static char errorbuffer[CURL_ERROR_SIZE];
+static char *proto,*port;
 
-struct keylist
+static size_t
+curl_mrindex_writer(const void *ptr,size_t size,size_t nmemb,void *stream)
 {
-  char str[MAX_LINE];
-  struct keylist *next;
-};
+  static int checked=0;
+  static int swallow=0;
 
-static int
-urlencode_filter( void *opaque, int control,
-                 IOBUF a, byte *buf, size_t *ret_len)
-{
-    size_t size = *ret_len;
-    int rc=0;
-
-    if( control == IOBUFCTRL_FLUSH ) {
-       const byte *p;
-       for(p=buf; size; p++, size-- ) {
-           if( isalnum(*p) || *p == '-' )
-               iobuf_put( a, *p );
-           else if( *p == ' ' )
-               iobuf_put( a, '+' );
-           else {
-               char numbuf[5];
-               sprintf(numbuf, "%%%02X", *p );
-               iobuf_writestr(a, numbuf );
-           }
-       }
+  if(!checked)
+    {
+      /* If the document begins with a '<', assume it's a HTML
+        response, which we don't support.  Discard the whole message
+        body.  GPG can handle it, but this is an optimization to deal
+        with it on this side of the pipe.  */
+      const char *buf=ptr;
+      if(buf[0]=='<')
+       swallow=1;
+
+      checked=1;
     }
-    else if( control == IOBUFCTRL_DESC )
-       *(char**)buf = "urlencode_filter";
-    return rc;
+
+  if(swallow || fwrite(ptr,size,nmemb,stream)==nmemb)
+    return size*nmemb;
+  else
+    return 0;
 }
 
-/* Returns 0 on success, -1 on failure, and 1 on eof */
-int send_key(void)
+/* Append but avoid creating a double slash // in the path. */
+static char *
+append_path(char *dest,const char *src)
 {
-  int rc,gotit=0,ret=-1;
-  char keyid[17];
-  char *request;
-  struct http_context hd;
-  unsigned int status;
-  IOBUF temp = iobuf_temp();
+  size_t n=strlen(dest);
+
+  if(src[0]=='/' && n>0 && dest[n-1]=='/')
+    dest[n-1]='\0';
+
+  return strcat(dest,src);
+}
+
+int
+send_key(int *eof)
+{
+  CURLcode res;
+  char request[MAX_URL+15];
+  int begin=0,end=0,ret=KEYSERVER_INTERNAL_ERROR;
+  char keyid[17],state[6];
   char line[MAX_LINE];
+  char *key=NULL,*encoded_key=NULL;
+  size_t keysize=1;
 
-  request=malloc(strlen(host)+100);
+  key = xtrymalloc(1);
+  if(!key)
+    {
+      fprintf(console,"gpgkeys: unable to allocate memory for key\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
 
-  iobuf_push_filter(temp,urlencode_filter,NULL);
+  key[0]='\0';
 
   /* Read and throw away input until we see the BEGIN */
 
   while(fgets(line,MAX_LINE,input)!=NULL)
-    if(sscanf(line,"KEY %16s BEGIN\n",keyid)==1)
+    if(sscanf(line,"KEY%*[ ]%16s%*[ ]%5s\n",keyid,state)==2
+       && strcmp(state,"BEGIN")==0)
       {
-       gotit=1;
+       begin=1;
        break;
       }
 
-  if(!gotit)
+  if(!begin)
     {
-      /* i.e. eof before the KEY BEGIN was found */
-      ret=1;
+      /* i.e. eof before the KEY BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
       goto fail;
     }
 
-  gotit=0;
-
   /* Now slurp up everything until we see the END */
 
   while(fgets(line,MAX_LINE,input))
-    if(sscanf(line,"KEY %16s END\n",keyid)==1)
+    if(sscanf(line,"KEY%*[ ]%16s%*[ ]%3s\n",keyid,state)==2
+       && strcmp(state,"END")==0)
       {
-       gotit=1;
+       end=1;
        break;
       }
     else
-      if(iobuf_writestr(temp,line))
-       {
-         fprintf(console,"gpgkeys: internal iobuf error\n");
-         goto fail;
-       }
+      {
+       char *tempkey;
+       keysize+=strlen(line);
+       tempkey=realloc(key,keysize);
+       if(tempkey==NULL)
+         {
+           fprintf(console,"gpgkeys: unable to reallocate for key\n");
+           ret=KEYSERVER_NO_MEMORY;
+           goto fail;
+         }
+       else
+         key=tempkey;
+
+       strcat(key,line);
+      }
 
-  if(!gotit)
+  if(!end)
     {
       fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
       goto fail;
     }
 
-  iobuf_flush_temp(temp);
-
-  sprintf(request,"x-hkp://%s%s%s/pks/add",
-         host,port[0]?":":"",port[0]?port:"");
-
-  if(verbose>2)
-    fprintf(console,"gpgkeys: HTTP URL is \"%s\"\n",request);
-
-  rc=http_open(&hd,HTTP_REQ_POST,request,http_flags);
-  if(rc)
+  encoded_key=curl_easy_escape(curl,key,keysize);
+  if(!encoded_key)
     {
-      fprintf(console,"gpgkeys: unable to connect to `%s'\n",host);
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
       goto fail;
     }
 
-  sprintf(request,"Content-Length: %u\n",
-         (unsigned)iobuf_get_temp_length(temp)+9);
-  iobuf_writestr(hd.fp_write,request);
-
-  http_start_data(&hd);
+  free(key);
 
-  iobuf_writestr(hd.fp_write,"keytext=");
-  iobuf_write(hd.fp_write,
-             iobuf_get_temp_buffer(temp),iobuf_get_temp_length(temp));
-  iobuf_put(hd.fp_write,'\n');
-
-  rc=http_wait_response(&hd,&status);
-  if(rc)
+  key=xtrymalloc(8+strlen(encoded_key)+1);
+  if(!key)
     {
-      fprintf(console,"gpgkeys: error sending to `%s': %s\n",
-             host,g10_errstr(rc));
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
       goto fail;
     }
 
-  if((status/100)!=2)
+  strcpy(key,"keytext=");
+  strcat(key,encoded_key);
+
+  strcpy(request,proto);
+  strcat(request,"://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  strcat(request,port);
+  strcat(request,opt->path);
+  /* request is MAX_URL+15 bytes long - MAX_URL covers the whole URL,
+     including any supplied path.  The 15 covers /pks/add. */
+  append_path(request,"/pks/add");
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
+
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_POST,1L);
+  curl_easy_setopt(curl,CURLOPT_POSTFIELDS,key);
+  curl_easy_setopt(curl,CURLOPT_FAILONERROR,1L);
+
+  res=curl_easy_perform(curl);
+  if(res!=0)
     {
-      fprintf(console,"gpgkeys: remote server returned error %d\n",status);
-      fprintf(output,"KEY %s FAILED\n",keyid);
+      fprintf(console,"gpgkeys: HTTP post error %d: %s\n",res,errorbuffer);
+      ret=curl_err_to_gpg_err(res);
       goto fail;
     }
+  else
+    fprintf(output,"\nKEY %s SENT\n",keyid);
 
-  ret=0;
+  ret=KEYSERVER_OK;
 
  fail:
-  free(request);
-  iobuf_close(temp);
-  http_close(&hd);
+  free(key);
+  curl_free(encoded_key);
+
+  if(ret!=0 && begin)
+    fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
 
   return ret;
 }
 
-int get_key(char *getkey)
+static int
+get_key(char *getkey)
 {
-  int rc,gotit=0;
-  unsigned int maxlen=1024,buflen=0;
-  char search[29];
-  char *request;
-  struct http_context hd;
-  byte *line=NULL;
+  CURLcode res;
+  char request[MAX_URL+92];
+  char *offset;
+  struct curl_writer_ctx ctx;
+  size_t keylen;
+
+  memset(&ctx,0,sizeof(ctx));
 
   /* Build the search string.  HKP only uses the short key IDs. */
 
   if(strncmp(getkey,"0x",2)==0)
     getkey+=2;
 
+  fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+
   if(strlen(getkey)==32)
     {
       fprintf(console,
              "gpgkeys: HKP keyservers do not support v3 fingerprints\n");
-      fprintf(output,"KEY 0x%s BEGIN\n",getkey);
-      fprintf(output,"KEY 0x%s FAILED\n",getkey);
-      return -1;
-    }
-
- if(strlen(getkey)>8)
-    {
-      char *offset=&getkey[strlen(getkey)-8];
-
-      /* fingerprint or long key id.  Take the last 8 characters and
-         treat it like a short key id */
-
-      sprintf(search,"0x%.8s",offset);
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey,KEYSERVER_NOT_SUPPORTED);
+      return KEYSERVER_NOT_SUPPORTED;
     }
- else
-   {
-      /* short key id */
-    
-      sprintf(search,"0x%.8s",getkey);
-    }
-
-  fprintf(output,"KEY 0x%s BEGIN\n",getkey);
 
-  if(verbose)
-    fprintf(console,"gpgkeys: requesting key 0x%s from hkp://%s%s%s\n",
-           getkey,host,port[0]?":":"",port[0]?port:"");
+  strcpy(request,proto);
+  strcat(request,"://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  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 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");
+
+  /* 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;
 
-  request=malloc(strlen(host)+100);
+  strcat(request,offset);
 
-  sprintf(request,"x-hkp://%s%s%s/pks/lookup?op=get&search=%s",
-         host,port[0]?":":"",port[0]?port:"", search);
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
 
-  if(verbose>2)
-    fprintf(console,"gpgkeys: HTTP URL is \"%s\"\n",request);
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
+  ctx.stream=output;
+  curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
 
-  rc=http_open_document(&hd,request,http_flags);
-  if(rc!=0)
+  res=curl_easy_perform(curl);
+  if(res!=CURLE_OK)
     {
-      fprintf(console,"gpgkeys: HKP fetch error: %s\n",
-             rc==G10ERR_NETWORK?strerror(errno):g10_errstr(rc));
-      fprintf(output,"KEY 0x%s FAILED\n",getkey);
+      fprintf(console,"gpgkeys: HTTP fetch error %d: %s\n",res,errorbuffer);
+      fprintf(output,"\nKEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
     }
   else
     {
-      while(iobuf_read_line(hd.fp_read,&line,&buflen,&maxlen))
+      curl_writer_finalize(&ctx);
+      if(!ctx.flags.done)
        {
-         if(gotit)
-           {
-             fprintf(output,line);
-             if(strcmp(line,"-----END PGP PUBLIC KEY BLOCK-----\n")==0)
-               {
-                 gotit=0;
-                 fprintf(output,"KEY 0x%s END\n",getkey);
-                 break;
-               }
-           }
-         else
-           if(strcmp(line,"-----BEGIN PGP PUBLIC KEY BLOCK-----\n")==0)
-             {
-               fprintf(output,line);
-               gotit=1;
-             }
+         fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+         fprintf(output,"\nKEY 0x%s FAILED %d\n",
+                 getkey,KEYSERVER_KEY_NOT_FOUND);
        }
+      else
+       fprintf(output,"\nKEY 0x%s END\n",getkey);
     }
 
-  m_free(line);
-  free(request);
-
-  return 0;
+  return KEYSERVER_OK;
 }
 
-/* Remove anything <between brackets> and de-urlencode in place.  Note
-   that this requires all brackets to be closed on the same line.  It
-   also means that the result is never larger than the input. */
-static void
-dehtmlize(char *line)
+static int
+get_name(const char *getkey)
 {
-  int parsedindex=0;
-  char *parsed=line;
+  CURLcode res;
+  char *request=NULL;
+  char *searchkey_encoded;
+  int ret=KEYSERVER_INTERNAL_ERROR;
+  struct curl_writer_ctx ctx;
 
-  while(*line!='\0')
+  memset(&ctx,0,sizeof(ctx));
+
+  searchkey_encoded=curl_easy_escape(curl,(char *)getkey,0);
+  if(!searchkey_encoded)
     {
-      switch(*line)
-       {
-       case '<':
-         while(*line!='>' && *line!='\0')
-           line++;
-
-         if(*line!='\0')
-           line++;
-         break;
-
-       case '&':
-         if((*(line+1)!='\0' && tolower(*(line+1))=='l') &&
-            (*(line+2)!='\0' && tolower(*(line+2))=='t') &&
-            (*(line+3)!='\0' && *(line+3)==';'))
-           {
-             parsed[parsedindex++]='<';
-             line+=4;
-             break;
-           }
-         else if((*(line+1)!='\0' && tolower(*(line+1))=='g') &&
-                 (*(line+2)!='\0' && tolower(*(line+2))=='t') &&
-                 (*(line+3)!='\0' && *(line+3)==';'))
-           {
-             parsed[parsedindex++]='>';
-             line+=4;
-             break;
-           }
-         else if((*(line+1)!='\0' && tolower(*(line+1))=='a') &&
-                 (*(line+2)!='\0' && tolower(*(line+2))=='m') &&
-                 (*(line+3)!='\0' && tolower(*(line+3))=='p') &&
-                 (*(line+4)!='\0' && *(line+4)==';'))
-           {
-             parsed[parsedindex++]='&';
-             line+=5;
-             break;
-           }
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
 
-       default:
-         parsed[parsedindex++]=*line;
-         line++;
-         break;
-       }
+  request=xtrymalloc(MAX_URL+60+strlen(searchkey_encoded));
+  if(!request)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
     }
 
-  parsed[parsedindex]='\0';
+  fprintf(output,"NAME %s BEGIN\n",getkey);
 
-  /* Chop off any trailing whitespace.  Note that the HKP servers have
-     \r\n as line endings, and the NAI HKP servers have just \n. */
+  strcpy(request,proto);
+  strcat(request,"://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  strcat(request,port);
+  strcat(request,opt->path);
+  append_path(request,"/pks/lookup?op=get&options=mr&search=");
+  strcat(request,searchkey_encoded);
 
-  if(parsedindex>0)
-    {
-      parsedindex--;
-      while(isspace(parsed[parsedindex]))
-       {
-         parsed[parsedindex]='\0';
-         parsedindex--;
-       }
-    }
-}
+  if(opt->action==KS_GETNAME)
+    strcat(request,"&exact=on");
 
-static int
-write_quoted(IOBUF a, const char *buf, char delim)
-{
-  char quoted[5];
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
 
-  sprintf(quoted,"\\x%02X",delim);
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
+  ctx.stream=output;
+  curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
 
-  while(*buf)
+  res=curl_easy_perform(curl);
+  if(res!=CURLE_OK)
     {
-      if(*buf==delim)
-       {
-         if(iobuf_writestr(a,quoted))
-           return -1;
-       }
-      else if(*buf=='\\')
+      fprintf(console,"gpgkeys: HTTP fetch error %d: %s\n",res,errorbuffer);
+      ret=curl_err_to_gpg_err(res);
+    }
+  else
+    {
+      curl_writer_finalize(&ctx);
+      if(!ctx.flags.done)
        {
-         if(iobuf_writestr(a,"\\x5c"))
-           return -1;
+         fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+         ret=KEYSERVER_KEY_NOT_FOUND;
        }
       else
        {
-         if(iobuf_writebyte(a,*buf))
-           return -1;
+         fprintf(output,"\nNAME %s END\n",getkey);
+         ret=KEYSERVER_OK;
        }
-
-      buf++;
     }
 
-  return 0;
-}
+ fail:
+  curl_free(searchkey_encoded);
+  free(request);
 
-/* pub  2048/<a href="/pks/lookup?op=get&search=0x3CB3B415">3CB3B415</a> 1998/04/03 David M. Shaw &lt;<a href="/pks/lookup?op=get&search=0x3CB3B415">dshaw@jabberwocky.com</a>&gt; */
+  if(ret!=KEYSERVER_OK)
+    fprintf(output,"\nNAME %s FAILED %d\n",getkey,ret);
 
-/* Luckily enough, both the HKP server and NAI HKP interface to their
-   LDAP server are close enough in output so the same function can
-   parse them both. */
+  return ret;
+}
 
-static int 
-parse_hkp_index(IOBUF buffer,char *line)
+static int
+search_key(const char *searchkey)
 {
-  static int open=0,revoked=0;
-  static char *key=NULL,*type=NULL,*uid=NULL;
-  static u32 bits,createtime;
-  int ret=0;
-
-  /*  printf("Open %d, LINE: \"%s\", uid: %s\n",open,line,uid); */
+  CURLcode res;
+  char *request=NULL;
+  char *searchkey_encoded;
+  int ret=KEYSERVER_INTERNAL_ERROR;
+  enum ks_search_type search_type;
 
-  dehtmlize(line);
+  search_type=classify_ks_search(&searchkey);
 
-  /*  printf("Now open %d, LINE: \"%s\", uid: %s\n",open,line,uid); */
+  if(opt->debug)
+    fprintf(console,"gpgkeys: search type is %d, and key is \"%s\"\n",
+           search_type,searchkey);
 
-  /* Try and catch some bastardization of HKP.  If we don't have
-     certain unchanging landmarks, we can't reliably parse the
-     response.  This only complains about problems within the key
-     section itself.  Headers and footers should not matter. */
-  if(open && line[0]!='\0' &&
-     ascii_memcasecmp(line,"pub ",4)!=0 &&
-     ascii_memcasecmp(line,"    ",4)!=0)
+  searchkey_encoded=curl_easy_escape(curl,(char *)searchkey,0);
+  if(!searchkey_encoded)
     {
-      free(key);
-      free(uid);
-      fprintf(console,"gpgkeys: this keyserver does not support searching\n");
-      return -1;
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
     }
 
-  /* For multiple UIDs */
-  if(open && uid!=NULL)
+  request=xtrymalloc(MAX_URL+60+strlen(searchkey_encoded));
+  if(!request)
     {
-      ret=0;
-
-      if(!(revoked && !include_revoked))
-       {
-         char intstr[11];
-
-         if(key)
-           write_quoted(buffer,key,':');
-         iobuf_writestr(buffer,":");
-         write_quoted(buffer,uid,':');
-         iobuf_writestr(buffer,":");
-         iobuf_writestr(buffer,revoked?"1:":":");
-         sprintf(intstr,"%u",createtime);
-         write_quoted(buffer,intstr,':');
-         iobuf_writestr(buffer,":::");
-         if(type)
-           write_quoted(buffer,type,':');
-         iobuf_writestr(buffer,":");
-         sprintf(intstr,"%u",bits);
-         write_quoted(buffer,intstr,':');
-         iobuf_writestr(buffer,"\n");
-
-         ret=1;
-       }
-
-      if(strncmp(line,"    ",4)!=0)
-       {
-         revoked=0;
-         free(key);
-         free(uid);
-         uid=NULL;
-         open=0;
-       }
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
     }
 
-  if(ascii_memcasecmp(line,"pub ",4)==0)
-    {
-      char *tok,*temp;
-
-      open=1;
-
-      line+=4;
-
-      tok=strsep(&line,"/");
-      if(tok==NULL)
-       return ret;
+  fprintf(output,"SEARCH %s BEGIN\n",searchkey);
 
-      if(tok[strlen(tok)-1]=='R')
-       type="RSA";
-      else if(tok[strlen(tok)-1]=='D')
-       type="DSA";
-      else
-       type=NULL;
+  strcpy(request,proto);
+  strcat(request,"://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  strcat(request,port);
+  strcat(request,opt->path);
+  append_path(request,"/pks/lookup?op=index&options=mr&search=");
 
-      bits=atoi(tok);
+  /* HKP keyservers like the 0x to be present when searching by
+     keyid */
+  if(search_type==KS_SEARCH_KEYID_SHORT || search_type==KS_SEARCH_KEYID_LONG)
+    strcat(request,"0x");
 
-      tok=strsep(&line," ");
-      if(tok==NULL)
-       return ret;
+  strcat(request,searchkey_encoded);
 
-      key=strdup(tok);
+  if(search_type!=KS_SEARCH_SUBSTR)
+    strcat(request,"&exact=on");
 
-      tok=strsep(&line," ");
-      if(tok==NULL)
-       return ret;
-  
-      /* The date parser wants '-' instead of '/', so... */
-      temp=tok;
-      while(*temp!='\0')
-       {
-         if(*temp=='/')
-           *temp='-';
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
 
-         temp++;
-       }
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_mrindex_writer);
+  curl_easy_setopt(curl,CURLOPT_FILE,output);
 
-      createtime=scan_isodatestr(tok);
+  res=curl_easy_perform(curl);
+  if(res!=0)
+    {
+      fprintf(console,"gpgkeys: HTTP search error %d: %s\n",res,errorbuffer);
+      ret=curl_err_to_gpg_err(res);
     }
-
-  if(open)
+  else
     {
-      if(line==NULL)
-       {
-         uid=strdup("Key index corrupted");
-         return ret;
-       }
-
-      while(*line==' ' && *line!='\0')
-       line++;
+      fprintf(output,"\nSEARCH %s END\n",searchkey);
+      ret=KEYSERVER_OK;
+    }
 
-      if(*line=='\0')
-       return ret;
+ fail:
 
-      if(strncmp(line,"*** KEY REVOKED ***",19)==0)
-       {
-         revoked=1;
-         return ret;
-       }
+  curl_free(searchkey_encoded);
+  free(request);
 
-      uid=strdup(line);
-    }
+  if(ret!=KEYSERVER_OK)
+    fprintf(output,"\nSEARCH %s FAILED %d\n",searchkey,ret);
 
   return ret;
 }
 
-int search_key(char *searchkey)
+void
+fail_all(struct keylist *keylist,int err)
 {
-  int max=0,len=0,ret=-1,rc;
-  struct http_context hd;
-  char *search=NULL,*request=searchkey;
-  byte *line=NULL;
-
-  fprintf(output,"SEARCH %s BEGIN\n",searchkey);
-
-  /* Build the search string.  It's going to need url-encoding. */
+  if(!keylist)
+    return;
 
-  while(*request!='\0')
+  if(opt->action==KS_SEARCH)
     {
-      if(max-len<3)
-       {
-         max+=100;
-         search=realloc(search,max+1); /* Note +1 for \0 */
-       }
-
-      if(isalnum(*request) || *request=='-')
-       search[len++]=*request;
-      else if(*request==' ')
-       search[len++]='+';
-      else
+      fprintf(output,"SEARCH ");
+      while(keylist)
        {
-         sprintf(&search[len],"%%%02X",*request);
-         len+=3;
+         fprintf(output,"%s ",keylist->str);
+         keylist=keylist->next;
        }
-
-      request++;
+      fprintf(output,"FAILED %d\n",err);
     }
+  else
+    while(keylist)
+      {
+       fprintf(output,"KEY %s FAILED %d\n",keylist->str,err);
+       keylist=keylist->next;
+      }
+}
 
-  search[len]='\0';
+#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 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! */
 
-  fprintf(console,("gpgkeys: searching for \"%s\" from HKP server %s\n"),
-         searchkey,host);
+#define HOST_HEADER "Host:"
 
-  request=malloc(strlen(host)+100+strlen(search));
+static void
+srv_replace(const char *srvtag,
+           struct curl_slist **headers, struct curl_slist **resolve)
+{
+  struct srventry *srvlist=NULL;
+  int srvcount, srvindex;
+  char *portstr;
 
-  sprintf(request,"x-hkp://%s%s%s/pks/lookup?op=index&search=%s",
-         host,port[0]?":":"",port[0]?port:"",search);
+  if(!srvtag)
+    return;
 
- if(verbose>2)
-    fprintf(console,"gpgkeys: HTTP URL is \"%s\"\n",request);
+  portstr=malloc (MAX_PORT);
+  if(!portstr)
+    return;
 
-  rc=http_open_document(&hd,request,http_flags);
-  if(rc)
+  if(1+strlen(srvtag)+6+strlen(opt->host)+1<=MAXDNAME)
     {
-      fprintf(console,"gpgkeys: can't search keyserver `%s': %s\n",
-             host,rc==G10ERR_NETWORK?strerror(errno):g10_errstr(rc));
+      char srvname[MAXDNAME];
+
+      strcpy(srvname,"_");
+      strcat(srvname,srvtag);
+      strcat(srvname,"._tcp.");
+      strcat(srvname,opt->host);
+      srvcount=getsrv(srvname,&srvlist);
     }
-  else
-    {
-      unsigned int maxlen=1024,buflen=0;
-      int count=1;
-      IOBUF buffer;
 
-      buffer=iobuf_temp();
+  for(srvindex=0 ; srvindex<srvcount && portstr ; srvindex++)
+    {
+      struct addrinfo hints, *res;
 
-      rc=1;
+      sprintf (portstr, "%hu", srvlist[srvindex].port);
+      memset (&hints, 0, sizeof (hints));
+      hints.ai_socktype = SOCK_STREAM;
 
-      while(rc!=0)
+      if (getaddrinfo (srvlist[srvindex].target, portstr, &hints, &res) == 0)
        {
-         /* This is a judgement call.  Is it better to slurp up all
-             the results before prompting the user?  On the one hand,
-             it probably makes the keyserver happier to not be blocked
-             on sending for a long time while the user picks a key.
-             On the other hand, it might be nice for the server to be
-             able to stop sending before a large search result page is
-             complete. */
-
-         rc=iobuf_read_line(hd.fp_read,&line,&buflen,&maxlen);
-
-         ret=parse_hkp_index(buffer,line);
-         if(ret==-1)
-           break;
-
-         if(rc!=0)
-           count+=ret;
-       }
-
-      http_close(&hd);
+         /* 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;
 
-      count--;
+             entry=malloc (strlen(opt->host)+1
+                           +strlen(portstr)+1+strlen(ipaddr)+1);
 
-      if(ret>-1)
-       fprintf(output,"COUNT %d\n%s",count,iobuf_get_temp_buffer(buffer));
+             host=malloc (strlen(HOST_HEADER)+1+strlen(opt->host)+1);
 
-      fprintf(output,"SEARCH %s END\n",searchkey);
+             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;
+                       }
+                   }
+               }
 
-      iobuf_close(buffer);
-      m_free(line);
+             free (entry);
+             free (host);
+           }
 
-      ret=0;
+         freeaddrinfo (res);
+       }
+      else
+       continue; /* Not found */
     }
 
-  free(request);
-  free(search);
+  free (srvlist);
+  free (portstr);
+}
+#endif
 
-  return ret;
+static void
+show_help (FILE *fp)
+{
+  fprintf (fp,"-h, --help\thelp\n");
+  fprintf (fp,"-V\t\tmachine readable version\n");
+  fprintf (fp,"--version\thuman readable version\n");
+  fprintf (fp,"-o\t\toutput to this file\n");
 }
 
-int main(int argc,char *argv[])
+int
+main(int argc,char *argv[])
 {
-  int arg,action=-1,ret=KEYSERVER_INTERNAL_ERROR;
+  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;
 
-  fprintf(console,
-         "gpgkeys: WARNING: this is an *experimental* HKP interface!\n");
+  /* Kludge to implement standard GNU options.  */
+  if (argc > 1 && !strcmp (argv[1], "--version"))
+    {
+      printf ("gpgkeys_hkp (GnuPG) %s\n", VERSION);
+      printf ("Uses: %s\n", curl_version());
+      return 0;
+    }
+  else if (argc > 1 && !strcmp (argv[1], "--help"))
+    {
+      show_help (stdout);
+      return 0;
+    }
 
-  while((arg=getopt(argc,argv,"ho:"))!=-1)
+  while((arg=getopt(argc,argv,"hVo:"))!=-1)
     switch(arg)
       {
       default:
       case 'h':
-       fprintf(console,"-h\thelp\n");
-       fprintf(console,"-o\toutput to this file\n");
+        show_help (console);
+       return KEYSERVER_OK;
+
+      case 'V':
+       fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
        return KEYSERVER_OK;
 
       case 'o':
        output=fopen(optarg,"w");
        if(output==NULL)
          {
-           fprintf(console,"gpgkeys: Cannot open output file \"%s\": %s\n",
+           fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
                    optarg,strerror(errno));
            return KEYSERVER_INTERNAL_ERROR;
          }
@@ -650,7 +688,7 @@ int main(int argc,char *argv[])
       input=fopen(argv[optind],"r");
       if(input==NULL)
        {
-         fprintf(console,"gpgkeys: Cannot open input file \"%s\": %s\n",
+         fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
                  argv[optind],strerror(errno));
          return KEYSERVER_INTERNAL_ERROR;
        }
@@ -662,111 +700,192 @@ int main(int argc,char *argv[])
   if(output==NULL)
     output=stdout;
 
+  opt=init_ks_options();
+  if(!opt)
+    return KEYSERVER_NO_MEMORY;
+
   /* Get the command and info block */
 
   while(fgets(line,MAX_LINE,input)!=NULL)
     {
-      int version;
-      char commandstr[7];
-      char optionstr[30];
-      char hash;
+      int err;
+      char option[MAX_OPTION+1];
 
       if(line[0]=='\n')
        break;
 
-      if(sscanf(line,"%c",&hash)==1 && hash=='#')
-       continue;
-
-      if(sscanf(line,"COMMAND %6s\n",commandstr)==1)
+      err=parse_ks_options(line,opt);
+      if(err>0)
        {
-         commandstr[6]='\0';
-
-         if(strcasecmp(commandstr,"get")==0)
-           action=GET;
-         else if(strcasecmp(commandstr,"send")==0)
-           action=SEND;
-         else if(strcasecmp(commandstr,"search")==0)
-           action=SEARCH;
-
-         continue;
-       }
-
-      if(sscanf(line,"HOST %79s\n",host)==1)
-       {
-         host[79]='\0';
-         continue;
-       }
-
-      if(sscanf(line,"PORT %9s\n",port)==1)
-       {
-         port[9]='\0';
-         continue;
-       }
-
-      if(sscanf(line,"VERSION %d\n",&version)==1)
-       {
-         if(version!=0)
-           {
-             ret=KEYSERVER_VERSION_ERROR;
-             goto fail;
-           }
-
-         continue;
+         ret=err;
+         goto fail;
        }
+      else if(err==0)
+       continue;
 
-      if(sscanf(line,"OPTION %29s\n",optionstr)==1)
+      if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
        {
          int no=0;
-         char *start=&optionstr[0];
+         char *start=&option[0];
 
-         optionstr[29]='\0';
+         option[MAX_OPTION]='\0';
 
-         if(strncasecmp(optionstr,"no-",3)==0)
+         if(ascii_strncasecmp(option,"no-",3)==0)
            {
              no=1;
-             start=&optionstr[3];
+             start=&option[3];
            }
 
-         if(strcasecmp(start,"verbose")==0)
-           {
-             if(no)
-               verbose--;
-             else
-               verbose++;
-           }
-         else if(strcasecmp(start,"include-revoked")==0)
-           {
-             if(no)
-               include_revoked=0;
-             else
-               include_revoked=1;
-           }
-         else if(strcasecmp(start,"honor-http-proxy")==0)
+         if(ascii_strncasecmp(start,"http-proxy",10)==0)
            {
              if(no)
-               http_flags&=~HTTP_FLAG_TRY_PROXY;
-             else
-               http_flags|=HTTP_FLAG_TRY_PROXY;
-
+               {
+                 free(proxy);
+                 proxy=strdup("");
+               }
+             else if(start[10]=='=')
+               {
+                 if(strlen(&start[11])<MAX_PROXY)
+                   {
+                     free(proxy);
+                     proxy=strdup(&start[11]);
+                   }
+               }
            }
-         else if(strcasecmp(start,"broken-http-proxy")==0)
+         else if(ascii_strcasecmp(start,"try-dns-srv")==0)
            {
              if(no)
-               http_flags&=~HTTP_FLAG_NO_SHUTDOWN;
+               try_srv=0;
              else
-               http_flags|=HTTP_FLAG_NO_SHUTDOWN;
+               try_srv=1;
            }
 
          continue;
        }
     }
 
+
+  if(!opt->scheme)
+    {
+      fprintf(console,"gpgkeys: no scheme supplied!\n");
+      ret=KEYSERVER_SCHEME_NOT_FOUND;
+      goto fail;
+    }
+
+  /* Defaults */
+  if(ascii_strcasecmp(opt->scheme,"hkps")==0)
+    {
+      proto="https";
+      port="443";
+    }
+  else
+    {
+      proto="http";
+      port="11371";
+    }
+
+  if(!opt->host)
+    {
+      fprintf(console,"gpgkeys: no keyserver host provided\n");
+      goto fail;
+    }
+
+  if(opt->timeout && register_timeout()==-1)
+    {
+      fprintf(console,"gpgkeys: unable to register timeout handler\n");
+      return KEYSERVER_INTERNAL_ERROR;
+    }
+
+  curl_global_init(CURL_GLOBAL_DEFAULT);
+  curl=curl_easy_init();
+  if(!curl)
+    {
+      fprintf(console,"gpgkeys: unable to initialize curl\n");
+      ret=KEYSERVER_INTERNAL_ERROR;
+      goto fail;
+    }
+
+  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;
+
+      if(ascii_strcasecmp(opt->scheme,"hkp")==0)
+       srvtag="pgpkey-http";
+      else if(ascii_strcasecmp(opt->scheme,"hkps")==0)
+       srvtag="pgpkey-https";
+      else
+       srvtag=NULL;
+
+#ifdef HAVE_LIBCURL
+      /* We're using libcurl, so fake SRV support via our wrapper.
+        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. */
+#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. */
+      curl_easy_setopt(curl,CURLOPT_SRVTAG_GPG_HACK,srvtag);
+#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);
+
+  curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(long)opt->flags.check_cert);
+  curl_easy_setopt(curl,CURLOPT_CAINFO,opt->ca_cert_file);
+
+  /* Avoid caches to get the most recent copy of the key.  This is bug
+     #1061.  In pre-curl versions of the code, we didn't do it.  Then
+     we did do it (as a curl default) until curl changed the default.
+     Now we're doing it again, but in such a way that changing
+     defaults in the future won't impact us.  We set both the Pragma
+     and Cache-Control versions of the header, so we're good with both
+     HTTP 1.0 and 1.1. */
+  headers=curl_slist_append(headers,"Pragma: no-cache");
+  if(headers)
+    headers=curl_slist_append(headers,"Cache-Control: no-cache");
+
+  if(!headers)
+    {
+      fprintf(console,"gpgkeys: out of memory when building HTTP headers\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  curl_easy_setopt(curl,CURLOPT_HTTPHEADER,headers);
+
+  if(proxy)
+    curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
+
   /* If it's a GET or a SEARCH, the next thing to come in is the
      keyids.  If it's a SEND, then there are no keyids. */
 
-  if(action==SEND)
+  if(opt->action==KS_SEND)
     while(fgets(line,MAX_LINE,input)!=NULL && line[0]!='\n');
-  else if(action==GET || action==SEARCH)
+  else if(opt->action==KS_GET
+         || opt->action==KS_GETNAME || opt->action==KS_SEARCH)
     {
       for(;;)
        {
@@ -776,14 +895,15 @@ int main(int argc,char *argv[])
            break;
          else
            {
-             if(line[0]=='\n')
+             if(line[0]=='\n' || line[0]=='\0')
                break;
 
-             work=malloc(sizeof(struct keylist));
+             work=xtrymalloc(sizeof(struct keylist));
              if(work==NULL)
                {
                  fprintf(console,"gpgkeys: out of memory while "
                          "building key list\n");
+                 ret=KEYSERVER_NO_MEMORY;
                  goto fail;
                }
 
@@ -813,108 +933,106 @@ int main(int argc,char *argv[])
 
   /* Send the response */
 
-  fprintf(output,"VERSION 0\n");
-  fprintf(output,"PROGRAM %s\n\n",VERSION);
+  fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
+  fprintf(output,"PROGRAM %s %s\n\n",VERSION,curl_version());
 
-  if(verbose>1)
+  if(opt->verbose>1)
     {
-      fprintf(console,"Host:\t\t%s\n",host);
-      if(port[0])
-       fprintf(console,"Port:\t\t%s\n",port);
-      fprintf(console,"Command:\t%s\n",action==GET?"GET":
-             action==SEND?"SEND":"SEARCH");
+      fprintf(console,"Host:\t\t%s\n",opt->host);
+      if(opt->port)
+       fprintf(console,"Port:\t\t%s\n",opt->port);
+      if(strcmp(opt->path,"/")!=0)
+       fprintf(console,"Path:\t\t%s\n",opt->path);
+      fprintf(console,"Command:\t%s\n",ks_action_to_string(opt->action));
     }
 
-#if 0
-  if(verbose>1)
+  if(opt->action==KS_GET)
     {
-      vals=ldap_get_values(ldap,res,"software");
-      if(vals!=NULL)
-       {
-         fprintf(console,"Server: \t%s\n",vals[0]);
-         ldap_value_free(vals);
-       }
+      keyptr=keylist;
 
-      vals=ldap_get_values(ldap,res,"version");
-      if(vals!=NULL)
+      while(keyptr!=NULL)
        {
-         fprintf(console,"Version:\t%s\n",vals[0]);
-         ldap_value_free(vals);
+         set_timeout(opt->timeout);
+
+         if(get_key(keyptr->str)!=KEYSERVER_OK)
+           failed++;
+
+         keyptr=keyptr->next;
        }
     }
-#endif
-
-  switch(action)
+  else if(opt->action==KS_GETNAME)
     {
-    case GET:
       keyptr=keylist;
 
       while(keyptr!=NULL)
        {
-         if(get_key(keyptr->str)==-1)
+         set_timeout(opt->timeout);
+
+         if(get_name(keyptr->str)!=KEYSERVER_OK)
            failed++;
 
          keyptr=keyptr->next;
        }
-      break;
+    }
+  else if(opt->action==KS_SEND)
+    {
+      int eof=0;
 
-    case SEND:
-      {
-       int ret2;
+      do
+       {
+         set_timeout(opt->timeout);
 
-       do
-         {
-           ret2=send_key();
-           if(ret2==-1)
-             failed++;
-         }
-       while(ret2!=1);
-      }
-      break;
+         if(send_key(&eof)!=KEYSERVER_OK)
+           failed++;
+       }
+      while(!eof);
+    }
+  else if(opt->action==KS_SEARCH)
+    {
+      char *searchkey=NULL;
+      int len=0;
 
-    case SEARCH:
-      {
-       char *searchkey=NULL;
-       int len=0;
+      set_timeout(opt->timeout);
 
-       /* To search, we stick a space in between each key to search
-           for. */
+      /* To search, we stick a space in between each key to search
+        for. */
 
-       keyptr=keylist;
-       while(keyptr!=NULL)
-         {
-           len+=strlen(keyptr->str)+1;
-           keyptr=keyptr->next;
-         }
+      keyptr=keylist;
+      while(keyptr!=NULL)
+       {
+         len+=strlen(keyptr->str)+1;
+         keyptr=keyptr->next;
+       }
 
-       searchkey=malloc(len+1);
-       if(searchkey==NULL)
+      searchkey=xtrymalloc(len+1);
+      if(searchkey==NULL)
+       {
+         ret=KEYSERVER_NO_MEMORY;
+         fail_all(keylist,KEYSERVER_NO_MEMORY);
          goto fail;
+       }
 
-       searchkey[0]='\0';
+      searchkey[0]='\0';
 
-       keyptr=keylist;
-       while(keyptr!=NULL)
-         {
-           strcat(searchkey,keyptr->str);
-           strcat(searchkey," ");
-           keyptr=keyptr->next;
-         }
+      keyptr=keylist;
+      while(keyptr!=NULL)
+       {
+         strcat(searchkey,keyptr->str);
+         strcat(searchkey," ");
+         keyptr=keyptr->next;
+       }
 
-       /* Nail that last space */
+      /* Nail that last space */
+      if(*searchkey)
        searchkey[strlen(searchkey)-1]='\0';
 
-       if(search_key(searchkey)==-1)
-         {
-           fprintf(output,"SEARCH %s FAILED\n",searchkey);
-           failed++;
-         }
-
-       free(searchkey);
-      }
+      if(search_key(searchkey)!=KEYSERVER_OK)
+       failed++;
 
-      break;
+      free(searchkey);
     }
+  else
+    abort();
 
   if(!failed)
     ret=KEYSERVER_OK;
@@ -933,5 +1051,15 @@ int main(int argc,char *argv[])
   if(output!=stdout)
     fclose(output);
 
+  free_ks_options(opt);
+
+  curl_slist_free_all(headers);
+  curl_slist_free_all(resolve);
+
+  if(curl)
+    curl_easy_cleanup(curl);
+
+  free(proxy);
+
   return ret;
 }