gpg: Fix honoring --cert-digest-algo when recreating a cert
[gnupg.git] / keyserver / gpgkeys_curl.c
index 1d206e0..28ec698 100644 (file)
@@ -1,11 +1,11 @@
 /* gpgkeys_curl.c - fetch a key via libcurl
- * Copyright (C) 2004 Free Software Foundation, Inc.
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008 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>
 #ifdef HAVE_GETOPT_H
 #include <getopt.h>
 #endif
+#ifdef HAVE_LIBCURL
 #include <curl/curl.h>
+#else
+#include "curl-shim.h"
+#endif
 #include "keyserver.h"
 #include "ksutil.h"
 
 extern char *optarg;
 extern int optind;
 
-#define GET         0
-#define MAX_SCHEME 20
-#define MAX_LINE   80
-#define MAX_PATH 1023
-#define MAX_AUTH  127
-#define MAX_HOST   79
-#define MAX_PORT    9
-#define MAX_URL (MAX_SCHEME+3+MAX_AUTH+1+1+MAX_HOST+1+1+MAX_PORT+1+1+MAX_PATH+1+50)
-
-#define STRINGIFY(x) #x
-#define MKSTRING(x) STRINGIFY(x)
-
-static int verbose=0;
-static char scheme[MAX_SCHEME+1],auth[MAX_AUTH+1],host[MAX_HOST+1]={'\0'},port[MAX_PORT+1]={'\0'},path[MAX_PATH+1]={'\0'};
-static FILE *input=NULL,*output=NULL,*console=NULL;
+static FILE *input,*output,*console;
 static CURL *curl;
-static char request[MAX_URL]={'\0'};
-
-static int
-curl_err_to_gpg_err(CURLcode error)
-{
-  switch(error)
-    {
-    case CURLE_FTP_COULDNT_RETR_FILE: return KEYSERVER_KEY_NOT_FOUND;
-    default: return KEYSERVER_INTERNAL_ERROR;
-    }
-}
-
-/* We wrap fwrite so to avoid DLL problems on Win32 (see curl faq for
-   more). */
-static size_t
-writer(const void *ptr,size_t size,size_t nmemb,void *stream)
-{
-  return fwrite(ptr,size,nmemb,stream);
-}
+static struct ks_options *opt;
 
 static int
 get_key(char *getkey)
 {
   CURLcode res;
   char errorbuffer[CURL_ERROR_SIZE];
+  char request[MAX_URL];
+  struct curl_writer_ctx ctx;
+
+  memset(&ctx,0,sizeof(ctx));
 
   if(strncmp(getkey,"0x",2)==0)
     getkey+=2;
 
   fprintf(output,"KEY 0x%s BEGIN\n",getkey);
 
-  sprintf(request,"%s://%s%s%s%s%s%s%s",scheme,auth[0]?auth:"",auth[0]?"@":"",
-         host,port[0]?":":"",port[0]?port:"",path[0]?"":"/",path);
+  sprintf(request,"%s://%s%s%s%s",opt->scheme,opt->host,
+         opt->port?":":"",opt->port?opt->port:"",opt->path?opt->path:"/");
 
   curl_easy_setopt(curl,CURLOPT_URL,request);
-  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writer);
-  curl_easy_setopt(curl,CURLOPT_FILE,output);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
+  ctx.stream=output;
+  curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
   curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
 
-  if(verbose>1)
-    {
-      curl_easy_setopt(curl,CURLOPT_STDERR,console);
-      curl_easy_setopt(curl,CURLOPT_VERBOSE,TRUE);
-    }
-
   res=curl_easy_perform(curl);
