1*7f2fe78bSCy Schubert# This is a simple KCM test server, used to exercise the KCM ccache 2*7f2fe78bSCy Schubert# client code. It will generally throw an uncaught exception if the 3*7f2fe78bSCy Schubert# client sends anything unexpected, so is unsuitable for production. 4*7f2fe78bSCy Schubert# (It also imposes no namespace or access constraints, and blocks 5*7f2fe78bSCy Schubert# while reading requests and writing responses.) 6*7f2fe78bSCy Schubert 7*7f2fe78bSCy Schubert# This code knows nothing about how to marshal and unmarshal principal 8*7f2fe78bSCy Schubert# names and credentials as is required in the KCM protocol; instead, 9*7f2fe78bSCy Schubert# it just remembers the marshalled forms and replays them to the 10*7f2fe78bSCy Schubert# client when asked. This works because marshalled creds and 11*7f2fe78bSCy Schubert# principal names are always the last part of marshalled request 12*7f2fe78bSCy Schubert# arguments, and because we don't need to implement remove_cred (which 13*7f2fe78bSCy Schubert# would need to know how to match a cred tag against previously stored 14*7f2fe78bSCy Schubert# credentials). 15*7f2fe78bSCy Schubert 16*7f2fe78bSCy Schubert# The following code is useful for debugging if anything appears to be 17*7f2fe78bSCy Schubert# going wrong in the server, since daemon output is generally not 18*7f2fe78bSCy Schubert# visible in Python test scripts. 19*7f2fe78bSCy Schubert# 20*7f2fe78bSCy Schubert# import sys, traceback 21*7f2fe78bSCy Schubert# def ehook(etype, value, tb): 22*7f2fe78bSCy Schubert# with open('/tmp/exception', 'w') as f: 23*7f2fe78bSCy Schubert# traceback.print_exception(etype, value, tb, file=f) 24*7f2fe78bSCy Schubert# sys.excepthook = ehook 25*7f2fe78bSCy Schubert 26*7f2fe78bSCy Schubertimport optparse 27*7f2fe78bSCy Schubertimport select 28*7f2fe78bSCy Schubertimport socket 29*7f2fe78bSCy Schubertimport struct 30*7f2fe78bSCy Schubertimport sys 31*7f2fe78bSCy Schubert 32*7f2fe78bSCy Schubertcaches = {} 33*7f2fe78bSCy Schubertcache_uuidmap = {} 34*7f2fe78bSCy Schubertdefname = b'default' 35*7f2fe78bSCy Schubertnext_unique = 1 36*7f2fe78bSCy Schubertnext_uuid = 1 37*7f2fe78bSCy Schubert 38*7f2fe78bSCy Schubertclass KCMOpcodes(object): 39*7f2fe78bSCy Schubert GEN_NEW = 3 40*7f2fe78bSCy Schubert INITIALIZE = 4 41*7f2fe78bSCy Schubert DESTROY = 5 42*7f2fe78bSCy Schubert STORE = 6 43*7f2fe78bSCy Schubert RETRIEVE = 7 44*7f2fe78bSCy Schubert GET_PRINCIPAL = 8 45*7f2fe78bSCy Schubert GET_CRED_UUID_LIST = 9 46*7f2fe78bSCy Schubert GET_CRED_BY_UUID = 10 47*7f2fe78bSCy Schubert REMOVE_CRED = 11 48*7f2fe78bSCy Schubert GET_CACHE_UUID_LIST = 18 49*7f2fe78bSCy Schubert GET_CACHE_BY_UUID = 19 50*7f2fe78bSCy Schubert GET_DEFAULT_CACHE = 20 51*7f2fe78bSCy Schubert SET_DEFAULT_CACHE = 21 52*7f2fe78bSCy Schubert GET_KDC_OFFSET = 22 53*7f2fe78bSCy Schubert SET_KDC_OFFSET = 23 54*7f2fe78bSCy Schubert GET_CRED_LIST = 13001 55*7f2fe78bSCy Schubert REPLACE = 13002 56*7f2fe78bSCy Schubert 57*7f2fe78bSCy Schubert 58*7f2fe78bSCy Schubertclass KRB5Errors(object): 59*7f2fe78bSCy Schubert KRB5_CC_NOTFOUND = -1765328243 60*7f2fe78bSCy Schubert KRB5_CC_END = -1765328242 61*7f2fe78bSCy Schubert KRB5_CC_NOSUPP = -1765328137 62*7f2fe78bSCy Schubert KRB5_FCC_NOFILE = -1765328189 63*7f2fe78bSCy Schubert KRB5_FCC_INTERNAL = -1765328188 64*7f2fe78bSCy Schubert 65*7f2fe78bSCy Schubert 66*7f2fe78bSCy Schubertdef make_uuid(): 67*7f2fe78bSCy Schubert global next_uuid 68*7f2fe78bSCy Schubert uuid = bytes(12) + struct.pack('>L', next_uuid) 69*7f2fe78bSCy Schubert next_uuid = next_uuid + 1 70*7f2fe78bSCy Schubert return uuid 71*7f2fe78bSCy Schubert 72*7f2fe78bSCy Schubert 73*7f2fe78bSCy Schubertclass Cache(object): 74*7f2fe78bSCy Schubert def __init__(self, name): 75*7f2fe78bSCy Schubert self.name = name 76*7f2fe78bSCy Schubert self.princ = None 77*7f2fe78bSCy Schubert self.uuid = make_uuid() 78*7f2fe78bSCy Schubert self.cred_uuids = [] 79*7f2fe78bSCy Schubert self.creds = {} 80*7f2fe78bSCy Schubert self.time_offset = 0 81*7f2fe78bSCy Schubert 82*7f2fe78bSCy Schubert 83*7f2fe78bSCy Schubertdef get_cache(name): 84*7f2fe78bSCy Schubert if name in caches: 85*7f2fe78bSCy Schubert return caches[name] 86*7f2fe78bSCy Schubert cache = Cache(name) 87*7f2fe78bSCy Schubert caches[name] = cache 88*7f2fe78bSCy Schubert cache_uuidmap[cache.uuid] = cache 89*7f2fe78bSCy Schubert return cache 90*7f2fe78bSCy Schubert 91*7f2fe78bSCy Schubert 92*7f2fe78bSCy Schubertdef unmarshal_name(argbytes): 93*7f2fe78bSCy Schubert offset = argbytes.find(b'\0') 94*7f2fe78bSCy Schubert return argbytes[0:offset], argbytes[offset+1:] 95*7f2fe78bSCy Schubert 96*7f2fe78bSCy Schubert 97*7f2fe78bSCy Schubert# Find the bounds of a marshalled principal, returning it and the 98*7f2fe78bSCy Schubert# remainder of argbytes. 99*7f2fe78bSCy Schubertdef extract_princ(argbytes): 100*7f2fe78bSCy Schubert ncomps, rlen = struct.unpack('>LL', argbytes[4:12]) 101*7f2fe78bSCy Schubert pos = 12 + rlen 102*7f2fe78bSCy Schubert for i in range(ncomps): 103*7f2fe78bSCy Schubert clen, = struct.unpack('>L', argbytes[pos:pos+4]) 104*7f2fe78bSCy Schubert pos += 4 + clen 105*7f2fe78bSCy Schubert return argbytes[0:pos], argbytes[pos:] 106*7f2fe78bSCy Schubert 107*7f2fe78bSCy Schubert 108*7f2fe78bSCy Schubert# Return true if the marshalled principals p1 and p2 name the same 109*7f2fe78bSCy Schubert# principal. 110*7f2fe78bSCy Schubertdef princ_eq(p1, p2): 111*7f2fe78bSCy Schubert # Ignore the name-types at bytes 0..3. The remaining bytes should 112*7f2fe78bSCy Schubert # be identical if the principals are the same. 113*7f2fe78bSCy Schubert return p1[4:] == p2[4:] 114*7f2fe78bSCy Schubert 115*7f2fe78bSCy Schubert 116*7f2fe78bSCy Schubertdef op_gen_new(argbytes): 117*7f2fe78bSCy Schubert # Does not actually check for uniqueness. 118*7f2fe78bSCy Schubert global next_unique 119*7f2fe78bSCy Schubert name = b'unique' + str(next_unique).encode('ascii') 120*7f2fe78bSCy Schubert next_unique += 1 121*7f2fe78bSCy Schubert return 0, name + b'\0' 122*7f2fe78bSCy Schubert 123*7f2fe78bSCy Schubert 124*7f2fe78bSCy Schubertdef op_initialize(argbytes): 125*7f2fe78bSCy Schubert name, princ = unmarshal_name(argbytes) 126*7f2fe78bSCy Schubert cache = get_cache(name) 127*7f2fe78bSCy Schubert cache.princ = princ 128*7f2fe78bSCy Schubert cache.cred_uuids = [] 129*7f2fe78bSCy Schubert cache.creds = {} 130*7f2fe78bSCy Schubert cache.time_offset = 0 131*7f2fe78bSCy Schubert return 0, b'' 132*7f2fe78bSCy Schubert 133*7f2fe78bSCy Schubert 134*7f2fe78bSCy Schubertdef op_destroy(argbytes): 135*7f2fe78bSCy Schubert name, rest = unmarshal_name(argbytes) 136*7f2fe78bSCy Schubert cache = get_cache(name) 137*7f2fe78bSCy Schubert del cache_uuidmap[cache.uuid] 138*7f2fe78bSCy Schubert del caches[name] 139*7f2fe78bSCy Schubert return 0, b'' 140*7f2fe78bSCy Schubert 141*7f2fe78bSCy Schubert 142*7f2fe78bSCy Schubertdef op_store(argbytes): 143*7f2fe78bSCy Schubert name, cred = unmarshal_name(argbytes) 144*7f2fe78bSCy Schubert cache = get_cache(name) 145*7f2fe78bSCy Schubert uuid = make_uuid() 146*7f2fe78bSCy Schubert cache.creds[uuid] = cred 147*7f2fe78bSCy Schubert cache.cred_uuids.append(uuid) 148*7f2fe78bSCy Schubert return 0, b'' 149*7f2fe78bSCy Schubert 150*7f2fe78bSCy Schubert 151*7f2fe78bSCy Schubertdef op_retrieve(argbytes): 152*7f2fe78bSCy Schubert name, rest = unmarshal_name(argbytes) 153*7f2fe78bSCy Schubert # Ignore the flags at rest[0:4] and the header at rest[4:8]. 154*7f2fe78bSCy Schubert # Assume there are client and server creds in the tag and match 155*7f2fe78bSCy Schubert # only against them. 156*7f2fe78bSCy Schubert cprinc, rest = extract_princ(rest[8:]) 157*7f2fe78bSCy Schubert sprinc, rest = extract_princ(rest) 158*7f2fe78bSCy Schubert cache = get_cache(name) 159*7f2fe78bSCy Schubert for cred in (cache.creds[u] for u in cache.cred_uuids): 160*7f2fe78bSCy Schubert cred_cprinc, rest = extract_princ(cred) 161*7f2fe78bSCy Schubert cred_sprinc, rest = extract_princ(rest) 162*7f2fe78bSCy Schubert if princ_eq(cred_cprinc, cprinc) and princ_eq(cred_sprinc, sprinc): 163*7f2fe78bSCy Schubert return 0, cred 164*7f2fe78bSCy Schubert return KRB5Errors.KRB5_CC_NOTFOUND, b'' 165*7f2fe78bSCy Schubert 166*7f2fe78bSCy Schubert 167*7f2fe78bSCy Schubertdef op_get_principal(argbytes): 168*7f2fe78bSCy Schubert name, rest = unmarshal_name(argbytes) 169*7f2fe78bSCy Schubert cache = get_cache(name) 170*7f2fe78bSCy Schubert if cache.princ is None: 171*7f2fe78bSCy Schubert return KRB5Errors.KRB5_FCC_NOFILE, b'' 172*7f2fe78bSCy Schubert return 0, cache.princ + b'\0' 173*7f2fe78bSCy Schubert 174*7f2fe78bSCy Schubert 175*7f2fe78bSCy Schubertdef op_get_cred_uuid_list(argbytes): 176*7f2fe78bSCy Schubert name, rest = unmarshal_name(argbytes) 177*7f2fe78bSCy Schubert cache = get_cache(name) 178*7f2fe78bSCy Schubert return 0, b''.join(cache.cred_uuids) 179*7f2fe78bSCy Schubert 180*7f2fe78bSCy Schubert 181*7f2fe78bSCy Schubertdef op_get_cred_by_uuid(argbytes): 182*7f2fe78bSCy Schubert name, uuid = unmarshal_name(argbytes) 183*7f2fe78bSCy Schubert cache = get_cache(name) 184*7f2fe78bSCy Schubert if uuid not in cache.creds: 185*7f2fe78bSCy Schubert return KRB5Errors.KRB5_CC_END, b'' 186*7f2fe78bSCy Schubert return 0, cache.creds[uuid] 187*7f2fe78bSCy Schubert 188*7f2fe78bSCy Schubert 189*7f2fe78bSCy Schubertdef op_remove_cred(argbytes): 190*7f2fe78bSCy Schubert return KRB5Errors.KRB5_CC_NOSUPP, b'' 191*7f2fe78bSCy Schubert 192*7f2fe78bSCy Schubert 193*7f2fe78bSCy Schubertdef op_get_cache_uuid_list(argbytes): 194*7f2fe78bSCy Schubert return 0, b''.join(cache_uuidmap.keys()) 195*7f2fe78bSCy Schubert 196*7f2fe78bSCy Schubert 197*7f2fe78bSCy Schubertdef op_get_cache_by_uuid(argbytes): 198*7f2fe78bSCy Schubert uuid = argbytes 199*7f2fe78bSCy Schubert if uuid not in cache_uuidmap: 200*7f2fe78bSCy Schubert return KRB5Errors.KRB5_CC_END, b'' 201*7f2fe78bSCy Schubert return 0, cache_uuidmap[uuid].name + b'\0' 202*7f2fe78bSCy Schubert 203*7f2fe78bSCy Schubert 204*7f2fe78bSCy Schubertdef op_get_default_cache(argbytes): 205*7f2fe78bSCy Schubert return 0, defname + b'\0' 206*7f2fe78bSCy Schubert 207*7f2fe78bSCy Schubert 208*7f2fe78bSCy Schubertdef op_set_default_cache(argbytes): 209*7f2fe78bSCy Schubert global defname 210*7f2fe78bSCy Schubert defname, rest = unmarshal_name(argbytes) 211*7f2fe78bSCy Schubert return 0, b'' 212*7f2fe78bSCy Schubert 213*7f2fe78bSCy Schubert 214*7f2fe78bSCy Schubertdef op_get_kdc_offset(argbytes): 215*7f2fe78bSCy Schubert name, rest = unmarshal_name(argbytes) 216*7f2fe78bSCy Schubert cache = get_cache(name) 217*7f2fe78bSCy Schubert return 0, struct.pack('>l', cache.time_offset) 218*7f2fe78bSCy Schubert 219*7f2fe78bSCy Schubert 220*7f2fe78bSCy Schubertdef op_set_kdc_offset(argbytes): 221*7f2fe78bSCy Schubert name, obytes = unmarshal_name(argbytes) 222*7f2fe78bSCy Schubert cache = get_cache(name) 223*7f2fe78bSCy Schubert cache.time_offset, = struct.unpack('>l', obytes) 224*7f2fe78bSCy Schubert return 0, b'' 225*7f2fe78bSCy Schubert 226*7f2fe78bSCy Schubert 227*7f2fe78bSCy Schubertdef op_get_cred_list(argbytes): 228*7f2fe78bSCy Schubert name, rest = unmarshal_name(argbytes) 229*7f2fe78bSCy Schubert cache = get_cache(name) 230*7f2fe78bSCy Schubert creds = [cache.creds[u] for u in cache.cred_uuids] 231*7f2fe78bSCy Schubert return 0, (struct.pack('>L', len(creds)) + 232*7f2fe78bSCy Schubert b''.join(struct.pack('>L', len(c)) + c for c in creds)) 233*7f2fe78bSCy Schubert 234*7f2fe78bSCy Schubert 235*7f2fe78bSCy Schubertdef op_replace(argbytes): 236*7f2fe78bSCy Schubert name, rest = unmarshal_name(argbytes) 237*7f2fe78bSCy Schubert offset, = struct.unpack('>L', rest[0:4]) 238*7f2fe78bSCy Schubert princ, rest = extract_princ(rest[4:]) 239*7f2fe78bSCy Schubert ncreds, = struct.unpack('>L', rest[0:4]) 240*7f2fe78bSCy Schubert rest = rest[4:] 241*7f2fe78bSCy Schubert creds = [] 242*7f2fe78bSCy Schubert for i in range(ncreds): 243*7f2fe78bSCy Schubert len, = struct.unpack('>L', rest[0:4]) 244*7f2fe78bSCy Schubert creds.append(rest[4:4+len]) 245*7f2fe78bSCy Schubert rest = rest[4+len:] 246*7f2fe78bSCy Schubert 247*7f2fe78bSCy Schubert cache = get_cache(name) 248*7f2fe78bSCy Schubert cache.princ = princ 249*7f2fe78bSCy Schubert cache.cred_uuids = [] 250*7f2fe78bSCy Schubert cache.creds = {} 251*7f2fe78bSCy Schubert cache.time_offset = offset 252*7f2fe78bSCy Schubert for i in range(ncreds): 253*7f2fe78bSCy Schubert uuid = make_uuid() 254*7f2fe78bSCy Schubert cache.creds[uuid] = creds[i] 255*7f2fe78bSCy Schubert cache.cred_uuids.append(uuid) 256*7f2fe78bSCy Schubert 257*7f2fe78bSCy Schubert return 0, b'' 258*7f2fe78bSCy Schubert 259*7f2fe78bSCy Schubert 260*7f2fe78bSCy Schubertophandlers = { 261*7f2fe78bSCy Schubert KCMOpcodes.GEN_NEW : op_gen_new, 262*7f2fe78bSCy Schubert KCMOpcodes.INITIALIZE : op_initialize, 263*7f2fe78bSCy Schubert KCMOpcodes.DESTROY : op_destroy, 264*7f2fe78bSCy Schubert KCMOpcodes.STORE : op_store, 265*7f2fe78bSCy Schubert KCMOpcodes.RETRIEVE : op_retrieve, 266*7f2fe78bSCy Schubert KCMOpcodes.GET_PRINCIPAL : op_get_principal, 267*7f2fe78bSCy Schubert KCMOpcodes.GET_CRED_UUID_LIST : op_get_cred_uuid_list, 268*7f2fe78bSCy Schubert KCMOpcodes.GET_CRED_BY_UUID : op_get_cred_by_uuid, 269*7f2fe78bSCy Schubert KCMOpcodes.REMOVE_CRED : op_remove_cred, 270*7f2fe78bSCy Schubert KCMOpcodes.GET_CACHE_UUID_LIST : op_get_cache_uuid_list, 271*7f2fe78bSCy Schubert KCMOpcodes.GET_CACHE_BY_UUID : op_get_cache_by_uuid, 272*7f2fe78bSCy Schubert KCMOpcodes.GET_DEFAULT_CACHE : op_get_default_cache, 273*7f2fe78bSCy Schubert KCMOpcodes.SET_DEFAULT_CACHE : op_set_default_cache, 274*7f2fe78bSCy Schubert KCMOpcodes.GET_KDC_OFFSET : op_get_kdc_offset, 275*7f2fe78bSCy Schubert KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset, 276*7f2fe78bSCy Schubert KCMOpcodes.GET_CRED_LIST : op_get_cred_list, 277*7f2fe78bSCy Schubert KCMOpcodes.REPLACE : op_replace 278*7f2fe78bSCy Schubert} 279*7f2fe78bSCy Schubert 280*7f2fe78bSCy Schubert# Read and respond to a request from the socket s. 281*7f2fe78bSCy Schubertdef service_request(s): 282*7f2fe78bSCy Schubert lenbytes = b'' 283*7f2fe78bSCy Schubert while len(lenbytes) < 4: 284*7f2fe78bSCy Schubert lenbytes += s.recv(4 - len(lenbytes)) 285*7f2fe78bSCy Schubert if lenbytes == b'': 286*7f2fe78bSCy Schubert return False 287*7f2fe78bSCy Schubert 288*7f2fe78bSCy Schubert reqlen, = struct.unpack('>L', lenbytes) 289*7f2fe78bSCy Schubert req = b'' 290*7f2fe78bSCy Schubert while len(req) < reqlen: 291*7f2fe78bSCy Schubert req += s.recv(reqlen - len(req)) 292*7f2fe78bSCy Schubert 293*7f2fe78bSCy Schubert majver, minver, op = struct.unpack('>BBH', req[:4]) 294*7f2fe78bSCy Schubert argbytes = req[4:] 295*7f2fe78bSCy Schubert 296*7f2fe78bSCy Schubert if op in ophandlers: 297*7f2fe78bSCy Schubert code, payload = ophandlers[op](argbytes) 298*7f2fe78bSCy Schubert else: 299*7f2fe78bSCy Schubert code, payload = KRB5Errors.KRB5_FCC_INTERNAL, b'' 300*7f2fe78bSCy Schubert 301*7f2fe78bSCy Schubert # The KCM response is the code (4 bytes) and the response payload. 302*7f2fe78bSCy Schubert # The Heimdal IPC response is the length of the KCM response (4 303*7f2fe78bSCy Schubert # bytes), a status code which is essentially always 0 (4 bytes), 304*7f2fe78bSCy Schubert # and the KCM response. 305*7f2fe78bSCy Schubert kcm_response = struct.pack('>l', code) + payload 306*7f2fe78bSCy Schubert hipc_response = struct.pack('>LL', len(kcm_response), 0) + kcm_response 307*7f2fe78bSCy Schubert s.sendall(hipc_response) 308*7f2fe78bSCy Schubert return True 309*7f2fe78bSCy Schubert 310*7f2fe78bSCy Schubertparser = optparse.OptionParser() 311*7f2fe78bSCy Schubertparser.add_option('-f', '--fallback', action='store_true', dest='fallback', 312*7f2fe78bSCy Schubert default=False, 313*7f2fe78bSCy Schubert help='Do not support RETRIEVE/GET_CRED_LIST/REPLACE') 314*7f2fe78bSCy Schubert(options, args) = parser.parse_args() 315*7f2fe78bSCy Schubertif options.fallback: 316*7f2fe78bSCy Schubert del ophandlers[KCMOpcodes.RETRIEVE] 317*7f2fe78bSCy Schubert del ophandlers[KCMOpcodes.GET_CRED_LIST] 318*7f2fe78bSCy Schubert del ophandlers[KCMOpcodes.REPLACE] 319*7f2fe78bSCy Schubert 320*7f2fe78bSCy Schubertserver = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 321*7f2fe78bSCy Schubertserver.bind(args[0]) 322*7f2fe78bSCy Schubertserver.listen(5) 323*7f2fe78bSCy Schubertselect_input = [server,] 324*7f2fe78bSCy Schubertsys.stderr.write('starting...\n') 325*7f2fe78bSCy Schubertsys.stderr.flush() 326*7f2fe78bSCy Schubert 327*7f2fe78bSCy Schubertwhile True: 328*7f2fe78bSCy Schubert iready, oready, xready = select.select(select_input, [], []) 329*7f2fe78bSCy Schubert for s in iready: 330*7f2fe78bSCy Schubert if s == server: 331*7f2fe78bSCy Schubert client, addr = server.accept() 332*7f2fe78bSCy Schubert select_input.append(client) 333*7f2fe78bSCy Schubert else: 334*7f2fe78bSCy Schubert if not service_request(s): 335*7f2fe78bSCy Schubert select_input.remove(s) 336*7f2fe78bSCy Schubert s.close() 337