summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2026-05-21 15:36:11 +0200
committermanuel <manuel@mausz.at>2026-05-21 15:36:11 +0200
commit06c245e98b6871f2145ca05863805666237e8afc (patch)
treea2b70ca28e48daa4e701cc98fdd50a94cca84330
parent2a7f632316cda04ddaf6672c64d61a1ca1d2a6e6 (diff)
downloadqmail-06c245e98b6871f2145ca05863805666237e8afc.tar.gz
qmail-06c245e98b6871f2145ca05863805666237e8afc.tar.bz2
qmail-06c245e98b6871f2145ca05863805666237e8afc.zip
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)
-rw-r--r--qmail-send.c124
1 files 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 @@
17#include "alloc.h" 17#include "alloc.h"
18#include "error.h" 18#include "error.h"
19#include "stralloc.h" 19#include "stralloc.h"
20#include "gen_alloc.h"
21#include "gen_allocdefs.h"
20#include "str.h" 22#include "str.h"
21#include "byte.h" 23#include "byte.h"
22#include "fmt.h" 24#include "fmt.h"
@@ -646,6 +648,11 @@ char *report;
646 if (bouncetext.s[pos] == '\n') 648 if (bouncetext.s[pos] == '\n')
647 bouncetext.s[pos] = '_'; 649 bouncetext.s[pos] = '_';
648 while (!stralloc_cats(&bouncetext,">:\n")) nomem(); 650 while (!stralloc_cats(&bouncetext,">:\n")) nomem();
651 if (usebounceencap > 0) {
652 while (!stralloc_cats(&bouncetext,"XQSBMF-RECIP=")) nomem();
653 while (!stralloc_cats(&bouncetext,stripvdomprepend(recip))) nomem();
654 while (!stralloc_cats(&bouncetext,"\n")) nomem();
655 }
649 while (!stralloc_cats(&bouncetext,report)) nomem(); 656 while (!stralloc_cats(&bouncetext,report)) nomem();
650 if (report[0]) 657 if (report[0])
651 if (report[str_len(report) - 1] != '\n') 658 if (report[str_len(report) - 1] != '\n')
@@ -678,6 +685,28 @@ char *report;
678 close(fd); 685 close(fd);
679} 686}
680 687
688GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
689GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
690static stralloc sauninit = {0};
691
692saa reciplist = {0};
693
694static int parse_qsbmf(const stralloc *line)
695{
696 if (!str_start(line->s, "XQSBMF-"))
697 return 0;
698
699 if (str_start(line->s, "XQSBMF-RECIP=")) {
700 if (!saa_readyplus(&reciplist, 1)) nomem();
701 stralloc *sa = &reciplist.sa[reciplist.len];
702 *sa = sauninit;
703 const int prefix = sizeof("XQSBMF-RECIP=") - 1;
704 if (!stralloc_copyb(sa, line->s + prefix, line->len - prefix)) nomem();
705 ++reciplist.len;
706 }
707 return 1;
708}
709
681int injectbounce(id) 710int injectbounce(id)
682unsigned long id; 711unsigned long id;
683{ 712{
@@ -739,18 +768,30 @@ unsigned long id;
739 qmail_puts(&qqt,"\nTo: "); 768 qmail_puts(&qqt,"\nTo: ");
740 while (!quote2(&quoted,bouncerecip)) nomem(); 769 while (!quote2(&quoted,bouncerecip)) nomem();
741 qmail_put(&qqt,quoted.s,quoted.len); 770 qmail_put(&qqt,quoted.s,quoted.len);
742 qmail_puts(&qqt,"\n\ 771 qmail_puts(&qqt,"\nSubject: failure notice\n");
743Subject: failure notice\n"); 772 qmail_puts(&qqt, "Auto-Submitted: auto-replied\n");
744 if(usebounceencap > 0) { 773 if(usebounceencap > 0) {
745 strid[fmt_ulong(strid,id)] = 0; 774 strid[fmt_ulong(strid,id)] = 0;
746 strqp[fmt_ulong(strqp,qp)] = 0; 775 strqp[fmt_ulong(strqp,qp)] = 0;
747 qmail_puts(&qqt,"MIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"qrfm-"); 776 qmail_puts(&qqt,"MIME-Version: 1.0\n");
777#ifdef SMTPUTF8
778 qmail_puts(&qqt,"Content-Transfer-Encoding: 8bit\n");
779#else
780 qmail_puts(&qqt,"Content-Transfer-Encoding: 7bit\n");
781#endif
782 qmail_puts(&qqt,"Content-Type: multipart/report; report-type=delivery-status;\n\tboundary=\"qrfm-");
748 qmail_puts(&qqt,strid); 783 qmail_puts(&qqt,strid);
749 qmail_puts(&qqt,strqp); 784 qmail_puts(&qqt,strqp);
750 qmail_puts(&qqt,"\"\n\nThis is a multi-part message in MIME format.\n\n--qrfm-"); 785 qmail_puts(&qqt,"\"\n\nThis is a MIME-encapsulated message.\n\n--qrfm-");
751 qmail_puts(&qqt,strid); 786 qmail_puts(&qqt,strid);
752 qmail_puts(&qqt,strqp); 787 qmail_puts(&qqt,strqp);
753 qmail_puts(&qqt,"\nContent-Type: text/plain\n"); 788 qmail_puts(&qqt,"\nContent-Description: Notification\n");
789 qmail_puts(&qqt,"Content-Type: text/plain; charset=utf-8\n");
790#ifdef SMTPUTF8
791 qmail_puts(&qqt,"Content-Transfer-Encoding: 8bit\n");
792#else
793 qmail_puts(&qqt,"Content-Transfer-Encoding: 7bit\n");
794#endif
754 } 795 }
755 qmail_puts(&qqt,"\n\ 796 qmail_puts(&qqt,"\n\
756Hi. This is the qmail-send program at "); 797Hi. 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\
770 else 811 else
771 { 812 {
772 substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf)); 813 substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf));
773 while ((r = substdio_get(&ssread,buf,sizeof(buf))) > 0) 814 char ch;
774 qmail_put(&qqt,buf,r); 815 static stralloc line = {0};
816 if (!saa_readyplus(&reciplist, 0)) nomem();
817 reciplist.len = 0;
818 while ((r = substdio_get(&ssread, &ch, 1)) == 1) {
819 if (!stralloc_append(&line, &ch))
820 qmail_fail(&qqt);
821 if (ch == '\n') {
822 if (!stralloc_0(&line))
823 qmail_fail(&qqt);
824 line.len--;
825 if (!parse_qsbmf(&line))
826 qmail_put(&qqt, line.s, line.len);
827 line.len = 0;
828 }
829 }
830 if (line.len > 0) {
831 if (!stralloc_0(&line))
832 qmail_fail(&qqt);
833 line.len--;
834 if (!parse_qsbmf(&line))
835 qmail_put(&qqt, line.s, line.len);
836 }
775 close(fd); 837 close(fd);
776 if (r == -1) 838 if (r == -1)
777 qmail_fail(&qqt); 839 qmail_fail(&qqt);
778 } 840 }
779 841
780 if(usebounceencap > 0) { 842 if(usebounceencap > 0) {
781 qmail_puts(&qqt,*sender.s ? "--- Attached is a copy of the message.\n\n" : "--- Attached is the original bounce.\n\n"); 843 qmail_puts(&qqt,*sender.s
844 ? (bouncemaxbytes > 0 ? "--- Attached is a copy of the message.\n\n" : "--- Attached are the original headers of the message.\n\n")
845 : (bouncemaxbytes > 0 ? "--- Attached is the original bounce.\n\n" : "--- Attached are the original headers of the bounce.\n\n"));
782 qmail_puts(&qqt,"--qrfm-"); 846 qmail_puts(&qqt,"--qrfm-");
783 qmail_puts(&qqt,strid); 847 qmail_puts(&qqt,strid);
784 qmail_puts(&qqt,strqp); 848 qmail_puts(&qqt,strqp);
785 qmail_puts(&qqt,"\nContent-Type: message/rfc822\nContent-Disposition: attachment\n\n"); 849 qmail_puts(&qqt,"\nContent-Description: Delivery report\n");
850 qmail_puts(&qqt,"Content-Type: message/delivery-status\n\n");
851 qmail_puts(&qqt,"Reporting-MTA: dns; ");
852 qmail_put(&qqt,bouncehost.s,bouncehost.len);
853 qmail_puts(&qqt,"\nArrival-");
854 while (!newfield_datemake(birth)) nomem();
855 qmail_put(&qqt,newfield_date.s,newfield_date.len);
856 for (int i = 0; i < reciplist.len; ++i) {
857 qmail_puts(&qqt,"\nOriginal-Recipient: rfc822; ");
858 qmail_put(&qqt, reciplist.sa[i].s, reciplist.sa[i].len);
859 qmail_puts(&qqt,"Action: failed\n");
860 }
861 qmail_puts(&qqt,"\n--qrfm-");
862 qmail_puts(&qqt,strid);
863 qmail_puts(&qqt,strqp);
864 qmail_puts(&qqt,"\nContent-Description: Undelivered Message\n");
865 qmail_puts(&qqt,"Content-Type: message/rfc822\n\n");
786 } else { 866 } else {
787 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"); 867 qmail_puts(&qqt,*sender.s
868 ? (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")
869 : (bouncemaxbytes > 0 ? "--- Below this line is the original bounce.\n\n" : "--- Below this line are the original headers of the bounce.\n\n"));
788 } 870 }
789 871
790 qmail_puts(&qqt,"Return-Path: <"); 872 qmail_puts(&qqt,"Return-Path: <");
@@ -798,19 +880,25 @@ I tried to deliver a bounce message to this address, but the bounce bounced!\n\
798 else 880 else
799 { 881 {
800 int bytestogo = bouncemaxbytes; 882 int bytestogo = bouncemaxbytes;
801 int bytestoget = (bytestogo < sizeof buf) ? bytestogo : sizeof buf;
802 substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf)); 883 substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf));
803 while (bytestoget > 0 && (r = substdio_get(&ssread,buf,bytestoget)) > 0) { 884 char ch, prev = 0;
804 qmail_put(&qqt,buf,r); 885 while ((r = substdio_get(&ssread, &ch, 1)) == 1) {
805 bytestogo -= bytestoget; 886 qmail_put(&qqt, &ch, 1);
806 bytestoget = (bytestogo < sizeof buf) ? bytestogo : sizeof buf; 887 if (bouncemaxbytes == 0) {
807 } 888 if (prev == '\n' && ch == '\n')
808 if (r > 0) { 889 break;
809 qmail_puts(&qqt,"\n\n--- End of message stripped.\n"); 890 prev = ch;
891 }
892 else if (--bytestogo == 0)
893 break;
810 } 894 }
811 close(fd); 895 close(fd);
812 if (r == -1) 896 if (r == -1)
813 qmail_fail(&qqt); 897 qmail_fail(&qqt);
898 if (bouncemaxbytes == 0)
899 qmail_puts(&qqt, "(Body supressed)\n");
900 else if (bytestogo == 0)
901 qmail_puts(&qqt, "\n\n--- End of message stripped.\n");
814 } 902 }
815 903
816 if(usebounceencap > 0) { 904 if(usebounceencap > 0) {