From 712cb487cb72d8b407dbbd60b23be1995d30b70e Mon Sep 17 00:00:00 2001 From: Simon Schubert Date: Sun, 20 Sep 2009 20:58:37 +0200 Subject: [PATCH] dma: perform MX lookups --- libexec/dma/Makefile | 2 +- libexec/dma/TODO | 1 - libexec/dma/dma.h | 20 +++- libexec/dma/dns.c | 273 +++++++++++++++++++++++++++++++++++++++++++ libexec/dma/net.c | 190 ++++++++++++++++-------------- 5 files changed, 393 insertions(+), 93 deletions(-) create mode 100644 libexec/dma/dns.c diff --git a/libexec/dma/Makefile b/libexec/dma/Makefile index 42e0044850..6e99f6dda1 100644 --- a/libexec/dma/Makefile +++ b/libexec/dma/Makefile @@ -8,7 +8,7 @@ LDADD= -lssl -lcrypto PROG= dma SRCS= aliases_parse.y aliases_scan.l base64.c conf.c crypto.c -SRCS+= dma.c local.c mail.c net.c spool.c util.c +SRCS+= dma.c dns.c local.c mail.c net.c spool.c util.c MAN= dma.8 BINOWN= root diff --git a/libexec/dma/TODO b/libexec/dma/TODO index 86a023624b..77b497ffb5 100644 --- a/libexec/dma/TODO +++ b/libexec/dma/TODO @@ -1,5 +1,4 @@ - unquote/handle quoted local recipients -- resolve mail servers using MX, not plain queries - use proper sysexit codes - handle/use ESMTP extensions - .forward support diff --git a/libexec/dma/dma.h b/libexec/dma/dma.h index 81c6ed9d83..cb49b65879 100644 --- a/libexec/dma/dma.h +++ b/libexec/dma/dma.h @@ -36,9 +36,13 @@ #ifndef DMA_H #define DMA_H -#include - +#include #include +#include +#include +#include +#include +#include #ifndef __unused #ifdef __GNUC__ @@ -137,6 +141,15 @@ struct authuser { SLIST_HEAD(authusers, authuser); +struct mx_hostentry { + char host[MAXDNAME]; + char addr[INET6_ADDRSTRLEN]; + int pref; + struct addrinfo ai; + struct sockaddr_storage sa; +}; + + /* global variables */ extern struct aliases aliases; extern struct config *config; @@ -163,6 +176,9 @@ void hmac_md5(unsigned char *, int, unsigned char *, int, caddr_t); int smtp_auth_md5(int, char *, char *); int smtp_init_crypto(int, int); +/* dns.c */ +int dns_get_mx_list(const char *, int, struct mx_hostentry **, int); + /* net.c */ char *ssl_errstr(void); int read_remote(int, int, char *); diff --git a/libexec/dma/dns.c b/libexec/dma/dns.c new file mode 100644 index 0000000000..8cfdd04645 --- /dev/null +++ b/libexec/dma/dns.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dma.h" + +static int +sort_pref(const void *a, const void *b) +{ + const struct mx_hostentry *ha = a, *hb = b; + int v; + + /* sort increasing by preference primarily */ + v = ha->pref - hb->pref; + if (v != 0) + return (v); + + /* sort PF_INET6 before PF_INET */ + v = - (ha->ai.ai_family - hb->ai.ai_family); + return (v); +} + +static int +add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps) +{ + struct addrinfo hints, *res, *res0 = NULL; + char servname[10]; + struct mx_hostentry *p; + size_t onhosts; + const int count_inc = 10; + int err; + + onhosts = *ps; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + snprintf(servname, sizeof(servname), "%d", port); + err = getaddrinfo(host, servname, &hints, &res0); + if (err) + return (-1); + + for (res = res0; res != NULL; res = res->ai_next) { + if (*ps + 1 >= roundup(*ps, count_inc)) { + size_t newsz = roundup(*ps + 2, count_inc); + *he = reallocf(*he, newsz * sizeof(**he)); + if (*he == NULL) + goto out; + } + + p = &(*he)[*ps]; + strlcpy(p->host, host, sizeof(p->host)); + p->pref = pref; + p->ai = *res; + p->ai.ai_addr = NULL; + bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen); + + getnameinfo((struct sockaddr *)&p->sa, p->sa.ss_len, + p->addr, sizeof(p->addr), + NULL, 0, NI_NUMERICHOST); + + (*ps)++; + } + freeaddrinfo(res0); + + return (*ps - onhosts); + +out: + if (res0 != NULL) + freeaddrinfo(res0); + return (-1); +} + +int +dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx) +{ + char outname[MAXDNAME]; + ns_msg msg; + ns_rr rr; + const char *searchhost; + const char *cp; + char *ans; + struct mx_hostentry *hosts = NULL; + size_t nhosts = 0; + size_t anssz; + int pref; + int cname_recurse; + int err; + int i; + + res_init(); + searchhost = host; + cname_recurse = 0; + + anssz = 65536; + ans = malloc(anssz); + if (ans == NULL) + return (1); + + if (no_mx) + goto out; + +repeat: + err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz); + if (err < 0) { + switch (h_errno) { + case NO_DATA: + /* + * Host exists, but no MX (or CNAME) entry. + * Not an error, use host name instead. + */ + goto out; + case TRY_AGAIN: + /* transient error */ + goto transerr; + case NO_RECOVERY: + case HOST_NOT_FOUND: + default: + errno = ENOENT; + goto err; + } + } + + if (!ns_initparse(ans, anssz, &msg)) + goto transerr; + + switch (ns_msg_getflag(msg, ns_f_rcode)) { + case ns_r_noerror: + break; + case ns_r_nxdomain: + goto err; + default: + goto transerr; + } + + for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) { + if (ns_parserr(&msg, ns_s_an, i, &rr)) + goto transerr; + + cp = (const char *)ns_rr_rdata(rr); + + switch (ns_rr_type(rr)) { + case ns_t_mx: + pref = ns_get16(cp); + cp += 2; + err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), + cp, outname, sizeof(outname)); + if (err < 0) + goto transerr; + + add_host(pref, outname, port, &hosts, &nhosts); + break; + + case ns_t_cname: + err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), + cp, outname, sizeof(outname)); + if (err < 0) + goto transerr; + + /* Prevent a CNAME loop */ + if (cname_recurse++ > 10) + goto err; + + searchhost = outname; + goto repeat; + + default: + break; + } + } + +out: + err = 0; + if (0) { +transerr: + if (nhosts == 0) + err = 1; + } + if (0) { +err: + err = -1; + } + + free(ans); + + if (!err) { + /* + * If we didn't find any MX, use the hostname instead. + */ + if (nhosts == 0) + add_host(0, searchhost, port, &hosts, &nhosts); + + qsort(hosts, nhosts, sizeof(*hosts), sort_pref); + } + + if (nhosts > 0) { + /* terminate list */ + *hosts[nhosts].host = 0; + } else { + if (hosts != NULL) + free(hosts); + hosts = NULL; + } + + *he = hosts; + return (err); + + free(ans); + if (hosts != NULL) + free(hosts); + return (err); +} + +#if defined(TESTING) +int +main(int argc, char **argv) +{ + struct mx_hostentry *he, *p; + int err; + + err = dns_get_mx_list(argv[1], 53, &he, 0); + if (err) + return (err); + + for (p = he; *p->host != 0; p++) { + printf("%d\t%s\t%s\n", p->pref, p->host, p->addr); + } + + return (0); +} +#endif diff --git a/libexec/dma/net.c b/libexec/dma/net.c index 38d8614654..f34490c686 100644 --- a/libexec/dma/net.c +++ b/libexec/dma/net.c @@ -276,53 +276,27 @@ encerr: } static int -open_connection(const char *host) +open_connection(struct mx_hostentry *h) { - struct addrinfo hints, *res, *res0; - char servname[128]; - const char *errmsg = NULL; - int fd, error = 0, port; - - if (config->port != 0) - port = config->port; - else - port = SMTP_PORT; - - /* XXX FIXME get MX record of host */ - /* Shamelessly taken from getaddrinfo(3) */ - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - snprintf(servname, sizeof(servname), "%d", port); - error = getaddrinfo(host, servname, &hints, &res0); - if (error) { - syslog(LOG_NOTICE, "remote delivery deferred: %s", gai_strerror(error)); + int fd; + + syslog(LOG_INFO, "trying remote delivery to %s [%s] pref %d", + h->host, h->addr, h->pref); + + fd = socket(h->ai.ai_family, h->ai.ai_socktype, h->ai.ai_protocol); + if (fd < 0) { + syslog(LOG_INFO, "socket for %s [%s] failed: %m", + h->host, h->addr); return (-1); } - fd = -1; - for (res = res0; res; res = res->ai_next) { - fd=socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (fd < 0) { - errmsg = "socket failed"; - continue; - } - if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { - errmsg = "connect failed"; - close(fd); - fd = -1; - continue; - } - break; - } - if (fd < 0) { - syslog(LOG_NOTICE, "remote delivery deferred: %s (%s:%s)", - errmsg, host, servname); - freeaddrinfo(res0); + + if (connect(fd, (struct sockaddr *)&h->sa, h->sa.ss_len) < 0) { + syslog(LOG_INFO, "connect to %s [%s] failed: %m", + h->host, h->addr); + close(fd); return (-1); } - freeaddrinfo(res0); + return (fd); } @@ -339,39 +313,19 @@ close_connection(int fd) close(fd); } -int -deliver_remote(struct qitem *it, const char **errmsg) +static int +deliver_to_host(struct qitem *it, struct mx_hostentry *host, void *errmsgc) { struct authuser *a; - char *host, line[1000]; - int fd, error = 0, do_auth = 0, res = 0; + char line[1000]; size_t linelen; - /* asprintf can't take const */ - void *errmsgc = __DECONST(char **, errmsg); + int fd, error = 0, do_auth = 0, res = 0; if (fseek(it->mailf, 0, SEEK_SET) != 0) { asprintf(errmsgc, "can not seek: %s", strerror(errno)); return (-1); } - host = strrchr(it->addr, '@'); - /* Should not happen */ - if (host == NULL) { - asprintf(errmsgc, "Internal error: badly formed address %s", - it->addr); - return(-1); - } else { - /* Step over the @ */ - host++; - } - - /* Smarthost support? */ - if (config->smarthost != NULL && strlen(config->smarthost) > 0) { - syslog(LOG_INFO, "using smarthost (%s:%i)", - config->smarthost, config->port); - host = config->smarthost; - } - fd = open_connection(host); if (fd < 0) return (1); @@ -393,22 +347,31 @@ deliver_remote(struct qitem *it, const char **errmsg) goto out; } +#define READ_REMOTE_CHECK(c, exp) \ + res = read_remote(fd, 0, NULL); \ + if (res == 5) { \ + syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \ + host->host, host->addr, c, neterr); \ + asprintf(errmsgc, "%s [%s] did not like our %s:\n%s", \ + host->host, host->addr, c, neterr); \ + return (-1); \ + } else if (res != exp) { \ + syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ + host->host, host->addr, c, neterr); \ + return (1); \ + } + /* XXX allow HELO fallback */ /* XXX record ESMTP keywords */ send_remote_command(fd, "EHLO %s", hostname()); - if (read_remote(fd, 0, NULL) != 2) { - syslog(LOG_WARNING, "remote delivery deferred: EHLO failed: %s", neterr); - asprintf(errmsgc, "%s did not like our EHLO:\n%s", - host, neterr); - return (-1); - } + READ_REMOTE_CHECK("EHLO", 2); /* * Use SMTP authentication if the user defined an entry for the remote * or smarthost */ SLIST_FOREACH(a, &authusers, next) { - if (strcmp(a->host, host) == 0) { + if (strcmp(a->host, host->host) == 0) { do_auth = 1; break; } @@ -424,7 +387,7 @@ deliver_remote(struct qitem *it, const char **errmsg) if (error < 0) { syslog(LOG_ERR, "remote delivery failed:" " SMTP login failed: %m"); - asprintf(errmsgc, "SMTP login to %s failed", host); + asprintf(errmsgc, "SMTP login to %s failed", host->host); return (-1); } /* SMTP login is not available, so try without */ @@ -433,20 +396,6 @@ deliver_remote(struct qitem *it, const char **errmsg) } } -#define READ_REMOTE_CHECK(c, exp) \ - res = read_remote(fd, 0, NULL); \ - if (res == 5) { \ - syslog(LOG_ERR, "remote delivery failed: " \ - c " failed: %s", neterr); \ - asprintf(errmsgc, "%s did not like our " c ":\n%s", \ - host, neterr); \ - return (-1); \ - } else if (res != exp) { \ - syslog(LOG_NOTICE, "remote delivery deferred: " \ - c " failed: %s", neterr); \ - return (1); \ - } - /* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */ send_remote_command(fd, "MAIL FROM:<%s>", it->sender); READ_REMOTE_CHECK("MAIL FROM", 2); @@ -465,7 +414,7 @@ deliver_remote(struct qitem *it, const char **errmsg) linelen = strlen(line); if (linelen == 0 || line[linelen - 1] != '\n') { syslog(LOG_CRIT, "remote delivery failed: corrupted queue file"); - *errmsg = "corrupted queue file"; + *(const char **)errmsgc = "corrupted queue file"; error = -1; goto out; } @@ -499,3 +448,66 @@ out: return (error); } +int +deliver_remote(struct qitem *it, const char **errmsg) +{ + /* asprintf can't take const */ + void *errmsgc = __DECONST(char **, errmsg); + struct mx_hostentry *hosts, *h; + char *host; + int port; + int error = 1, smarthost = 0; + + host = strrchr(it->addr, '@'); + /* Should not happen */ + if (host == NULL) { + asprintf(errmsgc, "Internal error: badly formed address %s", + it->addr); + return(-1); + } else { + /* Step over the @ */ + host++; + } + + port = SMTP_PORT; + + /* Smarthost support? */ + if (config->smarthost != NULL && strlen(config->smarthost) > 0) { + syslog(LOG_INFO, "using smarthost (%s:%i)", + config->smarthost, config->port); + host = config->smarthost; + smarthost = 1; + + if (config->port != 0) + port = config->port; + } + + error = dns_get_mx_list(host, port, &hosts, smarthost); + if (error) { + syslog(LOG_NOTICE, "remote delivery %s: DNS failure (%s)", + error < 0 ? "failed" : "deferred", + host); + return (error); + } + + for (h = hosts; *h->host != 0; h++) { + switch (deliver_to_host(it, h, errmsgc)) { + case 0: + /* success */ + error = 0; + goto out; + case 1: + /* temp failure */ + error = 1; + break; + default: + /* perm failure */ + error = -1; + goto out; + } + } +out: + free(hosts); + + return (error); +} -- 2.41.0