483b06
From 2f8033174775f55cb2377baf524fc36914aa38fa Mon Sep 17 00:00:00 2001
483b06
From: Simo Sorce <simo@redhat.com>
483b06
Date: Thu, 23 Mar 2017 13:02:00 -0400
483b06
Subject: [PATCH] Work around issues fetching session data
483b06
483b06
Unfortunately the MIT krb5 library has a severe limitation with FILE
483b06
ccaches when retrieving config data. It will always only search until
483b06
the first entry is found and return that one.
483b06
483b06
For FILE caches MIT krb5 does not support removing old entries when a
483b06
new one is stored, and storage happens only in append mode, so the end
483b06
result is that even if an update is stored it is never returned with the
483b06
standard krb5_cc_get_config() call.
483b06
483b06
To work around this issue we simply implement what krb5_cc_get_config()
483b06
does under the hood with the difference that we do not stop at the first
483b06
match but keep going until all ccache entries have been checked.
483b06
483b06
Related https://pagure.io/freeipa/issue/6775
483b06
483b06
Signed-off-by: Simo Sorce <simo@redhat.com>
483b06
Reviewed-By: Christian Heimes <cheimes@redhat.com>
483b06
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
483b06
---
483b06
 ipapython/session_storage.py | 213 ++++++++++++++++++++++++++++++++++++++-----
483b06
 1 file changed, 190 insertions(+), 23 deletions(-)
483b06
483b06
diff --git a/ipapython/session_storage.py b/ipapython/session_storage.py
483b06
index a88f9f7a75c73d4dc753183a100350d197d02199..f3094f60000aa6e3f4b27fe91092c4214936f651 100644
483b06
--- a/ipapython/session_storage.py
483b06
+++ b/ipapython/session_storage.py
483b06
@@ -13,6 +13,12 @@ try:
483b06
 except OSError as e:  # pragma: no cover
483b06
     raise ImportError(str(e))
483b06
 
483b06
+krb5_int32 = ctypes.c_int32
483b06
+krb5_error_code = krb5_int32
483b06
+krb5_magic = krb5_error_code
483b06
+krb5_enctype = krb5_int32
483b06
+krb5_octet = ctypes.c_uint8
483b06
+krb5_timestamp = krb5_int32
483b06
 
483b06
 class _krb5_context(ctypes.Structure):  # noqa
483b06
     """krb5/krb5.h struct _krb5_context"""
483b06
@@ -27,7 +33,7 @@ class _krb5_ccache(ctypes.Structure):  # noqa
483b06
 class _krb5_data(ctypes.Structure):  # noqa
483b06
     """krb5/krb5.h struct _krb5_data"""
483b06
     _fields_ = [
483b06
-        ("magic", ctypes.c_int32),
483b06
+        ("magic", krb5_magic),
483b06
         ("length", ctypes.c_uint),
483b06
         ("data", ctypes.c_char_p),
483b06
     ]
483b06
@@ -38,6 +44,63 @@ class krb5_principal_data(ctypes.Structure):  # noqa
483b06
     _fields_ = []
483b06
 
483b06
 
483b06
+class _krb5_keyblock(ctypes.Structure):  # noqa
483b06
+    """krb5/krb5.h struct _krb5_keyblock"""
483b06
+    _fields_ = [
483b06
+        ("magic", krb5_magic),
483b06
+        ("enctype", krb5_enctype),
483b06
+        ("length", ctypes.c_uint),
483b06
+        ("contents", ctypes.POINTER(krb5_octet))
483b06
+    ]
483b06
+
483b06
+
483b06
+class _krb5_ticket_times(ctypes.Structure):  # noqa
483b06
+    """krb5/krb5.h struct _krb5_ticket_times"""
483b06
+    _fields_ = [
483b06
+        ("authtime", krb5_timestamp),
483b06
+        ("starttime", krb5_timestamp),
483b06
+        ("endtime", krb5_timestamp),
483b06
+        ("renew_till", krb5_timestamp),
483b06
+    ]
483b06
+
483b06
+
483b06
+class _krb5_address(ctypes.Structure):  # noqa
483b06
+    """krb5/krb5.h struct _krb5_address"""
483b06
+    _fields_ = []
483b06
+
483b06
+
483b06
+class _krb5_authdata(ctypes.Structure):  # noqa
483b06
+    """krb5/krb5.h struct _krb5_authdata"""
483b06
+    _fields_ = []
483b06
+
483b06
+
483b06
+krb5_principal = ctypes.POINTER(krb5_principal_data)
483b06
+krb5_keyblock = _krb5_keyblock
483b06
+krb5_ticket_times = _krb5_ticket_times
483b06
+krb5_boolean = ctypes.c_uint
483b06
+krb5_flags = krb5_int32
483b06
+krb5_data = _krb5_data
483b06
+krb5_address_p = ctypes.POINTER(_krb5_address)
483b06
+krb5_authdata_p = ctypes.POINTER(_krb5_authdata)
483b06
+
483b06
+
483b06
+class _krb5_creds(ctypes.Structure):  # noqa
483b06
+    """krb5/krb5.h struct _krb5_creds"""
483b06
+    _fields_ = [
483b06
+        ("magic", krb5_magic),
483b06
+        ("client", krb5_principal),
483b06
+        ("server", krb5_principal),
483b06
+        ("keyblock", krb5_keyblock),
483b06
+        ("times", krb5_ticket_times),
483b06
+        ("is_skey", krb5_boolean),
483b06
+        ("ticket_flags", krb5_flags),
483b06
+        ("addresses", ctypes.POINTER(krb5_address_p)),
483b06
+        ("ticket", krb5_data),
483b06
+        ("second_ticket", krb5_data),
483b06
+        ("authdata", ctypes.POINTER(krb5_authdata_p))
483b06
+    ]
483b06
+
483b06
+
483b06
 class KRB5Error(Exception):
