root/libexec/dma/dns.c
/*
 * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
 * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Simon Schubert <2@0x2c.org>.
 *
 * 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 <sys/param.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;
        const int count_inc = 10;

        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);
        switch (getaddrinfo(host, servname, &hints, &res0)) {
        case 0:
                break;
        case EAI_AGAIN:
        case EAI_NONAME:
                /*
                 * EAI_NONAME gets returned for:
                 * SMARTHOST set but DNS server not reachable -> defer
                 * SMARTHOST set but DNS server returns "host does not exist"
                 *           -> buggy configuration
                 *           -> either defer or bounce would be ok -> defer
                 * MX entry was returned by DNS server but name doesn't resolve
                 *           -> hopefully transient situation -> defer
                 * all other DNS problems should have been caught earlier
                 * in dns_get_mx_list().
                 */
                goto out;
        default:
                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->ai.ai_addrlen,
                            p->addr, sizeof(p->addr),
                            NULL, 0, NI_NUMERICHOST);

                (*ps)++;
        }
        freeaddrinfo(res0);

        return (0);

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 unsigned char *cp;
        unsigned char *ans;
        struct mx_hostentry *hosts = NULL;
        size_t nhosts = 0;
        size_t anssz;
        int pref;
        int cname_recurse;
        int have_mx = 0;
        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 = ns_rr_rdata(rr);

                switch (ns_rr_type(rr)) {
                case ns_t_mx:
                        have_mx = 1;
                        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;

                        err = add_host(pref, outname, port, &hosts, &nhosts);
                        if (err == -1)
                                goto err;
                        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 == 0) {
                if (!have_mx) {
                        /*
                         * If we didn't find any MX, use the hostname instead.
                         */
                        err = add_host(0, host, port, &hosts, &nhosts);
                } else if (nhosts == 0) {
                        /*
                         * We did get MX, but couldn't resolve any of them
                         * due to transient errors.
                         */
                        err = 1;
                }
        }

        if (nhosts > 0) {
                qsort(hosts, nhosts, sizeof(*hosts), sort_pref);
                /* terminate list */
                *hosts[nhosts].host = 0;
        } else {
                if (hosts != NULL)
                        free(hosts);
                hosts = NULL;
        }

        *he = 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