agent/
[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 2 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, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19  * USA.
20  */
21
22 #include <config.h>
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #ifdef USE_DNS_PKA
29 #include <sys/types.h>
30 #ifdef _WIN32
31 #include <windows.h>
32 #else
33 #include <netinet/in.h>
34 #include <arpa/nameser.h>
35 #include <resolv.h>
36 #endif
37 #endif /* USE_DNS_PKA */
38
39 #include "util.h"
40 #include "pka.h"
41
42 #ifdef USE_DNS_PKA
43 /* Parse the TXT resource record. Format is:
44
45    v=pka1;fpr=a4d94e92b0986ab5ee9dcd755de249965b0358a2;uri=string
46    
47    For simplicity white spaces are not allowed.  Because we expect to
48    use a new RRTYPE for this in the future we define the TXT really
49    strict for simplicity: No white spaces, case sensitivity of the
50    names, order must be as given above.  Only URI is optional.
51
52    This function modifies BUFFER.  On success 0 is returned, the 20
53    byte fingerprint stored at FPR and BUFFER contains the URI or an
54    empty string.
55 */
56 static int
57 parse_txt_record (char *buffer, unsigned char *fpr)
58 {
59   char *p, *pend;
60   int i;
61
62   p = buffer;
63   pend = strchr (p, ';');
64   if (!pend)
65     return -1;
66   *pend++ = 0;
67   if (strcmp (p, "v=pka1"))
68     return -1; /* Wrong or missing version. */
69   
70   p = pend;
71   pend = strchr (p, ';');
72   if (pend)
73     *pend++ = 0;
74   if (strncmp (p, "fpr=", 4))
75     return -1; /* Missing fingerprint part. */
76   p += 4;
77   for (i=0; i < 20 && hexdigitp (p) && hexdigitp (p+1); i++, p += 2)
78     fpr[i] = xtoi_2 (p);
79   if (i != 20)
80     return -1; /* Fingerprint consists not of exactly 40 hexbytes. */
81     
82   p = pend;
83   if (!p || !*p)
84     {
85       *buffer = 0;  
86       return 0; /* Success (no URI given). */
87     }
88   if (strncmp (p, "uri=", 4))
89     return -1; /* Unknown part. */
90   p += 4;
91   /* There is an URI, copy it to the start of the buffer. */
92   while (*p)
93     *buffer++ = *p++;
94   *buffer = 0;
95   return 0;
96 }
97
98
99 /* For the given email ADDRESS lookup the PKA information in the DNS.
100
101    On success the 20 byte SHA-1 fingerprint is stored at FPR and the
102    URI will be returned in an allocated buffer.  Note that the URI
103    might be an zero length string as this information is optiobnal.
104    Caller must xfree the returned string.
105
106    On error NULL is returned and the 20 bytes at FPR are not
107    defined. */
108 char *
109 get_pka_info (const char *address, unsigned char *fpr)
110 {
111   unsigned char answer[PACKETSZ];
112   int anslen;
113   int qdcount, ancount, nscount, arcount;
114   int rc;
115   unsigned char *p, *pend;
116   const char *domain;
117   char *name;
118
119
120   domain = strrchr (address, '@');
121   if (!domain || domain == address || !domain[1])
122     return NULL; /* invalid mail address given. */
123
124   name = malloc (strlen (address) + 5 + 1);
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 */