Path: blob/main/crypto/krb5/src/tests/kcmserver.py
34878 views
# This is a simple KCM test server, used to exercise the KCM ccache1# client code. It will generally throw an uncaught exception if the2# client sends anything unexpected, so is unsuitable for production.3# (It also imposes no namespace or access constraints, and blocks4# while reading requests and writing responses.)56# This code knows nothing about how to marshal and unmarshal principal7# names and credentials as is required in the KCM protocol; instead,8# it just remembers the marshalled forms and replays them to the9# client when asked. This works because marshalled creds and10# principal names are always the last part of marshalled request11# arguments, and because we don't need to implement remove_cred (which12# would need to know how to match a cred tag against previously stored13# credentials).1415# The following code is useful for debugging if anything appears to be16# going wrong in the server, since daemon output is generally not17# visible in Python test scripts.18#19# import sys, traceback20# def ehook(etype, value, tb):21# with open('/tmp/exception', 'w') as f:22# traceback.print_exception(etype, value, tb, file=f)23# sys.excepthook = ehook2425import optparse26import select27import socket28import struct29import sys3031caches = {}32cache_uuidmap = {}33defname = b'default'34next_unique = 135next_uuid = 13637class KCMOpcodes(object):38GEN_NEW = 339INITIALIZE = 440DESTROY = 541STORE = 642RETRIEVE = 743GET_PRINCIPAL = 844GET_CRED_UUID_LIST = 945GET_CRED_BY_UUID = 1046REMOVE_CRED = 1147GET_CACHE_UUID_LIST = 1848GET_CACHE_BY_UUID = 1949GET_DEFAULT_CACHE = 2050SET_DEFAULT_CACHE = 2151GET_KDC_OFFSET = 2252SET_KDC_OFFSET = 2353GET_CRED_LIST = 1300154REPLACE = 13002555657class KRB5Errors(object):58KRB5_CC_NOTFOUND = -176532824359KRB5_CC_END = -176532824260KRB5_CC_NOSUPP = -176532813761KRB5_FCC_NOFILE = -176532818962KRB5_FCC_INTERNAL = -1765328188636465def make_uuid():66global next_uuid67uuid = bytes(12) + struct.pack('>L', next_uuid)68next_uuid = next_uuid + 169return uuid707172class Cache(object):73def __init__(self, name):74self.name = name75self.princ = None76self.uuid = make_uuid()77self.cred_uuids = []78self.creds = {}79self.time_offset = 0808182def get_cache(name):83if name in caches:84return caches[name]85cache = Cache(name)86caches[name] = cache87cache_uuidmap[cache.uuid] = cache88return cache899091def unmarshal_name(argbytes):92offset = argbytes.find(b'\0')93return argbytes[0:offset], argbytes[offset+1:]949596# Find the bounds of a marshalled principal, returning it and the97# remainder of argbytes.98def extract_princ(argbytes):99ncomps, rlen = struct.unpack('>LL', argbytes[4:12])100pos = 12 + rlen101for i in range(ncomps):102clen, = struct.unpack('>L', argbytes[pos:pos+4])103pos += 4 + clen104return argbytes[0:pos], argbytes[pos:]105106107# Return true if the marshalled principals p1 and p2 name the same108# principal.109def princ_eq(p1, p2):110# Ignore the name-types at bytes 0..3. The remaining bytes should111# be identical if the principals are the same.112return p1[4:] == p2[4:]113114115def op_gen_new(argbytes):116# Does not actually check for uniqueness.117global next_unique118name = b'unique' + str(next_unique).encode('ascii')119next_unique += 1120return 0, name + b'\0'121122123def op_initialize(argbytes):124name, princ = unmarshal_name(argbytes)125cache = get_cache(name)126cache.princ = princ127cache.cred_uuids = []128cache.creds = {}129cache.time_offset = 0130return 0, b''131132133def op_destroy(argbytes):134name, rest = unmarshal_name(argbytes)135cache = get_cache(name)136del cache_uuidmap[cache.uuid]137del caches[name]138return 0, b''139140141def op_store(argbytes):142name, cred = unmarshal_name(argbytes)143cache = get_cache(name)144uuid = make_uuid()145cache.creds[uuid] = cred146cache.cred_uuids.append(uuid)147return 0, b''148149150def op_retrieve(argbytes):151name, rest = unmarshal_name(argbytes)152# Ignore the flags at rest[0:4] and the header at rest[4:8].153# Assume there are client and server creds in the tag and match154# only against them.155cprinc, rest = extract_princ(rest[8:])156sprinc, rest = extract_princ(rest)157cache = get_cache(name)158for cred in (cache.creds[u] for u in cache.cred_uuids):159cred_cprinc, rest = extract_princ(cred)160cred_sprinc, rest = extract_princ(rest)161if princ_eq(cred_cprinc, cprinc) and princ_eq(cred_sprinc, sprinc):162return 0, cred163return KRB5Errors.KRB5_CC_NOTFOUND, b''164165166def op_get_principal(argbytes):167name, rest = unmarshal_name(argbytes)168cache = get_cache(name)169if cache.princ is None:170return KRB5Errors.KRB5_FCC_NOFILE, b''171return 0, cache.princ + b'\0'172173174def op_get_cred_uuid_list(argbytes):175name, rest = unmarshal_name(argbytes)176cache = get_cache(name)177return 0, b''.join(cache.cred_uuids)178179180def op_get_cred_by_uuid(argbytes):181name, uuid = unmarshal_name(argbytes)182cache = get_cache(name)183if uuid not in cache.creds:184return KRB5Errors.KRB5_CC_END, b''185return 0, cache.creds[uuid]186187188def op_remove_cred(argbytes):189return KRB5Errors.KRB5_CC_NOSUPP, b''190191192def op_get_cache_uuid_list(argbytes):193return 0, b''.join(cache_uuidmap.keys())194195196def op_get_cache_by_uuid(argbytes):197uuid = argbytes198if uuid not in cache_uuidmap:199return KRB5Errors.KRB5_CC_END, b''200return 0, cache_uuidmap[uuid].name + b'\0'201202203def op_get_default_cache(argbytes):204return 0, defname + b'\0'205206207def op_set_default_cache(argbytes):208global defname209defname, rest = unmarshal_name(argbytes)210return 0, b''211212213def op_get_kdc_offset(argbytes):214name, rest = unmarshal_name(argbytes)215cache = get_cache(name)216return 0, struct.pack('>l', cache.time_offset)217218219def op_set_kdc_offset(argbytes):220name, obytes = unmarshal_name(argbytes)221cache = get_cache(name)222cache.time_offset, = struct.unpack('>l', obytes)223return 0, b''224225226def op_get_cred_list(argbytes):227name, rest = unmarshal_name(argbytes)228cache = get_cache(name)229creds = [cache.creds[u] for u in cache.cred_uuids]230return 0, (struct.pack('>L', len(creds)) +231b''.join(struct.pack('>L', len(c)) + c for c in creds))232233234def op_replace(argbytes):235name, rest = unmarshal_name(argbytes)236offset, = struct.unpack('>L', rest[0:4])237princ, rest = extract_princ(rest[4:])238ncreds, = struct.unpack('>L', rest[0:4])239rest = rest[4:]240creds = []241for i in range(ncreds):242len, = struct.unpack('>L', rest[0:4])243creds.append(rest[4:4+len])244rest = rest[4+len:]245246cache = get_cache(name)247cache.princ = princ248cache.cred_uuids = []249cache.creds = {}250cache.time_offset = offset251for i in range(ncreds):252uuid = make_uuid()253cache.creds[uuid] = creds[i]254cache.cred_uuids.append(uuid)255256return 0, b''257258259ophandlers = {260KCMOpcodes.GEN_NEW : op_gen_new,261KCMOpcodes.INITIALIZE : op_initialize,262KCMOpcodes.DESTROY : op_destroy,263KCMOpcodes.STORE : op_store,264KCMOpcodes.RETRIEVE : op_retrieve,265KCMOpcodes.GET_PRINCIPAL : op_get_principal,266KCMOpcodes.GET_CRED_UUID_LIST : op_get_cred_uuid_list,267KCMOpcodes.GET_CRED_BY_UUID : op_get_cred_by_uuid,268KCMOpcodes.REMOVE_CRED : op_remove_cred,269KCMOpcodes.GET_CACHE_UUID_LIST : op_get_cache_uuid_list,270KCMOpcodes.GET_CACHE_BY_UUID : op_get_cache_by_uuid,271KCMOpcodes.GET_DEFAULT_CACHE : op_get_default_cache,272KCMOpcodes.SET_DEFAULT_CACHE : op_set_default_cache,273KCMOpcodes.GET_KDC_OFFSET : op_get_kdc_offset,274KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset,275KCMOpcodes.GET_CRED_LIST : op_get_cred_list,276KCMOpcodes.REPLACE : op_replace277}278279# Read and respond to a request from the socket s.280def service_request(s):281lenbytes = b''282while len(lenbytes) < 4:283lenbytes += s.recv(4 - len(lenbytes))284if lenbytes == b'':285return False286287reqlen, = struct.unpack('>L', lenbytes)288req = b''289while len(req) < reqlen:290req += s.recv(reqlen - len(req))291292majver, minver, op = struct.unpack('>BBH', req[:4])293argbytes = req[4:]294295if op in ophandlers:296code, payload = ophandlers[op](argbytes)297else:298code, payload = KRB5Errors.KRB5_FCC_INTERNAL, b''299300# The KCM response is the code (4 bytes) and the response payload.301# The Heimdal IPC response is the length of the KCM response (4302# bytes), a status code which is essentially always 0 (4 bytes),303# and the KCM response.304kcm_response = struct.pack('>l', code) + payload305hipc_response = struct.pack('>LL', len(kcm_response), 0) + kcm_response306s.sendall(hipc_response)307return True308309parser = optparse.OptionParser()310parser.add_option('-f', '--fallback', action='store_true', dest='fallback',311default=False,312help='Do not support RETRIEVE/GET_CRED_LIST/REPLACE')313(options, args) = parser.parse_args()314if options.fallback:315del ophandlers[KCMOpcodes.RETRIEVE]316del ophandlers[KCMOpcodes.GET_CRED_LIST]317del ophandlers[KCMOpcodes.REPLACE]318319server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)320server.bind(args[0])321server.listen(5)322select_input = [server,]323sys.stderr.write('starting...\n')324sys.stderr.flush()325326while True:327iready, oready, xready = select.select(select_input, [], [])328for s in iready:329if s == server:330client, addr = server.accept()331select_input.append(client)332else:333if not service_request(s):334select_input.remove(s)335s.close()336337338