Avoid using the protect-tool to import pkcs#12.
[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   HEADER header;
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
200   /* Don't despair: A good compiler should optimize this away, as
201      header is just 32 byte and constant at compile time.  It's
202      one way to comply with strict aliasing rules.  */
203   memcpy (&header, answer, sizeof (header));
204
205   if ( (rc=header.rcode) != NOERROR )
206     return NULL; /* DNS resolver returned an error. */
207
208   /* We assume that PACKETSZ is large enough and don't do dynmically
209      expansion of the buffer. */
210   if (anslen > PACKETSZ)
211     return NULL; /* DNS resolver returned a too long answer */
212   
213   qdcount = ntohs (header.qdcount);
214   ancount = ntohs (header.ancount);
215   nscount = ntohs (header.nscount);
216   arcount = ntohs (header.arcount);
217
218   if (!ancount)
219     return NULL; /* Got no answer. */
220
221   p = answer + sizeof (HEADER);
222   pend = answer + anslen; /* Actually points directly behind the buffer. */
223
224   while (qdcount-- && p < pend)
225     {
226       rc = dn_skipname (p, pend);
227       if (rc == -1)
228         return NULL;
229       p += rc + QFIXEDSZ; 
230     }
231
232   if (ancount > 1)
233     return NULL; /* more than one possible gpg trustdns record - none used. */
234
235   while (ancount-- && p <= pend)
236     {
237       unsigned int type, class, txtlen, n;
238       char *buffer, *bufp;
239
240       rc = dn_skipname (p, pend);
241       if (rc == -1)
242         return NULL;
243       p += rc;
244       if (p >= pend - 10)
245         return NULL; /* RR too short. */
246
247       type = *p++ << 8;
248       type |= *p++;
249       class = *p++ << 8;
250       class |= *p++;
251       p += 4;
252       txtlen = *p++ << 8;
253       txtlen |= *p++;
254       if (type != T_TXT || class != C_IN)
255         return NULL; /* Answer does not match the query. */
256
257       buffer = bufp = xmalloc (txtlen + 1);
258       while (txtlen && p < pend)
259         {
260           for (n = *p++, txtlen--; txtlen && n && p < pend; txtlen--, n--)
261             *bufp++ = *p++;
262         }
263       *bufp = 0;
264       if (parse_txt_record (buffer, fpr))
265         {
266           xfree (buffer);
267           return NULL; /* Not a valid gpg trustdns RR. */
268         }
269       return buffer;
270     }
271
272   return NULL;
273 #endif /*!USE_ADNS*/
274 }
275
276 #else /* !USE_DNS_PKA */
277
278 /* Dummy version of the function if we can't use the resolver
279    functions. */
280 char *
281 get_pka_info (const char *address, unsigned char *fpr)
282 {
283   (void)address;
284   (void)fpr;
285   return NULL;
286 }
287 #endif /* !USE_DNS_PKA */
288
289
290 #ifdef TEST
291 int
292 main(int argc,char *argv[])
293 {
294   unsigned char fpr[20];
295   char *uri;
296   int i;
297
298   if (argc < 2)
299     {
300       fprintf (stderr, "usage: pka mail-addresses\n");
301       return 1;
302     }
303   argc--;
304   argv++;
305
306   for (; argc; argc--, argv++)
307     {
308       uri = get_pka_info ( *argv, fpr );
309       printf ("%s", *argv);
310       if (uri)
311         {
312           putchar (' ');
313           for (i=0; i < 20; i++)
314             printf ("%02X", fpr[i]);
315           if (*uri)
316             printf (" %s", uri);
317           xfree (uri);
318         }
319       putchar ('\n');
320     }
321   return 0;
322 }
323 #endif /* TEST */
324
325 /*
326 Local Variables:
327 compile-command: "cc -DUSE_DNS_PKA -DTEST -I.. -I../include -Wall -g -o pka pka.c -lresolv ../tools/no-libgcrypt.o ../jnlib/libjnlib.a"
328 End:
329 */