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