]> code.delx.au - offlineimap/commitdiff
Bug#359213: Kerberos
authorEric Dorland <eric@kuroneko.ca>
Sun, 9 Mar 2008 05:46:51 +0000 (00:46 -0500)
committerJohn Goerzen <jgoerzen@complete.org>
Mon, 10 Mar 2008 01:30:23 +0000 (20:30 -0500)
* 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 <eric@kuroneko.ca>
ICQ: #61138586, Jabber: hooty@jabber.com

offlineimap/imapserver.py

index 6ca1fbcff3969ea489e6c0c88bd8883bd056824d..4e37ece334e6cfc8dad8a566e24d9b24034cce37 100644 (file)
@@ -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