diff options
Diffstat (limited to 'Lib/smtplib.py')
| -rwxr-xr-x | Lib/smtplib.py | 79 |
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: |
