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