From 6d8f89d74c1f320f60e55e36f73c692be2ed476e Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 21 Oct 2026 10:10:09 +0200 Subject: [PATCH 1/3] Fixed SMTP mock in cf-execd tests ending DATA too early The mock checked each recv() chunk on its own for the end of DATA marker. A chunk boundary inside the body could match it, e.g. when a dot-stuffed line was split across two chunks. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lars Erik Wik --- .../25_cf-execd/cf-execd-rpl-functions.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c b/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c index 9b6c3f445f8..926816684d6 100644 --- a/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c +++ b/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c @@ -66,14 +66,24 @@ static void *FeedSmtpDirectives(void *data) COND_SEND("250 to@test.com... Recipient ok\r\n"); COND_RECV(recvbuf); COND_SEND("354 Enter mail, end with \".\" on a line by itself\r\n"); - while (true) + /* The end of DATA marker may be split across several recv() calls, so + * track the last bytes of the stream rather than each chunk alone. */ + char last[5] = { '\0', '\0', '\0', '\r', '\n' }; + bool done = false; + while (!done) { COND_RECV(recvbuf); - if ((rcvd == 3 && memcmp(recvbuf + rcvd - 3, ".\r\n", 3) == 0) || - (rcvd > 3 && memcmp(recvbuf + rcvd - 4, "\n.\r\n", 4) == 0)) + /* It's a sliding window: + * For each received byte we left shift the buffer and append the new + * byte to the end. The client waits for our reply after sending the + * End-of-Data marker, so the marker can only appear at the end of + * the received data. */ + for (int i = 0; i < rcvd; i++) { - break; + memmove(last, last + 1, sizeof(last) - 1); + last[sizeof(last) - 1] = recvbuf[i]; } + done = (memcmp(last, "\r\n.\r\n", sizeof(last)) == 0); } COND_SEND("250 Message accepted for delivery\r\n"); COND_RECV(recvbuf); From 99dc767f4eaa1f1b3989b1a11d672564660fdb66 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 21 Oct 2026 10:10:33 +0200 Subject: [PATCH 2/3] Reapply "cf-execd: dot-stuff mailed output lines starting with '.'" This reverts commit dee2d4d085340643284bd0a4a827376130fba06b. The test failures were caused by the SMTP mock, fixed in the previous commit. Co-Authored-By: Claude Opus 5.5 Changelog: cf-execd now dot-stuffs mailed output lines starting with '.' Signed-off-by: Lars Erik Wik --- cf-execd/cf-execd-runner.c | 6 ++- tests/acceptance/25_cf-execd/mail_dot_line.cf | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/acceptance/25_cf-execd/mail_dot_line.cf diff --git a/cf-execd/cf-execd-runner.c b/cf-execd/cf-execd-runner.c index 574bcf7534c..04edb8f3ccf 100644 --- a/cf-execd/cf-execd-runner.c +++ b/cf-execd/cf-execd-runner.c @@ -797,7 +797,11 @@ static void MailResult(const ExecConfig *config, const char *file) { continue; } - if (send(sd, line, n_read, 0) == -1 || + /* Dot-stuff lines starting with '.' (RFC 5321, section 4.5.2), or a + * lone "." in the output would end the DATA section early and the + * remaining lines would be read by the server as SMTP commands. */ + if ((line[0] == '.' && send(sd, ".", 1, 0) == -1) || + send(sd, line, n_read, 0) == -1 || send(sd, "\r\n", 2, 0) == -1) { Log(LOG_LEVEL_ERR, "Error while sending mail to mailserver " diff --git a/tests/acceptance/25_cf-execd/mail_dot_line.cf b/tests/acceptance/25_cf-execd/mail_dot_line.cf new file mode 100644 index 00000000000..7d43a5c9c30 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mail_dot_line.cf @@ -0,0 +1,44 @@ +# Tests that a line consisting of a single "." in the agent output is +# dot-stuffed by cf-execd, so it does not end the SMTP DATA section early. +body common control +{ + inputs => { "../default.sub.cf", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { "CorrectString1$(const.n).$(const.n)CorrectString2" }; + "includes" slist => {}; + "excludes" slist => {}; + + methods: + "any" + usebundle => prepare_mailfilter_test( + @(reports), @(includes), @(excludes) + ); +} + +bundle agent test +{ + meta: + "test_soft_fail" + string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + vars: + "wanted" slist => { 'CorrectString1\r +\.\.\r +CorrectString2' }; + "empty_list" slist => {}; + + methods: + "any" + usebundle => check_cf_execd_mail( + @(wanted), @(empty_list), $(this.promise_filename) + ); +} From 5cbb7641595b2c76588a9fda1e54821af58966b4 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 21 Oct 2026 10:13:57 +0200 Subject: [PATCH 3/3] Fixed SMTP mock potentially looping forever on closed connection Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lars Erik Wik --- tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c b/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c index 926816684d6..d0320ea1334 100644 --- a/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c +++ b/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c @@ -73,6 +73,11 @@ static void *FeedSmtpDirectives(void *data) while (!done) { COND_RECV(recvbuf); + if (rcvd == 0) + { + Log(LOG_LEVEL_ERR, "Connection closed before end of DATA in %s:%i", __FILE__, __LINE__); + return NULL; + } /* It's a sliding window: * For each received byte we left shift the buffer and append the new * byte to the end. The client waits for our reply after sending the