From 06c245e98b6871f2145ca05863805666237e8afc Mon Sep 17 00:00:00 2001 From: manuel Date: Thu, 21 May 2026 15:36:11 +0200 Subject: qmail-send: add QSBMF recipient capture and RFC-compliant DSN encapsulation with header-only mode * parse and store original recipients from bounce metadata * include Original-Recipient fields in DSN delivery-status part * extend multipart/report formatting for RFC-compliant bounce encapsulation * improve MIME structure and headers (Auto-Submitted, Content-Type, encoding) --- qmail-send.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 106 insertions(+), 18 deletions(-) diff --git a/qmail-send.c b/qmail-send.c index 9d76228..8eed423 100644 --- a/qmail-send.c +++ b/qmail-send.c @@ -17,6 +17,8 @@ #include "alloc.h" #include "error.h" #include "stralloc.h" +#include "gen_alloc.h" +#include "gen_allocdefs.h" #include "str.h" #include "byte.h" #include "fmt.h" @@ -646,6 +648,11 @@ char *report; if (bouncetext.s[pos] == '\n') bouncetext.s[pos] = '_'; while (!stralloc_cats(&bouncetext,">:\n")) nomem(); + if (usebounceencap > 0) { + while (!stralloc_cats(&bouncetext,"XQSBMF-RECIP=")) nomem(); + while (!stralloc_cats(&bouncetext,stripvdomprepend(recip))) nomem(); + while (!stralloc_cats(&bouncetext,"\n")) nomem(); + } while (!stralloc_cats(&bouncetext,report)) nomem(); if (report[0]) if (report[str_len(report) - 1] != '\n') @@ -678,6 +685,28 @@ char *report; close(fd); } +GEN_ALLOC_typedef(saa,stralloc,sa,len,a) +GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus) +static stralloc sauninit = {0}; + +saa reciplist = {0}; + +static int parse_qsbmf(const stralloc *line) +{ + if (!str_start(line->s, "XQSBMF-")) + return 0; + + if (str_start(line->s, "XQSBMF-RECIP=")) { + if (!saa_readyplus(&reciplist, 1)) nomem(); + stralloc *sa = &reciplist.sa[reciplist.len]; + *sa = sauninit; + const int prefix = sizeof("XQSBMF-RECIP=") - 1; + if (!stralloc_copyb(sa, line->s + prefix, line->len - prefix)) nomem(); + ++reciplist.len; + } + return 1; +} + int injectbounce(id) unsigned long id; { @@ -739,18 +768,30 @@ unsigned long id; qmail_puts(&qqt,"\nTo: "); while (!quote2("ed,bouncerecip)) nomem(); qmail_put(&qqt,quoted.s,quoted.len); - qmail_puts(&qqt,"\n\ -Subject: failure notice\n"); + qmail_puts(&qqt,"\nSubject: failure notice\n"); + qmail_puts(&qqt, "Auto-Submitted: auto-replied\n"); if(usebounceencap > 0) { strid[fmt_ulong(strid,id)] = 0; strqp[fmt_ulong(strqp,qp)] = 0; - qmail_puts(&qqt,"MIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"qrfm-"); + qmail_puts(&qqt,"MIME-Version: 1.0\n"); +#ifdef SMTPUTF8 + qmail_puts(&qqt,"Content-Transfer-Encoding: 8bit\n"); +#else + qmail_puts(&qqt,"Content-Transfer-Encoding: 7bit\n"); +#endif + qmail_puts(&qqt,"Content-Type: multipart/report; report-type=delivery-status;\n\tboundary=\"qrfm-"); qmail_puts(&qqt,strid); qmail_puts(&qqt,strqp); - qmail_puts(&qqt,"\"\n\nThis is a multi-part message in MIME format.\n\n--qrfm-"); + qmail_puts(&qqt,"\"\n\nThis is a MIME-encapsulated message.\n\n--qrfm-"); qmail_puts(&qqt,strid); qmail_puts(&qqt,strqp); - qmail_puts(&qqt,"\nContent-Type: text/plain\n"); + qmail_puts(&qqt,"\nContent-Description: Notification\n"); + qmail_puts(&qqt,"Content-Type: text/plain; charset=utf-8\n"); +#ifdef SMTPUTF8 + qmail_puts(&qqt,"Content-Transfer-Encoding: 8bit\n"); +#else + qmail_puts(&qqt,"Content-Transfer-Encoding: 7bit\n"); +#endif } qmail_puts(&qqt,"\n\ Hi. This is the qmail-send program at "); @@ -770,21 +811,62 @@ I tried to deliver a bounce message to this address, but the bounce bounced!\n\ else { substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf)); - while ((r = substdio_get(&ssread,buf,sizeof(buf))) > 0) - qmail_put(&qqt,buf,r); + char ch; + static stralloc line = {0}; + if (!saa_readyplus(&reciplist, 0)) nomem(); + reciplist.len = 0; + while ((r = substdio_get(&ssread, &ch, 1)) == 1) { + if (!stralloc_append(&line, &ch)) + qmail_fail(&qqt); + if (ch == '\n') { + if (!stralloc_0(&line)) + qmail_fail(&qqt); + line.len--; + if (!parse_qsbmf(&line)) + qmail_put(&qqt, line.s, line.len); + line.len = 0; + } + } + if (line.len > 0) { + if (!stralloc_0(&line)) + qmail_fail(&qqt); + line.len--; + if (!parse_qsbmf(&line)) + qmail_put(&qqt, line.s, line.len); + } close(fd); if (r == -1) qmail_fail(&qqt); } if(usebounceencap > 0) { - qmail_puts(&qqt,*sender.s ? "--- Attached is a copy of the message.\n\n" : "--- Attached is the original bounce.\n\n"); + qmail_puts(&qqt,*sender.s + ? (bouncemaxbytes > 0 ? "--- Attached is a copy of the message.\n\n" : "--- Attached are the original headers of the message.\n\n") + : (bouncemaxbytes > 0 ? "--- Attached is the original bounce.\n\n" : "--- Attached are the original headers of the bounce.\n\n")); qmail_puts(&qqt,"--qrfm-"); qmail_puts(&qqt,strid); qmail_puts(&qqt,strqp); - qmail_puts(&qqt,"\nContent-Type: message/rfc822\nContent-Disposition: attachment\n\n"); + qmail_puts(&qqt,"\nContent-Description: Delivery report\n"); + qmail_puts(&qqt,"Content-Type: message/delivery-status\n\n"); + qmail_puts(&qqt,"Reporting-MTA: dns; "); + qmail_put(&qqt,bouncehost.s,bouncehost.len); + qmail_puts(&qqt,"\nArrival-"); + while (!newfield_datemake(birth)) nomem(); + qmail_put(&qqt,newfield_date.s,newfield_date.len); + for (int i = 0; i < reciplist.len; ++i) { + qmail_puts(&qqt,"\nOriginal-Recipient: rfc822; "); + qmail_put(&qqt, reciplist.sa[i].s, reciplist.sa[i].len); + qmail_puts(&qqt,"Action: failed\n"); + } + qmail_puts(&qqt,"\n--qrfm-"); + qmail_puts(&qqt,strid); + qmail_puts(&qqt,strqp); + qmail_puts(&qqt,"\nContent-Description: Undelivered Message\n"); + qmail_puts(&qqt,"Content-Type: message/rfc822\n\n"); } else { - qmail_puts(&qqt,*sender.s ? "--- Below this line is a copy of the message.\n\n" : "--- Below this line is the original bounce.\n\n"); + qmail_puts(&qqt,*sender.s + ? (bouncemaxbytes > 0 ? "--- Below this line is a copy of the message.\n\n" : "--- Below this line are the original headers of the message.\n\n") + : (bouncemaxbytes > 0 ? "--- Below this line is the original bounce.\n\n" : "--- Below this line are the original headers of the bounce.\n\n")); } qmail_puts(&qqt,"Return-Path: <"); @@ -798,19 +880,25 @@ I tried to deliver a bounce message to this address, but the bounce bounced!\n\ else { int bytestogo = bouncemaxbytes; - int bytestoget = (bytestogo < sizeof buf) ? bytestogo : sizeof buf; substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf)); - while (bytestoget > 0 && (r = substdio_get(&ssread,buf,bytestoget)) > 0) { - qmail_put(&qqt,buf,r); - bytestogo -= bytestoget; - bytestoget = (bytestogo < sizeof buf) ? bytestogo : sizeof buf; - } - if (r > 0) { - qmail_puts(&qqt,"\n\n--- End of message stripped.\n"); + char ch, prev = 0; + while ((r = substdio_get(&ssread, &ch, 1)) == 1) { + qmail_put(&qqt, &ch, 1); + if (bouncemaxbytes == 0) { + if (prev == '\n' && ch == '\n') + break; + prev = ch; + } + else if (--bytestogo == 0) + break; } close(fd); if (r == -1) qmail_fail(&qqt); + if (bouncemaxbytes == 0) + qmail_puts(&qqt, "(Body supressed)\n"); + else if (bytestogo == 0) + qmail_puts(&qqt, "\n\n--- End of message stripped.\n"); } if(usebounceencap > 0) { -- cgit v1.2.3