blob: f3a2a20901dc06ee279604643d68410c1a04306a [file] [log] [blame]
#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);
}