diff options
author | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
commit | 5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch) | |
tree | 8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/smtp/mxdial.c | |
parent | cd3745196389579fb78b9b01ef1daefb5a57aa71 (diff) | |
download | plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2 plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip |
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/smtp/mxdial.c')
-rw-r--r-- | src/cmd/upas/smtp/mxdial.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/src/cmd/upas/smtp/mxdial.c b/src/cmd/upas/smtp/mxdial.c new file mode 100644 index 00000000..aea5f256 --- /dev/null +++ b/src/cmd/upas/smtp/mxdial.c @@ -0,0 +1,333 @@ +#include "common.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; +}; +static Mx mx[Nmx]; + +Ndb *db; +extern int debug; + +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); +extern int cistrcmp(char*, char*); + +int +mxdial(char *addr, char *ddomain, char *gdomain) +{ + int fd; + DS ds; + char err[Errlen]; + + addr = netmkaddr(addr, 0, "smtp"); + dial_string_parse(addr, &ds); + + /* try connecting to destination or any of it's mail routers */ + fd = callmx(&ds, addr, ddomain); + + /* try our mail gateway */ + rerrstr(err, sizeof(err)); + if(fd < 0 && gdomain && strstr(err, "can't translate") != 0) { + fprint(2,"dialing %s\n",gdomain); + fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0); + } + + return fd; +} + +/* + * take an address and return all the mx entries for it, + * most preferred first + */ +static int +callmx(DS *ds, char *dest, char *domain) +{ + 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); + } + + /* 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"); + return -1; + } + } + + /* sort by preference */ + if(nmx > 1) + qsort(mx, nmx, sizeof(Mx), compar); + + /* dial each one in turn */ + for(i = 0; i < nmx; i++){ + 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); + fd = dial(addr, 0, 0, 0); + if(fd >= 0) + return fd; + } + return -1; +} + +/* + * 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) +{ + int n; + + /* 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); + } + } + + return n; +} + +static int +mxlookup1(DS *ds, char *domain) +{ + char buf[1024]; + char dnsname[Maxstring]; + char *fields[4]; + int i, n, fd, nmx; + + snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir); + + fd = open(dnsname, ORDWR); + if(fd < 0) + return 0; + + nmx = 0; + snprint(buf, sizeof(buf), "%s mx", ds->host); + if(debug) + fprint(2, "sending %s '%s'\n", dnsname, buf); + n = write(fd, buf, strlen(buf)); + 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; + + if(strchr(domain, '.') == 0) + strcpy(domain, fields[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); + } + + /* + * 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++; + } + + /* + * 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; + if(write(fd, buf, strlen(buf)) < 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--; + } + return nmx; +} + +static int +compar(void *a, void *b) +{ + return ((Mx*)a)->pref - ((Mx*)b)->pref; +} + +/* break up an address to its component parts */ +void +dial_string_parse(char *str, DS *ds) +{ + char *p, *p2; + + strncpy(ds->buf, str, sizeof(ds->buf)); + ds->buf[sizeof(ds->buf)-1] = 0; + + 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_meta(ds); +} + +#if 0 /* jpc */ +static void +expand_meta(DS *ds) +{ + 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; + } + + 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; + } + + 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; + } + 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; + + /* take only first one returned (quasi-bug) */ + if((p = strchr(ds->host, ' ')) != nil) + *p = 0; +} +#endif /* jpc */ + +static void +expand_meta(DS *ds) +{ + Ndb *db; + Ndbs s; + char *sys, *smtpserver; + + sys = sysname(); + db = ndbopen(unsharp("#9/ndb/local")); + fprint(2,"%s",ds->host); + smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil); + snprint(ds->host,128,"%s",smtpserver); + fprint(2," exanded to %s\n",ds->host); + +} |