On 2015-07-27 11:38, Simo Sorce wrote:
On Sun, 2015-07-26 at 21:51 +0200, Michael Šimáček wrote:
It would probably be nicer to do the full cycle, but I'd like to
avoid
changes in behavior when porting from one library to another. And the
code above doesn't actually hold any connection, so it would require
more refactoring to make that happen. For now I would follow what the
original code was doing. As for the exceptions, I think it would
actually be justifiable to use the raw api's init_sec_context,
because
the high level api would just do the same call + the exception
handling
magic, which we want to avoid for now. Please let me know what do you
think.
Attaching updated patch that uses 'unicode' instead of
raw.display_name
and reverts back to using init_sec_context.

Sorry,
but we should really not use the raw API here.
If it means more changes to the code, so be it, please us the high level
API as recommended by Robbie, we wrote a better API so that people would
use it, and we want to apply best practices when changing code in IPA.


Attaching new revision of the patch that performs the full negotiation cycle.

Michael
From 9cd3f604ba4c2a8ccc116296ed9c4a5b4b2075fe Mon Sep 17 00:00:00 2001
From: Michael Simacek <msima...@redhat.com>
Date: Thu, 16 Jul 2015 18:22:00 +0200
Subject: [PATCH] Port from python-kerberos to python-gssapi

kerberos library doesn't support Python 3 and probably never will.
python-gssapi library is Python 3 compatible.

https://fedorahosted.org/freeipa/ticket/5147
---
 BUILD.txt            |   2 +-
 freeipa.spec.in      |   4 +-
 ipalib/rpc.py        | 112 +++++++++++++++++++++++++++++++++++++++------------
 ipalib/util.py       |  13 +++---
 ipapython/ipautil.py |  17 --------
 5 files changed, 95 insertions(+), 53 deletions(-)

diff --git a/BUILD.txt b/BUILD.txt
index 6a28beba1e0844971fb5625c0e1adf3f0c0fc0e3..53012b14d05673d4fbc4d0567e877348d5e78444 100644
--- a/BUILD.txt
+++ b/BUILD.txt
@@ -20,7 +20,7 @@ systemd-units samba-devel samba-python libwbclient-devel libtalloc-devel \
 libtevent-devel nspr-devel nss-devel openssl-devel openldap-devel krb5-devel \
 krb5-workstation libuuid-devel libcurl-devel xmlrpc-c-devel popt-devel \
 autoconf automake m4 libtool gettext python-devel python-ldap \
-python-setuptools python-krbV python-nss python-netaddr python-kerberos \
+python-setuptools python-krbV python-nss python-netaddr python-gssapi \
 python-rhsm pyOpenSSL pylint python-polib libipa_hbac-python python-memcached \
 sssd python-lxml python-pyasn1 python-qrcode-core python-dns m2crypto \
 check libsss_idmap-devel libsss_nss_idmap-devel java-headless rhino \
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 0351952c692eb0cee2148053462c50b6d9073b5d..57d3d26e94aab6267143793943268175ed440586 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -72,7 +72,7 @@ BuildRequires:  python-krbV
 BuildRequires:  python-nss
 BuildRequires:  python-cryptography
 BuildRequires:  python-netaddr
-BuildRequires:  python-kerberos >= 1.1-14
+BuildRequires:  python-gssapi >= 1.1.1
 BuildRequires:  python-rhsm
 BuildRequires:  pyOpenSSL
 BuildRequires:  pylint >= 1.0
@@ -303,7 +303,7 @@ IPA administrators.
 %package python
 Summary: Python libraries used by IPA
 Group: System Environment/Libraries
-Requires: python-kerberos >= 1.1-14
+Requires: python-gssapi >= 1.1.1
 Requires: gnupg
 Requires: iproute
 Requires: keyutils
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 466b49a6dd60370db4d588389acba8dcaa493aa1..4176bbd283da709b60844bdb38651af97ea8f48f 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -44,7 +44,7 @@ from urllib2 import urlparse
 
 from xmlrpclib import (Binary, Fault, DateTime, dumps, loads, ServerProxy,
         Transport, ProtocolError, MININT, MAXINT)
