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