diff --git a/.python3.11.checksum b/.python3.11.checksum
index aadcaae93b2c63a2711248b93f13f9fbc23a89f3..f0eb027bd1abbdb62008b77dfa7ce0e3f4b420bc 100644
--- a/.python3.11.checksum
+++ b/.python3.11.checksum
@@ -1 +1 @@
-1d78875a13fabf6d44fc113db389ff8d9936f0a9d00613c60a149814f5e9357d
+0077902d7fd66f5994187ec1cffe8fb343286ece0bbc8d38a97ac540b13600bd
diff --git a/.python3.11.metadata b/.python3.11.metadata
index 1b385f804ba9f5a335c5f098e4f7419783661d5f..019ffb6091d8b6c7d7d621fea02453d9ea61bbea 100644
--- a/.python3.11.metadata
+++ b/.python3.11.metadata
@@ -1,2 +1,2 @@
-2f0e409df2ab57aa9fc4cbddfb976af44e4e55bf6f619eee6bc5c2297264a7f6 SOURCES/Python-3.11.4.tar.xz
+18e1aa7e66ff3a58423d59ed22815a6954e53342122c45df20c96877c062b9b7 SOURCES/Python-3.11.7.tar.xz
 fb28243ffeb9725b14b60586a9a123682a89604c025b7a9d4bcdeb67078203c6 SOURCES/pgp_keys.asc
diff --git a/SOURCES/00329-fips.patch b/SOURCES/00329-fips.patch
index b4763dd997e2bb89b8ab02b11f623c144aa8bbda..256d214e657edec760b93d9db87a23b61e69c172 100644
--- a/SOURCES/00329-fips.patch
+++ b/SOURCES/00329-fips.patch
@@ -1,4 +1,4 @@
-From c96f1bea2ffc5c0ca849d5406236c07ea229a64f Mon Sep 17 00:00:00 2001
+From ecc5137120f471c22ff6dcb1bd128561c31e023c Mon Sep 17 00:00:00 2001
 From: Charalampos Stratakis <cstratak@redhat.com>
 Date: Thu, 12 Dec 2019 16:58:31 +0100
 Subject: [PATCH 1/7] Expose blake2b and blake2s hashes from OpenSSL
@@ -29,10 +29,10 @@ index 67becdd..6607ef7 100644
              computed = m.hexdigest() if not shake else m.hexdigest(length)
              self.assertEqual(
 diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
-index 3c40f09..e819d02 100644
+index 57d64bd..d0c3b9e 100644
 --- a/Modules/_hashopenssl.c
 +++ b/Modules/_hashopenssl.c
-@@ -1077,6 +1077,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
+@@ -1078,6 +1078,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
  }
  
  
@@ -74,7 +74,7 @@ index 3c40f09..e819d02 100644
  #ifdef PY_OPENSSL_HAS_SHA3
  
  /*[clinic input]
-@@ -2065,6 +2100,8 @@ static struct PyMethodDef EVP_functions[] = {
+@@ -2066,6 +2101,8 @@ static struct PyMethodDef EVP_functions[] = {
      _HASHLIB_OPENSSL_SHA256_METHODDEF
      _HASHLIB_OPENSSL_SHA384_METHODDEF
      _HASHLIB_OPENSSL_SHA512_METHODDEF
@@ -205,10 +205,10 @@ index 5d84f4a..011026a 100644
 -/*[clinic end generated code: output=69f2374071bff707 input=a9049054013a1b77]*/
 +/*[clinic end generated code: output=c6a9af5563972eda input=a9049054013a1b77]*/
 -- 
-2.39.1
+2.43.0
 
 
-From 9a7e164840aa35602e1c6dddadd461fafc666a63 Mon Sep 17 00:00:00 2001
+From 0198d467525e79cb4be4418708719af3eaee7a40 Mon Sep 17 00:00:00 2001
 From: Petr Viktorin <pviktori@redhat.com>
 Date: Thu, 1 Aug 2019 17:57:05 +0200
 Subject: [PATCH 2/7] Use a stronger hash in multiprocessing handshake
@@ -220,10 +220,10 @@ https://bugs.python.org/issue17258
  1 file changed, 6 insertions(+), 2 deletions(-)
 
 diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py
-index b08144f..0497557 100644
+index 8b81f99..69c0b7e 100644
 --- a/Lib/multiprocessing/connection.py
 +++ b/Lib/multiprocessing/connection.py
-@@ -42,6 +42,10 @@ BUFSIZE = 8192
+@@ -43,6 +43,10 @@ BUFSIZE = 8192
  # A very generous timeout when it comes to local connections...
  CONNECTION_TIMEOUT = 20.
  
@@ -234,7 +234,7 @@ index b08144f..0497557 100644
  _mmap_counter = itertools.count()
  
  default_family = 'AF_INET'
-@@ -735,7 +739,7 @@ def deliver_challenge(connection, authkey):
+@@ -752,7 +756,7 @@ def deliver_challenge(connection, authkey):
              "Authkey must be bytes, not {0!s}".format(type(authkey)))
      message = os.urandom(MESSAGE_LENGTH)
      connection.send_bytes(CHALLENGE + message)
@@ -243,7 +243,7 @@ index b08144f..0497557 100644
      response = connection.recv_bytes(256)        # reject large message
      if response == digest:
          connection.send_bytes(WELCOME)
-@@ -751,7 +755,7 @@ def answer_challenge(connection, authkey):
+@@ -768,7 +772,7 @@ def answer_challenge(connection, authkey):
      message = connection.recv_bytes(256)         # reject large message
      assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message
      message = message[len(CHALLENGE):]
@@ -253,10 +253,10 @@ index b08144f..0497557 100644
      response = connection.recv_bytes(256)        # reject large message
      if response != WELCOME:
 -- 
-2.39.1
+2.43.0
 
 
-From 10b91783a2f22153738c5658a98daf7475ad9a8c Mon Sep 17 00:00:00 2001
+From a7822e2e1f21529e9730885bd8c9c6ab7c704d5b Mon Sep 17 00:00:00 2001
 From: Petr Viktorin <pviktori@redhat.com>
 Date: Thu, 25 Jul 2019 17:19:06 +0200
 Subject: [PATCH 3/7] Disable Python's hash implementations in FIPS mode,
@@ -359,7 +359,7 @@ index c2cac98..55b1677 100644
  
      if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE)
 diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c
-index 44d783b..d247e44 100644
+index 93478f5..e3a024d 100644
 --- a/Modules/_blake2/blake2module.c
 +++ b/Modules/_blake2/blake2module.c
 @@ -13,6 +13,7 @@
@@ -370,7 +370,7 @@ index 44d783b..d247e44 100644
  #include "blake2module.h"
  
  extern PyType_Spec blake2b_type_spec;
