diff --git a/.travis.yml b/.travis.yml
index d47dc5a..5aa16ea 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,14 @@
 language: python
 python:
-- '2.7'
+- '3.6'
+- '3.7'
+- '3.8'
+- '3.9'
+- '3.10'
+- '3.11'
+- '3.12'
+- '3.13'
+dist: jammy
 notifications:
   webhooks:
     urls:
@@ -25,10 +33,10 @@ matrix:
   include:
   - os: osx
     language: generic
-    env: PYTHON=2.7.14 OUTLOOK_AUTH=PLAIN GMAIL_AUTH=XOAUTH2
+    env: PYTHON=3.12.0 OUTLOOK_AUTH=PLAIN GMAIL_AUTH=XOAUTH2
   - os: osx
     language: generic
-    env: PYTHON=2.7.14 OUTLOOK_AUTH=LOGIN GMAIL_AUTH=XOAUTH2
+    env: PYTHON=3.12.0 OUTLOOK_AUTH=LOGIN GMAIL_AUTH=XOAUTH2
   allow_failures:
   - os: osx
 cache: pip
diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
index 4da8986..74cb0c9 100644
--- a/offlineimap/folder/IMAP.py
+++ b/offlineimap/folder/IMAP.py
@@ -927,15 +927,33 @@ class IMAPFolder(BaseFolder):
             try:
                 # See if the defects after fixes are preventing us from obtaining bytes
                 _ = ndata1.as_bytes(policy=self.policy['8bit-RFC'])
-            except UnicodeEncodeError as err:
-                # Unknown issue which is causing failure of as_bytes()
-                msg_id = self.getmessageheader(ndata1, "message-id")
-                if msg_id is None:
-                    msg_id = '<Unknown Message-ID>'
-                raise OfflineImapError(
-                        "UID {} ({}) has defects preventing it from being processed!\n  {}: {}".format(
-                            uids, msg_id, type(err).__name__, err),
-                        OfflineImapError.ERROR.MESSAGE)
+            except UnicodeEncodeError:
+                # Apply utf-8 charset to parts that fail to encode with their current charset
+                # This usually happens when malformed bytes were replaced with \ufffd during parsing
+                # The email.policy.default also replaces unencodable characters with \ufffd
+                # so we need to set the charset to utf-8 to avoid this and instead let the user
+                # know that the message has defects.
+                self.ui.warn(" ... applying charset fix for unencodable characters (\\ufffd).")
+                for part in ndata1.walk():
+                    payload = part.get_payload()
+                    if isinstance(payload, str):
+                        charset = part.get_content_charset() or 'us-ascii'
+                        try:
+                            payload.encode(charset, 'surrogateescape')
+                        except UnicodeEncodeError:
+                            part.set_charset('utf-8')
+                try:
+                    # Retry obtaining bytes after charset fixes
+                    _ = ndata1.as_bytes(policy=self.policy['8bit-RFC'])
+                except UnicodeEncodeError as err:
+                    # Unknown issue which is causing failure of as_bytes()
+                    msg_id = self.getmessageheader(ndata1, "message-id")
+                    if msg_id is None:
+                        msg_id = '<Unknown Message-ID>'
+                    raise OfflineImapError(
+                            "UID {} ({}) has defects preventing it from being processed!\n  {}: {}".format(
+                                uids, msg_id, type(err).__name__, err),
+                            OfflineImapError.ERROR.MESSAGE)
         ndata = [ndata0, ndata1]
 
         return ndata
diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py
index 5e447b8..fd86686 100644
--- a/offlineimap/imapserver.py
+++ b/offlineimap/imapserver.py
@@ -158,6 +158,7 @@ class IMAPServer:
         self.lastowner = {}
         self.semaphore = BoundedSemaphore(self.maxconnections)
         self.connectionlock = Lock()
+        self.closing = False
         self.reference = repos.getreference()
         self.idlefolders = repos.getidlefolders()
         self.gss_vc = None
@@ -382,6 +383,12 @@ class IMAPServer:
             finally:
                 # Always restore the original method, even on success.
                 imapobj._get_capabilities = _orig_get_cap
+        elif self.starttls and not self.usessl:
+            raise OfflineImapError(
+                "STARTTLS requested but server does not advertise STARTTLS capability. "
+                "Aborting to prevent sending password in plaintext (possible MITM attack). "
+                "If you REALLY want an insecure connection, set 'starttls = no'.",
+                OfflineImapError.ERROR.REPO)
 
     # All __authn_* procedures are helpers that do authentication.
     # They are class methods that take one parameter, IMAP object.
@@ -455,20 +462,20 @@ class IMAPServer:
         # Stack stores pairs of (method name, exception)
         exc_stack = []
         tried_to_authn = False
