xref: /freebsd/crypto/krb5/src/tests/kcmserver.py (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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