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