This patch adds control of queue lifetimes to qmail. This patch permits user control of the length of time a message will live in the queue before being bounced. For example, a very urgent message might be sent with a queue lifetime of 1 hour. If it is not sent in that time, it will be bounced, and the user will know to try some other means. This is only a part of the problem of handling urgent e-mail, since the queue lifetime is not passed on to the next host. But a partial solution can still be helpful in some circumstances. This patch also permits the queue lifetime of a bounce message to be controlled separately from that of ordinary messages. Bounce messages which are not delivered quickly are often the result of spam to bogus local addresses or otherwise have undeliverable sender addresses. There is often no reason to let them sit in the mail queue for a long time before double bouncing. With this patch, you can set the environment variable QMAILQUEUELIFETIME when calling qmail-inject. It should be set to a decimal string: the number of seconds the mail message should live in the queue. If a delivery of the mail message fails after that many seconds have passed, any temporary error will be treated as a permanent error, and the mail message will be bounced to the sender. This patch adds two new control files: bouncequeuelifetime: the number of seconds that a bounce message should remain in the queue before it is double bounced. The default is 604800 (1 week). maxqueuelifetime: the maximum number of seconds that QMAILQUEUELIFETIME may be set to. This also controls queuelifetime and bouncequeuelifetime. The default is 1209600 (2 weeks). It would be possible to add another control file to specify the queue lifetime of a mail message being relayed for a remote system, as opposed to one that is injected locally, but I did not implement that. DJB, please consider implementing similar functionality in the next release of qmail. Thanks. Ian diff -u qmail-1.03/INTERNALS qmail-1.03-lifetime/INTERNALS --- qmail-1.03/INTERNALS Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/INTERNALS Tue May 2 14:28:42 2000 @@ -140,11 +140,12 @@ 7. Further notes -Currently info/457 serves two purposes: first, it records the envelope +Currently info/457 serves three purposes: first, it records the envelope sender; second, its modification time is used to decide when a message -has been in the queue too long. In the future info/457 may store more -information. Any non-backwards-compatible changes will be identified by -version numbers. +has been in the queue too long; third, it optionally records the +lifetime of the message in the queue. In the future info/457 may store +more information. Any non-backwards-compatible changes will be +identified by version numbers. When qmail-queue has successfully placed a message into the queue, it pulls a trigger offered by qmail-send. Here is the current triggering diff -u qmail-1.03/Makefile qmail-1.03-lifetime/Makefile --- qmail-1.03/Makefile Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/Makefile Thu Apr 27 10:01:28 2000 @@ -1639,10 +1639,10 @@ qreceipt: \ load qreceipt.o headerbody.o hfield.o quote.o token822.o qmail.o \ getln.a fd.a wait.a sig.a env.a stralloc.a alloc.a substdio.a error.a \ -str.a auto_qmail.o +str.a fs.a auto_qmail.o ./load qreceipt headerbody.o hfield.o quote.o token822.o \ qmail.o getln.a fd.a wait.a sig.a env.a stralloc.a alloc.a \ - substdio.a error.a str.a auto_qmail.o + substdio.a error.a str.a fs.a auto_qmail.o qreceipt.0: \ qreceipt.1 diff -u qmail-1.03/condredirect.c qmail-1.03-lifetime/condredirect.c --- qmail-1.03/condredirect.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/condredirect.c Thu Apr 27 09:36:23 2000 @@ -77,7 +77,7 @@ num[fmt_ulong(num,qmail_qp(&qqt))] = 0; - qmail_from(&qqt,sender); + qmail_from(&qqt,sender,0); qmail_to(&qqt,argv[1]); qqx = qmail_close(&qqt); if (*qqx) strerr_die2x(*qqx == 'D' ? 100 : 111,FATAL,qqx + 1); diff -u qmail-1.03/forward.c qmail-1.03-lifetime/forward.c --- qmail-1.03/forward.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/forward.c Thu Apr 27 09:36:24 2000 @@ -52,7 +52,7 @@ num[fmt_ulong(num,qmail_qp(&qqt))] = 0; - qmail_from(&qqt,sender); + qmail_from(&qqt,sender,0); while (*++argv) qmail_to(&qqt,*argv); qqx = qmail_close(&qqt); if (*qqx) strerr_die2x(*qqx == 'D' ? 100 : 111,FATAL,qqx + 1); diff -u qmail-1.03/qmail-control.9 qmail-1.03-lifetime/qmail-control.9 --- qmail-1.03/qmail-control.9 Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-control.9 Tue May 2 14:24:32 2000 @@ -43,6 +43,7 @@ .I badmailfrom \fR(none) \fRqmail-smtpd .I bouncefrom \fRMAILER-DAEMON \fRqmail-send .I bouncehost \fIme \fRqmail-send +.I bouncequeuelifetime \fR604800 \fRqmail-send .I concurrencylocal \fR10 \fRqmail-send .I concurrencyremote \fR20 \fRqmail-send .I defaultdomain \fIme \fRqmail-inject @@ -55,6 +56,7 @@ .I idhost \fIme \fRqmail-inject .I localiphost \fIme \fRqmail-smtpd .I locals \fIme \fRqmail-send +.I maxqueuelifetime \fR1209600 \fRqmail-send .I morercpthosts \fR(none) \fRqmail-smtpd .I percenthack \fR(none) \fRqmail-send .I plusdomain \fIme \fRqmail-inject diff -u qmail-1.03/qmail-inject.8 qmail-1.03-lifetime/qmail-inject.8 --- qmail-1.03/qmail-inject.8 Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-inject.8 Tue May 2 09:11:12 2000 @@ -91,6 +91,11 @@ does not add Mail-Followup-To to a message that already has one. +If +.B QMAILQUEUELIFETIME +is set, it sets the maximum number of seconds the mail message will +spend in the mail queue before being bounced back to the sender. + The .B QMAILINJECT environment variable diff -u qmail-1.03/qmail-inject.c qmail-1.03-lifetime/qmail-inject.c --- qmail-1.03/qmail-inject.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-inject.c Thu Apr 27 09:57:22 2000 @@ -7,6 +7,7 @@ #include "alloc.h" #include "str.h" #include "fmt.h" +#include "scan.h" #include "hfield.h" #include "token822.h" #include "control.h" @@ -39,6 +40,7 @@ int mailusertokentype; char *mailrhost; char *mailruser; +int lifetime = 0; stralloc control_idhost = {0}; stralloc control_defaultdomain = {0}; @@ -99,7 +101,7 @@ int i; if (!stralloc_0(&sender)) die_nomem(); - qmail_from(&qqt,sender.s); + qmail_from(&qqt,sender.s,lifetime); for (i = 0;i < reciplist.len;++i) { @@ -689,6 +691,7 @@ int i; int opt; int recipstrategy; + char *envlifetime; sig_pipeignore(); @@ -721,6 +724,14 @@ if (quote_need(mailuser,str_len(mailuser))) mailusertokentype = TOKEN822_QUOTE; mailruser = env_get("QMAILSUSER"); if (!mailruser) mailruser = mailuser; + + envlifetime = env_get("QMAILQUEUELIFETIME"); + if (envlifetime) + { + unsigned long ult; + scan_ulong(envlifetime,&ult); + lifetime = ult; + } for (i = 0;i < H_NUM;++i) htypeseen[i] = 0; diff -u qmail-1.03/qmail-local.c qmail-1.03-lifetime/qmail-local.c --- qmail-1.03/qmail-local.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-local.c Thu Apr 27 09:36:28 2000 @@ -282,7 +282,7 @@ qmail_put(&qqt,messline.s,messline.len); } while (match); - qmail_from(&qqt,ueo.s); + qmail_from(&qqt,ueo.s,0); while (*recips) qmail_to(&qqt,*recips++); qqx = qmail_close(&qqt); if (!*qqx) return; diff -u qmail-1.03/qmail-qmqpd.c qmail-1.03-lifetime/qmail-qmqpd.c --- qmail-1.03/qmail-qmqpd.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-qmqpd.c Thu Apr 27 09:36:31 2000 @@ -133,9 +133,9 @@ getcomma(); if (getbuf()) - qmail_from(&qq,buf); + qmail_from(&qq,buf,0); else { - qmail_from(&qq,""); + qmail_from(&qq,"",0); qmail_fail(&qq); flagok = 0; } diff -u qmail-1.03/qmail-qmtpd.c qmail-1.03-lifetime/qmail-qmtpd.c --- qmail-1.03/qmail-qmtpd.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-qmtpd.c Thu Apr 27 09:36:33 2000 @@ -180,7 +180,7 @@ getcomma(); flagbother = 0; - qmail_from(&qq,buf); + qmail_from(&qq,buf,0); if (!flagsenderok) qmail_fail(&qq); biglen = getlen(); diff -u qmail-1.03/qmail-qread.c qmail-1.03-lifetime/qmail-qread.c --- qmail-1.03/qmail-qread.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-qread.c Tue May 2 08:44:53 2000 @@ -50,6 +50,7 @@ char inbuf[1024]; stralloc sender = {0}; +stralloc lifetime = {0}; unsigned long id; datetime_sec qtime; @@ -73,6 +74,11 @@ i = fmt_str(s," <"); len += i; if (s) s += i; i = fmt_str(s,sender.s + 1); len += i; if (s) s += i; i = fmt_str(s,"> "); len += i; if (s) s += i; + if (lifetime.len) + { + i = fmt_str(s," lifetime "); len += i; if (s) s += i; + i = fmt_str(s,lifetime.s + 1); len += i; if (s) s += i; + } if (flagbounce) { i = fmt_str(s," bouncing"); len += i; if (s) s += i; @@ -135,6 +141,11 @@ if (fd == -1) { err(id); continue; } substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); if (getln(&ss,&sender,&match,0) == -1) die_nomem(); + if (sender.s[0] == 'L') + { + stralloc_copy(&lifetime,&sender); + if (getln(&ss,&sender,&match,0) == -1) die_nomem(); + } if (fstat(fd,&st) == -1) { close(fd); err(id); continue; } close(fd); qtime = st.st_mtime; diff -u qmail-1.03/qmail-queue.8 qmail-1.03-lifetime/qmail-queue.8 --- qmail-1.03/qmail-queue.8 Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-queue.8 Tue May 2 09:13:37 2000 @@ -12,8 +12,12 @@ .BR qmail-send . The envelope information is +an optional lifetime +followed by an envelope sender address followed by a list of envelope recipient addresses. +The optional lifetime is a decimal string, preceded by the letter L +and terminated by a 0 byte. The sender address is preceded by the letter F and terminated by a 0 byte. Each recipient address is preceded by the letter T @@ -67,7 +71,7 @@ indicate permanent errors: .TP 5 .B 11 -Address too long. +Address or lifetime string too long. .TP .B 31 Mail server permanently refuses to send the message to any recipients. diff -u qmail-1.03/qmail-queue.c qmail-1.03-lifetime/qmail-queue.c --- qmail-1.03/qmail-queue.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-queue.c Thu Apr 27 09:39:34 2000 @@ -217,6 +217,18 @@ if (substdio_bput(&ssout,"",1) == -1) die_write(); if (substdio_get(&ssin,&ch,1) < 1) die_read(); + if (ch == 'L') + { + if (substdio_bput(&ssout,&ch,1) == -1) die_write(); + for (len = 0;len < FMT_ULONG+1;++len) + { + if (substdio_get(&ssin,&ch,1) < 1) die_read(); + if (substdio_put(&ssout,&ch,1) == -1) die_write(); + if (!ch) break; + } + if (len >= FMT_ULONG+1) die(11); + if (substdio_get(&ssin,&ch,1) < 1) die_read(); + } if (ch != 'F') die(91); if (substdio_bput(&ssout,&ch,1) == -1) die_write(); for (len = 0;len < ADDR;++len) diff -u qmail-1.03/qmail-send.9 qmail-1.03-lifetime/qmail-send.9 --- qmail-1.03/qmail-send.9 Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-send.9 Tue May 2 14:23:22 2000 @@ -77,6 +77,10 @@ .B From: \fIbouncefrom\fB@\fIbouncehost\fR, although its envelope sender is empty. .TP 5 +.I bouncequeuelifetime +The number of seconds a bounce message will stay in the queue before +it is double-bounced. +.TP 5 .I concurrencylocal Maximum number of simultaneous local delivery attempts. Default: 10. @@ -147,6 +151,11 @@ is listed in .IR locals . .TP 5 +.I maxqueuelifetime +The maximum number of seconds a message can stay in the queue. This +sets a limit to the use of QMAILQUEUELIFETIME in +.BR qmail-inject . +.TP 5 .I percenthack List of domain names where the percent hack is applied. If @@ -242,5 +251,6 @@ qmail-log(5), qmail-queue(8), qmail-clean(8), +qmail-inject(8), qmail-lspawn(8), qmail-rspawn(8) diff -u qmail-1.03/qmail-send.c qmail-1.03-lifetime/qmail-send.c --- qmail-1.03/qmail-send.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-send.c Thu Apr 27 09:53:31 2000 @@ -43,6 +43,8 @@ #define OSSIFIED 129600 /* 36 hours; _must_ exceed q-q's DEATH (24 hours) */ int lifetime = 604800; +int maxlifetime = 1209600; +int bouncelifetime = 604800; stralloc percenthack = {0}; struct constmap mappercenthack; @@ -195,9 +197,10 @@ /* this file is too long ---------------------------------------------- INFO */ -int getinfo(sa,dt,id) +int getinfo(sa,dt,lt,id) stralloc *sa; datetime_sec *dt; +int *lt; unsigned long id; { int fdinfo; @@ -213,6 +216,16 @@ if (fstat(fdinfo,&st) == -1) { close(fdinfo); return 0; } substdio_fdbuf(&ss,read,fdinfo,buf,sizeof(buf)); if (getln(&ss,&line,&match,'\0') == -1) { close(fdinfo); return 0; } + if (!match) { close(fdinfo); return 0; } + *lt = lifetime; + if (line.s[0] == 'L') + { + unsigned long ult; + scan_ulong(line.s+1,&ult); + if (ult > maxlifetime) ult = maxlifetime; + *lt = ult; + if (getln(&ss,&line,&match,'\0') == -1) { close(fdinfo); return 0; } + } close(fdinfo); if (!match) return 0; if (line.s[0] != 'F') return 0; @@ -660,9 +673,10 @@ static stralloc sender = {0}; static stralloc quoted = {0}; datetime_sec birth; + int lt; unsigned long qp; - if (!getinfo(&sender,&birth,id)) return 0; /* XXX: print warning */ + if (!getinfo(&sender,&birth,<,id)) return 0; /* XXX: print warning */ /* owner-@host-@[] -> owner-@host */ if (sender.len >= 5) @@ -748,7 +762,7 @@ qmail_fail(&qqt); } - qmail_from(&qqt,bouncesender); + qmail_from(&qqt,bouncesender,bouncelifetime); qmail_to(&qqt,bouncerecip); if (*qmail_close(&qqt)) { log1("warning: trouble injecting bounce message, will try later\n"); return 0; } @@ -1057,6 +1071,7 @@ int c; { datetime_sec birth; + int lt; struct prioq_elt pe; static stralloc line = {0}; int match; @@ -1074,12 +1089,12 @@ pass[c].mpos = 0; pass[c].fd = open_read(fn.s); if (pass[c].fd == -1) goto trouble; - if (!getinfo(&line,&birth,pe.id)) { close(pass[c].fd); goto trouble; } + if (!getinfo(&line,&birth,<,pe.id)) { close(pass[c].fd); goto trouble; } pass[c].id = pe.id; substdio_fdbuf(&pass[c].ss,read,pass[c].fd,pass[c].buf,sizeof(pass[c].buf)); pass[c].j = job_open(pe.id,c); jo[pass[c].j].retry = nextretry(birth,c); - jo[pass[c].j].flagdying = (recent > birth + lifetime); + jo[pass[c].j].flagdying = (recent > birth + lt); while (!stralloc_copy(&jo[pass[c].j].sender,&line)) nomem(); } @@ -1346,6 +1361,13 @@ case 'p': scan_ulong(todoline.s + 1,&pid); break; + case 'L': + if (substdio_putflush(&ssinfo,todoline.s,todoline.len) == -1) + { + fnmake_info(id); + log3("warning: trouble writing to ",fn.s,"\n"); goto fail; + } + break; case 'F': if (substdio_putflush(&ssinfo,todoline.s,todoline.len) == -1) { @@ -1443,6 +1465,10 @@ int getcontrols() { if (control_init() == -1) return 0; if (control_readint(&lifetime,"control/queuelifetime") == -1) return 0; + if (control_readint(&maxlifetime,"control/maxqueuelifetime") == -1) return 0; + if (control_readint(&bouncelifetime,"control/bouncequeuelifetime") == -1) return 0; + if (lifetime > maxlifetime) lifetime = maxlifetime; + if (bouncelifetime > maxlifetime) bouncelifetime = maxlifetime; if (control_readint(&concurrency[0],"control/concurrencylocal") == -1) return 0; if (control_readint(&concurrency[1],"control/concurrencyremote") == -1) return 0; if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) return 0; diff -u qmail-1.03/qmail-smtpd.c qmail-1.03-lifetime/qmail-smtpd.c --- qmail-1.03/qmail-smtpd.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail-smtpd.c Thu Apr 27 09:36:38 2000 @@ -382,7 +382,7 @@ blast(&hops); hops = (hops >= MAXHOPS); if (hops) qmail_fail(&qqt); - qmail_from(&qqt,mailfrom.s); + qmail_from(&qqt,mailfrom.s,0); qmail_put(&qqt,rcptto.s,rcptto.len); qqx = qmail_close(&qqt); diff -u qmail-1.03/qmail.c qmail-1.03-lifetime/qmail.c --- qmail-1.03/qmail.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qmail.c Thu Apr 27 09:37:31 2000 @@ -4,6 +4,7 @@ #include "exit.h" #include "fork.h" #include "fd.h" +#include "fmt.h" #include "qmail.h" #include "auto_qmail.h" @@ -60,11 +61,19 @@ if (!qq->flagerr) if (substdio_puts(&qq->ss,s) == -1) qq->flagerr = 1; } -void qmail_from(qq,s) struct qmail *qq; char *s; +void qmail_from(qq,s,lt) struct qmail *qq; char *s; int lt; { if (substdio_flush(&qq->ss) == -1) qq->flagerr = 1; close(qq->fdm); substdio_fdbuf(&qq->ss,write,qq->fde,qq->buf,sizeof(qq->buf)); + if (lt) + { + char strnum[FMT_ULONG]; + qmail_put(qq,"L",1); + strnum[fmt_ulong(strnum,(unsigned long) lt)] = 0; + qmail_puts(qq,strnum); + qmail_put(qq,"",1); + } qmail_put(qq,"F",1); qmail_puts(qq,s); qmail_put(qq,"",1); diff -u qmail-1.03/qreceipt.c qmail-1.03-lifetime/qreceipt.c --- qmail-1.03/qreceipt.c Mon Jun 15 03:53:16 1998 +++ qmail-1.03-lifetime/qreceipt.c Thu Apr 27 09:36:44 2000 @@ -88,7 +88,7 @@ qmail_put(&qqt,messageid.s,messageid.len); } - qmail_from(&qqt,""); + qmail_from(&qqt,"",0); qmail_to(&qqt,returnpath); qqx = qmail_close(&qqt);