Add a supported hook for aborted/partial SMTP DATA when the client disconnects
- 主要言語
- Python
- スター
- 373
- フォーク
- 105
- 平均マージ
- 4分
- マージ済み PR(30日)
- 2
説明
### Summary
We are using `aiosmtpd` as an SMTP receiver. In some cases, a client connection drops while the SMTP `DATA` command is still in progress. The message is not completed with `.`, so `handle_DATA()` is never called and `envelope.content` / `envelope.original_content` are never made available.
That behavior makes sense from an SMTP protocol perspective: the message was not accepted and the server cannot return `250 OK`.
However, we need a supported way to detect and process these aborted partial payloads for quarantine / diagnostics / remediation. We do **not** want to treat them as successfully accepted messages.
### Current behavior
Sequence:
```text
EHLO ...
MAIL FROM:
RCPT TO:
DATA
354 End data with .
.>
```
Result:
* `handle_DATA()` is not called.
* No completed `envelope.content` / `envelope.original_content` is available.
* The only way we found to capture bytes is to subclass `SMTP` and wrap private/internal stream reader behavior.
### Expected / requested behavior
Is there any supported way to access the partial `DATA` bytes when the client disconnects before the message terminator?
If not, would the project consider adding an opt-in hook for this case, for example:
```python
async def handle_DATA_aborted(
self,
server,
session,
envelope,
partial_content: bytes,
error: BaseException | None,
) -> None:
...
```
or a similar streaming / chunk-based hook.
Important constraints:
* This should not call normal `handle_DATA()`.
* This should clearly mark the message as incomplete / not accepted.
* It should not attempt to return an SMTP status if the client is already gone.
* It should be opt-in.
* It should probably respect size limits or expose truncation metadata to avoid memory pressure.
* It would be useful to include `MAIL FROM`, `RCPT TO`, peer/session data, partial byte count, and whether the SMTP terminator was seen.
### Why this is needed
Some upstream clients or network paths occasionally drop during `DATA`. From the receiver side, we need to detect these incomplete messages and move them into a separate handling path rather than losing all visibility.
The goal is not to accept corrupted emails as valid. The goal is to have a supported server-side hook for partial/incomplete payloads so they can be logged, quarantined, or correlated with upstream retries.
### Workaround tried
We tried a custom `SMTP` subclass that temporarily wraps `self._reader` during `smtp_DATA()` and writes received bytes to a temporary file. On `CancelledError`, `ConnectionResetError`, or EOF-like cases, it persists the partial payload and calls our own internal `process_partial_email(...)`.
This works only as a brittle diagnostic workaround because it relies on private/internal implementation details such as `self._reader` and the reader buffer.
### Minimal reproducer
Server:
```python
import time
from aiosmtpd.controller import Controller
class Handler:
async def handle_DATA(self, server, session, envelope):
print("handle_DATA called")
print("bytes:", len(envelope.original_content or b""))
return "250 OK"
controller = Controller(Handler(), hostname="127.0.0.1", port=8025)
controller.start()
try:
while True:
time.sleep(1)
finally:
controller.stop()
```
Client that disconnects before the DATA terminator:
```python
import socket
def recv_reply(sock):
print(sock.recv(4096).decode(errors="replace"))
sock = socket.create_connection(("127.0.0.1", 8025))
recv_reply(sock)
sock.sendall(b"EHLO example.test\r\n")
recv_reply(sock)
sock.sendall(b"MAIL FROM:\r\n")
recv_reply(sock)
sock.sendall(b"RCPT TO:\r\n")
recv_reply(sock)
sock.sendall(b"DATA\r\n")
recv_reply(sock)
sock.sendall(
b"From: sender@example.test\r\n"
b"To: recipient@example.test\r\n"
b"Subject: partial message\r\n"
b"\r\n"
b"This is an incomplete body.\r\n"
)
# Close without sending b"\r\n.\r\n"
sock.close()
```
Observed result: `handle_DATA()` is not called.
Requested result: an optional hook or supported extension point for the aborted partial DATA case.
### Related issues
This seems related to previous connection reset handling issues such as #127 and #162, but those appear to focus on exception/logging behavior after disconnects, not on exposing partial `DATA` to application code.
コントリビューションガイド
評価
この issue はまだ評価されていません。