xref: /openbsd/usr.sbin/smtpd/dns.c (revision 09467b48)
1 /*	$OpenBSD: dns.c,v 1.89 2019/09/18 11:26:30 eric Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
5  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6  * Copyright (c) 2011-2014 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 #include <netdb.h>
31 
32 #include <asr.h>
33 #include <event.h>
34 #include <imsg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <limits.h>
39 
40 #include "smtpd.h"
41 #include "log.h"
42 #include "unpack_dns.h"
43 
44 struct dns_lookup {
45 	struct dns_session	*session;
46 	char			*host;
47 	int			 preference;
48 };
49 
50 struct dns_session {
51 	struct mproc		*p;
52 	uint64_t		 reqid;
53 	int			 type;
54 	char			 name[HOST_NAME_MAX+1];
55 	size_t			 mxfound;
56 	int			 error;
57 	int			 refcount;
58 };
59 
60 static void dns_lookup_host(struct dns_session *, const char *, int);
61 static void dns_dispatch_host(struct asr_result *, void *);
62 static void dns_dispatch_mx(struct asr_result *, void *);
63 static void dns_dispatch_mx_preference(struct asr_result *, void *);
64 
65 static int
66 domainname_is_addr(const char *s, struct sockaddr *sa, socklen_t *sl)
67 {
68 	struct addrinfo	hints, *res;
69 	socklen_t	sl2;
70 	size_t		l;
71 	char		buf[SMTPD_MAXDOMAINPARTSIZE];
72 	int		i6, error;
73 
74 	if (*s != '[')
75 		return (0);
76 
77 	i6 = (strncasecmp("[IPv6:", s, 6) == 0);
78 	s += i6 ? 6 : 1;
79 
80 	l = strlcpy(buf, s, sizeof(buf));
81 	if (l >= sizeof(buf) || l == 0 || buf[l - 1] != ']')
82 		return (0);
83 
84 	buf[l - 1] = '\0';
85 	memset(&hints, 0, sizeof(hints));
86 	hints.ai_flags = AI_NUMERICHOST;
87 	hints.ai_socktype = SOCK_STREAM;
88 	if (i6)
89 		hints.ai_family = AF_INET6;
90 
91 	res = NULL;
92 	if ((error = getaddrinfo(buf, NULL, &hints, &res))) {
93 		log_warnx("getaddrinfo: %s", gai_strerror(error));
94 	}
95 
96 	if (!res)
97 		return (0);
98 
99 	if (sa && sl) {
100 		sl2 = *sl;
101 		if (sl2 > res->ai_addrlen)
102 			sl2 = res->ai_addrlen;
103 		memmove(sa, res->ai_addr, sl2);
104 		*sl = res->ai_addrlen;
105 	}
106 
107 	freeaddrinfo(res);
108 	return (1);
109 }
110 
111 void
112 dns_imsg(struct mproc *p, struct imsg *imsg)
113 {
114 	struct sockaddr_storage	 ss;
115 	struct dns_session	*s;
116 	struct sockaddr		*sa;
117 	struct asr_query	*as;
118 	struct msg		 m;
119 	const char		*domain, *mx, *host;
120 	socklen_t		 sl;
121 
122 	s = xcalloc(1, sizeof *s);
123 	s->type = imsg->hdr.type;
124 	s->p = p;
125 
126 	m_msg(&m, imsg);
127 	m_get_id(&m, &s->reqid);
128 
129 	switch (s->type) {
130 
131 	case IMSG_MTA_DNS_HOST:
132 		m_get_string(&m, &host);
133 		m_end(&m);
134 		dns_lookup_host(s, host, -1);
135 		return;
136 
137 	case IMSG_MTA_DNS_MX:
138 		m_get_string(&m, &domain);
139 		m_end(&m);
140 		(void)strlcpy(s->name, domain, sizeof(s->name));
141 
142 		sa = (struct sockaddr *)&ss;
143 		sl = sizeof(ss);
144 
145 		if (domainname_is_addr(domain, sa, &sl)) {
146 			m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1);
147 			m_add_id(s->p, s->reqid);
148 			m_add_string(s->p, sockaddr_to_text(sa));
149 			m_add_sockaddr(s->p, sa);
150 			m_add_int(s->p, -1);
151 			m_close(s->p);
152 
153 			m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
154 			m_add_id(s->p, s->reqid);
155 			m_add_int(s->p, DNS_OK);
156 			m_close(s->p);
157 			free(s);
158 			return;
159 		}
160 
161 		as = res_query_async(s->name, C_IN, T_MX, NULL);
162 		if (as == NULL) {
163 			log_warn("warn: res_query_async: %s", s->name);
164 			m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
165 			m_add_id(s->p, s->reqid);
166 			m_add_int(s->p, DNS_EINVAL);
167 			m_close(s->p);
168 			free(s);
169 			return;
170 		}
171 
172 		event_asr_run(as, dns_dispatch_mx, s);
173 		return;
174 
175 	case IMSG_MTA_DNS_MX_PREFERENCE:
176 		m_get_string(&m, &domain);
177 		m_get_string(&m, &mx);
178 		m_end(&m);
179 		(void)strlcpy(s->name, mx, sizeof(s->name));
180 
181 		as = res_query_async(domain, C_IN, T_MX, NULL);
182 		if (as == NULL) {
183 			m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1);
184 			m_add_id(s->p, s->reqid);
185 			m_add_int(s->p, DNS_ENOTFOUND);
186 			m_close(s->p);
187 			free(s);
188 			return;
189 		}
190 
191 		event_asr_run(as, dns_dispatch_mx_preference, s);
192 		return;
193 
194 	default:
195 		log_warnx("warn: bad dns request %d", s->type);
196 		fatal(NULL);
197 	}
198 }
199 
200 static void
201 dns_dispatch_host(struct asr_result *ar, void *arg)
202 {
203 	struct dns_session	*s;
204 	struct dns_lookup	*lookup = arg;
205 	struct addrinfo		*ai;
206 
207 	s = lookup->session;
208 
209 	for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) {
210 		s->mxfound++;
211 		m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1);
212 		m_add_id(s->p, s->reqid);
213 		m_add_string(s->p, lookup->host);
214 		m_add_sockaddr(s->p, ai->ai_addr);
215 		m_add_int(s->p, lookup->preference);
216 		m_close(s->p);
217 	}
218 	free(lookup->host);
219 	free(lookup);
220 	if (ar->ar_addrinfo)
221 		freeaddrinfo(ar->ar_addrinfo);
222 
223 	if (ar->ar_gai_errno)
224 		s->error = ar->ar_gai_errno;
225 
226 	if (--s->refcount)
227 		return;
228 
229 	m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
230 	m_add_id(s->p, s->reqid);
231 	m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND);
232 	m_close(s->p);
233 	free(s);
234 }
235 
236 static void
237 dns_dispatch_mx(struct asr_result *ar, void *arg)
238 {
239 	struct dns_session	*s = arg;
240 	struct unpack		 pack;
241 	struct dns_header	 h;
242 	struct dns_query	 q;
243 	struct dns_rr		 rr;
244 	char			 buf[512];
245 	size_t			 found;
246 
247 	if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA &&
248 	    ar->ar_h_errno != NOTIMP) {
249 
250 		m_create(s->p,  IMSG_MTA_DNS_HOST_END, 0, 0, -1);
251 		m_add_id(s->p, s->reqid);
252 		if (ar->ar_rcode == NXDOMAIN)
253 			m_add_int(s->p, DNS_ENONAME);
254 		else if (ar->ar_h_errno == NO_RECOVERY)
255 			m_add_int(s->p, DNS_EINVAL);
256 		else
257 			m_add_int(s->p, DNS_RETRY);
258 		m_close(s->p);
259 		free(s);
260 		free(ar->ar_data);
261 		return;
262 	}
263 
264 	unpack_init(&pack, ar->ar_data, ar->ar_datalen);
265 	unpack_header(&pack, &h);
266 	unpack_query(&pack, &q);
267 
268 	found = 0;
269 	for (; h.ancount; h.ancount--) {
270 		unpack_rr(&pack, &rr);
271 		if (rr.rr_type != T_MX)
272 			continue;
273 		print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
274 		buf[strlen(buf) - 1] = '\0';
275 		dns_lookup_host(s, buf, rr.rr.mx.preference);
276 		found++;
277 	}
278 	free(ar->ar_data);
279 
280 	/* fallback to host if no MX is found. */
281 	if (found == 0)
282 		dns_lookup_host(s, s->name, 0);
283 }
284 
285 static void
286 dns_dispatch_mx_preference(struct asr_result *ar, void *arg)
287 {
288 	struct dns_session	*s = arg;
289 	struct unpack		 pack;
290 	struct dns_header	 h;
291 	struct dns_query	 q;
292 	struct dns_rr		 rr;
293 	char			 buf[512];
294 	int			 error;
295 
296 	if (ar->ar_h_errno) {
297 		if (ar->ar_rcode == NXDOMAIN)
298 			error = DNS_ENONAME;
299 		else if (ar->ar_h_errno == NO_RECOVERY
300 		    || ar->ar_h_errno == NO_DATA)
301 			error = DNS_EINVAL;
302 		else
303 			error = DNS_RETRY;
304 	}
305 	else {
306 		error = DNS_ENOTFOUND;
307 		unpack_init(&pack, ar->ar_data, ar->ar_datalen);
308 		unpack_header(&pack, &h);
309 		unpack_query(&pack, &q);
310 		for (; h.ancount; h.ancount--) {
311 			unpack_rr(&pack, &rr);
312 			if (rr.rr_type != T_MX)
313 				continue;
314 			print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
315 			buf[strlen(buf) - 1] = '\0';
316 			if (!strcasecmp(s->name, buf)) {
317 				error = DNS_OK;
318 				break;
319 			}
320 		}
321 	}
322 
323 	free(ar->ar_data);
324 
325 	m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1);
326 	m_add_id(s->p, s->reqid);
327 	m_add_int(s->p, error);
328 	if (error == DNS_OK)
329 		m_add_int(s->p, rr.rr.mx.preference);
330 	m_close(s->p);
331 	free(s);
332 }
333 
334 static void
335 dns_lookup_host(struct dns_session *s, const char *host, int preference)
336 {
337 	struct dns_lookup	*lookup;
338 	struct addrinfo		 hints;
339 	char			 hostcopy[HOST_NAME_MAX+1];
340 	char			*p;
341 	void			*as;
342 
343 	lookup = xcalloc(1, sizeof *lookup);
344 	lookup->preference = preference;
345 	lookup->host = xstrdup(host);
346 	lookup->session = s;
347 	s->refcount++;
348 
349 	if (*host == '[') {
350 		if (strncasecmp("[IPv6:", host, 6) == 0)
351 			host += 6;
352 		else
353 			host += 1;
354 		(void)strlcpy(hostcopy, host, sizeof hostcopy);
355 		p = strchr(hostcopy, ']');
356 		if (p)
357 			*p = 0;
358 		host = hostcopy;
359 	}
360 
361 	memset(&hints, 0, sizeof(hints));
362 	hints.ai_flags = AI_ADDRCONFIG;
363 	hints.ai_family = PF_UNSPEC;
364 	hints.ai_socktype = SOCK_STREAM;
365 	as = getaddrinfo_async(host, NULL, &hints, NULL);
366 	event_asr_run(as, dns_dispatch_host, lookup);
367 }
368