summaryrefslogtreecommitdiff
path: root/sys/src/cmd/upas/smtp/mxdial.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/src/cmd/upas/smtp/mxdial.c')
-rw-r--r--sys/src/cmd/upas/smtp/mxdial.c534
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;
}