-  if(res!=0)
+  if(res!=CURLE_OK)
     {
-      fprintf(console,"gpgkeys: %s fetch error %d: %s\n",scheme,
+      fprintf(console,"gpgkeys: %s fetch error %d: %s\n",opt->scheme,
              res,errorbuffer);
-      fprintf(output,"KEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
+      fprintf(output,"\nKEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
     }
   else
-    fprintf(output,"KEY 0x%s END\n",getkey);
+    {
+      curl_writer_finalize(&ctx);
+      if(!ctx.flags.done)
+       {
+         fprintf(console,"gpgkeys: no key data found for %s\n",request);
+         fprintf(output,"\nKEY 0x%s FAILED %d\n",
+                 getkey,KEYSERVER_KEY_NOT_FOUND);
+       }
+      else
+       fprintf(output,"\nKEY 0x%s END\n",getkey);
+    }
 
-  return KEYSERVER_OK;
+  return curl_err_to_gpg_err(res);
 }
 
 static void 
 show_help (FILE *fp)
 {
-  fprintf (fp,"-h\thelp\n");
-  fprintf (fp,"-V\tversion\n");
-  fprintf (fp,"-o\toutput to this file\n");
+  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 arg,action=-1,ret=KEYSERVER_INTERNAL_ERROR;
+  int arg,ret=KEYSERVER_INTERNAL_ERROR,i;
   char line[MAX_LINE];
   char *thekey=NULL;
-  unsigned int timeout=DEFAULT_KEYSERVER_TIMEOUT;
+  long follow_redirects=5;
+  char *proxy=NULL;
+  curl_version_info_data *curldata;
+  struct curl_slist *headers=NULL;
 
   console=stderr;
 
   /* Kludge to implement standard GNU options.  */
   if (argc > 1 && !strcmp (argv[1], "--version"))
     {
-      fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
+      printf ("gpgkeys_curl (GnuPG) %s\n", VERSION);
+      printf ("Uses: %s\n", curl_version());
       return 0;
     }
   else if (argc > 1 && !strcmp (argv[1], "--help"))
@@ -179,131 +175,95 @@ 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[256];
-      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)
-       {
-         commandstr[6]='\0';
-
-         if(strcasecmp(commandstr,"get")==0)
-           action=GET;
-
-         continue;
-       }
-
-      if(sscanf(line,"SCHEME %" MKSTRING(MAX_SCHEME) "s\n",scheme)==1)
-       {
-         scheme[MAX_SCHEME]='\0';
-         continue;
-       }
-
-      if(sscanf(line,"AUTH %" MKSTRING(MAX_AUTH) "s\n",auth)==1)
-       {
-         auth[MAX_AUTH]='\0';
-         continue;
-       }
-
-      if(sscanf(line,"HOST %" MKSTRING(MAX_HOST) "s\n",host)==1)
-       {
-         host[MAX_HOST]='\0';
-         continue;
-       }
-
-      if(sscanf(line,"PORT %" MKSTRING(MAX_PORT) "s\n",port)==1)
-       {
-         port[MAX_PORT]='\0';
-         continue;
-       }
-
-      if(sscanf(line,"PATH %" MKSTRING(MAX_PATH) "s\n",path)==1)
+      err=parse_ks_options(line,opt);
+      if(err>0)
        {
-         path[MAX_PATH]='\0';
-         continue;
-       }
-
-      if(sscanf(line,"VERSION %d\n",&version)==1)
-       {
-         if(version!=KEYSERVER_PROTO_VERSION)
-           {
-             ret=KEYSERVER_VERSION_ERROR;
-             goto fail;
-           }
-
-         continue;
+         ret=err;
+         goto fail;
        }
+      else if(err==0)
+       continue;
 
-      if(sscanf(line,"OPTION %255s\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[255]='\0';
+         option[MAX_OPTION]='\0';
 
-         if(strncasecmp(optionstr,"no-",3)==0)
+         if(strncasecmp(option,"no-",3)==0)
            {
              no=1;
-             start=&optionstr[3];
+             start=&option[3];
            }
 
-         if(strcasecmp(start,"verbose")==0)
+         if(strncasecmp(start,"http-proxy",10)==0)
            {
+             /* Safe to not check the return code of strdup() here.
+                If it fails, we simply won't use a proxy. */
              if(no)
-               verbose--;
-             else
-               verbose++;
+               {
+                 free(proxy);
+                 proxy=strdup("");
+               }
+             else if(start[10]=='=')
+               {
+                 if(strlen(&start[11])<MAX_PROXY)
+                   {
+                     free(proxy);
+                     proxy=strdup(&start[11]);
+                   }
+               }
            }
-         else if(strncasecmp(start,"timeout",7)==0)
+         else if(strncasecmp(start,"follow-redirects",16)==0)
            {
              if(no)
-               timeout=0;
-             else
-               timeout=atoi(&start[8]);
+               follow_redirects=0;
+             else if(start[16]=='=')
+               follow_redirects=atoi(&start[17]);
+             else if(start[16]=='\0')
+               follow_redirects=-1;
            }
 
          continue;
        }
     }
 
-  if(scheme[0]=='\0')
+  if(!opt->scheme)
     {
       fprintf(console,"gpgkeys: no scheme supplied!\n");
-      return KEYSERVER_SCHEME_NOT_FOUND;
-    }
-#ifndef HTTP_SUPPORT
-  else if(strcasecmp(scheme,"http")==0)
-    {
-      fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
-      return KEYSERVER_SCHEME_NOT_FOUND;
+      ret=KEYSERVER_SCHEME_NOT_FOUND;
+      goto fail;
     }
-#endif /* HTTP_SUPPORT */
-#ifndef FTP_SUPPORT
-  else if(strcasecmp(scheme,"ftp")==0)
+
+  if(!opt->host)
     {
-      fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
-      return KEYSERVER_SCHEME_NOT_FOUND;
+      fprintf(console,"gpgkeys: no keyserver host provided\n");
+      goto fail;
     }
-#endif /* FTP_SUPPORT */
 
-  if(timeout && register_timeout()==-1)
+  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)
     {
@@ -312,10 +272,67 @@ main(int argc,char *argv[])
       goto fail;
     }
 
+  /* Make sure we have the protocol the user is asking for so we can
+     print a nicer error message. */
+  curldata=curl_version_info(CURLVERSION_NOW);
+  for(i=0;curldata->protocols[i];i++)
+    if(strcasecmp(curldata->protocols[i],opt->scheme)==0)
+      break;
+
+  if(curldata->protocols[i]==NULL)
+    {
+      fprintf(console,"gpgkeys: protocol `%s' not supported\n",opt->scheme);
+      ret=KEYSERVER_SCHEME_NOT_FOUND;
+      goto fail;
+    }
+
+  if(follow_redirects)
+    {
+      curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1L);
+      if(follow_redirects>0)
+       curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
+    }
+
+  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);
+
+  /* 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==GET)
+  if(opt->action==KS_GET)
     {
       /* Eat the rest of the file */
       for(;;)
