* gpgkeys_curl.c (main): New "follow-redirects" option. Takes an optional
[gnupg.git] / keyserver / gpgkeys_curl.c
1 /* gpgkeys_curl.c - fetch a key via libcurl
2  * Copyright (C) 2004 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 #include <curl/curl.h>
31 #include "keyserver.h"
32 #include "ksutil.h"
33
34 extern char *optarg;
35 extern int optind;
36
37 #define GET         0
38 #define MAX_SCHEME 20
39 #define MAX_LINE   80
40 #define MAX_PATH 1023
41 #define MAX_AUTH  127
42 #define MAX_HOST   79
43 #define MAX_PORT    9
44 #define MAX_URL (MAX_SCHEME+3+MAX_AUTH+1+1+MAX_HOST+1+1+MAX_PORT+1+1+MAX_PATH+1+50)
45
46 #define STRINGIFY(x) #x
47 #define MKSTRING(x) STRINGIFY(x)
48
49 static int verbose=0;
50 static char scheme[MAX_SCHEME+1],auth[MAX_AUTH+1],host[MAX_HOST+1]={'\0'},port[MAX_PORT+1]={'\0'},path[MAX_PATH+1]={'\0'};
51 static FILE *input=NULL,*output=NULL,*console=NULL;
52 static CURL *curl;
53 static char request[MAX_URL]={'\0'};
54
55 static int
56 curl_err_to_gpg_err(CURLcode error)
57 {
58   switch(error)
59     {
60     case CURLE_FTP_COULDNT_RETR_FILE: return KEYSERVER_KEY_NOT_FOUND;
61     default: return KEYSERVER_INTERNAL_ERROR;
62     }
63 }
64
65 /* We wrap fwrite so to avoid DLL problems on Win32 (see curl faq for
66    more). */
67 static size_t
68 writer(const void *ptr,size_t size,size_t nmemb,void *stream)
69 {
70   return fwrite(ptr,size,nmemb,stream);
71 }
72
73 static int
74 get_key(char *getkey)
75 {
76   CURLcode res;
77   char errorbuffer[CURL_ERROR_SIZE];
78
79   if(strncmp(getkey,"0x",2)==0)
80     getkey+=2;
81
82   fprintf(output,"KEY 0x%s BEGIN\n",getkey);
83
84   sprintf(request,"%s://%s%s%s%s%s%s%s",scheme,auth[0]?auth:"",auth[0]?"@":"",
85           host,port[0]?":":"",port[0]?port:"",path[0]?"":"/",path);
86
87   curl_easy_setopt(curl,CURLOPT_URL,request);
88   curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writer);
89   curl_easy_setopt(curl,CURLOPT_FILE,output);
90   curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
91
92   if(verbose>1)
93     {
94       curl_easy_setopt(curl,CURLOPT_STDERR,console);
95       curl_easy_setopt(curl,CURLOPT_VERBOSE,TRUE);
96     }
97
98   res=curl_easy_perform(curl);
99   if(res!=0)
100     {
101       fprintf(console,"gpgkeys: %s fetch error %d: %s\n",scheme,
102               res,errorbuffer);
103       fprintf(output,"KEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
104     }
105   else
106     fprintf(output,"KEY 0x%s END\n",getkey);
107
108   return KEYSERVER_OK;
109 }
110
111 static void 
112 show_help (FILE *fp)
113 {
114   fprintf (fp,"-h\thelp\n");
115   fprintf (fp,"-V\tversion\n");
116   fprintf (fp,"-o\toutput to this file\n");
117 }
118
119 int
120 main(int argc,char *argv[])
121 {
122   int arg,action=-1,ret=KEYSERVER_INTERNAL_ERROR;
123   char line[MAX_LINE];
124   char *thekey=NULL;
125   unsigned int timeout=DEFAULT_KEYSERVER_TIMEOUT;
126   long follow_redirects=5;
127
128   console=stderr;
129
130   /* Kludge to implement standard GNU options.  */
131   if (argc > 1 && !strcmp (argv[1], "--version"))
132     {
133       fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
134       return 0;
135     }
136   else if (argc > 1 && !strcmp (argv[1], "--help"))
137     {
138       show_help (stdout);
139       return 0;
140     }
141
142   while((arg=getopt(argc,argv,"hVo:"))!=-1)
143     switch(arg)
144       {
145       default:
146       case 'h':
147         show_help (console);
148         return KEYSERVER_OK;
149
150       case 'V':
151         fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
152         return KEYSERVER_OK;
153
154       case 'o':
155         output=fopen(optarg,"wb");
156         if(output==NULL)
157           {
158             fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
159                     optarg,strerror(errno));
160             return KEYSERVER_INTERNAL_ERROR;
161           }
162
163         break;
164       }
165
166   if(argc>optind)
167     {
168       input=fopen(argv[optind],"r");
169       if(input==NULL)
170         {
171           fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
172                   argv[optind],strerror(errno));
173           return KEYSERVER_INTERNAL_ERROR;
174         }
175     }
176
177   if(input==NULL)
178     input=stdin;
179
180   if(output==NULL)
181     output=stdout;
182
183   /* Get the command and info block */
184
185   while(fgets(line,MAX_LINE,input)!=NULL)
186     {
187       int version;
188       char commandstr[7];
189       char optionstr[256];
190       char hash;
191
192       if(line[0]=='\n')
193         break;
194
195       if(sscanf(line,"%c",&hash)==1 && hash=='#')
196         continue;
197
198       if(sscanf(line,"COMMAND %6s\n",commandstr)==1)
199         {
200           commandstr[6]='\0';
201
202           if(strcasecmp(commandstr,"get")==0)
203             action=GET;
204
205           continue;
206         }
207
208       if(sscanf(line,"SCHEME %" MKSTRING(MAX_SCHEME) "s\n",scheme)==1)
209         {
210           scheme[MAX_SCHEME]='\0';
211           continue;
212         }
213
214       if(sscanf(line,"AUTH %" MKSTRING(MAX_AUTH) "s\n",auth)==1)
215         {
216           auth[MAX_AUTH]='\0';
217           continue;
218         }
219
220       if(sscanf(line,"HOST %" MKSTRING(MAX_HOST) "s\n",host)==1)
221         {
222           host[MAX_HOST]='\0';
223           continue;
224         }
225
226       if(sscanf(line,"PORT %" MKSTRING(MAX_PORT) "s\n",port)==1)
227         {
228           port[MAX_PORT]='\0';
229           continue;
230         }
231
232       if(sscanf(line,"PATH %" MKSTRING(MAX_PATH) "s\n",path)==1)
233         {
234           path[MAX_PATH]='\0';
235           continue;
236         }
237
238       if(sscanf(line,"VERSION %d\n",&version)==1)
239         {
240           if(version!=KEYSERVER_PROTO_VERSION)
241             {
242               ret=KEYSERVER_VERSION_ERROR;
243               goto fail;
244             }
245
246           continue;
247         }
248
249       if(sscanf(line,"OPTION %255s\n",optionstr)==1)
250         {
251           int no=0;
252           char *start=&optionstr[0];
253
254           optionstr[255]='\0';
255
256           if(strncasecmp(optionstr,"no-",3)==0)
257             {
258               no=1;
259               start=&optionstr[3];
260             }
261
262           if(strcasecmp(start,"verbose")==0)
263             {
264               if(no)
265                 verbose--;
266               else
267                 verbose++;
268             }
269           else if(strncasecmp(start,"timeout",7)==0)
270             {
271               if(no)
272                 timeout=0;
273               else if(start[7]=='=')
274                 timeout=atoi(&start[8]);
275               else if(start[7]=='\0')
276                 timeout=DEFAULT_KEYSERVER_TIMEOUT;
277             }
278           else if(strncasecmp(start,"follow-redirects",16)==0)
279             {
280               if(no)
281                 follow_redirects=0;
282               else if(start[16]=='=')
283                 follow_redirects=atoi(&start[17]);
284               else if(start[16]=='\0')
285                 follow_redirects=-1;
286             }
287
288           continue;
289         }
290     }
291
292   if(scheme[0]=='\0')
293     {
294       fprintf(console,"gpgkeys: no scheme supplied!\n");
295       return KEYSERVER_SCHEME_NOT_FOUND;
296     }
297 #ifndef HTTP_SUPPORT
298   else if(strcasecmp(scheme,"http")==0)
299     {
300       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
301       return KEYSERVER_SCHEME_NOT_FOUND;
302     }
303 #endif /* HTTP_SUPPORT */
304 #ifndef FTP_SUPPORT
305   else if(strcasecmp(scheme,"ftp")==0)
306     {
307       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
308       return KEYSERVER_SCHEME_NOT_FOUND;
309     }
310 #endif /* FTP_SUPPORT */
311
312   if(timeout && register_timeout()==-1)
313     {
314       fprintf(console,"gpgkeys: unable to register timeout handler\n");
315       return KEYSERVER_INTERNAL_ERROR;
316     }
317
318   curl_global_init(CURL_GLOBAL_DEFAULT);
319   curl=curl_easy_init();
320   if(!curl)
321     {
322       fprintf(console,"gpgkeys: unable to initialize curl\n");
323       ret=KEYSERVER_INTERNAL_ERROR;
324       goto fail;
325     }
326
327   if(follow_redirects)
328     {
329       curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1);
330       if(follow_redirects>0)
331         curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
332     }
333
334
335   /* If it's a GET or a SEARCH, the next thing to come in is the
336      keyids.  If it's a SEND, then there are no keyids. */
337
338   if(action==GET)
339     {
340       /* Eat the rest of the file */
341       for(;;)
342         {
343           if(fgets(line,MAX_LINE,input)==NULL)
344             break;
345           else
346             {
347               if(line[0]=='\n' || line[0]=='\0')
348                 break;
349
350               if(!thekey)
351                 {
352                   thekey=strdup(line);
353                   if(!thekey)
354                     {
355                       fprintf(console,"gpgkeys: out of memory while "
356                               "building key list\n");
357                       ret=KEYSERVER_NO_MEMORY;
358                       goto fail;
359                     }
360
361                   /* Trim the trailing \n */
362                   thekey[strlen(line)-1]='\0';
363                 }
364             }
365         }
366     }
367   else
368     {
369       fprintf(console,
370               "gpgkeys: this keyserver type only supports key retrieval\n");
371       goto fail;
372     }
373
374   if(!thekey || !host[0])
375     {
376       fprintf(console,"gpgkeys: invalid keyserver instructions\n");
377       goto fail;
378     }
379
380   /* Send the response */
381
382   fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
383   fprintf(output,"PROGRAM %s\n\n",VERSION);
384
385   if(verbose)
386     {
387       fprintf(console,"Scheme:\t\t%s\n",scheme);
388       fprintf(console,"Host:\t\t%s\n",host);
389       if(port[0])
390         fprintf(console,"Port:\t\t%s\n",port);
391       if(path[0])
392         fprintf(console,"Path:\t\t%s\n",path);
393       fprintf(console,"Command:\tGET\n");
394     }
395
396   set_timeout(timeout);
397
398   ret=get_key(thekey);
399
400   curl_easy_cleanup(curl);
401
402  fail:
403
404   free(thekey);
405
406   if(input!=stdin)
407     fclose(input);
408
409   if(output!=stdout)
410     fclose(output);
411
412   curl_global_cleanup();
413
414   return ret;
415 }