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