common: Check option arguments for a valid range.
[gnupg.git] / common / pka.c
1 /* pka.c - DNS Public Key Association RR access
2  * Copyright (C) 2005, 2009 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * This file is free software; you can redistribute it and/or modify
7  * it under the terms of either
8  *
9  *   - the GNU Lesser General Public License as published by the Free
10  *     Software Foundation; either version 3 of the License, or (at
11  *     your option) any later version.
12  *
13  * or
14  *
15  *   - the GNU General Public License as published by the Free
16  *     Software Foundation; either version 2 of the License, or (at
17  *     your option) any later version.
18  *
19  * or both in parallel, as here.
20  *
21  * This file is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, see <http://www.gnu.org/licenses/>.
28  */
29
30 #include <config.h>
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #ifdef USE_DNS_PKA
37 #include <sys/types.h>
38 #ifdef _WIN32
39 # ifdef HAVE_WINSOCK2_H
40 #  include <winsock2.h>
41 # endif
42 # include <windows.h>
43 #else
44 #include <netinet/in.h>
45 #include <arpa/nameser.h>
46 #include <resolv.h>
47 #endif
48 #endif /* USE_DNS_PKA */
49 #ifdef USE_ADNS
50 # include <adns.h>
51 # ifndef HAVE_ADNS_FREE
52 #  define adns_free free
53 # endif
54 #endif
55
56 #include "util.h"
57 #include "pka.h"
58
59 #ifdef USE_DNS_PKA
60 /* Parse the TXT resource record. Format is:
61
62    v=pka1;fpr=a4d94e92b0986ab5ee9dcd755de249965b0358a2;uri=string
63
64    For simplicity white spaces are not allowed.  Because we expect to
65    use a new RRTYPE for this in the future we define the TXT really
66    strict for simplicity: No white spaces, case sensitivity of the
67    names, order must be as given above.  Only URI is optional.
68
69    This function modifies BUFFER.  On success 0 is returned, the 20
70    byte fingerprint stored at FPR and BUFFER contains the URI or an
71    empty string.
72 */
73 static int
74 parse_txt_record (char *buffer, unsigned char *fpr)
75 {
76   char *p, *pend;
77   int i;
78
79   p = buffer;
80   pend = strchr (p, ';');
81   if (!pend)
82     return -1;
83   *pend++ = 0;
84   if (strcmp (p, "v=pka1"))
85     return -1; /* Wrong or missing version. */
86
87   p = pend;
88   pend = strchr (p, ';');
89   if (pend)
90     *pend++ = 0;
91   if (strncmp (p, "fpr=", 4))
92     return -1; /* Missing fingerprint part. */
93   p += 4;
94   for (i=0; i < 20 && hexdigitp (p) && hexdigitp (p+1); i++, p += 2)
95     fpr[i] = xtoi_2 (p);
96   if (i != 20)
97     return -1; /* Fingerprint consists not of exactly 40 hexbytes. */
98
99   p = pend;
100   if (!p || !*p)
101     {
102       *buffer = 0;
103       return 0; /* Success (no URI given). */
104     }
105   if (strncmp (p, "uri=", 4))
106     return -1; /* Unknown part. */
107   p += 4;
108   /* There is an URI, copy it to the start of the buffer. */
109   while (*p)
110     *buffer++ = *p++;
111   *buffer = 0;
112   return 0;
113 }
114
115
116 /* For the given email ADDRESS lookup the PKA information in the DNS.
117
118    On success the 20 byte SHA-1 fingerprint is stored at FPR and the
119    URI will be returned in an allocated buffer.  Note that the URI
120    might be an zero length string as this information is optional.
121    Caller must xfree the returned string.
122
123    On error NULL is returned and the 20 bytes at FPR are not
124    defined. */
125 char *
126 get_pka_info (const char *address, unsigned char *fpr)
127 {
128 #ifdef USE_ADNS
129   int rc;
130   adns_state state;
131   const char *domain;
132   char *name;
133   adns_answer *answer = NULL;
134   char *buffer = NULL;
135
136   domain = strrchr (address, '@');
137   if (!domain || domain == address || !domain[1])
138     return NULL; /* Invalid mail address given.  */
139   name = xtrymalloc (strlen (address) + 5 + 1);
140   if (!name)
141     return NULL;
142   memcpy (name, address, domain - address);
143   strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
144
145   rc = adns_init (&state, adns_if_noerrprint, NULL);
146   if (rc)
147     {
148       log_error ("error initializing adns: %s\n", strerror (errno));
149       xfree (name);
150       return NULL;
151     }
152
153   rc = adns_synchronous (state, name, adns_r_txt, adns_qf_quoteok_query,
154                          &answer);
155   xfree (name);
156   if (rc)
157     {
158       log_error ("DNS query failed: %s\n", strerror (errno));
159       adns_finish (state);
160       return NULL;
161     }
162   if (answer->status != adns_s_ok
163       || answer->type != adns_r_txt || !answer->nrrs)
164     {
165       log_error ("DNS query returned an error: %s (%s)\n",
166                  adns_strerror (answer->status),
167                  adns_errabbrev (answer->status));
168       adns_free (answer);
169       adns_finish (state);
170       return NULL;
171     }
172
173   /* We use a PKA records iff there is exactly one record.  */
174   if (answer->nrrs == 1 && answer->rrs.manyistr[0]->i != -1)
175     {
176       buffer = xtrystrdup (answer->rrs.manyistr[0]->str);
177       if (parse_txt_record (buffer, fpr))
178         {
179           xfree (buffer);
180           buffer = NULL;   /* Not a valid gpg trustdns RR. */
181         }
182     }
183
184   adns_free (answer);
185   adns_finish (state);
186   return buffer;
187
188 #else /*!USE_ADNS*/
189   unsigned char answer[PACKETSZ];
190   int anslen;
191   int qdcount, ancount;
192   int rc;
193   unsigned char *p, *pend;
194   const char *domain;
195   char *name;
196   HEADER header;
197
198   domain = strrchr (address, '@');
199   if (!domain || domain == address || !domain[1])
200     return NULL; /* invalid mail address given. */
201
202   name = xtrymalloc (strlen (address) + 5 + 1);
203   if (!name)
204     return NULL;
205   memcpy (name, address, domain - address);
206   strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
207
208   anslen = res_query (name, C_IN, T_TXT, answer, PACKETSZ);
209   xfree (name);
210   if (anslen < sizeof(HEADER))
211     return NULL; /* DNS resolver returned a too short answer. */
212
213   /* Don't despair: A good compiler should optimize this away, as
214      header is just 32 byte and constant at compile time.  It's
215      one way to comply with strict aliasing rules.  */
216   memcpy (&header, answer, sizeof (header));
217
218   if ( (rc=header.rcode) != NOERROR )
219     return NULL; /* DNS resolver returned an error. */
220
221   /* We assume that PACKETSZ is large enough and don't do dynmically
222      expansion of the buffer. */
223   if (anslen > PACKETSZ)
224     return NULL; /* DNS resolver returned a too long answer */
225
226   qdcount = ntohs (header.qdcount);
227   ancount = ntohs (header.ancount);
228
229   if (!ancount)
230     return NULL; /* Got no answer. */
231
232   p = answer + sizeof (HEADER);
233   pend = answer + anslen; /* Actually points directly behind the buffer. */
234
235   while (qdcount-- && p < pend)
236     {
237       rc = dn_skipname (p, pend);
238       if (rc == -1)
239         return NULL;
240       p += rc + QFIXEDSZ;
241     }
242
243   if (ancount > 1)
244     return NULL; /* more than one possible gpg trustdns record - none used. */
245
246   while (ancount-- && p <= pend)
247     {
248       unsigned int type, class, txtlen, n;
249       char *buffer, *bufp;
250
251       rc = dn_skipname (p, pend);
252       if (rc == -1)
253         return NULL;
254       p += rc;
255       if (p >= pend - 10)
256         return NULL; /* RR too short. */
257
258       type = *p++ << 8;
259       type |= *p++;
260       class = *p++ << 8;
261       class |= *p++;
262       p += 4;
263       txtlen = *p++ << 8;
264       txtlen |= *p++;
265       if (type != T_TXT || class != C_IN)
266         return NULL; /* Answer does not match the query. */
267
268       buffer = bufp = xmalloc (txtlen + 1);
269       while (txtlen && p < pend)
270         {
271           for (n = *p++, txtlen--; txtlen && n && p < pend; txtlen--, n--)
272             *bufp++ = *p++;
273         }
274       *bufp = 0;
275       if (parse_txt_record (buffer, fpr))
276         {
277           xfree (buffer);
278           return NULL; /* Not a valid gpg trustdns RR. */
279         }
280       return buffer;
281     }
282
283   return NULL;
284 #endif /*!USE_ADNS*/
285 }
286
287 #else /* !USE_DNS_PKA */
288
289 /* Dummy version of the function if we can't use the resolver
290    functions. */
291 char *
292 get_pka_info (const char *address, unsigned char *fpr)
293 {
294   (void)address;
295   (void)fpr;
296   return NULL;
297 }
298 #endif /* !USE_DNS_PKA */
299
300
301 #ifdef TEST
302 int
303 main(int argc,char *argv[])
304 {
305   unsigned char fpr[20];
306   char *uri;
307   int i;
308
309   if (argc < 2)
310     {
311       fprintf (stderr, "usage: pka mail-addresses\n");
312       return 1;
313     }
314   argc--;
315   argv++;
316
317   for (; argc; argc--, argv++)
318     {
319       uri = get_pka_info ( *argv, fpr );
320       printf ("%s", *argv);
321       if (uri)
322         {
323           putchar (' ');
324           for (i=0; i < 20; i++)
325             printf ("%02X", fpr[i]);
326           if (*uri)
327             printf (" %s", uri);
328           xfree (uri);
329         }
330       putchar ('\n');
331     }
332   return 0;
333 }
334 #endif /* TEST */
335
336 /*
337 Local Variables:
338 compile-command: "cc -DUSE_DNS_PKA -DTEST -I.. -I../include -Wall -g -o pka pka.c -lresolv ../tools/no-libgcrypt.o ../jnlib/libjnlib.a"
339 End:
340 */