@@ -351,7 +368,7 @@ main(int argc,char *argv[])
       goto fail;
     }
 
-  if(!thekey || !host[0])
+  if(!thekey)
     {
       fprintf(console,"gpgkeys: invalid keyserver instructions\n");
       goto fail;
@@ -362,23 +379,21 @@ main(int argc,char *argv[])
   fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
   fprintf(output,"PROGRAM %s\n\n",VERSION);
 
-  if(verbose)
+  if(opt->verbose)
     {
-      fprintf(console,"Scheme:\t\t%s\n",scheme);
-      fprintf(console,"Host:\t\t%s\n",host);
-      if(port[0])
-       fprintf(console,"Port:\t\t%s\n",port);
-      if(path[0])
-       fprintf(console,"Path:\t\t%s\n",path);
+      fprintf(console,"Scheme:\t\t%s\n",opt->scheme);
+      fprintf(console,"Host:\t\t%s\n",opt->host);
+      if(opt->port)
+       fprintf(console,"Port:\t\t%s\n",opt->port);
+      if(opt->path)
+       fprintf(console,"Path:\t\t%s\n",opt->path);
       fprintf(console,"Command:\tGET\n");
     }
 
-  set_timeout(timeout);
+  set_timeout(opt->timeout);
 
   ret=get_key(thekey);
 
-  curl_easy_cleanup(curl);
-
  fail:
 
   free(thekey);
@@ -389,6 +404,15 @@ main(int argc,char *argv[])
   if(output!=stdout)
     fclose(output);
 
+  free_ks_options(opt);
+
+  curl_slist_free_all(headers);
+
+  if(curl)
+    curl_easy_cleanup(curl);
+
+  free(proxy);
+
   curl_global_cleanup();
 
   return ret;