From acaa96291d2fe6e03d46c48140a64832bedc312a Mon Sep 17 00:00:00 2001 From: Andreas Mack Date: Sat, 3 Aug 2013 14:06:44 +0200 Subject: [PATCH] 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 Signed-off-by: Eygene Ryabinkin --- Changelog.rst | 1 + offlineimap.conf | 12 ++++++++++++ offlineimap/imapserver.py | 33 +++++++++++++++++++++++++++++++++ offlineimap/repository/IMAP.py | 12 ++++++++++++ 4 files changed, 58 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index 0aab40b..5a17b90 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -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) =================================== diff --git a/offlineimap.conf b/offlineimap.conf index 2794f32..9aba014 100644 --- a/offlineimap.conf +++ b/offlineimap.conf @@ -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. diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py index 896c073..f6d7c27 100644 --- a/offlineimap/imapserver.py +++ b/offlineimap/imapserver.py @@ -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: diff --git a/offlineimap/repository/IMAP.py b/offlineimap/repository/IMAP.py index 3aec2fa..ab466b6 100644 --- a/offlineimap/repository/IMAP.py +++ b/offlineimap/repository/IMAP.py @@ -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 -- 1.8.1.3