calibre-smtp: Verify relay server TLS certificates by default. New option --dont-verify-server-sertificate to restore old behavior.

This commit is contained in:
Kovid Goyal 2018-06-26 10:14:04 +05:30
parent 0fd8a9aee9
commit 37582afb76
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 23 additions and 7 deletions

View File

@ -129,7 +129,7 @@ def sendmail_direct(from_, to, msg, timeout, localhost, verbose,
def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=None, def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=None,
relay=None, username=None, password=None, encryption='TLS', relay=None, username=None, password=None, encryption='TLS',
port=-1, debug_output=None): port=-1, debug_output=None, verify_server_cert=False, cafile=None):
if relay is None: if relay is None:
for x in to: for x in to:
return sendmail_direct(from_, x, msg, timeout, localhost, verbose) return sendmail_direct(from_, x, msg, timeout, localhost, verbose)
@ -146,7 +146,11 @@ def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=None,
port = 25 if encryption != 'SSL' else 465 port = 25 if encryption != 'SSL' else 465
s.connect(relay, port) s.connect(relay, port)
if encryption == 'TLS': if encryption == 'TLS':
s.starttls() context = None
if verify_server_cert:
import ssl
context = ssl.create_default_context(cafile=cafile)
s.starttls(context=context)
s.ehlo() s.ehlo()
if username is not None and password is not None: if username is not None and password is not None:
if encryption == 'SSL': if encryption == 'SSL':
@ -205,6 +209,14 @@ are only used in the SMTP negotiation, the message headers are not modified.
choices=['TLS', 'SSL', 'NONE'], choices=['TLS', 'SSL', 'NONE'],
help=_('Encryption method to use when connecting to relay. Choices are ' help=_('Encryption method to use when connecting to relay. Choices are '
'TLS, SSL and NONE. Default is TLS. WARNING: Choosing NONE is highly insecure')) 'TLS, SSL and NONE. Default is TLS. WARNING: Choosing NONE is highly insecure'))
r('--dont-verify-server-certificate', help=_(
'Do not verify the server certificate when connecting using TLS. This used'
' to be the default behavior in calibre versions before 3.27. If you are using'
' a relay with a self-signed or otherwise invalid certificate, you can use this option to restore'
' the pre 3.27 behavior'))
r('--cafile', help=_(
'Path to a file of concatenated CA certificates in PEM format, used to verify the'
' server certificate when using TLS. By default, the system CA certificates are used.'))
parser.add_option('-o', '--outbox', help=_('Path to maildir folder to store ' parser.add_option('-o', '--outbox', help=_('Path to maildir folder to store '
'failed email messages in.')) 'failed email messages in.'))
parser.add_option('-f', '--fork', default=False, action='store_true', parser.add_option('-f', '--fork', default=False, action='store_true',
@ -285,7 +297,7 @@ def main(args=sys.argv):
sendmail(msg, efrom, eto, localhost=opts.localhost, verbose=opts.verbose, sendmail(msg, efrom, eto, localhost=opts.localhost, verbose=opts.verbose,
timeout=opts.timeout, relay=opts.relay, username=opts.username, timeout=opts.timeout, relay=opts.relay, username=opts.username,
password=opts.password, port=opts.port, password=opts.password, port=opts.port,
encryption=opts.encryption_method) encryption=opts.encryption_method, verify_server_cert=not opts.dont_verify_server_certificate, cafile=opts.cafile)
except: except:
if outbox is not None: if outbox is not None:
outbox.add(msg) outbox.add(msg)

View File

@ -265,6 +265,7 @@ class SMTP:
sys.stderr. You should pass in a print function of your own to control sys.stderr. You should pass in a print function of your own to control
where debug output is written. where debug output is written.
""" """
self._host = host
self.timeout = timeout self.timeout = timeout
self.debug = debug_to self.debug = debug_to
self.esmtp_features = {} self.esmtp_features = {}
@ -331,6 +332,7 @@ class SMTP:
port = self.default_port port = self.default_port
if self.debuglevel > 0: if self.debuglevel > 0:
self.debug('connect:', (host, port)) self.debug('connect:', (host, port))
self._host = host
self.sock = self._get_socket(host, port, self.timeout) self.sock = self._get_socket(host, port, self.timeout)
(code, msg) = self.getreply() (code, msg) = self.getreply()
if self.debuglevel > 0: if self.debuglevel > 0:
@ -385,8 +387,7 @@ class SMTP:
line = self.file.readline(_MAXLINE + 1) line = self.file.readline(_MAXLINE + 1)
except socket.error as e: except socket.error as e:
self.close() self.close()
raise SMTPServerDisconnected("Connection unexpectedly closed: " + raise SMTPServerDisconnected("Connection unexpectedly closed: " + str(e))
str(e))
if line == '': if line == '':
self.close() self.close()
raise SMTPServerDisconnected("Connection unexpectedly closed") raise SMTPServerDisconnected("Connection unexpectedly closed")
@ -647,7 +648,7 @@ class SMTP:
raise SMTPAuthenticationError(code, resp) raise SMTPAuthenticationError(code, resp)
return (code, resp) return (code, resp)
def starttls(self, keyfile=None, certfile=None): def starttls(self, context=None):
"""Puts the connection to the SMTP server into TLS mode. """Puts the connection to the SMTP server into TLS mode.
If there has been no previous EHLO or HELO command this session, this If there has been no previous EHLO or HELO command this session, this
@ -671,7 +672,10 @@ class SMTP:
if resp == 220: if resp == 220:
if not _have_ssl: if not _have_ssl:
raise RuntimeError("No SSL support included in this Python") raise RuntimeError("No SSL support included in this Python")
self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) 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) self.file = SSLFakeFile(self.sock)
# RFC 3207: # RFC 3207:
# The client MUST discard any knowledge obtained from # The client MUST discard any knowledge obtained from