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