1 /* $OpenBSD: dns.c,v 1.89 2019/09/18 11:26:30 eric 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/types.h> 22 #include <sys/socket.h> 23 #include <sys/tree.h> 24 #include <sys/queue.h> 25 #include <sys/uio.h> 26 27 #include <netinet/in.h> 28 #include <arpa/inet.h> 29 #include <arpa/nameser.h> 30 #include <netdb.h> 31 32 #include <asr.h> 33 #include <event.h> 34 #include <imsg.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <limits.h> 39 40 #include "smtpd.h" 41 #include "log.h" 42 #include "unpack_dns.h" 43 44 struct dns_lookup { 45 struct dns_session *session; 46 char *host; 47 int preference; 48 }; 49 50 struct dns_session { 51 struct mproc *p; 52 uint64_t reqid; 53 int type; 54 char name[HOST_NAME_MAX+1]; 55 size_t mxfound; 56 int error; 57 int refcount; 58 }; 59 60 static void dns_lookup_host(struct dns_session *, const char *, int); 61 static void dns_dispatch_host(struct asr_result *, void *); 62 static void dns_dispatch_mx(struct asr_result *, void *); 63 static void dns_dispatch_mx_preference(struct asr_result *, void *); 64 65 static int 66 domainname_is_addr(const char *s, struct sockaddr *sa, socklen_t *sl) 67 { 68 struct addrinfo hints, *res; 69 socklen_t sl2; 70 size_t l; 71 char buf[SMTPD_MAXDOMAINPARTSIZE]; 72 int i6, error; 73 74 if (*s != '[') 75 return (0); 76 77 i6 = (strncasecmp("[IPv6:", s, 6) == 0); 78 s += i6 ? 6 : 1; 79 80 l = strlcpy(buf, s, sizeof(buf)); 81 if (l >= sizeof(buf) || l == 0 || buf[l - 1] != ']') 82 return (0); 83 84 buf[l - 1] = '\0'; 85 memset(&hints, 0, sizeof(hints)); 86 hints.ai_flags = AI_NUMERICHOST; 87 hints.ai_socktype = SOCK_STREAM; 88 if (i6) 89 hints.ai_family = AF_INET6; 90 91 res = NULL; 92 if ((error = getaddrinfo(buf, NULL, &hints, &res))) { 93 log_warnx("getaddrinfo: %s", gai_strerror(error)); 94 } 95 96 if (!res) 97 return (0); 98 99 if (sa && sl) { 100 sl2 = *sl; 101 if (sl2 > res->ai_addrlen) 102 sl2 = res->ai_addrlen; 103 memmove(sa, res->ai_addr, sl2); 104 *sl = res->ai_addrlen; 105 } 106 107 freeaddrinfo(res); 108 return (1); 109 } 110 111 void 112 dns_imsg(struct mproc *p, struct imsg *imsg) 113 { 114 struct sockaddr_storage ss; 115 struct dns_session *s; 116 struct sockaddr *sa; 117 struct asr_query *as; 118 struct msg m; 119 const char *domain, *mx, *host; 120 socklen_t sl; 121 122 s = xcalloc(1, sizeof *s); 123 s->type = imsg->hdr.type; 124 s->p = p; 125 126 m_msg(&m, imsg); 127 m_get_id(&m, &s->reqid); 128 129 switch (s->type) { 130 131 case IMSG_MTA_DNS_HOST: 132 m_get_string(&m, &host); 133 m_end(&m); 134 dns_lookup_host(s, host, -1); 135 return; 136 137 case IMSG_MTA_DNS_MX: 138 m_get_string(&m, &domain); 139 m_end(&m); 140 (void)strlcpy(s->name, domain, sizeof(s->name)); 141 142 sa = (struct sockaddr *)&ss; 143 sl = sizeof(ss); 144 145 if (domainname_is_addr(domain, sa, &sl)) { 146 m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); 147 m_add_id(s->p, s->reqid); 148 m_add_string(s->p, sockaddr_to_text(sa)); 149 m_add_sockaddr(s->p, sa); 150 m_add_int(s->p, -1); 151 m_close(s->p); 152 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_OK); 156 m_close(s->p); 157 free(s); 158 return; 159 } 160 161 as = res_query_async(s->name, C_IN, T_MX, NULL); 162 if (as == NULL) { 163 log_warn("warn: res_query_async: %s", s->name); 164 m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); 165 m_add_id(s->p, s->reqid); 166 m_add_int(s->p, DNS_EINVAL); 167 m_close(s->p); 168 free(s); 169 return; 170 } 171 172 event_asr_run(as, dns_dispatch_mx, s); 173 return; 174 175 case IMSG_MTA_DNS_MX_PREFERENCE: 176 m_get_string(&m, &domain); 177 m_get_string(&m, &mx); 178 m_end(&m); 179 (void)strlcpy(s->name, mx, sizeof(s->name)); 180 181 as = res_query_async(domain, C_IN, T_MX, NULL); 182 if (as == NULL) { 183 m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); 184 m_add_id(s->p, s->reqid); 185 m_add_int(s->p, DNS_ENOTFOUND); 186 m_close(s->p); 187 free(s); 188 return; 189 } 190 191 event_asr_run(as, dns_dispatch_mx_preference, s); 192 return; 193 194 default: 195 log_warnx("warn: bad dns request %d", s->type); 196 fatal(NULL); 197 } 198 } 199 200 static void 201 dns_dispatch_host(struct asr_result *ar, void *arg) 202 { 203 struct dns_session *s; 204 struct dns_lookup *lookup = arg; 205 struct addrinfo *ai; 206 207 s = lookup->session; 208 209 for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { 210 s->mxfound++; 211 m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); 212 m_add_id(s->p, s->reqid); 213 m_add_string(s->p, lookup->host); 214 m_add_sockaddr(s->p, ai->ai_addr); 215 m_add_int(s->p, lookup->preference); 216 m_close(s->p); 217 } 218 free(lookup->host); 219 free(lookup); 220 if (ar->ar_addrinfo) 221 freeaddrinfo(ar->ar_addrinfo); 222 223 if (ar->ar_gai_errno) 224 s->error = ar->ar_gai_errno; 225 226 if (--s->refcount) 227 return; 228 229 m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); 230 m_add_id(s->p, s->reqid); 231 m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND); 232 m_close(s->p); 233 free(s); 234 } 235 236 static void 237 dns_dispatch_mx(struct asr_result *ar, void *arg) 238 { 239 struct dns_session *s = arg; 240 struct unpack pack; 241 struct dns_header h; 242 struct dns_query q; 243 struct dns_rr rr; 244 char buf[512]; 245 size_t found; 246 247 if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA && 248 ar->ar_h_errno != NOTIMP) { 249 250 m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); 251 m_add_id(s->p, s->reqid); 252 if (ar->ar_rcode == NXDOMAIN) 253 m_add_int(s->p, DNS_ENONAME); 254 else if (ar->ar_h_errno == NO_RECOVERY) 255 m_add_int(s->p, DNS_EINVAL); 256 else 257 m_add_int(s->p, DNS_RETRY); 258 m_close(s->p); 259 free(s); 260 free(ar->ar_data); 261 return; 262 } 263 264 unpack_init(&pack, ar->ar_data, ar->ar_datalen); 265 unpack_header(&pack, &h); 266 unpack_query(&pack, &q); 267 268 found = 0; 269 for (; h.ancount; h.ancount--) { 270 unpack_rr(&pack, &rr); 271 if (rr.rr_type != T_MX) 272 continue; 273 print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); 274 buf[strlen(buf) - 1] = '\0'; 275 dns_lookup_host(s, buf, rr.rr.mx.preference); 276 found++; 277 } 278 free(ar->ar_data); 279 280 /* fallback to host if no MX is found. */ 281 if (found == 0) 282 dns_lookup_host(s, s->name, 0); 283 } 284 285 static void 286 dns_dispatch_mx_preference(struct asr_result *ar, void *arg) 287 { 288 struct dns_session *s = arg; 289 struct unpack pack; 290 struct dns_header h; 291 struct dns_query q; 292 struct dns_rr rr; 293 char buf[512]; 294 int error; 295 296 if (ar->ar_h_errno) { 297 if (ar->ar_rcode == NXDOMAIN) 298 error = DNS_ENONAME; 299 else if (ar->ar_h_errno == NO_RECOVERY 300 || ar->ar_h_errno == NO_DATA) 301 error = DNS_EINVAL; 302 else 303 error = DNS_RETRY; 304 } 305 else { 306 error = DNS_ENOTFOUND; 307 unpack_init(&pack, ar->ar_data, ar->ar_datalen); 308 unpack_header(&pack, &h); 309 unpack_query(&pack, &q); 310 for (; h.ancount; h.ancount--) { 311 unpack_rr(&pack, &rr); 312 if (rr.rr_type != T_MX) 313 continue; 314 print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); 315 buf[strlen(buf) - 1] = '\0'; 316 if (!strcasecmp(s->name, buf)) { 317 error = DNS_OK; 318 break; 319 } 320 } 321 } 322 323 free(ar->ar_data); 324 325 m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); 326 m_add_id(s->p, s->reqid); 327 m_add_int(s->p, error); 328 if (error == DNS_OK) 329 m_add_int(s->p, rr.rr.mx.preference); 330 m_close(s->p); 331 free(s); 332 } 333 334 static void 335 dns_lookup_host(struct dns_session *s, const char *host, int preference) 336 { 337 struct dns_lookup *lookup; 338 struct addrinfo hints; 339 char hostcopy[HOST_NAME_MAX+1]; 340 char *p; 341 void *as; 342 343 lookup = xcalloc(1, sizeof *lookup); 344 lookup->preference = preference; 345 lookup->host = xstrdup(host); 346 lookup->session = s; 347 s->refcount++; 348 349 if (*host == '[') { 350 if (strncasecmp("[IPv6:", host, 6) == 0) 351 host += 6; 352 else 353 host += 1; 354 (void)strlcpy(hostcopy, host, sizeof hostcopy); 355 p = strchr(hostcopy, ']'); 356 if (p) 357 *p = 0; 358 host = hostcopy; 359 } 360 361 memset(&hints, 0, sizeof(hints)); 362 hints.ai_flags = AI_ADDRCONFIG; 363 hints.ai_family = PF_UNSPEC; 364 hints.ai_socktype = SOCK_STREAM; 365 as = getaddrinfo_async(host, NULL, &hints, NULL); 366 event_asr_run(as, dns_dispatch_host, lookup); 367 } 368