Refactored authentication handling
- created helper routine that will do authentication; - routine tries each method in turn, first successful one terminates it: makes things easier to read and handle; - renamed plainauth() inside offlineimap/imapserver.py to loginauth(): the function does IMAP LOGIN authentication and there is PLAIN SASL method, so previous name was a bit misleading; - slightly improved error reporting: all exceptions during authentication will be reported at the end of the run; - now loginauth() is never called if LOGINDISABLED is advertized by the server; it used to be invoked unconditionally when CRAM-MD5 fails, but we should respect server's opinion on how to handle its users. Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
parent
1184b6c1a3
commit
7d313f49dc
@ -128,8 +128,9 @@ class IMAPServer:
|
|||||||
self.ui.debug('imap', 'md5handler: returning %s' % retval)
|
self.ui.debug('imap', 'md5handler: returning %s' % retval)
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def plainauth(self, imapobj):
|
def loginauth(self, imapobj):
|
||||||
self.ui.debug('imap', 'Attempting plain authentication')
|
""" Basic authentication via LOGIN command """
|
||||||
|
self.ui.debug('imap', 'Attempting IMAP LOGIN authentication')
|
||||||
imapobj.login(self.username, self.getpassword())
|
imapobj.login(self.username, self.getpassword())
|
||||||
|
|
||||||
def gssauth(self, response):
|
def gssauth(self, response):
|
||||||
@ -159,6 +160,107 @@ class IMAPServer:
|
|||||||
response = ''
|
response = ''
|
||||||
return base64.b64decode(response)
|
return base64.b64decode(response)
|
||||||
|
|
||||||
|
|
||||||
|
def _authn_helper(self, imapobj):
|
||||||
|
"""
|
||||||
|
Authentication machinery for self.acquireconnection().
|
||||||
|
|
||||||
|
Raises OfflineImapError() of type ERROR.REPO when
|
||||||
|
there are either fatal problems or no authentications
|
||||||
|
succeeded.
|
||||||
|
|
||||||
|
If any authentication method succeeds, routine should exit:
|
||||||
|
warnings for failed methods are to be produced in the
|
||||||
|
respective except blocks.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Stack stores pairs of (method name, exception)
|
||||||
|
exc_stack = []
|
||||||
|
tried_to_authn = False
|
||||||
|
|
||||||
|
# Try GSSAPI and continue if it fails
|
||||||
|
if 'AUTH=GSSAPI' in imapobj.capabilities and have_gss:
|
||||||
|
self.connectionlock.acquire()
|
||||||
|
self.ui.debug('imap', 'Attempting GSSAPI authentication')
|
||||||
|
tried_to_authn = True
|
||||||
|
try:
|
||||||
|
imapobj.authenticate('GSSAPI', self.gssauth)
|
||||||
|
except imapobj.error as e:
|
||||||
|
self.gssapi = False
|
||||||
|
self.ui.warn('GSSAPI authentication failed: %s' % e)
|
||||||
|
exc_stack.append(('GSSAPI', e))
|
||||||
|
else:
|
||||||
|
self.gssapi = True
|
||||||
|
kerberos.authGSSClientClean(self.gss_vc)
|
||||||
|
self.gss_vc = None
|
||||||
|
self.gss_step = self.GSS_STATE_STEP
|
||||||
|
#if we do self.password = None then the next attempt cannot try...
|
||||||
|
#self.password = None
|
||||||
|
return
|
||||||
|
finally:
|
||||||
|
self.connectionlock.release()
|
||||||
|
|
||||||
|
# Fire up TLS if we can and asked to: gonna to authenticate
|
||||||
|
# via plaintext or hashed schemes, so it is best to have
|
||||||
|
# channel that is protected from eavesdropping.
|
||||||
|
if 'STARTTLS' in imapobj.capabilities and not self.usessl:
|
||||||
|
self.ui.debug('imap', 'Using STARTTLS connection')
|
||||||
|
try:
|
||||||
|
imapobj.starttls()
|
||||||
|
except imapobj.error as e:
|
||||||
|
raise OfflineImapError("Failed to start "
|
||||||
|
"TLS connection: %s" % str(e),
|
||||||
|
OfflineImapError.ERROR.REPO)
|
||||||
|
|
||||||
|
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
|
||||||
|
tried_to_authn = True
|
||||||
|
self.ui.debug('imap', 'Attempting '
|
||||||
|
'CRAM-MD5 authentication')
|
||||||
|
try:
|
||||||
|
imapobj.authenticate('CRAM-MD5', self.md5handler)
|
||||||
|
return
|
||||||
|
except imapobj.error as e:
|
||||||
|
self.ui.warn('CRAM-MD5 authentication failed: %s' % e)
|
||||||
|
exc_stack.append(('CRAM-MD5', e))
|
||||||
|
|
||||||
|
# Last resort: use LOGIN command,
|
||||||
|
# unless LOGINDISABLED is advertized (RFC 2595)
|
||||||
|
if 'LOGINDISABLED' in imapobj.capabilities:
|
||||||
|
e = OfflineImapError("IMAP LOGIN is "
|
||||||
|
"disabled by server. Need to use SSL?",
|
||||||
|
OfflineImapError.ERROR.REPO)
|
||||||
|
exc_stack.append(('IMAP LOGIN', e))
|
||||||
|
else:
|
||||||
|
tried_to_authn = True
|
||||||
|
self.ui.debug('imap', 'Attempting '
|
||||||
|
'IMAP LOGIN authentication')
|
||||||
|
try:
|
||||||
|
self.loginauth(imapobj)
|
||||||
|
return
|
||||||
|
except imapobj.error as e:
|
||||||
|
self.ui.warn('IMAP LOGIN authentication failed: %s' % e)
|
||||||
|
exc_stack.append(('IMAP LOGIN', e))
|
||||||
|
|
||||||
|
if len(exc_stack):
|
||||||
|
msg = "\n\t".join(map(
|
||||||
|
lambda x: ": ".join((x[0], str(x[1]))),
|
||||||
|
exc_stack
|
||||||
|
))
|
||||||
|
raise OfflineImapError("All authentication types "
|
||||||
|
"failed:\n\t%s" % msg, OfflineImapError.ERROR.REPO)
|
||||||
|
|
||||||
|
if not tried_to_authn:
|
||||||
|
methods = ", ".join(map(
|
||||||
|
lambda x: x[5:], filter(lambda x: x[0:5] == "AUTH=",
|
||||||
|
imapobj.capabilities)
|
||||||
|
))
|
||||||
|
raise OfflineImapError("No supported "
|
||||||
|
"authentication mechanisms found; "
|
||||||
|
"server advertises %s" % methods,
|
||||||
|
OfflineImapError.ERROR.REPO)
|
||||||
|
|
||||||
|
|
||||||
def acquireconnection(self):
|
def acquireconnection(self):
|
||||||
"""Fetches a connection from the pool, making sure to create a new one
|
"""Fetches a connection from the pool, making sure to create a new one
|
||||||
if needed, to obey the maximum connection limits, etc.
|
if needed, to obey the maximum connection limits, etc.
|
||||||
@ -223,54 +325,11 @@ class IMAPServer:
|
|||||||
|
|
||||||
if not self.tunnel:
|
if not self.tunnel:
|
||||||
try:
|
try:
|
||||||
# Try GSSAPI and continue if it fails
|
self._authn_helper(imapobj)
|
||||||
if 'AUTH=GSSAPI' in imapobj.capabilities and have_gss:
|
|
||||||
self.connectionlock.acquire()
|
|
||||||
self.ui.debug('imap',
|
|
||||||
'Attempting GSSAPI authentication')
|
|
||||||
try:
|
|
||||||
imapobj.authenticate('GSSAPI', self.gssauth)
|
|
||||||
except imapobj.error as val:
|
|
||||||
self.gssapi = False
|
|
||||||
self.ui.debug('imap',
|
|
||||||
'GSSAPI Authentication failed')
|
|
||||||
else:
|
|
||||||
self.gssapi = True
|
|
||||||
kerberos.authGSSClientClean(self.gss_vc)
|
|
||||||
self.gss_vc = None
|
|
||||||
self.gss_step = self.GSS_STATE_STEP
|
|
||||||
#if we do self.password = None then the next attempt cannot try...
|
|
||||||
#self.password = None
|
|
||||||
self.connectionlock.release()
|
|
||||||
|
|
||||||
if not self.gssapi:
|
|
||||||
if 'STARTTLS' in imapobj.capabilities and not\
|
|
||||||
self.usessl:
|
|
||||||
self.ui.debug('imap',
|
|
||||||
'Using STARTTLS connection')
|
|
||||||
imapobj.starttls()
|
|
||||||
|
|
||||||
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
|
|
||||||
self.ui.debug('imap',
|
|
||||||
'Attempting CRAM-MD5 authentication')
|
|
||||||
try:
|
|
||||||
imapobj.authenticate('CRAM-MD5',
|
|
||||||
self.md5handler)
|
|
||||||
except imapobj.error as val:
|
|
||||||
self.plainauth(imapobj)
|
|
||||||
else:
|
|
||||||
# Use plaintext login, unless
|
|
||||||
# LOGINDISABLED (RFC2595)
|
|
||||||
if 'LOGINDISABLED' in imapobj.capabilities:
|
|
||||||
raise OfflineImapError("Plaintext login "
|
|
||||||
"disabled by server. Need to use SSL?",
|
|
||||||
OfflineImapError.ERROR.REPO)
|
|
||||||
self.plainauth(imapobj)
|
|
||||||
# Would bail by here if there was a failure.
|
|
||||||
success = 1
|
|
||||||
self.goodpassword = self.password
|
self.goodpassword = self.password
|
||||||
except imapobj.error as val:
|
success = 1
|
||||||
self.passworderror = str(val)
|
except OfflineImapError as e:
|
||||||
|
self.passworderror = str(e)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# update capabilities after login, e.g. gmail serves different ones
|
# update capabilities after login, e.g. gmail serves different ones
|
||||||
|
Loading…
Reference in New Issue
Block a user