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