|
0
|
1 |
#include "sig.h"
|
|
|
2 |
#include "readwrite.h"
|
|
|
3 |
#include "stralloc.h"
|
|
|
4 |
#include "substdio.h"
|
|
|
5 |
#include "alloc.h"
|
|
|
6 |
#include "auto_qmail.h"
|
|
|
7 |
#include "control.h"
|
|
|
8 |
#include "received.h"
|
|
|
9 |
#include "constmap.h"
|
|
|
10 |
#include "error.h"
|
|
|
11 |
#include "ipme.h"
|
|
|
12 |
#include "ip.h"
|
|
|
13 |
#include "qmail.h"
|
|
|
14 |
#include "str.h"
|
|
|
15 |
#include "fmt.h"
|
|
|
16 |
#include "scan.h"
|
|
|
17 |
#include "byte.h"
|
|
|
18 |
#include "case.h"
|
|
|
19 |
#include "env.h"
|
|
|
20 |
#include "now.h"
|
|
|
21 |
#include "exit.h"
|
|
|
22 |
#include "rcpthosts.h"
|
|
|
23 |
#include "timeoutread.h"
|
|
|
24 |
#include "timeoutwrite.h"
|
|
|
25 |
#include "commands.h"
|
|
|
26 |
|
|
|
27 |
#define MAXHOPS 100
|
|
|
28 |
unsigned int databytes = 0;
|
|
|
29 |
int timeout = 1200;
|
|
|
30 |
|
|
|
31 |
int safewrite(fd,buf,len) int fd; char *buf; int len;
|
|
|
32 |
{
|
|
|
33 |
int r;
|
|
|
34 |
r = timeoutwrite(timeout,fd,buf,len);
|
|
|
35 |
if (r <= 0) _exit(1);
|
|
|
36 |
return r;
|
|
|
37 |
}
|
|
|
38 |
|
|
|
39 |
char ssoutbuf[512];
|
|
|
40 |
substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf);
|
|
|
41 |
|
|
|
42 |
void flush() { substdio_flush(&ssout); }
|
|
|
43 |
void out(s) char *s; { substdio_puts(&ssout,s); }
|
|
|
44 |
|
|
|
45 |
void die_read() { _exit(1); }
|
|
|
46 |
void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); }
|
|
|
47 |
void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); }
|
|
|
48 |
void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); }
|
|
|
49 |
void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }
|
|
|
50 |
void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); }
|
|
|
51 |
|
|
|
52 |
void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
|
|
|
53 |
void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
|
|
|
54 |
void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }
|
|
|
55 |
void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }
|
|
|
56 |
void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }
|
|
|
57 |
void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); }
|
|
|
58 |
void err_noop() { out("250 ok\r\n"); }
|
|
|
59 |
void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); }
|
|
|
60 |
void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }
|
|
|
61 |
|
|
|
62 |
|
|
|
63 |
stralloc greeting = {0};
|
|
|
64 |
|
|
|
65 |
void smtp_greet(code) char *code;
|
|
|
66 |
{
|
|
|
67 |
substdio_puts(&ssout,code);
|
|
|
68 |
substdio_put(&ssout,greeting.s,greeting.len);
|
|
|
69 |
}
|
|
|
70 |
void smtp_help()
|
|
|
71 |
{
|
|
|
72 |
out("214 qmail home page: http://pobox.com/~djb/qmail.html\r\n");
|
|
|
73 |
}
|
|
|
74 |
void smtp_quit()
|
|
|
75 |
{
|
|
|
76 |
smtp_greet("221 "); out("\r\n"); flush(); _exit(0);
|
|
|
77 |
}
|
|
|
78 |
|
|
|
79 |
char *remoteip;
|
|
|
80 |
char *remotehost;
|
|
|
81 |
char *remoteinfo;
|
|
|
82 |
char *local;
|
|
|
83 |
char *relayclient;
|
|
|
84 |
|
|
|
85 |
stralloc helohost = {0};
|
|
|
86 |
char *fakehelo; /* pointer into helohost, or 0 */
|
|
|
87 |
|
|
|
88 |
void dohelo(arg) char *arg; {
|
|
|
89 |
if (!stralloc_copys(&helohost,arg)) die_nomem();
|
|
|
90 |
if (!stralloc_0(&helohost)) die_nomem();
|
|
|
91 |
fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0;
|
|
|
92 |
}
|
|
|
93 |
|
|
|
94 |
int liphostok = 0;
|
|
|
95 |
stralloc liphost = {0};
|
|
|
96 |
int bmfok = 0;
|
|
|
97 |
stralloc bmf = {0};
|
|
|
98 |
struct constmap mapbmf;
|
|
|
99 |
|
|
|
100 |
void setup()
|
|
|
101 |
{
|
|
|
102 |
char *x;
|
|
|
103 |
unsigned long u;
|
|
|
104 |
|
|
|
105 |
if (control_init() == -1) die_control();
|
|
|
106 |
if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1)
|
|
|
107 |
die_control();
|
|
|
108 |
liphostok = control_rldef(&liphost,"control/localiphost",1,(char *) 0);
|
|
|
109 |
if (liphostok == -1) die_control();
|
|
|
110 |
if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control();
|
|
|
111 |
if (timeout <= 0) timeout = 1;
|
|
|
112 |
|
|
|
113 |
if (rcpthosts_init() == -1) die_control();
|
|
|
114 |
|
|
|
115 |
bmfok = control_readfile(&bmf,"control/badmailfrom",0);
|
|
|
116 |
if (bmfok == -1) die_control();
|
|
|
117 |
if (bmfok)
|
|
|
118 |
if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem();
|
|
|
119 |
|
|
|
120 |
if (control_readint(&databytes,"control/databytes") == -1) die_control();
|
|
|
121 |
x = env_get("DATABYTES");
|
|
|
122 |
if (x) { scan_ulong(x,&u); databytes = u; }
|
|
|
123 |
if (!(databytes + 1)) --databytes;
|
|
|
124 |
|
|
|
125 |
remoteip = env_get("TCPREMOTEIP");
|
|
|
126 |
if (!remoteip) remoteip = "unknown";
|
|
|
127 |
local = env_get("TCPLOCALHOST");
|
|
|
128 |
if (!local) local = env_get("TCPLOCALIP");
|
|
|
129 |
if (!local) local = "unknown";
|
|
|
130 |
remotehost = env_get("TCPREMOTEHOST");
|
|
|
131 |
if (!remotehost) remotehost = "unknown";
|
|
|
132 |
remoteinfo = env_get("TCPREMOTEINFO");
|
|
|
133 |
relayclient = env_get("RELAYCLIENT");
|
|
|
134 |
dohelo(remotehost);
|
|
|
135 |
}
|
|
|
136 |
|
|
|
137 |
|
|
|
138 |
stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */
|
|
|
139 |
|
|
|
140 |
int addrparse(arg)
|
|
|
141 |
char *arg;
|
|
|
142 |
{
|
|
|
143 |
int i;
|
|
|
144 |
char ch;
|
|
|
145 |
char terminator;
|
|
|
146 |
struct ip_address ip;
|
|
|
147 |
int flagesc;
|
|
|
148 |
int flagquoted;
|
|
|
149 |
|
|
|
150 |
terminator = '>';
|
|
|
151 |
i = str_chr(arg,'<');
|
|
|
152 |
if (arg[i])
|
|
|
153 |
arg += i + 1;
|
|
|
154 |
else { /* partner should go read rfc 821 */
|
|
|
155 |
terminator = ' ';
|
|
|
156 |
arg += str_chr(arg,':');
|
|
|
157 |
if (*arg == ':') ++arg;
|
|
|
158 |
while (*arg == ' ') ++arg;
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
/* strip source route */
|
|
|
162 |
if (*arg == '@') while (*arg) if (*arg++ == ':') break;
|
|
|
163 |
|
|
|
164 |
if (!stralloc_copys(&addr,"")) die_nomem();
|
|
|
165 |
flagesc = 0;
|
|
|
166 |
flagquoted = 0;
|
|
|
167 |
for (i = 0;ch = arg[i];++i) { /* copy arg to addr, stripping quotes */
|
|
|
168 |
if (flagesc) {
|
|
|
169 |
if (!stralloc_append(&addr,&ch)) die_nomem();
|
|
|
170 |
flagesc = 0;
|
|
|
171 |
}
|
|
|
172 |
else {
|
|
|
173 |
if (!flagquoted && (ch == terminator)) break;
|
|
|
174 |
switch(ch) {
|
|
|
175 |
case '\\': flagesc = 1; break;
|
|
|
176 |
case '"': flagquoted = !flagquoted; break;
|
|
|
177 |
default: if (!stralloc_append(&addr,&ch)) die_nomem();
|
|
|
178 |
}
|
|
|
179 |
}
|
|
|
180 |
}
|
|
|
181 |
/* could check for termination failure here, but why bother? */
|
|
|
182 |
if (!stralloc_append(&addr,"")) die_nomem();
|
|
|
183 |
|
|
|
184 |
if (liphostok) {
|
|
|
185 |
i = byte_rchr(addr.s,addr.len,'@');
|
|
|
186 |
if (i < addr.len) /* if not, partner should go read rfc 821 */
|
|
|
187 |
if (addr.s[i + 1] == '[')
|
|
|
188 |
if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)])
|
|
|
189 |
if (ipme_is(&ip)) {
|
|
|
190 |
addr.len = i + 1;
|
|
|
191 |
if (!stralloc_cat(&addr,&liphost)) die_nomem();
|
|
|
192 |
if (!stralloc_0(&addr)) die_nomem();
|
|
|
193 |
}
|
|
|
194 |
}
|
|
|
195 |
|
|
|
196 |
if (addr.len > 900) return 0;
|
|
|
197 |
return 1;
|
|
|
198 |
}
|
|
|
199 |
|
|
|
200 |
int bmfcheck()
|
|
|
201 |
{
|
|
|
202 |
int j;
|
|
|
203 |
if (!bmfok) return 0;
|
|
|
204 |
if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1;
|
|
|
205 |
j = byte_rchr(addr.s,addr.len,'@');
|
|
|
206 |
if (j < addr.len)
|
|
|
207 |
if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1;
|
|
|
208 |
return 0;
|
|
|
209 |
}
|
|
|
210 |
|
|
|
211 |
int addrallowed()
|
|
|
212 |
{
|
|
|
213 |
int r;
|
|
|
214 |
r = rcpthosts(addr.s,str_len(addr.s));
|
|
|
215 |
if (r == -1) die_control();
|
|
|
216 |
return r;
|
|
|
217 |
}
|
|
|
218 |
|
|
|
219 |
|
|
|
220 |
int seenmail = 0;
|
|
|
221 |
int flagbarf; /* defined if seenmail */
|
|
|
222 |
stralloc mailfrom = {0};
|
|
|
223 |
stralloc rcptto = {0};
|
|
|
224 |
|
|
|
225 |
void smtp_helo(arg) char *arg;
|
|
|
226 |
{
|
|
|
227 |
smtp_greet("250 "); out("\r\n");
|
|
|
228 |
seenmail = 0; dohelo(arg);
|
|
|
229 |
}
|
|
|
230 |
void smtp_ehlo(arg) char *arg;
|
|
|
231 |
{
|
|
|
232 |
smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
|
|
|
233 |
seenmail = 0; dohelo(arg);
|
|
|
234 |
}
|
|
|
235 |
void smtp_rset()
|
|
|
236 |
{
|
|
|
237 |
seenmail = 0;
|
|
|
238 |
out("250 flushed\r\n");
|
|
|
239 |
}
|
|
|
240 |
void smtp_mail(arg) char *arg;
|
|
|
241 |
{
|
|
|
242 |
if (!addrparse(arg)) { err_syntax(); return; }
|
|
|
243 |
flagbarf = bmfcheck();
|
|
|
244 |
seenmail = 1;
|
|
|
245 |
if (!stralloc_copys(&rcptto,"")) die_nomem();
|
|
|
246 |
if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();
|
|
|
247 |
if (!stralloc_0(&mailfrom)) die_nomem();
|
|
|
248 |
out("250 ok\r\n");
|
|
|
249 |
}
|
|
|
250 |
void smtp_rcpt(arg) char *arg; {
|
|
|
251 |
if (!seenmail) { err_wantmail(); return; }
|
|
|
252 |
if (!addrparse(arg)) { err_syntax(); return; }
|
|
|
253 |
if (flagbarf) { err_bmf(); return; }
|
|
|
254 |
if (relayclient) {
|
|
|
255 |
--addr.len;
|
|
|
256 |
if (!stralloc_cats(&addr,relayclient)) die_nomem();
|
|
|
257 |
if (!stralloc_0(&addr)) die_nomem();
|
|
|
258 |
}
|
|
|
259 |
else
|
|
|
260 |
if (!addrallowed()) { err_nogateway(); return; }
|
|
|
261 |
if (!stralloc_cats(&rcptto,"T")) die_nomem();
|
|
|
262 |
if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
|
|
|
263 |
if (!stralloc_0(&rcptto)) die_nomem();
|
|
|
264 |
out("250 ok\r\n");
|
|
|
265 |
}
|
|
|
266 |
|
|
|
267 |
|
|
|
268 |
int saferead(fd,buf,len) int fd; char *buf; int len;
|
|
|
269 |
{
|
|
|
270 |
int r;
|
|
|
271 |
flush();
|
|
|
272 |
r = timeoutread(timeout,fd,buf,len);
|
|
|
273 |
if (r == -1) if (errno == error_timeout) die_alarm();
|
|
|
274 |
if (r <= 0) die_read();
|
|
|
275 |
return r;
|
|
|
276 |
}
|
|
|
277 |
|
|
|
278 |
char ssinbuf[1024];
|
|
|
279 |
substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf);
|
|
|
280 |
|
|
|
281 |
struct qmail qqt;
|
|
|
282 |
unsigned int bytestooverflow = 0;
|
|
|
283 |
|
|
|
284 |
void put(ch)
|
|
|
285 |
char *ch;
|
|
|
286 |
{
|
|
|
287 |
if (bytestooverflow)
|
|
|
288 |
if (!--bytestooverflow)
|
|
|
289 |
qmail_fail(&qqt);
|
|
|
290 |
qmail_put(&qqt,ch,1);
|
|
|
291 |
}
|
|
|
292 |
|
|
|
293 |
void blast(hops)
|
|
|
294 |
int *hops;
|
|
|
295 |
{
|
|
|
296 |
char ch;
|
|
|
297 |
int state;
|
|
|
298 |
int flaginheader;
|
|
|
299 |
int pos; /* number of bytes since most recent \n, if fih */
|
|
|
300 |
int flagmaybex; /* 1 if this line might match RECEIVED, if fih */
|
|
|
301 |
int flagmaybey; /* 1 if this line might match \r\n, if fih */
|
|
|
302 |
int flagmaybez; /* 1 if this line might match DELIVERED, if fih */
|
|
|
303 |
|
|
|
304 |
state = 1;
|
|
|
305 |
*hops = 0;
|
|
|
306 |
flaginheader = 1;
|
|
|
307 |
pos = 0; flagmaybex = flagmaybey = flagmaybez = 1;
|
|
|
308 |
for (;;) {
|
|
|
309 |
substdio_get(&ssin,&ch,1);
|
|
|
310 |
if (flaginheader) {
|
|
|
311 |
if (pos < 9) {
|
|
|
312 |
if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0;
|
|
|
313 |
if (flagmaybez) if (pos == 8) ++*hops;
|
|
|
314 |
if (pos < 8)
|
|
|
315 |
if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0;
|
|
|
316 |
if (flagmaybex) if (pos == 7) ++*hops;
|
|
|
317 |
if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0;
|
|
|
318 |
if (flagmaybey) if (pos == 1) flaginheader = 0;
|
|
|
319 |
}
|
|
|
320 |
++pos;
|
|
|
321 |
if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; }
|
|
|
322 |
}
|
|
|
323 |
switch(state) {
|
|
|
324 |
case 0:
|
|
|
325 |
if (ch == '\n') straynewline();
|
|
|
326 |
if (ch == '\r') { state = 4; continue; }
|
|
|
327 |
break;
|
|
|
328 |
case 1: /* \r\n */
|
|
|
329 |
if (ch == '\n') straynewline();
|
|
|
330 |
if (ch == '.') { state = 2; continue; }
|
|
|
331 |
if (ch == '\r') { state = 4; continue; }
|
|
|
332 |
state = 0;
|
|
|
333 |
break;
|
|
|
334 |
case 2: /* \r\n + . */
|
|
|
335 |
if (ch == '\n') straynewline();
|
|
|
336 |
if (ch == '\r') { state = 3; continue; }
|
|
|
337 |
state = 0;
|
|
|
338 |
break;
|
|
|
339 |
case 3: /* \r\n + .\r */
|
|
|
340 |
if (ch == '\n') return;
|
|
|
341 |
put(".");
|
|
|
342 |
put("\r");
|
|
|
343 |
if (ch == '\r') { state = 4; continue; }
|
|
|
344 |
state = 0;
|
|
|
345 |
break;
|
|
|
346 |
case 4: /* + \r */
|
|
|
347 |
if (ch == '\n') { state = 1; break; }
|
|
|
348 |
if (ch != '\r') { put("\r"); state = 0; }
|
|
|
349 |
}
|
|
|
350 |
put(&ch);
|
|
|
351 |
}
|
|
|
352 |
}
|
|
|
353 |
|
|
|
354 |
char accept_buf[FMT_ULONG];
|
|
|
355 |
void acceptmessage(qp) unsigned long qp;
|
|
|
356 |
{
|
|
|
357 |
datetime_sec when;
|
|
|
358 |
when = now();
|
|
|
359 |
out("250 ok ");
|
|
|
360 |
accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0;
|
|
|
361 |
out(accept_buf);
|
|
|
362 |
out(" qp ");
|
|
|
363 |
accept_buf[fmt_ulong(accept_buf,qp)] = 0;
|
|
|
364 |
out(accept_buf);
|
|
|
365 |
out("\r\n");
|
|
|
366 |
}
|
|
|
367 |
|
|
|
368 |
void smtp_data() {
|
|
|
369 |
int hops;
|
|
|
370 |
unsigned long qp;
|
|
|
371 |
char *qqx;
|
|
|
372 |
|
|
|
373 |
if (!seenmail) { err_wantmail(); return; }
|
|
|
374 |
if (!rcptto.len) { err_wantrcpt(); return; }
|
|
|
375 |
seenmail = 0;
|
|
|
376 |
if (databytes) bytestooverflow = databytes + 1;
|
|
|
377 |
if (qmail_open(&qqt) == -1) { err_qqt(); return; }
|
|
|
378 |
qp = qmail_qp(&qqt);
|
|
|
379 |
out("354 go ahead\r\n");
|
|
|
380 |
|
|
|
381 |
received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
|
|
|
382 |
blast(&hops);
|
|
|
383 |
hops = (hops >= MAXHOPS);
|
|
|
384 |
if (hops) qmail_fail(&qqt);
|
|
|
385 |
qmail_from(&qqt,mailfrom.s);
|
|
|
386 |
qmail_put(&qqt,rcptto.s,rcptto.len);
|
|
|
387 |
|
|
|
388 |
qqx = qmail_close(&qqt);
|
|
|
389 |
if (!*qqx) { acceptmessage(qp); return; }
|
|
|
390 |
if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }
|
|
|
391 |
if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; }
|
|
|
392 |
if (*qqx == 'D') out("554 "); else out("451 ");
|
|
|
393 |
out(qqx + 1);
|
|
|
394 |
out("\r\n");
|
|
|
395 |
}
|
|
|
396 |
|
|
|
397 |
struct commands smtpcommands[] = {
|
|
|
398 |
{ "rcpt", smtp_rcpt, 0 }
|
|
|
399 |
, { "mail", smtp_mail, 0 }
|
|
|
400 |
, { "data", smtp_data, flush }
|
|
|
401 |
, { "quit", smtp_quit, flush }
|
|
|
402 |
, { "helo", smtp_helo, flush }
|
|
|
403 |
, { "ehlo", smtp_ehlo, flush }
|
|
|
404 |
, { "rset", smtp_rset, 0 }
|
|
|
405 |
, { "help", smtp_help, flush }
|
|
|
406 |
, { "noop", err_noop, flush }
|
|
|
407 |
, { "vrfy", err_vrfy, flush }
|
|
|
408 |
, { 0, err_unimpl, flush }
|
|
|
409 |
} ;
|
|
|
410 |
|
|
|
411 |
void main()
|
|
|
412 |
{
|
|
|
413 |
sig_pipeignore();
|
|
|
414 |
if (chdir(auto_qmail) == -1) die_control();
|
|
|
415 |
setup();
|
|
|
416 |
if (ipme_init() != 1) die_ipme();
|
|
|
417 |
smtp_greet("220 ");
|
|
|
418 |
out(" ESMTP\r\n");
|
|
|
419 |
if (commands(&ssin,&smtpcommands) == 0) die_read();
|
|
|
420 |
die_nomem();
|
|
|
421 |
}
|