483b06
     pass
483b06
 
483b06
@@ -48,11 +111,13 @@ def krb5_errcheck(result, func, arguments):
483b06
         raise KRB5Error(result, func.__name__, arguments)
483b06
 
483b06
 
483b06
-krb5_principal = ctypes.POINTER(krb5_principal_data)
483b06
 krb5_context = ctypes.POINTER(_krb5_context)
483b06
 krb5_ccache = ctypes.POINTER(_krb5_ccache)
483b06
 krb5_data_p = ctypes.POINTER(_krb5_data)
483b06
 krb5_error = ctypes.c_int32
483b06
+krb5_creds = _krb5_creds
483b06
+krb5_pointer = ctypes.c_void_p
483b06
+krb5_cc_cursor = krb5_pointer
483b06
 
483b06
 krb5_init_context = LIBKRB5.krb5_init_context
483b06
 krb5_init_context.argtypes = (ctypes.POINTER(krb5_context), )
483b06
@@ -61,15 +126,15 @@ krb5_init_context.errcheck = krb5_errcheck
483b06
 
483b06
 krb5_free_context = LIBKRB5.krb5_free_context
483b06
 krb5_free_context.argtypes = (krb5_context, )
483b06
-krb5_free_context.retval = None
483b06
+krb5_free_context.restype = None
483b06
 
483b06
 krb5_free_principal = LIBKRB5.krb5_free_principal
483b06
 krb5_free_principal.argtypes = (krb5_context, krb5_principal)
483b06
-krb5_free_principal.retval = None
483b06
+krb5_free_principal.restype = None
483b06
 
483b06
 krb5_free_data_contents = LIBKRB5.krb5_free_data_contents
483b06
 krb5_free_data_contents.argtypes = (krb5_context, krb5_data_p)
483b06
-krb5_free_data_contents.retval = None
483b06
+krb5_free_data_contents.restype = None
483b06
 
483b06
 krb5_cc_default = LIBKRB5.krb5_cc_default
483b06
 krb5_cc_default.argtypes = (krb5_context, ctypes.POINTER(krb5_ccache), )
483b06
@@ -78,26 +143,79 @@ krb5_cc_default.errcheck = krb5_errcheck
483b06
 
483b06
 krb5_cc_close = LIBKRB5.krb5_cc_close
483b06
 krb5_cc_close.argtypes = (krb5_context, krb5_ccache, )
483b06
-krb5_cc_close.retval = krb5_error
483b06
+krb5_cc_close.restype = krb5_error
483b06
 krb5_cc_close.errcheck = krb5_errcheck
483b06
 
483b06
 krb5_parse_name = LIBKRB5.krb5_parse_name
483b06
 krb5_parse_name.argtypes = (krb5_context, ctypes.c_char_p,
483b06
                             ctypes.POINTER(krb5_principal), )
483b06
-krb5_parse_name.retval = krb5_error
483b06
+krb5_parse_name.restype = krb5_error
483b06
 krb5_parse_name.errcheck = krb5_errcheck
483b06
 
483b06
 krb5_cc_set_config = LIBKRB5.krb5_cc_set_config
483b06
 krb5_cc_set_config.argtypes = (krb5_context, krb5_ccache, krb5_principal,
483b06
                                ctypes.c_char_p, krb5_data_p, )
