* curl-shim.h, curl-shim.c: New. This is code to fake the curl API in
[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
162   console=stderr;
163
164   /* Kludge to implement standard GNU options.  */
165   if (argc > 1 && !strcmp (argv[1], "--version"))
166     {
167       fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
168       return 0;
169     }
170   else if (argc > 1 && !strcmp (argv[1], "--help"))
171     {
172       show_help (stdout);
173       return 0;
174     }
175
176   while((arg=getopt(argc,argv,"hVo:"))!=-1)
177     switch(arg)
178       {
179       default:
180       case 'h':
181         show_help (console);
182         return KEYSERVER_OK;
183
184       case 'V':
185         fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
186         return KEYSERVER_OK;
187
188       case 'o':
189         output=fopen(optarg,"wb");
190         if(output==NULL)
191           {
192             fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
193                     optarg,strerror(errno));
194             return KEYSERVER_INTERNAL_ERROR;
195           }
196
197         break;
198       }
199
200   if(argc>optind)
201     {
202       input=fopen(argv[optind],"r");
203       if(input==NULL)
204         {
205           fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
206                   argv[optind],strerror(errno));
207           return KEYSERVER_INTERNAL_ERROR;
208         }
209     }
210
211   if(input==NULL)
212     input=stdin;
213
214   if(output==NULL)
215     output=stdout;
216
217   /* Get the command and info block */
218
219   while(fgets(line,MAX_LINE,input)!=NULL)
220     {
221       int version;
222       char command[MAX_COMMAND+1];
223       char option[MAX_OPTION+1];
224       char hash;
225
226       if(line[0]=='\n')
227         break;
228
229       if(sscanf(line,"%c",&hash)==1 && hash=='#')
230         continue;
231
232       if(sscanf(line,"COMMAND %" MKSTRING(MAX_COMMAND) "s\n",command)==1)
233         {
234           command[MAX_COMMAND]='\0';
235
236           if(strcasecmp(command,"get")==0)
237             action=GET;
238
239           continue;
240         }
241
242       if(sscanf(line,"SCHEME %" MKSTRING(MAX_SCHEME) "s\n",scheme)==1)
243         {
244           scheme[MAX_SCHEME]='\0';
245           continue;
246         }
247
248       if(sscanf(line,"AUTH %" MKSTRING(MAX_AUTH) "s\n",auth)==1)
249         {
250           auth[MAX_AUTH]='\0';
251           continue;
252         }
253
254       if(sscanf(line,"HOST %" MKSTRING(MAX_HOST) "s\n",host)==1)
255         {
256           host[MAX_HOST]='\0';
257           continue;
258         }
259
260       if(sscanf(line,"PORT %" MKSTRING(MAX_PORT) "s\n",port)==1)
261         {
262           port[MAX_PORT]='\0';
263           continue;
264         }
265
266       if(sscanf(line,"PATH %" MKSTRING(URLMAX_PATH) "s\n",path)==1)
267         {
268           path[URLMAX_PATH]='\0';
269           continue;
270         }
271
272       if(sscanf(line,"VERSION %d\n",&version)==1)
273         {
274           if(version!=KEYSERVER_PROTO_VERSION)
275             {
276               ret=KEYSERVER_VERSION_ERROR;
277               goto fail;
278             }
279
280           continue;
281         }
282
283       if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
284         {
285           int no=0;
286           char *start=&option[0];
287
288           option[MAX_OPTION]='\0';
289
290           if(strncasecmp(option,"no-",3)==0)
291             {
292               no=1;
293               start=&option[3];
294             }
295
296           if(strcasecmp(start,"verbose")==0)
297             {
298               if(no)
299                 verbose--;
300               else
301                 verbose++;
302             }
303           else if(strncasecmp(start,"http-proxy",10)==0)
304             {
305               if(no)
306                 proxy[0]='\0';
307               else if(start[10]=='=')
308                 {
309                   strncpy(proxy,&start[11],MAX_PROXY);
310                   proxy[MAX_PROXY]='\0';
311                 }
312             }
313           else if(strncasecmp(start,"timeout",7)==0)
314             {
315               if(no)
316                 timeout=0;
317               else if(start[7]=='=')
318                 timeout=atoi(&start[8]);
319               else if(start[7]=='\0')
320                 timeout=DEFAULT_KEYSERVER_TIMEOUT;
321             }
322           else if(strncasecmp(start,"follow-redirects",16)==0)
323             {
324               if(no)
325                 follow_redirects=0;
326               else if(start[16]=='=')
327                 follow_redirects=atoi(&start[17]);
328               else if(start[16]=='\0')
329                 follow_redirects=-1;
330             }
331           else if(strncasecmp(start,"debug",5)==0)
332             {
333               if(no)
334                 debug=0;
335               else if(start[5]=='=')
336                 debug=atoi(&start[6]);
337               else if(start[5]=='\0')
338                 debug=1;
339             }
340           else if(strcasecmp(start,"check-cert")==0)
341             {
342               if(no)
343                 check_cert=0;
344               else
345                 check_cert=1;
346             }
347
348           continue;
349         }
350     }
351
352   if(scheme[0]=='\0')
353     {
354       fprintf(console,"gpgkeys: no scheme supplied!\n");
355       return KEYSERVER_SCHEME_NOT_FOUND;
356     }
357 #ifdef HTTP_VIA_LIBCURL
358   else if(strcasecmp(scheme,"http")==0)
359     ;
360 #endif /* HTTP_VIA_LIBCURL */
361 #ifdef HTTPS_VIA_LIBCURL
362   else if(strcasecmp(scheme,"https")==0)
363     ;
364 #endif /* HTTP_VIA_LIBCURL */
365 #ifdef FTP_VIA_LIBCURL
366   else if(strcasecmp(scheme,"ftp")==0)
367     ;
368 #endif /* FTP_VIA_LIBCURL */
369 #ifdef FTPS_VIA_LIBCURL
370   else if(strcasecmp(scheme,"ftps")==0)
371     ;
372 #endif /* FTPS_VIA_LIBCURL */
373   else
374     {
375       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
376       return KEYSERVER_SCHEME_NOT_FOUND;
377     }
378
379   if(timeout && register_timeout()==-1)
380     {
381       fprintf(console,"gpgkeys: unable to register timeout handler\n");
382       return KEYSERVER_INTERNAL_ERROR;
383     }
384
385   curl_global_init(CURL_GLOBAL_DEFAULT);
386   curl=curl_easy_init();
387   if(!curl)
388     {
389       fprintf(console,"gpgkeys: unable to initialize curl\n");
390       ret=KEYSERVER_INTERNAL_ERROR;
391       goto fail;
392     }
393
394   if(follow_redirects)
395     {
396       curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1);
397       if(follow_redirects>0)
398         curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
399     }
400
401   if(debug)
402     {
403       curl_easy_setopt(curl,CURLOPT_STDERR,console);
404       curl_easy_setopt(curl,CURLOPT_VERBOSE,1);
405     }
406
407   curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,check_cert);
408
409   if(proxy[0])
410     curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
411
412   /* If it's a GET or a SEARCH, the next thing to come in is the
413      keyids.  If it's a SEND, then there are no keyids. */
414
415   if(action==GET)
416     {
417       /* Eat the rest of the file */
418       for(;;)
419         {
420           if(fgets(line,MAX_LINE,input)==NULL)
421             break;
422           else
423             {
424               if(line[0]=='\n' || line[0]=='\0')
425                 break;
426
427               if(!thekey)
428                 {
429                   thekey=strdup(line);
430                   if(!thekey)
431                     {
432                       fprintf(console,"gpgkeys: out of memory while "
433                               "building key list\n");
434                       ret=KEYSERVER_NO_MEMORY;
435                       goto fail;
436                     }
437
438                   /* Trim the trailing \n */
439                   thekey[strlen(line)-1]='\0';
440                 }
441             }
442         }
443     }
444   else
445     {
446       fprintf(console,
447               "gpgkeys: this keyserver type only supports key retrieval\n");
448       goto fail;
449     }
450
451   if(!thekey || !host[0])
452     {
453       fprintf(console,"gpgkeys: invalid keyserver instructions\n");
454       goto fail;
455     }
456
457   /* Send the response */
458
459   fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
460   fprintf(output,"PROGRAM %s\n\n",VERSION);
461
462   if(verbose)
463     {
464       fprintf(console,"Scheme:\t\t%s\n",scheme);
465       fprintf(console,"Host:\t\t%s\n",host);
466       if(port[0])
467         fprintf(console,"Port:\t\t%s\n",port);
468       if(path[0])
469         fprintf(console,"Path:\t\t%s\n",path);
470       fprintf(console,"Command:\tGET\n");
471     }
472
473   set_timeout(timeout);
474
475   ret=get_key(thekey);
476
477  fail:
478
479   free(thekey);
480
481   if(input!=stdin)
482     fclose(input);
483
484   if(output!=stdout)
485     fclose(output);
486
487   if(curl)
488     curl_easy_cleanup(curl);
489
490   curl_global_cleanup();
491
492   return ret;
493 }