Blob


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>
7 enum
8 {
9 Nmx= 16,
10 Maxstring= 256
11 };
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];
22 Ndb *db;
23 extern int debug;
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*);
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;
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 }
70 int
71 mxdial(char *addr, char *ddomain, char *gdomain)
72 {
73 int fd;
74 DS ds;
75 char err[Errlen];
77 addr = netmkaddr(addr, 0, "smtp");
78 dial_string_parse(addr, &ds);
80 /* try connecting to destination or any of it's mail routers */
81 fd = callmx(&ds, addr, ddomain);
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);
88 return fd;
89 }
91 static int
92 timeout(void *v, char *msg)
93 {
94 USED(v);
96 if(strstr(msg, "alarm"))
97 return 1;
98 return 0;
99 }
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)
108 int fd, i, nmx;
109 char addr[Maxstring];
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;
117 if(nmx == 0){
118 if(debug)
119 fprint(2, "mxlookup returns nothing\n");
120 return dial(dest, 0, 0, 0);
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;
133 /* sort by preference */
134 if(nmx > 1)
135 qsort(mx, nmx, sizeof(Mx), compar);
137 if(debug){
138 for(i=0; i<nmx; i++)
139 print("%s %d\n", mx[i].host, mx[i].pref);
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;
163 return -1;
166 /*
167 * use dns to resolve the mx request
168 */
169 static int
170 mxlookup(DS *ds, char *domain)
172 int i, n, nmx;
173 Ndbtuple *t, *tmx, *tpref, *tip;
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;
189 ndbfree(t);
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++;
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;
216 strncpy(mx[i].ip, tip->val, sizeof(mx[i].ip)-1);
217 ndbfree(t);
218 continue;
220 no:
221 /* remove mx[i] and go around again */
222 nmx--;
223 mx[i] = mx[nmx];
224 i--;
226 return nmx;
229 static int
230 compar(const void *a, const void *b)
232 return ((Mx*)a)->pref - ((Mx*)b)->pref;
235 /* break up an address to its component parts */
236 void
237 dial_string_parse(char *str, DS *ds)
239 char *p, *p2;
241 strncpy(ds->buf, str, sizeof(ds->buf));
242 ds->buf[sizeof(ds->buf)-1] = 0;
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--)
256 *p2++ = 0;
257 ds->netdir = ds->buf;
258 ds->proto = p2;
260 *p = 0;
261 ds->host = p + 1;
263 ds->service = strchr(ds->host, '!');
264 if(ds->service)
265 *ds->service++ = 0;
266 if(*ds->host == '$')
267 expand_meta(ds);
270 static void
271 expand_meta(DS *ds)
273 static Ndb *db;
274 Ndbs s;
275 char *sys, *smtpserver;
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);