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(&quoted,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(&quoted,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(&quoted,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