https://bz.apache.org/SpamAssassin/show_bug.cgi?id=8383
Bug ID: 8383
Summary: spamc silently fails on TLS 1.3 connections to spamd —
three bugs in SSL read/write handling
Product: Spamassassin
Version: 4.0.3
Hardware: PC
OS: Mac OS X
Status: NEW
Severity: normal
Priority: P2
Component: spamc/spamd
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: Undefined
Environment:
FreeBSD (ports build)
SpamAssassin 4.0.2
OpenSSL with TLS 1.3 support
spamc built with ENABLE_SSL=yes
Summary:
spamc hangs or silently fails when connecting to spamd over TLS 1.3. The
connection completes but spamc never successfully sends the SPAMC protocol,
resulting in a 30-second wait followed by spamd closing the connection. Three
bugs were found in spamc/libspamc.c and spamc/utils.c.
Diagnosis:
Using openssl s_client confirmed spamd itself handles TLS 1.3 correctly —
manually sending PING SPAMC/1.5\r\n\r\n after the handshake returns a clean
SPAMD/1.5 0 PONG. The bug is entirely in spamc's SSL handling.
Bug 1: ssl_timeout_read retry loop checks wrong error mechanism (spamc/utils.c)
The retry loop used errno == EWOULDBLOCK to decide whether to retry a failed
SSL_read. OpenSSL does not use errno for this — it uses its own error queue,
accessed via SSL_get_error(). The correct retry condition is SSL_get_error(ssl,
rc) == SSL_ERROR_WANT_READ. As written, the retry loop never fired for any
SSL-level condition.
Bug 2: SSL_write not retried on SSL_ERROR_WANT_READ (spamc/libspamc.c)
In TLS 1.3, the server sends post-handshake NewSessionTicket records after the
handshake completes. When spamc calls SSL_write to send the SPAMC protocol
header, OpenSSL may need to drain these pending records first and returns
SSL_ERROR_WANT_READ. The original code treated any rc <= 0 from SSL_write as a
fatal error with no retry, causing spamc to fail silently. The fix wraps
SSL_write in a retry loop that handles both SSL_ERROR_WANT_READ and
SSL_ERROR_WANT_WRITE.
Bug 3: SSL_write(ssl, buf, 0) treated as fatal error (spamc/libspamc.c)
For commands with no body — specifically PING (-K) — towrite_len is 0. Calling
SSL_write with a length of 0 returns 0, which the existing rc <= 0 error check
treated as a fatal failure. The non-SSL full_write path handles zero-length
writes as a harmless no-op. The fix skips the body SSL_write call entirely when
towrite_len == 0.
Fix:
Pull request in place here: https://github.com/apache/spamassassin/pull/26
Three commits:
4ec853f — Fix spamc hang on TLS 1.3 (bugs 1 & 2), add -D/--ssl-debug flag
18200047 — Fix SSL_write failure when body length is zero (bug 3)
20e98f0 — Document -D/--ssl-debug in spamc.pod
Testing:
spamc -S -K -d localhost -p 784 # PING: returns SPAMD/1.5 0 PONG
spamc -S -d localhost -p 784 < spam.msg # Full message: returns tagged output
Both confirmed working over TLS 1.3 after the fix.
Note: This report was prepared with assistance from an LLM (Claude). The
debugging, testing, and confirmation were performed by the submitter on live
hardware.
--
You are receiving this mail because:
You are the assignee for the bug.