From 88724949fa960549ade0107c15624eb54aa4ae5b Mon Sep 17 00:00:00 2001
From: Robbie Harwood <rharwood@redhat.com>
Date: Mon, 26 Feb 2018 18:08:00 -0500
Subject: [PATCH] Port to python-gssapi from pykerberos
python-gssapi has a visible, active upstream and a more pleasant
interface. python-gssapi is present in most distributions, while
pykerberos is slated for removal from Fedora/RHEL/CentOS.
Github-ref: https://github.com/OfflineIMAP/offlineimap/pull/529
Tested-by: Robbie Harwood <rharwood@redhat.com>
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
---
offlineimap/imapserver.py | 69 +++++++++++++++++++++--------------------------
1 file changed, 30 insertions(+), 39 deletions(-)
diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py
index 0a130575..95c4d662 100644
--- a/offlineimap/imapserver.py
+++ b/offlineimap/imapserver.py
@@ -17,7 +17,6 @@
import hmac
import socket
-import base64
import json
import urllib
import time
@@ -36,13 +35,10 @@
try:
- # do we have a recent pykerberos?
- have_gss = False
- import kerberos
- if 'authGSSClientWrap' in dir(kerberos):
- have_gss = True
+ import gssapi
+ have_gss = True
except ImportError:
- pass
+ have_gss = False
class IMAPServer(object):
@@ -55,9 +51,6 @@ class IMAPServer(object):
delim The server's folder delimiter. Only valid after acquireconnection()
"""
- GSS_STATE_STEP = 0
- GSS_STATE_WRAP = 1
-
def __init__(self, repos):
""":repos: a IMAPRepository instance."""
@@ -127,7 +120,6 @@ def __init__(self, repos):
self.connectionlock = Lock()
self.reference = repos.getreference()
self.idlefolders = repos.getidlefolders()
- self.gss_step = self.GSS_STATE_STEP
self.gss_vc = None
self.gssapi = False
@@ -267,33 +259,35 @@ def __xoauth2handler(self, response):
self.ui.debug('imap', 'xoauth2handler: returning "%s"'% auth_string)
return auth_string
- def __gssauth(self, response):
- data = base64.b64encode(response)
+ # Perform the next step handling a GSSAPI connection.
+ # Client sends first, so token will be ignored if there is no context.
+ def __gsshandler(self, token):
+ if token == "":
+ token = None
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 as err:
- # Kerberos errored out on us, respond with None to cancel the
+ if not self.gss_vc:
+ name = gssapi.Name('imap@' + self.hostname,
+ gssapi.NameType.hostbased_service)
+ self.gss_vc = gssapi.SecurityContext(usage="initiate",
+ name=name)
+
+ if not self.gss_vc.complete:
+ response = self.gss_vc.step(token)
+ return response if response else ""
+
+ # Don't bother checking qop because we're over a TLS channel
+ # already. But hey, if some server started encrypting tomorrow,
+ # we'd be ready since krb5 always requests integrity and
+ # confidentiality support.
+ response = self.gss_vc.unwrap(token)
+ response = self.gss_vc.wrap(response.message, response.encrypted)
+ return response.message if response.message else ""
+ except gssapi.exceptions.GSSError as err:
+ # GSSAPI errored out on us; respond with None to cancel the
# authentication
- self.ui.debug('imap', '%s: %s'% (err[0][0], err[1][0]))
+ self.ui.debug('imap', err.gen_message())
return None
- if not response:
- response = ''
- return base64.b64decode(response)
-
def __start_tls(self, imapobj):
if 'STARTTLS' in imapobj.capabilities and not self.usessl:
self.ui.debug('imap', 'Using STARTTLS connection')
@@ -327,16 +321,14 @@ def __authn_gssapi(self, imapobj):
self.connectionlock.acquire()
try:
- imapobj.authenticate('GSSAPI', self.__gssauth)
+ imapobj.authenticate('GSSAPI', self.__gsshandler)
return True
except imapobj.error as e:
self.gssapi = False
raise
else:
self.gssapi = True
- kerberos.authGSSClientClean(self.gss_vc)
self.gss_vc = None
- self.gss_step = self.GSS_STATE_STEP
finally:
self.connectionlock.release()
@@ -680,8 +672,7 @@ def close(self):
self.assignedconnections = []
self.availableconnections = []
self.lastowner = {}
- # reset kerberos state
- self.gss_step = self.GSS_STATE_STEP
+ # reset GSSAPI state
self.gss_vc = None
self.gssapi = False