79a0bc3f53bcfb33a19a1dec8006b49799cc42aa
[gnupg.git] / common / pka.c
1 /* pka.c - DNS Public Key Association RR access
2  * Copyright (C) 2005 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
37 #include "util.h"
38 #include "pka.h"
39
40 #ifdef USE_DNS_PKA
41 /* Parse the TXT resource record. Format is:
42
43    v=pka1;fpr=a4d94e92b0986ab5ee9dcd755de249965b0358a2;uri=string
44    
45    For simplicity white spaces are not allowed.  Because we expect to
46    use a new RRTYPE for this in the future we define the TXT really
47    strict for simplicity: No white spaces, case sensitivity of the
48    names, order must be as given above.  Only URI is optional.
49
50    This function modifies BUFFER.  On success 0 is returned, the 20
51    byte fingerprint stored at FPR and BUFFER contains the URI or an
52    empty string.
53 */
54 static int
55 parse_txt_record (char *buffer, unsigned char *fpr)
56 {
57   char *p, *pend;
58   int i;
59
60   p = buffer;
61   pend = strchr (p, ';');
62   if (!pend)
63     return -1;
64   *pend++ = 0;
65   if (strcmp (p, "v=pka1"))
66     return -1; /* Wrong or missing version. */
67   
68   p = pend;
69   pend = strchr (p, ';');
70   if (pend)
71     *pend++ = 0;
72   if (strncmp (p, "fpr=", 4))
73     return -1; /* Missing fingerprint part. */
74   p += 4;
75   for (i=0; i < 20 && hexdigitp (p) && hexdigitp (p+1); i++, p += 2)
76     fpr[i] = xtoi_2 (p);
77   if (i != 20)
78     return -1; /* Fingerprint consists not of exactly 40 hexbytes. */
79     
80   p = pend;
81   if (!p || !*p)
82     {
83       *buffer = 0;  
84       return 0; /* Success (no URI given). */
85     }
86   if (strncmp (p, "uri=", 4))
87     return -1; /* Unknown part. */
88   p += 4;
89   /* There is an URI, copy it to the start of the buffer. */
90   while (*p)
91     *buffer++ = *p++;
92   *buffer = 0;
93   return 0;
94 }
95
96
97 /* For the given email ADDRESS lookup the PKA information in the DNS.
98
99    On success the 20 byte SHA-1 fingerprint is stored at FPR and the
100    URI will be returned in an allocated buffer.  Note that the URI
101    might be an zero length string as this information is optional.
102    Caller must xfree the returned string.
103
104    On error NULL is returned and the 20 bytes at FPR are not
105    defined. */
106 char *
107 get_pka_info (const char *address, unsigned char *fpr)
108 {
109   unsigned char answer[PACKETSZ];
110   int anslen;
111   int qdcount, ancount, nscount, arcount;
112   int rc;
113   unsigned char *p, *pend;
114   const char *domain;
115   char *name;
116
117
118   domain = strrchr (address, '@');
119   if (!domain || domain == address || !domain[1])
120     return NULL; /* invalid mail address given. */
121
122   name = xtrymalloc (strlen (address) + 5 + 1);
123   if (!name)
124     return NULL;
125   memcpy (name, address, domain - address);
126   strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
127
128   anslen = res_query (name, C_IN, T_TXT, answer, PACKETSZ);
129   xfree (name);
130   if (anslen < sizeof(HEADER))
131     return NULL; /* DNS resolver returned a too short answer. */
132   if ( (rc=((HEADER*)answer)->rcode) != NOERROR )
133     return NULL; /* DNS resolver returned an error. */
134
135   /* We assume that PACKETSZ is large enough and don't do dynmically
136      expansion of the buffer. */
137   if (anslen > PACKETSZ)
138     return NULL; /* DNS resolver returned a too long answer */
139
140   qdcount = ntohs (((HEADER*)answer)->qdcount);
141   ancount = ntohs (((HEADER*)answer)->ancount);
142   nscount = ntohs (((HEADER*)answer)->nscount);
143   arcount = ntohs (((HEADER*)answer)->arcount);
144
145   if (!ancount)
146     return NULL; /* Got no answer. */
147
148   p = answer + sizeof (HEADER);
149   pend = answer + anslen; /* Actually points directly behind the buffer. */
150
151   while (qdcount-- && p < pend)
152     {
153       rc = dn_skipname (p, pend);
154       if (rc == -1)
155         return NULL;
156       p += rc + QFIXEDSZ; 
157     }
158
159   if (ancount > 1)
160     return NULL; /* more than one possible gpg trustdns record - none used. */
161
162   while (ancount-- && p <= pend)
163     {
164       unsigned int type, class, txtlen, n;
165       char *buffer, *bufp;
166
167       rc = dn_skipname (p, pend);
168       if (rc == -1)
169         return NULL;
170       p += rc;
171       if (p >= pend - 10)
172         return NULL; /* RR too short. */
173
174       type = *p++ << 8;
175       type |= *p++;
176       class = *p++ << 8;
177       class |= *p++;
178       p += 4;
179       txtlen = *p++ << 8;
180       txtlen |= *p++;
181       if (type != T_TXT || class != C_IN)
182         return NULL; /* Answer does not match the query. */
183
184       buffer = bufp = xmalloc (txtlen + 1);
185       while (txtlen && p < pend)
186         {
187           for (n = *p++, txtlen--; txtlen && n && p < pend; txtlen--, n--)
188             *bufp++ = *p++;
189         }
190       *bufp = 0;
191       if (parse_txt_record (buffer, fpr))
192         {
193           xfree (buffer);
194           return NULL; /* Not a valid gpg trustdns RR. */
195         }
196       return buffer;
197     }
198
199   return NULL;
200 }
201 #else /* !USE_DNS_PKA */
202
203 /* Dummy version of the function if we can't use the resolver
204    functions. */
205 char *
206 get_pka_info (const char *address, unsigned char *fpr)
207 {
208   return NULL;
209 }
210 #endif /* !USE_DNS_PKA */
211
212
213 #ifdef TEST
214 int
215 main(int argc,char *argv[])
216 {
217   unsigned char fpr[20];
218   char *uri;
219   int i;
220
221   if (argc < 2)
222     {
223       fprintf (stderr, "usage: pka mail-addresses\n");
224       return 1;
225     }
226   argc--;
227   argv++;
228
229   for (; argc; argc--, argv++)
230     {
231       uri = get_pka_info ( *argv, fpr );
232       printf ("%s", *argv);
233       if (uri)
234         {
235           putchar (' ');
236           for (i=0; i < 20; i++)
237             printf ("%02X", fpr[i]);
238           if (*uri)
239             printf (" %s", uri);
240           xfree (uri);
241         }
242       putchar ('\n');
243     }
244   return 0;
245 }
246 #endif /* TEST */
247
248 /*
249 Local Variables:
250 compile-command: "cc -DUSE_DNS_PKA -DTEST -I.. -I../include -Wall -g -o pka pka.c -lresolv libutil.a"
251 End:
252 */