Add the CHUNKING extension (RFC 3030)
Command.bdat parses "BDAT <size> [LAST]" strictly. The server
advertises CHUNKING and receives chunked messages on both handler
paths: the collecting path reassembles raw chunks (no dot-stuffing,
max_message_size enforced with 552), and BdatReader adapts the chunk
sequence into the streaming handler's reader, replying 250 between
chunks and handling RSET/QUIT/protocol violations mid-stream, with
unread remainder drained. Framing is strictly length-based: a BDAT
without a transaction still consumes its payload octets, and chunk
payloads that look like commands are data.
The client gains Extensions.chunking, bdat(chunk, last) (verbatim
transmission, one flush per chunk), and sendMessageChunked; the CLI
gains send --chunking, streaming stdin as BDAT chunks.
The exim-client torture script and the byte-for-byte gauntlet unit
test gain a BDAT section, and the VM interop suite delivers via
CHUNKING to real Postfix and Exim (18 subtests passing). BINARYMIME
remains deliberately unimplemented (BODY=BINARYMIME is rejected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HBHFhoTYa8TU9GLwobfbx
Add the CHUNKING extension (RFC 3030)
Command.bdat parses "BDAT <size> [LAST]" strictly. The server
advertises CHUNKING and receives chunked messages on both handler
paths: the collecting path reassembles raw chunks (no dot-stuffing,
max_message_size enforced with 552), and BdatReader adapts the chunk
sequence into the streaming handler's reader, replying 250 between
chunks and handling RSET/QUIT/protocol violations mid-stream, with
unread remainder drained. Framing is strictly length-based: a BDAT
without a transaction still consumes its payload octets, and chunk
payloads that look like commands are data.
The client gains Extensions.chunking, bdat(chunk, last) (verbatim
transmission, one flush per chunk), and sendMessageChunked; the CLI
gains send --chunking, streaming stdin as BDAT chunks.
The exim-client torture script and the byte-for-byte gauntlet unit
test gain a BDAT section, and the VM interop suite delivers via
CHUNKING to real Postfix and Exim (18 subtests passing). BINARYMIME
remains deliberately unimplemented (BODY=BINARYMIME is rejected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HBHFhoTYa8TU9GLwobfbx
Add the CHUNKING extension (RFC 3030)
Command.bdat parses "BDAT <size> [LAST]" strictly. The server
advertises CHUNKING and receives chunked messages on both handler
paths: the collecting path reassembles raw chunks (no dot-stuffing,
max_message_size enforced with 552), and BdatReader adapts the chunk
sequence into the streaming handler's reader, replying 250 between
chunks and handling RSET/QUIT/protocol violations mid-stream, with
unread remainder drained. Framing is strictly length-based: a BDAT
without a transaction still consumes its payload octets, and chunk
payloads that look like commands are data.
The client gains Extensions.chunking, bdat(chunk, last) (verbatim
transmission, one flush per chunk), and sendMessageChunked; the CLI
gains send --chunking, streaming stdin as BDAT chunks.
The exim-client torture script and the byte-for-byte gauntlet unit
test gain a BDAT section, and the VM interop suite delivers via
CHUNKING to real Postfix and Exim (18 subtests passing). BINARYMIME
remains deliberately unimplemented (BODY=BINARYMIME is rejected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HBHFhoTYa8TU9GLwobfbx
Add the CHUNKING extension (RFC 3030)
Command.bdat parses "BDAT <size> [LAST]" strictly. The server
advertises CHUNKING and receives chunked messages on both handler
paths: the collecting path reassembles raw chunks (no dot-stuffing,
max_message_size enforced with 552), and BdatReader adapts the chunk
sequence into the streaming handler's reader, replying 250 between
chunks and handling RSET/QUIT/protocol violations mid-stream, with
unread remainder drained. Framing is strictly length-based: a BDAT
without a transaction still consumes its payload octets, and chunk
payloads that look like commands are data.
The client gains Extensions.chunking, bdat(chunk, last) (verbatim
transmission, one flush per chunk), and sendMessageChunked; the CLI
gains send --chunking, streaming stdin as BDAT chunks.
The exim-client torture script and the byte-for-byte gauntlet unit
test gain a BDAT section, and the VM interop suite delivers via
CHUNKING to real Postfix and Exim (18 subtests passing). BINARYMIME
remains deliberately unimplemented (BODY=BINARYMIME is rejected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HBHFhoTYa8TU9GLwobfbx
RFC review: standards list, doc links, exim-derived protocol gauntlet
README gains a Standards section listing every implemented RFC with
its per-side coverage (5321, 1870, 6152, 2920, 3207, 8314, 4954, 4616,
2195, draft-murchison-sasl-login, 3463/2034, 6531, and 8446 via
tls.zig). Doc comments now link each RFC mention to the datatracker,
with section fragments where a section is cited; authLogin's doc notes
it has no RFC.
The review surfaced two fixes: the server always emitted RFC 3463
enhanced status codes but never advertised ENHANCEDSTATUSCODES
(RFC 2034) - now it does; and root.zig's module doc still called TLS
an eventual feature.
Also adds a protocol gauntlet unit test distilled from exim's test
suite (test/scripts/0000-Basic, notably 0019's syntax-error dialogue
and the 0008/0100 dotted message lines), asserting the exact 28-reply
transcript and resulting envelope. The dialogue was first validated by
running exim's own scriptable test client (test/src/client.c, built
with zig cc) against zsmtp serve.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HBHFhoTYa8TU9GLwobfbx