-        tried_tls = False
         # Authentication routines, hash keyed by method name
         # with value that is a tuple with
         # - authentication function,
         # - tryTLS flag,
         # - check IMAP capability flag.
         auth_methods = {
-            "GSSAPI": (self.__authn_gssapi, False, True),
-            "XOAUTH2": (self.__authn_xoauth2, True, True),
-            "CRAM-MD5": (self.__authn_cram_md5, True, True),
-            "PLAIN": (self.__authn_plain, True, True),
-            "LOGIN": (self.__authn_login, True, False),
+            "GSSAPI": (self.__authn_gssapi, True),
+            "XOAUTH2": (self.__authn_xoauth2, True),
+            "CRAM-MD5": (self.__authn_cram_md5, True),
+            "PLAIN": (self.__authn_plain, True),
+            "LOGIN": (self.__authn_login, False),
         }
 
+        did_starttls = False
         # GSSAPI is tried first by default: we will probably go TLS after it and
         # GSSAPI mustn't be tunneled over TLS.
         for m in self.authmechs:
@@ -476,12 +483,12 @@ class IMAPServer:
                 raise Exception("Bad authentication method %s, "
                                 "please, file OfflineIMAP bug" % m)
 
-            func, tryTLS, check_cap = auth_methods[m]
+            func, check_cap = auth_methods[m]
 
             # TLS must be initiated before checking capabilities:
             # they could have been changed after STARTTLS.
-            if tryTLS and self.starttls and not tried_tls:
-                tried_tls = True
+            if self.starttls and not did_starttls:
+                did_starttls = True
                 self.__start_tls(imapobj)
 
             if check_cap:
@@ -569,6 +576,11 @@ class IMAPServer:
 
         self.semaphore.acquire()
         self.connectionlock.acquire()
+        if self.closing:
+            self.connectionlock.release()
+            self.semaphore.release()
+            raise OfflineImapError("Server is closing",
+                                   OfflineImapError.ERROR.REPO)
         curThread = current_thread()
         imapobj = None
 
@@ -618,7 +630,8 @@ class IMAPServer:
         # and release locks / threads so that the next attempt can try...
         success = False
         try:
-            while success is not True:
+            retries = 0
+            while success is not True and retries < 3:
                 # Generate a new connection.
                 if self.tunnel:
                     self.ui.connecting(
@@ -672,6 +685,19 @@ class IMAPServer:
                         self.goodpassword = self.password
                         success = True
                     except OfflineImapError as e:
+                        if not _is_socket_alive(imapobj):
+                            retries += 1
+                            if retries >= 3:
+                                self.ui.warn("Authentication failed after 3 attempts due to dead sockets.")
+                                raise
+                            self.ui.warn("Connection lost during authentication. "
+                                         "Retrying from scratch (%d/3)..." % retries)
+                            if imapobj is not None:
+                                try:
+                                    imapobj.shutdown()
+                                except:
+                                    pass
+                            continue
                         self.passworderror = str(e)
                         raise
 
@@ -793,14 +819,17 @@ class IMAPServer:
         self.semaphore.release()
 
     def close(self):
+        # First make sure no new connections can be established.
+        self.connectionlock.acquire()
+        self.closing = True
+        self.connectionlock.release()
+
         # Make sure I own all the semaphores.  Let the threads finish
         # their stuff.  This is a blocking method.
+        # Make sure to not call this under connectionlock to avoid deadlocks.
+        threadutil.semaphorereset(self.semaphore, self.maxconnections)
+
         with self.connectionlock:
-            # first, wait till all connections had been released.
-            # TODO: won't work IMHO, as releaseconnection() also
-            # requires the connectionlock, leading to a potential
-            # deadlock! Audit & check!
-            threadutil.semaphorereset(self.semaphore, self.maxconnections)
             for imapobj in self.assignedconnections + self.availableconnections:
                 imapobj.logout()
             self.assignedconnections = []
@@ -809,6 +838,7 @@ class IMAPServer:
             # reset GSSAPI state
             self.gss_vc = None
             self.gssapi = False
+            self.closing = False
 
     def keepalive(self, timeout, event):
         """Sends a NOOP to each connection recorded.
@@ -972,7 +1002,7 @@ class IdleThread:
             while not success:
                 imapobj = self.parent.acquireconnection()
                 try:
-                    imapobj.select(self.folder)
+                    imapobj.select(imaputil.foldername_to_imapname(self.folder))
                 except OfflineImapError as e:
                     if e.severity == OfflineImapError.ERROR.FOLDER_RETRY:
                         # Connection closed, release connection and retry.
