Add SASL PLAIN authentication method

- this method isn't as deprecated as IMAP LOGIN;

 - it allows to keep hashed passwords on the server side;

 - it has the ability to specify that the remote identity
   is different from authenticating username, so it even
   can be useful in some cases (e.g., migrated mailboxes);
   configuration variable "remote_identity" was introduced
   to leverage this functionality.

From: Andreas Mack <andreas.mack@konsec.com>
Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
Andreas Mack 2013-08-03 14:06:44 +02:00 committed by Eygene Ryabinkin
parent 7d313f49dc
commit acaa96291d
4 changed files with 58 additions and 0 deletions

View File

@ -16,6 +16,7 @@ WIP (add new stuff for the next release)
* Honor the timezone of emails (Tobias Thierer)
* Allow mbnames output to be sorted by a custom sort key by specifying
a 'sort_keyfunc' function in the [mbnames] section of the config.
* Support SASL PLAIN authentication method. (Andreas Mack)
OfflineIMAP v6.5.5-rc1 (2012-09-05)
===================================

View File

@ -353,6 +353,18 @@ ssl = yes
# Specify the remote user name.
remoteuser = username
# Specify the user to be authorized as. Sometimes we want to
# authenticate with our login/password, but tell the server that we
# really want to be treated as some other user; perhaps server will
# allow us to do that (or, may be, not). Some IMAP servers migrate
# account names using this functionality: your credentials remain
# intact, but remote identity changes.
#
# Currently this variable is used only for SASL PLAIN authentication
# mechanism.
#
# remote_identity = authzuser
# There are six ways to specify the password for the IMAP server:
#
# 1. No password at all specified in the config file.

View File

@ -55,6 +55,7 @@ class IMAPServer:
self.tunnel = repos.getpreauthtunnel()
self.usessl = repos.getssl()
self.username = None if self.tunnel else repos.getuser()
self.user_identity = repos.get_remote_identity()
self.password = None
self.passworderror = None
self.goodpassword = None
@ -133,6 +134,24 @@ class IMAPServer:
self.ui.debug('imap', 'Attempting IMAP LOGIN authentication')
imapobj.login(self.username, self.getpassword())
def plainhandler(self, response):
"""
Implements SASL PLAIN authentication, RFC 4616,
http://tools.ietf.org/html/rfc4616
"""
authc = self.username
passwd = self.getpassword()
authz = ''
if self.user_identity != None:
authz = self.user_identity
NULL = u'\x00'
retval = NULL.join((authz, authc, passwd)).encode('utf-8')
self.ui.debug('imap', 'plainhandler: returning %s' % retval)
return retval
def gssauth(self, response):
data = base64.b64encode(response)
try:
@ -213,6 +232,8 @@ class IMAPServer:
"TLS connection: %s" % str(e),
OfflineImapError.ERROR.REPO)
# Hashed authenticators come first: they don't reveal
# passwords.
if 'AUTH=CRAM-MD5' in imapobj.capabilities:
tried_to_authn = True
self.ui.debug('imap', 'Attempting '
@ -224,6 +245,18 @@ class IMAPServer:
self.ui.warn('CRAM-MD5 authentication failed: %s' % e)
exc_stack.append(('CRAM-MD5', e))
# Try plaintext authenticators.
if 'AUTH=PLAIN' in imapobj.capabilities:
tried_to_authn = True
self.ui.debug('imap', 'Attempting '
'PLAIN authentication')
try:
imapobj.authenticate('PLAIN', self.plainhandler)
return
except imapobj.error as e:
self.ui.warn('PLAIN authentication failed: %s' % e)
exc_stack.append(('PLAIN', e))
# Last resort: use LOGIN command,
# unless LOGINDISABLED is advertized (RFC 2595)
if 'LOGINDISABLED' in imapobj.capabilities:

View File

@ -115,6 +115,18 @@ class IMAPRepository(BaseRepository):
"'%s' specified." % self,
OfflineImapError.ERROR.REPO)
def get_remote_identity(self):
"""
Remote identity is used for certain SASL mechanisms
(currently -- PLAIN) to inform server about the ID
we want to authorize as instead of our login name.
"""
return self.getconf('remote_identity', default=None)
def getuser(self):
user = None
localeval = self.localeval