|
1 #include <sys/types.h> |
|
2 #include <sys/socket.h> |
|
3 #include <netinet/in.h> |
|
4 #include <arpa/inet.h> |
|
5 #include "sig.h" |
|
6 #include "stralloc.h" |
|
7 #include "substdio.h" |
|
8 #include "subfd.h" |
|
9 #include "scan.h" |
|
10 #include "case.h" |
|
11 #include "error.h" |
|
12 #include "auto_qmail.h" |
|
13 #include "control.h" |
|
14 #include "dns.h" |
|
15 #include "alloc.h" |
|
16 #include "quote.h" |
|
17 #include "ip.h" |
|
18 #include "ipalloc.h" |
|
19 #include "ipme.h" |
|
20 #include "gen_alloc.h" |
|
21 #include "gen_allocdefs.h" |
|
22 #include "str.h" |
|
23 #include "now.h" |
|
24 #include "exit.h" |
|
25 #include "constmap.h" |
|
26 #include "tcpto.h" |
|
27 #include "readwrite.h" |
|
28 #include "timeoutconn.h" |
|
29 #include "timeoutread.h" |
|
30 #include "timeoutwrite.h" |
|
31 |
|
32 #define HUGESMTPTEXT 5000 |
|
33 |
|
34 #define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */ |
|
35 unsigned long port = PORT_SMTP; |
|
36 |
|
37 GEN_ALLOC_typedef(saa,stralloc,sa,len,a) |
|
38 GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus) |
|
39 static stralloc sauninit = {0}; |
|
40 |
|
41 stralloc helohost = {0}; |
|
42 stralloc routes = {0}; |
|
43 struct constmap maproutes; |
|
44 stralloc host = {0}; |
|
45 stralloc sender = {0}; |
|
46 |
|
47 saa reciplist = {0}; |
|
48 |
|
49 struct ip_address partner; |
|
50 |
|
51 void out(s) char *s; { if (substdio_puts(subfdoutsmall,s) == -1) _exit(0); } |
|
52 void zero() { if (substdio_put(subfdoutsmall,"\0",1) == -1) _exit(0); } |
|
53 void zerodie() { zero(); substdio_flush(subfdoutsmall); _exit(0); } |
|
54 void outsafe(sa) stralloc *sa; { int i; char ch; |
|
55 for (i = 0;i < sa->len;++i) { |
|
56 ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?'; |
|
57 if (substdio_put(subfdoutsmall,&ch,1) == -1) _exit(0); } } |
|
58 |
|
59 void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); } |
|
60 void temp_oserr() { out("Z\ |
|
61 System resources temporarily unavailable. (#4.3.0)\n"); zerodie(); } |
|
62 void temp_noconn() { out("Z\ |
|
63 Sorry, I wasn't able to establish an SMTP connection. (#4.4.1)\n"); zerodie(); } |
|
64 void temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); } |
|
65 void temp_dnscanon() { out("Z\ |
|
66 CNAME lookup failed temporarily. (#4.4.3)\n"); zerodie(); } |
|
67 void temp_dns() { out("Z\ |
|
68 Sorry, I couldn't find any host by that name. (#4.1.2)\n"); zerodie(); } |
|
69 void temp_chdir() { out("Z\ |
|
70 Unable to switch to home directory. (#4.3.0)\n"); zerodie(); } |
|
71 void temp_control() { out("Z\ |
|
72 Unable to read control files. (#4.3.0)\n"); zerodie(); } |
|
73 void perm_partialline() { out("D\ |
|
74 SMTP cannot transfer messages with partial final lines. (#5.6.2)\n"); zerodie(); } |
|
75 void perm_usage() { out("D\ |
|
76 I (qmail-remote) was invoked improperly. (#5.3.5)\n"); zerodie(); } |
|
77 void perm_dns() { out("D\ |
|
78 Sorry, I couldn't find any host named "); |
|
79 outsafe(&host); |
|
80 out(". (#5.1.2)\n"); zerodie(); } |
|
81 void perm_nomx() { out("D\ |
|
82 Sorry, I couldn't find a mail exchanger or IP address. (#5.4.4)\n"); |
|
83 zerodie(); } |
|
84 void perm_ambigmx() { out("D\ |
|
85 Sorry. Although I'm listed as a best-preference MX or A for that host,\n\ |
|
86 it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n"); |
|
87 zerodie(); } |
|
88 |
|
89 void outhost() |
|
90 { |
|
91 char x[IPFMT]; |
|
92 if (substdio_put(subfdoutsmall,x,ip_fmt(x,&partner)) == -1) _exit(0); |
|
93 } |
|
94 |
|
95 int flagcritical = 0; |
|
96 |
|
97 void dropped() { |
|
98 out("ZConnected to "); |
|
99 outhost(); |
|
100 out(" but connection died. "); |
|
101 if (flagcritical) out("Possible duplicate! "); |
|
102 out("(#4.4.2)\n"); |
|
103 zerodie(); |
|
104 } |
|
105 |
|
106 int timeoutconnect = 60; |
|
107 int smtpfd; |
|
108 int timeout = 1200; |
|
109 |
|
110 int saferead(fd,buf,len) int fd; char *buf; int len; |
|
111 { |
|
112 int r; |
|
113 r = timeoutread(timeout,smtpfd,buf,len); |
|
114 if (r <= 0) dropped(); |
|
115 return r; |
|
116 } |
|
117 int safewrite(fd,buf,len) int fd; char *buf; int len; |
|
118 { |
|
119 int r; |
|
120 r = timeoutwrite(timeout,smtpfd,buf,len); |
|
121 if (r <= 0) dropped(); |
|
122 return r; |
|
123 } |
|
124 |
|
125 char inbuf[1024]; |
|
126 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof inbuf); |
|
127 char smtptobuf[1024]; |
|
128 substdio smtpto = SUBSTDIO_FDBUF(safewrite,-1,smtptobuf,sizeof smtptobuf); |
|
129 char smtpfrombuf[128]; |
|
130 substdio smtpfrom = SUBSTDIO_FDBUF(saferead,-1,smtpfrombuf,sizeof smtpfrombuf); |
|
131 |
|
132 stralloc smtptext = {0}; |
|
133 |
|
134 void get(ch) |
|
135 char *ch; |
|
136 { |
|
137 substdio_get(&smtpfrom,ch,1); |
|
138 if (*ch != '\r') |
|
139 if (smtptext.len < HUGESMTPTEXT) |
|
140 if (!stralloc_append(&smtptext,ch)) temp_nomem(); |
|
141 } |
|
142 |
|
143 unsigned long smtpcode() |
|
144 { |
|
145 unsigned char ch; |
|
146 unsigned long code; |
|
147 |
|
148 if (!stralloc_copys(&smtptext,"")) temp_nomem(); |
|
149 |
|
150 get(&ch); code = ch - '0'; |
|
151 get(&ch); code = code * 10 + (ch - '0'); |
|
152 get(&ch); code = code * 10 + (ch - '0'); |
|
153 for (;;) { |
|
154 get(&ch); |
|
155 if (ch != '-') break; |
|
156 while (ch != '\n') get(&ch); |
|
157 get(&ch); |
|
158 get(&ch); |
|
159 get(&ch); |
|
160 } |
|
161 while (ch != '\n') get(&ch); |
|
162 |
|
163 return code; |
|
164 } |
|
165 |
|
166 void outsmtptext() |
|
167 { |
|
168 int i; |
|
169 if (smtptext.s) if (smtptext.len) { |
|
170 out("Remote host said: "); |
|
171 for (i = 0;i < smtptext.len;++i) |
|
172 if (!smtptext.s[i]) smtptext.s[i] = '?'; |
|
173 if (substdio_put(subfdoutsmall,smtptext.s,smtptext.len) == -1) _exit(0); |
|
174 smtptext.len = 0; |
|
175 } |
|
176 } |
|
177 |
|
178 void quit(prepend,append) |
|
179 char *prepend; |
|
180 char *append; |
|
181 { |
|
182 substdio_putsflush(&smtpto,"QUIT\r\n"); |
|
183 /* waiting for remote side is just too ridiculous */ |
|
184 out(prepend); |
|
185 outhost(); |
|
186 out(append); |
|
187 out(".\n"); |
|
188 outsmtptext(); |
|
189 zerodie(); |
|
190 } |
|
191 |
|
192 void blast() |
|
193 { |
|
194 int r; |
|
195 char ch; |
|
196 |
|
197 for (;;) { |
|
198 r = substdio_get(&ssin,&ch,1); |
|
199 if (r == 0) break; |
|
200 if (r == -1) temp_read(); |
|
201 if (ch == '.') |
|
202 substdio_put(&smtpto,".",1); |
|
203 while (ch != '\n') { |
|
204 substdio_put(&smtpto,&ch,1); |
|
205 r = substdio_get(&ssin,&ch,1); |
|
206 if (r == 0) perm_partialline(); |
|
207 if (r == -1) temp_read(); |
|
208 } |
|
209 substdio_put(&smtpto,"\r\n",2); |
|
210 } |
|
211 |
|
212 flagcritical = 1; |
|
213 substdio_put(&smtpto,".\r\n",3); |
|
214 substdio_flush(&smtpto); |
|
215 } |
|
216 |
|
217 stralloc recip = {0}; |
|
218 |
|
219 void smtp() |
|
220 { |
|
221 unsigned long code; |
|
222 int flagbother; |
|
223 int i; |
|
224 |
|
225 if (smtpcode() != 220) quit("ZConnected to "," but greeting failed"); |
|
226 |
|
227 substdio_puts(&smtpto,"HELO "); |
|
228 substdio_put(&smtpto,helohost.s,helohost.len); |
|
229 substdio_puts(&smtpto,"\r\n"); |
|
230 substdio_flush(&smtpto); |
|
231 if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected"); |
|
232 |
|
233 substdio_puts(&smtpto,"MAIL FROM:<"); |
|
234 substdio_put(&smtpto,sender.s,sender.len); |
|
235 substdio_puts(&smtpto,">\r\n"); |
|
236 substdio_flush(&smtpto); |
|
237 code = smtpcode(); |
|
238 if (code >= 500) quit("DConnected to "," but sender was rejected"); |
|
239 if (code >= 400) quit("ZConnected to "," but sender was rejected"); |
|
240 |
|
241 flagbother = 0; |
|
242 for (i = 0;i < reciplist.len;++i) { |
|
243 substdio_puts(&smtpto,"RCPT TO:<"); |
|
244 substdio_put(&smtpto,reciplist.sa[i].s,reciplist.sa[i].len); |
|
245 substdio_puts(&smtpto,">\r\n"); |
|
246 substdio_flush(&smtpto); |
|
247 code = smtpcode(); |
|
248 if (code >= 500) { |
|
249 out("h"); outhost(); out(" does not like recipient.\n"); |
|
250 outsmtptext(); zero(); |
|
251 } |
|
252 else if (code >= 400) { |
|
253 out("s"); outhost(); out(" does not like recipient.\n"); |
|
254 outsmtptext(); zero(); |
|
255 } |
|
256 else { |
|
257 out("r"); zero(); |
|
258 flagbother = 1; |
|
259 } |
|
260 } |
|
261 if (!flagbother) quit("DGiving up on ",""); |
|
262 |
|
263 substdio_putsflush(&smtpto,"DATA\r\n"); |
|
264 code = smtpcode(); |
|
265 if (code >= 500) quit("D"," failed on DATA command"); |
|
266 if (code >= 400) quit("Z"," failed on DATA command"); |
|
267 |
|
268 blast(); |
|
269 code = smtpcode(); |
|
270 flagcritical = 0; |
|
271 if (code >= 500) quit("D"," failed after I sent the message"); |
|
272 if (code >= 400) quit("Z"," failed after I sent the message"); |
|
273 quit("K"," accepted message"); |
|
274 } |
|
275 |
|
276 stralloc canonhost = {0}; |
|
277 stralloc canonbox = {0}; |
|
278 |
|
279 void addrmangle(saout,s,flagalias,flagcname) |
|
280 stralloc *saout; /* host has to be canonical, box has to be quoted */ |
|
281 char *s; |
|
282 int *flagalias; |
|
283 int flagcname; |
|
284 { |
|
285 int j; |
|
286 |
|
287 *flagalias = flagcname; |
|
288 |
|
289 j = str_rchr(s,'@'); |
|
290 if (!s[j]) { |
|
291 if (!stralloc_copys(saout,s)) temp_nomem(); |
|
292 return; |
|
293 } |
|
294 if (!stralloc_copys(&canonbox,s)) temp_nomem(); |
|
295 canonbox.len = j; |
|
296 if (!quote(saout,&canonbox)) temp_nomem(); |
|
297 if (!stralloc_cats(saout,"@")) temp_nomem(); |
|
298 |
|
299 if (!stralloc_copys(&canonhost,s + j + 1)) temp_nomem(); |
|
300 if (flagcname) |
|
301 switch(dns_cname(&canonhost)) { |
|
302 case 0: *flagalias = 0; break; |
|
303 case DNS_MEM: temp_nomem(); |
|
304 case DNS_SOFT: temp_dnscanon(); |
|
305 case DNS_HARD: ; /* alias loop, not our problem */ |
|
306 } |
|
307 |
|
308 if (!stralloc_cat(saout,&canonhost)) temp_nomem(); |
|
309 } |
|
310 |
|
311 void getcontrols() |
|
312 { |
|
313 if (control_init() == -1) temp_control(); |
|
314 if (control_readint(&timeout,"control/timeoutremote") == -1) temp_control(); |
|
315 if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1) |
|
316 temp_control(); |
|
317 if (control_rldef(&helohost,"control/helohost",1,(char *) 0) != 1) |
|
318 temp_control(); |
|
319 switch(control_readfile(&routes,"control/smtproutes",0)) { |
|
320 case -1: |
|
321 temp_control(); |
|
322 case 0: |
|
323 if (!constmap_init(&maproutes,"",0,1)) temp_nomem(); break; |
|
324 case 1: |
|
325 if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break; |
|
326 } |
|
327 } |
|
328 |
|
329 void main(argc,argv) |
|
330 int argc; |
|
331 char **argv; |
|
332 { |
|
333 static ipalloc ip = {0}; |
|
334 int i; |
|
335 unsigned long random; |
|
336 char **recips; |
|
337 unsigned long prefme; |
|
338 int flagallaliases; |
|
339 int flagalias; |
|
340 char *relayhost; |
|
341 |
|
342 sig_pipeignore(); |
|
343 if (argc < 4) perm_usage(); |
|
344 if (chdir(auto_qmail) == -1) temp_chdir(); |
|
345 getcontrols(); |
|
346 |
|
347 |
|
348 if (!stralloc_copys(&host,argv[1])) temp_nomem(); |
|
349 |
|
350 relayhost = 0; |
|
351 for (i = 0;i <= host.len;++i) |
|
352 if ((i == 0) || (i == host.len) || (host.s[i] == '.')) |
|
353 if (relayhost = constmap(&maproutes,host.s + i,host.len - i)) |
|
354 break; |
|
355 if (relayhost && !*relayhost) relayhost = 0; |
|
356 |
|
357 if (relayhost) { |
|
358 i = str_chr(relayhost,':'); |
|
359 if (relayhost[i]) { |
|
360 scan_ulong(relayhost + i + 1,&port); |
|
361 relayhost[i] = 0; |
|
362 } |
|
363 if (!stralloc_copys(&host,relayhost)) temp_nomem(); |
|
364 } |
|
365 |
|
366 |
|
367 addrmangle(&sender,argv[2],&flagalias,0); |
|
368 |
|
369 if (!saa_readyplus(&reciplist,0)) temp_nomem(); |
|
370 if (ipme_init() != 1) temp_oserr(); |
|
371 |
|
372 flagallaliases = 1; |
|
373 recips = argv + 3; |
|
374 while (*recips) { |
|
375 if (!saa_readyplus(&reciplist,1)) temp_nomem(); |
|
376 reciplist.sa[reciplist.len] = sauninit; |
|
377 addrmangle(reciplist.sa + reciplist.len,*recips,&flagalias,!relayhost); |
|
378 if (!flagalias) flagallaliases = 0; |
|
379 ++reciplist.len; |
|
380 ++recips; |
|
381 } |
|
382 |
|
383 |
|
384 random = now() + (getpid() << 16); |
|
385 switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&host,random)) { |
|
386 case DNS_MEM: temp_nomem(); |
|
387 case DNS_SOFT: temp_dns(); |
|
388 case DNS_HARD: perm_dns(); |
|
389 case 1: |
|
390 if (ip.len <= 0) temp_dns(); |
|
391 } |
|
392 |
|
393 if (ip.len <= 0) perm_nomx(); |
|
394 |
|
395 prefme = 100000; |
|
396 for (i = 0;i < ip.len;++i) |
|
397 if (ipme_is(&ip.ix[i].ip)) |
|
398 if (ip.ix[i].pref < prefme) |
|
399 prefme = ip.ix[i].pref; |
|
400 |
|
401 if (relayhost) prefme = 300000; |
|
402 if (flagallaliases) prefme = 500000; |
|
403 |
|
404 for (i = 0;i < ip.len;++i) |
|
405 if (ip.ix[i].pref < prefme) |
|
406 break; |
|
407 |
|
408 if (i >= ip.len) |
|
409 perm_ambigmx(); |
|
410 |
|
411 for (i = 0;i < ip.len;++i) if (ip.ix[i].pref < prefme) { |
|
412 if (tcpto(&ip.ix[i].ip)) continue; |
|
413 |
|
414 smtpfd = socket(AF_INET,SOCK_STREAM,0); |
|
415 if (smtpfd == -1) temp_oserr(); |
|
416 |
|
417 if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0) { |
|
418 tcpto_err(&ip.ix[i].ip,0); |
|
419 partner = ip.ix[i].ip; |
|
420 smtp(); /* does not return */ |
|
421 } |
|
422 tcpto_err(&ip.ix[i].ip,errno == error_timeout); |
|
423 close(smtpfd); |
|
424 } |
|
425 |
|
426 temp_noconn(); |
|
427 } |