xref: /openbsd/usr.sbin/smtpd/dns.c (revision 28e3f7cb)
1 /*	$OpenBSD: dns.c,v 1.92 2023/11/16 10:23:21 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
domainname_is_addr(const char * s,struct sockaddr * sa,socklen_t * sl)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
dns_imsg(struct mproc * p,struct imsg * imsg)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
dns_dispatch_host(struct asr_result * ar,void * arg)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
dns_dispatch_mx(struct asr_result * ar,void * arg)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 		    !strcmp(buf, "localhost")) {
268 			nullmx = 1;
269 			continue;
270 		}
271 
272 		dns_lookup_host(s, buf, rr.rr.mx.preference);
273 		found++;
274 	}
275 	free(ar->ar_data);
276 
277 	if (nullmx && found == 0) {
278 		m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
279 		m_add_id(s->p, s->reqid);
280 		m_add_int(s->p, DNS_NULLMX);
281 		m_close(s->p);
282 		free(s);
283 		return;
284 	}
285 
286 	/* fallback to host if no MX is found. */
287 	if (found == 0)
288 		dns_lookup_host(s, s->name, 0);
289 }
290 
291 static void
dns_dispatch_mx_preference(struct asr_result * ar,void * arg)292 dns_dispatch_mx_preference(struct asr_result *ar, void *arg)
293 {
294 	struct dns_session	*s = arg;
295 	struct unpack		 pack;
296 	struct dns_header	 h;
297 	struct dns_query	 q;
298 	struct dns_rr		 rr;
299 	char			 buf[512];
300 	int			 error;
301 
302 	if (ar->ar_h_errno) {
303 		if (ar->ar_rcode == NXDOMAIN)
304 			error = DNS_ENONAME;
305 		else if (ar->ar_h_errno == NO_RECOVERY
306 		    || ar->ar_h_errno == NO_DATA)
307 			error = DNS_EINVAL;
308 		else
309 			error = DNS_RETRY;
310 	}
311 	else {
312 		error = DNS_ENOTFOUND;
313 		unpack_init(&pack, ar->ar_data, ar->ar_datalen);
314 		unpack_header(&pack, &h);
315 		unpack_query(&pack, &q);
316 		for (; h.ancount; h.ancount--) {
317 			unpack_rr(&pack, &rr);
318 			if (rr.rr_type != T_MX)
319 				continue;
320 			print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
321 			buf[strlen(buf) - 1] = '\0';
322 			if (!strcasecmp(s->name, buf)) {
323 				error = DNS_OK;
324 				break;
325 			}
326 		}
327 	}
328 
329 	free(ar->ar_data);
330 
331 	m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1);
332 	m_add_id(s->p, s->reqid);
333 	m_add_int(s->p, error);
334 	if (error == DNS_OK)
335 		m_add_int(s->p, rr.rr.mx.preference);
336 	m_close(s->p);
337 	free(s);
338 }
339 
340 static void
dns_lookup_host(struct dns_session * s,const char * host,int preference)341 dns_lookup_host(struct dns_session *s, const char *host, int preference)
342 {
343 	struct dns_lookup	*lookup;
344 	struct addrinfo		 hints;
345 	char			 hostcopy[HOST_NAME_MAX+1];
346 	char			*p;
347 	void			*as;
348 
349 	lookup = xcalloc(1, sizeof *lookup);
350 	lookup->preference = preference;
351 	lookup->host = xstrdup(host);
352 	lookup->session = s;
353 	s->refcount++;
354 
355 	if (*host == '[') {
356 		if (strncasecmp("[IPv6:", host, 6) == 0)
357 			host += 6;
358 		else
359 			host += 1;
360 		(void)strlcpy(hostcopy, host, sizeof hostcopy);
361 		p = strchr(hostcopy, ']');
362 		if (p)
363 			*p = 0;
364 		host = hostcopy;
365 	}
366 
367 	memset(&hints, 0, sizeof(hints));
368 	hints.ai_flags = AI_ADDRCONFIG;
369 	hints.ai_family = PF_UNSPEC;
370 	hints.ai_socktype = SOCK_STREAM;
371 	as = getaddrinfo_async(host, NULL, &hints, NULL);
372 	event_asr_run(as, dns_dispatch_host, lookup);
373 }
374