summaryrefslogtreecommitdiff
path: root/Lib/smtplib.py
diff options
context:
space:
mode:
authorBarry Warsaw <barry@python.org>2015-07-09 10:39:55 -0400
committerBarry Warsaw <barry@python.org>2015-07-09 10:39:55 -0400
commitc5ea754e484d73f04b1a361d82d0eed1b51dfdc8 (patch)
tree9b311e7a3649ea679a47a81ea085efe3c6ace849 /Lib/smtplib.py
parentb85b427507a8034878d08c2abe2d6b1b7eab97d0 (diff)
downloadcpython-git-c5ea754e484d73f04b1a361d82d0eed1b51dfdc8.tar.gz
- Issue #15014: SMTP.auth() and SMTP.login() now support RFC 4954's optional
initial-response argument to the SMTP AUTH command.
Diffstat (limited to 'Lib/smtplib.py')
-rwxr-xr-xLib/smtplib.py52
1 files changed, 36 insertions, 16 deletions
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 71ccd2a207..4fe6aaf8e1 100755
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -601,7 +601,7 @@ class SMTP:
if not (200 <= code <= 299):
raise SMTPHeloError(code, resp)
- def auth(self, mechanism, authobject):
+ def auth(self, mechanism, authobject, *, initial_response_ok=True):
"""Authentication command - requires response processing.
'mechanism' specifies which authentication mechanism is to
@@ -615,32 +615,46 @@ class SMTP:
It will be called to process the server's challenge response; the
challenge argument it is passed will be a bytes. It should return
bytes data that will be base64 encoded and sent to the server.
- """
+ Keyword arguments:
+ - initial_response_ok: Allow sending the RFC 4954 initial-response
+ to the AUTH command, if the authentication methods supports it.
+ """
+ # RFC 4954 allows auth methods to provide an initial response. Not all
+ # methods support it. By definition, if they return something other
+ # than None when challenge is None, then they do. See issue #15014.
mechanism = mechanism.upper()
- (code, resp) = self.docmd("AUTH", mechanism)
- # Server replies with 334 (challenge) or 535 (not supported)
- if code == 334:
- challenge = base64.decodebytes(resp)
- response = encode_base64(
- authobject(challenge).encode('ascii'), eol='')
- (code, resp) = self.docmd(response)
- if code in (235, 503):
- return (code, resp)
+ initial_response = (authobject() if initial_response_ok else None)
+ if initial_response is not None:
+ response = encode_base64(initial_response.encode('ascii'), eol='')
+ (code, resp) = self.docmd("AUTH", mechanism + " " + response)
+ else:
+ (code, resp) = self.docmd("AUTH", mechanism)
+ # Server replies with 334 (challenge) or 535 (not supported)
+ if code == 334:
+ challenge = base64.decodebytes(resp)
+ response = encode_base64(
+ authobject(challenge).encode('ascii'), eol='')
+ (code, resp) = self.docmd(response)
+ if code in (235, 503):
+ return (code, resp)
raise SMTPAuthenticationError(code, resp)
- def auth_cram_md5(self, challenge):
+ def auth_cram_md5(self, challenge=None):
""" Authobject to use with CRAM-MD5 authentication. Requires self.user
and self.password to be set."""
+ # CRAM-MD5 does not support initial-response.
+ if challenge is None:
+ return None
return self.user + " " + hmac.HMAC(
self.password.encode('ascii'), challenge, 'md5').hexdigest()
- def auth_plain(self, challenge):
+ def auth_plain(self, challenge=None):
""" Authobject to use with PLAIN authentication. Requires self.user and
self.password to be set."""
return "\0%s\0%s" % (self.user, self.password)
- def auth_login(self, challenge):
+ def auth_login(self, challenge=None):
""" Authobject to use with LOGIN authentication. Requires self.user and
self.password to be set."""
(code, resp) = self.docmd(
@@ -649,13 +663,17 @@ class SMTP:
return self.password
raise SMTPAuthenticationError(code, resp)
- def login(self, user, password):
+ def login(self, user, password, *, initial_response_ok=True):
"""Log in on an SMTP server that requires authentication.
The arguments are:
- user: The user name to authenticate with.
- password: The password for the authentication.
+ Keyword arguments:
+ - initial_response_ok: Allow sending the RFC 4954 initial-response
+ to the AUTH command, if the authentication methods supports it.
+
If there has been no previous EHLO or HELO command this session, this
method tries ESMTP EHLO first.
@@ -698,7 +716,9 @@ class SMTP:
for authmethod in authlist:
method_name = 'auth_' + authmethod.lower().replace('-', '_')
try:
- (code, resp) = self.auth(authmethod, getattr(self, method_name))
+ (code, resp) = self.auth(
+ authmethod, getattr(self, method_name),
+ initial_response_ok=initial_response_ok)
# 235 == 'Authentication successful'
# 503 == 'Error: already authenticated'
if code in (235, 503):