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