Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-aiosmtplib for 
openSUSE:Factory checked in at 2026-06-15 19:41:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-aiosmtplib (Old)
 and      /work/SRC/openSUSE:Factory/.python-aiosmtplib.new.1981 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-aiosmtplib"

Mon Jun 15 19:41:54 2026 rev:17 rq:1359217 version:5.1.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-aiosmtplib/python-aiosmtplib.changes      
2026-03-27 16:54:03.238867644 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-aiosmtplib.new.1981/python-aiosmtplib.changes
    2026-06-15 19:44:49.194873975 +0200
@@ -1,0 +2,17 @@
+Sun Jun 14 09:27:07 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 5.1.1 (CVE-2026-53533):
+  * Security: Reject control characters (the C0 range 0x00-0x1F
+    and DEL 0x7F, including CR, LF, and NUL) in SMTP command
+    arguments, preventing command injection via input passed to
+    mail(), rcpt(), vrfy(), expn() or sendmail(). Such input now
+    raises ValueError before anything is written to the
+    connection. More details: https://github.com/cole/aiosmtplib/
+    security/advisories/GHSA-v3q9-hj7j-63hq Thanks to
+    @tonghuaroot for the report.
+  * Bugfix: SMTP.quit() no longer hangs until the read timeout
+    when the peer drops the transport with an exception after
+    QUIT is sent but before the 221 reply is parsed (e.g. AWS SES
+    closing TLS without close_notify).
+
+-------------------------------------------------------------------

Old:
----
  aiosmtplib-5.1.0.tar.gz

New:
----
  aiosmtplib-5.1.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-aiosmtplib.spec ++++++
--- /var/tmp/diff_new_pack.B0lp86/_old  2026-06-15 19:44:50.506928958 +0200
+++ /var/tmp/diff_new_pack.B0lp86/_new  2026-06-15 19:44:50.510929125 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-aiosmtplib
-Version:        5.1.0
+Version:        5.1.1
 Release:        0
 Summary:        Python asyncio SMTP client
 License:        MIT

++++++ aiosmtplib-5.1.0.tar.gz -> aiosmtplib-5.1.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/.gitignore 
new/aiosmtplib-5.1.1/.gitignore
--- old/aiosmtplib-5.1.0/.gitignore     2020-02-02 01:00:00.000000000 +0100
+++ new/aiosmtplib-5.1.1/.gitignore     2020-02-02 01:00:00.000000000 +0100
@@ -29,3 +29,4 @@
 venv/
 poetry.lock
 uv.lock
+__pycache__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/.pre-commit-config.yaml 
new/aiosmtplib-5.1.1/.pre-commit-config.yaml
--- old/aiosmtplib-5.1.0/.pre-commit-config.yaml        2020-02-02 
01:00:00.000000000 +0100
+++ new/aiosmtplib-5.1.1/.pre-commit-config.yaml        2020-02-02 
01:00:00.000000000 +0100
@@ -12,7 +12,7 @@
       - id: trailing-whitespace
       - id: end-of-file-fixer
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: "v0.14.10"
+    rev: "v0.15.12"
     hooks:
       - id: ruff-check
       - id: ruff-format
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/CHANGELOG.rst 
new/aiosmtplib-5.1.1/CHANGELOG.rst
--- old/aiosmtplib-5.1.0/CHANGELOG.rst  2020-02-02 01:00:00.000000000 +0100
+++ new/aiosmtplib-5.1.1/CHANGELOG.rst  2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,22 @@
 Changelog
 =========
 
+5.1.1
+-----
+
+- Security: Reject control characters (the C0 range ``0x00``-``0x1F`` and DEL
+  ``0x7F``, including CR, LF, and NUL) in SMTP command arguments, preventing
+  command injection via input passed to ``mail()``, ``rcpt()``, ``vrfy()``,
+  ``expn()`` or ``sendmail()``. Such input now raises ``ValueError`` before
+  anything is written to the connection.
+  More details: 
https://github.com/cole/aiosmtplib/security/advisories/GHSA-v3q9-hj7j-63hq
+  Thanks to @tonghuaroot for the report.
+- Bugfix: ``SMTP.quit()`` no longer hangs until the read timeout when the
+  peer drops the transport with an exception after ``QUIT`` is sent but
+  before the 221 reply is parsed (e.g. AWS SES closing TLS without
+  ``close_notify``).
+
+
 5.1.0
 -----
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/PKG-INFO 
new/aiosmtplib-5.1.1/PKG-INFO
--- old/aiosmtplib-5.1.0/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
+++ new/aiosmtplib-5.1.1/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: aiosmtplib
-Version: 5.1.0
+Version: 5.1.1
 Summary: asyncio SMTP client
 Project-URL: Documentation, https://aiosmtplib.readthedocs.io/en/stable/
 Project-URL: Changelog, 
