diff options
Diffstat (limited to 'smtpd/resolver.c')
-rw-r--r-- | smtpd/resolver.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/smtpd/resolver.c b/smtpd/resolver.c new file mode 100644 index 00000000..f0f0f8ea --- /dev/null +++ b/smtpd/resolver.c @@ -0,0 +1,462 @@ +/* $OpenBSD: resolver.c,v 1.5 2019/06/13 11:45:35 eric Exp $ */ + +/* + * Copyright (c) 2017-2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <netinet/in.h> + +#include <asr.h> +#include <ctype.h> +#include <errno.h> +#include <imsg.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +#define p_resolver p_lka + +struct request { + SPLAY_ENTRY(request) entry; + uint32_t id; + void (*cb_ai)(void *, int, struct addrinfo *); + void (*cb_ni)(void *, int, const char *, const char *); + void (*cb_res)(void *, int, int, int, const void *, int); + void *arg; + struct addrinfo *ai; +}; + +struct session { + uint32_t reqid; + struct mproc *proc; + char *host; + char *serv; +}; + +SPLAY_HEAD(reqtree, request); + +static void resolver_init(void); +static void resolver_getaddrinfo_cb(struct asr_result *, void *); +static void resolver_getnameinfo_cb(struct asr_result *, void *); +static void resolver_res_query_cb(struct asr_result *, void *); + +static int request_cmp(struct request *, struct request *); +SPLAY_PROTOTYPE(reqtree, request, entry, request_cmp); + +/* musl work-around */ +void portable_freeaddrinfo(struct addrinfo *); + +static struct reqtree reqs; + +void +resolver_getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, void (*cb)(void *, int, struct addrinfo *), + void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, EAI_MEMORY, NULL); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_ai = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_GETADDRINFO, req->id, 0, -1); + m_add_int(p_resolver, hints ? hints->ai_flags : 0); + m_add_int(p_resolver, hints ? hints->ai_family : 0); + m_add_int(p_resolver, hints ? hints->ai_socktype : 0); + m_add_int(p_resolver, hints ? hints->ai_protocol : 0); + m_add_string(p_resolver, hostname); + m_add_string(p_resolver, servname); + m_close(p_resolver); +} + +void +resolver_getnameinfo(const struct sockaddr *sa, int flags, + void(*cb)(void *, int, const char *, const char *), void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, EAI_MEMORY, NULL, NULL); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_ni = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_GETNAMEINFO, req->id, 0, -1); + m_add_sockaddr(p_resolver, sa); + m_add_int(p_resolver, flags); + m_close(p_resolver); +} + +void +resolver_res_query(const char *dname, int class, int type, + void (*cb)(void *, int, int, int, const void *, int), void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, NETDB_INTERNAL, 0, 0, NULL, 0); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_res = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_RES_QUERY, req->id, 0, -1); + m_add_string(p_resolver, dname); + m_add_int(p_resolver, class); + m_add_int(p_resolver, type); + m_close(p_resolver); +} + +void +resolver_dispatch_request(struct mproc *proc, struct imsg *imsg) +{ + const char *hostname, *servname, *dname; + struct session *s; + struct asr_query *q; + struct addrinfo hints; + struct sockaddr_storage ss; + struct sockaddr *sa; + struct msg m; + uint32_t reqid; + int class, type, flags, save_errno; + + reqid = imsg->hdr.peerid; + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + servname = NULL; + memset(&hints, 0 , sizeof(hints)); + m_get_int(&m, &hints.ai_flags); + m_get_int(&m, &hints.ai_family); + m_get_int(&m, &hints.ai_socktype); + m_get_int(&m, &hints.ai_protocol); + m_get_string(&m, &hostname); + m_get_string(&m, &servname); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (q = getaddrinfo_async(hostname, servname, &hints, NULL)) && + (event_asr_run(q, resolver_getaddrinfo_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) + free(s); + + m_create(proc, IMSG_GETADDRINFO_END, reqid, 0, -1); + m_add_int(proc, EAI_SYSTEM); + m_add_int(proc, save_errno); + m_close(proc); + break; + + case IMSG_GETNAMEINFO: + sa = (struct sockaddr*)&ss; + m_get_sockaddr(&m, sa); + m_get_int(&m, &flags); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (s->host = malloc(NI_MAXHOST)) && + (s->serv = malloc(NI_MAXSERV)) && + (q = getnameinfo_async(sa, SA_LEN(sa), s->host, NI_MAXHOST, + s->serv, NI_MAXSERV, flags, NULL)) && + (event_asr_run(q, resolver_getnameinfo_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) { + free(s->host); + free(s->serv); + free(s); + } + + m_create(proc, IMSG_GETNAMEINFO, reqid, 0, -1); + m_add_int(proc, EAI_SYSTEM); + m_add_int(proc, save_errno); + m_add_string(proc, NULL); + m_add_string(proc, NULL); + m_close(proc); + break; + + case IMSG_RES_QUERY: + m_get_string(&m, &dname); + m_get_int(&m, &class); + m_get_int(&m, &type); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (q = res_query_async(dname, class, type, NULL)) && + (event_asr_run(q, resolver_res_query_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) + free(s); + + m_create(proc, IMSG_RES_QUERY, reqid, 0, -1); + m_add_int(proc, NETDB_INTERNAL); + m_add_int(proc, save_errno); + m_add_int(proc, 0); + m_add_int(proc, 0); + m_add_data(proc, NULL, 0); + m_close(proc); + break; + + default: + fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); + } +} + +void +resolver_dispatch_result(struct mproc *proc, struct imsg *imsg) +{ + struct request key, *req; + struct sockaddr_storage ss; + struct addrinfo *ai; + struct msg m; + const char *cname, *host, *serv; + const void *data; + size_t datalen; + int gai_errno, herrno, rcode, count; + + key.id = imsg->hdr.peerid; + req = SPLAY_FIND(reqtree, &reqs, &key); + if (req == NULL) + fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); + + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + ai = calloc(1, sizeof(*ai)); + if (ai == NULL) { + log_warn("%s: calloc", __func__); + break; + } + m_get_int(&m, &ai->ai_flags); + m_get_int(&m, &ai->ai_family); + m_get_int(&m, &ai->ai_socktype); + m_get_int(&m, &ai->ai_protocol); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_get_string(&m, &cname); + m_end(&m); + + ai->ai_addr = malloc(SS_LEN(&ss)); + if (ai->ai_addr == NULL) { + log_warn("%s: malloc", __func__); + free(ai); + break; + } + + memmove(ai->ai_addr, &ss, SS_LEN(&ss)); + + if (cname) { + ai->ai_canonname = strdup(cname); + if (ai->ai_canonname == NULL) { + log_warn("%s: strdup", __func__); + free(ai->ai_addr); + free(ai); + break; + } + } + + ai->ai_next = req->ai; + req->ai = ai; + break; + + case IMSG_GETADDRINFO_END: + m_get_int(&m, &gai_errno); + m_get_int(&m, &errno); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_ai(req->arg, gai_errno, req->ai); + free(req); + break; + + case IMSG_GETNAMEINFO: + m_get_int(&m, &gai_errno); + m_get_int(&m, &errno); + m_get_string(&m, &host); + m_get_string(&m, &serv); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_ni(req->arg, gai_errno, host, serv); + free(req); + break; + + case IMSG_RES_QUERY: + m_get_int(&m, &herrno); + m_get_int(&m, &errno); + m_get_int(&m, &rcode); + m_get_int(&m, &count); + m_get_data(&m, &data, &datalen); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_res(req->arg, herrno, rcode, count, data, datalen); + free(req); + break; + } +} + +static void +resolver_init(void) +{ + static int init = 0; + + if (init == 0) { + SPLAY_INIT(&reqs); + init = 1; + } +} + +static void +resolver_getaddrinfo_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + struct addrinfo *ai; + + for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { + m_create(s->proc, IMSG_GETADDRINFO, s->reqid, 0, -1); + m_add_int(s->proc, ai->ai_flags); + m_add_int(s->proc, ai->ai_family); + m_add_int(s->proc, ai->ai_socktype); + m_add_int(s->proc, ai->ai_protocol); + m_add_sockaddr(s->proc, ai->ai_addr); + m_add_string(s->proc, ai->ai_canonname); + m_close(s->proc); + } + + m_create(s->proc, IMSG_GETADDRINFO_END, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_gai_errno); + m_add_int(s->proc, ar->ar_errno); + m_close(s->proc); + + if (ar->ar_addrinfo) + portable_freeaddrinfo(ar->ar_addrinfo); + free(s); +} + +static void +resolver_getnameinfo_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + + m_create(s->proc, IMSG_GETNAMEINFO, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_gai_errno); + m_add_int(s->proc, ar->ar_errno); + m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->host); + m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->serv); + m_close(s->proc); + + free(s->host); + free(s->serv); + free(s); +} + +static void +resolver_res_query_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + + m_create(s->proc, IMSG_RES_QUERY, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_h_errno); + m_add_int(s->proc, ar->ar_errno); + m_add_int(s->proc, ar->ar_rcode); + m_add_int(s->proc, ar->ar_count); + m_add_data(s->proc, ar->ar_data, ar->ar_datalen); + m_close(s->proc); + + free(ar->ar_data); + free(s); +} + +static int +request_cmp(struct request *a, struct request *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + return 0; +} + +SPLAY_GENERATE(reqtree, request, entry, request_cmp); |