* curl-shim.c (curl_easy_perform): Fix compile warning.
[gnupg.git] / keyserver / gpgkeys_curl.c
1 /* gpgkeys_curl.c - fetch a key via libcurl
2  * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #ifdef HAVE_GETOPT_H
28 #include <getopt.h>
29 #endif
30 #ifdef FAKE_CURL
31 #include "curl-shim.h"
32 #else
33 #include <curl/curl.h>
34 #endif
35 #include "keyserver.h"
36 #include "ksutil.h"
37
38 extern char *optarg;
39 extern int optind;
40
41 static int verbose=0;
42 static char scheme[MAX_SCHEME+1];
43 static char auth[MAX_AUTH+1];
44 static char host[MAX_HOST+1];
45 static char port[MAX_PORT+1];
46 static char path[URLMAX_PATH+1];
47 static char proxy[MAX_PROXY+1];
48 static FILE *input, *output, *console;
49 static CURL *curl;
50 static char request[MAX_URL];
51
52 static int
53 curl_err_to_gpg_err(CURLcode error)
54 {
55   switch(error)
56     {
57     case CURLE_FTP_COULDNT_RETR_FILE: return KEYSERVER_KEY_NOT_FOUND;
58     default: return KEYSERVER_INTERNAL_ERROR;
59     }
60 }
61
62 static size_t
63 writer(const void *ptr,size_t size,size_t nmemb,void *stream)
64 {
65   const char *buf=ptr;
66   size_t i;
67   static int markeridx=0,begun=0,done=0;
68   static const char *marker=BEGIN;
69
70   /* scan the incoming data for our marker */
71   for(i=0;!done && i<(size*nmemb);i++)
72     {
73       if(buf[i]==marker[markeridx])
74         {
75           markeridx++;
76           if(marker[markeridx]=='\0')
77             {
78               if(begun)
79                 done=1;
80               else
81                 {
82                   /* We've found the BEGIN marker, so now we're looking
83                      for the END marker. */
84                   begun=1;
85                   marker=END;
86                   markeridx=0;
87                   fprintf(output,BEGIN);
88                   continue;
89                 }
90             }
91         }
92       else
93         markeridx=0;
94
95       if(begun)
96         {
97           /* Canonicalize CRLF to just LF by stripping CRs.  This
98              actually makes sense, since on Unix-like machines LF is
99              correct, and on win32-like machines, our output buffer is
100              opened in textmode and will re-canonicalize line endings
101              back to CRLF.  Since we only need to handle armored keys,
102              we don't have to worry about odd cases like CRCRCR and
103              the like. */
104
105           if(buf[i]!='\r')
106             fputc(buf[i],output);
107         }
108     }
109
110   return size*nmemb;
111 }
112
113 static int
114 get_key(char *getkey)
115 {
116   CURLcode res;
117   char errorbuffer[CURL_ERROR_SIZE];
118
119   if(strncmp(getkey,"0x",2)==0)
120     getkey+=2;
121
122   fprintf(output,"KEY 0x%s BEGIN\n",getkey);
123
124   sprintf(request,"%s://%s%s%s%s%s%s%s",scheme,auth[0]?auth:"",auth[0]?"@":"",
125           host,port[0]?":":"",port[0]?port:"",path[0]?"":"/",path);
126
127   curl_easy_setopt(curl,CURLOPT_URL,request);
128   curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writer);
129   curl_easy_setopt(curl,CURLOPT_FILE,output);
130   curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
131
132   res=curl_easy_perform(curl);
133   if(res!=0)
134     {
135       fprintf(console,"gpgkeys: %s fetch error %d: %s\n",scheme,
136               res,errorbuffer);
137       fprintf(output,"\nKEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
138     }
139   else
140     fprintf(output,"\nKEY 0x%s END\n",getkey);
141
142   return KEYSERVER_OK;
143 }
144
145 static void 
146 show_help (FILE *fp)
147 {
148   fprintf (fp,"-h\thelp\n");
149   fprintf (fp,"-V\tversion\n");
150   fprintf (fp,"-o\toutput to this file\n");
151 }
152
153 int
154 main(int argc,char *argv[])
155 {
156   int arg,action=-1,ret=KEYSERVER_INTERNAL_ERROR;
157   char line[MAX_LINE];
158   char *thekey=NULL;
159   unsigned int timeout=DEFAULT_KEYSERVER_TIMEOUT;
160   long follow_redirects=5,debug=0,check_cert=1;
161   char *ca_cert_file=NULL;
162
163   console=stderr;
164
165   /* Kludge to implement standard GNU options.  */
166   if (argc > 1 && !strcmp (argv[1], "--version"))
167     {
168       fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
169       return 0;
170     }
171   else if (argc > 1 && !strcmp (argv[1], "--help"))
172     {
173       show_help (stdout);
174       return 0;
175     }
176
177   while((arg=getopt(argc,argv,"hVo:"))!=-1)
178     switch(arg)
179       {
180       default:
181       case 'h':
182         show_help (console);
183         return KEYSERVER_OK;
184
185       case 'V':
186         fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
187         return KEYSERVER_OK;
188
189       case 'o':
190         output=fopen(optarg,"wb");
191         if(output==NULL)
192           {
193             fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
194                     optarg,strerror(errno));
195             return KEYSERVER_INTERNAL_ERROR;
196           }
197
198         break;
199       }
200
201   if(argc>optind)
202     {
203       input=fopen(argv[optind],"r");
204       if(input==NULL)
205         {
206           fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
207                   argv[optind],strerror(errno));
208           return KEYSERVER_INTERNAL_ERROR;
209         }
210     }
211
212   if(input==NULL)
213     input=stdin;
214
215   if(output==NULL)
216     output=stdout;
217
218   /* Get the command and info block */
219
220   while(fgets(line,MAX_LINE,input)!=NULL)
221     {
222       int version;
223       char command[MAX_COMMAND+1];
224       char option[MAX_OPTION+1];
225       char hash;
226
227       if(line[0]=='\n')
228         break;
229
230       if(sscanf(line,"%c",&hash)==1 && hash=='#')
231         continue;
232
233       if(sscanf(line,"COMMAND %" MKSTRING(MAX_COMMAND) "s\n",command)==1)
234         {
235           command[MAX_COMMAND]='\0';
236
237           if(strcasecmp(command,"get")==0)
238             action=GET;
239
240           continue;
241         }
242
243       if(sscanf(line,"SCHEME %" MKSTRING(MAX_SCHEME) "s\n",scheme)==1)
244         {
245           scheme[MAX_SCHEME]='\0';
246           continue;
247         }
248
249       if(sscanf(line,"AUTH %" MKSTRING(MAX_AUTH) "s\n",auth)==1)
250         {
251           auth[MAX_AUTH]='\0';
252           continue;
253         }
254
255       if(sscanf(line,"HOST %" MKSTRING(MAX_HOST) "s\n",host)==1)
256         {
257           host[MAX_HOST]='\0';
258           continue;
259         }
260
261       if(sscanf(line,"PORT %" MKSTRING(MAX_PORT) "s\n",port)==1)
262         {
263           port[MAX_PORT]='\0';
264           continue;
265         }
266
267       if(sscanf(line,"PATH %" MKSTRING(URLMAX_PATH) "s\n",path)==1)
268         {
269           path[URLMAX_PATH]='\0';
270           continue;
271         }
272
273       if(sscanf(line,"VERSION %d\n",&version)==1)
274         {
275           if(version!=KEYSERVER_PROTO_VERSION)
276             {
277               ret=KEYSERVER_VERSION_ERROR;
278               goto fail;
279             }
280
281           continue;
282         }
283
284       if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
285         {
286           int no=0;
287           char *start=&option[0];
288
289           option[MAX_OPTION]='\0';
290
291           if(strncasecmp(option,"no-",3)==0)
292             {
293               no=1;
294               start=&option[3];
295             }
296
297           if(strcasecmp(start,"verbose")==0)
298             {
299               if(no)
300                 verbose--;
301               else
302                 verbose++;
303             }
304           else if(strncasecmp(start,"http-proxy",10)==0)
305             {
306               if(no)
307                 proxy[0]='\0';
308               else if(start[10]=='=')
309                 {
310                   strncpy(proxy,&start[11],MAX_PROXY);
311                   proxy[MAX_PROXY]='\0';
312                 }
313             }
314           else if(strncasecmp(start,"timeout",7)==0)
315             {
316               if(no)
317                 timeout=0;
318               else if(start[7]=='=')
319                 timeout=atoi(&start[8]);
320               else if(start[7]=='\0')
321                 timeout=DEFAULT_KEYSERVER_TIMEOUT;
322             }
323           else if(strncasecmp(start,"follow-redirects",16)==0)
324             {
325               if(no)
326                 follow_redirects=0;
327               else if(start[16]=='=')
328                 follow_redirects=atoi(&start[17]);
329               else if(start[16]=='\0')
330                 follow_redirects=-1;
331             }
332           else if(strncasecmp(start,"debug",5)==0)
333             {
334               if(no)
335                 debug=0;
336               else if(start[5]=='=')
337                 debug=atoi(&start[6]);
338               else if(start[5]=='\0')
339                 debug=1;
340             }
341           else if(strcasecmp(start,"check-cert")==0)
342             {
343               if(no)
344                 check_cert=0;
345               else
346                 check_cert=1;
347             }
348           else if(strncasecmp(start,"ca-cert-file",12)==0)
349             {
350               if(no)
351                 {
352                   free(ca_cert_file);
353                   ca_cert_file=NULL;
354                 }
355               else if(start[12]=='=')
356                 {
357                   free(ca_cert_file);
358                   ca_cert_file=strdup(&start[13]);
359                   if(!ca_cert_file)
360                     {
361                       fprintf(console,"gpgkeys: out of memory while creating "
362                               "ca_cert_file\n");
363                       ret=KEYSERVER_NO_MEMORY;
364                       goto fail;
365                     }
366                 }
367             }
368
369           continue;
370         }
371     }
372
373   if(scheme[0]=='\0')
374     {
375       fprintf(console,"gpgkeys: no scheme supplied!\n");
376       return KEYSERVER_SCHEME_NOT_FOUND;
377     }
378 #ifdef HTTP_VIA_LIBCURL
379   else if(strcasecmp(scheme,"http")==0)
380     ;
381 #endif /* HTTP_VIA_LIBCURL */
382 #ifdef HTTPS_VIA_LIBCURL
383   else if(strcasecmp(scheme,"https")==0)
384     ;
385 #endif /* HTTP_VIA_LIBCURL */
386 #ifdef FTP_VIA_LIBCURL
387   else if(strcasecmp(scheme,"ftp")==0)
388     ;
389 #endif /* FTP_VIA_LIBCURL */
390 #ifdef FTPS_VIA_LIBCURL
391   else if(strcasecmp(scheme,"ftps")==0)
392     ;
393 #endif /* FTPS_VIA_LIBCURL */
394   else
395     {
396       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
397       return KEYSERVER_SCHEME_NOT_FOUND;
398     }
399
400   if(timeout && register_timeout()==-1)
401     {
402       fprintf(console,"gpgkeys: unable to register timeout handler\n");
403       return KEYSERVER_INTERNAL_ERROR;
404     }
405
406   curl_global_init(CURL_GLOBAL_DEFAULT);
407   curl=curl_easy_init();
408   if(!curl)
409     {
410       fprintf(console,"gpgkeys: unable to initialize curl\n");
411       ret=KEYSERVER_INTERNAL_ERROR;
412       goto fail;
413     }
414
415   if(follow_redirects)
416     {
417       curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1);
418       if(follow_redirects>0)
419         curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
420     }
421
422   if(debug)
423     {
424       curl_easy_setopt(curl,CURLOPT_STDERR,console);
425       curl_easy_setopt(curl,CURLOPT_VERBOSE,1);
426     }
427
428   curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,check_cert);
429
430   if(ca_cert_file)
431     curl_easy_setopt(curl,CURLOPT_CAINFO,ca_cert_file);
432
433   if(proxy[0])
434     curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
435
436   /* If it's a GET or a SEARCH, the next thing to come in is the
437      keyids.  If it's a SEND, then there are no keyids. */
438
439   if(action==GET)
440     {
441       /* Eat the rest of the file */
442       for(;;)
443         {
444           if(fgets(line,MAX_LINE,input)==NULL)
445             break;
446           else
447             {
448               if(line[0]=='\n' || line[0]=='\0')
449                 break;
450
451               if(!thekey)
452                 {
453                   thekey=strdup(line);
454                   if(!thekey)
455                     {
456                       fprintf(console,"gpgkeys: out of memory while "
457                               "building key list\n");
458                       ret=KEYSERVER_NO_MEMORY;
459                       goto fail;
460                     }
461
462                   /* Trim the trailing \n */
463                   thekey[strlen(line)-1]='\0';
464                 }
465             }
466         }
467     }
468   else
469     {
470       fprintf(console,
471               "gpgkeys: this keyserver type only supports key retrieval\n");
472       goto fail;
473     }
474
475   if(!thekey || !host[0])
476     {
477       fprintf(console,"gpgkeys: invalid keyserver instructions\n");
478       goto fail;
479     }
480
481   /* Send the response */
482
483   fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
484   fprintf(output,"PROGRAM %s\n\n",VERSION);
485
486   if(verbose)
487     {
488       fprintf(console,"Scheme:\t\t%s\n",scheme);
489       fprintf(console,"Host:\t\t%s\n",host);
490       if(port[0])
491         fprintf(console,"Port:\t\t%s\n",port);
492       if(path[0])
493         fprintf(console,"Path:\t\t%s\n",path);
494       fprintf(console,"Command:\tGET\n");
495     }
496
497   set_timeout(timeout);
498
499   ret=get_key(thekey);
500
501  fail:
502
503   free(thekey);
504
505   if(input!=stdin)
506     fclose(input);
507
508   if(output!=stdout)
509     fclose(output);
510
511   if(curl)
512     curl_easy_cleanup(curl);
513
514   curl_global_cleanup();
515
516   return ret;
517 }