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