* curl-shim.c (curl_easy_perform): Minor cleanup of proxy code.
[gnupg.git] / keyserver / gpgkeys_curl.c
1 /* gpgkeys_curl.c - fetch a key via libcurl
2  * Copyright (C) 2004, 2005, 2006 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 2 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, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19  * USA.
20  */
21
22 #include <config.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #ifdef HAVE_GETOPT_H
29 #include <getopt.h>
30 #endif
31 #ifdef HAVE_LIBCURL
32 #include <curl/curl.h>
33 #else
34 #include "curl-shim.h"
35 #endif
36 #include "keyserver.h"
37 #include "ksutil.h"
38
39 extern char *optarg;
40 extern int optind;
41
42 static FILE *input,*output,*console;
43 static CURL *curl;
44 static struct ks_options *opt;
45
46 static int
47 get_key(char *getkey)
48 {
49   CURLcode res;
50   char errorbuffer[CURL_ERROR_SIZE];
51   char request[MAX_URL];
52   struct curl_writer_ctx ctx;
53
54   memset(&ctx,0,sizeof(ctx));
55
56   if(strncmp(getkey,"0x",2)==0)
57     getkey+=2;
58
59   fprintf(output,"KEY 0x%s BEGIN\n",getkey);
60
61   sprintf(request,"%s://%s%s%s%s",opt->scheme,opt->host,
62           opt->port?":":"",opt->port?opt->port:"",opt->path?opt->path:"/");
63
64   curl_easy_setopt(curl,CURLOPT_URL,request);
65   curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
66   ctx.stream=output;
67   curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
68   curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
69
70   res=curl_easy_perform(curl);
71   if(res!=CURLE_OK)
72     {
73       fprintf(console,"gpgkeys: %s fetch error %d: %s\n",opt->scheme,
74               res,errorbuffer);
75       fprintf(output,"\nKEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
76     }
77   else
78     {
79       curl_writer_finalize(&ctx);
80       if(!ctx.flags.done)
81         {
82           fprintf(console,"gpgkeys: no key data found for %s\n",request);
83           fprintf(output,"\nKEY 0x%s FAILED %d\n",
84                   getkey,KEYSERVER_KEY_NOT_FOUND);
85         }
86       else
87         fprintf(output,"\nKEY 0x%s END\n",getkey);
88     }
89
90   return curl_err_to_gpg_err(res);
91 }
92
93 static void 
94 show_help (FILE *fp)
95 {
96   fprintf (fp,"-h\thelp\n");
97   fprintf (fp,"-V\tversion\n");
98   fprintf (fp,"-o\toutput to this file\n");
99 }
100
101 int
102 main(int argc,char *argv[])
103 {
104   int arg,ret=KEYSERVER_INTERNAL_ERROR;
105   char line[MAX_LINE];
106   char *thekey=NULL;
107   long follow_redirects=5;
108   char *proxy=NULL;
109
110   console=stderr;
111
112   /* Kludge to implement standard GNU options.  */
113   if (argc > 1 && !strcmp (argv[1], "--version"))
114     {
115       fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
116       return 0;
117     }
118   else if (argc > 1 && !strcmp (argv[1], "--help"))
119     {
120       show_help (stdout);
121       return 0;
122     }
123
124   while((arg=getopt(argc,argv,"hVo:"))!=-1)
125     switch(arg)
126       {
127       default:
128       case 'h':
129         show_help (console);
130         return KEYSERVER_OK;
131
132       case 'V':
133         fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
134         return KEYSERVER_OK;
135
136       case 'o':
137         output=fopen(optarg,"wb");
138         if(output==NULL)
139           {
140             fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
141                     optarg,strerror(errno));
142             return KEYSERVER_INTERNAL_ERROR;
143           }
144
145         break;
146       }
147
148   if(argc>optind)
149     {
150       input=fopen(argv[optind],"r");
151       if(input==NULL)
152         {
153           fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
154                   argv[optind],strerror(errno));
155           return KEYSERVER_INTERNAL_ERROR;
156         }
157     }
158
159   if(input==NULL)
160     input=stdin;
161
162   if(output==NULL)
163     output=stdout;
164
165   opt=init_ks_options();
166   if(!opt)
167     return KEYSERVER_NO_MEMORY;
168
169   /* Get the command and info block */
170
171   while(fgets(line,MAX_LINE,input)!=NULL)
172     {
173       int err;
174       char option[MAX_OPTION+1];
175
176       if(line[0]=='\n')
177         break;
178
179       err=parse_ks_options(line,opt);
180       if(err>0)
181         {
182           ret=err;
183           goto fail;
184         }
185       else if(err==0)
186         continue;
187
188       if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
189         {
190           int no=0;
191           char *start=&option[0];
192
193           option[MAX_OPTION]='\0';
194
195           if(strncasecmp(option,"no-",3)==0)
196             {
197               no=1;
198               start=&option[3];
199             }
200
201           if(strncasecmp(start,"http-proxy",10)==0)
202             {
203               /* Safe to not check the return code of strdup() here.
204                  If it fails, we simply won't use a proxy. */
205               if(no)
206                 {
207                   free(proxy);
208                   proxy=strdup("");
209                 }
210               else if(start[10]=='=')
211                 {
212                   if(strlen(&start[11])<MAX_PROXY)
213                     {
214                       free(proxy);
215                       proxy=strdup(&start[11]);
216                     }
217                 }
218             }
219           else if(strncasecmp(start,"follow-redirects",16)==0)
220             {
221               if(no)
222                 follow_redirects=0;
223               else if(start[16]=='=')
224                 follow_redirects=atoi(&start[17]);
225               else if(start[16]=='\0')
226                 follow_redirects=-1;
227             }
228
229           continue;
230         }
231     }
232
233   if(!opt->scheme)
234     {
235       fprintf(console,"gpgkeys: no scheme supplied!\n");
236       ret=KEYSERVER_SCHEME_NOT_FOUND;
237       goto fail;
238     }
239
240   if(!opt->host)
241     {
242       fprintf(console,"gpgkeys: no keyserver host provided\n");
243       goto fail;
244     }
245
246   if(opt->timeout && register_timeout()==-1)
247     {
248       fprintf(console,"gpgkeys: unable to register timeout handler\n");
249       return KEYSERVER_INTERNAL_ERROR;
250     }
251
252   curl_global_init(CURL_GLOBAL_DEFAULT);
253   curl=curl_easy_init();
254   if(!curl)
255     {
256       fprintf(console,"gpgkeys: unable to initialize curl\n");
257       ret=KEYSERVER_INTERNAL_ERROR;
258       goto fail;
259     }
260
261   if(follow_redirects)
262     {
263       curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1);
264       if(follow_redirects>0)
265         curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
266     }
267
268   if(opt->auth)
269     curl_easy_setopt(curl,CURLOPT_USERPWD,opt->auth);
270
271   if(opt->debug)
272     {
273       fprintf(console,"gpgkeys: curl version = %s\n",curl_version());
274       curl_easy_setopt(curl,CURLOPT_STDERR,console);
275       curl_easy_setopt(curl,CURLOPT_VERBOSE,1);
276     }
277
278   curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,opt->flags.check_cert);
279   curl_easy_setopt(curl,CURLOPT_CAINFO,opt->ca_cert_file);
280
281   if(proxy)
282     curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
283
284   /* If it's a GET or a SEARCH, the next thing to come in is the
285      keyids.  If it's a SEND, then there are no keyids. */
286
287   if(opt->action==KS_GET)
288     {
289       /* Eat the rest of the file */
290       for(;;)
291         {
292           if(fgets(line,MAX_LINE,input)==NULL)
293             break;
294           else
295             {
296               if(line[0]=='\n' || line[0]=='\0')
297                 break;
298
299               if(!thekey)
300                 {
301                   thekey=strdup(line);
302                   if(!thekey)
303                     {
304                       fprintf(console,"gpgkeys: out of memory while "
305                               "building key list\n");
306                       ret=KEYSERVER_NO_MEMORY;
307                       goto fail;
308                     }
309
310                   /* Trim the trailing \n */
311                   thekey[strlen(line)-1]='\0';
312                 }
313             }
314         }
315     }
316   else
317     {
318       fprintf(console,
319               "gpgkeys: this keyserver type only supports key retrieval\n");
320       goto fail;
321     }
322
323   if(!thekey)
324     {
325       fprintf(console,"gpgkeys: invalid keyserver instructions\n");
326       goto fail;
327     }
328
329   /* Send the response */
330
331   fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
332   fprintf(output,"PROGRAM %s\n\n",VERSION);
333
334   if(opt->verbose)
335     {
336       fprintf(console,"Scheme:\t\t%s\n",opt->scheme);
337       fprintf(console,"Host:\t\t%s\n",opt->host);
338       if(opt->port)
339         fprintf(console,"Port:\t\t%s\n",opt->port);
340       if(opt->path)
341         fprintf(console,"Path:\t\t%s\n",opt->path);
342       fprintf(console,"Command:\tGET\n");
343     }
344
345   set_timeout(opt->timeout);
346
347   ret=get_key(thekey);
348
349  fail:
350
351   free(thekey);
352
353   if(input!=stdin)
354     fclose(input);
355
356   if(output!=stdout)
357     fclose(output);
358
359   free_ks_options(opt);
360
361   if(curl)
362     curl_easy_cleanup(curl);
363
364   free(proxy);
365
366   curl_global_cleanup();
367
368   return ret;
369 }