diff options
Diffstat (limited to 'sys/src/cmd/upas/smtp/mxdial.c')
-rw-r--r-- | sys/src/cmd/upas/smtp/mxdial.c | 534 |
1 files changed, 256 insertions, 278 deletions
diff --git a/sys/src/cmd/upas/smtp/mxdial.c b/sys/src/cmd/upas/smtp/mxdial.c index c17e280c3..74516a030 100644 --- a/sys/src/cmd/upas/smtp/mxdial.c +++ b/sys/src/cmd/upas/smtp/mxdial.c @@ -1,60 +1,96 @@ #include "common.h" +#include "smtp.h" #include <ndb.h> -#include <smtp.h> /* to publish dial_string_parse */ - -enum -{ - Nmx= 16, - Maxstring= 256, -}; - -typedef struct Mx Mx; -struct Mx -{ - char host[256]; - char ip[24]; - int pref; -}; char *bustedmxs[Maxbustedmx]; -Ndb *db; - -static int mxlookup(DS*, char*); -static int mxlookup1(DS*, char*); -static int compar(void*, void*); -static int callmx(DS*, char*, char*); -static void expand_meta(DS *ds); - -static Mx mx[Nmx]; -int -mxdial(char *addr, char *ddomain, char *gdomain) +static void +expand(DS *ds) { - int fd; - DS ds; - - addr = netmkaddr(addr, 0, "smtp"); - dial_string_parse(addr, &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); +} - /* try connecting to destination or any of it's mail routers */ - fd = callmx(&ds, addr, ddomain); +/* break up an address to its component parts */ +void +dialstringparse(char *str, DS *ds) +{ + char *p, *p2; - /* try our mail gateway */ - if(fd < 0 && gdomain) - fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0); + 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); +} - return fd; +void +mxtabfree(Mxtab *mx) +{ + free(mx->mx); + memset(mx, 0, sizeof *mx); } -static int -busted(char *mx) +static void +mxtabrealloc(Mxtab *mx) { - char **bmp; + 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"); +} - for (bmp = bustedmxs; *bmp != nil; bmp++) - if (strcmp(mx, *bmp) == 0) - return 1; - return 0; +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 @@ -65,293 +101,235 @@ timeout(void*, char *msg) return 0; } -long +static long timedwrite(int fd, void *buf, long len, long ms) { long n, oalarm; atnotify(timeout, 1); oalarm = alarm(ms); - n = write(fd, buf, len); + n = pwrite(fd, buf, len, 0); alarm(oalarm); atnotify(timeout, 0); return n; } -/* - * take an address and return all the mx entries for it, - * most preferred first - */ static int -callmx(DS *ds, char *dest, char *domain) +dnslookup(Mxtab *mx, int fd, char *query, char *domain, char *net, int pref0) { - int fd, i, nmx; - char addr[Maxstring]; - - /* get a list of mx entries */ - nmx = mxlookup(ds, domain); - if(nmx < 0){ - /* dns isn't working, don't just dial */ - return -1; - } - if(nmx == 0){ - if(debug) - fprint(2, "mxlookup returns nothing\n"); - return dial(dest, 0, 0, 0); - } + int n; + char buf[1024], *f[4]; - /* refuse to honor loopback addresses given by dns */ - for(i = 0; i < nmx; i++) - if(strcmp(mx[i].ip, "127.0.0.1") == 0){ - if(debug) - fprint(2, "mxlookup returns loopback\n"); - werrstr("illegal: domain lists 127.0.0.1 as mail server"); + 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; + } - /* sort by preference */ - if(nmx > 1) - qsort(mx, nmx, sizeof(Mx), compar); - - /* dial each one in turn */ - for(i = 0; i < nmx; i++){ - if (busted(mx[i].host)) { - if (debug) - fprint(2, "mxdial skipping busted mx %s\n", - mx[i].host); + 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); } - snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto, - mx[i].host, ds->service); - if(debug) - fprint(2, "mxdial trying %s\n", addr); - atnotify(timeout, 1); - alarm(10*1000); - fd = dial(addr, 0, 0, 0); - alarm(0); - atnotify(timeout, 0); - if(fd >= 0) - return fd; } - return -1; + + return 0; } -/* - * call the dns process and have it try to resolve the mx request - * - * this routine knows about the firewall and tries inside and outside - * dns's seperately. - */ static int -mxlookup(DS *ds, char *domain) +busted(char *mx) { - int n; + char **bmp; - /* just in case we find no domain name */ - strcpy(domain, ds->host); - - if(ds->netdir) - n = mxlookup1(ds, domain); - else { - ds->netdir = "/net"; - n = mxlookup1(ds, domain); - if(n <= 0) { - ds->netdir = "/net.alt"; - n = mxlookup1(ds, domain); - } - } + for (bmp = bustedmxs; *bmp != nil; bmp++) + if (strcmp(mx, *bmp) == 0) + return 1; + return 0; +} - return n; +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 -mxlookup1(DS *ds, char *domain) +okaymx(Mxtab *mx, char *domain) { - int i, n, fd, nmx; - char buf[1024], dnsname[Maxstring]; - char *fields[4]; + 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; +} - snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir); +static int +lookup(Mxtab *mx, char *net, char *host, char *domain, char *type) +{ + char dns[128], buf[1024]; + int fd, i; + Mx *x; - fd = open(dnsname, ORDWR); - if(fd < 0) - return 0; + snprint(dns, sizeof dns, "%s/dns", net); + fd = open(dns, ORDWR); + if(fd == -1) + return -1; - nmx = 0; - snprint(buf, sizeof buf, "%s mx", ds->host); - if(debug) - fprint(2, "sending %s '%s'\n", dnsname, buf); - /* - * don't hang indefinitely in the write to /net/dns. - */ - n = timedwrite(fd, buf, strlen(buf), 60*1000); - if(n < 0){ - rerrstr(buf, sizeof buf); - if(debug) - fprint(2, "dns: %s\n", buf); - if(strstr(buf, "dns failure")){ - /* if dns fails for the mx lookup, we have to stop */ - close(fd); - return -1; - } - } else { - /* - * get any mx entries - */ - seek(fd, 0, 0); - while(nmx < Nmx && (n = read(fd, buf, sizeof buf-1)) > 0){ - buf[n] = 0; - if(debug) - fprint(2, "dns mx: %s\n", buf); - n = getfields(buf, fields, 4, 1, " \t"); - if(n < 4) - continue; + snprint(buf, sizeof buf, "%s %s", host, type); + dprint("sending %s '%s'\n", dns, buf); + dnslookup(mx, fd, buf, domain, net, 10000); - if(strchr(domain, '.') == 0) - strcpy(domain, fields[0]); + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + if(x->ip[0] != 0) + continue; + x->valid = 0; - strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1); - mx[nmx].pref = atoi(fields[2]); - nmx++; - } - if(debug) - fprint(2, "dns mx; got %d entries\n", nmx); + snprint(buf, sizeof buf, "%s %s", x->host, "ip"); + dprint("sending %s '%s'\n", dns, buf); + dnslookup(mx, fd, buf, domain, net, x->pref); } - /* - * no mx record? try name itself. - */ - /* - * BUG? If domain has no dots, then we used to look up ds->host - * but return domain instead of ds->host in the list. Now we return - * ds->host. What will this break? - */ - if(nmx == 0){ - mx[0].pref = 1; - strncpy(mx[0].host, ds->host, sizeof(mx[0].host)); - nmx++; - } + close(fd); - /* - * look up all ip addresses - */ - for(i = 0; i < nmx; i++){ - seek(fd, 0, 0); - snprint(buf, sizeof buf, "%s ip", mx[i].host); - mx[i].ip[0] = 0; - /* - * don't hang indefinitely in the write to /net/dns. - */ - if(timedwrite(fd, buf, strlen(buf), 60*1000) < 0) - goto no; - seek(fd, 0, 0); - if((n = read(fd, buf, sizeof buf-1)) < 0) - goto no; - buf[n] = 0; - if(getfields(buf, fields, 4, 1, " \t") < 3) - goto no; - strncpy(mx[i].ip, fields[2], sizeof(mx[i].ip)-1); - continue; - - no: - /* remove mx[i] and go around again */ - nmx--; - mx[i] = mx[nmx]; - i--; + 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 nmx; -} -static int -compar(void *a, void *b) -{ - return ((Mx*)a)->pref - ((Mx*)b)->pref; + return 0; } -/* break up an address to its component parts */ -void -dial_string_parse(char *str, DS *ds) +static int +lookcall(Mxtab *mx, DS *d, char *domain, char *type) { - char *p, *p2; - - strncpy(ds->buf, str, sizeof(ds->buf)); - ds->buf[sizeof(ds->buf)-1] = 0; + 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; + } - 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; + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + if(x->ip[0] == 0 || x->valid == 0){ + x->valid = 0; + continue; } - *p = 0; - ds->host = p + 1; + 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; } - ds->service = strchr(ds->host, '!'); - if(ds->service) - *ds->service++ = 0; - if(*ds->host == '$') - expand_meta(ds); + + return -1; } -static void -expand_meta(DS *ds) +int +mxdial0(char *addr, char *ddomain, char *gdomain, Mxtab *mx) { - char buf[128], cs[128], *net, *p; - int fd, n; - - net = ds->netdir; - if(!net) - net = "/net"; - - if(debug) - fprint(2, "expanding %s!%s\n", net, ds->host); - snprint(cs, sizeof(cs), "%s/cs", net); - if((fd = open(cs, ORDWR)) == -1){ - if(debug) - fprint(2, "open %s: %r\n", cs); - syslog(0, "smtp", "cannot open %s: %r", cs); - return; - } + int nd, i, j; + DS *d; + static char *tab[] = {"mx", "ip", }; - snprint(buf, sizeof buf, "!ipinfo %s", ds->host+1); // +1 to skip $ - if(write(fd, buf, strlen(buf)) <= 0){ - if(debug) - fprint(2, "write %s: %r\n", cs); - syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs); - close(fd); - return; + 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; } - seek(fd, 0, 0); - if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){ - if(debug) - fprint(2, "read %s: %r\n", cs); - syslog(0, "smtp", "%s - read failed: %r", cs); - close(fd); - return; + /* 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; } - close(fd); - ds->expand[n] = 0; - if((p = strchr(ds->expand, '=')) == nil){ - if(debug) - fprint(2, "response %s: %s\n", cs, ds->expand); - syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs); - return; - } - ds->host = p+1; + return -1; +} - /* take only first one returned (quasi-bug) */ - if((p = strchr(ds->host, ' ')) != nil) - *p = 0; +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; } |