summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/rpc.py30
-rw-r--r--ipapython/ccache_storage.py234
2 files changed, 242 insertions, 22 deletions
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 8d1bba5a8..be3133335 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -56,7 +56,7 @@ from ipalib import errors, capabilities
from ipalib.request import context, Connection
from ipapython.ipa_log_manager import root_logger
from ipapython import ipautil
-from ipapython import kernel_keyring
+from ipapython import ccache_storage
from ipapython.cookie import Cookie
from ipapython.dnsutil import DNSName
from ipalib.text import _
@@ -84,19 +84,11 @@ if six.PY3:
unicode = str
COOKIE_NAME = 'ipa_session'
-KEYRING_COOKIE_NAME = '%s_cookie:%%s' % COOKIE_NAME
+CCACHE_COOKIE_KEY_NAME = 'X-IPA-Session-Cookie'
errors_by_code = dict((e.errno, e) for e in public_errors)
-def client_session_keyring_keyname(principal):
- '''
- Return the key name used for storing the client session data for
- the given principal.
- '''
-
- return KEYRING_COOKIE_NAME % principal
-
def update_persistent_client_session_data(principal, data):
'''
Given a principal create or update the session data for that
@@ -106,13 +98,11 @@ def update_persistent_client_session_data(principal, data):
'''
try:
- keyname = client_session_keyring_keyname(principal)
+ s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+ s.store_data(principal, data)
except Exception as e:
raise ValueError(str(e))
- # kernel_keyring only raises ValueError (why??)
- kernel_keyring.update_key(keyname, data)
-
def read_persistent_client_session_data(principal):
'''
Given a principal return the stored session data for that
@@ -122,13 +112,11 @@ def read_persistent_client_session_data(principal):
'''
try:
- keyname = client_session_keyring_keyname(principal)
+ s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+ s.get_data(principal)
except Exception as e:
raise ValueError(str(e))
- # kernel_keyring only raises ValueError (why??)
- return kernel_keyring.read_key(keyname)
-
def delete_persistent_client_session_data(principal):
'''
Given a principal remove the session data for that
@@ -138,13 +126,11 @@ def delete_persistent_client_session_data(principal):
'''
try:
- keyname = client_session_keyring_keyname(principal)
+ s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+ s.remove_data(principal)
except Exception as e:
raise ValueError(str(e))
- # kernel_keyring only raises ValueError (why??)
- kernel_keyring.del_key(keyname)
-
def xml_wrap(value, version):
"""
Wrap all ``str`` in ``xmlrpc.client.Binary``.
diff --git a/ipapython/ccache_storage.py b/ipapython/ccache_storage.py
new file mode 100644
index 000000000..f2de30152
--- /dev/null
+++ b/ipapython/ccache_storage.py
@@ -0,0 +1,234 @@
+#
+# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
+#
+
+import ctypes
+import os
+import sys
+
+import six
+
+
+class KRB5Error(Exception):
+ pass
+
+
+PY3 = sys.version_info[0] == 3
+
+
+try:
+ LIBKRB5 = ctypes.CDLL('libkrb5.so.3')
+except OSError as e: # pragma: no cover
+ LIBKRB5 = e
+else:
+ class c_text_p(ctypes.c_char_p): # noqa
+ """A c_char_p variant that can handle UTF-8 text"""
+ @classmethod
+ def from_param(cls, value):
+ if value is None:
+ return None
+ if PY3 and isinstance(value, str):
+ return value.encode('utf-8')
+ elif not PY3 and isinstance(value, unicode): # noqa
+ return value.encode('utf-8')
+ elif not isinstance(value, bytes):
+ raise TypeError(value)
+ else:
+ return value
+
+ @property
+ def text(self):
+ value = self.value
+ if value is None:
+ return None
+ elif not isinstance(value, str):
+ return value.decode('utf-8')
+ return value
+
+ class _krb5_context(ctypes.Structure): # noqa
+ """krb5/krb5.h struct _krb5_context"""
+ __slots__ = ()
+ _fields_ = []
+
+ class _krb5_ccache(ctypes.Structure): # noqa
+ """krb5/krb5.h struct _krb5_ccache"""
+ __slots__ = ()
+ _fields_ = []
+
+ class _krb5_data(ctypes.Structure): # noqa
+ """krb5/krb5.h struct _krb5_data"""
+ __slots__ = ()
+ _fields_ = [
+ ("magic", ctypes.c_int32),
+ ("length", ctypes.c_uint),
+ ("data", ctypes.c_char_p),
+ ]
+
+ class krb5_principal_data(ctypes.Structure): # noqa
+ """krb5/krb5.h struct krb5_principal_data"""
+ __slots__ = ()
+ _fields_ = []
+
+ def krb5_errcheck(result, func, arguments):
+ """Error checker for krb5_error return value"""
+ if result != 0:
+ raise KRB5Error(result, func.__name__, arguments)
+
+ krb5_principal = ctypes.POINTER(krb5_principal_data)
+ krb5_context = ctypes.POINTER(_krb5_context)
+ krb5_ccache = ctypes.POINTER(_krb5_ccache)
+ krb5_data_p = ctypes.POINTER(_krb5_data)
+ krb5_error = ctypes.c_int32
+
+ krb5_init_context = LIBKRB5.krb5_init_context
+ krb5_init_context.argtypes = (ctypes.POINTER(krb5_context), )
+ krb5_init_context.restype = krb5_error
+ krb5_init_context.errcheck = krb5_errcheck
+
+ krb5_free_context = LIBKRB5.krb5_free_context
+ krb5_free_context.argtypes = (krb5_context, )
+ krb5_free_context.retval = None
+
+ krb5_free_principal = LIBKRB5.krb5_free_principal
+ krb5_free_principal.argtypes = (krb5_context, krb5_principal)
+ krb5_free_principal.retval = None
+
+ krb5_free_data_contents = LIBKRB5.krb5_free_data_contents
+ krb5_free_data_contents.argtypes = (krb5_context, krb5_data_p)
+ krb5_free_data_contents.retval = None
+
+ krb5_cc_default = LIBKRB5.krb5_cc_default
+ krb5_cc_default.argtypes = (krb5_context, ctypes.POINTER(krb5_ccache), )
+ krb5_cc_default.restype = krb5_error
+ krb5_cc_default.errcheck = krb5_errcheck
+
+ krb5_cc_close = LIBKRB5.krb5_cc_close
+ krb5_cc_close.argtypes = (krb5_context, krb5_ccache, )
+ krb5_cc_close.retval = krb5_error
+ krb5_cc_close.errcheck = krb5_errcheck
+
+ krb5_parse_name = LIBKRB5.krb5_parse_name
+ krb5_parse_name.argtypes = (krb5_context, ctypes.c_char_p,
+ ctypes.POINTER(krb5_principal), )
+ krb5_parse_name.retval = krb5_error
+ krb5_parse_name.errcheck = krb5_errcheck
+
+ krb5_cc_set_config = LIBKRB5.krb5_cc_set_config
+ krb5_cc_set_config.argtypes = (krb5_context, krb5_ccache, krb5_principal,
+ ctypes.c_char_p, krb5_data_p, )
+ krb5_cc_set_config.retval = krb5_error
+ krb5_cc_set_config.errcheck = krb5_errcheck
+
+ krb5_cc_get_config = LIBKRB5.krb5_cc_get_config
+ krb5_cc_get_config.argtypes = (krb5_context, krb5_ccache, krb5_principal,
+ ctypes.c_char_p, krb5_data_p, )
+ krb5_cc_get_config.retval = krb5_error
+ krb5_cc_get_config.errcheck = krb5_errcheck
+
+class session_store:
+ def __init__(self, name='X-IPA-Session-Cookie'):
+ self.__context = None
+ if isinstance(LIBKRB5, Exception): # pragma: no cover
+ raise LIBKRB5
+ context = krb5_context()
+ krb5_init_context(ctypes.byref(context))
+ self.__context = context
+
+ self._hidden_cred_name = name
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ if self.__context:
+ krb5_free_context(self.__context)
+ self.__context = None
+
+ def __del__(self):
+ self.__exit__(None, None, None)
+
+ def store_cookie(self, client, value):
+ """
+ Stores the session cookie in a hidden ccache entry.
+ """
+ assert isinstance(client, six.string_types)
+ assert isinstance(value, bytes)
+
+ principal = ccache = None
+
+ try:
+ principal = krb5_principal()
+ krb5_parse_name(self.__context, ctypes.c_char_p(client),
+ ctypes.byref(principal))
+
+ ccache = krb5_ccache()
+ krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+ buf = ctypes.create_string_buffer(value)
+ data = _krb5_data()
+ data.data = buf.value
+ data.length = len(buf)
+ krb5_cc_set_config(self.__context, ccache, principal,
+ self._hidden_cred_name, ctypes.byref(data))
+
+ finally:
+ if principal:
+ krb5_free_principal(self.__context, principal)
+ if ccache:
+ krb5_cc_close(self.__context, ccache)
+
+ def get_cookie(self, client):
+ """
+ Gets the session cookie in a hidden ccache entry.
+ """
+ assert isinstance(client, six.string_types)
+
+ principal = ccache = data = None
+
+ try:
+ principal = krb5_principal()
+ krb5_parse_name(self.__context, ctypes.c_char_p(client),
+ ctypes.byref(principal))
+
+ ccache = krb5_ccache()
+ krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+ data = _krb5_data()
+ krb5_cc_get_config(self.__context, ccache, principal,
+ self._hidden_cred_name, ctypes.byref(data))
+
+ return str(data.data)
+
+ finally:
+ if principal:
+ krb5_free_principal(self.__context, principal)
+ if ccache:
+ krb5_cc_close(self.__context, ccache)
+ if data:
+ krb5_free_data_contents(self.__context, data)
+
+ def remove_cookie(self, client):
+ """
+ Stores the session cookie in a hidden ccache entry.
+ """
+ assert isinstance(client, six.string_types)
+
+ principal = ccache = None
+
+ try:
+ principal = krb5_principal()
+ krb5_parse_name(self.__context, ctypes.c_char_p(client),
+ ctypes.byref(principal))
+
+ ccache = krb5_ccache()
+ krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+ krb5_cc_set_config(self.__context, ccache, principal,
+ self._hidden_cred_name, None)
+
+ finally:
+ if principal:
+ krb5_free_principal(self.__context, principal)
+ if ccache:
+ krb5_cc_close(self.__context, ccache)
+