Skip to content

Commit

Permalink
Switch to smtplib.SMTP for more compliant SMTP handling
Browse files Browse the repository at this point in the history
The current "client" did not wait for the greeting to finish before
sending commands, and took some other liberties with the SMTP standard.
This causes issues with some servers.
  • Loading branch information
mxsasha committed Sep 3, 2024
1 parent 7c38a4e commit 077c0cc
Showing 1 changed file with 31 additions and 17 deletions.
48 changes: 31 additions & 17 deletions sslyze/connection_helpers/opportunistic_tls_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import struct
from abc import abstractmethod, ABC
from enum import Enum
from smtplib import SMTP, SMTPException
from typing import ClassVar, Optional


Expand Down Expand Up @@ -65,19 +66,34 @@ def __init__(self, smtp_ehlo_hostname: str):
self._smtp_ehlo_hostname = smtp_ehlo_hostname

def prepare_socket_for_tls_handshake(self, sock: socket.socket) -> None:
# Get the SMTP banner
sock.recv(2048)
# SMTP parsing has some complicated areas and some unusual but legal
# server behavior - this code uses Python's smtplib to handle the protocol.
smtp = SMTP(local_hostname=self._smtp_ehlo_hostname)
smtp.sock = sock

# Send a EHLO and wait for the 250 status
sock.send(f"EHLO {self._smtp_ehlo_hostname}\r\n".encode("ascii"))
data = sock.recv(2048)
if b"250 " not in data:
raise OpportunisticTlsError(f"SMTP EHLO was rejected: {repr(data)}")
try:
code, message = smtp.getreply()
except SMTPException as exc:
code, message = -1, str(exc)
if code != 220:
raise OpportunisticTlsError(f"Unable to find 220 service ready response: {repr(message)}")

try:
code, message = smtp.ehlo()
except SMTPException as exc:
code, message = -1, str(exc)
if code != 250:
raise OpportunisticTlsError(f"SMTP EHLO was rejected: {repr(message)}")

if not smtp.has_extn("starttls"):
raise OpportunisticTlsError(f"Server does not support STARTTLS: {repr(message)}")

# Send a STARTTLS
sock.send(b"STARTTLS\r\n")
if b"220" not in sock.recv(2048):
raise OpportunisticTlsError("SMTP STARTTLS not supported")
try:
code, message = smtp.docmd("STARTTLS")
except SMTPException as exc:
code, message = -1, str(exc)
if code != 220:
raise OpportunisticTlsError(f"SMTP STARTTLS rejected: {message}")


class _XmppHelper(_OpportunisticTlsHelper):
Expand Down Expand Up @@ -223,16 +239,14 @@ class _PostgresHelper(_GenericOpportunisticTlsHelper):


def get_opportunistic_tls_helper(
protocol: ProtocolWithOpportunisticTlsEnum, xmpp_to_hostname: Optional[str], smtp_ehlo_hostname: str
protocol: ProtocolWithOpportunisticTlsEnum, xmpp_to_hostname: Optional[str]
) -> _OpportunisticTlsHelper:
helper_cls = _START_TLS_HELPER_CLASSES[protocol]
if protocol in [ProtocolWithOpportunisticTlsEnum.XMPP, ProtocolWithOpportunisticTlsEnum.XMPP_SERVER]:
if protocol not in [ProtocolWithOpportunisticTlsEnum.XMPP, ProtocolWithOpportunisticTlsEnum.XMPP_SERVER]:
opportunistic_tls_helper = helper_cls()
else:
if xmpp_to_hostname is None:
raise ValueError("Received None for xmpp_to_hostname")
opportunistic_tls_helper = helper_cls(xmpp_to=xmpp_to_hostname)
elif protocol == ProtocolWithOpportunisticTlsEnum.SMTP:
opportunistic_tls_helper = helper_cls(smtp_ehlo_hostname)
else:
opportunistic_tls_helper = helper_cls()

return opportunistic_tls_helper

0 comments on commit 077c0cc

Please sign in to comment.