1 /* $OpenBSD: dns.c,v 1.49 2012/07/09 12:16:24 eric Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> 5 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 6 * Copyright (c) 2011 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 31 #include <event.h> 32 #include <imsg.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 37 #include "asr.h" 38 #include "asr_private.h" 39 #include "smtpd.h" 40 #include "log.h" 41 42 43 struct mx { 44 char host[MAXHOSTNAMELEN]; 45 int prio; 46 }; 47 48 struct dnssession { 49 SPLAY_ENTRY(dnssession) nodes; 50 u_int64_t id; 51 struct dns query; 52 struct event ev; 53 struct async *as; 54 struct mx mxarray[MAX_MX_COUNT]; 55 size_t mxarraysz; 56 size_t mxcurrent; 57 size_t mxfound; 58 }; 59 60 static int dnssession_cmp(struct dnssession *, struct dnssession *); 61 62 SPLAY_HEAD(dnstree, dnssession) dns_sessions = SPLAY_INITIALIZER(&dns_sessions); 63 64 SPLAY_PROTOTYPE(dnstree, dnssession, nodes, dnssession_cmp); 65 66 67 static struct dnssession *dnssession_init(struct dns *); 68 static void dnssession_destroy(struct dnssession *); 69 static void dnssession_mx_insert(struct dnssession *, const char *, int); 70 static void dns_asr_event_set(struct dnssession *, struct async_res *); 71 static void dns_asr_handler(int, short, void *); 72 static int dns_asr_error(int); 73 static void dns_asr_dispatch_host(struct dnssession *); 74 static void dns_asr_dispatch_mx(struct dnssession *); 75 static void dns_asr_dispatch_cname(struct dnssession *); 76 static void dns_reply(struct dns *, int); 77 78 #define print_dname(a,b,c) asr_strdname(a, b, c) 79 80 /* 81 * User interface. 82 */ 83 84 void 85 dns_query_host(char *host, int port, u_int64_t id) 86 { 87 struct dns query; 88 89 bzero(&query, sizeof(query)); 90 strlcpy(query.host, host, sizeof(query.host)); 91 query.port = port; 92 query.id = id; 93 94 imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_HOST, 0, 0, -1, 95 &query, sizeof(query)); 96 } 97 98 void 99 dns_query_mx(char *host, int port, u_int64_t id) 100 { 101 struct dns query; 102 103 bzero(&query, sizeof(query)); 104 strlcpy(query.host, host, sizeof(query.host)); 105 query.port = port; 106 query.id = id; 107 108 imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_MX, 0, 0, -1, &query, 109 sizeof(query)); 110 } 111 112 void 113 dns_query_ptr(struct sockaddr_storage *ss, u_int64_t id) 114 { 115 struct dns query; 116 117 bzero(&query, sizeof(query)); 118 query.ss = *ss; 119 query.id = id; 120 121 imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_DNS_PTR, 0, 0, -1, &query, 122 sizeof(query)); 123 } 124 125 /* LKA interface */ 126 void 127 dns_async(struct imsgev *asker, int type, struct dns *query) 128 { 129 struct dnssession *s; 130 131 query->type = type; 132 query->asker = asker; 133 s = dnssession_init(query); 134 135 switch (type) { 136 case IMSG_DNS_HOST: 137 log_debug("dns: lookup host \"%s\"", query->host); 138 if (sockaddr_from_str((struct sockaddr*)&query->ss, PF_UNSPEC, 139 query->host) == 0) { 140 log_debug("dns: \"%s\" is an IP address", query->host); 141 query->error = DNS_OK; 142 dns_reply(query, IMSG_DNS_HOST); 143 dns_reply(query, IMSG_DNS_HOST_END); 144 dnssession_destroy(s); 145 return; 146 } 147 dnssession_mx_insert(s, query->host, 0); 148 stat_increment(STATS_LKA_SESSION_HOST); 149 log_debug("dns: ready?"); 150 dns_asr_dispatch_host(s); 151 return; 152 case IMSG_DNS_PTR: 153 s->as = getnameinfo_async((struct sockaddr*)&query->ss, 154 query->ss.ss_len, 155 s->query.host, sizeof(s->query.host), NULL, 0, 0, NULL); 156 stat_increment(STATS_LKA_SESSION_CNAME); 157 if (s->as == NULL) { 158 log_debug("dns_async: asr_query_cname error"); 159 break; 160 } 161 dns_asr_dispatch_cname(s); 162 return; 163 case IMSG_DNS_MX: 164 log_debug("dns: lookup mx \"%s\"", query->host); 165 s->as = res_query_async(query->host, C_IN, T_MX, NULL, 0, NULL); 166 stat_increment(STATS_LKA_SESSION_MX); 167 if (s->as == NULL) { 168 log_debug("dns_async: asr_query_dns error"); 169 break; 170 } 171 dns_asr_dispatch_mx(s); 172 return; 173 default: 174 log_debug("dns_async: bad request"); 175 break; 176 } 177 178 stat_increment(STATS_LKA_FAILURE); 179 dnssession_destroy(s); 180 } 181 182 static void 183 dns_reply(struct dns *query, int type) 184 { 185 imsg_compose_event(query->asker, type, 0, 0, -1, query, sizeof(*query)); 186 } 187 188 static void 189 dns_asr_event_set(struct dnssession *s, struct async_res *ar) 190 { 191 struct timeval tv = { 0, 0 }; 192 193 tv.tv_usec = ar->ar_timeout * 1000; 194 event_set(&s->ev, ar->ar_fd, 195 ar->ar_cond == ASYNC_READ ? EV_READ : EV_WRITE, dns_asr_handler, s); 196 event_add(&s->ev, &tv); 197 } 198 199 static void 200 dns_asr_handler(int fd, short event, void *arg) 201 { 202 struct dnssession *s = arg; 203 204 switch(s->query.type) { 205 case IMSG_DNS_HOST: 206 dns_asr_dispatch_host(s); 207 break; 208 case IMSG_DNS_PTR: 209 dns_asr_dispatch_cname(s); 210 break; 211 case IMSG_DNS_MX: 212 dns_asr_dispatch_mx(s); 213 break; 214 default: 215 fatalx("bad query type"); 216 } 217 } 218 219 static int 220 dns_asr_error(int ar_err) 221 { 222 switch (ar_err) { 223 case 0: 224 return DNS_OK; 225 case NO_DATA: 226 case NO_RECOVERY: 227 stat_increment(STATS_LKA_FAILURE); 228 return DNS_EINVAL; 229 default: 230 return DNS_RETRY; 231 } 232 } 233 234 static void 235 dns_asr_dispatch_mx(struct dnssession *s) 236 { 237 struct dns *query = &s->query; 238 struct async_res ar; 239 struct packed pack; 240 struct header h; 241 struct query q; 242 struct rr rr; 243 char buf[512]; 244 245 if (async_run(s->as, &ar) == ASYNC_COND) { 246 dns_asr_event_set(s, &ar); 247 return; 248 } 249 250 if (ar.ar_errno || ar.ar_h_errno || ar.ar_rcode == NXDOMAIN) { 251 query->error = ar.ar_rcode == NXDOMAIN ? \ 252 DNS_ENONAME : dns_asr_error(ar.ar_h_errno); 253 dns_reply(query, IMSG_DNS_HOST_END); 254 dnssession_destroy(s); 255 free(ar.ar_data); 256 return; 257 } 258 259 packed_init(&pack, ar.ar_data, ar.ar_datalen); 260 unpack_header(&pack, &h); 261 unpack_query(&pack, &q); 262 263 if (h.ancount == 0) 264 /* fallback to host if no MX is found. */ 265 dnssession_mx_insert(s, query->host, 0); 266 267 for (; h.ancount; h.ancount--) { 268 unpack_rr(&pack, &rr); 269 print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); 270 buf[strlen(buf) - 1] = '\0'; 271 dnssession_mx_insert(s, buf, rr.rr.mx.preference); 272 } 273 274 free(ar.ar_data); 275 276 /* Now we have a sorted list of MX to resolve. Simply "turn" this 277 * MX session into a regular host session. 278 */ 279 s->as = NULL; 280 s->query.type = IMSG_DNS_HOST; 281 dns_asr_dispatch_host(s); 282 } 283 284 static void 285 dns_asr_dispatch_host(struct dnssession *s) 286 { 287 struct dns *query = &s->query; 288 struct mx *mx; 289 struct async_res ar; 290 struct addrinfo hints, *ai; 291 292 /* default to notfound, override with retry or ok later */ 293 if (s->mxcurrent == 0) 294 query->error = DNS_ENOTFOUND; 295 296 next: 297 /* query all listed hosts in turn */ 298 while (s->as == NULL) { 299 if (s->mxcurrent == s->mxarraysz) { 300 if (s->mxfound) 301 query->error = DNS_OK; 302 dns_reply(query, IMSG_DNS_HOST_END); 303 dnssession_destroy(s); 304 return; 305 } 306 307 mx = s->mxarray + s->mxcurrent++; 308 memset(&hints, 0, sizeof(hints)); 309 hints.ai_family = PF_UNSPEC; 310 hints.ai_socktype = SOCK_STREAM; 311 s->as = getaddrinfo_async(mx->host, NULL, &hints, NULL); 312 } 313 314 if (async_run(s->as, &ar) == ASYNC_COND) { 315 dns_asr_event_set(s, &ar); 316 return; 317 } 318 319 if (ar.ar_gai_errno == 0) { 320 for (ai = ar.ar_addrinfo; ai; ai = ai->ai_next) { 321 memcpy(&query->ss, ai->ai_addr, ai->ai_addrlen); 322 dns_reply(query, IMSG_DNS_HOST); 323 s->mxfound++; 324 } 325 freeaddrinfo(ar.ar_addrinfo); 326 } 327 328 s->as = NULL; 329 goto next; 330 } 331 332 static void 333 dns_asr_dispatch_cname(struct dnssession *s) 334 { 335 struct dns *query = &s->query; 336 struct async_res ar; 337 338 if (async_run(s->as, &ar) == ASYNC_COND) { 339 dns_asr_event_set(s, &ar); 340 return; 341 } 342 343 /* the error code could be more precise, but we don't currently care */ 344 query->error = ar.ar_gai_errno ? DNS_ENOTFOUND : DNS_OK; 345 dns_reply(query, IMSG_DNS_PTR); 346 dnssession_destroy(s); 347 } 348 349 static struct dnssession * 350 dnssession_init(struct dns *query) 351 { 352 struct dnssession *s; 353 354 s = calloc(1, sizeof(struct dnssession)); 355 if (s == NULL) 356 fatal("dnssession_init: calloc"); 357 358 stat_increment(STATS_LKA_SESSION); 359 360 s->id = query->id; 361 s->query = *query; 362 SPLAY_INSERT(dnstree, &dns_sessions, s); 363 return (s); 364 } 365 366 static void 367 dnssession_destroy(struct dnssession *s) 368 { 369 stat_decrement(STATS_LKA_SESSION); 370 SPLAY_REMOVE(dnstree, &dns_sessions, s); 371 event_del(&s->ev); 372 free(s); 373 } 374 375 static void 376 dnssession_mx_insert(struct dnssession *s, const char *host, int prio) 377 { 378 size_t i, j; 379 380 for (i = 0; i < s->mxarraysz; i++) 381 if (prio < s->mxarray[i].prio) 382 break; 383 384 if (i == MAX_MX_COUNT) 385 return; 386 387 if (s->mxarraysz < MAX_MX_COUNT) 388 s->mxarraysz++; 389 390 for (j = s->mxarraysz - 1; j > i; j--) 391 s->mxarray[j] = s->mxarray[j - 1]; 392 393 s->mxarray[i].prio = prio; 394 strlcpy(s->mxarray[i].host, host, 395 sizeof (s->mxarray[i].host)); 396 } 397 398 static int 399 dnssession_cmp(struct dnssession *s1, struct dnssession *s2) 400 { 401 /* 402 * do not return u_int64_t's 403 */ 404 if (s1->id < s2->id) 405 return (-1); 406 407 if (s1->id > s2->id) 408 return (1); 409 410 return (0); 411 } 412 413 SPLAY_GENERATE(dnstree, dnssession, nodes, dnssession_cmp); 414