* gpgkeys_curl.c (get_key, writer): New function to wrap around fwrite to
[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
127   console=stderr;
128
129   /* Kludge to implement standard GNU options.  */
130   if (argc > 1 && !strcmp (argv[1], "--version"))
131     {
132       fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
133       return 0;
134     }
135   else if (argc > 1 && !strcmp (argv[1], "--help"))
136     {
137       show_help (stdout);
138       return 0;
139     }
140
141   while((arg=getopt(argc,argv,"hVo:"))!=-1)
142     switch(arg)
143       {
144       default:
145       case 'h':
146         show_help (console);
147         return KEYSERVER_OK;
148
149       case 'V':
150         fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
151         return KEYSERVER_OK;
152
153       case 'o':
154         output=fopen(optarg,"wb");
155         if(output==NULL)
156           {
157             fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
158                     optarg,strerror(errno));
159             return KEYSERVER_INTERNAL_ERROR;
160           }
161
162         break;
163       }
164
165   if(argc>optind)
166     {
167       input=fopen(argv[optind],"r");
168       if(input==NULL)
169         {
170           fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
171                   argv[optind],strerror(errno));
172           return KEYSERVER_INTERNAL_ERROR;
173         }
174     }
175
176   if(input==NULL)
177     input=stdin;
178
179   if(output==NULL)
180     output=stdout;
181
182   /* Get the command and info block */
183
184   while(fgets(line,MAX_LINE,input)!=NULL)
185     {
186       int version;
187       char commandstr[7];
188       char optionstr[256];
189       char hash;
190
191       if(line[0]=='\n')
192         break;
193
194       if(sscanf(line,"%c",&hash)==1 && hash=='#')
195         continue;
196
197       if(sscanf(line,"COMMAND %6s\n",commandstr)==1)
198         {
199           commandstr[6]='\0';
200
201           if(strcasecmp(commandstr,"get")==0)
202             action=GET;
203
204           continue;
205         }
206
207       if(sscanf(line,"SCHEME %" MKSTRING(MAX_SCHEME) "s\n",scheme)==1)
208         {
209           scheme[MAX_SCHEME]='\0';
210           continue;
211         }
212
213       if(sscanf(line,"AUTH %" MKSTRING(MAX_AUTH) "s\n",auth)==1)
214         {
215           auth[MAX_AUTH]='\0';
216           continue;
217         }
218
219       if(sscanf(line,"HOST %" MKSTRING(MAX_HOST) "s\n",host)==1)
220         {
221           host[MAX_HOST]='\0';
222           continue;
223         }
224
225       if(sscanf(line,"PORT %" MKSTRING(MAX_PORT) "s\n",port)==1)
226         {
227           port[MAX_PORT]='\0';
228           continue;
229         }
230
231       if(sscanf(line,"PATH %" MKSTRING(MAX_PATH) "s\n",path)==1)
232         {
233           path[MAX_PATH]='\0';
234           continue;
235         }
236
237       if(sscanf(line,"VERSION %d\n",&version)==1)
238         {
239           if(version!=KEYSERVER_PROTO_VERSION)
240             {
241               ret=KEYSERVER_VERSION_ERROR;
242               goto fail;
243             }
244
245           continue;
246         }
247
248       if(sscanf(line,"OPTION %255s\n",optionstr)==1)
249         {
250           int no=0;
251           char *start=&optionstr[0];
252
253           optionstr[255]='\0';
254
255           if(strncasecmp(optionstr,"no-",3)==0)
256             {
257               no=1;
258               start=&optionstr[3];
259             }
260
261           if(strcasecmp(start,"verbose")==0)
262             {
263               if(no)
264                 verbose--;
265               else
266                 verbose++;
267             }
268           else if(strncasecmp(start,"timeout",7)==0)
269             {
270               if(no)
271                 timeout=0;
272               else
273                 timeout=atoi(&start[8]);
274             }
275
276           continue;
277         }
278     }
279
280   if(scheme[0]=='\0')
281     {
282       fprintf(console,"gpgkeys: no scheme supplied!\n");
283       return KEYSERVER_SCHEME_NOT_FOUND;
284     }
285 #ifndef HTTP_SUPPORT
286   else if(strcasecmp(scheme,"http")==0)
287     {
288       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
289       return KEYSERVER_SCHEME_NOT_FOUND;
290     }
291 #endif /* HTTP_SUPPORT */
292 #ifndef FTP_SUPPORT
293   else if(strcasecmp(scheme,"ftp")==0)
294     {
295       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
296       return KEYSERVER_SCHEME_NOT_FOUND;
297     }
298 #endif /* FTP_SUPPORT */
299
300   if(timeout && register_timeout()==-1)
301     {
302       fprintf(console,"gpgkeys: unable to register timeout handler\n");
303       return KEYSERVER_INTERNAL_ERROR;
304     }
305
306   curl_global_init(CURL_GLOBAL_DEFAULT);
307   curl=curl_easy_init();
308   if(!curl)
309     {
310       fprintf(console,"gpgkeys: unable to initialize curl\n");
311       ret=KEYSERVER_INTERNAL_ERROR;
312       goto fail;
313     }
314
315   /* If it's a GET or a SEARCH, the next thing to come in is the
316      keyids.  If it's a SEND, then there are no keyids. */
317
318   if(action==GET)
319     {
320       /* Eat the rest of the file */
321       for(;;)
322         {
323           if(fgets(line,MAX_LINE,input)==NULL)
324             break;
325           else
326             {
327               if(line[0]=='\n' || line[0]=='\0')
328                 break;
329
330               if(!thekey)
331                 {
332                   thekey=strdup(line);
333                   if(!thekey)
334                     {
335                       fprintf(console,"gpgkeys: out of memory while "
336                               "building key list\n");
337                       ret=KEYSERVER_NO_MEMORY;
338                       goto fail;
339                     }
340
341                   /* Trim the trailing \n */
342                   thekey[strlen(line)-1]='\0';
343                 }
344             }
345         }
346     }
347   else
348     {
349       fprintf(console,
350               "gpgkeys: this keyserver type only supports key retrieval\n");
351       goto fail;
352     }
353
354   if(!thekey || !host[0])
355     {
356       fprintf(console,"gpgkeys: invalid keyserver instructions\n");
357       goto fail;
358     }
359
360   /* Send the response */
361
362   fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
363   fprintf(output,"PROGRAM %s\n\n",VERSION);
364
365   if(verbose)
366     {
367       fprintf(console,"Scheme:\t\t%s\n",scheme);
368       fprintf(console,"Host:\t\t%s\n",host);
369       if(port[0])
370         fprintf(console,"Port:\t\t%s\n",port);
371       if(path[0])
372         fprintf(console,"Path:\t\t%s\n",path);
373       fprintf(console,"Command:\tGET\n");
374     }
375
376   set_timeout(timeout);
377
378   ret=get_key(thekey);
379
380   curl_easy_cleanup(curl);
381
382  fail:
383
384   free(thekey);
385
386   if(input!=stdin)
387     fclose(input);
388
389   if(output!=stdout)
390     fclose(output);
391
392   curl_global_cleanup();
393
394   return ret;
395 }