diff -r de81e0fe4905 Lib/smtplib.py --- a/Lib/smtplib.py Sun Mar 02 20:29:18 2014 +0100 +++ b/Lib/smtplib.py Mon Mar 03 20:31:21 2014 +0100 @@ -558,12 +558,77 @@ if not (200 <= code <= 299): raise SMTPHeloError(code, resp) + def _pre_authentication(self): + self.ehlo_or_helo_if_needed() + if not self.has_extn("auth"): + raise SMTPException("SMTP AUTH extension not supported by server.") + + def login_auth_plain(self, user, password): + """Log in on SMTP server using plain authentication.""" + + self._pre_authentication() + + # Encode arguments to SMTP-Syntax. + string = "\0%s\0%s" % (user, password) + encoded = encode_base64(string.encode('ascii'), eol='') + + (code, resp) = self.docmd("AUTH", "PLAIN %s" % encoded) + # 235 == 'Authentication successful' + # 503 == 'Error: already authenticated' + if code in (235, 503): + return (code, resp) + raise SMTPAuthenticationError(code, resp) + + def login_auth_cram_md5(self, user, password): + """Log in on SMTP server using CRAM-MD5 authentication.""" + + self._pre_authentication() + + # Get challenge from Server and encode arguments. + (code, challenge) = self.docmd("AUTH", "CRAM-MD5") + challenge = base64.decodebytes(challenge) + response = user + " " + hmac.HMAC(password.encode('ascii'), + challenge, 'md5').hexdigest() + encoded = encode_base64(response.encode('ascii'), eol='') + if code == 334: + (code, resp) = self.docmd(encoded) + + # 235 == 'Authentication successful' + # 503 == 'Error: already authenticated' + if code in (235, 503): + return (code, resp) + raise SMTPAuthenticationError(code, resp) + + def login_auth_login(self, user, password): + """Log in on SMTP server using LOGIN authentication.""" + + self._pre_authentication() + + # Send username and password to server. + (code, resp) = self.docmd("AUTH", + "%s %s" % ('LOGIN', encode_base64(user.encode('ascii'), + eol=''))) + if code == 334: + (code, resp) = self.docmd(encode_base64(password.encode('ascii'), + eol='')) + + # 235 == 'Authentication successful' + # 503 == 'Error: already authenticated' + if code in (235, 503): + return (code, resp) + raise SMTPAuthenticationError(code, resp) + def login(self, user, password): """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. + - user: The user name to authenticate with. + - password: The password for the authentication. + + Optional parameter: + - auth_encoder: Function used to generate the string to send to + server based on username and password. Must take + exactly this two arguments and return a string. If there has been no previous EHLO or HELO command this session, this method tries ESMTP EHLO first. @@ -580,24 +645,11 @@ found. """ - def encode_cram_md5(challenge, user, password): - challenge = base64.decodebytes(challenge) - response = user + " " + hmac.HMAC(password.encode('ascii'), - challenge, 'md5').hexdigest() - return encode_base64(response.encode('ascii'), eol='') - - def encode_plain(user, password): - s = "\0%s\0%s" % (user, password) - return encode_base64(s.encode('ascii'), eol='') - AUTH_PLAIN = "PLAIN" AUTH_CRAM_MD5 = "CRAM-MD5" AUTH_LOGIN = "LOGIN" - self.ehlo_or_helo_if_needed() - - if not self.has_extn("auth"): - raise SMTPException("SMTP AUTH extension not supported by server.") + self._pre_authentication() # Authentication methods the server claims to support advertised_authlist = self.esmtp_features["auth"].split() @@ -617,26 +669,22 @@ # support, so if authentication fails, we continue until we've tried # all methods. for authmethod in authlist: - if authmethod == AUTH_CRAM_MD5: - (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5) - if code == 334: - (code, resp) = self.docmd(encode_cram_md5(resp, user, password)) - elif authmethod == AUTH_PLAIN: - (code, resp) = self.docmd("AUTH", - AUTH_PLAIN + " " + encode_plain(user, password)) - elif authmethod == AUTH_LOGIN: - (code, resp) = self.docmd("AUTH", - "%s %s" % (AUTH_LOGIN, encode_base64(user.encode('ascii'), eol=''))) - if code == 334: - (code, resp) = self.docmd(encode_base64(password.encode('ascii'), eol='')) - - # 235 == 'Authentication successful' - # 503 == 'Error: already authenticated' - if code in (235, 503): - return (code, resp) + try: + if authmethod == AUTH_CRAM_MD5: + (code, resp) = self.login_auth_cram_md5(user, password) + elif authmethod == AUTH_PLAIN: + (code, resp) = self.login_auth_plain(user, password) + elif authmethod == AUTH_LOGIN: + (code, resp) = self.login_auth_login(user, password) + # 235 == 'Authentication successful' + # 503 == 'Error: already authenticated' + if code in (235, 503): + return (code, resp) + except SMTPAuthenticationError as e: + last_exception = e # We could not login sucessfully. Return result of last attempt. - raise SMTPAuthenticationError(code, resp) + raise last_exception def starttls(self, keyfile=None, certfile=None, context=None): """Puts the connection to the SMTP server into TLS mode.