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