Blame SOURCES/Add-tests-for-KCM-ccache-type.patch

afd354
From 7ab0bbac058d2b82aa3432759c600b22012f8afe Mon Sep 17 00:00:00 2001
afd354
From: Greg Hudson <ghudson@mit.edu>
afd354
Date: Thu, 22 Nov 2018 00:27:35 -0500
afd354
Subject: [PATCH] Add tests for KCM ccache type
afd354
afd354
Using a trivial Python implementation of a KCM server, run the
afd354
t_ccache.py tests against the KCM ccache type.
afd354
afd354
(cherry picked from commit f0bcb86131e385b2603ccf0f3c7d65aa3891b220)
afd354
(cherry picked from commit 5ecbe8d3ab4f53c0923a0442273bf18a9ff04fd5)
afd354
---
afd354
 src/tests/kcmserver.py | 246 +++++++++++++++++++++++++++++++++++++++++
afd354
 src/tests/t_ccache.py  |   9 +-
afd354
 2 files changed, 254 insertions(+), 1 deletion(-)
afd354
 create mode 100644 src/tests/kcmserver.py
afd354
afd354
diff --git a/src/tests/kcmserver.py b/src/tests/kcmserver.py
afd354
new file mode 100644
afd354
index 000000000..57432e5a7
afd354
--- /dev/null
afd354
+++ b/src/tests/kcmserver.py
afd354
@@ -0,0 +1,246 @@
afd354
+# This is a simple KCM test server, used to exercise the KCM ccache
afd354
+# client code.  It will generally throw an uncaught exception if the
afd354
+# client sends anything unexpected, so is unsuitable for production.
afd354
+# (It also imposes no namespace or access constraints, and blocks
afd354
+# while reading requests and writing responses.)
afd354
+
afd354
+# This code knows nothing about how to marshal and unmarshal principal
afd354
+# names and credentials as is required in the KCM protocol; instead,
afd354
+# it just remembers the marshalled forms and replays them to the
afd354
+# client when asked.  This works because marshalled creds and
afd354
+# principal names are always the last part of marshalled request
afd354
+# arguments, and because we don't need to implement remove_cred (which
afd354
+# would need to know how to match a cred tag against previously stored
afd354
+# credentials).
afd354
+
afd354
+# The following code is useful for debugging if anything appears to be
afd354
+# going wrong in the server, since daemon output is generally not
afd354
+# visible in Python test scripts.
afd354
+#
afd354
+# import sys, traceback
afd354
+# def ehook(etype, value, tb):
afd354
+#     with open('/tmp/exception', 'w') as f:
afd354
+#         traceback.print_exception(etype, value, tb, file=f)
afd354
+# sys.excepthook = ehook
afd354
+
afd354
+import select
afd354
+import socket
afd354
+import struct
afd354
+import sys
afd354
+
afd354
+caches = {}
afd354
+cache_uuidmap = {}
afd354
+defname = b'default'
afd354
+next_unique = 1
afd354
+next_uuid = 1
afd354
+
afd354
+class KCMOpcodes(object):
afd354
+    GEN_NEW = 3
afd354
+    INITIALIZE = 4
afd354
+    DESTROY = 5
afd354
+    STORE = 6
afd354
+    GET_PRINCIPAL = 8
afd354
+    GET_CRED_UUID_LIST = 9
afd354
+    GET_CRED_BY_UUID = 10
afd354
+    REMOVE_CRED = 11
afd354
+    GET_CACHE_UUID_LIST = 18
afd354
+    GET_CACHE_BY_UUID = 19
afd354
+    GET_DEFAULT_CACHE = 20
afd354
+    SET_DEFAULT_CACHE = 21
afd354
+    GET_KDC_OFFSET = 22
afd354
+    SET_KDC_OFFSET = 23
afd354
+
afd354
+
afd354
+class KRB5Errors(object):
afd354
+    KRB5_CC_END = -1765328242
afd354
+    KRB5_CC_NOSUPP = -1765328137
afd354
+    KRB5_FCC_NOFILE = -1765328189
afd354
+
afd354
+
afd354
+def make_uuid():
afd354
+    global next_uuid
afd354
+    uuid = bytes(12) + struct.pack('>L', next_uuid)
afd354
+    next_uuid = next_uuid + 1
afd354
+    return uuid
afd354
+
afd354
+
afd354
+class Cache(object):
afd354
+    def __init__(self, name):
afd354
+        self.name = name
afd354
+        self.princ = None
afd354
+        self.uuid = make_uuid()
afd354
+        self.cred_uuids = []
afd354
+        self.creds = {}
afd354
+        self.time_offset = 0
afd354
+
afd354
+
afd354
+def get_cache(name):
afd354
+    if name in caches:
afd354
+        return caches[name]
afd354
+    cache = Cache(name)
afd354
+    caches[name] = cache
afd354
+    cache_uuidmap[cache.uuid] = cache
afd354
+    return cache
afd354
+
afd354
+
afd354
+def unmarshal_name(argbytes):
afd354
+    offset = argbytes.find(b'\0')
afd354
+    return argbytes[0:offset], argbytes[offset+1:]
afd354
+
afd354
+
afd354
+def op_gen_new(argbytes):
afd354
+    # Does not actually check for uniqueness.
afd354
+    global next_unique
afd354
+    name = b'unique' + str(next_unique).encode('ascii')
afd354
+    next_unique += 1
afd354
+    return 0, name + b'\0'
afd354
+
afd354
+
afd354
+def op_initialize(argbytes):
afd354
+    name, princ = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    cache.princ = princ
afd354
+    cache.cred_uuids = []
afd354
+    cache.creds = {}
afd354
+    cache.time_offset = 0
afd354
+    return 0, b''
afd354
+
afd354
+
afd354
+def op_destroy(argbytes):
afd354
+    name, rest = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    del cache_uuidmap[cache.uuid]
afd354
+    del caches[name]
afd354
+    return 0, b''
afd354
+
afd354
+
afd354
+def op_store(argbytes):
afd354
+    name, cred = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    uuid = make_uuid()
afd354
+    cache.creds[uuid] = cred
afd354
+    cache.cred_uuids.append(uuid)
afd354
+    return 0, b''
afd354
+
afd354
+
afd354
+def op_get_principal(argbytes):
afd354
+    name, rest = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    if cache.princ is None:
afd354
+        return KRB5Errors.KRB5_FCC_NOFILE, b''
afd354
+    return 0, cache.princ + b'\0'
afd354
+
afd354
+
afd354
+def op_get_cred_uuid_list(argbytes):
afd354
+    name, rest = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    return 0, b''.join(cache.cred_uuids)
afd354
+
afd354
+
afd354
+def op_get_cred_by_uuid(argbytes):
afd354
+    name, uuid = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    if uuid not in cache.creds:
afd354
+        return KRB5Errors.KRB5_CC_END, b''
afd354
+    return 0, cache.creds[uuid]
afd354
+
afd354
+
afd354
+def op_remove_cred(argbytes):
afd354
+    return KRB5Errors.KRB5_CC_NOSUPP, b''
afd354
+
afd354
+
afd354
+def op_get_cache_uuid_list(argbytes):
afd354
+    return 0, b''.join(cache_uuidmap.keys())
afd354
+
afd354
+
afd354
+def op_get_cache_by_uuid(argbytes):
afd354
+    uuid = argbytes
afd354
+    if uuid not in cache_uuidmap:
afd354
+        return KRB5Errors.KRB5_CC_END, b''
afd354
+    return 0, cache_uuidmap[uuid].name + b'\0'
afd354
+
afd354
+
afd354
+def op_get_default_cache(argbytes):
afd354
+    return 0, defname + b'\0'
afd354
+
afd354
+
afd354
+def op_set_default_cache(argbytes):
afd354
+    global defname
afd354
+    defname, rest = unmarshal_name(argbytes)
afd354
+    return 0, b''
afd354
+
afd354
+
afd354
+def op_get_kdc_offset(argbytes):
afd354
+    name, rest = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    return 0, struct.pack('>l', cache.time_offset)
afd354
+
afd354
+
afd354
+def op_set_kdc_offset(argbytes):
afd354
+    name, obytes = unmarshal_name(argbytes)
afd354
+    cache = get_cache(name)
afd354
+    cache.time_offset, = struct.unpack('>l', obytes)
afd354
+    return 0, b''
afd354
+
afd354
+
afd354
+ophandlers = {
afd354
+    KCMOpcodes.GEN_NEW : op_gen_new,
afd354
+    KCMOpcodes.INITIALIZE : op_initialize,
afd354
+    KCMOpcodes.DESTROY : op_destroy,
afd354
+    KCMOpcodes.STORE : op_store,
afd354
+    KCMOpcodes.GET_PRINCIPAL : op_get_principal,
afd354
+    KCMOpcodes.GET_CRED_UUID_LIST : op_get_cred_uuid_list,
afd354
+    KCMOpcodes.GET_CRED_BY_UUID : op_get_cred_by_uuid,
afd354
+    KCMOpcodes.REMOVE_CRED : op_remove_cred,
afd354
+    KCMOpcodes.GET_CACHE_UUID_LIST : op_get_cache_uuid_list,
afd354
+    KCMOpcodes.GET_CACHE_BY_UUID : op_get_cache_by_uuid,
afd354
+    KCMOpcodes.GET_DEFAULT_CACHE : op_get_default_cache,
afd354
+    KCMOpcodes.SET_DEFAULT_CACHE : op_set_default_cache,
afd354
+    KCMOpcodes.GET_KDC_OFFSET : op_get_kdc_offset,
afd354
+    KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset
afd354
+}
afd354
+
afd354
+# Read and respond to a request from the socket s.
afd354
+def service_request(s):
afd354
+    lenbytes = b''
afd354
+    while len(lenbytes) < 4:
afd354
+        lenbytes += s.recv(4 - len(lenbytes))
afd354
+        if lenbytes == b'':
afd354
+                return False
afd354
+
afd354
+    reqlen, = struct.unpack('>L', lenbytes)
afd354
+    req = b''
afd354
+    while len(req) < reqlen:
afd354
+        req += s.recv(reqlen - len(req))
afd354
+
afd354
+    majver, minver, op = struct.unpack('>BBH', req[:4])
afd354
+    argbytes = req[4:]
afd354
+    code, payload = ophandlers[op](argbytes)
afd354
+
afd354
+    # The KCM response is the code (4 bytes) and the response payload.
afd354
+    # The Heimdal IPC response is the length of the KCM response (4
afd354
+    # bytes), a status code which is essentially always 0 (4 bytes),
afd354
+    # and the KCM response.
afd354
+    kcm_response = struct.pack('>l', code) + payload
afd354
+    hipc_response = struct.pack('>LL', len(kcm_response), 0) + kcm_response
afd354
+    s.sendall(hipc_response)
afd354
+    return True
afd354
+
afd354
+
afd354
+server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
afd354
+server.bind(sys.argv[1])
afd354
+server.listen(5)
afd354
+select_input = [server,]
afd354
+sys.stderr.write('starting...\n')
afd354
+sys.stderr.flush()
afd354
+
afd354
+while True:
afd354
+    iready, oready, xready = select.select(select_input, [], [])
afd354
+    for s in iready:
afd354
+        if s == server:
afd354
+            client, addr = server.accept()
afd354
+            select_input.append(client)
afd354
+        else:
afd354
+            if not service_request(s):
afd354
+                select_input.remove(s)
afd354
+                s.close()
afd354
diff --git a/src/tests/t_ccache.py b/src/tests/t_ccache.py
afd354
index fcf1a611e..66804afa5 100755
afd354
--- a/src/tests/t_ccache.py
afd354
+++ b/src/tests/t_ccache.py
afd354
@@ -22,7 +22,10 @@
afd354
 
afd354
 from k5test import *
afd354
 
afd354
-realm = K5Realm(create_host=False)
afd354
+kcm_socket_path = os.path.join(os.getcwd(), 'testdir', 'kcm')
afd354
+conf = {'libdefaults': {'kcm_socket': kcm_socket_path,
afd354
+                        'kcm_mach_service': '-'}}
afd354
+realm = K5Realm(create_host=False, krb5_conf=conf)
afd354
 
afd354
 keyctl = which('keyctl')
afd354
 out = realm.run([klist, '-c', 'KEYRING:process:abcd'], expected_code=1)
afd354
@@ -122,6 +125,10 @@ def collection_test(realm, ccname):
afd354
 
afd354
 
afd354
 collection_test(realm, 'DIR:' + os.path.join(realm.testdir, 'cc'))
afd354
+kcmserver_path = os.path.join(srctop, 'tests', 'kcmserver.py')
afd354
+realm.start_server([sys.executable, kcmserver_path, kcm_socket_path],
afd354
+                   'starting...')
afd354
+collection_test(realm, 'KCM:')
afd354
 if test_keyring:
afd354
     def cleanup_keyring(anchor, name):
afd354
         out = realm.run(['keyctl', 'list', anchor])