More chnages to use estream. Add a way to replace the standard
[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   unsigned char answer[PACKETSZ];
177   int anslen;
178   int qdcount, ancount, nscount, arcount;
179   int rc;
180   unsigned char *p, *pend;
181   const char *domain;
182   char *name;
183
184
185   domain = strrchr (address, '@');
186   if (!domain || domain == address || !domain[1])
187     return NULL; /* invalid mail address given. */
188
189   name = xtrymalloc (strlen (address) + 5 + 1);
190   if (!name)
191     return NULL;
192   memcpy (name, address, domain - address);
193   strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
194
195   anslen = res_query (name, C_IN, T_TXT, answer, PACKETSZ);
196   xfree (name);
197   if (anslen < sizeof(HEADER))
198     return NULL; /* DNS resolver returned a too short answer. */
199   if ( (rc=((HEADER*)answer)->rcode) != NOERROR )
200     return NULL; /* DNS resolver returned an error. */
201
202   /* We assume that PACKETSZ is large enough and don't do dynmically
203      expansion of the buffer. */
204   if (anslen > PACKETSZ)
205     return NULL; /* DNS resolver returned a too long answer */
206
207   qdcount = ntohs (((HEADER*)answer)->qdcount);
208   ancount = ntohs (((HEADER*)answer)->ancount);
209   nscount = ntohs (((HEADER*)answer)->nscount);
210   arcount = ntohs (((HEADER*)answer)->arcount);
211
212   if (!ancount)
213     return NULL; /* Got no answer. */
214
215   p = answer + sizeof (HEADER);
216   pend = answer + anslen; /* Actually points directly behind the buffer. */
217
218   while (qdcount-- && p < pend)
219     {
220       rc = dn_skipname (p, pend);
221       if (rc == -1)
222         return NULL;
223       p += rc + QFIXEDSZ; 
224     }
225
226   if (ancount > 1)
227     return NULL; /* more than one possible gpg trustdns record - none used. */
228
229   while (ancount-- && p <= pend)
230     {
231       unsigned int type, class, txtlen, n;
232       char *buffer, *bufp;
233
234       rc = dn_skipname (p, pend);
235       if (rc == -1)
236         return NULL;
237       p += rc;
238       if (p >= pend - 10)
239         return NULL; /* RR too short. */
240
241       type = *p++ << 8;
242       type |= *p++;
243       class = *p++ << 8;
244       class |= *p++;
245       p += 4;
246       txtlen = *p++ << 8;
247       txtlen |= *p++;
248       if (type != T_TXT || class != C_IN)
249         return NULL; /* Answer does not match the query. */
250
251       buffer = bufp = xmalloc (txtlen + 1);
252       while (txtlen && p < pend)
253         {
254           for (n = *p++, txtlen--; txtlen && n && p < pend; txtlen--, n--)
255             *bufp++ = *p++;
256         }
257       *bufp = 0;
258       if (parse_txt_record (buffer, fpr))
259         {
260           xfree (buffer);
261           return NULL; /* Not a valid gpg trustdns RR. */
262         }
263       return buffer;
264     }
265
266   return NULL;
267 #endif /*!USE_ADNS*/
268 }
269
270 #else /* !USE_DNS_PKA */
271
272 /* Dummy version of the function if we can't use the resolver
273    functions. */
274 char *
275 get_pka_info (const char *address, unsigned char *fpr)
276 {
277   (void)address;
278   (void)fpr;
279   return NULL;
280 }
281 #endif /* !USE_DNS_PKA */
282
283
284 #ifdef TEST
285 int
286 main(int argc,char *argv[])
287 {
288   unsigned char fpr[20];
289   char *uri;
290   int i;
291
292   if (argc < 2)
293     {
294       fprintf (stderr, "usage: pka mail-addresses\n");
295       return 1;
296     }
297   argc--;
298   argv++;
299
300   for (; argc; argc--, argv++)
301     {
302       uri = get_pka_info ( *argv, fpr );
303       printf ("%s", *argv);
304       if (uri)
305         {
306           putchar (' ');
307           for (i=0; i < 20; i++)
308             printf ("%02X", fpr[i]);
309           if (*uri)
310             printf (" %s", uri);
311           xfree (uri);
312         }
313       putchar ('\n');
314     }
315   return 0;
316 }
317 #endif /* TEST */
318
319 /*
320 Local Variables:
321 compile-command: "cc -DUSE_DNS_PKA -DTEST -I.. -I../include -Wall -g -o pka pka.c -lresolv ../tools/no-libgcrypt.o ../jnlib/libjnlib.a"
322 End:
323 */