https://github.com/cole/aiosmtplib/blob/main/CHANGELOG.rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/src/aiosmtplib/__init__.py 
new/aiosmtplib-5.1.1/src/aiosmtplib/__init__.py
--- old/aiosmtplib-5.1.0/src/aiosmtplib/__init__.py     2020-02-02 
01:00:00.000000000 +0100
+++ new/aiosmtplib-5.1.1/src/aiosmtplib/__init__.py     2020-02-02 
01:00:00.000000000 +0100
@@ -14,6 +14,7 @@
 from .errors import (
     SMTPAuthenticationError,
     SMTPConnectError,
+    SMTPConnectResponseError,
     SMTPConnectTimeoutError,
     SMTPDataError,
     SMTPException,
@@ -26,15 +27,13 @@
     SMTPSenderRefused,
     SMTPServerDisconnected,
     SMTPTimeoutError,
-    SMTPConnectResponseError,
 )
 from .response import SMTPResponse
 from .smtp import SMTP
 from .typing import SMTPStatus, SMTPTokenGenerator
 
-
 __title__ = "aiosmtplib"
-__version__ = "5.1.0"
+__version__ = "5.1.1"
 __author__ = "Cole Maclean"
 __license__ = "MIT"
 __copyright__ = "Copyright 2022 Cole Maclean"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/src/aiosmtplib/protocol.py 
new/aiosmtplib-5.1.1/src/aiosmtplib/protocol.py
--- old/aiosmtplib-5.1.0/src/aiosmtplib/protocol.py     2020-02-02 
01:00:00.000000000 +0100
+++ new/aiosmtplib-5.1.1/src/aiosmtplib/protocol.py     2020-02-02 
01:00:00.000000000 +0100
@@ -25,6 +25,8 @@
 MAX_LINE_LENGTH = 8192
 LINE_ENDINGS_REGEX = re.compile(rb"(?:\r\n|\n|\r(?!\n))")
 PERIOD_REGEX = re.compile(rb"(?m)^\.")
+# Reject all C0 controls + DEL; CR/LF/NUL in particular enable injection.
+COMMAND_INJECTION_REGEX = re.compile(rb"[\x00-\x1f\x7f]")
 
 
 class FlowControlMixin(asyncio.Protocol):
@@ -132,12 +134,15 @@
     def connection_lost(self, exc: Exception | None) -> None:
         super().connection_lost(exc)
 
-        if not self._quit_sent:
-            smtp_exc = SMTPServerDisconnected("Connection lost")
-            if exc:
-                smtp_exc.__cause__ = exc
-
-            if self._response_waiter and not self._response_waiter.done():
+        if self._response_waiter and not self._response_waiter.done():
+            if self._quit_sent:
+                self._response_waiter.set_result(
+                    SMTPResponse(SMTPStatus.closing.value, "")
+                )
+            else:
+                smtp_exc = SMTPServerDisconnected("Connection lost")
+                if exc:
+                    smtp_exc.__cause__ = exc
                 self._response_waiter.set_exception(smtp_exc)
 
         self.transport = None
@@ -288,6 +293,9 @@
         Sends an SMTP command along with any args to the server, and returns
         a response.
         """
+        for arg in args:
+            if COMMAND_INJECTION_REGEX.search(arg):
+                raise ValueError("Command arg contains a prohibited control 
character")
         if self._command_lock is None:
             raise SMTPServerDisconnected("Server not connected")
         command = b" ".join(args) + b"\r\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/tests/test_commands.py 
new/aiosmtplib-5.1.1/tests/test_commands.py
--- old/aiosmtplib-5.1.0/tests/test_commands.py 2020-02-02 01:00:00.000000000 
+0100
+++ new/aiosmtplib-5.1.1/tests/test_commands.py 2020-02-02 01:00:00.000000000 
+0100
@@ -2,6 +2,9 @@
 Lower level SMTP command tests.
 """
 
+import contextlib
+import email.errors
+import email.message
 from typing import Any
 
 import pytest
@@ -18,16 +21,16 @@
 
 from .smtpd import (
     RecordingHandler,
-    mock_response_done,
+    mock_response_bad_command_sequence,
     mock_response_bad_data,
+    mock_response_done,
     mock_response_ehlo_full,
     mock_response_expn,
     mock_response_gibberish,
-    mock_response_unavailable,
-    mock_response_unrecognized_command,
-    mock_response_bad_command_sequence,
     mock_response_syntax_error,
     mock_response_syntax_error_and_cleanup,
+    mock_response_unavailable,
+    mock_response_unrecognized_command,
 )
 
 
