7a4ec52c649b501e3434196c130c53bc328d7b31
[gnupg.git] / keyserver / gpgkeys_curl.c
1 /* gpgkeys_curl.c - fetch a key via libcurl
2  * Copyright (C) 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * In addition, as a special exception, the Free Software Foundation
20  * gives permission to link the code of the keyserver helper tools:
21  * gpgkeys_ldap, gpgkeys_curl and gpgkeys_hkp with the OpenSSL
22  * project's "OpenSSL" library (or with modified versions of it that
23  * use the same license as the "OpenSSL" library), and distribute the
24  * linked executables.  You must obey the GNU General Public License
25  * in all respects for all of the code used other than "OpenSSL".  If
26  * you modify this file, you may extend this exception to your version
27  * of the file, but you are not obligated to do so.  If you do not
28  * wish to do so, delete this exception statement from your version.
29  */
30
31 #include <config.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #ifdef HAVE_GETOPT_H
38 #include <getopt.h>
39 #endif
40 #ifdef HAVE_LIBCURL
41 #include <curl/curl.h>
42 #else
43 #include "curl-shim.h"
44 #endif
45 #include "keyserver.h"
46 #include "ksutil.h"
47
48 extern char *optarg;
49 extern int optind;
50
51 static FILE *input,*output,*console;
52 static CURL *curl;
53 static struct ks_options *opt;
54
55 static int
56 get_key(char *getkey)
57 {
58   CURLcode res;
59   char errorbuffer[CURL_ERROR_SIZE];
60   char request[MAX_URL];
61   struct curl_writer_ctx ctx;
62
63   memset(&ctx,0,sizeof(ctx));
64
65   if(strncmp(getkey,"0x",2)==0)
66     getkey+=2;
67
68   fprintf(output,"KEY 0x%s BEGIN\n",getkey);
69
70   sprintf(request,"%s://%s%s%s%s",opt->scheme,opt->host,
71           opt->port?":":"",opt->port?opt->port:"",opt->path?opt->path:"/");
72
73   curl_easy_setopt(curl,CURLOPT_URL,request);
74   curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
75   ctx.stream=output;
76   curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
77   curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
78
79   res=curl_easy_perform(curl);
80   if(res!=CURLE_OK)
81     {
82       fprintf(console,"gpgkeys: %s fetch error %d: %s\n",opt->scheme,
83               res,errorbuffer);
84       fprintf(output,"\nKEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
85     }
86   else
87     {
88       curl_writer_finalize(&ctx);
89       if(!ctx.flags.done)
90         {
91           fprintf(console,"gpgkeys: no key data found for %s\n",request);
92           fprintf(output,"\nKEY 0x%s FAILED %d\n",
93                   getkey,KEYSERVER_KEY_NOT_FOUND);
94         }
95       else
96         fprintf(output,"\nKEY 0x%s END\n",getkey);
97     }
98
99   return curl_err_to_gpg_err(res);
100 }
101
102 static void 
103 show_help (FILE *fp)
104 {
105   fprintf (fp,"-h, --help\thelp\n");
106   fprintf (fp,"-V\t\tmachine readable version\n");
107   fprintf (fp,"--version\thuman readable version\n");
108   fprintf (fp,"-o\t\toutput to this file\n");
109 }
110
111 int
112 main(int argc,char *argv[])
113 {
114   int arg,ret=KEYSERVER_INTERNAL_ERROR,i;
115   char line[MAX_LINE];
116   char *thekey=NULL;
117   long follow_redirects=5;
118   char *proxy=NULL;
119   curl_version_info_data *curldata;
120   struct curl_slist *headers=NULL;
121
122   console=stderr;
123
124   /* Kludge to implement standard GNU options.  */
125   if (argc > 1 && !strcmp (argv[1], "--version"))
126     {
127       printf ("gpgkeys_curl (GnuPG) %s\n", VERSION);
128       printf ("Uses: %s\n", curl_version());
129       return 0;
130     }
131   else if (argc > 1 && !strcmp (argv[1], "--help"))
132     {
133       show_help (stdout);
134       return 0;
135     }
136
137   while((arg=getopt(argc,argv,"hVo:"))!=-1)
138     switch(arg)
139       {
140       default:
141       case 'h':
142         show_help (console);
143         return KEYSERVER_OK;
144
145       case 'V':
146         fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
147         return KEYSERVER_OK;
148
149       case 'o':
150         output=fopen(optarg,"wb");
151         if(output==NULL)
152           {
153             fprintf(console,"gpgkeys: Cannot open output file '%s': %s\n",
154                     optarg,strerror(errno));
155             return KEYSERVER_INTERNAL_ERROR;
156           }
157
158         break;
159       }
160
161   if(argc>optind)
162     {
163       input=fopen(argv[optind],"r");
164       if(input==NULL)
165         {
166           fprintf(console,"gpgkeys: Cannot open input file '%s': %s\n",
167                   argv[optind],strerror(errno));
168           return KEYSERVER_INTERNAL_ERROR;
169         }
170     }
171
172   if(input==NULL)
173     input=stdin;
174
175   if(output==NULL)
176     output=stdout;
177
178   opt=init_ks_options();
179   if(!opt)
180     return KEYSERVER_NO_MEMORY;
181
182   /* Get the command and info block */
183
184   while(fgets(line,MAX_LINE,input)!=NULL)
185     {
186       int err;
187       char option[MAX_OPTION+1];
188
189       if(line[0]=='\n')
190         break;
191
192       err=parse_ks_options(line,opt);
193       if(err>0)
194         {
195           ret=err;
196           goto fail;
197         }
198       else if(err==0)
199         continue;
200
201       if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
202         {
203           int no=0;
204           char *start=&option[0];
205
206           option[MAX_OPTION]='\0';
207
208           if(strncasecmp(option,"no-",3)==0)
209             {
210               no=1;
211               start=&option[3];
212             }
213
214           if(strncasecmp(start,"http-proxy",10)==0)
215             {
216               /* Safe to not check the return code of strdup() here.
217                  If it fails, we simply won't use a proxy. */
218               if(no)
219                 {
220                   free(proxy);
221                   proxy=strdup("");
222                 }
223               else if(start[10]=='=')
224                 {
225                   if(strlen(&start[11])<MAX_PROXY)
226                     {
227                       free(proxy);
228                       proxy=strdup(&start[11]);
229                     }
230                 }
231             }
232           else if(strncasecmp(start,"follow-redirects",16)==0)
233             {
234               if(no)
235                 follow_redirects=0;
236               else if(start[16]=='=')
237                 follow_redirects=atoi(&start[17]);
238               else if(start[16]=='\0')
239                 follow_redirects=-1;
240             }
241
242           continue;
243         }
244     }
245
246   if(!opt->scheme)
247     {
248       fprintf(console,"gpgkeys: no scheme supplied!\n");
249       ret=KEYSERVER_SCHEME_NOT_FOUND;
250       goto fail;
251     }
252
253   if(!opt->host)
254     {
255       fprintf(console,"gpgkeys: no keyserver host provided\n");
256       goto fail;
257     }
258
259   if(opt->timeout && register_timeout()==-1)
260     {
261       fprintf(console,"gpgkeys: unable to register timeout handler\n");
262       return KEYSERVER_INTERNAL_ERROR;
263     }
264
265   curl_global_init(CURL_GLOBAL_DEFAULT);
266
267   curl=curl_easy_init();
268   if(!curl)
269     {
270       fprintf(console,"gpgkeys: unable to initialize curl\n");
271       ret=KEYSERVER_INTERNAL_ERROR;
272       goto fail;
273     }
274
275   /* Make sure we have the protocol the user is asking for so we can
276      print a nicer error message. */
277   curldata=curl_version_info(CURLVERSION_NOW);
278   for(i=0;curldata->protocols[i];i++)
279     if(strcasecmp(curldata->protocols[i],opt->scheme)==0)
280       break;
281
282   if(curldata->protocols[i]==NULL)
283     {
284       fprintf(console,"gpgkeys: protocol '%s' not supported\n",opt->scheme);
285       ret=KEYSERVER_SCHEME_NOT_FOUND;
286       goto fail;
287     }
288
289   if(follow_redirects)
290     {
291       curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1L);
292       if(follow_redirects>0)
293         curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
294     }
295
296   if(opt->auth)
297     curl_easy_setopt(curl,CURLOPT_USERPWD,opt->auth);
298
299   if(opt->debug)
300     {
301       fprintf(console,"gpgkeys: curl version = %s\n",curl_version());
302       curl_easy_setopt(curl,CURLOPT_STDERR,console);
303       curl_easy_setopt(curl,CURLOPT_VERBOSE,1L);
304     }
305
306   curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(long)opt->flags.check_cert);
307   curl_easy_setopt(curl,CURLOPT_CAINFO,opt->ca_cert_file);
308
309   /* Avoid caches to get the most recent copy of the key.  This is bug
310      #1061.  In pre-curl versions of the code, we didn't do it.  Then
311      we did do it (as a curl default) until curl changed the default.
312      Now we're doing it again, but in such a way that changing
313      defaults in the future won't impact us.  We set both the Pragma
314      and Cache-Control versions of the header, so we're good with both
315      HTTP 1.0 and 1.1. */
316   headers=curl_slist_append(headers,"Pragma: no-cache");
317   if(headers)
318     headers=curl_slist_append(headers,"Cache-Control: no-cache");
319
320   if(!headers)
321     {
322       fprintf(console,"gpgkeys: out of memory when building HTTP headers\n");
323       ret=KEYSERVER_NO_MEMORY;
324       goto fail;
325     }
326
327   curl_easy_setopt(curl,CURLOPT_HTTPHEADER,headers);
328
329   if(proxy)
330     curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
331
332   /* If it's a GET or a SEARCH, the next thing to come in is the
333      keyids.  If it's a SEND, then there are no keyids. */
334
335   if(opt->action==KS_GET)
336     {
337       /* Eat the rest of the file */
338       for(;;)
339         {
340           if(fgets(line,MAX_LINE,input)==NULL)
341             break;
342           else
343             {
344               if(line[0]=='\n' || line[0]=='\0')
345                 break;
346
347               if(!thekey)
348                 {
349                   thekey=strdup(line);
350                   if(!thekey)
351                     {
352                       fprintf(console,"gpgkeys: out of memory while "
353                               "building key list\n");
354                       ret=KEYSERVER_NO_MEMORY;
355                       goto fail;
356                     }
357
358                   /* Trim the trailing \n */
359                   thekey[strlen(line)-1]='\0';
360                 }
361             }
362         }
363     }
364   else
365     {
366       fprintf(console,
367               "gpgkeys: this keyserver type only supports key retrieval\n");
368       goto fail;
369     }
370
371   if(!thekey)
372     {
373       fprintf(console,"gpgkeys: invalid keyserver instructions\n");
374       goto fail;
375     }
376
377   /* Send the response */
378
379   fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
380   fprintf(output,"PROGRAM %s\n\n",VERSION);
381
382   if(opt->verbose)
383     {
384       fprintf(console,"Scheme:\t\t%s\n",opt->scheme);
385       fprintf(console,"Host:\t\t%s\n",opt->host);
386       if(opt->port)
387         fprintf(console,"Port:\t\t%s\n",opt->port);
388       if(opt->path)
389         fprintf(console,"Path:\t\t%s\n",opt->path);
390       fprintf(console,"Command:\tGET\n");
391     }
392
393   set_timeout(opt->timeout);
394
395   ret=get_key(thekey);
396
397  fail:
398
399   free(thekey);
400
401   if(input!=stdin)
402     fclose(input);
403
404   if(output!=stdout)
405     fclose(output);
406
407   free_ks_options(opt);
408
409   curl_slist_free_all(headers);
410
411   if(curl)
412     curl_easy_cleanup(curl);
413
414   free(proxy);
415
416   curl_global_cleanup();
417
418   return ret;
419 }