summaryrefslogtreecommitdiffstats
path: root/Lib/smtplib.py
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2001-09-11 15:57:46 (GMT)
committerGuido van Rossum <guido@python.org>2001-09-11 15:57:46 (GMT)
commitae01046f7bf846dd984e89cf84e83d6ad0f45afd (patch)
treed7411bd75833a8a1e05599dc3de6a174d09947ec /Lib/smtplib.py
parentf166994b8346708a867df823def3ab12e02d43e9 (diff)
downloadcpython-ae01046f7bf846dd984e89cf84e83d6ad0f45afd.zip
cpython-ae01046f7bf846dd984e89cf84e83d6ad0f45afd.tar.gz
cpython-ae01046f7bf846dd984e89cf84e83d6ad0f45afd.tar.bz2
Add login() method and SMTPAuthenticationError exception. SF patch
#460112 by Gerhard Haering. (With slight layout changes to conform to docstrings guidelines and to prevent a line longer than 78 characters. Also fixed some docstrings that Gerhard didn't touch.)
Diffstat (limited to 'Lib/smtplib.py')
-rwxr-xr-xLib/smtplib.py96
1 files changed, 93 insertions, 3 deletions
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 61b733e..d64fa20 100755
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -2,7 +2,8 @@
'''SMTP/ESMTP client class.
-This should follow RFC 821 (SMTP) and RFC 1869 (ESMTP).
+This should follow RFC 821 (SMTP), RFC 1869 (ESMTP) and RFC 2554 (SMTP
+Authentication).
Notes:
@@ -36,6 +37,7 @@ Example:
# Eric S. Raymond <esr@thyrsus.com>
# Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
# by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
+# RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
#
# This was modified from the Python 1.5 library HTTP lib.
@@ -43,11 +45,13 @@ import socket
import re
import rfc822
import types
+import base64
+import hmac
__all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
"SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
- "SMTPConnectError","SMTPHeloError","quoteaddr","quotedata",
- "SMTP"]
+ "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError",
+ "quoteaddr","quotedata","SMTP"]
SMTP_PORT = 25
CRLF="\r\n"
@@ -80,6 +84,7 @@ class SMTPResponseException(SMTPException):
class SMTPSenderRefused(SMTPResponseException):
"""Sender address refused.
+
In addition to the attributes set by on all SMTPResponseException
exceptions, this sets `sender' to the string that the SMTP refused.
"""
@@ -92,6 +97,7 @@ class SMTPSenderRefused(SMTPResponseException):
class SMTPRecipientsRefused(SMTPException):
"""All recipient addresses refused.
+
The errors for each recipient are accessible through the attribute
'recipients', which is a dictionary of exactly the same sort as
SMTP.sendmail() returns.
@@ -111,6 +117,12 @@ class SMTPConnectError(SMTPResponseException):
class SMTPHeloError(SMTPResponseException):
"""The server refused our HELO reply."""
+class SMTPAuthenticationError(SMTPResponseException):
+ """Authentication error.
+
+ Most probably the server didn't accept the username/password
+ combination provided.
+ """
def quoteaddr(addr):
"""Quote a subset of the email addresses defined by RFC 821.
@@ -416,6 +428,84 @@ class SMTP:
return self.getreply()
# some useful methods
+
+ 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.
+
+ If there has been no previous EHLO or HELO command this session, this
+ method tries ESMTP EHLO first.
+
+ This method will return normally if the authentication was successful.
+
+ This method may raise the following exceptions:
+
+ SMTPHeloError The server didn't reply properly to
+ the helo greeting.
+ SMTPAuthenticationError The server didn't accept the username/
+ password combination.
+ SMTPError No suitable authentication method was
+ found.
+ """
+
+ def encode_cram_md5(challenge, user, password):
+ challenge = base64.decodestring(challenge)
+ response = user + " " + hmac.HMAC(password, challenge).hexdigest()
+ return base64.encodestring(response)[:-1]
+
+ def encode_plain(user, password):
+ return base64.encodestring("%s\0%s\0%s" %
+ (user, user, password))[:-1]
+
+ AUTH_PLAIN = "PLAIN"
+ AUTH_CRAM_MD5 = "CRAM-MD5"
+
+ if self.helo_resp is None and self.ehlo_resp is None:
+ if not (200 <= self.ehlo()[0] <= 299):
+ (code, resp) = self.helo()
+ if not (200 <= code <= 299):
+ raise SMTPHeloError(code, resp)
+
+ if not self.has_extn("auth"):
+ raise SMTPException("SMTP AUTH extension not supported by server.")
+
+ # Authentication methods the server supports:
+ authlist = self.esmtp_features["auth"].split()
+
+ # List of authentication methods we support: from preferred to
+ # less preferred methods. Except for the purpose of testing the weaker
+ # ones, we prefer stronger methods like CRAM-MD5:
+ preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN]
+ #preferred_auths = [AUTH_PLAIN, AUTH_CRAM_MD5]
+
+ # Determine the authentication method we'll use
+ authmethod = None
+ for method in preferred_auths:
+ if method in authlist:
+ authmethod = method
+ break
+ if self.debuglevel > 0: print "AuthMethod:", authmethod
+
+ if authmethod == AUTH_CRAM_MD5:
+ (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
+ if code == 503:
+ # 503 == 'Error: already authenticated'
+ return (code, resp)
+ (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 == None:
+ raise SMTPError("No suitable authentication method found.")
+ if code not in [235, 503]:
+ # 235 == 'Authentication successful'
+ # 503 == 'Error: already authenticated'
+ raise SMTPAuthenticationError(code, resp)
+ return (code, resp)
+
def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
rcpt_options=[]):
"""This command performs an entire mail transaction.