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