Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cf-execd/cf-execd-runner.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
23 changes: 19 additions & 4 deletions tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,29 @@ 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))
if (rcvd == 0)
{
break;
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
* End-of-Data marker, so the marker can only appear at the end of
* the received data. */
for (int i = 0; i < rcvd; i++)
{
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);
Expand Down
44 changes: 44 additions & 0 deletions tests/acceptance/25_cf-execd/mail_dot_line.cf
Original file line number Diff line number Diff line change
@@ -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)
);
}
Loading