xref: /openbsd-src/usr.sbin/smtpd/dns.c (revision 4c1e55dc91edd6e69ccc60ce855900fbc12cf34f)
1 /*	$OpenBSD: dns.c,v 1.49 2012/07/09 12:16:24 eric Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
5  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6  * Copyright (c) 2011 Eric Faurot <eric@faurot.net>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/socket.h>
23 #include <sys/tree.h>
24 #include <sys/queue.h>
25 #include <sys/uio.h>
26 
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include <arpa/nameser.h>
30 
31 #include <event.h>
32 #include <imsg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #include "asr.h"
38 #include "asr_private.h"
39 #include "smtpd.h"
40 #include "log.h"
41 
42 
43 struct mx {
44 	char	host[MAXHOSTNAMELEN];
45 	int	prio;
46 };
47 
48 struct dnssession {
49 	SPLAY_ENTRY(dnssession)		 nodes;
50 	u_int64_t			 id;
51 	struct dns			 query;
52 	struct event			 ev;
53 	struct async			*as;
54 	struct mx			 mxarray[MAX_MX_COUNT];
55 	size_t				 mxarraysz;
56 	size_t				 mxcurrent;
57 	size_t				 mxfound;
58 };
59 
60 static int  dnssession_cmp(struct dnssession *, struct dnssession *);
61 
62 SPLAY_HEAD(dnstree, dnssession) dns_sessions = SPLAY_INITIALIZER(&dns_sessions);
63 
64 SPLAY_PROTOTYPE(dnstree, dnssession, nodes, dnssession_cmp);
65 
66 
67 static struct dnssession *dnssession_init(struct dns *);
68 static void dnssession_destroy(struct dnssession *);
69 static void dnssession_mx_insert(struct dnssession *, const char *, int);
70 static void dns_asr_event_set(struct dnssession *, struct async_res *);
71 static void dns_asr_handler(int, short, void *);
72 static int  dns_asr_error(int);
73 static void dns_asr_dispatch_host(struct dnssession *);
74 static void dns_asr_dispatch_mx(struct dnssession *);
75 static void dns_asr_dispatch_cname(struct dnssession *);
76 static void dns_reply(struct dns *, int);
77 
78 #define print_dname(a,b,c) asr_strdname(a, b, c)
79 
80 /*
81  * User interface.
82  */
83 
84 void
85 dns_query_host(char *host, int port, u_int64_t id)
86 {
87 	struct dns	 query;
88 
89 	bzero(&query, sizeof(query));
90 	strlcpy(query.host, host, sizeof(query.host));
91 	query.port = port;
92 	query.id = id;
93 
94 	imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_HOST, 0, 0, -1,
95 	    &query, sizeof(query));
96 }
97 
98 void
99 dns_query_mx(char *host, int port, u_int64_t id)
100 {
101 	struct dns	 query;
102 
103 	bzero(&query, sizeof(query));
104 	strlcpy(query.host, host, sizeof(query.host));
105 	query.port = port;
106 	query.id = id;
107 
108 	imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_MX, 0, 0, -1, &query,
109 	    sizeof(query));
110 }
111 
112 void
113 dns_query_ptr(struct sockaddr_storage *ss, u_int64_t id)
114 {
115 	struct dns	 query;
116 
117 	bzero(&query, sizeof(query));
118 	query.ss = *ss;
119 	query.id = id;
120 
121 	imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_PTR, 0, 0, -1, &query,
122 	    sizeof(query));
123 }
124 
125 /* LKA interface */
126 void
127 dns_async(struct imsgev *asker, int type, struct dns *query)
128 {
129 	struct dnssession *s;
130 
131 	query->type  = type;
132 	query->asker = asker;
133 	s = dnssession_init(query);
134 
135 	switch (type) {
136 	case IMSG_DNS_HOST:
137 		log_debug("dns: lookup host \"%s\"", query->host);
138 		if (sockaddr_from_str((struct sockaddr*)&query->ss, PF_UNSPEC,
139 		    query->host) == 0) {
140 			log_debug("dns:  \"%s\" is an IP address", query->host);
141 			query->error = DNS_OK;
142 			dns_reply(query, IMSG_DNS_HOST);
143 			dns_reply(query, IMSG_DNS_HOST_END);
144 			dnssession_destroy(s);
145 			return;
146 		}
147 		dnssession_mx_insert(s, query->host, 0);
148 		stat_increment(STATS_LKA_SESSION_HOST);
149 		log_debug("dns: ready?");
150 		dns_asr_dispatch_host(s);
151 		return;
152 	case IMSG_DNS_PTR:
153 		s->as = getnameinfo_async((struct sockaddr*)&query->ss,
154 		    query->ss.ss_len,
155 		    s->query.host, sizeof(s->query.host), NULL, 0, 0, NULL);
156 		stat_increment(STATS_LKA_SESSION_CNAME);
157 		if (s->as == NULL) {
158 			log_debug("dns_async: asr_query_cname error");
159 			break;
160 		}
161 		dns_asr_dispatch_cname(s);
162 		return;
163 	case IMSG_DNS_MX:
164 		log_debug("dns: lookup mx \"%s\"", query->host);
165 		s->as = res_query_async(query->host, C_IN, T_MX, NULL, 0, NULL);
166 		stat_increment(STATS_LKA_SESSION_MX);
167 		if (s->as == NULL) {
168 			log_debug("dns_async: asr_query_dns error");
169 			break;
170 		}
171 		dns_asr_dispatch_mx(s);
172 		return;
173 	default:
174 		log_debug("dns_async: bad request");
175 		break;
176 	}
177 
178 	stat_increment(STATS_LKA_FAILURE);
179 	dnssession_destroy(s);
180 }
181 
182 static void
183 dns_reply(struct dns *query, int type)
184 {
185 	imsg_compose_event(query->asker, type, 0, 0, -1, query, sizeof(*query));
186 }
187 
188 static void
189 dns_asr_event_set(struct dnssession *s, struct async_res *ar)
190 {
191 	struct timeval tv = { 0, 0 };
192 
193 	tv.tv_usec = ar->ar_timeout * 1000;
194 	event_set(&s->ev, ar->ar_fd,
195 	    ar->ar_cond == ASYNC_READ ? EV_READ : EV_WRITE, dns_asr_handler, s);
196 	event_add(&s->ev, &tv);
197 }
198 
199 static void
200 dns_asr_handler(int fd, short event, void *arg)
201 {
202 	struct dnssession *s = arg;
203 
204 	switch(s->query.type) {
205 	case IMSG_DNS_HOST:
206 		dns_asr_dispatch_host(s);
207 		break;
208 	case IMSG_DNS_PTR:
209 		dns_asr_dispatch_cname(s);
210 		break;
211 	case IMSG_DNS_MX:
212 		dns_asr_dispatch_mx(s);
213 		break;
214 	default:
215 		fatalx("bad query type");
216 	}
217 }
218 
219 static int
220 dns_asr_error(int ar_err)
221 {
222 	switch (ar_err) {
223 	case 0:
224 		return DNS_OK;
225 	case NO_DATA:
226 	case NO_RECOVERY:
227 		stat_increment(STATS_LKA_FAILURE);
228 		return DNS_EINVAL;
229 	default:
230 		return DNS_RETRY;
231 	}
232 }
233 
234 static void
235 dns_asr_dispatch_mx(struct dnssession *s)
236 {
237 	struct dns		*query = &s->query;
238 	struct async_res	 ar;
239 	struct packed		 pack;
240 	struct header		 h;
241 	struct query		 q;
242 	struct rr		 rr;
243 	char			 buf[512];
244 
245 	if (async_run(s->as, &ar) == ASYNC_COND) {
246 		dns_asr_event_set(s, &ar);
247 		return;
248 	}
249 
250 	if (ar.ar_errno || ar.ar_h_errno || ar.ar_rcode == NXDOMAIN) {
251 		query->error = ar.ar_rcode == NXDOMAIN ? \
252 			DNS_ENONAME : dns_asr_error(ar.ar_h_errno);
253 		dns_reply(query, IMSG_DNS_HOST_END);
254 		dnssession_destroy(s);
255 		free(ar.ar_data);
256 		return;
257 	}
258 
259 	packed_init(&pack, ar.ar_data, ar.ar_datalen);
260 	unpack_header(&pack, &h);
261 	unpack_query(&pack, &q);
262 
263 	if (h.ancount == 0)
264 		/* fallback to host if no MX is found. */
265 		dnssession_mx_insert(s, query->host, 0);
266 
267 	for (; h.ancount; h.ancount--) {
268 		unpack_rr(&pack, &rr);
269 		print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
270 		buf[strlen(buf) - 1] = '\0';
271 		dnssession_mx_insert(s, buf, rr.rr.mx.preference);
272 	}
273 
274 	free(ar.ar_data);
275 
276 	/* Now we have a sorted list of MX to resolve. Simply "turn" this
277 	 * MX session into a regular host session.
278 	 */
279 	s->as = NULL;
280 	s->query.type = IMSG_DNS_HOST;
281 	dns_asr_dispatch_host(s);
282 }
283 
284 static void
285 dns_asr_dispatch_host(struct dnssession *s)
286 {
287 	struct dns		*query = &s->query;
288 	struct mx		*mx;
289 	struct async_res	 ar;
290 	struct addrinfo		 hints, *ai;
291 
292 	/* default to notfound, override with retry or ok later */
293 	if (s->mxcurrent == 0)
294 		query->error = DNS_ENOTFOUND;
295 
296 next:
297 	/* query all listed hosts in turn */
298 	while (s->as == NULL) {
299 		if (s->mxcurrent == s->mxarraysz) {
300 			if (s->mxfound)
301 				query->error = DNS_OK;
302 			dns_reply(query, IMSG_DNS_HOST_END);
303 			dnssession_destroy(s);
304 			return;
305 		}
306 
307 		mx = s->mxarray + s->mxcurrent++;
308 		memset(&hints, 0, sizeof(hints));
309            	hints.ai_family = PF_UNSPEC;
310            	hints.ai_socktype = SOCK_STREAM;
311 		s->as = getaddrinfo_async(mx->host, NULL, &hints, NULL);
312 	}
313 
314 	if (async_run(s->as, &ar) == ASYNC_COND) {
315 		dns_asr_event_set(s, &ar);
316 		return;
317 	}
318 
319 	if (ar.ar_gai_errno == 0) {
320 		for (ai = ar.ar_addrinfo; ai; ai = ai->ai_next) {
321 			memcpy(&query->ss, ai->ai_addr, ai->ai_addrlen);
322 			dns_reply(query, IMSG_DNS_HOST);
323 			s->mxfound++;
324 		}
325 		freeaddrinfo(ar.ar_addrinfo);
326 	}
327 
328 	s->as = NULL;
329 	goto next;
330 }
331 
332 static void
333 dns_asr_dispatch_cname(struct dnssession *s)
334 {
335 	struct dns		*query = &s->query;
336 	struct async_res	 ar;
337 
338 	if (async_run(s->as, &ar) == ASYNC_COND) {
339 		dns_asr_event_set(s, &ar);
340 		return;
341 	}
342 
343 	/* the error code could be more precise, but we don't currently care */
344 	query->error = ar.ar_gai_errno ? DNS_ENOTFOUND : DNS_OK;
345 	dns_reply(query, IMSG_DNS_PTR);
346 	dnssession_destroy(s);
347 }
348 
349 static struct dnssession *
350 dnssession_init(struct dns *query)
351 {
352 	struct dnssession *s;
353 
354 	s = calloc(1, sizeof(struct dnssession));
355 	if (s == NULL)
356 		fatal("dnssession_init: calloc");
357 
358 	stat_increment(STATS_LKA_SESSION);
359 
360 	s->id = query->id;
361 	s->query = *query;
362 	SPLAY_INSERT(dnstree, &dns_sessions, s);
363 	return (s);
364 }
365 
366 static void
367 dnssession_destroy(struct dnssession *s)
368 {
369 	stat_decrement(STATS_LKA_SESSION);
370 	SPLAY_REMOVE(dnstree, &dns_sessions, s);
371 	event_del(&s->ev);
372 	free(s);
373 }
374 
375 static void
376 dnssession_mx_insert(struct dnssession *s, const char *host, int prio)
377 {
378 	size_t i, j;
379 
380 	for (i = 0; i < s->mxarraysz; i++)
381 		if (prio < s->mxarray[i].prio)
382 			break;
383 
384 	if (i == MAX_MX_COUNT)
385 		return;
386 
387 	if (s->mxarraysz < MAX_MX_COUNT)
388 		s->mxarraysz++;
389 
390 	for (j = s->mxarraysz - 1; j > i; j--)
391 		s->mxarray[j] = s->mxarray[j - 1];
392 
393         s->mxarray[i].prio = prio;
394 	strlcpy(s->mxarray[i].host, host,
395 	    sizeof (s->mxarray[i].host));
396 }
397 
398 static int
399 dnssession_cmp(struct dnssession *s1, struct dnssession *s2)
400 {
401 	/*
402 	 * do not return u_int64_t's
403 	 */
404 	if (s1->id < s2->id)
405 		return (-1);
406 
407 	if (s1->id > s2->id)
408 		return (1);
409 
410 	return (0);
411 }
412 
413 SPLAY_GENERATE(dnstree, dnssession, nodes, dnssession_cmp);
414