-@@ -77,6 +78,7 @@ _blake2_free(void *module)
+@@ -83,6 +84,7 @@ _blake2_free(void *module)
  static int
  blake2_exec(PyObject *m)
  {
@@ -378,7 +378,7 @@ index 44d783b..d247e44 100644
      Blake2State* st = blake2_get_state(m);
  
      st->blake2b_type = (PyTypeObject *)PyType_FromModuleAndSpec(
-@@ -145,5 +147,6 @@ static struct PyModuleDef blake2_module = {
+@@ -154,5 +156,6 @@ static struct PyModuleDef blake2_module = {
  PyMODINIT_FUNC
  PyInit__blake2(void)
  {
@@ -446,10 +446,10 @@ index 56ae7a5..45fb403 100644
 +    if (_Py_hashlib_fips_error(exc, name)) return NULL; \
 +} while (0)
 diff --git a/configure.ac b/configure.ac
-index c62a565..861f7a0 100644
+index 52d5c1f..56aff78 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -7044,7 +7044,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes])
+@@ -7069,7 +7069,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes])
  PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes])
  PY_STDLIB_MOD([_blake2],
    [test "$with_builtin_blake2" = yes], [],
@@ -460,10 +460,10 @@ index c62a565..861f7a0 100644
  PY_STDLIB_MOD([_crypt],
    [], [test "$ac_cv_crypt_crypt" = yes],
 -- 
-2.39.1
+2.43.0
 
 
-From e26066b1c05c9768e38cb6f45d6a01058de55b3f Mon Sep 17 00:00:00 2001
+From e9ce6d33544559172dbebbe0c0dfba2757c62331 Mon Sep 17 00:00:00 2001
 From: Charalampos Stratakis <cstratak@redhat.com>
 Date: Fri, 29 Jan 2021 14:16:21 +0100
 Subject: [PATCH 4/7] Use python's fall back crypto implementations only if we
@@ -623,10 +623,10 @@ index 01d12f5..a7cdb07 100644
      def test_pbkdf2_hmac_py(self):
          with warnings_helper.check_warnings():
 -- 
-2.39.1
+2.43.0
 
 
-From 9ccbd22b8538fee379717c8b2916dc1ff8b96f07 Mon Sep 17 00:00:00 2001
+From 641c617775b6973ed84711a2602ba190fe064474 Mon Sep 17 00:00:00 2001
 From: Charalampos Stratakis <cstratak@redhat.com>
 Date: Wed, 31 Jul 2019 15:43:43 +0200
 Subject: [PATCH 5/7] Test equivalence of hashes for the various digests with
@@ -783,10 +783,10 @@ index a7cdb07..c071f28 100644
  class KDFTests(unittest.TestCase):
  
 -- 
-2.39.1
+2.43.0
 
 
-From c3b8d6ecc76c87e8b05fd2cb212d5dece50ce0b1 Mon Sep 17 00:00:00 2001
+From a706c8342f0f9307d44c43c203702e1476fe73b4 Mon Sep 17 00:00:00 2001
 From: Petr Viktorin <pviktori@redhat.com>
 Date: Mon, 26 Aug 2019 19:39:48 +0200
 Subject: [PATCH 6/7] Guard against Python HMAC in FIPS mode
@@ -844,7 +844,7 @@ index 8b4f920..20ef96c 100644
              digest_cons = digestmod
          elif isinstance(digestmod, str):
 diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
-index 7cf9973..a9e4e39 100644
+index a39a2c4..0742a1c 100644
 --- a/Lib/test/test_hmac.py
 +++ b/Lib/test/test_hmac.py
 @@ -5,6 +5,7 @@ import hashlib
@@ -875,7 +875,7 @@ index 7cf9973..a9e4e39 100644
          with warnings.catch_warnings():
              warnings.simplefilter('error', RuntimeWarning)
              with self.assertRaises(RuntimeWarning):
-@@ -443,6 +450,7 @@ class ConstructorTestCase(unittest.TestCase):
+@@ -453,6 +460,7 @@ class ConstructorTestCase(unittest.TestCase):
          with self.assertRaisesRegex(TypeError, "immutable type"):
              C_HMAC.value = None
  
@@ -883,7 +883,7 @@ index 7cf9973..a9e4e39 100644
      @unittest.skipUnless(sha256_module is not None, 'need _sha256')
      def test_with_sha256_module(self):
          h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256)
-@@ -471,6 +479,7 @@ class SanityTestCase(unittest.TestCase):
+@@ -481,6 +489,7 @@ class SanityTestCase(unittest.TestCase):
  
  class CopyTestCase(unittest.TestCase):
  
@@ -891,7 +891,7 @@ index 7cf9973..a9e4e39 100644
      @hashlib_helper.requires_hashdigest('sha256')
      def test_attributes_old(self):
          # Testing if attributes are of same type.
-@@ -482,6 +491,7 @@ class CopyTestCase(unittest.TestCase):
+@@ -492,6 +501,7 @@ class CopyTestCase(unittest.TestCase):
          self.assertEqual(type(h1._outer), type(h2._outer),
              "Types of outer don't match.")
  
@@ -900,10 +900,10 @@ index 7cf9973..a9e4e39 100644
      def test_realcopy_old(self):
          # Testing if the copy method created a real copy.
 -- 
-2.39.1
+2.43.0
 
 
-From 2b06ee89344e8735cdc8435aadbdf83fe289e934 Mon Sep 17 00:00:00 2001
+From 03f1dedfe5d29af20fb3686d76b045384d41d8dd Mon Sep 17 00:00:00 2001
 From: Petr Viktorin <encukou@gmail.com>
 Date: Wed, 25 Aug 2021 16:44:43 +0200
 Subject: [PATCH 7/7] Disable hash-based PYCs in FIPS mode
@@ -946,11 +946,11 @@ index db52725..5fca65e 100644
          return PycInvalidationMode.CHECKED_HASH
      else:
 diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
-index c33f90d..7d40540 100644
+index dc7a6e6..646b328 100644
 --- a/Lib/test/support/__init__.py
 +++ b/Lib/test/support/__init__.py
-@@ -2225,6 +2225,20 @@ def requires_venv_with_pip():
-     return unittest.skipUnless(ctypes, 'venv: pip requires ctypes')
+@@ -2203,6 +2203,20 @@ def sleeping_retry(timeout, err_msg=None, /,
+         delay = min(delay * 2, max_delay)
  
  
 +def fails_in_fips_mode(expected_error):
@@ -971,7 +971,7 @@ index c33f90d..7d40540 100644
  def adjust_int_max_str_digits(max_digits):
      """Temporarily change the integer string conversion length limit."""
 diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
-index 4dadbc0..7dc7e51 100644
+index 7fcd563..476b557 100644
 --- a/Lib/test/test_cmd_line_script.py
 +++ b/Lib/test/test_cmd_line_script.py
 @@ -286,6 +286,7 @@ class CmdLineTest(unittest.TestCase):
@@ -991,10 +991,10 @@ index 4dadbc0..7dc7e51 100644
          with os_helper.temp_dir() as script_dir:
              script_name = _make_test_script(script_dir, '__main__')
 diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
-index 05154c8..c678d4a 100644
+index 9cd92ad..4ec29a1 100644
 --- a/Lib/test/test_compileall.py
 +++ b/Lib/test/test_compileall.py
-@@ -800,14 +800,23 @@ class CommandLineTestsBase:
+@@ -806,14 +806,23 @@ class CommandLineTestsBase:
          out = self.assertRunOK('badfilename')
          self.assertRegex(out, b"Can't list 'badfilename'")
  
@@ -1020,10 +1020,10 @@ index 05154c8..c678d4a 100644
          with open(pyc, 'rb') as fp:
              data = fp.read()
 diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py
-index 4bb0390..ff62483 100644
+index 4062afd..6bc276d 100644
 --- a/Lib/test/test_imp.py
 +++ b/Lib/test/test_imp.py
-@@ -350,6 +350,7 @@ class ImportTests(unittest.TestCase):
+@@ -352,6 +352,7 @@ class ImportTests(unittest.TestCase):
          import _frozen_importlib
          self.assertEqual(_frozen_importlib.__spec__.origin, "frozen")
  
@@ -1031,7 +1031,7 @@ index 4bb0390..ff62483 100644
      def test_source_hash(self):
          self.assertEqual(_imp.source_hash(42, b'hi'), b'\xfb\xd9G\x05\xaf$\x9b~')
          self.assertEqual(_imp.source_hash(43, b'hi'), b'\xd0/\x87C\xccC\xff\xe2')
-@@ -369,6 +370,7 @@ class ImportTests(unittest.TestCase):
+@@ -371,6 +372,7 @@ class ImportTests(unittest.TestCase):
              res = script_helper.assert_python_ok(*args)
              self.assertEqual(res.out.strip().decode('utf-8'), expected)
  
@@ -1092,10 +1092,10 @@ index 378dcbe..7b223a1 100644
          with util.create_modules('_temp') as mapping:
              bc_path = self.manipulate_bytecode(
 diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
-index e53f5d9..7266212 100644
+index 9b420d2..dd6460a 100644
 --- a/Lib/test/test_py_compile.py
 +++ b/Lib/test/test_py_compile.py
-@@ -141,13 +141,16 @@ class PyCompileTestsBase:
+@@ -143,13 +143,16 @@ class PyCompileTestsBase:
              importlib.util.cache_from_source(bad_coding)))
  
      def test_source_date_epoch(self):
@@ -1113,7 +1113,7 @@ index e53f5d9..7266212 100644
              expected_flags = 0b11
          else:
              expected_flags = 0b00
-@@ -178,7 +181,8 @@ class PyCompileTestsBase:
+@@ -180,7 +183,8 @@ class PyCompileTestsBase:
          # Specifying optimized bytecode should lead to a path reflecting that.
          self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2))
  
