From f7b5aa4ba37b703bb049f5cb40f5f4eea26d573a Mon Sep 17 00:00:00 2001 From: Eric Dorland Date: Sun, 9 Mar 2008 00:46:51 -0500 Subject: [PATCH] Bug#359213: Kerberos * John Goerzen (jgoerzen@complete.org) wrote: > tages 359213 help > thanks > > Hi folks, > > Unfortunately, I lack both the expertise to add Kerberos > authentication and a way to test this. A patch would be very welcome > :-) Well it was a bit unclear but there's a patch for offlineimap attached to Guido's message: http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=10;filename=support-GSSAPI-via-pykerberos.diff;att=1;bug=359213 I've tried it out and it works great! Except that if the Kerberos layer throws an exception it wouldn't fail over to plain authentication properly. Attached is a slightly reworked patch that I tested it in the following situations: 1. python-kerberos uninstalled, with kerberos credentials. Prompted for password authentication as expected. 2. python-kerberos installed, with kerberos credentials. Keberos authentication was successful and my mail was fetched without password prompting. Yeehaw! 3. python-kerberos installed, with no kerberos credentials. Prompted for password authentication as expected. I think those are all the interesting situations. So I think this is ready to go in, with a Suggest on python-kerberos (>= 1.0+mk080218-1). -- Eric Dorland ICQ: #61138586, Jabber: hooty@jabber.com --- offlineimap/imapserver.py | 67 +++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py index 6ca1fbc..4e37ece 100644 --- a/offlineimap/imapserver.py +++ b/offlineimap/imapserver.py @@ -21,7 +21,16 @@ from offlineimap import imaplibutil, imaputil, threadutil from offlineimap.ui import UIBase from threading import * import thread, hmac, os +import base64 +try: + # do we have a recent pykerberos? + have_gss = False + import kerberos + if 'authGSSClientWrap' in dir(kerberos): + have_gss = True +except ImportError: + pass class UsefulIMAPMixIn: def getstate(self): @@ -58,6 +67,8 @@ class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplibutil.WrappedIMAP4_SSL): class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplibutil.IMAP4_Tunnel): pass class IMAPServer: + GSS_STATE_STEP = 0 + GSS_STATE_WRAP = 1 def __init__(self, config, reposname, username = None, password = None, hostname = None, port = None, ssl = 1, maxconnections = 1, tunnel = None, @@ -86,6 +97,9 @@ class IMAPServer: self.semaphore = BoundedSemaphore(self.maxconnections) self.connectionlock = Lock() self.reference = reference + self.gss_step = self.GSS_STATE_STEP + self.gss_vc = None + self.gssapi = False def getpassword(self): if self.goodpassword != None: @@ -134,7 +148,34 @@ class IMAPServer: UIBase.getglobalui().debug('imap', 'Attempting plain authentication') imapobj.login(self.username, self.getpassword()) - + + def gssauth(self, response): + data = base64.b64encode(response) + try: + if self.gss_step == self.GSS_STATE_STEP: + if not self.gss_vc: + rc, self.gss_vc = kerberos.authGSSClientInit('imap@' + + self.hostname) + response = kerberos.authGSSClientResponse(self.gss_vc) + rc = kerberos.authGSSClientStep(self.gss_vc, data) + if rc != kerberos.AUTH_GSS_CONTINUE: + self.gss_step = self.GSS_STATE_WRAP + elif self.gss_step == self.GSS_STATE_WRAP: + rc = kerberos.authGSSClientUnwrap(self.gss_vc, data) + response = kerberos.authGSSClientResponse(self.gss_vc) + rc = kerberos.authGSSClientWrap(self.gss_vc, response, + self.username) + response = kerberos.authGSSClientResponse(self.gss_vc) + except kerberos.GSSError, err: + # Kerberos errored out on us, respond with None to cancel the + # authentication + UIBase.getglobalui().debug('imap', + '%s: %s' % (err[0][0], err[1][0])) + return None + + if not response: + response = '' + return base64.b64decode(response) def acquireconnection(self): """Fetches a connection from the pool, making sure to create a new one @@ -186,15 +227,29 @@ class IMAPServer: if not self.tunnel: try: - if 'AUTH=CRAM-MD5' in imapobj.capabilities: + # Try GSSAPI and continue if it fails + if 'AUTH=GSSAPI' in imapobj.capabilities and have_gss: UIBase.getglobalui().debug('imap', - 'Attempting CRAM-MD5 authentication') + 'Attempting GSSAPI authentication') try: - imapobj.authenticate('CRAM-MD5', self.md5handler) + imapobj.authenticate('GSSAPI', self.gssauth) except imapobj.error, val: + UIBase.getglobalui().debug('imap', + 'GSSAPI Authentication failed') + else: + self.gssapi = True + self.password = None + + if not self.gssapi: + if 'AUTH=CRAM-MD5' in imapobj.capabilities: + UIBase.getglobalui().debug('imap', + 'Attempting CRAM-MD5 authentication') + try: + imapobj.authenticate('CRAM-MD5', self.md5handler) + except imapobj.error, val: + self.plainauth(imapobj) + else: self.plainauth(imapobj) - else: - self.plainauth(imapobj) # Would bail by here if there was a failure. success = 1 self.goodpassword = self.password -- 2.39.2