483b06
-krb5_cc_set_config.retval = krb5_error
483b06
+krb5_cc_set_config.restype = krb5_error
483b06
 krb5_cc_set_config.errcheck = krb5_errcheck
483b06
 
483b06
-krb5_cc_get_config = LIBKRB5.krb5_cc_get_config
483b06
-krb5_cc_get_config.argtypes = (krb5_context, krb5_ccache, krb5_principal,
483b06
-                               ctypes.c_char_p, krb5_data_p, )
483b06
-krb5_cc_get_config.retval = krb5_error
483b06
-krb5_cc_get_config.errcheck = krb5_errcheck
483b06
+krb5_cc_get_principal = LIBKRB5.krb5_cc_get_principal
483b06
+krb5_cc_get_principal.argtypes = (krb5_context, krb5_ccache,
483b06
+                                  ctypes.POINTER(krb5_principal), )
483b06
+krb5_cc_get_principal.restype = krb5_error
483b06
+krb5_cc_get_principal.errcheck = krb5_errcheck
483b06
+
483b06
+# krb5_build_principal is a variadic function but that can't be expressed
483b06
+# in a ctypes argtypes definition, so I explicitly listed the number of
483b06
+# arguments we actually use through the code for type checking purposes
483b06
+krb5_build_principal = LIBKRB5.krb5_build_principal
483b06
+krb5_build_principal.argtypes = (krb5_context, ctypes.POINTER(krb5_principal),
483b06
+                                 ctypes.c_uint, ctypes.c_char_p,
483b06
+                                 ctypes.c_char_p, ctypes.c_char_p,
483b06
+                                 ctypes.c_char_p, ctypes.c_char_p, )
483b06
+krb5_build_principal.restype = krb5_error
483b06
+krb5_build_principal.errcheck = krb5_errcheck
483b06
+
483b06
+krb5_cc_start_seq_get = LIBKRB5.krb5_cc_start_seq_get
483b06
+krb5_cc_start_seq_get.argtypes = (krb5_context, krb5_ccache,
483b06
+                                  ctypes.POINTER(krb5_cc_cursor), )
483b06
+krb5_cc_start_seq_get.restype = krb5_error
483b06
+krb5_cc_start_seq_get.errcheck = krb5_errcheck
483b06
+
483b06
+krb5_cc_next_cred = LIBKRB5.krb5_cc_next_cred
483b06
+krb5_cc_next_cred.argtypes = (krb5_context, krb5_ccache,
483b06
+                              ctypes.POINTER(krb5_cc_cursor),
483b06
+                              ctypes.POINTER(krb5_creds), )
483b06
+krb5_cc_next_cred.restype = krb5_error
483b06
+krb5_cc_next_cred.errcheck = krb5_errcheck
483b06
+
483b06
+krb5_cc_end_seq_get = LIBKRB5.krb5_cc_end_seq_get
483b06
+krb5_cc_end_seq_get.argtypes = (krb5_context, krb5_ccache,
483b06
+                                ctypes.POINTER(krb5_cc_cursor), )
483b06
+krb5_cc_end_seq_get.restype = krb5_error
483b06
+krb5_cc_end_seq_get.errcheck = krb5_errcheck
483b06
+
483b06
+krb5_free_cred_contents = LIBKRB5.krb5_free_cred_contents
483b06
+krb5_free_cred_contents.argtypes = (krb5_context, ctypes.POINTER(krb5_creds))
483b06
+krb5_free_cred_contents.restype = krb5_error
483b06
+krb5_free_cred_contents.errcheck = krb5_errcheck
483b06
+
483b06
+krb5_principal_compare = LIBKRB5.krb5_principal_compare
483b06
+krb5_principal_compare.argtypes = (krb5_context, krb5_principal,
483b06
+                                   krb5_principal, )
483b06
+krb5_principal_compare.restype = krb5_boolean
483b06
+
483b06
+krb5_unparse_name = LIBKRB5.krb5_unparse_name
483b06
+krb5_unparse_name.argtypes = (krb5_context, krb5_principal,
483b06
+                              ctypes.POINTER(ctypes.c_char_p), )
483b06
+krb5_unparse_name.restype = krb5_error
483b06
+krb5_unparse_name.errcheck = krb5_errcheck
483b06
+
483b06
+krb5_free_unparsed_name = LIBKRB5.krb5_free_unparsed_name
483b06
+krb5_free_unparsed_name.argtypes = (krb5_context, ctypes.c_char_p, )
483b06
+krb5_free_unparsed_name.restype = None
483b06
+
483b06
+CONF_REALM = "X-CACHECONF:"
483b06
+CONF_NAME = "krb5_ccache_conf_data"
483b06
 
