Minor cleanups.
[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
67
68
69 \f
70 /* Retrieve one key.  ADDRESS should be an RFC-2822 addr-spec. */
71 static int
72 get_key (adns_state adns_ctx, char *address)
73 {
74   int ret = KEYSERVER_INTERNAL_ERROR;
75   const char *domain;
76   char *name = NULL;
77   adns_answer *answer = NULL;
78   const unsigned char *data;
79   int datalen;
80   struct b64state b64state;
81   char *p;
82
83   domain = strrchr (address, '@');
84   if (!domain || domain == address || !domain[1])
85     {
86       fprintf (console, PGM": invalid mail address `%s'\n", address);
87       ret = KEYSERVER_GENERAL_ERROR;
88       goto leave;
89     }
90   name = xtrymalloc (strlen (address) + strlen (kdns_at_repl)
91                      + 1 + strlen (kdns_root) + 1);
92   if (!name)
93     goto leave;
94   memcpy (name, address, domain - address);
95   p = stpcpy (name + (domain-address), ".");
96   if (*kdns_at_repl)
97     p = stpcpy (stpcpy (p, kdns_at_repl), ".");
98   p = stpcpy (p, domain+1);
99   if (*kdns_root)
100     strcpy (stpcpy (p, "."), kdns_root);
101
102   fprintf (output,"NAME %s BEGIN\n", address);
103   if (opt->verbose > 2)
104     fprintf(console, PGM": looking up `%s'\n", name);
105
106
107   if ( adns_synchronous (adns_ctx, name, (adns_r_unknown | my_adns_r_cert),
108                          adns_qf_quoteok_query,
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          "\n", fp);
200   fputs ("Example:  A query for \"hacker@gnupg.org\" with\n"
201          "  kdns://10.0.0.1/example.net?at=_key?\n"
202          "setup as --auto-key-lookup does a CERT record query\n"
203          "with type PGP on the nameserver 10.0.0.1 for\n"
204          "  hacker._key_.gnupg.org.example.net\n"
205          "\n", fp);
206 }
207
208
209 int
210 main (int argc, char *argv[])
211 {
212   int arg;
213   int ret = KEYSERVER_INTERNAL_ERROR;
214   char line[MAX_LINE];
215   struct keylist *keylist = NULL;
216   struct keylist **keylist_tail = &keylist;
217   struct keylist *akey;
218   int failed = 0;
219   adns_state adns_ctx = NULL;
220   adns_initflags my_adns_initflags = MY_ADNS_INITFLAGS;
221   int tmprc;
222
223   /* The defaults for the KDNS name mangling.  */
224   kdns_root = "";
225   kdns_at_repl = "";
226
227   console = stderr;
228
229   /* Kludge to implement standard GNU options.  */
230   if (argc > 1 && !strcmp (argv[1], "--version"))
231     {
232       fputs (PGM" (GnuPG) " VERSION"\n", stdout);
233       return 0;
234     }
235   else if (argc > 1 && !strcmp (argv[1], "--help"))
236     {
237       show_help (stdout);
238       return 0;
239     }
240
241   while ( (arg = getopt (argc, argv, "hVo:")) != -1 )
242     {
243       switch(arg)
244         {
245         case 'V':
246           printf ("%d\n%s\n", KEYSERVER_PROTO_VERSION, VERSION);
247           return KEYSERVER_OK;
248
249         case 'o':
250           output = fopen (optarg,"w");
251           if (!output)
252             {
253               fprintf (console, PGM": cannot open output file `%s': %s\n",
254                        optarg, strerror(errno) );
255               return KEYSERVER_INTERNAL_ERROR;
256             }
257           break;
258
259         case 'h':
260         default:
261           show_help (console);
262           return KEYSERVER_OK;
263         }
264     }
265
266   if (argc > optind)
267     {
268       input = fopen (argv[optind], "r");
269       if (!input)
270         {
271           fprintf (console, PGM": cannot open input file `%s': %s\n",
272                    argv[optind], strerror(errno) );
273           return KEYSERVER_INTERNAL_ERROR;
274         }
275     }
276
277   if (!input)
278     input = stdin;
279
280   if (!output)
281     output = stdout;
282   
283   opt = init_ks_options();
284   if(!opt)
285     return KEYSERVER_NO_MEMORY;
286
287   /* Get the command and info block */
288   while ( fgets(line,MAX_LINE,input) )
289     {
290       int err;
291       
292       if(line[0]=='\n')
293         break;
294       
295       err = parse_ks_options (line, opt);
296       if (err > 0)
297         {
298           ret = err;
299           goto leave;
300         }
301       else if (!err)
302         continue;
303     }
304
305   if (opt->timeout && register_timeout() == -1 )
306     {
307       fprintf (console, PGM": unable to register timeout handler\n");
308       return KEYSERVER_INTERNAL_ERROR;
309     }
310
311   fprintf (console, PGM": HOST=%s\n", opt->host? opt->host:"(none)");
312   fprintf (console, PGM": PATH=%s\n", opt->path? opt->path:"(none)");
313   if (opt->path && *opt->path == '/')
314     {
315       char *p, *pend;
316
317       kdns_root = opt->path+1;
318       p = strchr (opt->path+1, '?');
319       if (p)
320         {
321           *p++ = 0;
322           do 
323             {
324               pend = strchr (p, '&');
325               if (pend)
326                 *pend++ = 0;
327               if (!strncmp (p, "at=", 3))
328                 {
329                   /* Found.  */
330                   kdns_at_repl = p+3;
331                   break;
332                 }
333             }
334           while ((p = pend));
335         }
336     }
337   if (strchr (kdns_root, '/'))
338     {
339       fprintf (console, PGM": invalid character in KDNS root\n");
340       return KEYSERVER_GENERAL_ERROR;
341     }
342   if (!strcmp (kdns_at_repl, "."))
343     kdns_at_repl = "";
344   fprintf (console, PGM": kdns_root=%s\n", kdns_root);
345   fprintf (console, PGM": kdns_at=%s\n", kdns_at_repl);
346
347
348   if (opt->debug)
349     my_adns_initflags |= adns_if_debug;
350   if (opt->host)
351     {
352       char cfgtext[200];
353
354       snprintf (cfgtext, sizeof cfgtext, "nameserver %s\n", opt->host);
355       tmprc = adns_init_strcfg (&adns_ctx, my_adns_initflags, console,cfgtext);
356     }
357   else
358     tmprc = adns_init (&adns_ctx, my_adns_initflags, console);
359   if (tmprc)
360     {
361       fprintf (console, PGM": error initializing ADNS: %s\n",
362                strerror (errno));
363       goto leave;
364     }
365   
366   if (opt->action == KS_GETNAME)
367     {
368       while ( fgets (line,MAX_LINE,input) )
369         {
370           if (line[0]=='\n' || !line[0] )
371             break;
372           line[strlen(line)-1] = 0;  /* Trim the trailing LF. */
373           
374           akey = xtrymalloc (sizeof *akey);
375           if (!akey)
376             {
377               fprintf (console, 
378                        PGM": out of memory while building key list\n");
379               ret = KEYSERVER_NO_MEMORY;
380               goto leave;
381             }
382           assert (sizeof (akey->str) > strlen(line));
383           strcpy (akey->str, line);
384           akey->next = NULL;
385           *keylist_tail = akey;
386           keylist_tail = &akey->next;
387         }
388     }
389   else
390     {
391       fprintf (console,
392                PGM": this keyserver type only supports "
393                "key retrieval by name\n");
394       goto leave;
395     }
396   
397   /* Send the response */
398   fprintf (output, "VERSION %d\n", KEYSERVER_PROTO_VERSION);
399   fprintf (output, "PROGRAM %s\n\n", VERSION);
400
401   if (opt->verbose > 1)
402     {
403       if (opt->opaque)
404         fprintf (console, "User:\t\t%s\n", opt->opaque);
405       fprintf (console, "Command:\tGET\n");
406     }
407   
408   for (akey = keylist; akey; akey = akey->next)
409     {
410       set_timeout (opt->timeout);
411       if ( get_key (adns_ctx, akey->str) )
412         failed++;
413     }      
414   if (!failed)
415     ret = KEYSERVER_OK;
416
417
418  leave:
419   if (adns_ctx)
420     adns_finish (adns_ctx);
421   while (keylist)
422     {
423       akey = keylist->next;
424       xfree (keylist);
425       keylist = akey;
426     }
427   if (input != stdin)
428     fclose (input);
429   if (output != stdout)
430     fclose (output);
431   kdns_root = "";
432   kdns_at_repl = ".";
433   free_ks_options (opt);
434   return ret;
435 }