bpo-27820: Fix AUTH LOGIN logic in smtplib.SMTP (GH-24118)

* Fix auth_login logic (bpo-27820)

* Also fix a longstanding bug in the SimSMTPChannel.found_terminator() method that causes inability to test
SMTP AUTH with initial_response_ok=False.
This commit is contained in:
Pandu E POLUAN 2021-03-13 06:25:49 +07:00 committed by GitHub
parent ba251c2ae6
commit 7591d9455e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 3 deletions

View file

@ -64,6 +64,7 @@ SMTP_SSL_PORT = 465
CRLF = "\r\n"
bCRLF = b"\r\n"
_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3
_MAXCHALLENGE = 5 # Maximum number of AUTH challenges sent
OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
@ -248,6 +249,7 @@ class SMTP:
self.esmtp_features = {}
self.command_encoding = 'ascii'
self.source_address = source_address
self._auth_challenge_count = 0
if host:
(code, msg) = self.connect(host, port)
@ -633,14 +635,23 @@ class SMTP:
if initial_response is not None:
response = encode_base64(initial_response.encode('ascii'), eol='')
(code, resp) = self.docmd("AUTH", mechanism + " " + response)
self._auth_challenge_count = 1
else:
(code, resp) = self.docmd("AUTH", mechanism)
self._auth_challenge_count = 0
# If server responds with a challenge, send the response.
if code == 334:
while code == 334:
self._auth_challenge_count += 1
challenge = base64.decodebytes(resp)
response = encode_base64(
authobject(challenge).encode('ascii'), eol='')
(code, resp) = self.docmd(response)
# If server keeps sending challenges, something is wrong.
if self._auth_challenge_count > _MAXCHALLENGE:
raise SMTPException(
"Server AUTH mechanism infinite loop. Last response: "
+ repr((code, resp))
)
if code in (235, 503):
return (code, resp)
raise SMTPAuthenticationError(code, resp)
@ -662,7 +673,7 @@ class SMTP:
def auth_login(self, challenge=None):
""" Authobject to use with LOGIN authentication. Requires self.user and
self.password to be set."""
if challenge is None:
if challenge is None or self._auth_challenge_count < 2:
return self.user
else:
return self.password