123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- #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)
- fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
- return fd;
- }
- static int
- timeout(void*, char *msg)
- {
- 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);
- /* 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);
- atnotify(timeout, 1);
- alarm(10*1000);
- fd = dial(addr, 0, 0, 0);
- alarm(0);
- atnotify(timeout, 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)
- {
- int i, n, fd, nmx;
- long oalarm;
- char buf[1024], dnsname[Maxstring];
- char *fields[4];
- 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);
- /*
- * don't hang indefinitely in the write to /net/dns.
- */
- atnotify(timeout, 1);
- oalarm = alarm(60*1000);
- n = write(fd, buf, strlen(buf));
- alarm(oalarm);
- atnotify(timeout, 0);
- 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);
- }
- 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;
- }
|