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>
This commit is contained in:
Robbie Harwood 2018-02-26 18:08:00 -05:00 committed by Nicolas Sebrecht
parent c8847ccff9
commit 88724949fa
1 changed files with 31 additions and 40 deletions

View File

@ -17,7 +17,6 @@
import hmac import hmac
import socket import socket
import base64
import json import json
import urllib import urllib
import time import time
@ -36,13 +35,10 @@ from offlineimap.ui import getglobalui
try: try:
# do we have a recent pykerberos? import gssapi
have_gss = False have_gss = True
import kerberos
if 'authGSSClientWrap' in dir(kerberos):
have_gss = True
except ImportError: except ImportError:
pass have_gss = False
class IMAPServer(object): class IMAPServer(object):
@ -55,9 +51,6 @@ class IMAPServer(object):
delim The server's folder delimiter. Only valid after acquireconnection() delim The server's folder delimiter. Only valid after acquireconnection()
""" """
GSS_STATE_STEP = 0
GSS_STATE_WRAP = 1
def __init__(self, repos): def __init__(self, repos):
""":repos: a IMAPRepository instance.""" """:repos: a IMAPRepository instance."""
@ -127,7 +120,6 @@ class IMAPServer(object):
self.connectionlock = Lock() self.connectionlock = Lock()
self.reference = repos.getreference() self.reference = repos.getreference()
self.idlefolders = repos.getidlefolders() self.idlefolders = repos.getidlefolders()
self.gss_step = self.GSS_STATE_STEP
self.gss_vc = None self.gss_vc = None
self.gssapi = False self.gssapi = False
@ -267,32 +259,34 @@ class IMAPServer(object):
self.ui.debug('imap', 'xoauth2handler: returning "%s"'% auth_string) self.ui.debug('imap', 'xoauth2handler: returning "%s"'% auth_string)
return auth_string return auth_string
def __gssauth(self, response): # Perform the next step handling a GSSAPI connection.
data = base64.b64encode(response) # Client sends first, so token will be ignored if there is no context.
def __gsshandler(self, token):
if token == "":
token = None
try: try:
if self.gss_step == self.GSS_STATE_STEP: if not self.gss_vc:
if not self.gss_vc: name = gssapi.Name('imap@' + self.hostname,
rc, self.gss_vc = kerberos.authGSSClientInit( gssapi.NameType.hostbased_service)
'imap@' + self.hostname) self.gss_vc = gssapi.SecurityContext(usage="initiate",
response = kerberos.authGSSClientResponse(self.gss_vc) name=name)
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
# authentication
self.ui.debug('imap', '%s: %s'% (err[0][0], err[1][0]))
return None
if not response: if not self.gss_vc.complete:
response = '' response = self.gss_vc.step(token)
return base64.b64decode(response) 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', err.gen_message())
return None
def __start_tls(self, imapobj): def __start_tls(self, imapobj):
if 'STARTTLS' in imapobj.capabilities and not self.usessl: if 'STARTTLS' in imapobj.capabilities and not self.usessl:
@ -327,16 +321,14 @@ class IMAPServer(object):
self.connectionlock.acquire() self.connectionlock.acquire()
try: try:
imapobj.authenticate('GSSAPI', self.__gssauth) imapobj.authenticate('GSSAPI', self.__gsshandler)
return True return True
except imapobj.error as e: except imapobj.error as e:
self.gssapi = False self.gssapi = False
raise raise
else: else:
self.gssapi = True self.gssapi = True
kerberos.authGSSClientClean(self.gss_vc)
self.gss_vc = None self.gss_vc = None
self.gss_step = self.GSS_STATE_STEP
finally: finally:
self.connectionlock.release() self.connectionlock.release()
@ -680,8 +672,7 @@ class IMAPServer(object):
self.assignedconnections = [] self.assignedconnections = []
self.availableconnections = [] self.availableconnections = []
self.lastowner = {} self.lastowner = {}
# reset kerberos state # reset GSSAPI state
self.gss_step = self.GSS_STATE_STEP
self.gss_vc = None self.gss_vc = None
self.gssapi = False self.gssapi = False