| #include "common.h" |
| #include <ndb.h> |
| #include "smtp.h" /* to publish dial_string_parse */ |
| #include <ip.h> |
| |
| 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 compar(const void*, const 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) |
| fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0); |
| |
| return fd; |
| } |
| |
| static int |
| timeout(void *v, char *msg) |
| { |
| USED(v); |
| |
| if(strstr(msg, "alarm")) |
| return 1; |
| return 0; |
| } |
| |
| /* |
| * 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); |
| |
| if(debug){ |
| for(i=0; i<nmx; i++) |
| print("%s %d\n", mx[i].host, mx[i].pref); |
| } |
| /* dial each one in turn */ |
| for(i = 0; i < nmx; i++){ |
| snprint(addr, sizeof(addr), "%s!%s!%s", ds->proto, |
| mx[i].host, ds->service); |
| if(debug) |
| fprint(2, "mxdial trying %s (%d)\n", addr, i); |
| atnotify(timeout, 1); |
| alarm(10*1000); |
| fd = dial(addr, 0, 0, 0); |
| alarm(0); |
| atnotify(timeout, 0); |
| if(fd >= 0) |
| return fd; |
| } |
| return -1; |
| } |
| |
| /* |
| * use dns to resolve the mx request |
| */ |
| static int |
| mxlookup(DS *ds, char *domain) |
| { |
| int i, n, nmx; |
| Ndbtuple *t, *tmx, *tpref, *tip; |
| |
| strcpy(domain, ds->host); |
| ds->netdir = "/net"; |
| nmx = 0; |
| if((t = dnsquery(nil, ds->host, "mx")) != nil){ |
| for(tmx=t; (tmx=ndbfindattr(tmx->entry, nil, "mx")) != nil && nmx<Nmx; ){ |
| for(tpref=tmx->line; tpref != tmx; tpref=tpref->line){ |
| if(strcmp(tpref->attr, "pref") == 0){ |
| strncpy(mx[nmx].host, tmx->val, sizeof(mx[n].host)-1); |
| mx[nmx].pref = atoi(tpref->val); |
| nmx++; |
| break; |
| } |
| } |
| } |
| ndbfree(t); |
| } |
| |
| /* |
| * 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++){ |
| if((t = dnsquery(nil, mx[i].host, "ip")) == nil) |
| goto no; |
| if((tip = ndbfindattr(t, nil, "ip")) == nil){ |
| ndbfree(t); |
| goto no; |
| } |
| strncpy(mx[i].ip, tip->val, sizeof(mx[i].ip)-1); |
| ndbfree(t); |
| continue; |
| |
| no: |
| /* remove mx[i] and go around again */ |
| nmx--; |
| mx[i] = mx[nmx]; |
| i--; |
| } |
| return nmx; |
| } |
| |
| static int |
| compar(const void *a, const 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); |
| } |
| |
| static void |
| expand_meta(DS *ds) |
| { |
| static Ndb *db; |
| Ndbs s; |
| char *sys, *smtpserver; |
| |
| /* can't ask cs, so query database directly. */ |
| sys = sysname(); |
| if(db == nil) |
| db = ndbopen(0); |
| smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil); |
| snprint(ds->host, 128, "%s", smtpserver); |
| } |