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