From 0e1dc84203b391416bf8e29a3d9176a973da4937 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Sep 2025 13:23:25 +0530 Subject: [PATCH] CodeQL: Update the smtplib module to use the ssl stdlib context This prevents usage of outdated SSL protocol versions and matches the behavior of the python stdlib smtplib library as of python 3.13 --- src/calibre/utils/smtplib.py | 96 +++++++++++------------------------- 1 file changed, 30 insertions(+), 66 deletions(-) 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