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 }