#include "common.h" #include "smtp.h" #include char *bustedmxs[Maxbustedmx]; static void expand(DS *ds) { char *s; Ndbtuple *t; s = ds->host + 1; t = csipinfo(ds->netdir, "sys", sysname(), &s, 1); if(t != nil){ strecpy(ds->expand, ds->expand+sizeof ds->expand, t->val); ds->host = ds->expand; } ndbfree(t); } /* break up an address to its component parts */ void dialstringparse(char *str, DS *ds) { char *p, *p2; strecpy(ds->buf, ds->buf + sizeof ds->buf, str); p = strchr(ds->buf, '!'); if(p == 0) { ds->netdir = 0; ds->proto = "net"; ds->host = ds->buf; } else { if(*ds->buf != '/'){ ds->netdir = 0; ds->proto = ds->buf; } else { for(p2 = p; *p2 != '/'; p2--) ; *p2++ = 0; ds->netdir = ds->buf; ds->proto = p2; } *p = 0; ds->host = p + 1; } ds->service = strchr(ds->host, '!'); if(ds->service) *ds->service++ = 0; if(*ds->host == '$') expand(ds); } void mxtabfree(Mxtab *mx) { free(mx->mx); memset(mx, 0, sizeof *mx); } static void mxtabrealloc(Mxtab *mx) { if(mx->nmx < mx->amx) return; if(mx->amx == 0) mx->amx = 1; mx->amx <<= 1; mx->mx = realloc(mx->mx, sizeof mx->mx[0] * mx->amx); if(mx->mx == nil) sysfatal("no memory for mx"); } static void mxtabadd(Mxtab *mx, char *host, char *ip, char *net, int pref) { int i; Mx *x; mxtabrealloc(mx); x = mx->mx; for(i = mx->nmx; i>0 && x[i-1].pref>pref && x[i-1].netdir == net; i--) x[i] = x[i-1]; strecpy(x[i].host, x[i].host + sizeof x[i].host, host); if(ip != nil) strecpy(x[i].ip, x[i].ip + sizeof x[i].ip, ip); else x[i].ip[0] = 0; x[i].netdir = net; x[i].pref = pref; x[i].valid = 1; mx->nmx++; } static int timeout(void*, char *msg) { if(strstr(msg, "alarm")) return 1; return 0; } static long timedwrite(int fd, void *buf, long len, long ms) { long n, oalarm; atnotify(timeout, 1); oalarm = alarm(ms); n = pwrite(fd, buf, len, 0); alarm(oalarm); atnotify(timeout, 0); return n; } static int dnslookup(Mxtab *mx, int fd, char *query, char *domain, char *net, int pref0) { int n; char buf[1024], *f[4]; n = timedwrite(fd, query, strlen(query), 60*1000); if(n < 0){ rerrstr(buf, sizeof buf); dprint("dns: %s\n", buf); if(strstr(buf, "dns failure")){ /* if dns fails for the mx lookup, we have to stop */ close(fd); return -1; } return 0; } seek(fd, 0, 0); for(;;){ if((n = read(fd, buf, sizeof buf - 1)) < 1) break; buf[n] = 0; // chat("dns: %s\n", buf); n = tokenize(buf, f, nelem(f)); if(n < 2) continue; if(strcmp(f[1], "mx") == 0 && n == 4){ if(strchr(domain, '.') == 0) strcpy(domain, f[0]); mxtabadd(mx, f[3], nil, net, atoi(f[2])); } else if (strcmp(f[1], "ip") == 0 && n == 3){ if(strchr(domain, '.') == 0) strcpy(domain, f[0]); mxtabadd(mx, f[0], f[2], net, pref0); } } return 0; } static int busted(char *mx) { char **bmp; for (bmp = bustedmxs; *bmp != nil; bmp++) if (strcmp(mx, *bmp) == 0) return 1; return 0; } static void complain(Mxtab *mx, char *domain) { char buf[1024], *e, *p; int i; p = buf; e = buf + sizeof buf; for(i = 0; i < mx->nmx; i++) p = seprint(p, e, "%s ", mx->mx[i].ip); syslog(0, "smtpd.mx", "loopback for %s %s", domain, buf); } static int okaymx(Mxtab *mx, char *domain) { int i; Mx *x; /* look for malicious dns entries; TODO use badcidr in ../spf/ to catch more than ip4 */ for(i = 0; i < mx->nmx; i++){ x = mx->mx + i; if(x->valid && strcmp(x->ip, "127.0.0.1") == 0){ dprint("illegal: domain %s lists 127.0.0.1 as mail server", domain); complain(mx, domain); werrstr("illegal: domain %s lists 127.0.0.1 as mail server", domain); return -1; } if(x->valid && busted(x->host)){ dprint("lookup: skipping busted mx %s\n", x->host); x->valid = 0; } } return 0; } static int lookup(Mxtab *mx, char *net, char *host, char *domain, char *type) { char dns[128], buf[1024]; int fd, i; Mx *x; snprint(dns, sizeof dns, "%s/dns", net); fd = open(dns, ORDWR); if(fd == -1) return -1; snprint(buf, sizeof buf, "%s %s", host, type); dprint("sending %s '%s'\n", dns, buf); dnslookup(mx, fd, buf, domain, net, 10000); for(i = 0; i < mx->nmx; i++){ x = mx->mx + i; if(x->ip[0] != 0) continue; x->valid = 0; snprint(buf, sizeof buf, "%s %s", x->host, "ip"); dprint("sending %s '%s'\n", dns, buf); dnslookup(mx, fd, buf, domain, net, x->pref); } close(fd); if(strcmp(type, "mx") == 0){ if(okaymx(mx, domain) == -1) return -1; for(i = 0; i < mx->nmx; i++){ x = mx->mx + i; dprint("mx list: %s %d %s\n", x->host, x->pref, x->ip); } dprint("\n"); } return 0; } static int lookcall(Mxtab *mx, DS *d, char *domain, char *type) { char buf[1024]; int i; Mx *x; if(lookup(mx, d->netdir, d->host, domain, type) == -1){ for(i = 0; i < mx->nmx; i++) if(mx->mx[i].netdir == d->netdir) mx->mx[i].valid = 0; return -1; } for(i = 0; i < mx->nmx; i++){ x = mx->mx + i; if(x->ip[0] == 0 || x->valid == 0){ x->valid = 0; continue; } snprint(buf, sizeof buf, "%s/%s!%s!%s", d->netdir, d->proto, x->ip /*x->host*/, d->service); dprint("mxdial trying %s [%s]\n", x->host, buf); atnotify(timeout, 1); alarm(10*1000); mx->fd = dial(buf, 0, 0, 0); alarm(0); atnotify(timeout, 0); if(mx->fd >= 0){ mx->pmx = i; return mx->fd; } dprint(" failed %r\n"); x->valid = 0; } return -1; } int mxdial0(char *addr, char *ddomain, char *gdomain, Mxtab *mx) { int nd, i, j; DS *d; static char *tab[] = {"mx", "ip", }; dprint("mxdial(%s, %s, %s, mx)\n", addr, ddomain, gdomain); memset(mx, 0, sizeof *mx); addr = netmkaddr(addr, 0, "smtp"); d = mx->ds; dialstringparse(addr, d + 0); nd = 1; if(d[0].netdir == nil){ d[1] = d[0]; d[0].netdir = "/net"; d[1].netdir = "/net.alt"; nd = 2; } /* search all networks for mx records; then ip records */ for(j = 0; j < nelem(tab); j++) for(i = 0; i < nd; i++) if(lookcall(mx, d + i, ddomain, tab[j]) != -1) return mx->fd; /* grotty: try gateway machine by ip only (fixme: try cs lookup) */ if(gdomain != nil){ dialstringparse(netmkaddr(gdomain, 0, "smtp"), d + 0); if(lookcall(mx, d + 0, gdomain, "ip") != -1) return mx->fd; } return -1; } int mxdial(char *addr, char *ddomain, char *gdomain, Mx *x) { int fd; Mxtab mx; memset(x, 0, sizeof *x); fd = mxdial0(addr, ddomain, gdomain, &mx); if(fd >= 0 && mx.pmx >= 0) *x = mx.mx[mx.pmx]; mxtabfree(&mx); return fd; }