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
33struct dns_lookup {
34	struct dns_session	*session;
35	char			*host;
36	int			 preference;
37};
38
39struct 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
49static void dns_lookup_host(struct dns_session *, const char *, int);
50static void dns_dispatch_host(struct asr_result *, void *);
51static void dns_dispatch_mx(struct asr_result *, void *);
52static void dns_dispatch_mx_preference(struct asr_result *, void *);
53
54static int
55domainname_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
100void
101dns_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
189static void
190dns_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
225static void
226dns_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
291static void
292dns_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
340static void
341dns_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