diff options
author | Guido van Rossum <guido@python.org> | 2001-09-11 15:57:46 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2001-09-11 15:57:46 (GMT) |
commit | ae01046f7bf846dd984e89cf84e83d6ad0f45afd (patch) | |
tree | d7411bd75833a8a1e05599dc3de6a174d09947ec /Lib/smtplib.py | |
parent | f166994b8346708a867df823def3ab12e02d43e9 (diff) | |
download | cpython-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-x | Lib/smtplib.py | 96 |
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. |