* ksutil.h, gpgkeys_curl.c, gpgkeys_hkp.c, gpgkeys_ldap.c,
[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],auth[MAX_AUTH+1],host[MAX_HOST+1]={'\0'},port[MAX_PORT+1]={'\0'},path[MAX_PATH+1]={'\0'},proxy[MAX_PROXY+1]={'\0'};
39 static FILE *input=NULL,*output=NULL,*console=NULL;
40 static CURL *curl;
41 static char request[MAX_URL]={'\0'};
42
43 static int
44 curl_err_to_gpg_err(CURLcode error)
45 {
46   switch(error)
47     {
48     case CURLE_FTP_COULDNT_RETR_FILE: return KEYSERVER_KEY_NOT_FOUND;
49     default: return KEYSERVER_INTERNAL_ERROR;
50     }
51 }
52
53 /* We wrap fwrite so to avoid DLL problems on Win32 (see curl faq for
54    more). */
55 static size_t
56 writer(const void *ptr,size_t size,size_t nmemb,void *stream)
57 {
58   return fwrite(ptr,size,nmemb,stream);
59 }
60
61 static int
62 get_key(char *getkey)
63 {
64   CURLcode res;
65   char errorbuffer[CURL_ERROR_SIZE];
66
67   if(strncmp(getkey,"0x",2)==0)
68     getkey+=2;
69
70   fprintf(output,"KEY 0x%s BEGIN\n",getkey);
71
72   sprintf(request,"%s://%s%s%s%s%s%s%s",scheme,auth[0]?auth:"",auth[0]?"@":"",
73           host,port[0]?":":"",port[0]?port:"",path[0]?"":"/",path);
74
75   curl_easy_setopt(curl,CURLOPT_URL,request);
76   curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writer);
77   curl_easy_setopt(curl,CURLOPT_FILE,output);
78   curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
79
80   if(verbose>1)
81     {
82       curl_easy_setopt(curl,CURLOPT_STDERR,console);
83       curl_easy_setopt(curl,CURLOPT_VERBOSE,1);
84     }
85
86   res=curl_easy_perform(curl);
87   if(res!=0)
88     {
89       fprintf(console,"gpgkeys: %s fetch error %d: %s\n",scheme,
90               res,errorbuffer);
91       fprintf(output,"KEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
92     }
93   else
94     fprintf(output,"KEY 0x%s END\n",getkey);
95
96   return KEYSERVER_OK;
97 }
98
99 static void 
100 show_help (FILE *fp)
101 {
102   fprintf (fp,"-h\thelp\n");
103   fprintf (fp,"-V\tversion\n");
104   fprintf (fp,"-o\toutput to this file\n");
105 }
106
107 int
108 main(int argc,char *argv[])
109 {
110   int arg,action=-1,ret=KEYSERVER_INTERNAL_ERROR;
111   char line[MAX_LINE];
112   char *thekey=NULL;
113   unsigned int timeout=DEFAULT_KEYSERVER_TIMEOUT;
114   long follow_redirects=5;
115
116   console=stderr;
117
118   /* Kludge to implement standard GNU options.  */
119   if (argc > 1 && !strcmp (argv[1], "--version"))
120     {
121       fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
122       return 0;
123     }
124   else if (argc > 1 && !strcmp (argv[1], "--help"))
125     {
126       show_help (stdout);
127       return 0;
128     }
129
130   while((arg=getopt(argc,argv,"hVo:"))!=-1)
131     switch(arg)
132       {
133       default:
134       case 'h':
135         show_help (console);
136         return KEYSERVER_OK;
137
138       case 'V':
139         fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
140         return KEYSERVER_OK;
141
142       case 'o':
143         output=fopen(optarg,"wb");
144         if(output==NULL)
145           {
146             fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
147                     optarg,strerror(errno));
148             return KEYSERVER_INTERNAL_ERROR;
149           }
150
151         break;
152       }
153
154   if(argc>optind)
155     {
156       input=fopen(argv[optind],"r");
157       if(input==NULL)
158         {
159           fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
160                   argv[optind],strerror(errno));
161           return KEYSERVER_INTERNAL_ERROR;
162         }
163     }
164
165   if(input==NULL)
166     input=stdin;
167
168   if(output==NULL)
169     output=stdout;
170
171   /* Get the command and info block */
172
173   while(fgets(line,MAX_LINE,input)!=NULL)
174     {
175       int version;
176       char command[MAX_COMMAND+1];
177       char option[MAX_OPTION+1];
178       char hash;
179
180       if(line[0]=='\n')
181         break;
182
183       if(sscanf(line,"%c",&hash)==1 && hash=='#')
184         continue;
185
186       if(sscanf(line,"COMMAND %" MKSTRING(MAX_COMMAND) "s\n",command)==1)
187         {
188           command[MAX_COMMAND]='\0';
189
190           if(strcasecmp(command,"get")==0)
191             action=GET;
192
193           continue;
194         }
195
196       if(sscanf(line,"SCHEME %" MKSTRING(MAX_SCHEME) "s\n",scheme)==1)
197         {
198           scheme[MAX_SCHEME]='\0';
199           continue;
200         }
201
202       if(sscanf(line,"AUTH %" MKSTRING(MAX_AUTH) "s\n",auth)==1)
203         {
204           auth[MAX_AUTH]='\0';
205           continue;
206         }
207
208       if(sscanf(line,"HOST %" MKSTRING(MAX_HOST) "s\n",host)==1)
209         {
210           host[MAX_HOST]='\0';
211           continue;
212         }
213
214       if(sscanf(line,"PORT %" MKSTRING(MAX_PORT) "s\n",port)==1)
215         {
216           port[MAX_PORT]='\0';
217           continue;
218         }
219
220       if(sscanf(line,"PATH %" MKSTRING(MAX_PATH) "s\n",path)==1)
221         {
222           path[MAX_PATH]='\0';
223           continue;
224         }
225
226       if(sscanf(line,"VERSION %d\n",&version)==1)
227         {
228           if(version!=KEYSERVER_PROTO_VERSION)
229             {
230               ret=KEYSERVER_VERSION_ERROR;
231               goto fail;
232             }
233
234           continue;
235         }
236
237       if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
238         {
239           int no=0;
240           char *start=&option[0];
241
242           option[MAX_OPTION]='\0';
243
244           if(strncasecmp(option,"no-",3)==0)
245             {
246               no=1;
247               start=&option[3];
248             }
249
250           if(strcasecmp(start,"verbose")==0)
251             {
252               if(no)
253                 verbose--;
254               else
255                 verbose++;
256             }
257           else if(strncasecmp(start,"http-proxy",10)==0)
258             {
259               if(no)
260                 proxy[0]='\0';
261               else if(start[10]=='=')
262                 {
263                   strncpy(proxy,&start[11],MAX_PROXY);
264                   proxy[MAX_PROXY]='\0';
265                 }
266             }
267           else if(strncasecmp(start,"timeout",7)==0)
268             {
269               if(no)
270                 timeout=0;
271               else if(start[7]=='=')
272                 timeout=atoi(&start[8]);
273               else if(start[7]=='\0')
274                 timeout=DEFAULT_KEYSERVER_TIMEOUT;
275             }
276           else if(strncasecmp(start,"follow-redirects",16)==0)
277             {
278               if(no)
279                 follow_redirects=0;
280               else if(start[16]=='=')
281                 follow_redirects=atoi(&start[17]);
282               else if(start[16]=='\0')
283                 follow_redirects=-1;
284             }
285
286           continue;
287         }
288     }
289
290   if(scheme[0]=='\0')
291     {
292       fprintf(console,"gpgkeys: no scheme supplied!\n");
293       return KEYSERVER_SCHEME_NOT_FOUND;
294     }
295 #ifndef HTTP_VIA_LIBCURL
296   else if(strcasecmp(scheme,"http")==0)
297     {
298       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
299       return KEYSERVER_SCHEME_NOT_FOUND;
300     }
301 #endif /* HTTP_VIA_LIBCURL */
302 #ifndef FTP_VIA_LIBCURL
303   else if(strcasecmp(scheme,"ftp")==0)
304     {
305       fprintf(console,"gpgkeys: scheme `%s' not supported\n",scheme);
306       return KEYSERVER_SCHEME_NOT_FOUND;
307     }
308 #endif /* FTP_VIA_LIBCURL */
309
310   if(timeout && register_timeout()==-1)
311     {
312       fprintf(console,"gpgkeys: unable to register timeout handler\n");
313       return KEYSERVER_INTERNAL_ERROR;
314     }
315
316   curl_global_init(CURL_GLOBAL_DEFAULT);
317   curl=curl_easy_init();
318   if(!curl)
319     {
320       fprintf(console,"gpgkeys: unable to initialize curl\n");
321       ret=KEYSERVER_INTERNAL_ERROR;
322       goto fail;
323     }
324
325   if(follow_redirects)
326     {
327       curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1);
328       if(follow_redirects>0)
329         curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
330     }
331
332   if(proxy[0])
333     curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
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 }