1 /*
2 ** Copyright (c) 2005-2009 Sendmail, Inc. and its suppliers.
3 ** All rights reserved.
4 **
5 ** Copyright (c) 2009-2015, The Trusted Domain Project. All rights reserved.
6 */
7
8 /* system includes */
9 #include <sys/param.h>
10 #include <sys/types.h>
11 #include <netinet/in.h>
12 #include <arpa/inet.h>
13 #include <arpa/nameser.h>
14 #include <netdb.h>
15 #include <resolv.h>
16 #include <assert.h>
17 #include <stdio.h>
18 #include <ctype.h>
19 #include <string.h>
20 #include <errno.h>
21
22 #include "build-config.h"
23
24 /* libopendkim includes */
25 #include "dkim-internal.h"
26 #include "dkim-types.h"
27 #include "dkim-keys.h"
28 #include "dkim-cache.h"
29 #include "dkim-test.h"
30 #include "util.h"
31
32 /* libbsd if found */
33 #ifdef USE_BSD_H
34 # include <bsd/string.h>
35 #endif /* USE_BSD_H */
36
37 /* libstrl if needed */
38 #ifdef USE_STRL_H
39 # include <strl.h>
40 #endif /* USE_STRL_H */
41
42 /* prototypes */
43 extern void dkim_error __P((DKIM *, const char *, ...));
44
45 /* local definitions needed for DNS queries */
46 #define MAXPACKET 8192
47 #if defined(__RES) && (__RES >= 19940415)
48 # define RES_UNC_T char *
49 #else /* __RES && __RES >= 19940415 */
50 # define RES_UNC_T unsigned char *
51 #endif /* __RES && __RES >= 19940415 */
52 #ifndef T_RRSIG
53 # define T_RRSIG 46
54 #endif /* ! T_RRSIG */
55
56 /*
57 ** DKIM_GET_KEY_DNS -- retrieve a DKIM key from DNS
58 **
59 ** Parameters:
60 ** dkim -- DKIM handle
61 ** sig -- DKIM_SIGINFO handle
62 ** buf -- buffer into which to write the result
63 ** buflen -- bytes available at "buf"
64 **
65 ** Return value:
66 ** A DKIM_STAT_* constant.
67 */
68
69 DKIM_STAT
dkim_get_key_dns(DKIM * dkim,DKIM_SIGINFO * sig,u_char * buf,size_t buflen)70 dkim_get_key_dns(DKIM *dkim, DKIM_SIGINFO *sig, u_char *buf, size_t buflen)
71 {
72 #ifdef QUERY_CACHE
73 _Bool cached = FALSE;
74 uint32_t ttl = 0;
75 #endif /* QUERY_CACHE */
76 int status;
77 int qdcount;
78 int ancount;
79 int error;
80 int dnssec = DKIM_DNSSEC_UNKNOWN;
81 int c;
82 int n = 0;
83 int rdlength = 0;
84 int type = -1;
85 int class = -1;
86 size_t anslen;
87 void *q;
88 DKIM_LIB *lib;
89 unsigned char *txtfound = NULL;
90 unsigned char *p;
91 unsigned char *cp;
92 unsigned char *eom;
93 unsigned char *eob;
94 unsigned char qname[DKIM_MAXHOSTNAMELEN + 1];
95 unsigned char ansbuf[MAXPACKET];
96 struct timeval timeout;
97 HEADER hdr;
98
99 assert(dkim != NULL);
100 assert(sig != NULL);
101 assert(sig->sig_selector != NULL);
102 assert(sig->sig_domain != NULL);
103
104 lib = dkim->dkim_libhandle;
105
106 n = snprintf((char *) qname, sizeof qname - 1, "%s.%s.%s",
107 sig->sig_selector, DKIM_DNSKEYNAME, sig->sig_domain);
108 if (n == -1 || n > sizeof qname - 1)
109 {
110 dkim_error(dkim, "key query name too large");
111 return DKIM_STAT_NORESOURCE;
112 }
113
114 #ifdef QUERY_CACHE
115 /* see if we have this data already cached */
116 if (dkim->dkim_libhandle->dkiml_cache != NULL)
117 {
118 int err = 0;
119 size_t blen = buflen;
120
121 dkim->dkim_cache_queries++;
122
123 status = dkim_cache_query(dkim->dkim_libhandle->dkiml_cache,
124 qname, 0, buf, &blen, &err);
125 if (status == 0)
126 {
127 dkim->dkim_cache_hits++;
128 return DKIM_STAT_OK;
129 }
130 /* XXX -- do something with errors here */
131 }
132 #endif /* QUERY_CACHE */
133
134 /* see if there's a simulated reply queued; if so, use it */
135 anslen = dkim_test_dns_get(dkim, ansbuf, sizeof ansbuf);
136 if (anslen == -1)
137 {
138 anslen = sizeof ansbuf;
139
140 timeout.tv_sec = dkim->dkim_timeout;
141 timeout.tv_usec = 0;
142
143 if (lib->dkiml_dns_service == NULL &&
144 lib->dkiml_dns_init != NULL &&
145 lib->dkiml_dns_init(&lib->dkiml_dns_service) != 0)
146 {
147 dkim_error(dkim, "cannot initialize resolver");
148 return DKIM_STAT_KEYFAIL;
149 }
150
151 status = lib->dkiml_dns_start(lib->dkiml_dns_service, T_TXT,
152 qname, ansbuf, anslen, &q);
153
154 if (status != 0)
155 {
156 dkim_error(dkim, "'%s' query failed", qname);
157 return DKIM_STAT_KEYFAIL;
158 }
159
160 if (lib->dkiml_dns_callback == NULL)
161 {
162 timeout.tv_sec = dkim->dkim_timeout;
163 timeout.tv_usec = 0;
164
165 status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
166 q,
167 dkim->dkim_timeout == 0 ? NULL
168 : &timeout,
169 &anslen, &error,
170 &dnssec);
171 }
172 else
173 {
174 struct timeval master;
175 struct timeval next;
176 struct timeval *wt;
177
178 (void) gettimeofday(&master, NULL);
179 master.tv_sec += dkim->dkim_timeout;
180
181 for (;;)
182 {
183 (void) gettimeofday(&next, NULL);
184 next.tv_sec += lib->dkiml_callback_int;
185
186 dkim_min_timeval(&master, &next,
187 &timeout, &wt);
188
189 status = lib->dkiml_dns_waitreply(lib->dkiml_dns_service,
190 q,
191 dkim->dkim_timeout == 0 ? NULL
192 : &timeout,
193 &anslen,
194 &error,
195 &dnssec);
196
197 if (wt == &next)
198 {
199 if (status == DKIM_DNS_NOREPLY ||
200 status == DKIM_DNS_EXPIRED)
201 lib->dkiml_dns_callback(dkim->dkim_user_context);
202 else
203 break;
204 }
205 else
206 {
207 break;
208 }
209 }
210 }
211
212 if (status == DKIM_DNS_EXPIRED)
213 {
214 (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q);
215 dkim_error(dkim, "'%s' query timed out", qname);
216 return DKIM_STAT_KEYFAIL;
217 }
218 else if (status == DKIM_DNS_ERROR)
219 {
220 (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q);
221 dkim_error(dkim, "'%s' query failed", qname);
222 return DKIM_STAT_KEYFAIL;
223 }
224
225 (void) lib->dkiml_dns_cancel(lib->dkiml_dns_service, q);
226
227 sig->sig_dnssec_key = dnssec;
228 }
229
230 /* set up pointers */
231 memcpy(&hdr, ansbuf, sizeof hdr);
232 cp = (u_char *) &ansbuf + HFIXEDSZ;
233 eom = (u_char *) &ansbuf + anslen;
234
235 /* skip over the name at the front of the answer */
236 for (qdcount = ntohs((unsigned short) hdr.qdcount);
237 qdcount > 0;
238 qdcount--)
239 {
240 /* copy it first */
241 (void) dn_expand((unsigned char *) &ansbuf, eom, cp,
242 (char *) qname, sizeof qname);
243
244 if ((n = dn_skipname(cp, eom)) < 0)
245 {
246 dkim_error(dkim, "'%s' reply corrupt", qname);
247 return DKIM_STAT_KEYFAIL;
248 }
249 cp += n;
250
251 /* extract the type and class */
252 if (cp + INT16SZ + INT16SZ > eom)
253 {
254 dkim_error(dkim, "'%s' reply corrupt", qname);
255 return DKIM_STAT_KEYFAIL;
256 }
257 GETSHORT(type, cp);
258 GETSHORT(class, cp);
259 }
260
261 if (type != T_TXT || class != C_IN)
262 {
263 dkim_error(dkim, "'%s' unexpected reply class/type (%d/%d)",
264 qname, class, type);
265 return DKIM_STAT_KEYFAIL;
266 }
267
268 /* if NXDOMAIN, return DKIM_STAT_NOKEY */
269 if (hdr.rcode == NXDOMAIN)
270 {
271 dkim_error(dkim, "'%s' record not found", qname);
272 return DKIM_STAT_NOKEY;
273 }
274
275 /* if truncated, we can't do it */
276 if (dkim_check_dns_reply(ansbuf, anslen, C_IN, T_TXT) == 1)
277 {
278 dkim_error(dkim, "'%s' reply truncated", qname);
279 return DKIM_STAT_KEYFAIL;
280 }
281
282 /* get the answer count */
283 ancount = ntohs((unsigned short) hdr.ancount);
284 if (ancount == 0)
285 return DKIM_STAT_NOKEY;
286
287 /*
288 ** Extract the data from the first TXT answer.
289 */
290
291 while (--ancount >= 0 && cp < eom)
292 {
293 /* grab the label, even though we know what we asked... */
294 if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
295 (RES_UNC_T) qname, sizeof qname)) < 0)
296 {
297 dkim_error(dkim, "'%s' reply corrupt", qname);
298 return DKIM_STAT_KEYFAIL;
299 }
300 /* ...and move past it */
301 cp += n;
302
303 /* extract the type and class */
304 if (cp + INT16SZ + INT16SZ + INT32SZ + INT16SZ > eom)
305 {
306 dkim_error(dkim, "'%s' reply corrupt", qname);
307 return DKIM_STAT_KEYFAIL;
308 }
309
310 GETSHORT(type, cp); /* TYPE */
311 GETSHORT(class, cp); /* CLASS */
312 #ifdef QUERY_CACHE
313 /* get the TTL */
314 GETLONG(ttl, cp); /* TTL */
315 #else /* QUERY_CACHE */
316 /* skip the TTL */
317 cp += INT32SZ; /* TTL */
318 #endif /* QUERY_CACHE */
319 GETSHORT(n, cp); /* RDLENGTH */
320
321 /* skip CNAME if found; assume it was resolved */
322 if (type == T_CNAME)
323 {
324 cp += n;
325 continue;
326 }
327 else if (type == T_RRSIG)
328 {
329 cp += n;
330 continue;
331 }
332 else if (type != T_TXT)
333 {
334 dkim_error(dkim, "'%s' reply was unexpected type %d",
335 qname, type);
336 return DKIM_STAT_KEYFAIL;
337 }
338
339 if (txtfound != NULL)
340 {
341 dkim_error(dkim, "multiple DNS replies for '%s'",
342 qname);
343 return DKIM_STAT_MULTIDNSREPLY;
344 }
345
346 /* remember where this one started */
347 txtfound = cp;
348 rdlength = n;
349
350 /* move forward for now */
351 cp += n;
352 }
353
354 /* if ancount went below 0, there were no good records */
355 if (txtfound == NULL)
356 {
357 dkim_error(dkim, "'%s' reply was unresolved CNAME", qname);
358 return DKIM_STAT_NOKEY;
359 }
360
361 /* come back to the one we found */
362 cp = txtfound;
363
364 /*
365 ** XXX -- maybe deal with a partial reply rather than require
366 ** it all
367 */
368
369 if (cp + rdlength > eom)
370 {
371 dkim_error(dkim, "'%s' reply corrupt", qname);
372 return DKIM_STAT_SYNTAX;
373 }
374
375 /* extract the payload */
376 memset(buf, '\0', buflen);
377 p = buf;
378 eob = buf + buflen - 1;
379 while (rdlength > 0 && p < eob)
380 {
381 c = *cp++;
382 rdlength--;
383 while (c > 0 && p < eob)
384 {
385 *p++ = *cp++;
386 c--;
387 rdlength--;
388 }
389 }
390
391 #ifdef QUERY_CACHE
392 if (!cached && buf[0] != '\0' &&
393 dkim->dkim_libhandle->dkiml_cache != NULL)
394 {
395 int err = 0;
396
397 status = dkim_cache_insert(dkim->dkim_libhandle->dkiml_cache,
398 qname, buf, ttl, &err);
399 /* XXX -- do something with errors here */
400 }
401 #endif /* QUERY_CACHE */
402
403 return DKIM_STAT_OK;
404 }
405
406 /*
407 ** DKIM_GET_KEY_FILE -- retrieve a DKIM key from a text file (for testing)
408 **
409 ** Parameters:
410 ** dkim -- DKIM handle
411 ** sig -- DKIM_SIGINFO handle
412 ** buf -- buffer into which to write the result
413 ** buflen -- bytes available at "buf"
414 **
415 ** Return value:
416 ** A DKIM_STAT_* constant.
417 **
418 ** Notes:
419 ** The file opened is defined by the library option DKIM_OPTS_QUERYINFO
420 ** and must be set prior to use of this function. Failing to do
421 ** so will cause this function to return DKIM_STAT_KEYFAIL every time.
422 ** The file should contain lines of the form:
423 **
424 ** <selector>._domainkey.<domain> <space> key-data
425 **
426 ** Case matching on the left is case-sensitive, but libopendkim already
427 ** wraps the domain name to lowercase.
428 */
429
430 DKIM_STAT
dkim_get_key_file(DKIM * dkim,DKIM_SIGINFO * sig,u_char * buf,size_t buflen)431 dkim_get_key_file(DKIM *dkim, DKIM_SIGINFO *sig, u_char *buf, size_t buflen)
432 {
433 int n;
434 FILE *f;
435 u_char *p;
436 u_char *p2;
437 u_char *path;
438 char name[DKIM_MAXHOSTNAMELEN + 1];
439
440 assert(dkim != NULL);
441 assert(sig != NULL);
442 assert(sig->sig_selector != NULL);
443 assert(sig->sig_domain != NULL);
444 assert(sig->sig_query == DKIM_QUERY_FILE);
445
446 path = dkim->dkim_libhandle->dkiml_queryinfo;
447 if (path[0] == '\0')
448 {
449 dkim_error(dkim, "query file not defined");
450 return DKIM_STAT_KEYFAIL;
451 }
452
453 f = fopen((char *) path, "r");
454 if (f == NULL)
455 {
456 dkim_error(dkim, "%s: fopen(): %s", path, strerror(errno));
457 return DKIM_STAT_KEYFAIL;
458 }
459
460 n = snprintf(name, sizeof name, "%s.%s.%s", sig->sig_selector,
461 DKIM_DNSKEYNAME, sig->sig_domain);
462 if (n == -1 || n > sizeof name)
463 {
464 dkim_error(dkim, "key query name too large");
465 fclose(f);
466 return DKIM_STAT_NORESOURCE;
467 }
468
469 memset(buf, '\0', buflen);
470 while (fgets((char *) buf, buflen, f) != NULL)
471 {
472 if (buf[0] == '#')
473 continue;
474
475 p2 = NULL;
476
477 for (p = buf; *p != '\0'; p++)
478 {
479 if (*p == '\n')
480 {
481 *p = '\0';
482 break;
483 }
484 else if (isascii(*p) && isspace(*p))
485 {
486 *p = '\0';
487 p2 = p + 1;
488 }
489 else if (p2 != NULL)
490 {
491 break;
492 }
493 }
494
495 if (strcasecmp((char *) name, (char *) buf) == 0 && p2 != NULL)
496 {
497 memmove(buf, p2, strlen(p2) + 1);
498 fclose(f);
499 return DKIM_STAT_OK;
500 }
501 }
502
503 fclose(f);
504
505 return DKIM_STAT_NOKEY;
506 }
507