-import kerberos
+import gssapi
 from dns import resolver, rdatatype
 from dns.exception import DNSException
 from nss.error import NSPRError
@@ -510,24 +510,32 @@ class KerbTransport(SSLTransport):
     """
     Handles Kerberos Negotiation authentication to an XML-RPC server.
     """
-    flags = kerberos.GSS_C_MUTUAL_FLAG | kerberos.GSS_C_SEQUENCE_FLAG
+    flags = [gssapi.RequirementFlag.mutual_authentication,
+             gssapi.RequirementFlag.out_of_sequence_detection]
+
+    def __init__(self, *args, **kwargs):
+        SSLTransport.__init__(self, *args, **kwargs)
+        self._sec_context = None
 
     def _handle_exception(self, e, service=None):
-        (major, minor) = ipautil.get_gsserror(e)
-        if minor[1] == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
+        # kerberos library coerced error codes to signed, gssapi uses unsigned
+        minor = e.min_code
+        if minor & (1 << 31):
+            minor -= 1 << 32
+        if minor == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
             raise errors.ServiceError(service=service)
-        elif minor[1] == KRB5_FCC_NOFILE:
+        elif minor == KRB5_FCC_NOFILE:
             raise errors.NoCCacheError()
-        elif minor[1] == KRB5KRB_AP_ERR_TKT_EXPIRED:
+        elif minor == KRB5KRB_AP_ERR_TKT_EXPIRED:
             raise errors.TicketExpired()
-        elif minor[1] == KRB5_FCC_PERM:
+        elif minor == KRB5_FCC_PERM:
             raise errors.BadCCachePerms()
-        elif minor[1] == KRB5_CC_FORMAT:
+        elif minor == KRB5_CC_FORMAT:
             raise errors.BadCCacheFormat()
-        elif minor[1] == KRB5_REALM_CANT_RESOLVE:
+        elif minor == KRB5_REALM_CANT_RESOLVE:
             raise errors.CannotResolveKDC()
         else:
-            raise errors.KerberosError(major=major, minor=minor)
+            raise errors.KerberosError(major=e.maj_code, minor=minor)
 
     def get_host_info(self, host):
         """
@@ -548,30 +556,83 @@ class KerbTransport(SSLTransport):
         service = "HTTP@" + host.split(':')[0]
 
         try:
-            (rc, vc) = kerberos.authGSSClientInit(service=service,
-                                                  gssflags=self.flags)
-        except kerberos.GSSError, e:
-            self._handle_exception(e)
-
-        try:
-            kerberos.authGSSClientStep(vc, "")
-        except kerberos.GSSError, e:
+            name = gssapi.Name(service, gssapi.NameType.hostbased_service)
+            self._sec_context = gssapi.SecurityContext(name=name, flags=self.flags)
+            response = self._sec_context.step()
+        except gssapi.exceptions.GSSError as e:
             self._handle_exception(e, service=service)
 
+        self._set_auth_header(extra_headers, response)
+
+        return (host, extra_headers, x509)
+
+    def _set_auth_header(self, extra_headers, token):
         for (h, v) in extra_headers:
             if h == 'Authorization':
                 extra_headers.remove((h, v))
                 break
 
-        extra_headers.append(
-            ('Authorization', 'negotiate %s' % kerberos.authGSSClientResponse(vc))
-        )
+        if token:
+            extra_headers.append(
+                ('Authorization', 'negotiate %s' % base64.b64encode(token))
+            )
 
-        return (host, extra_headers, x509)
+    def _auth_complete(self, response):
+        if self._sec_context:
+            header = response.getheader('www-authenticate', '')
+            token = None
+            for field in header.split(','):
+                k, _, v = field.strip().partition(' ')
+                if k.lower() == 'negotiate':
+                    try:
+                        token = base64.b64decode(v)
+                        break
+                    # b64decode raises TypeError on invalid input
+                    except TypeError:
+                        pass
+            if not token:
+                raise KerberosError("No valid Negotiate header in server response")
+            token = self._sec_context.step(token=token)
+            if self._sec_context.complete:
+                self._sec_context = None
+                return True
+            self._set_auth_header(self._extra_headers, token)
+            return False
+        return True
 
     def single_request(self, host, handler, request_body, verbose=0):
+        # Based on xmlrpclib.Transport.single_request
         try:
-            return SSLTransport.single_request(self, host, handler, request_body, verbose)
+            h = SSLTransport.make_connection(self, host)
+            if verbose:
+                h.set_debuglevel(1)
+
+            while True:
+                self.send_request(h, handler, request_body)
+                self.send_host(h, host)
+                self.send_user_agent(h)
+                self.send_content(h, request_body)
+
+                response = h.getresponse(buffering=True)
+                if response.status != 200:
+                    if (response.getheader("content-length", 0)):
+                        response.read()
+
+                    if response.status == 401:
+                        if not self._auth_complete(response):
+                            continue
+
+                    raise ProtocolError(
+                        host + handler,
+                        response.status, response.reason,
+                        response.msg)
+
+                self.verbose = verbose
+                if not self._auth_complete(response):
+                    continue
+                return self.parse_response(response)
+        except gssapi.exceptions.GSSError as e:
+            self._handle_exception(e)
         finally:
             self.close()
 
@@ -632,8 +693,9 @@ class DelegatedKerbTransport(KerbTransport):
     Handles Kerberos Negotiation authentication and TGT delegation to an
     XML-RPC server.
     """
