Allow requesting TCP queries.
[gnupg.git] / keyserver / gpgkeys_kdns.c
1 /* gpgkeys_kdns.c - Fetch a key via the GnuPG specific KDNS scheme.
2  * Copyright (C) 2008 g10 Code GmbH
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 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, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #ifdef HAVE_GETOPT_H
27 # include <getopt.h>
28 #endif
29 #include <assert.h>
30 #ifdef HAVE_ADNS_H
31 # include <adns.h>
32 #endif
33
34 #define INCLUDED_BY_MAIN_MODULE 1
35 #include "util.h"
36 #include "keyserver.h"
37 #include "ksutil.h"
38
39 /* Our own name.  */
40 #define PGM "gpgkeys_kdns"
41
42 /* getopt(3) requires declarion of some global variables.  */
43 extern char *optarg;
44 extern int optind;
45
46 /* Convenience variables usually intialized withn std{in,out,err}.  */
47 static FILE *input, *output, *console;
48
49 /* Standard keyserver module options.  */
50 static struct ks_options *opt;
51
52 /* The flags we pass to adns_init: Do not allow any environment
53    variables and for now enable debugging.  */
54 #define MY_ADNS_INITFLAGS  (adns_if_noenv)
55
56
57 /* ADNS has no support for CERT yes. */
58 #define my_adns_r_cert 37
59
60 /* The root of the KDNS tree. */
61 static const char *kdns_root;
62
63 /* The replacement string for the at sign.  */
64 static const char *kdns_at_repl;
65
66 /* Flag indicating that a TCP conenction should be used.  */
67 static int kdns_usevc;
68
69
70 \f
71 /* Retrieve one key.  ADDRESS should be an RFC-2822 addr-spec. */
72 static int
73 get_key (adns_state adns_ctx, char *address)
74 {
75   int ret = KEYSERVER_INTERNAL_ERROR;
76   const char *domain;
77   char *name = NULL;
78   adns_answer *answer = NULL;
79   const unsigned char *data;
80   int datalen;
81   struct b64state b64state;
82   char *p;
83
84   domain = strrchr (address, '@');
85   if (!domain || domain == address || !domain[1])
86     {
87       fprintf (console, PGM": invalid mail address `%s'\n", address);
88       ret = KEYSERVER_GENERAL_ERROR;
89       goto leave;
90     }
91   name = xtrymalloc (strlen (address) + strlen (kdns_at_repl)
92                      + 1 + strlen (kdns_root) + 1);
93   if (!name)
94     goto leave;
95   memcpy (name, address, domain - address);
96   p = stpcpy (name + (domain-address), ".");
97   if (*kdns_at_repl)
98     p = stpcpy (stpcpy (p, kdns_at_repl), ".");
99   p = stpcpy (p, domain+1);
100   if (*kdns_root)
101     strcpy (stpcpy (p, "."), kdns_root);
102
103   fprintf (output,"NAME %s BEGIN\n", address);
104   if (opt->verbose > 2)
105     fprintf(console, PGM": looking up `%s'\n", name);
106
107   if ( adns_synchronous (adns_ctx, name, (adns_r_unknown | my_adns_r_cert),
108                          adns_qf_quoteok_query|(kdns_usevc?adns_qf_usevc:0),
109                          &answer) )
110     {
111       fprintf (console, PGM": DNS query failed: %s\n", strerror (errno));
112       ret = KEYSERVER_KEY_NOT_FOUND;
113       goto leave;
114     }
115   if (answer->status != adns_s_ok) 
116     {
117       fprintf (console, PGM": DNS query returned: %s (%s)\n", 
118                adns_strerror (answer->status),
119                adns_errabbrev (answer->status));
120       ret = KEYSERVER_KEY_NOT_FOUND;
121       goto leave;
122     }
123   datalen = answer->rrs.byteblock->len;
124   data = answer->rrs.byteblock->data;
125
126   if ( opt->debug > 1 )
127     {
128       int i;
129
130       fprintf (console, "got %d  bytes of data:", datalen);
131       for (i=0; i < datalen; i++)
132         {
133           if (!(i % 32))
134             fprintf (console, "\n%08x  ", i);
135           fprintf (console, "%02x", data[i]);
136         }
137       putc ('\n', console);
138     }
139   if ( datalen < 5 )
140     {
141       fprintf (console, PGM": error: truncated CERT record\n"); 
142       ret = KEYSERVER_KEY_NOT_FOUND;
143       goto leave;
144     }
145
146   switch ( ((data[0]<<8)|data[1]) )
147     {
148     case 3: /* CERT type is PGP.  */
149       /* (key tag and algorithm fields are ignored for this CERT type). */
150       data += 5;
151       datalen -= 5;
152       if ( datalen < 11 )
153         {
154           /* Gpg checks for a minium length of 11, thus we do the same.  */
155           fprintf (console, PGM": error: OpenPGP data to short\n"); 
156           ret = KEYSERVER_KEY_NOT_FOUND;
157           goto leave;
158         }
159       if (b64enc_start (&b64state, output, "PGP PUBLIC KEY BLOCK")
160           || b64enc_write (&b64state, data, datalen)
161           || b64enc_finish (&b64state))
162         goto leave; /* Oops, base64 encoder failed.  */
163       break;
164
165     default:
166       fprintf (console, PGM": CERT type %d ignored\n", (data[0] <<8|data[1])); 
167       ret = KEYSERVER_KEY_NOT_FOUND;
168       goto leave;
169     }
170   
171   ret = 0; /* All fine.  */
172
173  leave:
174   if (ret)
175     fprintf (output, "\nNAME %s FAILED %d\n", address, ret);
176   else
177     fprintf (output, "\nNAME %s END\n", address);
178   free (answer);  /* (Right, this is free and not xfree.) */
179   xfree (name);
180   return ret;
181 }
182
183
184 /* Print some help.  */
185 static void 
186 show_help (FILE *fp)
187 {
188   fputs (PGM" (GnuPG) " VERSION"\n\n", fp);
189   fputs (" -h\thelp\n"
190          " -V\tversion\n"
191          " -o\toutput to this file\n"
192          "\n", fp);
193   fputs ("This keyserver helper accepts URLs of the form:\n"
194          "  kdns://[NAMESERVER]/[ROOT][?at=STRING]\n"
195          "with\n"
196          "  NAMESERVER  used for queries (default: system standard)\n"
197          "  ROOT        a DNS name appended to the query (default: none)\n"
198          "  STRING      a string to replace the '@' (default: \".\")\n"
199          "If a long answer is expected add the parameter \"usevc=1\".\n"
200          "\n", fp);
201   fputs ("Example:  A query for \"hacker@gnupg.org\" with\n"
202          "  kdns://10.0.0.1/example.net?at=_key&usevc=1\n"
203          "setup as --auto-key-lookup does a CERT record query\n"
204          "with type PGP on the nameserver 10.0.0.1 for\n"
205          "  hacker._key_.gnupg.org.example.net\n"
206          "\n", fp);
207 }
208
209
210 int
211 main (int argc, char *argv[])
212 {
213   int arg;
214   int ret = KEYSERVER_INTERNAL_ERROR;
215   char line[MAX_LINE];
216   struct keylist *keylist = NULL;
217   struct keylist **keylist_tail = &keylist;
218   struct keylist *akey;
219   int failed = 0;
220   adns_state adns_ctx = NULL;
221   adns_initflags my_adns_initflags = MY_ADNS_INITFLAGS;
222   int tmprc;
223
224   /* The defaults for the KDNS name mangling.  */
225   kdns_root = "";
226   kdns_at_repl = "";
227
228   console = stderr;
229
230   /* Kludge to implement standard GNU options.  */
231   if (argc > 1 && !strcmp (argv[1], "--version"))
232     {
233       fputs (PGM" (GnuPG) " VERSION"\n", stdout);
234       return 0;
235     }
236   else if (argc > 1 && !strcmp (argv[1], "--help"))
237     {
238       show_help (stdout);
239       return 0;
240     }
241
242   while ( (arg = getopt (argc, argv, "hVo:")) != -1 )
243     {
244       switch(arg)
245         {
246         case 'V':
247           printf ("%d\n%s\n", KEYSERVER_PROTO_VERSION, VERSION);
248           return KEYSERVER_OK;
249
250         case 'o':
251           output = fopen (optarg,"w");
252           if (!output)
253             {
254               fprintf (console, PGM": cannot open output file `%s': %s\n",
255                        optarg, strerror(errno) );
256               return KEYSERVER_INTERNAL_ERROR;
257             }
258           break;
259
260         case 'h':
261         default:
262           show_help (console);
263           return KEYSERVER_OK;
264         }
265     }
266
267   if (argc > optind)
268     {
269       input = fopen (argv[optind], "r");
270       if (!input)
271         {
272           fprintf (console, PGM": cannot open input file `%s': %s\n",
273                    argv[optind], strerror(errno) );
274           return KEYSERVER_INTERNAL_ERROR;
275         }
276     }
277
278   if (!input)
279     input = stdin;
280
281   if (!output)
282     output = stdout;
283   
284   opt = init_ks_options();
285   if(!opt)
286     return KEYSERVER_NO_MEMORY;
287
288   /* Get the command and info block */
289   while ( fgets(line,MAX_LINE,input) )
290     {
291       int err;
292       
293       if(line[0]=='\n')
294         break;
295       
296       err = parse_ks_options (line, opt);
297       if (err > 0)
298         {
299           ret = err;
300           goto leave;
301         }
302       else if (!err)
303         continue;
304     }
305
306   if (opt->timeout && register_timeout() == -1 )
307     {
308       fprintf (console, PGM": unable to register timeout handler\n");
309       return KEYSERVER_INTERNAL_ERROR;
310     }
311
312   if (opt->verbose)
313     {
314       fprintf (console, PGM": HOST=%s\n", opt->host? opt->host:"(none)");
315       fprintf (console, PGM": PATH=%s\n", opt->path? opt->path:"(none)");
316     }
317   if (opt->path && *opt->path == '/')
318     {
319       char *p, *pend;
320
321       kdns_root = opt->path+1;
322       p = strchr (opt->path+1, '?');
323       if (p)
324         {
325           *p++ = 0;
326           do 
327             {
328               pend = strchr (p, '&');
329               if (pend)
330                 *pend++ = 0;
331               if (!strncmp (p, "at=", 3))
332                 kdns_at_repl = p+3;
333               else if (!strncmp (p, "usevc=", 6))
334                 kdns_usevc = !!atoi (p+6);
335             }
336           while ((p = pend));
337         }
338     }
339   if (strchr (kdns_root, '/'))
340     {
341       fprintf (console, PGM": invalid character in KDNS root\n");
342       return KEYSERVER_GENERAL_ERROR;
343     }
344   if (!strcmp (kdns_at_repl, "."))
345     kdns_at_repl = "";
346
347   if (opt->verbose)
348     {
349       fprintf (console, PGM": kdns_root=%s\n", kdns_root);
350       fprintf (console, PGM": kdns_at=%s\n", kdns_at_repl);
351       fprintf (console, PGM": kdns_usevc=%d\n", kdns_usevc);
352     }
353
354   if (opt->debug)
355     my_adns_initflags |= adns_if_debug;
356   if (opt->host)
357     {
358       char cfgtext[200];
359
360       snprintf (cfgtext, sizeof cfgtext, "nameserver %s\n", opt->host);
361       tmprc = adns_init_strcfg (&adns_ctx, my_adns_initflags, console,cfgtext);
362     }
363   else
364     tmprc = adns_init (&adns_ctx, my_adns_initflags, console);
365   if (tmprc)
366     {
367       fprintf (console, PGM": error initializing ADNS: %s\n",
368                strerror (errno));
369       goto leave;
370     }
371   
372   if (opt->action == KS_GETNAME)
373     {
374       while ( fgets (line,MAX_LINE,input) )
375         {
376           if (line[0]=='\n' || !line[0] )
377             break;
378           line[strlen(line)-1] = 0;  /* Trim the trailing LF. */
379           
380           akey = xtrymalloc (sizeof *akey);
381           if (!akey)
382             {
383               fprintf (console, 
384                        PGM": out of memory while building key list\n");
385               ret = KEYSERVER_NO_MEMORY;
386               goto leave;
387             }
388           assert (sizeof (akey->str) > strlen(line));
389           strcpy (akey->str, line);
390           akey->next = NULL;
391           *keylist_tail = akey;
392           keylist_tail = &akey->next;
393         }
394     }
395   else
396     {
397       fprintf (console,
398                PGM": this keyserver type only supports "
399                "key retrieval by name\n");
400       goto leave;
401     }
402   
403   /* Send the response */
404   fprintf (output, "VERSION %d\n", KEYSERVER_PROTO_VERSION);
405   fprintf (output, "PROGRAM %s\n\n", VERSION);
406
407   if (opt->verbose > 1)
408     {
409       if (opt->opaque)
410         fprintf (console, "User:\t\t%s\n", opt->opaque);
411       fprintf (console, "Command:\tGET\n");
412     }
413   
414   for (akey = keylist; akey; akey = akey->next)
415     {
416       set_timeout (opt->timeout);
417       if ( get_key (adns_ctx, akey->str) )
418         failed++;
419     }      
420   if (!failed)
421     ret = KEYSERVER_OK;
422
423
424  leave:
425   if (adns_ctx)
426     adns_finish (adns_ctx);
427   while (keylist)
428     {
429       akey = keylist->next;
430       xfree (keylist);
431       keylist = akey;
432     }
433   if (input != stdin)
434     fclose (input);
435   if (output != stdout)
436     fclose (output);
437   kdns_root = "";
438   kdns_at_repl = ".";
439   free_ks_options (opt);
440   return ret;
441 }