@@ -433,14 +436,70 @@
     assert response.code == SMTPStatus.completed
 
 
-async def test_header_injection(
[email protected]("command", ("mail", "rcpt", "vrfy", "expn"))
+async def test_address_command_rejects_injection(
     smtp_client: SMTP,
     received_commands: list[tuple[str, tuple[Any, ...]]],
+    command: str,
 ) -> None:
     async with smtp_client:
-        await smtp_client.mail("[email protected]\r\nX-Malicious-Header: bad 
stuff")
+        await smtp_client.ehlo()
+        received_commands.clear()
+
+        method = getattr(smtp_client, command)
+        with pytest.raises(ValueError):
+            await method("[email protected]\r\nRCPT TO:<[email protected]>")
+
+        assert received_commands == []
+
+
+async def test_sendmail_rejects_command_injection(
+    smtp_client: SMTP,
+    received_commands: list[tuple[str, tuple[Any, ...]]],
+    received_messages: list[email.message.EmailMessage],
+) -> None:
+    """
+    A complete transaction smuggled into the sender must never be sent.
+    """
+    injected_sender = (
+        "[email protected]>\r\n"
+        "MAIL FROM:<[email protected]>\r\n"
+        "RCPT TO:<[email protected]>\r\n"
+        "DATA\r\n"
+        "Subject: smuggled\r\n"
+        "\r\n"
+        "smuggled body\r\n"
+        ".\r\n"
+        "RSET\r\nMAIL FROM:<x"
+    )
+    async with smtp_client:
+        await smtp_client.ehlo()
+        received_commands.clear()
+
+        with pytest.raises(ValueError):
+            await smtp_client.sendmail(
+                injected_sender, ["[email protected]"], "Subject: 
legit\n\nhi"
+            )
+
+        assert received_commands == []
+        assert received_messages == []
+
+
+async def test_send_message_compat32_does_not_smuggle_envelope_commands(
+    smtp_client: SMTP,
+    received_commands: list[tuple[str, tuple[Any, ...]]],
+) -> None:
+    message = email.message.Message()
+    message["From"] = "[email protected]\r\nRCPT TO:<[email protected]>"
+    message["To"] = "[email protected]"
+    message.set_payload("hello")
+
+    async with smtp_client:
+        # PyPy's BytesGenerator enforces verify_generated_headers and rejects 
the
+        # injected newline; CPython's BytesGenerator does not. Either way, no
+        # envelope command may be smuggled.
+        with contextlib.suppress(email.errors.HeaderWriteError):
+            await smtp_client.send_message(message)
 
-    assert len(received_commands) > 0
-    for command in received_commands:
-        for arg in command:
-            assert "bad stuff" not in arg
+    for _, args in received_commands:
+        assert all("hijacker" not in str(arg) for arg in args)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosmtplib-5.1.0/tests/test_protocol.py 
new/aiosmtplib-5.1.1/tests/test_protocol.py
--- old/aiosmtplib-5.1.0/tests/test_protocol.py 2020-02-02 01:00:00.000000000 
+0100
+++ new/aiosmtplib-5.1.1/tests/test_protocol.py 2020-02-02 01:00:00.000000000 
+0100
@@ -61,7 +61,7 @@
     monkeypatch.setattr("aiosmtplib.protocol.MAX_LINE_LENGTH", 128)
 
     with pytest.raises(SMTPResponseException) as exc_info:
-        await protocol.execute_command(b"TEST\n", timeout=1.0)  # type: ignore
+        await protocol.execute_command(b"TEST", timeout=1.0)  # type: ignore
 
     assert exc_info.value.code == 500
     assert "Response too long" in exc_info.value.message
@@ -254,7 +254,7 @@
     monkeypatch.setattr(protocol, "_response_waiter", None)
 
     with pytest.raises(SMTPServerDisconnected):
-        await protocol.execute_command(b"TEST\n", timeout=1.0)  # type: ignore
+        await protocol.execute_command(b"TEST", timeout=1.0)  # type: ignore
 
     server.close()
     await cleanup_server(server)
@@ -287,7 +287,7 @@
 
     _, protocol = await asyncio.wait_for(connect_future, timeout=1.0)
 
-    response = await protocol.execute_command(b"TEST\n", timeout=1.0)  # type: 
ignore
+    response = await protocol.execute_command(b"TEST", timeout=1.0)  # type: 
ignore
 
     assert response.code == 220
     assert response.message == "Hi"
@@ -348,8 +348,8 @@
     )
     _, protocol = await asyncio.wait_for(connect_future, timeout=1.0)
 
-    await protocol.execute_command(b"HELO\n", timeout=1.0)
-    await protocol.execute_command(b"QUIT\n", timeout=1.0)
+    await protocol.execute_command(b"HELO", timeout=1.0)
+    await protocol.execute_command(b"QUIT", timeout=1.0)
 
     del protocol
     # Force garbage collection
@@ -374,7 +374,7 @@
     protocol = SMTPProtocol(event_loop)
 
     with pytest.raises(SMTPServerDisconnected):
-        await protocol.execute_command(b"TEST\n")
+        await protocol.execute_command(b"TEST")
 
     with pytest.raises(SMTPServerDisconnected):
         await protocol.execute_data_command(b"TEST\n")
@@ -480,3 +480,105 @@
 
     with pytest.raises(ConnectionResetError):
         await flow_control._drain_helper()
+
+
+class _FakeTransport(asyncio.Transport):
+    """Minimal transport stub for driving SMTPProtocol callbacks directly."""
+
+    def __init__(self) -> None:
+        super().__init__()
+        self._extra: dict[str, object] = {"sslcontext": object()}
+
+    def get_extra_info(self, name: str, default: object = None) -> object:
+        return self._extra.get(name, default)
+
+    def is_closing(self) -> bool:
+        return False
+
+
+async def test_protocol_connection_lost_after_quit_resolves_waiter() -> None:
+    """
+    Regression test for https://github.com/cole/aiosmtplib/issues/345.
+
+    When the peer drops the transport with an exception after ``QUIT\\r\\n``
+    has been written but before the 221 reply is parsed (e.g. AWS SES closes
+    TLS without ``close_notify``, surfaced as 
``connection_lost(SSLEOFError)``),
+    the response waiter must be resolved so that ``SMTP.quit()`` returns
+    promptly instead of blocking until the read timeout.
+    """
+    protocol = SMTPProtocol()
+    protocol.connection_made(_FakeTransport())
+
+    protocol._quit_sent = True
+    waiter = protocol._response_waiter
+    assert waiter is not None and not waiter.done()
+
+    protocol.connection_lost(ssl.SSLEOFError("EOF occurred in violation of 
protocol"))
+
+    assert waiter.done()
+    assert waiter.exception() is None
+    response = waiter.result()
+    assert response.code == 221
+
+
+async def test_protocol_connection_lost_without_quit_raises() -> None:
+    """
+    Without an outstanding QUIT, ``connection_lost`` must continue to
+    surface ``SMTPServerDisconnected`` on the response waiter.
+    """
+    protocol = SMTPProtocol()
+    protocol.connection_made(_FakeTransport())
+
+    waiter = protocol._response_waiter
+    assert waiter is not None and not waiter.done()
+
+    protocol.connection_lost(ConnectionResetError("boom"))
+
+    assert waiter.done()
+    exc = waiter.exception()
+    assert isinstance(exc, SMTPServerDisconnected)
+
+
+class _WriteRecordingTransport(_FakeTransport):
+    def __init__(self) -> None:
+        super().__init__()
+        self.writes: list[bytes] = []
+
+    def write(self, data: bytes) -> None:
+        self.writes.append(data)
+
+
[email protected](
+    "arg",
+    (
+        b"FROM:<[email protected]\r\nRCPT TO:<[email protected]>",
+        b"FROM:<[email protected]\rRCPT TO:<[email protected]>",
+        b"FROM:<[email protected]\nRCPT TO:<[email protected]>",
+        b"FROM:<[email protected]\x00>",
+        b"FROM:<[email protected]\tEVIL>",
+        b"FROM:<[email protected]\x7f>",
+    ),
+    ids=("crlf", "cr", "lf", "nul", "tab", "del"),
+)
+async def test_protocol_execute_command_rejects_injected_args(arg: bytes) -> 
None:
+    protocol = SMTPProtocol()
+    transport = _WriteRecordingTransport()
+    protocol.connection_made(transport)
+
+    with pytest.raises(ValueError, match="prohibited"):
+        await protocol.execute_command(b"MAIL", arg, timeout=1.0)
+
+    assert transport.writes == []
+
+
+async def test_protocol_execute_command_rejects_injected_option() -> None:
+    protocol = SMTPProtocol()
+    transport = _WriteRecordingTransport()
+    protocol.connection_made(transport)
+
+    with pytest.raises(ValueError, match="prohibited"):
+        await protocol.execute_command(
+            b"MAIL", b"FROM:<[email protected]>", b"BODY=8BITMIME\r\nDATA", 
timeout=1.0
+        )
+
+    assert transport.writes == []

Reply via email to