]> git.ipfire.org Git - thirdparty/mlmmj.git/commitdiff
send_mail: add X-Forwarded-To and X-Signed-Recipient headers
authorBaptiste Daroussin <bapt@FreeBSD.org>
Sun, 29 Mar 2026 02:38:25 +0000 (04:38 +0200)
committerBaptiste Daroussin <bapt@FreeBSD.org>
Sun, 29 Mar 2026 07:55:14 +0000 (09:55 +0200)
Add per-recipient headers to improve deliverability and support DARA
(draft ARC replay-resistant authentication):

- X-Forwarded-To: helps Gmail recognize legitimate forwarding
- X-Signed-Recipient: used in ARC signatures to prove the message
  was intended for a specific recipient

Both are enabled independently via control files (xforwardedto and dara).
Like addtohdr, these are incompatible with VERP since they require
per-recipient header injection.

Closes #34

include/send_mail.h
src/mlmmj-send.c
src/send_mail.c
tests/mlmmj.c

index c7fce13b3370b8fe8ca12c9638e29f4d3ad634e0..6a53c5174082b5afa5ab0cfb4942258e67e792d4 100644 (file)
@@ -34,6 +34,8 @@ struct mail {
        const char *replyto;
        FILE *fp;
        bool addtohdr;
+       bool xforwardedto;
+       bool xsignedrecipient;
 };
 
 int newsmtp(struct ml *ml, const char *relayhost);
index 105c6dc8bd29c0a1913f8f73808a381751c7f7f0..3b5e260a93a4cc9a8611f2b6ac79e6bbc2ab892e 100644 (file)
@@ -432,6 +432,8 @@ int main(int argc, char **argv)
                break;
        case '3':
                mail.addtohdr = statctrl(ml.ctrlfd, "addtohdr");
+               mail.xforwardedto = statctrl(ml.ctrlfd, "xforwardedto");
+               mail.xsignedrecipient = statctrl(ml.ctrlfd, "dara");
                /* FALLTHROUGH */
        case '4': /* sending mails to subfile */
                subfd = strtoim(subfilename, 0, INT_MAX, &errp);
@@ -443,6 +445,8 @@ int main(int argc, char **argv)
                break;
        default: /* normal list mail -- now handled when forking */
                mail.addtohdr = statctrl(ml.ctrlfd, "addtohdr");
+               mail.xforwardedto = statctrl(ml.ctrlfd, "xforwardedto");
+               mail.xsignedrecipient = statctrl(ml.ctrlfd, "dara");
                break;
        }
 
@@ -526,10 +530,9 @@ int main(int argc, char **argv)
                        verp = xstrdup("XVERP=-=");
                }
 
-               if(mail.addtohdr && verp) {
-                       log_error(LOG_ARGS, "Cannot use VERP and add "
-                                       "To: header. Not sending with "
-                                       "VERP.");
+               if((mail.addtohdr || mail.xforwardedto || mail.xsignedrecipient) && verp) {
+                       log_error(LOG_ARGS, "Cannot use VERP with per-recipient "
+                                       "headers. Not sending with VERP.");
                        free(verp);
                        verp = NULL;
                }
index 807855032db66b96ee1893518fe98a46d2c1224a..5876a133105298721900cec6020f717ae94f9299 100644 (file)
@@ -327,6 +327,19 @@ send_mail(int sockfd, struct mail *mail, int listfd, int ctrlfd, bool bounce)
                }
        }
 
+       if (mail->xforwardedto) {
+               if (dprintf(sockfd, "X-Forwarded-To: %s\r\n", mail->to) < 0) {
+                       log_error(LOG_ARGS, "Could not write X-Forwarded-To header\n");
+                       return -1;
+               }
+       }
+       if (mail->xsignedrecipient) {
+               if (dprintf(sockfd, "X-Signed-Recipient: i=1; %s\r\n", mail->to) < 0) {
+                       log_error(LOG_ARGS, "Could not write X-Signed-Recipient header\n");
+                       return -1;
+               }
+       }
+
        retval = write_mailbody(sockfd, mail->fp,
            mail->addtohdr ? mail->to : NULL);
        if(retval) {
index 49d48682b7e1e2f842fdddb2fef4c48a658b2303..5e4733cdb23423a33a7020e9cd6210fba546a7b7 100644 (file)
@@ -118,6 +118,9 @@ ATF_TC_WITHOUT_HEAD(do_bouncemail);
 ATF_TC_WITHOUT_HEAD(bouncemail);
 ATF_TC_WITHOUT_HEAD(send_mail_basics);
 ATF_TC_WITHOUT_HEAD(send_mail);
+ATF_TC_WITHOUT_HEAD(send_mail_xforwardedto);
+ATF_TC_WITHOUT_HEAD(send_mail_xsignedrecipient);
+ATF_TC_WITHOUT_HEAD(send_mail_both_headers);
 ATF_TC_WITHOUT_HEAD(getlistdelim);
 ATF_TC_WITHOUT_HEAD(getlistdelim_0);
 ATF_TC_WITHOUT_HEAD(getlistdelim_1);
@@ -1343,6 +1346,103 @@ ATF_TC_BODY(send_mail, tc)
        atf_utils_wait(p, 0, "MAIL FROM:<test@meh>\r\nRCPT TO:<plop@meh>\r\nDATA\r\nheaders\r\nTo: plop@meh\r\n\r\nbody\r\n\r\n.\r\n", "");
 }
 