483b06
 
483b06
 def store_data(princ_name, key, value):
483b06
@@ -144,29 +262,78 @@ def get_data(princ_name, key):
483b06
     """
483b06
     context = krb5_context()
483b06
     principal = krb5_principal()
483b06
+    srv_princ = krb5_principal()
483b06
     ccache = krb5_ccache()
483b06
-    data = _krb5_data()
483b06
+    pname_princ = krb5_principal()
483b06
+    pname = ctypes.c_char_p()
483b06
 
483b06
     try:
483b06
         krb5_init_context(ctypes.byref(context))
483b06
 
483b06
-        krb5_parse_name(context, ctypes.c_char_p(princ_name),
483b06
-                        ctypes.byref(principal))
483b06
-
483b06
         krb5_cc_default(context, ctypes.byref(ccache))
483b06
+        krb5_cc_get_principal(context, ccache, ctypes.byref(principal))
483b06
 
483b06
-        krb5_cc_get_config(context, ccache, principal, key,
483b06
-                           ctypes.byref(data))
483b06
-
483b06
-        return str(data.data)
483b06
+        # We need to parse and then unparse the name in case the pric_name
483b06
+        # passed in comes w/o a realm attached
483b06
+        krb5_parse_name(context, ctypes.c_char_p(princ_name),
483b06
+                        ctypes.byref(pname_princ))
483b06
+        krb5_unparse_name(context, pname_princ, ctypes.byref(pname))
483b06
+
483b06
+        krb5_build_principal(context, ctypes.byref(srv_princ),
483b06
+                             len(CONF_REALM), ctypes.c_char_p(CONF_REALM),
483b06
+                             ctypes.c_char_p(CONF_NAME), ctypes.c_char_p(key),
483b06
+                             pname, ctypes.c_char_p(None))
483b06
+
483b06
+        # Unfortunately we can't just use krb5_cc_get_config()
483b06
+        # because of bugs in some ccache handling code in krb5
483b06
+        # libraries that would always return the first entry
483b06
+        # stored and not the last one, which is the one we want.
483b06
+        cursor = krb5_cc_cursor()
483b06
+        creds = krb5_creds()
483b06
+        got_creds = False
483b06
+        krb5_cc_start_seq_get(context, ccache, ctypes.byref(cursor))
483b06
+        try:
483b06
+            while True:
483b06
+                checkcreds = krb5_creds()
483b06
+                # the next function will throw an error and break out of the
483b06
+                # while loop when we try to access past the last cred
483b06
+                krb5_cc_next_cred(context, ccache, ctypes.byref(cursor),
483b06
+                                  ctypes.byref(checkcreds))
483b06
+                if (krb5_principal_compare(context, principal,
483b06
+                                          checkcreds.client) == 1 and
483b06
+                    krb5_principal_compare(context, srv_princ,
483b06
+                                           checkcreds.server) == 1):
483b06
+                    if got_creds:
483b06
+                        krb5_free_cred_contents(context, ctypes.byref(creds))
483b06
+                    creds = checkcreds
483b06
+                    got_creds = True
483b06
+                    # We do not stop here, as we want the LAST entry
483b06
+                    # in the ccache for those ccaches that cannot delete
483b06
+                    # but only always append, like FILE
483b06
+                else:
483b06
+                    krb5_free_cred_contents(context,
483b06
+                                            ctypes.byref(checkcreds))
483b06
+        except KRB5Error:
483b06
+            pass
483b06
+        finally:
483b06
+            krb5_cc_end_seq_get(context, ccache, ctypes.byref(cursor))
483b06
+
483b06
+        if got_creds:
483b06
+            data = creds.ticket.data.decode('utf-8')
483b06
+            krb5_free_cred_contents(context, ctypes.byref(creds))
483b06
+            return data
483b06
 
483b06
     finally:
483b06
         if principal:
483b06
             krb5_free_principal(context, principal)
483b06
+        if srv_princ:
483b06
+            krb5_free_principal(context, srv_princ)
483b06
+        if pname_princ:
483b06
+            krb5_free_principal(context, pname_princ)
483b06
+        if pname:
483b06
+            krb5_free_unparsed_name(context, pname)
483b06
         if ccache:
483b06
             krb5_cc_close(context, ccache)
483b06
-        if data:
483b06
-            krb5_free_data_contents(context, data)
483b06
         if context:
483b06
             krb5_free_context(context)
483b06
 
483b06
-- 
483b06
2.12.1
483b06