Changed to GPLv3.
[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 optiobnal.
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 = malloc (strlen (address) + 5 + 1);
123   memcpy (name, address, domain - address);
124   strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
125
126   anslen = res_query (name, C_IN, T_TXT, answer, PACKETSZ);
127   xfree (name);
128   if (anslen < sizeof(HEADER))
129     return NULL; /* DNS resolver returned a too short answer. */
130   if ( (rc=((HEADER*)answer)->rcode) != NOERROR )
131     return NULL; /* DNS resolver returned an error. */
132
133   /* We assume that PACKETSZ is large enough and don't do dynmically
134      expansion of the buffer. */
135   if (anslen > PACKETSZ)
136     return NULL; /* DNS resolver returned a too long answer */
137
138   qdcount = ntohs (((HEADER*)answer)->qdcount);
139   ancount = ntohs (((HEADER*)answer)->ancount);
140   nscount = ntohs (((HEADER*)answer)->nscount);
141   arcount = ntohs (((HEADER*)answer)->arcount);
142
143   if (!ancount)
144     return NULL; /* Got no answer. */
145
146   p = answer + sizeof (HEADER);
147   pend = answer + anslen; /* Actually points directly behind the buffer. */
148
149   while (qdcount-- && p < pend)
150     {
151       rc = dn_skipname (p, pend);
152       if (rc == -1)
153         return NULL;
154       p += rc + QFIXEDSZ; 
155     }
156
157   if (ancount > 1)
158     return NULL; /* more than one possible gpg trustdns record - none used. */
159
160   while (ancount-- && p <= pend)
161     {
162       unsigned int type, class, txtlen, n;
163       char *buffer, *bufp;
164
165       rc = dn_skipname (p, pend);
166       if (rc == -1)
167         return NULL;
168       p += rc;
169       if (p >= pend - 10)
170         return NULL; /* RR too short. */
171
172       type = *p++ << 8;
173       type |= *p++;
174       class = *p++ << 8;
175       class |= *p++;
176       p += 4;
177       txtlen = *p++ << 8;
178       txtlen |= *p++;
179       if (type != T_TXT || class != C_IN)
180         return NULL; /* Answer does not match the query. */
181
182       buffer = bufp = xmalloc (txtlen + 1);
183       while (txtlen && p < pend)
184         {
185           for (n = *p++, txtlen--; txtlen && n && p < pend; txtlen--, n--)
186             *bufp++ = *p++;
187         }
188       *bufp = 0;
189       if (parse_txt_record (buffer, fpr))
190         {
191           xfree (buffer);
192           return NULL; /* Not a valid gpg trustdns RR. */
193         }
194       return buffer;
195     }
196
197   return NULL;
198 }
199 #else /* !USE_DNS_PKA */
200
201 /* Dummy version of the function if we can't use the resolver
202    functions. */
203 char *
204 get_pka_info (const char *address, unsigned char *fpr)
205 {
206   return NULL;
207 }
208 #endif /* !USE_DNS_PKA */
209
210
211 #ifdef TEST
212 int
213 main(int argc,char *argv[])
214 {
215   unsigned char fpr[20];
216   char *uri;
217   int i;
218
219   if (argc < 2)
220     {
221       fprintf (stderr, "usage: pka mail-addresses\n");
222       return 1;
223     }
224   argc--;
225   argv++;
226
227   for (; argc; argc--, argv++)
228     {
229       uri = get_pka_info ( *argv, fpr );
230       printf ("%s", *argv);
231       if (uri)
232         {
233           putchar (' ');
234           for (i=0; i < 20; i++)
235             printf ("%02X", fpr[i]);
236           if (*uri)
237             printf (" %s", uri);
238           xfree (uri);
239         }
240       putchar ('\n');
241     }
242   return 0;
243 }
244 #endif /* TEST */
245
246 /*
247 Local Variables:
248 compile-command: "cc -DUSE_DNS_PKA -DTEST -I.. -I../include -Wall -g -o pka pka.c -lresolv libutil.a"
249 End:
250 */