@@ -1123,7 +1123,7 @@ index e53f5d9..7266212 100644
          py_compile.compile(
              self.source_path,
              invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
-@@ -187,6 +191,9 @@ class PyCompileTestsBase:
+@@ -189,6 +193,9 @@ class PyCompileTestsBase:
              flags = importlib._bootstrap_external._classify_pyc(
                  fp.read(), 'test', {})
          self.assertEqual(flags, 0b11)
@@ -1154,10 +1154,10 @@ index 59a5200..81fadb3 100644
      def test_checked_hash_based_change_pyc(self):
          source = b"state = 'old'"
 diff --git a/Python/import.c b/Python/import.c
-index 07a8b90..e97b47b 100644
+index 39144d3..b439059 100644
 --- a/Python/import.c
 +++ b/Python/import.c
-@@ -2437,6 +2437,26 @@ static PyObject *
+@@ -2449,6 +2449,26 @@ static PyObject *
  _imp_source_hash_impl(PyObject *module, long key, Py_buffer *source)
  /*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/
  {
@@ -1185,5 +1185,5 @@ index 07a8b90..e97b47b 100644
          uint64_t x;
          char data[sizeof(uint64_t)];
 -- 
-2.39.1
+2.43.0
 
diff --git a/SOURCES/00397-tarfile-filter.patch b/SOURCES/00397-tarfile-filter.patch
index bd8f98f853aab10b17edb2a7c6c2e2e6e6c23e6f..3c4ebf41bf15466a659253b4caf20b0a4f509aee 100644
--- a/SOURCES/00397-tarfile-filter.patch
+++ b/SOURCES/00397-tarfile-filter.patch
@@ -1,113 +1,3 @@
-From f36519078bde3cce4328c03fffccb846121fb5bc Mon Sep 17 00:00:00 2001
-From: Petr Viktorin <encukou@gmail.com>
-Date: Wed, 9 Aug 2023 20:23:03 +0200
-Subject: [PATCH] Fix symlink handling for tarfile.data_filter
-
----
- Doc/library/tarfile.rst  |  5 +++++
- Lib/tarfile.py           |  9 ++++++++-
- Lib/test/test_tarfile.py | 26 ++++++++++++++++++++++++--
- 3 files changed, 37 insertions(+), 3 deletions(-)
-
-diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
-index 00f3070324e..e0511bfeb64 100644
---- a/Doc/library/tarfile.rst
-+++ b/Doc/library/tarfile.rst
-@@ -740,6 +740,11 @@ A ``TarInfo`` object has the following public data attributes:
-    Name of the target file name, which is only present in :class:`TarInfo` objects
-    of type :const:`LNKTYPE` and :const:`SYMTYPE`.
- 
-+   For symbolic links (``SYMTYPE``), the linkname is relative to the directory
-+   that contains the link.
-+   For hard links (``LNKTYPE``), the linkname is relative to the root of
-+   the archive.
-+
- 
- .. attribute:: TarInfo.uid
-    :type: int
-diff --git a/Lib/tarfile.py b/Lib/tarfile.py
-index df4e41f7a0d..d62323715b4 100755
---- a/Lib/tarfile.py
-+++ b/Lib/tarfile.py
-@@ -802,7 +802,14 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
-         if member.islnk() or member.issym():
-             if os.path.isabs(member.linkname):
-                 raise AbsoluteLinkError(member)
--            target_path = os.path.realpath(os.path.join(dest_path, member.linkname))
-+            if member.issym():
-+                target_path = os.path.join(dest_path,
-+                                           os.path.dirname(name),
-+                                           member.linkname)
-+            else:
-+                target_path = os.path.join(dest_path,
-+                                           member.linkname)
-+            target_path = os.path.realpath(target_path)
-             if os.path.commonpath([target_path, dest_path]) != dest_path:
-                 raise LinkOutsideDestinationError(member, target_path)
-     return new_attrs
-diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
-index 2eda7fc4cea..79fc35c2895 100644
---- a/Lib/test/test_tarfile.py
-+++ b/Lib/test/test_tarfile.py
-@@ -3337,10 +3337,12 @@ def __exit__(self, *exc):
-         self.bio = None
- 
-     def add(self, name, *, type=None, symlink_to=None, hardlink_to=None,
--            mode=None, **kwargs):
-+            mode=None, size=None, **kwargs):
-         """Add a member to the test archive. Call within `with`."""
-         name = str(name)
-         tarinfo = tarfile.TarInfo(name).replace(**kwargs)
-+        if size is not None:
-+            tarinfo.size = size
-         if mode:
-             tarinfo.mode = _filemode_to_int(mode)
-         if symlink_to is not None:
-@@ -3416,7 +3418,8 @@ def check_context(self, tar, filter):
-                 raise self.raised_exception
-             self.assertEqual(self.expected_paths, set())
- 
--    def expect_file(self, name, type=None, symlink_to=None, mode=None):
-+    def expect_file(self, name, type=None, symlink_to=None, mode=None,
-+                    size=None):
-         """Check a single file. See check_context."""
-         if self.raised_exception:
-             raise self.raised_exception
-@@ -3445,6 +3448,8 @@ def expect_file(self, name, type=None, symlink_to=None, mode=None):
-             self.assertTrue(path.is_fifo())
-         else:
-             raise NotImplementedError(type)
-+        if size is not None:
-+            self.assertEqual(path.stat().st_size, size)
-         for parent in path.parents:
-             self.expected_paths.discard(parent)
- 
-@@ -3649,6 +3654,22 @@ def test_sly_relative2(self):
-                     + """['"].*moo['"], which is outside the """
-                     + "destination")
- 
-+    def test_deep_symlink(self):
-+        with ArchiveMaker() as arc:
-+            arc.add('targetdir/target', size=3)
-+            arc.add('linkdir/hardlink', hardlink_to='targetdir/target')
-+            arc.add('linkdir/symlink', symlink_to='../targetdir/target')
-+
-+        for filter in 'tar', 'data', 'fully_trusted':
-+            with self.check_context(arc.open(), filter):
-+                self.expect_file('targetdir/target', size=3)
-+                self.expect_file('linkdir/hardlink', size=3)
-+                if os_helper.can_symlink():
-+                    self.expect_file('linkdir/symlink', size=3,
-+                                     symlink_to='../targetdir/target')
-+                else:
-+                    self.expect_file('linkdir/symlink', size=3)
-+
-     def test_modes(self):
-         # Test how file modes are extracted
-         # (Note that the modes are ignored on platforms without working chmod)
--- 
-2.41.0
-
 From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001
 From: Petr Viktorin <encukou@gmail.com>
 Date: Mon, 6 Mar 2023 17:24:24 +0100
diff --git a/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch b/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch
new file mode 100644
index 0000000000000000000000000000000000000000..73bf9b76d06f56fe1ddcc46a5240e819c18553c1
--- /dev/null
+++ b/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch
@@ -0,0 +1,755 @@
+From d8b0fafb202bf884135a3f7f0ce0b086217a2da2 Mon Sep 17 00:00:00 2001
+From: Victor Stinner <vstinner@python.org>
+Date: Fri, 15 Dec 2023 16:10:40 +0100
+Subject: [PATCH 1/2] 00415: [CVE-2023-27043] gh-102988: Reject malformed
+ addresses in email.parseaddr() (#111116)
+
+Detect email address parsing errors and return empty tuple to
+indicate the parsing error (old API). Add an optional 'strict'
+parameter to getaddresses() and parseaddr() functions. Patch by
+Thomas Dwyer.
+
+Co-Authored-By: Thomas Dwyer <github@tomd.tel>
+---
+ Doc/library/email.utils.rst                   |  19 +-
+ Lib/email/utils.py                            | 151 ++++++++++++-
+ Lib/test/test_email/test_email.py             | 204 +++++++++++++++++-
+ ...-10-20-15-28-08.gh-issue-102988.dStNO7.rst |   8 +
+ 4 files changed, 361 insertions(+), 21 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
+
+diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
+index 0e266b6..6723dc4 100644
+--- a/Doc/library/email.utils.rst
++++ b/Doc/library/email.utils.rst
+@@ -60,13 +60,18 @@ of the new API.
+    begins with angle brackets, they are stripped off.
+ 
+ 
+-.. function:: parseaddr(address)
++.. function:: parseaddr(address, *, strict=True)
+ 
+    Parse address -- which should be the value of some address-containing field such
+    as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and
+    *email address* parts.  Returns a tuple of that information, unless the parse
+    fails, in which case a 2-tuple of ``('', '')`` is returned.
+ 
++   If *strict* is true, use a strict parser which rejects malformed inputs.
++
++   .. versionchanged:: 3.13
++      Add *strict* optional parameter and reject malformed inputs by default.
++
+ 
+ .. function:: formataddr(pair, charset='utf-8')
+ 
+@@ -84,12 +89,15 @@ of the new API.
+       Added the *charset* option.
+ 
+ 
+-.. function:: getaddresses(fieldvalues)
++.. function:: getaddresses(fieldvalues, *, strict=True)
+ 
+    This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
+    *fieldvalues* is a sequence of header field values as might be returned by
+-   :meth:`Message.get_all <email.message.Message.get_all>`.  Here's a simple
+-   example that gets all the recipients of a message::
++   :meth:`Message.get_all <email.message.Message.get_all>`.
++
++   If *strict* is true, use a strict parser which rejects malformed inputs.
++
++   Here's a simple example that gets all the recipients of a message::
+ 
+       from email.utils import getaddresses
+ 
+@@ -99,6 +107,9 @@ of the new API.
+       resent_ccs = msg.get_all('resent-cc', [])
+       all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
+ 
++   .. versionchanged:: 3.13
++      Add *strict* optional parameter and reject malformed inputs by default.
++
+ 
+ .. function:: parsedate(date)
+ 
+diff --git a/Lib/email/utils.py b/Lib/email/utils.py
+index cfdfeb3..9522341 100644
+--- a/Lib/email/utils.py
++++ b/Lib/email/utils.py
+@@ -48,6 +48,7 @@ TICK = "'"
+ specialsre = re.compile(r'[][\\()<>@,:;".]')
+ escapesre = re.compile(r'[\\"]')
+ 
++
+ def _has_surrogates(s):
+     """Return True if s contains surrogate-escaped binary data."""
+     # This check is based on the fact that unless there are surrogates, utf8
+@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'):
+     return address
+ 
+ 
++def _iter_escaped_chars(addr):
++    pos = 0
++    escape = False
++    for pos, ch in enumerate(addr):
++        if escape:
++            yield (pos, '\\' + ch)
++            escape = False
++        elif ch == '\\':
++            escape = True
++        else:
++            yield (pos, ch)
++    if escape:
++        yield (pos, '\\')
++
++
++def _strip_quoted_realnames(addr):
++    """Strip real names between quotes."""
++    if '"' not in addr:
++        # Fast path
++        return addr
++
++    start = 0
++    open_pos = None
++    result = []
++    for pos, ch in _iter_escaped_chars(addr):
++        if ch == '"':
++            if open_pos is None:
++                open_pos = pos
++            else:
++                if start != open_pos:
++                    result.append(addr[start:open_pos])
++                start = pos + 1
++                open_pos = None
++
++    if start < len(addr):
++        result.append(addr[start:])
++
++    return ''.join(result)
+ 
+-def getaddresses(fieldvalues):
+-    """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
+-    all = COMMASPACE.join(str(v) for v in fieldvalues)
+-    a = _AddressList(all)
+-    return a.addresslist
++
++supports_strict_parsing = True
++
++def getaddresses(fieldvalues, *, strict=True):
++    """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
++
++    When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
++    its place.
++
++    If strict is true, use a strict parser which rejects malformed inputs.
++    """
++
++    # If strict is true, if the resulting list of parsed addresses is greater
++    # than the number of fieldvalues in the input list, a parsing error has
++    # occurred and consequently a list containing a single empty 2-tuple [('',
++    # '')] is returned in its place. This is done to avoid invalid output.
++    #
++    # Malformed input: getaddresses(['alice@example.com <bob@example.com>'])
++    # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')]
++    # Safe output: [('', '')]
++
++    if not strict:
++        all = COMMASPACE.join(str(v) for v in fieldvalues)
++        a = _AddressList(all)
++        return a.addresslist
++
++    fieldvalues = [str(v) for v in fieldvalues]
++    fieldvalues = _pre_parse_validation(fieldvalues)
++    addr = COMMASPACE.join(fieldvalues)
++    a = _AddressList(addr)
++    result = _post_parse_validation(a.addresslist)
++
++    # Treat output as invalid if the number of addresses is not equal to the
++    # expected number of addresses.
++    n = 0
++    for v in fieldvalues:
++        # When a comma is used in the Real Name part it is not a deliminator.
++        # So strip those out before counting the commas.
++        v = _strip_quoted_realnames(v)
++        # Expected number of addresses: 1 + number of commas
++        n += 1 + v.count(',')
++    if len(result) != n:
++        return [('', '')]
++
++    return result
++
++
++def _check_parenthesis(addr):
++    # Ignore parenthesis in quoted real names.
++    addr = _strip_quoted_realnames(addr)
++
++    opens = 0
++    for pos, ch in _iter_escaped_chars(addr):
++        if ch == '(':
++            opens += 1
++        elif ch == ')':
++            opens -= 1
++            if opens < 0:
++                return False
++    return (opens == 0)
++
++
++def _pre_parse_validation(email_header_fields):
++    accepted_values = []
++    for v in email_header_fields:
++        if not _check_parenthesis(v):
++            v = "('', '')"
++        accepted_values.append(v)
++
++    return accepted_values
++
++
++def _post_parse_validation(parsed_email_header_tuples):
++    accepted_values = []
++    # The parser would have parsed a correctly formatted domain-literal
++    # The existence of an [ after parsing indicates a parsing failure
++    for v in parsed_email_header_tuples:
++        if '[' in v[1]:
++            v = ('', '')
++        accepted_values.append(v)
++
++    return accepted_values
+ 
+ 
+ def _format_timetuple_and_zone(timetuple, zone):
+@@ -205,16 +321,33 @@ def parsedate_to_datetime(data):
+             tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
+ 
+ 
+-def parseaddr(addr):
++def parseaddr(addr, *, strict=True):
+     """
+     Parse addr into its constituent realname and email address parts.
+ 
+     Return a tuple of realname and email address, unless the parse fails, in
+     which case return a 2-tuple of ('', '').
++
++    If strict is True, use a strict parser which rejects malformed inputs.
+     """
+-    addrs = _AddressList(addr).addresslist
+-    if not addrs:
+-        return '', ''
++    if not strict:
++        addrs = _AddressList(addr).addresslist
++        if not addrs:
++            return ('', '')
++        return addrs[0]
++
++    if isinstance(addr, list):
++        addr = addr[0]
++
++    if not isinstance(addr, str):
++        return ('', '')
++
++    addr = _pre_parse_validation([addr])[0]
++    addrs = _post_parse_validation(_AddressList(addr).addresslist)
++
++    if not addrs or len(addrs) > 1:
++        return ('', '')
++
+     return addrs[0]
+ 
+ 
+diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
+index 677f209..20b6779 100644
+--- a/Lib/test/test_email/test_email.py
++++ b/Lib/test/test_email/test_email.py
+@@ -17,6 +17,7 @@ from unittest.mock import patch
+ 
+ import email
+ import email.policy
++import email.utils
+ 
+ from email.charset import Charset
+ from email.generator import Generator, DecodedGenerator, BytesGenerator
+@@ -3321,15 +3322,154 @@ Foo
+            [('Al Person', 'aperson@dom.ain'),
+             ('Bud Person', 'bperson@dom.ain')])
+ 
++    def test_getaddresses_comma_in_name(self):
++        """GH-106669 regression test."""
++        self.assertEqual(
++            utils.getaddresses(
++                [
++                    '"Bud, Person" <bperson@dom.ain>',
++                    'aperson@dom.ain (Al Person)',
++                    '"Mariusz Felisiak" <to@example.com>',
++                ]
++            ),
++            [
++                ('Bud, Person', 'bperson@dom.ain'),
++                ('Al Person', 'aperson@dom.ain'),
++                ('Mariusz Felisiak', 'to@example.com'),
++            ],
++        )
++
++    def test_parsing_errors(self):
++        """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
++        alice = 'alice@example.org'
++        bob = 'bob@example.com'
++        empty = ('', '')
++
++        # Test utils.getaddresses() and utils.parseaddr() on malformed email
++        # addresses: default behavior (strict=True) rejects malformed address,
++        # and strict=False which tolerates malformed address.
++        for invalid_separator, expected_non_strict in (
++            ('(', [(f'<{bob}>', alice)]),
++            (')', [('', alice), empty, ('', bob)]),
++            ('<', [('', alice), empty, ('', bob), empty]),
++            ('>', [('', alice), empty, ('', bob)]),
++            ('[', [('', f'{alice}[<{bob}>]')]),
++            (']', [('', alice), empty, ('', bob)]),
++            ('@', [empty, empty, ('', bob)]),
++            (';', [('', alice), empty, ('', bob)]),
++            (':', [('', alice), ('', bob)]),
++            ('.', [('', alice + '.'), ('', bob)]),
++            ('"', [('', alice), ('', f'<{bob}>')]),
++        ):
++            address = f'{alice}{invalid_separator}<{bob}>'
++            with self.subTest(address=address):
++                self.assertEqual(utils.getaddresses([address]),
++                                 [empty])
++                self.assertEqual(utils.getaddresses([address], strict=False),
++                                 expected_non_strict)
++
++                self.assertEqual(utils.parseaddr([address]),
++                                 empty)
++                self.assertEqual(utils.parseaddr([address], strict=False),
++                                 ('', address))
++
++        # Comma (',') is treated differently depending on strict parameter.
++        # Comma without quotes.
++        address = f'{alice},<{bob}>'
++        self.assertEqual(utils.getaddresses([address]),
++                         [('', alice), ('', bob)])
++        self.assertEqual(utils.getaddresses([address], strict=False),
++                         [('', alice), ('', bob)])
++        self.assertEqual(utils.parseaddr([address]),
++                         empty)
++        self.assertEqual(utils.parseaddr([address], strict=False),
++                         ('', address))
++
++        # Real name between quotes containing comma.
++        address = '"Alice, alice@example.org" <bob@example.com>'
++        expected_strict = ('Alice, alice@example.org', 'bob@example.com')
++        self.assertEqual(utils.getaddresses([address]), [expected_strict])
++        self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
++        self.assertEqual(utils.parseaddr([address]), expected_strict)
++        self.assertEqual(utils.parseaddr([address], strict=False),
++                         ('', address))
++
++        # Valid parenthesis in comments.
++        address = 'alice@example.org (Alice)'
++        expected_strict = ('Alice', 'alice@example.org')
++        self.assertEqual(utils.getaddresses([address]), [expected_strict])
++        self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
++        self.assertEqual(utils.parseaddr([address]), expected_strict)
++        self.assertEqual(utils.parseaddr([address], strict=False),
++                         ('', address))
++
++        # Invalid parenthesis in comments.
++        address = 'alice@example.org )Alice('
++        self.assertEqual(utils.getaddresses([address]), [empty])
++        self.assertEqual(utils.getaddresses([address], strict=False),
++                         [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
++        self.assertEqual(utils.parseaddr([address]), empty)
++        self.assertEqual(utils.parseaddr([address], strict=False),
++                         ('', address))
++
++        # Two addresses with quotes separated by comma.
++        address = '"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>'
++        self.assertEqual(utils.getaddresses([address]),
++                         [('Jane Doe', 'jane@example.net'),
++                          ('John Doe', 'john@example.net')])
++        self.assertEqual(utils.getaddresses([address], strict=False),
++                         [('Jane Doe', 'jane@example.net'),
++                          ('John Doe', 'john@example.net')])
++        self.assertEqual(utils.parseaddr([address]), empty)
++        self.assertEqual(utils.parseaddr([address], strict=False),
++                         ('', address))
++
++        # Test email.utils.supports_strict_parsing attribute
++        self.assertEqual(email.utils.supports_strict_parsing, True)
++
+     def test_getaddresses_nasty(self):
+-        eq = self.assertEqual
+-        eq(utils.getaddresses(['foo: ;']), [('', '')])
+-        eq(utils.getaddresses(
+-           ['[]*-- =~$']),
+-           [('', ''), ('', ''), ('', '*--')])
+-        eq(utils.getaddresses(
+-           ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
+-           [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
++        for addresses, expected in (
++            (['"Sürname, Firstname" <to@example.com>'],
++             [('Sürname, Firstname', 'to@example.com')]),
++
++            (['foo: ;'],
++             [('', '')]),
++
++            (['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>'],
++             [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]),
++
++            ([r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>'],
++             [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]),
++
++            (['(Empty list)(start)Undisclosed recipients  :(nobody(I know))'],
++             [('', '')]),
++
++            (['Mary <@machine.tld:mary@example.net>, , jdoe@test   . example'],
++             [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]),
++
++            (['John Doe <jdoe@machine(comment).  example>'],
++             [('John Doe (comment)', 'jdoe@machine.example')]),
++
++            (['"Mary Smith: Personal Account" <smith@home.example>'],
++             [('Mary Smith: Personal Account', 'smith@home.example')]),
++
++            (['Undisclosed recipients:;'],
++             [('', '')]),
++
++            ([r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>'],
++             [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]),
++        ):
++            with self.subTest(addresses=addresses):
++                self.assertEqual(utils.getaddresses(addresses),
++                                 expected)
++                self.assertEqual(utils.getaddresses(addresses, strict=False),
++                                 expected)
++
++        addresses = ['[]*-- =~$']
++        self.assertEqual(utils.getaddresses(addresses),
++                         [('', '')])
++        self.assertEqual(utils.getaddresses(addresses, strict=False),
++                         [('', ''), ('', ''), ('', '*--')])
+ 
+     def test_getaddresses_embedded_comment(self):
+         """Test proper handling of a nested comment"""
+@@ -3520,6 +3660,54 @@ multipart/report
+                 m = cls(*constructor, policy=email.policy.default)
+                 self.assertIs(m.policy, email.policy.default)
+ 
++    def test_iter_escaped_chars(self):
++        self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
++                         [(0, 'a'),
++                          (2, '\\\\'),
++                          (3, 'b'),
++                          (5, '\\"'),
++                          (6, 'c'),
++                          (8, '\\\\'),
++                          (9, '"'),
++                          (10, 'd')])
++        self.assertEqual(list(utils._iter_escaped_chars('a\\')),
++                         [(0, 'a'), (1, '\\')])
++
++    def test_strip_quoted_realnames(self):
++        def check(addr, expected):
++            self.assertEqual(utils._strip_quoted_realnames(addr), expected)
++
++        check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
++              ' <jane@example.net>,  <john@example.net>')
++        check(r'"Jane \"Doe\"." <jane@example.net>',
++              ' <jane@example.net>')
++
++        # special cases
++        check(r'before"name"after', 'beforeafter')
++        check(r'before"name"', 'before')
++        check(r'b"name"', 'b')  # single char
++        check(r'"name"after', 'after')
++        check(r'"name"a', 'a')  # single char
++        check(r'"name"', '')
++
++        # no change
++        for addr in (
++            'Jane Doe <jane@example.net>, John Doe <john@example.net>',
++            'lone " quote',
++        ):
++            self.assertEqual(utils._strip_quoted_realnames(addr), addr)
++
++
++    def test_check_parenthesis(self):
++        addr = 'alice@example.net'
++        self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
++        self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
++        self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
++        self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
++
++        # Ignore real name between quotes
++        self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
++
+ 
+ # Test the iterator/generators
+ class TestIterators(TestEmailBase):
+diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
+new file mode 100644
+index 0000000..3d0e9e4
+--- /dev/null
++++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
+@@ -0,0 +1,8 @@
++:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
++return ``('', '')`` 2-tuples in more situations where invalid email
++addresses are encountered instead of potentially inaccurate values. Add
++optional *strict* parameter to these two functions: use ``strict=False`` to
++get the old behavior, accept malformed inputs.
++``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
++if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
++Stinner to improve the CVE-2023-27043 fix.
+-- 
+2.43.0
+
+
+From 6c34f5b95da90bd494e29776c0e807af44689fae Mon Sep 17 00:00:00 2001
+From: Lumir Balhar <lbalhar@redhat.com>
+Date: Wed, 10 Jan 2024 08:53:53 +0100
+Subject: [PATCH 2/2] Make it possible to disable strict parsing in email
+ module
+
+---
+ Doc/library/email.utils.rst       | 26 +++++++++++
+ Lib/email/utils.py                | 54 +++++++++++++++++++++-
+ Lib/test/test_email/test_email.py | 74 ++++++++++++++++++++++++++++++-
+ 3 files changed, 150 insertions(+), 4 deletions(-)
+
+diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
+index 6723dc4..c89602d 100644
+--- a/Doc/library/email.utils.rst
++++ b/Doc/library/email.utils.rst
+@@ -69,6 +69,19 @@ of the new API.
+ 
+    If *strict* is true, use a strict parser which rejects malformed inputs.
+ 
++   The default setting for *strict* is set to ``True``, but you can override
++   it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING``
++   to non-empty string.
++
++   Additionally, you can permanently set the default value for *strict* to
++   ``False`` by creating the configuration file ``/etc/python/email.cfg``
++   with the following content:
++
++   .. code-block:: ini
++
++      [email_addr_parsing]
++      PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true
++
+    .. versionchanged:: 3.13
+       Add *strict* optional parameter and reject malformed inputs by default.
+ 
+@@ -97,6 +110,19 @@ of the new API.
+ 
+    If *strict* is true, use a strict parser which rejects malformed inputs.
+ 
++   The default setting for *strict* is set to ``True``, but you can override
++   it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING``
++   to non-empty string.
++
++   Additionally, you can permanently set the default value for *strict* to
++   ``False`` by creating the configuration file ``/etc/python/email.cfg``
++   with the following content:
++
++   .. code-block:: ini
++
++      [email_addr_parsing]
++      PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true
++
+    Here's a simple example that gets all the recipients of a message::
+ 
+       from email.utils import getaddresses
+diff --git a/Lib/email/utils.py b/Lib/email/utils.py
+index 9522341..2e30e09 100644
+--- a/Lib/email/utils.py
++++ b/Lib/email/utils.py
+@@ -48,6 +48,46 @@ TICK = "'"
+ specialsre = re.compile(r'[][\\()<>@,:;".]')
+ escapesre = re.compile(r'[\\"]')
+ 
++_EMAIL_CONFIG_FILE = "/etc/python/email.cfg"
++_cached_strict_addr_parsing = None
++
++
++def _use_strict_email_parsing():
++    """"Cache implementation for _cached_strict_addr_parsing"""
++    global _cached_strict_addr_parsing
++    if _cached_strict_addr_parsing is None:
++        _cached_strict_addr_parsing = _use_strict_email_parsing_impl()
++    return _cached_strict_addr_parsing
++
++
++def _use_strict_email_parsing_impl():
++    """Returns True if strict email parsing is not disabled by
++    config file or env variable.
++    """
++    disabled = bool(os.environ.get("PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"))
++    if disabled:
++        return False
++
++    try:
++        file = open(_EMAIL_CONFIG_FILE)
++    except FileNotFoundError:
++        pass
++    else:
++        with file:
++            import configparser
++            config = configparser.ConfigParser(
++                interpolation=None,
++                comment_prefixes=('#', ),
++
++            )
++            config.read_file(file)
++            disabled = config.getboolean('email_addr_parsing', "PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING", fallback=None)
++
++    if disabled:
++        return False
++
++    return True
++
+ 
+ def _has_surrogates(s):
+     """Return True if s contains surrogate-escaped binary data."""
+@@ -149,7 +189,7 @@ def _strip_quoted_realnames(addr):
+ 
+ supports_strict_parsing = True
+ 
+-def getaddresses(fieldvalues, *, strict=True):
++def getaddresses(fieldvalues, *, strict=None):
+     """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
+ 
+     When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
+@@ -158,6 +198,11 @@ def getaddresses(fieldvalues, *, strict=True):
+     If strict is true, use a strict parser which rejects malformed inputs.
+     """
+ 
++    # If default is used, it's True unless disabled
++    # by env variable or config file.
++    if strict == None:
++        strict = _use_strict_email_parsing()
++
+     # If strict is true, if the resulting list of parsed addresses is greater
+     # than the number of fieldvalues in the input list, a parsing error has
+     # occurred and consequently a list containing a single empty 2-tuple [('',
+@@ -321,7 +366,7 @@ def parsedate_to_datetime(data):
+             tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
+ 
+ 
+-def parseaddr(addr, *, strict=True):
++def parseaddr(addr, *, strict=None):
+     """
+     Parse addr into its constituent realname and email address parts.
+ 
+@@ -330,6 +375,11 @@ def parseaddr(addr, *, strict=True):
+ 
+     If strict is True, use a strict parser which rejects malformed inputs.
+     """
++    # If default is used, it's True unless disabled
++    # by env variable or config file.
++    if strict == None:
++        strict = _use_strict_email_parsing()
++
+     if not strict:
+         addrs = _AddressList(addr).addresslist
+         if not addrs:
+diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
+index 20b6779..d7d99f0 100644
+--- a/Lib/test/test_email/test_email.py
++++ b/Lib/test/test_email/test_email.py
+@@ -8,6 +8,9 @@ import base64
+ import unittest
+ import textwrap
+ import warnings
++import contextlib
++import tempfile
++import os
+ 
+ from io import StringIO, BytesIO
+ from itertools import chain
+@@ -41,8 +44,8 @@ from email import quoprimime
+ from email import utils
+ 
+ from test import support
+-from test.support import threading_helper
+-from test.support.os_helper import unlink
++from test.support import threading_helper, swap_attr
++from test.support.os_helper import unlink, EnvironmentVarGuard
+ from test.test_email import openfile, TestEmailBase
+ 
+ # These imports are documented to work, but we are testing them using a
+@@ -3427,6 +3430,73 @@ Foo
+         # Test email.utils.supports_strict_parsing attribute
+         self.assertEqual(email.utils.supports_strict_parsing, True)
+ 
++    def test_parsing_errors_strict_set_via_env_var(self):
++        address = 'alice@example.org )Alice('
++        empty = ('', '')
++
++        # Reset cached default value to make the function
++        # reload the config file provided below.
++        utils._cached_strict_addr_parsing = None
++
++        # Strict disabled via env variable, old behavior expected
++        with EnvironmentVarGuard() as environ:
++            environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = "1"
++
++            self.assertEqual(utils.getaddresses([address]),
++                             [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
++            self.assertEqual(utils.parseaddr([address]), ('', address))
++
++        # Clear cache again
++        utils._cached_strict_addr_parsing = None
++
++        # Default strict=True, empty result expected
++        self.assertEqual(utils.getaddresses([address]), [empty])
++        self.assertEqual(utils.parseaddr([address]), empty)
++
++        # Clear cache again
++        utils._cached_strict_addr_parsing = None
++
++        # Empty string in env variable = strict parsing enabled (default)
++        with EnvironmentVarGuard() as environ:
++            environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = ""
++
++            # Default strict=True, empty result expected
++            self.assertEqual(utils.getaddresses([address]), [empty])
++            self.assertEqual(utils.parseaddr([address]), empty)
++
++    @contextlib.contextmanager
++    def _email_strict_parsing_conf(self):
++        """Context for the given email strict parsing configured in config file"""
++        with tempfile.TemporaryDirectory() as tmpdirname:
++            filename = os.path.join(tmpdirname, 'conf.cfg')
++            with swap_attr(utils, "_EMAIL_CONFIG_FILE", filename):
++                with open(filename, 'w') as file:
++                    file.write('[email_addr_parsing]\n')
++                    file.write('PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true')
++                utils._EMAIL_CONFIG_FILE = filename
++                yield
++
++    def test_parsing_errors_strict_disabled_via_config_file(self):
++        address = 'alice@example.org )Alice('
++        empty = ('', '')
++
++        # Reset cached default value to make the function
++        # reload the config file provided below.
++        utils._cached_strict_addr_parsing = None
++
++        # Strict disabled via config file, old results expected
++        with self._email_strict_parsing_conf():
++            self.assertEqual(utils.getaddresses([address]),
++                             [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
++            self.assertEqual(utils.parseaddr([address]), ('', address))
++
++        # Clear cache again
++        utils._cached_strict_addr_parsing = None
++
++        # Default strict=True, empty result expected
++        self.assertEqual(utils.getaddresses([address]), [empty])
++        self.assertEqual(utils.parseaddr([address]), empty)
++
+     def test_getaddresses_nasty(self):
+         for addresses, expected in (
+             (['"Sürname, Firstname" <to@example.com>'],
+-- 
+2.43.0
+
diff --git a/SOURCES/Python-3.11.7.tar.xz.asc b/SOURCES/Python-3.11.7.tar.xz.asc
new file mode 100644
index 0000000000000000000000000000000000000000..5d3dcbe779339fde01e875e8f1076f8ff541ef23
--- /dev/null
+++ b/SOURCES/Python-3.11.7.tar.xz.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmVuFigACgkQ/+h0BBaL
+2EeHPg/+LU5xs2ZDrQogDcH+A1v8RyursiggypdM5hXTrsFsTCIk4iekcI9xkhG1
+ltNX4UuCe5PUEbTgtaWP0ncXARrUnPCoQaQ1sHVDTYoHegancsk+sXZc1JM7qr0p
+Y4Ig6mKjuHFMXCInQSI2GaH4t5r4Z1jGk/PGrecIHOPJgqfA/6Z3TBF5N+y3jEvS
+2QazMB298q4RDhh9m3REe8LwFPHDlfw9eRohv0MB8xygg9KtxhLZrN7gLBQZvKGD
+ihNw6EgJj5OZ0dvwKCCXnlZuwknuJW7vAOPHhYeenPdVdYCGoRSyN7JdD07L+5AG
+O14l2rqZrz5Eu28by+kAUrcPYAfAXekw1PmtT3HSd9U/nqnUiTkkJcjyGG/e3cjJ
+sUDKMNCSBq0G7j5DB3bB6VHkZjVuz+T+iR5QdfJ4kI2pYSuE/rUj1rhkUXApYsHl
+7Wff0QbOW6QT1wCtQcMpJSzkTDVJVYxiqrko/ihlOhphDHYLdOIGOrxWAUwc06x/
+BhJD6tM1kEVZvifoJp1OsNwDzZ/Ku6CUs05E1vWxdeNVeANyKAgCZ5hOVmhnv866
+11zfgo/znRsMzMIyJuy0bhO0C6omVLzzfhipAbZM2jDorn37xxV0v/I0pceNtLrp
+YR7Tjs7+Ihe6/oItjW53j9T7ANdgQ1RVDg98lKlPFNL+hxfctwY=
+=0Pkd
+-----END PGP SIGNATURE-----
diff --git a/SOURCES/check-pyc-timestamps.py b/SOURCES/check-pyc-timestamps.py
index 91af4fd696ea21e5f0411942dc79aedb93727b6e..1a0a288cba1e747aec41405428d0d895b576477a 100644
--- a/SOURCES/check-pyc-timestamps.py
+++ b/SOURCES/check-pyc-timestamps.py
@@ -16,9 +16,9 @@ LEVELS = (None, 1, 2)
 # list of globs of test and other files that we expect not to have bytecode
 not_compiled = [
     '/usr/bin/*',
-    '*/test/bad_coding.py',
-    '*/test/bad_coding2.py',
-    '*/test/badsyntax_*.py',
+    '*/test/*/bad_coding.py',
+    '*/test/*/bad_coding2.py',
+    '*/test/*/badsyntax_*.py',
     '*/lib2to3/tests/data/bom.py',
     '*/lib2to3/tests/data/crlf.py',
     '*/lib2to3/tests/data/different_encoding.py',
diff --git a/SPECS/python3.11.spec b/SPECS/python3.11.spec
index e8383216f95250ba920d90b3a70dac1dab34d7c4..b8143b0c593742d2bb5ef3ec00e07fc7faec012b 100644
--- a/SPECS/python3.11.spec
+++ b/SPECS/python3.11.spec
@@ -16,11 +16,11 @@ URL: https://www.python.org/
 
 #  WARNING  When rebasing to a new Python version,
 #           remember to update the python3-docs package as well
-%global general_version %{pybasever}.4
+%global general_version %{pybasever}.7
 #global prerel ...
 %global upstream_version %{general_version}%{?prerel}
 Version: %{general_version}%{?prerel:~%{prerel}}
-Release: 3%{?dist}
+Release: 1%{?dist}
 License: Python
 
 
@@ -63,7 +63,7 @@ License: Python
 # If the rpmwheels condition is disabled, we use the bundled wheel packages
 # from Python with the versions below.
 # This needs to be manually updated when we update Python.
-%global pip_version 23.1.2
+%global pip_version 23.2.1
 %global setuptools_version 65.5.0
 
 # Expensive optimizations (mainly, profile-guided optimizations)
@@ -342,6 +342,20 @@ Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-g
 # - https://access.redhat.com/articles/7004769
 Patch397: 00397-tarfile-filter.patch
 
+# 00415 #
+# [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116)
+#
+# Detect email address parsing errors and return empty tuple to
+# indicate the parsing error (old API). Add an optional 'strict'
+# parameter to getaddresses() and parseaddr() functions. Patch by
+# Thomas Dwyer.
+#
+# Upstream PR: https://github.com/python/cpython/pull/111116
+#
+# Second patch implmenets the possibility to restore the old behavior via
+# config file or environment variable.
+Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch
+
 # (New patches go here ^^^)
 #
 # When adding new patches to "python" and "python3" in Fedora, EL, etc.,
@@ -937,6 +951,10 @@ for tool in pygettext msgfmt; do
   ln -s ${tool}%{pybasever}.py %{buildroot}%{_bindir}/${tool}3.py
 done
 
+# Install missing test data
+# Fixed upstream: https://github.com/python/cpython/pull/112784
+cp -rp Lib/test/regrtestdata/ %{buildroot}%{pylibdir}/test/
+
 # Switch all shebangs to refer to the specific Python version.
 # This currently only covers files matching ^[a-zA-Z0-9_]+\.py$,
 # so handle files named using other naming scheme separately.
@@ -1095,10 +1113,14 @@ CheckPython() {
   # test_freeze_simple_script is skipped, because it fails when bundled wheels
   #  are removed in Fedora.
   #  upstream report: https://bugs.python.org/issue45783
+  # test_check_probes is failing since it was introduced in 3.11.5,
+  # the test is skipped until it is fixed in upstream.
+  # see: https://github.com/python/cpython/issues/104280#issuecomment-1669249980
 
   LD_LIBRARY_PATH=$ConfDir $ConfDir/python -m test.regrtest \
     -wW --slowest -j0 --timeout=1800 \
     -i test_freeze_simple_script \
+    -i test_check_probes \
     %if %{with bootstrap}
     -x test_distutils \
     %endif
@@ -1609,6 +1631,19 @@ CheckPython optimized
 # ======================================================
 
 %changelog
+* Mon Jan 22 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.11.7-1
+- Rebase to 3.11.7
+Resolves: RHEL-20233
+
+* Tue Jan 09 2024 Lumír Balhar <lbalhar@redhat.com> - 3.11.5-2
+- Security fix for CVE-2023-27043
+Resolves: RHEL-21325
+
+* Thu Sep 07 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.5-1
+- Rebase to 3.11.5
+- Security fixes for CVE-2023-40217 and CVE-2023-41105
+Resolves: RHEL-3045, RHEL-3269
+
 * Wed Aug 09 2023 Petr Viktorin <pviktori@redhat.com> - 3.11.4-3
 - Fix symlink handling in the fix for CVE-2023-24329
 Resolves: rhbz#263261