plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

mxdial.c (5446B)


      1 #include "common.h"
      2 #include <ndb.h>
      3 #include "smtp.h"	/* to publish dial_string_parse */
      4 #include <ip.h>
      5 #include <thread.h>
      6 
      7 enum
      8 {
      9 	Nmx=	16,
     10 	Maxstring=	256
     11 };
     12 
     13 typedef struct Mx	Mx;
     14 struct Mx
     15 {
     16 	char host[256];
     17 	char ip[24];
     18 	int pref;
     19 };
     20 static Mx mx[Nmx];
     21 
     22 Ndb *db;
     23 extern int debug;
     24 
     25 static int	mxlookup(DS*, char*);
     26 static int	compar(const void*, const void*);
     27 static int	callmx(DS*, char*, char*);
     28 static void expand_meta(DS *ds);
     29 extern int	cistrcmp(char*, char*);
     30 
     31 /* Taken from imapdial, replaces tlsclient call with stunnel */
     32 static int
     33 smtpdial(char *server)
     34 {
     35 	int p[2];
     36 	int fd[3];
     37 	char *tmp;
     38 	char *fpath;
     39 
     40 	if(pipe(p) < 0)
     41 		return -1;
     42 	fd[0] = dup(p[0], -1);
     43 	fd[1] = dup(p[0], -1);
     44 	fd[2] = dup(2, -1);
     45 #ifdef PLAN9PORT
     46 	tmp = smprint("%s:587", server);
     47 	fpath = searchpath("stunnel3");
     48 	if (!fpath) {
     49 		werrstr("stunnel not found. it is required for tls support.");
     50 		return -1;
     51 	}
     52 	if(threadspawnl(fd, fpath, "stunnel", "-n", "smtp" , "-c", "-r", tmp, nil) < 0) {
     53 #else
     54 	tmp = smprint("tcp!%s!587", server);
     55 	if(threadspawnl(fd, "/bin/tlsclient", "tlsclient", tmp, nil) < 0){
     56 #endif
     57 		free(tmp);
     58 		close(p[0]);
     59 		close(p[1]);
     60 		close(fd[0]);
     61 		close(fd[1]);
     62 		close(fd[2]);
     63 		return -1;
     64 	}
     65 	free(tmp);
     66 	close(p[0]);
     67 	return p[1];
     68 }
     69 
     70 int
     71 mxdial(char *addr, char *ddomain, char *gdomain)
     72 {
     73 	int fd;
     74 	DS ds;
     75 	char err[Errlen];
     76 
     77 	addr = netmkaddr(addr, 0, "smtp");
     78 	dial_string_parse(addr, &ds);
     79 
     80 	/* try connecting to destination or any of it's mail routers */
     81 	fd = callmx(&ds, addr, ddomain);
     82 
     83 	/* try our mail gateway */
     84 	rerrstr(err, sizeof(err));
     85 	if(fd < 0 && gdomain && strstr(err, "can't translate") != 0)
     86 		fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
     87 
     88 	return fd;
     89 }
     90 
     91 static int
     92 timeout(void *v, char *msg)
     93 {
     94 	USED(v);
     95 
     96 	if(strstr(msg, "alarm"))
     97 		return 1;
     98 	return 0;
     99 }
    100 
    101 /*
    102  *  take an address and return all the mx entries for it,
    103  *  most preferred first
    104  */
    105 static int
    106 callmx(DS *ds, char *dest, char *domain)
    107 {
    108 	int fd, i, nmx;
    109 	char addr[Maxstring];
    110 
    111 	/* get a list of mx entries */
    112 	nmx = mxlookup(ds, domain);
    113 	if(nmx < 0){
    114 		/* dns isn't working, don't just dial */
    115 		return -1;
    116 	}
    117 	if(nmx == 0){
    118 		if(debug)
    119 			fprint(2, "mxlookup returns nothing\n");
    120 		return dial(dest, 0, 0, 0);
    121 	}
    122 
    123 	/* refuse to honor loopback addresses given by dns */
    124 	for(i = 0; i < nmx; i++){
    125 		if(strcmp(mx[i].ip, "127.0.0.1") == 0){
    126 			if(debug)
    127 				fprint(2, "mxlookup returns loopback\n");
    128 			werrstr("illegal: domain lists 127.0.0.1 as mail server");
    129 			return -1;
    130 		}
    131 	}
    132 
    133 	/* sort by preference */
    134 	if(nmx > 1)
    135 		qsort(mx, nmx, sizeof(Mx), compar);
    136 
    137 	if(debug){
    138 		for(i=0; i<nmx; i++)
    139 			print("%s %d\n", mx[i].host, mx[i].pref);
    140 	}
    141 	/* dial each one in turn */
    142 	for(i = 0; i < nmx; i++){
    143 #ifdef PLAN9PORT
    144 		snprint(addr, sizeof(addr), "%s", mx[i].host);
    145 #else
    146 		snprint(addr, sizeof(addr), "%s!%s!%s", ds->proto,
    147 			mx[i].host, ds->service);
    148 #endif
    149 		if(debug)
    150 			fprint(2, "mxdial trying %s (%d)\n", addr, i);
    151 		atnotify(timeout, 1);
    152 		alarm(10*1000);
    153 #ifdef PLAN9PORT
    154 		fd = smtpdial(addr);
    155 #else
    156 		fd = dial(addr, 0, 0, 0);
    157 #endif
    158 		alarm(0);
    159 		atnotify(timeout, 0);
    160 		if(fd >= 0)
    161 			return fd;
    162 	}
    163 	return -1;
    164 }
    165 
    166 /*
    167  *  use dns to resolve the mx request
    168  */
    169 static int
    170 mxlookup(DS *ds, char *domain)
    171 {
    172 	int i, n, nmx;
    173 	Ndbtuple *t, *tmx, *tpref, *tip;
    174 
    175 	strcpy(domain, ds->host);
    176 	ds->netdir = "/net";
    177 	nmx = 0;
    178 	if((t = dnsquery(nil, ds->host, "mx")) != nil){
    179 		for(tmx=t; (tmx=ndbfindattr(tmx->entry, nil, "mx")) != nil && nmx<Nmx; ){
    180 			for(tpref=tmx->line; tpref != tmx; tpref=tpref->line){
    181 				if(strcmp(tpref->attr, "pref") == 0){
    182 					strncpy(mx[nmx].host, tmx->val, sizeof(mx[n].host)-1);
    183 					mx[nmx].pref = atoi(tpref->val);
    184 					nmx++;
    185 					break;
    186 				}
    187 			}
    188 		}
    189 		ndbfree(t);
    190 	}
    191 
    192 	/*
    193 	 * no mx record? try name itself.
    194 	 */
    195 	/*
    196 	 * BUG? If domain has no dots, then we used to look up ds->host
    197 	 * but return domain instead of ds->host in the list.  Now we return
    198 	 * ds->host.  What will this break?
    199 	 */
    200 	if(nmx == 0){
    201 		mx[0].pref = 1;
    202 		strncpy(mx[0].host, ds->host, sizeof(mx[0].host));
    203 		nmx++;
    204 	}
    205 
    206 	/*
    207 	 * look up all ip addresses
    208 	 */
    209 	for(i = 0; i < nmx; i++){
    210 		if((t = dnsquery(nil, mx[i].host, "ip")) == nil)
    211 			goto no;
    212 		if((tip = ndbfindattr(t, nil, "ip")) == nil){
    213 			ndbfree(t);
    214 			goto no;
    215 		}
    216 		strncpy(mx[i].ip, tip->val, sizeof(mx[i].ip)-1);
    217 		ndbfree(t);
    218 		continue;
    219 
    220 	no:
    221 		/* remove mx[i] and go around again */
    222 		nmx--;
    223 		mx[i] = mx[nmx];
    224 		i--;
    225 	}
    226 	return nmx;
    227 }
    228 
    229 static int
    230 compar(const void *a, const void *b)
    231 {
    232 	return ((Mx*)a)->pref - ((Mx*)b)->pref;
    233 }
    234 
    235 /* break up an address to its component parts */
    236 void
    237 dial_string_parse(char *str, DS *ds)
    238 {
    239 	char *p, *p2;
    240 
    241 	strncpy(ds->buf, str, sizeof(ds->buf));
    242 	ds->buf[sizeof(ds->buf)-1] = 0;
    243 
    244 	p = strchr(ds->buf, '!');
    245 	if(p == 0) {
    246 		ds->netdir = 0;
    247 		ds->proto = "net";
    248 		ds->host = ds->buf;
    249 	} else {
    250 		if(*ds->buf != '/'){
    251 			ds->netdir = 0;
    252 			ds->proto = ds->buf;
    253 		} else {
    254 			for(p2 = p; *p2 != '/'; p2--)
    255 				;
    256 			*p2++ = 0;
    257 			ds->netdir = ds->buf;
    258 			ds->proto = p2;
    259 		}
    260 		*p = 0;
    261 		ds->host = p + 1;
    262 	}
    263 	ds->service = strchr(ds->host, '!');
    264 	if(ds->service)
    265 		*ds->service++ = 0;
    266 	if(*ds->host == '$')
    267 		expand_meta(ds);
    268 }
    269 
    270 static void
    271 expand_meta(DS *ds)
    272 {
    273 	static Ndb *db;
    274 	Ndbs s;
    275 	char *sys, *smtpserver;
    276 
    277 	/* can't ask cs, so query database directly. */
    278 	sys = sysname();
    279 	if(db == nil)
    280 		db = ndbopen(0);
    281 	smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil);
    282 	snprint(ds->host, 128, "%s", smtpserver);
    283 }