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