Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tail-call.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ jobs:
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
brew install llvm@${{ matrix.llvm }}
export SDKROOT="$(xcrun --show-sdk-path)"
export PATH="/usr/local/opt/llvm/bin:$PATH"
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
export PATH="/usr/local/opt/llvm@${{ matrix.llvm }}/bin:$PATH"
export PATH="/opt/homebrew/opt/llvm@${{ matrix.llvm }}/bin:$PATH"
CC=clang-20 ./configure --with-tail-call-interp
make all --jobs 4
./python.exe -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1764,7 +1764,7 @@ always available. Unless explicitly noted otherwise, all variables are read-only
:func:`settrace` for each thread being debugged or use :func:`threading.settrace`.

Trace functions should have three arguments: *frame*, *event*, and
*arg*. *frame* is the current stack frame. *event* is a string: ``'call'``,
*arg*. *frame* is the :ref:`current stack frame <frame-objects>`. *event* is a string: ``'call'``,
``'line'``, ``'return'``, ``'exception'`` or ``'opcode'``. *arg* depends on
the event type.

Expand Down
23 changes: 18 additions & 5 deletions Lib/smtplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ def _quote_periods(bindata):
def _fix_eols(data):
return re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)


try:
hmac.digest(b'', b'', 'md5')
except ValueError:
_have_cram_md5_support = False
else:
_have_cram_md5_support = True


try:
import ssl
except ImportError:
Expand Down Expand Up @@ -665,8 +674,11 @@ def auth_cram_md5(self, challenge=None):
# CRAM-MD5 does not support initial-response.
if challenge is None:
return None
return self.user + " " + hmac.HMAC(
self.password.encode('ascii'), challenge, 'md5').hexdigest()
if not _have_cram_md5_support:
raise SMTPException("CRAM-MD5 is not supported")
password = self.password.encode('ascii')
authcode = hmac.HMAC(password, challenge, 'md5')
return f"{self.user} {authcode.hexdigest()}"

def auth_plain(self, challenge=None):
""" Authobject to use with PLAIN authentication. Requires self.user and
Expand Down Expand Up @@ -718,9 +730,10 @@ def login(self, user, password, *, initial_response_ok=True):
advertised_authlist = self.esmtp_features["auth"].split()

# Authentication methods we can handle in our preferred order:
preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN']

# We try the supported authentications in our preferred order, if
if _have_cram_md5_support:
preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN']
else:
preferred_auths = ['PLAIN', 'LOGIN']
# the server supports them.
authlist = [auth for auth in preferred_auths
if auth in advertised_authlist]
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_dbm_sqlite3.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
from dbm.sqlite3 import _normalize_uri


root_in_posix = False
if hasattr(os, 'geteuid'):
root_in_posix = (os.geteuid() == 0)


class _SQLiteDbmTests(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -92,6 +97,7 @@ def test_readonly_iter(self):
self.assertEqual([k for k in self.db], [b"key1", b"key2"])


@unittest.skipIf(root_in_posix, "test is meanless with root privilege")
class ReadOnlyFilesystem(unittest.TestCase):

def setUp(self):
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,19 @@ def test_ast_fini(self):
""")
assert_python_ok("-c", code)

def test_warnings_fini(self):
# See https://github.com/python/cpython/issues/137384
code = textwrap.dedent('''
import asyncio
from contextvars import ContextVar

context_loop = ContextVar("context_loop", default=None)
loop = asyncio.new_event_loop()
context_loop.set(loop)
''')

assert_python_ok("-c", code)


def setUpModule():
global enabled, debug
Expand Down
47 changes: 42 additions & 5 deletions Lib/test/test_smtplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import threading

import unittest
import unittest.mock as mock
from test import support, mock_socket
from test.support import hashlib_helper
from test.support import socket_helper
Expand Down Expand Up @@ -926,11 +927,14 @@ def _auth_cram_md5(self, arg=None):
except ValueError as e:
self.push('535 Splitting response {!r} into user and password '
'failed: {}'.format(logpass, e))
return False
valid_hashed_pass = hmac.HMAC(
sim_auth[1].encode('ascii'),
self._decode_base64(sim_cram_md5_challenge).encode('ascii'),
'md5').hexdigest()
return
pwd = sim_auth[1].encode('ascii')
msg = self._decode_base64(sim_cram_md5_challenge).encode('ascii')
try:
valid_hashed_pass = hmac.HMAC(pwd, msg, 'md5').hexdigest()
except ValueError:
self.push('504 CRAM-MD5 is not supported')
return
self._authenticated(user, hashed_pass == valid_hashed_pass)
# end AUTH related stuff.

Expand Down Expand Up @@ -1181,6 +1185,39 @@ def testAUTH_CRAM_MD5(self):
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()

@hashlib_helper.block_algorithm('md5')
@mock.patch("smtplib._have_cram_md5_support", False)
def testAUTH_CRAM_MD5_blocked(self):
# CRAM-MD5 is the only "known" method by the server,
# but it is not supported by the client. In particular,
# no challenge will ever be sent.
self.serv.add_feature("AUTH CRAM-MD5")
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
msg = re.escape("No suitable authentication method found.")
with self.assertRaisesRegex(smtplib.SMTPException, msg):
smtp.login(sim_auth[0], sim_auth[1])

@hashlib_helper.block_algorithm('md5')
@mock.patch("smtplib._have_cram_md5_support", False)
def testAUTH_CRAM_MD5_blocked_and_fallback(self):
# Test that PLAIN is tried after CRAM-MD5 failed
self.serv.add_feature("AUTH CRAM-MD5 PLAIN")
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
with (
mock.patch.object(smtp, "auth_cram_md5") as smtp_auth_cram_md5,
mock.patch.object(
smtp, "auth_plain", wraps=smtp.auth_plain
) as smtp_auth_plain
):
resp = smtp.login(sim_auth[0], sim_auth[1])
smtp_auth_plain.assert_called_once()
smtp_auth_cram_md5.assert_not_called()
self.assertEqual(resp, (235, b'Authentication Succeeded'))

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def testAUTH_multiple(self):
# Test that multiple authentication methods are tried.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a crash when using the :mod:`warnings` module in a finalizer at shutdown. Patch by Kumar Aditya.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:meth:`!SMTP.auth_cram_md5` now raises an :exc:`~smtplib.SMTPException`
instead of a :exc:`ValueError` if Python has been built without MD5 support.
In particular, :class:`~smtplib.SMTP` clients will not attempt to use this
method even if the remote server is assumed to support it. Patch by Bénédikt
Tran.
5 changes: 4 additions & 1 deletion Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
_Py_ClearExecutorDeletionList(interp);
#endif
_PyAST_Fini(interp);
_PyWarnings_Fini(interp);
_PyAtExit_Fini(interp);

// All Python types must be destroyed before the last GC collection. Python
Expand All @@ -815,6 +814,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
/* Last garbage collection on this interpreter */
_PyGC_CollectNoFail(tstate);
_PyGC_Fini(interp);

// Finalize warnings after last gc so that any finalizers can
// access warnings state
_PyWarnings_Fini(interp);
struct _PyExecutorObject *cold = interp->cold_executor;
if (cold != NULL) {
interp->cold_executor = NULL;
Expand Down
Loading