--- /dev/null
+/*
+ * Copyright (c) 2008 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+ *
+ * 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 <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <string.h>
+#include <stdlib.h>
+
+#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
}
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);
}
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);
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;
}
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 */
}
}
-#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);
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;
}
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);
+}