294 lines
6.6 KiB
C
294 lines
6.6 KiB
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
|