dma: perform MX lookups
authorSimon Schubert <corecode@dragonflybsd.org>
Sun, 20 Sep 2009 18:58:37 +0000 (20:58 +0200)
committerMatthew Dillon <dillon@apollo.backplane.com>
Mon, 28 Sep 2009 19:07:40 +0000 (12:07 -0700)
libexec/dma/Makefile
libexec/dma/TODO
libexec/dma/dma.h
libexec/dma/dns.c [new file with mode: 0644]
libexec/dma/net.c

index 42e0044..6e99f6d 100644 (file)
@@ -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
index 86a0236..77b497f 100644 (file)
@@ -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
index 81c6ed9..cb49b65 100644 (file)
 #ifndef DMA_H
 #define DMA_H
 
-#include <openssl/ssl.h>
-
+#include <sys/types.h>
 #include <sys/queue.h>
+#include <sys/socket.h>
+#include <arpa/nameser.h>
+#include <arpa/inet.h>
+#include <openssl/ssl.h>
+#include <netdb.h>
 
 #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 (file)
index 0000000..8cfdd04
--- /dev/null
@@ -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 <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
index 38d8614..f34490c 100644 (file)
@@ -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);
+}