diff --git a/src/calibre/utils/smtplib.py b/src/calibre/utils/smtplib.py index 61be1e905d..72241711b7 100644 --- a/src/calibre/utils/smtplib.py +++ b/src/calibre/utils/smtplib.py @@ -48,6 +48,7 @@ import email.utils import hmac import re import socket +import ssl from email.base64mime import body_encode as encode_base64 from functools import partial from sys import stderr @@ -191,40 +192,6 @@ def quotedata(data): re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) -try: - import ssl -except ImportError: - _have_ssl = False -else: - class SSLFakeFile: - '''A fake file like object that really wraps a SSLObject. - - It only supports what is needed in smtplib. - ''' - - def __init__(self, sslobj): - self.sslobj = sslobj - - def readline(self, size=-1): - if size < 0: - size = None - str = '' - chr = None - while chr != '\n': - if size is not None and len(str) >= size: - break - chr = self.sslobj.read(1) - if not chr: - break - str += chr - return str - - def close(self): - pass - - _have_ssl = True - - class SMTP: '''This class manages a connection to an SMTP or ESMTP server. SMTP Objects: @@ -677,13 +644,10 @@ class SMTP: raise SMTPException('STARTTLS extension not supported by server.') resp, reply = self.docmd('STARTTLS') if resp == 220: - if not _have_ssl: - raise RuntimeError('No SSL support included in this Python') if context is None: - self.sock = ssl.wrap_socket(self.sock) - else: - self.sock = context.wrap_socket(self.sock, server_hostname=self._host) - self.file = SSLFakeFile(self.sock) + context = ssl._create_stdlib_context() + self.sock = context.wrap_socket(self.sock, server_hostname=self._host) + self.file = None # RFC 3207: # The client MUST discard any knowledge obtained from # the server, such as the list of SMTP service extensions, @@ -812,37 +776,37 @@ class SMTP: return res -if _have_ssl: +class SMTP_SSL(SMTP): + ''' This is a subclass derived from SMTP that connects over an SSL encrypted + socket (to use this class you need a socket module that was compiled with SSL + support). If host is not specified, '' (the local host) is used. If port is + omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile + are also optional - they can contain a PEM formatted private key and + certificate chain file for the SSL connection. + ''' - class SMTP_SSL(SMTP): - ''' This is a subclass derived from SMTP that connects over an SSL encrypted - socket (to use this class you need a socket module that was compiled with SSL - support). If host is not specified, '' (the local host) is used. If port is - omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile - are also optional - they can contain a PEM formatted private key and - certificate chain file for the SSL connection. - ''' + default_port = SMTP_SSL_PORT - default_port = SMTP_SSL_PORT + def __init__(self, host='', port=0, local_hostname=None, + keyfile=None, certfile=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + debug_to=partial(print, file=stderr), context=None): + self.keyfile = keyfile + self.certfile = certfile + if context is None: + context = ssl._create_stdlib_context() + self.context = context + SMTP.__init__(self, host, port, local_hostname, timeout, + debug_to=debug_to) - def __init__(self, host='', port=0, local_hostname=None, - keyfile=None, certfile=None, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - debug_to=partial(print, file=stderr)): - self.keyfile = keyfile - self.certfile = certfile - SMTP.__init__(self, host, port, local_hostname, timeout, - debug_to=debug_to) + def _get_socket(self, host, port, timeout): + new_socket = super()._get_socket(host, port, timeout) + new_socket = self.context.wrap_socket(new_socket, + server_hostname=self._host) + return new_socket - def _get_socket(self, host, port, timeout): - if self.debuglevel > 0: - self.debug('connect:', (host, port)) - new_socket = socket.create_connection((host, port), timeout) - new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile) - self.file = SSLFakeFile(new_socket) - return new_socket - __all__.append('SMTP_SSL') +__all__.append('SMTP_SSL') # # LMTP extension