+ATF_TC_BODY(send_mail_xforwardedto, tc)
+{
+       int smtppipe[2];
+       struct mail mail = { 0 };
+       ATF_REQUIRE(socketpair(AF_UNIX, SOCK_STREAM, 0, smtppipe) >= 0);
+       pid_t p = atf_utils_fork();
+       if (p == 0) {
+               const char *replies [] = {
+                       "250 2.1.0 OK\n",
+                       "250 2.1.0 OK\n",
+                       "350 2.1.0 OK\n",
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       "250 2.1.0 OK\n",
+               };
+               read_print_reply(smtppipe[0], replies, NELEM(replies));
+               exit(0);
+       }
+       close(smtppipe[0]);
+       mail.to = "plop@meh";
+       mail.from = "test@meh";
+       atf_utils_create_file("mymail.txt", "headers\n\nbody\n");
+       mail.fp = fopen("mymail.txt", "r");
+       mail.xforwardedto = true;
+       ATF_REQUIRE_EQ(send_mail(smtppipe[1], &mail, -1, -1, false), 0);
+       atf_utils_wait(p, 0, "MAIL FROM:<test@meh>\r\nRCPT TO:<plop@meh>\r\nDATA\r\nX-Forwarded-To: plop@meh\r\nheaders\r\n\r\nbody\r\n\r\n.\r\n", "");
+}
+
+ATF_TC_BODY(send_mail_xsignedrecipient, tc)
+{
+       int smtppipe[2];
+       struct mail mail = { 0 };
+       ATF_REQUIRE(socketpair(AF_UNIX, SOCK_STREAM, 0, smtppipe) >= 0);
+       pid_t p = atf_utils_fork();
+       if (p == 0) {
+               const char *replies [] = {
+                       "250 2.1.0 OK\n",
+                       "250 2.1.0 OK\n",
+                       "350 2.1.0 OK\n",
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       "250 2.1.0 OK\n",
+               };
+               read_print_reply(smtppipe[0], replies, NELEM(replies));
+               exit(0);
+       }
+       close(smtppipe[0]);
+       mail.to = "plop@meh";
+       mail.from = "test@meh";
+       atf_utils_create_file("mymail.txt", "headers\n\nbody\n");
+       mail.fp = fopen("mymail.txt", "r");
+       mail.xsignedrecipient = true;
+       ATF_REQUIRE_EQ(send_mail(smtppipe[1], &mail, -1, -1, false), 0);
+       atf_utils_wait(p, 0, "MAIL FROM:<test@meh>\r\nRCPT TO:<plop@meh>\r\nDATA\r\nX-Signed-Recipient: i=1; plop@meh\r\nheaders\r\n\r\nbody\r\n\r\n.\r\n", "");
+}
+
+ATF_TC_BODY(send_mail_both_headers, tc)
+{
+       int smtppipe[2];
+       struct mail mail = { 0 };
+       ATF_REQUIRE(socketpair(AF_UNIX, SOCK_STREAM, 0, smtppipe) >= 0);
+       pid_t p = atf_utils_fork();
+       if (p == 0) {
+               const char *replies [] = {
+                       "250 2.1.0 OK\n",
+                       "250 2.1.0 OK\n",
+                       "350 2.1.0 OK\n",
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       "250 2.1.0 OK\n",
+               };
+               read_print_reply(smtppipe[0], replies, NELEM(replies));
+               exit(0);
+       }
+       close(smtppipe[0]);
+       mail.to = "plop@meh";
+       mail.from = "test@meh";
+       atf_utils_create_file("mymail.txt", "headers\n\nbody\n");
+       mail.fp = fopen("mymail.txt", "r");
+       mail.addtohdr = true;
+       mail.xforwardedto = true;
+       mail.xsignedrecipient = true;
+       ATF_REQUIRE_EQ(send_mail(smtppipe[1], &mail, -1, -1, false), 0);
+       atf_utils_wait(p, 0, "MAIL FROM:<test@meh>\r\nRCPT TO:<plop@meh>\r\nDATA\r\nX-Forwarded-To: plop@meh\r\nX-Signed-Recipient: i=1; plop@meh\r\nheaders\r\nTo: plop@meh\r\n\r\nbody\r\n\r\n.\r\n", "");
+}
+
 ATF_TC_BODY(getlistdelim, tc)
 {
        init_ml(true);
@@ -4722,6 +4822,9 @@ ATF_TP_ADD_TCS(tp)
        ATF_TP_ADD_TC(tp, bouncemail);
        ATF_TP_ADD_TC(tp, send_mail_basics);
        ATF_TP_ADD_TC(tp, send_mail);
+       ATF_TP_ADD_TC(tp, send_mail_xforwardedto);
+       ATF_TP_ADD_TC(tp, send_mail_xsignedrecipient);
+       ATF_TP_ADD_TC(tp, send_mail_both_headers);
        ATF_TP_ADD_TC(tp, getlistdelim);
        ATF_TP_ADD_TC(tp, getlistdelim_0);
        ATF_TP_ADD_TC(tp, getlistdelim_1);