diff options
| author | manuel <manuel@mausz.at> | 2026-05-21 15:36:11 +0200 |
|---|---|---|
| committer | manuel <manuel@mausz.at> | 2026-05-21 15:36:11 +0200 |
| commit | 06c245e98b6871f2145ca05863805666237e8afc (patch) | |
| tree | a2b70ca28e48daa4e701cc98fdd50a94cca84330 /qmail-send.c | |
| parent | 2a7f632316cda04ddaf6672c64d61a1ca1d2a6e6 (diff) | |
| download | qmail-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)
Diffstat (limited to 'qmail-send.c')
| -rw-r--r-- | qmail-send.c | 124 |
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 | ||
| 688 | GEN_ALLOC_typedef(saa,stralloc,sa,len,a) | ||
| 689 | GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus) | ||
| 690 | static stralloc sauninit = {0}; | ||
| 691 | |||
| 692 | saa reciplist = {0}; | ||
| 693 | |||
| 694 | static 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 | |||
| 681 | int injectbounce(id) | 710 | int injectbounce(id) |
| 682 | unsigned long id; | 711 | unsigned long id; |
| 683 | { | 712 | { |
| @@ -739,18 +768,30 @@ unsigned long id; | |||
| 739 | qmail_puts(&qqt,"\nTo: "); | 768 | qmail_puts(&qqt,"\nTo: "); |
| 740 | while (!quote2("ed,bouncerecip)) nomem(); | 769 | while (!quote2("ed,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"); |
| 743 | Subject: 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\ |
| 756 | Hi. This is the qmail-send program at "); | 797 | 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\ | |||
| 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) { |
