Skip to content

Commit 077c0cc

Browse files
committed
Switch to smtplib.SMTP for more compliant SMTP handling
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.
1 parent 7c38a4e commit 077c0cc

File tree

1 file changed

+31
-17
lines changed

1 file changed

+31
-17
lines changed

sslyze/connection_helpers/opportunistic_tls_helpers.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import struct
33
from abc import abstractmethod, ABC
44
from enum import Enum
5+
from smtplib import SMTP, SMTPException
56
from typing import ClassVar, Optional
67

78

@@ -65,19 +66,34 @@ def __init__(self, smtp_ehlo_hostname: str):
6566
self._smtp_ehlo_hostname = smtp_ehlo_hostname
6667

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

71-
# Send a EHLO and wait for the 250 status
72-
sock.send(f"EHLO {self._smtp_ehlo_hostname}\r\n".encode("ascii"))
73-
data = sock.recv(2048)
74-
if b"250 " not in data:
75-
raise OpportunisticTlsError(f"SMTP EHLO was rejected: {repr(data)}")
74+
try:
75+
code, message = smtp.getreply()
76+
except SMTPException as exc:
77+
code, message = -1, str(exc)
78+
if code != 220:
79+
raise OpportunisticTlsError(f"Unable to find 220 service ready response: {repr(message)}")
80+
81+
try:
82+
code, message = smtp.ehlo()
83+
except SMTPException as exc:
84+
code, message = -1, str(exc)
85+
if code != 250:
86+
raise OpportunisticTlsError(f"SMTP EHLO was rejected: {repr(message)}")
87+
88+
if not smtp.has_extn("starttls"):
89+
raise OpportunisticTlsError(f"Server does not support STARTTLS: {repr(message)}")
7690

77-
# Send a STARTTLS
78-
sock.send(b"STARTTLS\r\n")
79-
if b"220" not in sock.recv(2048):
80-
raise OpportunisticTlsError("SMTP STARTTLS not supported")
91+
try:
92+
code, message = smtp.docmd("STARTTLS")
93+
except SMTPException as exc:
94+
code, message = -1, str(exc)
95+
if code != 220:
96+
raise OpportunisticTlsError(f"SMTP STARTTLS rejected: {message}")
8197

8298

8399
class _XmppHelper(_OpportunisticTlsHelper):
@@ -223,16 +239,14 @@ class _PostgresHelper(_GenericOpportunisticTlsHelper):
223239

224240

225241
def get_opportunistic_tls_helper(
226-
protocol: ProtocolWithOpportunisticTlsEnum, xmpp_to_hostname: Optional[str], smtp_ehlo_hostname: str
242+
protocol: ProtocolWithOpportunisticTlsEnum, xmpp_to_hostname: Optional[str]
227243
) -> _OpportunisticTlsHelper:
228244
helper_cls = _START_TLS_HELPER_CLASSES[protocol]
229-
if protocol in [ProtocolWithOpportunisticTlsEnum.XMPP, ProtocolWithOpportunisticTlsEnum.XMPP_SERVER]:
245+
if protocol not in [ProtocolWithOpportunisticTlsEnum.XMPP, ProtocolWithOpportunisticTlsEnum.XMPP_SERVER]:
246+
opportunistic_tls_helper = helper_cls()
247+
else:
230248
if xmpp_to_hostname is None:
231249
raise ValueError("Received None for xmpp_to_hostname")
232250
opportunistic_tls_helper = helper_cls(xmpp_to=xmpp_to_hostname)
233-
elif protocol == ProtocolWithOpportunisticTlsEnum.SMTP:
234-
opportunistic_tls_helper = helper_cls(smtp_ehlo_hostname)
235-
else:
236-
opportunistic_tls_helper = helper_cls()
237251

238252
return opportunistic_tls_helper

0 commit comments

Comments
 (0)