-    flags = kerberos.GSS_C_DELEG_FLAG |  kerberos.GSS_C_MUTUAL_FLAG | \
-            kerberos.GSS_C_SEQUENCE_FLAG
+    flags = [gssapi.RequirementFlag.delegate_to_peer,
+             gssapi.RequirementFlag.mutual_authentication,
+             gssapi.RequirementFlag.out_of_sequence_detection]
 
 
 class RPCClient(Connectible):
diff --git a/ipalib/util.py b/ipalib/util.py
index 649a4875fde0b44844749946cce53d81f7f6eea4..5a670146e0ea569458bf0086b075df3d4b7b4b5b 100644
--- a/ipalib/util.py
+++ b/ipalib/util.py
@@ -63,15 +63,12 @@ def json_serialize(obj):
 
 def get_current_principal():
     try:
-        import kerberos
-        rc, vc = kerberos.authGSSClientInit("notempty")
-        rc = kerberos.authGSSClientInquireCred(vc)
-        username = kerberos.authGSSClientUserName(vc)
-        kerberos.authGSSClientClean(vc)
-        return unicode(username)
+        import gssapi
+        cred = gssapi.Credentials(usage='initiate')
+        return unicode(cred.name)
     except ImportError:
-        raise RuntimeError('python-kerberos is not available.')
-    except kerberos.GSSError, e:
+        raise RuntimeError('python-gssapi is not available.')
+    except gssapi.exceptions.GSSError:
         #TODO: do a kinit?
         raise errors.CCacheError()
 
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 88e89706b8e2aa6dea80809510d88bceaa836e85..05a7eebf012b27e3e34cb74778c2724e9deaf616 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -783,23 +783,6 @@ def user_input(prompt, default = None, allow_empty = True):
                 return ret
 
 
-def get_gsserror(e):
-    """
-    A GSSError exception looks differently in python 2.4 than it does
-    in python 2.5. Deal with it.
-    """
-
-    try:
-       major = e[0]
-       minor = e[1]
-    except:
-       major = e[0][0]
-       minor = e[0][1]
-
-    return (major, minor)
-
-
-
 def host_port_open(host, port, socket_type=socket.SOCK_STREAM, socket_timeout=None):
     for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket_type):
         af, socktype, proto, canonname, sa = res
-- 
2.1.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to