Robustly handle dead sockets during authentication by retrying the connection

This commit addresses issues where certain IMAP servers (like Protonmail Bridge)
abruptly close the socket during the authentication phase.

Following a simplified approach:
1. Updated __authn_helper to initiate STARTTLS unconditionally at the
   beginning of the loop.
2. Modified acquireconnection to catch OfflineImapError. If the socket is
   detected as dead during authentication, it now logs a warning and retries
   the entire connection process from scratch (up to 3 attempts).
3. This ensures each retry starts with a fresh connection and all configured
   authentication methods available, avoiding permanent list modification.
This commit is contained in:
Rodolfo García Peñas (kix)
2026-04-20 09:41:10 +02:00
parent 915dd0a510
commit 854f3891aa
+24 -10
View File
@@ -461,20 +461,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:
@@ -482,12 +482,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:
@@ -624,7 +624,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(
@@ -678,6 +679,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