diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-02-17 12:56:00 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-02-17 12:56:00 +0000 |
commit | ad4e496b8acfbcaf1ba6aabd2ba8b508f196361d (patch) | |
tree | 5960b99cbae5352562b8aa1230119f4702241278 /src/dnsbase.c | |
parent | 4cb6c6dd32298e583c45c49fce63d452bb903f27 (diff) | |
download | mailfromd-ad4e496b8acfbcaf1ba6aabd2ba8b508f196361d.tar.gz mailfromd-ad4e496b8acfbcaf1ba6aabd2ba8b508f196361d.tar.bz2 |
Begin implementing SPF support
git-svn-id: file:///svnroot/mailfromd/trunk@1250 7a8a7f39-df28-0410-adc6-e0d955640f24
Diffstat (limited to 'src/dnsbase.c')
-rw-r--r-- | src/dnsbase.c | 789 |
1 files changed, 789 insertions, 0 deletions
diff --git a/src/dnsbase.c b/src/dnsbase.c new file mode 100644 index 00000000..49c86f03 --- /dev/null +++ b/src/dnsbase.c @@ -0,0 +1,789 @@ +/* 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 2, 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> +#include <resolv.h> +#include "mailfrom.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 0; +} + +/* 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(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 > 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(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(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 { + char *p; + int depth; + + for (p = host, depth = 0; p && depth < maxdepth; + p++, depth++) { + *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; /* ?? */ + + 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) + if (c = *ptr++ - *name++) + return c; + if (*domain && *ptr != '.') + return *ptr - *domain; + ptr++; + while (*domain) + if (c = *ptr++ - *domain++) + return c; + 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); + if (lp->ttl > 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) + l = lp->hbsize - lp->hbcount - 1; + 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 != T_ANY && lp->atype != type) + continue; + l = cp[0]; + if (lp->hbcount + l >= lp->hbsize) + l = lp->hbsize - lp->hbcount - 1; + 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) +{ + int i; + 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(char *host, char *answer, size_t answer_size, + char *ipbuf, size_t ipbsize, unsigned long *ttl) +{ + struct loop_data ld; + char domainbuf[256] = ""; + + ld.qtype = ld.atype = T_A; + ld.name = host; + ld.name_size = strlen(host); + 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(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 domainbuf[256] = ""; + + ld.qtype = ld.atype = T_A; + ld.name = host; + ld.name_size = strlen(host); + 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) { + *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) +{ + int i; + IPBUF ipstr; + 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(char *name, + char **names, size_t maxnames, unsigned long *ttl, + char *answer, size_t answer_size) +{ + int i; + IPBUF ipstr; + char namebuf[NSIZE]; + char domainbuf[NSIZE]; + char hbuf[NSIZE]; + struct loop_data ld; + char *p; + + ld.qtype = T_TXT; + ld.atype = T_TXT; + ld.name = name; + ld.name_size = strlen(name); + 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); + + 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(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) +{ + struct in_addr ip; + char *names[11], **p; + char *vnames[10]; + size_t vi = 0; + int status; + + if (!inet_aton(ipstr, &ip)) + return dns_failure; + + status = ptr_lookup(ip, names, NELEMS(names), + NULL, NULL, 0); + if (status != dns_success) + return status; + + for (p = names; *p; p++) { + GACOPYZ_UINT32_T ipbuf[10]; + size_t ipcount; + status = a_lookup(*p, + ipbuf, NELEMS(ipbuf), + &ipcount, + NULL, NULL, 0); + if (status == dns_success && vi < NELEMS(vnames[0])) { + size_t i; + + 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 && 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; +} |