xref: /plan9/sys/src/cmd/upas/smtp/mxdial.c (revision 7874c72cdf15e7bba44575d64a95358fcedce352)
1 #include "common.h"
2 #include <ndb.h>
3 #include <smtp.h>	/* to publish dial_string_parse */
4 
5 enum
6 {
7 	Nmx=		16,
8 	Maxstring=	256,
9 	Maxipstr=	8*5,		/* ipv6 */
10 };
11 
12 typedef struct Mx	Mx;
13 struct Mx
14 {
15 	char	host[Maxstring];
16 	char	ip[Maxipstr];		/* this is just the first ip */
17 	int	pref;
18 };
19 
20 char	*bustedmxs[Maxbustedmx];
21 Ndb *db;
22 
23 static Mx mx[Nmx];
24 
25 static int	callmx(DS*, char*, char*);
26 static int	compar(void*, void*);
27 static void	expand_meta(DS *ds);
28 static int	mxlookup(DS*, char*);
29 static int	mxlookup1(DS*, char*);
30 
31 int
mxdial(char * addr,char * ddomain,char * gdomain)32 mxdial(char *addr, char *ddomain, char *gdomain)
33 {
34 	int fd;
35 	DS ds;
36 	char err[Errlen];
37 
38 	addr = netmkaddr(addr, 0, "smtp");
39 	dial_string_parse(addr, &ds);
40 
41 	/* try connecting to destination or any of it's mail routers */
42 	fd = callmx(&ds, addr, ddomain);
43 
44 	/* try our mail gateway */
45 	rerrstr(err, sizeof(err));
46 	if(fd < 0 && gdomain && strstr(err, "can't translate") != 0)
47 		fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
48 
49 	return fd;
50 }
51 
52 static int
busted(char * mx)53 busted(char *mx)
54 {
55 	char **bmp;
56 
57 	for (bmp = bustedmxs; *bmp != nil; bmp++)
58 		if (strcmp(mx, *bmp) == 0)
59 			return 1;
60 	return 0;
61 }
62 
63 static int
timeout(void *,char * msg)64 timeout(void*, char *msg)
65 {
66 	if(strstr(msg, "alarm"))
67 		return 1;
68 	return 0;
69 }
70 
71 long
timedwrite(int fd,void * buf,long len,long ms)72 timedwrite(int fd, void *buf, long len, long ms)
73 {
74 	long n, oalarm;
75 
76 	atnotify(timeout, 1);
77 	oalarm = alarm(ms);
78 	n = write(fd, buf, len);
79 	alarm(oalarm);
80 	atnotify(timeout, 0);
81 	return n;
82 }
83 
84 static int
isloopback(char * ip)85 isloopback(char *ip)
86 {
87 	return strcmp(ip, "127.0.0.1") == 0 || strcmp(ip, "::1") == 0;
88 }
89 
90 /*
91  *  take an address and return all the mx entries for it,
92  *  most preferred first
93  */
94 static int
callmx(DS * ds,char * dest,char * domain)95 callmx(DS *ds, char *dest, char *domain)
96 {
97 	int fd, i, nmx;
98 	char *ip;
99 	char addr[Maxstring];
100 
101 	/* get a list of mx entries */
102 	nmx = mxlookup(ds, domain);
103 	if(nmx < 0){
104 		/* dns isn't working, don't just dial */
105 		return -1;
106 	}
107 	if(nmx == 0){
108 		if(debug)
109 			fprint(2, "mxlookup returns nothing\n");
110 		return dial(dest, 0, 0, 0);
111 	}
112 
113 	/* refuse to honor loopback addresses given by dns.  catch \n too. */
114 	for(i = 0; i < nmx; i++) {
115 		ip = mx[i].ip;
116 		if(strchr(ip, '\n') != nil){
117 			if(debug)
118 				fprint(2, "mxlookup ip contains newline\n");
119 			werrstr("illegal: newline in mail server ip");
120 			return -1;
121 		}else if(isloopback(ip)){
122 			if(debug)
123 				fprint(2, "mxlookup returns loopback\n");
124 			werrstr("illegal: domain lists %s as mail server", ip);
125 			return -1;
126 		}
127 	}
128 
129 	/* sort by preference */
130 	if(nmx > 1)
131 		qsort(mx, nmx, sizeof(Mx), compar);
132 
133 	/* dial each one in turn by name, not ip */
134 	for(i = 0; i < nmx; i++){
135 		if (busted(mx[i].host)) {
136 			if (debug)
137 				fprint(2, "mxdial skipping busted mx %s\n",
138 					mx[i].host);
139 			continue;
140 		}
141 		snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto,
142 			mx[i].host, ds->service);
143 		if(debug)
144 			fprint(2, "mxdial trying %s\n", addr);
145 		atnotify(timeout, 1);
146 		/* this was 10 seconds, but oclsc.org at least needs more. */
147 		alarm(60*1000);
148 		fd = dial(addr, 0, 0, 0);
149 		if (debug && fd < 0)
150 			fprint(2, "dial: %r\n");
151 		alarm(0);
152 		atnotify(timeout, 0);
153 		if(fd >= 0)
154 			return fd;
155 	}
156 	return -1;
157 }
158 
159 /*
160  *  call the dns process and have it try to resolve the mx request
161  *
162  *  this routine knows about the firewall and tries inside and outside
163  *  dns's seperately.
164  */
165 static int
mxlookup(DS * ds,char * domain)166 mxlookup(DS *ds, char *domain)
167 {
168 	int n;
169 
170 	/* just in case we find no domain name */
171 	strcpy(domain, ds->host);
172 
173 	if(ds->netdir)
174 		n = mxlookup1(ds, domain);
175 	else {
176 		ds->netdir = "/net";
177 		n = mxlookup1(ds, domain);
178 		if(n == 0) {
179 			ds->netdir = "/net.alt";
180 			n = mxlookup1(ds, domain);
181 		}
182 	}
183 
184 	return n;
185 }
186 
187 static int
mxlookup1(DS * ds,char * domain)188 mxlookup1(DS *ds, char *domain)
189 {
190 	int i, n, fd, nmx;
191 	char buf[Maxdomain], dnsname[Maxstring];
192 	char *fields[4];
193 	Mx *mxp;
194 
195 	snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir);
196 
197 	fd = open(dnsname, ORDWR);
198 	if(fd < 0)
199 		return 0;
200 
201 	nmx = 0;
202 	snprint(buf, sizeof buf, "%s mx", ds->host);
203 	if(debug)
204 		fprint(2, "sending %s '%s'\n", dnsname, buf);
205 	/*
206 	 * don't hang indefinitely in the write to /net/dns.
207 	 */
208 	n = timedwrite(fd, buf, strlen(buf), 60*1000);
209 	if(n < 0){
210 		rerrstr(buf, sizeof buf);
211 		if(debug)
212 			fprint(2, "dns: %s\n", buf);
213 		if(strstr(buf, "dns failure")){
214 			/* if dns fails for the mx lookup, we have to stop */
215 			close(fd);
216 			return -1;
217 		}
218 	} else {
219 		/*
220 		 *  get any mx entries
221 		 *  assumes one record per read
222 		 */
223 		seek(fd, 0, 0);
224 		while(nmx < Nmx && (n = read(fd, buf, sizeof buf-1)) > 0){
225 			mxp = &mx[nmx];
226 			buf[n] = 0;
227 			if(debug)
228 				fprint(2, "dns mx: %s\n", buf);
229 			n = getfields(buf, fields, 4, 1, " \t");
230 			if(n < 4)
231 				continue;
232 
233 			if(strchr(domain, '.') == 0)
234 				strcpy(domain, fields[0]);
235 
236 			strncpy(mxp->host, fields[3], sizeof mxp->host - 1);
237 			mxp->host[sizeof mxp->host - 1] = '\0';
238 			mxp->pref = atoi(fields[2]);
239 			nmx++;
240 		}
241 		if(debug)
242 			fprint(2, "dns mx: got %d mx servers\n", nmx);
243 	}
244 
245 	/*
246 	 * no mx record? try name itself.
247 	 *
248 	 * BUG? If domain has no dots, then we used to look up ds->host
249 	 * but return domain instead of ds->host in the list.  Now we return
250 	 * ds->host.  What will this break?
251 	 */
252 	if(nmx == 0){
253 		mx[0].pref = 1;
254 		strncpy(mx[0].host, ds->host, sizeof(mx[0].host));
255 		nmx++;
256 	}
257 
258 	/*
259 	 * look up first ip address of each mx name.
260 	 * should really look at all addresses.
261 	 * assumes one record per read.
262 	 */
263 	for(i = 0; i < nmx; i++){
264 		mxp = &mx[i];
265 		seek(fd, 0, 0);
266 		snprint(buf, sizeof buf, "%s ip", mxp->host);
267 		mxp->ip[0] = 0;
268 		/*
269 		 * don't hang indefinitely in the write to /net/dns.
270 		 */
271 		if(timedwrite(fd, buf, strlen(buf), 60*1000) < 0)
272 			goto no;
273 		seek(fd, 0, 0);
274 		if((n = read(fd, buf, sizeof buf-1)) < 0)
275 			goto no;
276 		buf[n] = 0;
277 		if(getfields(buf, fields, 4, 1, " \t") < 3)
278 			goto no;
279 		strncpy(mxp->ip, fields[2], sizeof mxp->ip - 1);
280 		mxp->ip[sizeof mxp->ip - 1] = '\0';
281 		continue;
282 
283 	no:
284 		/* remove mx[i] and go around again */
285 		nmx--;
286 		*mxp = mx[nmx];
287 		i--;
288 	}
289 	close(fd);
290 	return nmx;
291 }
292 
293 static int
compar(void * a,void * b)294 compar(void *a, void *b)
295 {
296 	return ((Mx*)a)->pref - ((Mx*)b)->pref;
297 }
298 
299 /* break up an address to its component parts */
300 void
dial_string_parse(char * str,DS * ds)301 dial_string_parse(char *str, DS *ds)
302 {
303 	char *p, *p2;
304 
305 	strncpy(ds->buf, str, sizeof(ds->buf));
306 	ds->buf[sizeof(ds->buf)-1] = 0;
307 
308 	p = strchr(ds->buf, '!');
309 	if(p == 0) {
310 		ds->netdir = 0;
311 		ds->proto = "net";
312 		ds->host = ds->buf;
313 	} else {
314 		if(*ds->buf != '/'){
315 			ds->netdir = 0;
316 			ds->proto = ds->buf;
317 		} else {
318 			for(p2 = p; *p2 != '/'; p2--)
319 				;
320 			*p2++ = 0;
321 			ds->netdir = ds->buf;
322 			ds->proto = p2;
323 		}
324 		*p = 0;
325 		ds->host = p + 1;
326 	}
327 	ds->service = strchr(ds->host, '!');
328 	if(ds->service)
329 		*ds->service++ = 0;
330 	if(*ds->host == '$')
331 		expand_meta(ds);
332 }
333 
334 static void
expand_meta(DS * ds)335 expand_meta(DS *ds)
336 {
337 	char buf[128], cs[128], *net, *p;
338 	int fd, n;
339 
340 	net = ds->netdir;
341 	if(!net)
342 		net = "/net";
343 
344 	if(debug)
345 		fprint(2, "expanding %s!%s\n", net, ds->host);
346 	snprint(cs, sizeof(cs), "%s/cs", net);
347 	if((fd = open(cs, ORDWR)) == -1){
348 		if(debug)
349 			fprint(2, "open %s: %r\n", cs);
350 		syslog(0, "smtp", "cannot open %s: %r", cs);
351 		return;
352 	}
353 
354 	snprint(buf, sizeof buf, "!ipinfo %s", ds->host+1);	// +1 to skip $
355 	if(write(fd, buf, strlen(buf)) <= 0){
356 		if(debug)
357 			fprint(2, "write %s: %r\n", cs);
358 		syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs);
359 		close(fd);
360 		return;
361 	}
362 
363 	seek(fd, 0, 0);
364 	if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){
365 		if(debug)
366 			fprint(2, "read %s: %r\n", cs);
367 		syslog(0, "smtp", "%s - read failed: %r", cs);
368 		close(fd);
369 		return;
370 	}
371 	close(fd);
372 
373 	ds->expand[n] = 0;
374 	if((p = strchr(ds->expand, '=')) == nil){
375 		if(debug)
376 			fprint(2, "response %s: %s\n", cs, ds->expand);
377 		syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs);
378 		return;
379 	}
380 	ds->host = p+1;
381 
382 	/* take only first one returned (quasi-bug) */
383 	if((p = strchr(ds->host, ' ')) != nil)
384 		*p = 0;
385 }
386