/* This file is part of mailfromd.
Copyright (C) 2005, 2006, 2007 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#define MF_SOURCE_NAME MF_SOURCE_DNSBASE
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include "mailfromd.h"
struct mx_buffer {
unsigned pref;
char *name;
};
static int
comp_pref(const void *a, const void *b)
{
const struct mx_buffer *ma = a, *mb = b;
if (ma->pref > mb->pref)
return 1;
else if (ma->pref < mb->pref)
return -1;
return strcmp(ma->name, mb->name);
}
/* Obtain MX records for domain name HOST. Return them in mxbuf, sorted by
preference in ascending order.
Notice that we *have* to use old BIND-4-style method, since glibc folks
in their incredible wiseness have not exported new interface functions
from libresolve.so. The only way to access them would be to link with
-lresolve statically.
Let's hope that someday this stupidity will change. */
static dns_status
_getmx(const char *host, char *answer, size_t answer_size, mxbuf_t mxbuf,
size_t *pcount, unsigned long *pttl)
{
int i, n, nmx;
struct mx_buffer mx_buffer[MAXMXCOUNT];
HEADER *hp;
unsigned char *eom, *cp;
unsigned short qdcount, ancount;
struct __res_state stat;
debug1(80,"Getting MX records for %s", host);
memset((void *)&stat, 0, sizeof(struct __res_state));
res_ninit(&stat);
n = res_nquery (&stat, host, C_IN, T_MX, answer, answer_size);
res_nclose(&stat);
if (n < 0) {
debug2(10, "res_nquery failed (errno=%s, h_errno=%d)",
mu_strerror(errno), h_errno);
switch (h_errno) {
case NO_DATA:
case NO_RECOVERY:
case HOST_NOT_FOUND:
return dns_not_found;
case TRY_AGAIN:
case -1:
return dns_temp_failure;
default:
mu_error(_("res_nquery(%s) failed with unexpected h_errno %d"),
host, h_errno);
return dns_failure;
}
}
if (n > answer_size)
n = answer_size;
hp = (HEADER*)answer;
cp = (unsigned char *) answer + HFIXEDSZ;
eom = (unsigned char *) answer + n;
/* Skip query part */
for (qdcount = ntohs((unsigned short)hp->qdcount);
qdcount--;
cp += n + QFIXEDSZ) {
if ((n = dn_skipname(cp, eom)) < 0)
return dns_failure;
}
ancount = ntohs((unsigned short)hp->ancount);
/* Collect MX records */
for (i = nmx = 0; i < ancount; i++) {
unsigned short pref, type;
char tname[NS_MAXDNAME];
unsigned long ttl;
if ((n = dn_expand((unsigned char *)answer,
eom, cp, tname, sizeof tname)) < 0)
break;
cp += n;
GETSHORT(type, cp);
cp += INT16SZ;
GETLONG(ttl, cp);
if (pttl)
UPDATE_TTL(*pttl, ttl);
GETSHORT(n, cp);
if (type != T_MX) {
debug2(90,"unexpected answer type %d, size %d\n",
type, n);
cp += n;
continue;
}
GETSHORT(pref, cp);
if ((n = dn_expand((u_char *)answer, eom, cp,
tname, sizeof tname)) < 0)
break;
cp += n;
mx_buffer[nmx].pref = pref;
mx_buffer[nmx].name = strdup(tname);
debug2(20,"MX %u %s", mx_buffer[nmx].pref,
mx_buffer[nmx].name);
if (++nmx >= MAXMXCOUNT)
break;
}
/* Sort according to preference value */
qsort(mx_buffer, nmx, sizeof mx_buffer[0], comp_pref);
/* Prepare return value */
memset(mxbuf, 0, sizeof(mxbuf_t));
for (i = 0; i < nmx; i++)
mxbuf[i] = mx_buffer[i].name;
*pcount = nmx;
return dns_success;
}
int
dns_str_is_ipv4(const char *addr)
{
int dot_count;
int digit_count;
dot_count = 0;
digit_count = 0;
while (*addr != 0 && *addr != ' ') {
if (*addr == '.') {
if (++dot_count > 3)
break;
digit_count = 0;
} else if (!(isdigit(*addr) && ++digit_count <= 3)) {
return 0;
}
addr++;
}
return dot_count == 3;
}
MUTEX_DCL(dns_mutex)
/* Return MX records for the given HOST. If no records were found, recurse
to its parent domains until any record is found or recursion depth reaches
MAXDEPTH */
dns_status
dns_get_mx_records(const char *host, int maxdepth, mxbuf_t mxbuf,
size_t *mxcount, unsigned long *ttl)
{
char *hbuf = NULL;
dns_status status = dns_failure;
MUTEX_LOCK(dns_mutex);
if (dns_str_is_ipv4(host)) {
status = resolve_ipstr(host, &hbuf);
if (status != dns_success)
host = NULL;
}
if (host) {
unsigned char *answer = malloc(MAXPACKET);
if (!answer)
status = dns_failure;
else {
const char *p;
int depth;
for (p = host, depth = 0; p && depth < maxdepth;
p++, depth++) {
if (ttl)
*ttl = ~(unsigned long)0;
status = _getmx(p, answer, MAXPACKET, mxbuf,
mxcount, ttl);
if (status == dns_success
|| status == dns_temp_failure)
break;
p = strchr(p, '.');
if (!p)
break;
}
free(answer);
}
}
MUTEX_UNLOCK(dns_mutex);
free(hbuf);
return status;
}
void
dns_freemx(mxbuf_t mxbuf)
{
int i;
for (i = 0; i < MAXMXCOUNT && mxbuf[i]; i++)
free(mxbuf[i]);
}
#define LOOKUP_FAILURE -1
#define LOOKUP_SUCCESS 0
#define LOOKUP_CNAME 1
struct loop_data {
int qtype; /* Type of the query */
char *name; /* Key to look up */
size_t name_size; /* Length of the key */
char *domain; /* Domain name */
size_t domain_size; /* Length of the domain name */
char *answer; /* Answer buffer */
size_t answer_size; /* Size of answer buffer */
/* Return data: */
char *hbuf; /* Return buffer */
size_t hbsize; /* Size of return buffer */
size_t hbcount; /* Number of items returned */
time_t ttl; /* TTL value */
dns_status status; /* Status */
int atype; /* On input: desired answer type or T_ANY
On output: Answer type */
/* Internal data */
size_t loopcnt; /* Number of CNAME loops allowed */
};
#define NSIZE MAX(MAXPACKET, MAXDNAME*2+2)
#define SET_STATUS(lp,s) if ((lp)->status != dns_success) (lp)->status = s
typedef struct {
GACOPYZ_UINT32_T x;
char a;
} align;
static int
domain_name_cmp(const char *ptr, const char *name, const char *domain)
{
int c;
while (*name && *ptr)
if (c = *ptr++ - *name++)
return c;
if (*domain) {
if (*ptr != '.')
return *ptr - *domain;
ptr++;
while (*domain && *ptr)
if (c = *ptr++ - *domain++)
return c;
if (*domain == '.' && domain[1] == 0)
domain++;
if (*ptr == '.' && ptr[1] == 0)
ptr++;
if (*domain || *ptr)
return 1;
}
return 0;
}
static int
cname_loop_body(struct loop_data *lp)
{
int i, n, rc;
HEADER *hp;
unsigned char *eom, *cp;
unsigned short qdcount, ancount;
char *p;
size_t len;
struct __res_state statb;
memset((void *)&statb, 0, sizeof(struct __res_state));
res_ninit(&statb);
n = res_nquerydomain (&statb, lp->name, lp->domain, C_IN, lp->qtype,
lp->answer, lp->answer_size);
res_nclose(&statb);
if (n < 0) {
debug2(10, "res_nquerydomain failed (errno=%s, h_errno=%d)",
mu_strerror(errno), h_errno);
switch (h_errno) {
case NO_DATA:
case NO_RECOVERY:
case HOST_NOT_FOUND:
SET_STATUS(lp, dns_not_found);
return LOOKUP_FAILURE;
case TRY_AGAIN:
case -1:
SET_STATUS(lp, dns_temp_failure);
return LOOKUP_FAILURE;
default:
mu_error(_("res_nquerydomain(%s) failed with unexpected h_errno %d"),
lp->name, h_errno);
SET_STATUS(lp, dns_failure);
return LOOKUP_FAILURE;
}
}
if (n > lp->answer_size)
n = lp->answer_size;
hp = (HEADER*) lp->answer;
cp = (unsigned char *) lp->answer + HFIXEDSZ;
eom = (unsigned char *) lp->answer + n;
/* Skip query part */
for (qdcount = ntohs((unsigned short)hp->qdcount);
qdcount--;
cp += n + QFIXEDSZ) {
if ((n = dn_skipname(cp, eom)) < 0) {
SET_STATUS(lp, dns_failure);
return LOOKUP_FAILURE;
}
}
SET_STATUS(lp, dns_not_found);
for (ancount = ntohs((unsigned short) hp->ancount), i = 0;
i < ancount && cp < eom;
cp += n, i++) {
unsigned short type;
unsigned long ttl = ~(unsigned long)0;
char nbuf[NSIZE];
char *bp = nbuf;
size_t blen = sizeof nbuf;
size_t l;
n = dn_expand((unsigned char *) lp->answer, eom, cp,
nbuf, sizeof nbuf);
if (n < 0)
break;
cp += n;
GETSHORT(type, cp);
cp += INT16SZ; /* skip over class */
GETLONG(ttl, cp);
UPDATE_TTL(lp->ttl, ttl);
GETSHORT(n, cp); /* rdlength */
switch (type) {
case T_A:
if (lp->atype != T_ANY && lp->atype != type)
continue;
if (domain_name_cmp(bp, lp->name, lp->domain) != 0)
continue;
/* Skip host name */
l = strlen(bp) + 1;
bp += l;
blen -= l;
blen -= sizeof(align) - ((u_long)bp % sizeof(align));
bp += sizeof(align) - ((u_long)bp % sizeof(align));
if (bp + n >= nbuf + blen) {
debug1(1, "size (%d) too big", n);
} else {
if (n != sizeof(GACOPYZ_UINT32_T)) {
debug1(1,
"unsupported address size: %d",
n);
} else if (lp->hbcount * sizeof(GACOPYZ_UINT32_T) >= lp->hbsize)
debug(1, "Too many addresses");
else {
memmove((GACOPYZ_UINT32_T *)
lp->hbuf + lp->hbcount,
cp, n);
lp->hbcount++;
}
}
bp += n;
blen -= n;
lp->atype = T_A;
SET_STATUS(lp, dns_success);
break;
case T_PTR:
if (lp->atype != T_ANY && lp->atype != type)
continue;
if ((rc = dn_expand((unsigned char *)lp->answer,
eom, cp,
nbuf, sizeof(nbuf))) < 0) {
SET_STATUS(lp, dns_failure);
return LOOKUP_FAILURE;
}
l = strlen(nbuf);
if (lp->hbcount + l >= lp->hbsize)
break;
memcpy(lp->hbuf + lp->hbcount, nbuf, l);
lp->hbcount += l;
lp->hbuf[lp->hbcount++] = 0;
lp->atype = T_PTR;
SET_STATUS(lp, dns_success);
break;
case T_TXT:
if (lp->atype != type)
continue;
l = cp[0];
if (lp->hbcount + l >= lp->hbsize)
break;
memcpy(lp->hbuf + lp->hbcount, cp + 1, l);
lp->hbcount += l;
lp->hbuf[lp->hbcount++] = 0;
lp->atype = T_TXT;
SET_STATUS(lp, dns_success);
break;
case T_CNAME:
if (--lp->loopcnt == 0) {
mu_error(_("DNS failure: CNAME loop for %s"),
lp->name);
SET_STATUS(lp, dns_failure);
return LOOKUP_FAILURE;
}
if ((rc = dn_expand((unsigned char *)lp->answer,
eom, cp,
nbuf, sizeof(nbuf))) < 0) {
SET_STATUS(lp, dns_failure);
return LOOKUP_FAILURE;
}
/* RFC 1034 section 3.6 specifies that CNAME
should point at the canonical name -- but
urges software to try again anyway.
*/
p = strchr (nbuf, '.');
if (!p)
return LOOKUP_SUCCESS;
len = p - nbuf;
if (len + 1 >= lp->name_size)
return LOOKUP_FAILURE;
memcpy(lp->name, nbuf, len);
lp->name[len] = 0;
len = strlen (p + 1);
if (len + 1 >= lp->domain_size)
return LOOKUP_FAILURE;
strcpy(lp->domain, p + 1);
return LOOKUP_CNAME;
}
}
return lp->status == dns_success ? LOOKUP_SUCCESS : LOOKUP_FAILURE;
}
static void
cnameloop(struct loop_data *lp)
{
MUTEX_LOCK(dns_mutex);
if (!lp->answer) {
static unsigned char *answer;
if (!answer)
answer = xmalloc(MAXPACKET);
lp->answer = answer;
lp->answer_size = MAXPACKET;
}
while (cname_loop_body(lp) == LOOKUP_CNAME)
;
MUTEX_UNLOCK(dns_mutex);
}
typedef char IPBUF[3*4+3+1];
int
dns_reverse_ipstr(const char *ipstr, char *revipstr)
{
int i;
const char *p;
char *q;
q = revipstr + strlen(ipstr);
*q = 0;
for (i = 0, p = ipstr; *p && i < 4; i++) {
int len;
for (len = 0; p[len] && p[len] != '.'; len++)
;
q -= len;
memcpy(q, p, len);
if (q > revipstr)
*--q = '.';
p += len;
if (*p == '.')
p++;
}
return *p || i != 4;
}
dns_status
dns_resolve_ipstr(const char *ipstr, const char *domain,
char *answer, size_t answer_size,
char *hbuf, size_t hbsize, unsigned long *ttl)
{
char namebuf[NSIZE];
char domainbuf[NSIZE];
struct loop_data ld;
ld.qtype = ld.atype = T_ANY;
if (!domain) {
if (dns_reverse_ipstr(ipstr, namebuf))
return dns_failure;
ld.name = namebuf;
ld.name_size = sizeof namebuf;
} else {
strncpy(namebuf, ipstr, sizeof(namebuf)-1);
namebuf[sizeof(namebuf)-1] = 0;
ld.name = namebuf;
ld.name_size = strlen(ld.name);
}
ld.domain = domainbuf;
strcpy(domainbuf, domain ? domain : "in-addr.arpa");
ld.domain_size = sizeof domainbuf;
ld.answer = answer;
ld.answer_size = answer_size;
ld.hbuf = hbuf;
ld.hbsize = hbsize;
ld.hbcount = 0;
ld.ttl = ~(unsigned long)0;
ld.status = dns_failure;
ld.loopcnt = MAXCNAMEDEPTH;
cnameloop(&ld);
if (ld.status == dns_success && ld.atype == T_A) {
struct in_addr s;
char *p;
s.s_addr = *(GACOPYZ_UINT32_T*)hbuf;
p = inet_ntoa(s);
strncpy(hbuf, p, hbsize);
}
*ttl = ld.ttl;
return ld.status;
}
dns_status
dns_resolve_hostname(const char *host, char *answer, size_t answer_size,
char *ipbuf, size_t ipbsize, unsigned long *ttl)
{
struct loop_data ld;
char namebuf[NSIZE];
char domainbuf[NSIZE] = "";
ld.qtype = ld.atype = T_A;
strncpy(namebuf, host, sizeof namebuf - 1);
ld.name = namebuf;
ld.name_size = sizeof namebuf;
ld.domain = domainbuf;
ld.domain_size = sizeof domainbuf;
ld.answer = answer;
ld.answer_size = answer_size;
ld.hbuf = ipbuf;
ld.hbsize = ipbsize;
ld.hbcount = 0;
ld.ttl = ~(unsigned long)0;
ld.status = dns_failure;
ld.loopcnt = MAXCNAMEDEPTH;
cnameloop(&ld);
if (ld.status == dns_success && ld.atype == T_A) {
struct in_addr s;
char *p;
s.s_addr = *(GACOPYZ_UINT32_T*)ipbuf;
p = inet_ntoa(s);
strncpy(ipbuf, p, ipbsize);
}
*ttl = ld.ttl;
return ld.status;
}
dns_status
a_lookup(const char *host,
GACOPYZ_UINT32_T *ipbuf, size_t ipbsize, size_t *ipcount,
unsigned long *ttl, char *answer, size_t answer_size)
{
struct loop_data ld;
char namebuf[NSIZE];
char domainbuf[NSIZE] = "";
ld.qtype = ld.atype = T_A;
strncpy(namebuf, host, sizeof namebuf - 1);
ld.name = namebuf;
ld.name_size = sizeof namebuf;
ld.domain = domainbuf;
ld.domain_size = sizeof domainbuf;
ld.answer = answer;
ld.answer_size = answer_size;
ld.hbuf = (char*) ipbuf;
ld.hbsize = ipbsize * sizeof ipbuf[0];
ld.hbcount = 0;
ld.ttl = ~(unsigned long)0;
ld.status = dns_failure;
ld.loopcnt = MAXCNAMEDEPTH;
cnameloop(&ld);
if (ld.status == dns_success) {
if (ipcount)
*ipcount = ld.hbcount;
if (ttl)
*ttl = ld.ttl;
}
return ld.status;
}
static void
textbuf_to_argv(const char *hbuf, size_t hsize, char **argv, size_t argc)
{
size_t i;
const char *p;
for (i = 0, p = hbuf; i < argc - 1 && p < hbuf + hsize;
p += strlen(p) + 1)
argv[i++] = xstrdup(p);
argv[i] = NULL;
}
dns_status
ptr_lookup(struct in_addr ip,
char **names, size_t maxnames, unsigned long *ttl,
char *answer, size_t answer_size)
{
char namebuf[NSIZE];
char domainbuf[NSIZE];
char hbuf[NSIZE];
struct loop_data ld;
char *p;
ip.s_addr = ntohl(ip.s_addr);
p = inet_ntoa(ip);
strncpy(namebuf, p, sizeof namebuf);
ld.qtype = ld.atype = T_PTR;
ld.name = namebuf;
ld.name_size = sizeof namebuf;
ld.domain = domainbuf;
strcpy(domainbuf, "in-addr.arpa");
ld.domain_size = sizeof domainbuf;
ld.answer = answer;
ld.answer_size = answer_size;
ld.hbuf = hbuf;
ld.hbsize = sizeof hbuf;
ld.hbcount = 0;
ld.ttl = ~(unsigned long)0;
ld.status = dns_failure;
ld.loopcnt = MAXCNAMEDEPTH;
cnameloop(&ld);
if (ld.status == dns_success) {
textbuf_to_argv(ld.hbuf, ld.hbcount, names, maxnames);
if (ttl)
*ttl = ld.ttl;
}
return ld.status;
}
dns_status
txt_lookup(const char *name,
char **names, size_t maxnames, unsigned long *ttl,
char *answer, size_t answer_size)
{
char domainbuf[NSIZE];
char hbuf[NSIZE];
struct loop_data ld;
ld.qtype = T_TXT;
ld.atype = T_TXT;
ld.name = xstrdup(name);
ld.name_size = strlen(ld.name);
domainbuf[0] = 0;
ld.domain = domainbuf;
ld.domain_size = sizeof domainbuf;
ld.answer = answer;
ld.answer_size = answer_size;
ld.hbuf = hbuf;
ld.hbsize = sizeof hbuf;
ld.hbcount = 0;
ld.ttl = ~(unsigned long)0;
ld.status = dns_failure;
ld.loopcnt = MAXCNAMEDEPTH;
cnameloop(&ld);
free(ld.name);
if (ld.status == dns_success) {
textbuf_to_argv(ld.hbuf, ld.hbcount, names, maxnames);
if (ttl)
*ttl = ld.ttl;
}
return ld.status;
}
/* FIXME: This is a placeholder for a function that should look up for any
SPF or TXT records for DOMAIN. If any SPF are found, TXT should be
discarded.
For the time being, it handles only TXT */
dns_status
spf_lookup(const char *domain,
char **txt, size_t maxtxt, unsigned long *ttl,
char *answer, size_t answer_size)
{
return txt_lookup(domain, txt, maxtxt, ttl, answer, answer_size);
}
/* rfc4408, chapter 5.5 */
dns_status
ptr_validate(const char *ipstr, char ***vnptr, size_t *vcount,
unsigned long *pttl)
{
struct in_addr ip;
char *names[11], **p;
char *vnames[10];
size_t vi = 0;
int status;
unsigned long minttl = ~(unsigned long)0;
unsigned long ttl;
if (!inet_aton(ipstr, &ip))
return dns_failure;
status = ptr_lookup(ip, names, NELEMS(names), &ttl, NULL, 0);
if (status != dns_success)
return status;
UPDATE_TTL(minttl, ttl);
for (p = names; *p; p++) {
GACOPYZ_UINT32_T ipbuf[10];
size_t ipcount;
status = a_lookup(*p,
ipbuf, NELEMS(ipbuf),
&ipcount,
&ttl, NULL, 0);
if (status == dns_success && vi < NELEMS(vnames[0])) {
size_t i;
UPDATE_TTL(minttl, ttl);
for (i = 0; i < ipcount; i++)
if (ipbuf[i] == ip.s_addr) {
vnames[vi++] = *p;
if (vnptr)
*p = NULL;
break;
}
}
free(*p);
}
if (vi > 0) {
if (pttl)
*pttl = minttl;
if (vnptr) {
size_t i;
*vnptr = xcalloc(vi+1, sizeof vnptr[0]);
for (i = 0; i < vi; i++)
(*vnptr)[i] = vnames[i];
(*vnptr)[i] = NULL;
*vcount = vi;
}
}
return vi > 0 ? dns_success : dns_not_found;
}