1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include "sgetopt.h"
4 #include "scan.h"
5 #include "stralloc.h"
6 #include "fd.h"
7 #include "open.h"
8 #include "getln.h"
9 #include "subfd.h"
10 #include "strerr.h"
11 #include "substdio.h"
12 #include "readwrite.h"
13 #include "maildir.h"
14 #include "prioq.h"
15 #include "wait.h"
16 #include "fork.h"
17 #include "sig.h"
18 #include "str.h"
19 #include "fmt.h"
20 #include "tai.h"
21 #include "mess822.h"
22 #include "now.h"
23 #include "config.h"
24 #include "qmail.h"
25 #include "auto_qmail.h"
26
27 #define FATAL "maildirserial: fatal: "
28 #define WARNING "maildirserial: warning: "
29 #define INFO "maildirserial: info: "
30
die_usage()31 void die_usage() {
32 strerr_die1x(100,"maildirserial: usage: maildirserial [ -b ] [ -t lifetime ] dir prefix client [ arg ... ]");
33 }
die_readclient()34 void die_readclient()
35 {
36 strerr_die2sys(111,FATAL,"unable to read from client: ");
37 }
die_nomem()38 void die_nomem()
39 {
40 strerr_die2x(111,FATAL,"out of memory");
41 }
die_qq()42 void die_qq()
43 {
44 strerr_die2sys(111,FATAL,"unable to run qq: ");
45 }
46
47 char *prefix;
48 char **client;
49
50 char messbuf[256];
51 substdio ssmess;
52
53 stralloc line = {0};
54 stralloc recipient = {0};
55
56 char buf[1024];
57 substdio ss; /* in parent, reading from child; in scanner, writing to child */
58
59 int pid; /* in parent, pid of scanner; in scanner, pid of child */
60 int wstat;
61
62 stralloc deadfiles = {0};
63
64
65 /* ---------------------------------------------------------------- BOUNCING */
66
67 config_str me = CONFIG_STR;
68 config_str bouncefrom = CONFIG_STR;
69 config_str bouncehost = CONFIG_STR;
70 config_str doublebounceto = CONFIG_STR;
71 config_str doublebouncehost = CONFIG_STR;
72
readcontrols()73 void readcontrols()
74 {
75 int fddir;
76
77 fddir = open_read(".");
78 if (fddir == -1)
79 strerr_die2sys(111,FATAL,"unable to open current directory: ");
80
81 if (chdir(auto_qmail) == -1)
82 strerr_die4sys(111,FATAL,"unable to chdir to ",auto_qmail,": ");
83
84 if (config_readline(&me,"control/me") == -1)
85 strerr_die4sys(111,FATAL,"unable to read ",auto_qmail,"/control/me: ");
86 if (config_default(&me,"me") == -1) die_nomem();
87
88 if (config_readline(&bouncefrom,"control/bouncefrom") == -1)
89 strerr_die4sys(111,FATAL,"unable to read ",auto_qmail,"/control/bouncefrom: ");
90 if (config_default(&bouncefrom,"MAILER-DAEMON") == -1) die_nomem();
91
92 if (config_readline(&bouncehost,"control/bouncehost") == -1)
93 strerr_die4sys(111,FATAL,"unable to read ",auto_qmail,"/control/bouncehost: ");
94 if (config_copy(&bouncehost,&me) == -1) die_nomem();
95
96 if (config_readline(&doublebouncehost,"control/doublebouncehost") == -1)
97 strerr_die4sys(111,FATAL,"unable to read ",auto_qmail,"/control/doublebouncehost: ");
98 if (config_copy(&doublebouncehost,&me) == -1) die_nomem();
99
100 if (config_readline(&doublebounceto,"control/doublebounceto") == -1)
101 strerr_die4sys(111,FATAL,"unable to read ",auto_qmail,"/control/doublebounceto: ");
102 if (config_default(&doublebounceto,"postmaster") == -1) die_nomem();
103
104 if (!stralloc_cats(config_data(&doublebounceto),"@")) die_nomem();
105 if (!stralloc_cat(config_data(&doublebounceto),config_data(&doublebouncehost))) die_nomem();
106 if (!stralloc_0(config_data(&doublebounceto))) die_nomem();
107
108 if (fchdir(fddir) == -1)
109 strerr_die2sys(111,FATAL,"unable to set current directory: ");
110 }
111
112 struct qmail qq;
113
put(buf,len)114 void put(buf,len) char *buf; int len; { qmail_put(&qq,buf,len); }
puts(buf)115 void puts(buf) char *buf; { qmail_puts(&qq,buf); }
116
117 char *qqx;
118 unsigned long qp;
119 char num[FMT_ULONG];
120
121 struct tai datetai;
122 mess822_time date;
123 stralloc datestr = {0};
124
125 stralloc sender = {0};
126 stralloc quoted = {0};
127
bounce(fd,why,flagtimeout)128 int bounce(fd,why,flagtimeout)
129 int fd;
130 stralloc *why; /* must end with \n; must not contain \n\n */
131 int flagtimeout;
132 {
133 int match;
134 char *bouncesender;
135 char *bouncerecip;
136 char *x;
137 int n;
138
139 substdio_fdbuf(&ssmess,read,fd,messbuf,sizeof messbuf);
140
141 if (getln(&ssmess,&line,&match,'\n') == -1) return -1;
142 if (!match) return -3;
143 if (!stralloc_starts(&line,"Return-Path: <")) return -3;
144 if (line.s[line.len - 2] != '>') return -3;
145 if (line.s[line.len - 1] != '\n') return -3;
146 if (!stralloc_copyb(&sender,line.s + 14,line.len - 16)) die_nomem();
147 if (byte_chr(sender.s,sender.len,'\0') < sender.len) return -3;
148 if (!stralloc_0(&sender)) die_nomem();
149
150 if (getln(&ssmess,&line,&match,'\n') == -1) return -1;
151 if (!match) return -3;
152 if (!stralloc_starts(&line,"Delivered-To: ")) return -3;
153 if (line.s[line.len - 1] != '\n') return -3;
154 if (!stralloc_copyb(&recipient,line.s + 14,line.len - 15)) die_nomem();
155
156 if (str_equal(sender.s,"#@[]")) return 2;
157
158 if (qmail_open(&qq) == -1) die_qq();
159 qp = qmail_qp(&qq);
160
161 if (*sender.s) { bouncesender = ""; bouncerecip = sender.s; }
162 else { bouncesender = "#@[]"; bouncerecip = config_data(&doublebounceto)->s; }
163
164 tai_now(&datetai);
165 caltime_utc(&date.ct,&datetai,(int *) 0,(int *) 0);
166 date.known = 1;
167 if (!mess822_date(&datestr,&date)) die_nomem();
168
169 puts("Date: ");
170 put(datestr.s,datestr.len);
171 puts("\n");
172 puts("From: ");
173 if (!quote("ed,config_data(&bouncefrom))) die_nomem();
174 put(quoted.s,quoted.len);
175 puts("@");
176 put(config_data(&bouncehost)->s,config_data(&bouncehost)->len);
177 puts("\nTo: ");
178 if (!quote2("ed,bouncerecip)) die_nomem();
179 put(quoted.s,quoted.len);
180
181 puts("\n\
182 Subject: failure notice\n\
183 \n\
184 Hi. This is the maildirbounce program at ");
185 put(config_data(&bouncehost)->s,config_data(&bouncehost)->len);
186
187 puts(*sender.s ? ".\n\
188 I'm afraid I wasn't able to deliver your message to the following address.\n\
189 This is a permanent error; I've given up. Sorry it didn't work out.\n\
190 \n\
191 " : ".\n\
192 I tried to deliver a bounce message to this address, but the bounce bounced!\n\
193 \n\
194 ");
195
196 puts("<");
197 if (stralloc_starts(&recipient,prefix))
198 put(recipient.s + str_len(prefix),recipient.len - str_len(prefix));
199 else
200 put(recipient.s,recipient.len);
201 puts(">:\n");
202 put(why->s,why->len);
203 if (flagtimeout)
204 puts("This message is too old. Giving up.\n");
205 puts("\n");
206
207 puts(*sender.s ? "--- Below this line is a copy of the message.\n\n" : "--- Below this line is the original bounce.\n\n");
208 puts("Return-Path: <");
209 if (!quote2("ed,sender.s)) die_nomem();
210 put(quoted.s,quoted.len);
211 puts(">\n");
212
213 for (;;) {
214 n = substdio_feed(&ssmess);
215 if (n < 0) strerr_die2sys(111,FATAL,"unable to read message: ");
216 if (!n) break;
217 x = substdio_PEEK(&ssmess);
218 put(x,n);
219 substdio_SEEK(&ssmess,n);
220 }
221
222 qmail_from(&qq,bouncesender);
223 qmail_to(&qq,bouncerecip);
224 qqx = qmail_close(&qq);
225 if (*qqx) return -2;
226
227 return 1;
228 }
229
230
231 /* ----------------------------------------------------------------- SCANNER */
232
hasprefix(fd)233 int hasprefix(fd)
234 int fd;
235 {
236 int match;
237
238 substdio_fdbuf(&ssmess,read,fd,messbuf,sizeof messbuf);
239
240 if (getln(&ssmess,&line,&match,'\n') == -1) return -1;
241 if (!match) return 0;
242
243 if (getln(&ssmess,&line,&match,'\n') == -1) return -1;
244 if (!match) return 0;
245 if (!stralloc_starts(&line,"Delivered-To: ")) return 0;
246 if (line.s[line.len - 1] != '\n') return 0;
247 if (!stralloc_copyb(&recipient,line.s + 14,line.len - 15)) return -1;
248
249 return stralloc_starts(&recipient,prefix);
250 }
251
usable(fn)252 int usable(fn)
253 char *fn;
254 {
255 int i;
256 int fd;
257
258 i = 0;
259 while (i < deadfiles.len) {
260 if (str_equal(fn,deadfiles.s + i)) return 0;
261 i += str_len(deadfiles.s + i);
262 ++i;
263 }
264
265 if (!*prefix) return 1; /* optimization */
266
267 fd = open_read(fn);
268 if (fd == -1) {
269 strerr_warn4(WARNING,"unable to open ",fn,": ",&strerr_sys);
270 return 0;
271 }
272
273 i = hasprefix(fd);
274 if (i == -1)
275 strerr_warn4(WARNING,"unable to read ",fn,": ",&strerr_sys);
276 close(fd);
277 return i == 1;
278 }
279
280 stralloc filenames = {0};
281 prioq pq = {0};
282
283 int pis2c[2];
284
scanner()285 void scanner()
286 {
287 struct prioq_elt pe;
288 char *fn;
289
290 if (pipe(pis2c) == -1)
291 strerr_die2sys(111,FATAL,"unable to create pipe: ");
292
293 substdio_fdbuf(&ss,write,pis2c[1],buf,sizeof buf);
294
295 maildir_clean(&filenames);
296
297 if (maildir_scan(&pq,&filenames,1,1) == -1)
298 strerr_die1(111,FATAL,&maildir_scan_err);
299
300 while (prioq_min(&pq,&pe)) {
301 prioq_delmin(&pq);
302 fn = filenames.s + pe.id;
303 if (usable(fn)) {
304 if (!pid) {
305 pid = fork();
306 if (pid == -1)
307 strerr_die2sys(111,FATAL,"unable to fork: ");
308 if (pid == 0) {
309 close(pis2c[1]);
310 fd_move(0,pis2c[0]);
311 sig_pipedefault();
312 execvp(*client,client);
313 strerr_die4sys(111,FATAL,"unable to run ",*client,": ");
314 }
315 close(pis2c[0]);
316 }
317 substdio_put(&ss,fn,str_len(fn) + 1); /* ignore errors */
318 }
319 }
320
321 if (!pid) _exit(0);
322
323 substdio_flush(&ss); /* ignore errors */
324 close(pis2c[1]);
325
326 if (wait_pid(&wstat,pid) == -1)
327 strerr_die2sys(111,FATAL,"unable to get client status: ");
328 if (wait_crashed(wstat))
329 strerr_die2x(111,FATAL,"client crashed");
330 if (wait_exitcode(wstat) == 100)
331 _exit(100); /* client has produced error message */
332
333 _exit(30);
334 }
335
336
337 /* ------------------------------------------------------------------ PARENT */
338
339 int flagbounce = 0;
340 int flaglifetime = 0;
341 unsigned long lifetime;
342 int flagtimeout;
343
344 int pic2p[2];
345
346 stralloc fn = {0};
347 stralloc err = {0};
348 int match;
349
info(result)350 void info(result)
351 char *result;
352 {
353 substdio_puts(subfderr,INFO);
354 substdio_puts(subfderr,fn.s);
355 substdio_puts(subfderr,result);
356 substdio_put(subfderr,err.s,err.len);
357 substdio_flush(subfderr);
358 }
359
main(argc,argv)360 void main(argc,argv)
361 int argc;
362 char **argv;
363 {
364 int opt;
365 char *dir;
366 int r;
367 char status;
368 struct stat st;
369 int progress;
370
371 sig_pipeignore();
372
373 while ((opt = getopt(argc,argv,"bt:")) != opteof)
374 switch(opt) {
375 case 'b':
376 flagbounce = 1;
377 break;
378 case 't':
379 scan_ulong(optarg,&lifetime);
380 flaglifetime = 1;
381 break;
382 default:
383 die_usage();
384 }
385
386 argv += optind;
387 dir = *argv++;
388 if (!dir) die_usage();
389 prefix = *argv++;
390 if (!prefix) die_usage();
391 client = argv;
392 if (!*client) die_usage();
393
394 if (chdir(dir) == -1)
395 strerr_die4sys(111,FATAL,"unable to chdir to ",dir,": ");
396
397 readcontrols();
398
399 if (!stralloc_copys(&deadfiles,"")) die_nomem();
400
401 progress = 3;
402
403 for (;;) {
404 if (pipe(pic2p) == -1)
405 strerr_die2sys(111,FATAL,"unable to create pipe: ");
406
407 pid = fork();
408 if (pid == -1)
409 strerr_die2sys(111,FATAL,"unable to fork: ");
410
411 if (!pid) {
412 close(pic2p[0]);
413 fd_move(1,pic2p[1]);
414 scanner();
415 }
416
417 close(pic2p[1]);
418 substdio_fdbuf(&ss,read,pic2p[0],buf,sizeof buf);
419
420 --progress;
421
422 for (;;) {
423 if (getln(&ss,&fn,&match,'\0') == -1) die_readclient();
424 if (!match) break;
425 r = substdio_get(&ss,&status,1);
426 if (r == -1) die_readclient();
427 if (!match) break;
428 if (getln(&ss,&err,&match,'\n') == -1) die_readclient();
429 if (!match) break;
430
431 progress = 3;
432
433 if (status == 'K') {
434 info(" succeeded: ");
435
436 if (unlink(fn.s) == -1) {
437 strerr_warn4(WARNING,"message will be delivered twice! unable to unlink ",fn.s,": ",&strerr_sys);
438 if (!stralloc_cat(&deadfiles,&fn)) die_nomem();
439 }
440 continue;
441 }
442
443 flagtimeout = 0;
444
445 if (flaglifetime && (status != 'D')) {
446 if (stat(fn.s,&st) == -1)
447 strerr_warn4(WARNING,"assuming message is still alive; unable to stat ",fn.s,": ",&strerr_sys);
448 else
449 if (now() > st.st_mtime + lifetime) {
450 status = 'D';
451 flagtimeout = 1;
452 }
453 }
454
455 if (flagbounce)
456 if (status == 'D') {
457 int fd;
458 int r;
459
460 info(" bounced: ");
461
462 fd = open_read(fn.s);
463 if (fd == -1) {
464 strerr_warn4(WARNING,"unable to read ",fn.s,": ",&strerr_sys);
465 if (!stralloc_cat(&deadfiles,&fn)) die_nomem();
466 continue;
467 }
468
469 r = bounce(fd,&err,flagtimeout);
470 if (r == -1)
471 strerr_warn4(WARNING,"unable to bounce ",fn.s,": ",&strerr_sys);
472 if (r == -2)
473 strerr_warn5(WARNING,"unable to bounce ",fn.s,": qq failed: ",qqx + 1,0);
474 if (r == -3)
475 strerr_warn4(WARNING,"unable to bounce ",fn.s,": bad file format",0);
476 if (r == 2)
477 strerr_warn4(INFO,"discarding ",fn.s,": triple bounce",0);
478 if (r == 1) {
479 substdio_puts(subfderr,INFO);
480 substdio_puts(subfderr,"returned ");
481 substdio_puts(subfderr,fn.s);
482 substdio_puts(subfderr,": qp ");
483 substdio_put(subfderr,num,fmt_ulong(num,qp));
484 substdio_puts(subfderr,"\n");
485 substdio_flush(subfderr);
486 }
487
488 close(fd);
489
490 if (r > 0) {
491 if (unlink(fn.s) == 0)
492 continue;
493 strerr_warn4(WARNING,"message has been bounced but not removed! unable to unlink ",fn.s,": ",&strerr_sys);
494 }
495 if (!stralloc_cat(&deadfiles,&fn)) die_nomem();
496 continue;
497 }
498
499 info(status == 'D' ? " failed: " : " failed temporarily: ");
500 if (!stralloc_cat(&deadfiles,&fn)) die_nomem();
501 }
502
503 close(pic2p[0]);
504
505 if (wait_pid(&wstat,pid) == -1)
506 strerr_die2sys(111,FATAL,"unable to get scanner status: ");
507 if (wait_crashed(wstat))
508 strerr_die2x(111,FATAL,"scanner crashed");
509 switch(wait_exitcode(wstat)) {
510 case 0: _exit(deadfiles.len ? 111 : 0); /* scanner says no files */
511 case 100: _exit(100); /* scanner has produced error message */
512 case 111: _exit(111); /* scanner has produced error message */
513 }
514
515 if (!progress)
516 strerr_die2x(111,FATAL,"making no progress, giving up");
517 }
518 }
519