summaryrefslogtreecommitdiff
path: root/Lib/smtplib.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/smtplib.py')
-rwxr-xr-xLib/smtplib.py79
1 files changed, 54 insertions, 25 deletions
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 57f181b986..ac1f593789 100755
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -67,7 +67,7 @@ _MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3
OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
# Exception classes used by this module.
-class SMTPException(Exception):
+class SMTPException(OSError):
"""Base class for all exceptions raised by this module."""
class SMTPServerDisconnected(SMTPException):
@@ -233,6 +233,7 @@ class SMTP:
will be used.
"""
+ self._host = host
self.timeout = timeout
self.esmtp_features = {}
self.source_address = source_address
@@ -312,7 +313,7 @@ class SMTP:
try:
port = int(port)
except ValueError:
- raise socket.error("nonnumeric port")
+ raise OSError("nonnumeric port")
if not port:
port = self.default_port
if self.debuglevel > 0:
@@ -333,7 +334,7 @@ class SMTP:
s = s.encode("ascii")
try:
self.sock.sendall(s)
- except socket.error:
+ except OSError:
self.close()
raise SMTPServerDisconnected('Server not connected')
else:
@@ -366,7 +367,7 @@ class SMTP:
while 1:
try:
line = self.file.readline(_MAXLINE + 1)
- except socket.error as e:
+ except OSError as e:
self.close()
raise SMTPServerDisconnected("Connection unexpectedly closed: "
+ str(e))
@@ -376,6 +377,7 @@ class SMTP:
if self.debuglevel > 0:
print('reply:', repr(line), file=stderr)
if len(line) > _MAXLINE:
+ self.close()
raise SMTPResponseException(500, "Line too long.")
resp.append(line[4:].strip(b' \t\r\n'))
code = line[:3]
@@ -477,6 +479,18 @@ class SMTP:
"""SMTP 'rset' command -- resets session."""
return self.docmd("rset")
+ def _rset(self):
+ """Internal 'rset' command which ignores any SMTPServerDisconnected error.
+
+ Used internally in the library, since the server disconnected error
+ should appear to the application when the *next* command is issued, if
+ we are doing an internal "safety" reset.
+ """
+ try:
+ self.rset()
+ except SMTPServerDisconnected:
+ pass
+
def noop(self):
"""SMTP 'noop' command -- doesn't do anything :>"""
return self.docmd("noop")
@@ -504,8 +518,8 @@ class SMTP:
Raises SMTPDataError if there is an unexpected reply to the
DATA command; the return value from this method is the final
response code received when the all data is sent. If msg
- is a string, lone '\r' and '\n' characters are converted to
- '\r\n' characters. If msg is bytes, it is transmitted as is.
+ is a string, lone '\\r' and '\\n' characters are converted to
+ '\\r\\n' characters. If msg is bytes, it is transmitted as is.
"""
self.putcmd("data")
(code, repl) = self.getreply()
@@ -582,7 +596,7 @@ class SMTP:
def encode_cram_md5(challenge, user, password):
challenge = base64.decodebytes(challenge)
response = user + " " + hmac.HMAC(password.encode('ascii'),
- challenge).hexdigest()
+ challenge, 'md5').hexdigest()
return encode_base64(response.encode('ascii'), eol='')
def encode_plain(user, password):
@@ -667,10 +681,11 @@ class SMTP:
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
- if context is not None:
- self.sock = context.wrap_socket(self.sock)
- else:
- self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
+ if context is None:
+ context = ssl._create_stdlib_context(certfile=certfile,
+ keyfile=keyfile)
+ self.sock = context.wrap_socket(self.sock,
+ server_hostname=self._host)
self.file = None
# RFC 3207:
# The client MUST discard any knowledge obtained from
@@ -680,6 +695,11 @@ class SMTP:
self.ehlo_resp = None
self.esmtp_features = {}
self.does_esmtp = 0
+ else:
+ # RFC 3207:
+ # 501 Syntax error (no parameters allowed)
+ # 454 TLS not available due to temporary reason
+ raise SMTPResponseException(resp, reply)
return (resp, reply)
def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
@@ -759,7 +779,7 @@ class SMTP:
if code == 421:
self.close()
else:
- self.rset()
+ self._rset()
raise SMTPSenderRefused(code, resp, from_addr)
senderrs = {}
if isinstance(to_addrs, str):
@@ -773,14 +793,14 @@ class SMTP:
raise SMTPRecipientsRefused(senderrs)
if len(senderrs) == len(to_addrs):
# the server refused all our recipients
- self.rset()
+ self._rset()
raise SMTPRecipientsRefused(senderrs)
(code, resp) = self.data(msg)
if code != 250:
if code == 421:
self.close()
else:
- self.rset()
+ self._rset()
raise SMTPDataError(code, resp)
#if we got here then somebody got our mail
return senderrs
@@ -840,16 +860,24 @@ class SMTP:
def close(self):
"""Close the connection to the SMTP server."""
- if self.file:
- self.file.close()
- self.file = None
- if self.sock:
- self.sock.close()
- self.sock = None
+ try:
+ file = self.file
+ self.file = None
+ if file:
+ file.close()
+ finally:
+ sock = self.sock
+ self.sock = None
+ if sock:
+ sock.close()
def quit(self):
"""Terminate the SMTP session."""
res = self.docmd("quit")
+ # A new EHLO is required after reconnecting with connect()
+ self.ehlo_resp = self.helo_resp = None
+ self.esmtp_features = {}
+ self.does_esmtp = False
self.close()
return res
@@ -883,6 +911,9 @@ if _have_ssl:
"exclusive")
self.keyfile = keyfile
self.certfile = certfile
+ if context is None:
+ context = ssl._create_stdlib_context(certfile=certfile,
+ keyfile=keyfile)
self.context = context
SMTP.__init__(self, host, port, local_hostname, timeout,
source_address)
@@ -892,10 +923,8 @@ if _have_ssl:
print('connect:', (host, port), file=stderr)
new_socket = socket.create_connection((host, port), timeout,
self.source_address)
- if self.context is not None:
- new_socket = self.context.wrap_socket(new_socket)
- else:
- new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
+ new_socket = self.context.wrap_socket(new_socket,
+ server_hostname=self._host)
return new_socket
__all__.append("SMTP_SSL")
@@ -937,7 +966,7 @@ class LMTP(SMTP):
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.file = None
self.sock.connect(host)
- except socket.error:
+ except OSError:
if self.debuglevel > 0:
print('connect fail:', host, file=stderr)
if self.sock: