1/*
2 * Copyright (c) 2004 - 2007 Kungliga Tekniska H�gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "mit-CredentialsCache.h"
35#include <string.h>
36#include <syslog.h>
37#include <stdlib.h>
38#include <errno.h>
39#include <stdarg.h>
40#include <stdio.h>
41
42#include "heim.h"
43#include "heim-sym.h"
44
45static cc_time_t context_change_time = 0;
46
47void
48update_time(cc_time_t *change_time)
49{
50    cc_time_t now = time(NULL);
51    if (*change_time >= now)
52	*change_time += 1;
53    else
54	*change_time = now;
55}
56
57static cc_int32
58string_release(cc_string_t io_string)
59{
60    free((char *)io_string->data);
61    free(io_string);
62    return ccNoError;
63}
64
65
66cc_string_f string_functions =  {
67    .release = string_release
68};
69
70static cc_string_t
71create_string(const char *string)
72{
73    cc_string_t s;
74    s = mshim_malloc(sizeof(*s));
75    s->functions = &string_functions;
76    s->data = strdup(string);
77    return s;
78}
79
80
81#define	KRB5_CCAPI_TKT_FLG_FORWARDABLE			0x40000000
82#define	KRB5_CCAPI_TKT_FLG_FORWARDED			0x20000000
83#define	KRB5_CCAPI_TKT_FLG_PROXIABLE			0x10000000
84#define	KRB5_CCAPI_TKT_FLG_PROXY			0x08000000
85#define	KRB5_CCAPI_TKT_FLG_MAY_POSTDATE			0x04000000
86#define	KRB5_CCAPI_TKT_FLG_POSTDATED			0x02000000
87#define	KRB5_CCAPI_TKT_FLG_INVALID			0x01000000
88#define	KRB5_CCAPI_TKT_FLG_RENEWABLE			0x00800000
89#define	KRB5_CCAPI_TKT_FLG_INITIAL			0x00400000
90#define	KRB5_CCAPI_TKT_FLG_PRE_AUTH			0x00200000
91#define	KRB5_CCAPI_TKT_FLG_HW_AUTH			0x00100000
92#define	KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED	0x00080000
93#define	KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE		0x00040000
94#define	KRB5_CCAPI_TKT_FLG_ANONYMOUS			0x00020000
95
96static krb5_error_code
97make_cred_from_ccred(krb5_context context,
98		     const cc_credentials_v5_t *incred,
99		     krb5_creds *cred)
100{
101    krb5_error_code ret;
102    unsigned int i;
103
104    memset(cred, 0, sizeof(*cred));
105
106    ret = heim_krb5_parse_name(context, incred->client, &cred->client);
107    if (ret)
108	goto fail;
109
110    ret = heim_krb5_parse_name(context, incred->server, &cred->server);
111    if (ret)
112	goto fail;
113
114    cred->session.keytype = incred->keyblock.type;
115    cred->session.keyvalue.length = incred->keyblock.length;
116    cred->session.keyvalue.data = malloc(incred->keyblock.length);
117    if (cred->session.keyvalue.data == NULL)
118	goto nomem;
119    memcpy(cred->session.keyvalue.data, incred->keyblock.data,
120	   incred->keyblock.length);
121
122    cred->times.authtime = incred->authtime;
123    cred->times.starttime = incred->starttime;
124    cred->times.endtime = incred->endtime;
125    cred->times.renew_till = incred->renew_till;
126
127    ret = heim_krb5_data_copy(&cred->ticket,
128			      incred->ticket.data,
129			      incred->ticket.length);
130    if (ret)
131	goto nomem;
132
133    ret = heim_krb5_data_copy(&cred->second_ticket,
134			      incred->second_ticket.data,
135			      incred->second_ticket.length);
136    if (ret)
137	goto nomem;
138
139    cred->authdata.val = NULL;
140    cred->authdata.len = 0;
141
142    cred->addresses.val = NULL;
143    cred->addresses.len = 0;
144
145    for (i = 0; incred->authdata && incred->authdata[i]; i++)
146	;
147
148    if (i) {
149	cred->authdata.val = calloc(i, sizeof(cred->authdata.val[0]));
150	if (cred->authdata.val == NULL)
151	    goto nomem;
152	cred->authdata.len = i;
153	for (i = 0; i < cred->authdata.len; i++) {
154	    cred->authdata.val[i].ad_type = incred->authdata[i]->type;
155	    ret = heim_krb5_data_copy(&cred->authdata.val[i].ad_data,
156				      incred->authdata[i]->data,
157				      incred->authdata[i]->length);
158	    if (ret)
159		goto nomem;
160	}
161    }
162
163    for (i = 0; incred->addresses && incred->addresses[i]; i++)
164	;
165
166    if (i) {
167	cred->addresses.val = calloc(i, sizeof(cred->addresses.val[0]));
168	if (cred->addresses.val == NULL)
169	    goto nomem;
170	cred->addresses.len = i;
171
172	for (i = 0; i < cred->addresses.len; i++) {
173	    cred->addresses.val[i].addr_type = incred->addresses[i]->type;
174	    ret = heim_krb5_data_copy(&cred->addresses.val[i].address,
175				      incred->addresses[i]->data,
176				      incred->addresses[i]->length);
177	    if (ret)
178		goto nomem;
179	}
180    }
181
182    cred->flags.i = 0;
183    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_FORWARDABLE)
184	cred->flags.b.forwardable = 1;
185    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_FORWARDED)
186	cred->flags.b.forwarded = 1;
187    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PROXIABLE)
188	cred->flags.b.proxiable = 1;
189    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PROXY)
190	cred->flags.b.proxy = 1;
191    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_MAY_POSTDATE)
192	cred->flags.b.may_postdate = 1;
193    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_POSTDATED)
194	cred->flags.b.postdated = 1;
195    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_INVALID)
196	cred->flags.b.invalid = 1;
197    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_RENEWABLE)
198	cred->flags.b.renewable = 1;
199    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_INITIAL)
200	cred->flags.b.initial = 1;
201    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PRE_AUTH)
202	cred->flags.b.pre_authent = 1;
203    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_HW_AUTH)
204	cred->flags.b.hw_authent = 1;
205    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED)
206	cred->flags.b.transited_policy_checked = 1;
207    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE)
208	cred->flags.b.ok_as_delegate = 1;
209    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_ANONYMOUS)
210	cred->flags.b.anonymous = 1;
211
212    return 0;
213
214nomem:
215    ret = ENOMEM;
216    krb5_set_error_message((mit_krb5_context)context, ret, "malloc: out of memory");
217
218fail:
219    heim_krb5_free_cred_contents(context, cred);
220    return ret;
221}
222
223static void
224free_ccred(cc_credentials_v5_t *cred)
225{
226    int i;
227
228    if (cred->addresses) {
229	for (i = 0; cred->addresses[i] != 0; i++) {
230	    if (cred->addresses[i]->data)
231		free(cred->addresses[i]->data);
232	    free(cred->addresses[i]);
233	}
234	free(cred->addresses);
235    }
236    if (cred->server)
237	free(cred->server);
238    if (cred->client)
239	free(cred->client);
240    memset(cred, 0, sizeof(*cred));
241}
242
243static krb5_error_code
244make_ccred_from_cred(krb5_context context,
245		     const krb5_creds *incred,
246		     cc_credentials_v5_t *cred)
247{
248    krb5_error_code ret;
249    int i;
250
251    memset(cred, 0, sizeof(*cred));
252
253    ret = heim_krb5_unparse_name(context, incred->client, &cred->client);
254    if (ret)
255	goto fail;
256
257    ret = heim_krb5_unparse_name(context, incred->server, &cred->server);
258    if (ret)
259	goto fail;
260
261    cred->keyblock.type = incred->session.keytype;
262    cred->keyblock.length = incred->session.keyvalue.length;
263    cred->keyblock.data = incred->session.keyvalue.data;
264
265    cred->authtime = incred->times.authtime;
266    cred->starttime = incred->times.starttime;
267    cred->endtime = incred->times.endtime;
268    cred->renew_till = incred->times.renew_till;
269
270    cred->ticket.length = incred->ticket.length;
271    cred->ticket.data = incred->ticket.data;
272
273    cred->second_ticket.length = incred->second_ticket.length;
274    cred->second_ticket.data = incred->second_ticket.data;
275
276    /* XXX this one should also be filled in */
277    cred->authdata = NULL;
278
279    cred->addresses = calloc(incred->addresses.len + 1,
280			     sizeof(cred->addresses[0]));
281    if (cred->addresses == NULL) {
282
283	ret = ENOMEM;
284	goto fail;
285    }
286
287    for (i = 0; i < incred->addresses.len; i++) {
288	cc_data *addr;
289	addr = malloc(sizeof(*addr));
290	if (addr == NULL) {
291	    ret = ENOMEM;
292	    goto fail;
293	}
294	addr->type = incred->addresses.val[i].addr_type;
295	addr->length = incred->addresses.val[i].address.length;
296	addr->data = malloc(addr->length);
297	if (addr->data == NULL) {
298	    free(addr);
299	    ret = ENOMEM;
300	    goto fail;
301	}
302	memcpy(addr->data, incred->addresses.val[i].address.data,
303	       addr->length);
304	cred->addresses[i] = addr;
305    }
306    cred->addresses[i] = NULL;
307
308    cred->ticket_flags = 0;
309    if (incred->flags.b.forwardable)
310	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_FORWARDABLE;
311    if (incred->flags.b.forwarded)
312	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_FORWARDED;
313    if (incred->flags.b.proxiable)
314	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PROXIABLE;
315    if (incred->flags.b.proxy)
316	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PROXY;
317    if (incred->flags.b.may_postdate)
318	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_MAY_POSTDATE;
319    if (incred->flags.b.postdated)
320	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_POSTDATED;
321    if (incred->flags.b.invalid)
322	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_INVALID;
323    if (incred->flags.b.renewable)
324	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_RENEWABLE;
325    if (incred->flags.b.initial)
326	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_INITIAL;
327    if (incred->flags.b.pre_authent)
328	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PRE_AUTH;
329    if (incred->flags.b.hw_authent)
330	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_HW_AUTH;
331    if (incred->flags.b.transited_policy_checked)
332	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED;
333    if (incred->flags.b.ok_as_delegate)
334	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE;
335    if (incred->flags.b.anonymous)
336	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_ANONYMOUS;
337
338    return 0;
339
340fail:
341    free_ccred(cred);
342
343    krb5_clear_error_message((mit_krb5_context)context);
344    return ret;
345}
346
347/*
348 *
349 */
350
351struct cred {
352    cc_credentials_union *data;
353    cc_credentials_f *functions;
354    cc_credentials_f *otherFunctions;
355    krb5_creds cred;
356};
357
358
359
360static cc_int32
361cred_release(cc_credentials_t io_credentials)
362{
363    struct cred *c = (struct cred *)io_credentials;
364    heim_krb5_free_cred_contents(milcontext, &c->cred);
365    free(c->data->credentials.credentials_v5);
366    free(c->data);
367    free(c);
368    return ccNoError;
369}
370
371static cc_int32
372cred_compare (cc_credentials_t  in_credentials,
373	      cc_credentials_t  in_compare_to_credentials,
374	      cc_uint32        *out_equal)
375{
376    *out_equal = 1;
377    return ccErrNoMem;
378}
379
380cc_credentials_f credential_functions = {
381    .release = cred_release,
382    .compare = cred_compare
383};
384
385
386
387static cc_credentials_t
388create_credentials(krb5_creds *cred)
389{
390    struct cred *c;
391
392    c = calloc(1, sizeof(*c));
393    c->data = calloc(1, sizeof(*c->data));
394    c->data->version = cc_credentials_v5;
395    c->data->credentials.credentials_v5 = calloc(1, sizeof(*c->data->credentials.credentials_v5));
396    c->functions = &credential_functions;
397
398    heim_krb5_copy_creds_contents(milcontext, cred, &c->cred);
399    make_ccred_from_cred(milcontext, &c->cred, c->data->credentials.credentials_v5);
400
401    return (cc_credentials_t)c;
402}
403
404
405struct cred_iterator {
406    cc_credentials_iterator_d iterator;
407    krb5_ccache id;
408    krb5_cc_cursor cursor;
409};
410
411static cc_int32
412cred_iter_release(cc_credentials_iterator_t io_credentials_iterator)
413{
414    struct cred_iterator *ci = (struct cred_iterator *)io_credentials_iterator;
415    LOG_ENTRY();
416    if (ci->id)
417	krb5_cc_end_seq_get ((mit_krb5_context)milcontext,
418			     (mit_krb5_ccache)ci->id,
419			     (mit_krb5_cc_cursor *)&ci->cursor);
420    free(ci);
421    return ccNoError;
422}
423
424cc_int32
425cred_iter_next(cc_credentials_iterator_t  in_credentials_iterator, cc_credentials_t *out_credentials)
426{
427    struct cred_iterator *ci = (struct cred_iterator *)in_credentials_iterator;
428    krb5_error_code ret;
429    krb5_creds cred;
430    LOG_ENTRY();
431
432    ret = heim_krb5_cc_next_cred(milcontext, ci->id, &ci->cursor, &cred);
433    if (ret == KRB5_CC_END)
434	return ccIteratorEnd;
435    else if (ret)
436	return ret; /* XXX */
437
438    *out_credentials = create_credentials(&cred);
439    heim_krb5_free_cred_contents(milcontext, &cred);
440    if (*out_credentials == NULL)
441	return ccErrNoMem;
442
443    return ccNoError;
444}
445
446cc_int32
447cred_iter_clone (cc_credentials_iterator_t  in_credentials_iterator,
448		 cc_credentials_iterator_t *out_credentials_iterator)
449{
450    LOG_UNIMPLEMENTED();
451    return ccErrNoMem;
452}
453
454
455struct cc_credentials_iterator_f cred_iter_functions = {
456    .release = cred_iter_release,
457    .next = cred_iter_next,
458    .clone = cred_iter_clone
459};
460
461
462
463struct cc_ccache {
464    cc_ccache_d ccache;
465    krb5_ccache id;
466    cc_time_t change_time;
467    cc_time_t last_default_time;
468};
469
470cc_int32
471ccache_release(cc_ccache_t io_ccache)
472{
473    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
474    LOG_ENTRY();
475    if (c->id)
476	heim_krb5_cc_close(milcontext, c->id);
477    free(c);
478    return ccNoError;
479}
480
481static cc_int32
482ccache_destroy(cc_ccache_t io_ccache)
483{
484    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
485    LOG_ENTRY();
486    update_time(&context_change_time);
487    if (c->id) {
488	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)c->id);
489	c->id = NULL;
490    }
491    return ccNoError;
492}
493
494static cc_int32
495ccache_set_default(cc_ccache_t io_ccache)
496{
497    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
498    LOG_ENTRY();
499    if (io_ccache == NULL || c->id == NULL)
500	return ccErrBadParam;
501    heim_krb5_cc_switch(milcontext, c->id);
502    update_time(&c->change_time);
503    update_time(&c->last_default_time);
504    return ccNoError;
505}
506
507static cc_int32
508ccache_get_credentials_version(cc_ccache_t  in_ccache, cc_uint32   *out_credentials_version)
509{
510    if (out_credentials_version == NULL)
511	return ccErrBadParam;
512    *out_credentials_version = cc_credentials_v5;
513    return ccNoError;
514}
515
516static cc_int32
517ccache_get_name(cc_ccache_t  in_ccache, cc_string_t *out_name)
518{
519    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
520    const char *name;
521    LOG_ENTRY();
522
523    if (out_name == NULL)
524	return ccErrBadParam;
525    if (c->id == NULL)
526	return ccErrInvalidCCache;
527
528    name = heim_krb5_cc_get_name(milcontext, c->id);
529    if (name == NULL)
530	return ccErrInvalidCCache;
531    *out_name = create_string(name);
532
533    return ccNoError;
534}
535
536static cc_int32
537ccache_get_principal(cc_ccache_t  in_ccache, cc_uint32    in_credentials_version, cc_string_t *out_principal)
538{
539    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
540    krb5_principal princ;
541    krb5_error_code ret;
542    char *name;
543
544    LOG_ENTRY();
545
546    if (out_principal == NULL)
547	return ccErrBadParam;
548    if (in_credentials_version != cc_credentials_v5)
549	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
550    if (c->id == NULL)
551	return ccErrInvalidCCache;
552
553    ret = heim_krb5_cc_get_principal(milcontext, c->id, &princ);
554    if (ret)
555	return LOG_FAILURE(ret, "get principal");
556    ret = heim_krb5_unparse_name(milcontext, princ, &name);
557    heim_krb5_free_principal(milcontext, princ);
558    if (ret)
559	return LOG_FAILURE(ret, "unparse name");
560    *out_principal = create_string(name);
561    free(name);
562
563    return ccNoError;
564}
565
566static cc_int32
567ccache_set_principal(cc_ccache_t  io_ccache, cc_uint32    in_credentials_version, const char  *in_principal)
568{
569    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
570    krb5_error_code ret;
571    krb5_principal p;
572    LOG_ENTRY();
573
574    if (in_principal == NULL)
575	return ccErrBadParam;
576    if (in_credentials_version != cc_credentials_v5)
577	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
578
579    update_time(&c->change_time);
580    update_time(&context_change_time);
581
582    ret = heim_krb5_parse_name(milcontext, in_principal, &p);
583    if (ret)
584	return LOG_FAILURE(ccErrBadParam, "parse name");
585
586    ret = heim_krb5_cc_initialize(milcontext, c->id, p);
587    heim_krb5_free_principal(milcontext, p);
588    if (ret)
589	return LOG_FAILURE(ccErrInvalidCCache, "init cache");
590
591    return ccNoError;
592}
593
594static cc_int32
595ccache_store_credentials(cc_ccache_t io_ccache, const cc_credentials_union *in_credentials_union)
596{
597    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
598    krb5_error_code ret;
599    krb5_creds hcred;
600    LOG_ENTRY();
601
602    if (in_credentials_union == NULL)
603	return ccErrBadParam;
604    if (in_credentials_union->version != cc_credentials_v5)
605	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
606    if (in_credentials_union->credentials.credentials_v5->client  == NULL)
607	return ccErrBadParam;
608
609    update_time(&c->change_time);
610    update_time(&context_change_time);
611
612    make_cred_from_ccred(milcontext, in_credentials_union->credentials.credentials_v5, &hcred);
613
614    ret = heim_krb5_cc_store_cred(milcontext, c->id, &hcred);
615    heim_krb5_free_cred_contents(milcontext, &hcred);
616    if (ret)
617	return LOG_FAILURE(ccErrInvalidCCache, "store cred");
618
619    return ccNoError;
620}
621
622static cc_int32
623ccache_remove_credentials(cc_ccache_t io_ccache, cc_credentials_t in_credentials)
624{
625    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
626    const cc_credentials_v5_t *incred;
627    krb5_creds cred;
628    krb5_error_code ret;
629
630    LOG_ENTRY();
631
632    update_time(&c->change_time);
633    update_time(&context_change_time);
634
635    memset(&cred, 0, sizeof(cred));
636
637    if (c->id == NULL)
638	return LOG_FAILURE(ccErrBadParam, "bad argument");
639
640    if (in_credentials == NULL || in_credentials->data == NULL)
641	return LOG_FAILURE(ccErrBadParam, "remove with no cred?");
642    if (in_credentials->data->version != cc_credentials_v5)
643	return LOG_FAILURE(ccErrBadParam, "wrong version");
644
645    incred = in_credentials->data->credentials.credentials_v5;
646    if (incred->client == NULL)
647	return LOG_FAILURE(ccErrBadParam, "no client to remove");
648    if (incred->server  == NULL)
649	return LOG_FAILURE(ccErrBadParam, "no server to remove");
650
651    ret = heim_krb5_parse_name(milcontext, incred->client, &cred.client);
652    if (ret)
653	goto fail;
654    ret = heim_krb5_parse_name(milcontext, incred->server, &cred.server);
655    if (ret)
656	goto fail;
657
658    ret = heim_krb5_cc_remove_cred(milcontext, c->id, 0, &cred);
659
660    update_time(&context_change_time);
661 fail:
662    heim_krb5_free_cred_contents(milcontext, &cred);
663    if (ret)
664	return ccErrCredentialsNotFound;
665    return ccNoError;
666}
667
668static cc_int32
669ccache_new_credentials_iterator(cc_ccache_t in_ccache, cc_credentials_iterator_t *out_credentials_iterator)
670{
671    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
672    struct cred_iterator *ci;
673    krb5_error_code ret;
674    LOG_ENTRY();
675
676    if (c == NULL || c->id == NULL)
677	return ccErrInvalidCCache;
678    if (out_credentials_iterator == NULL)
679	return ccErrBadParam;
680
681    ci = calloc(1, sizeof(*ci));
682    ci->iterator.functions = &cred_iter_functions;
683    ci->id = c->id;
684    ret = krb5_cc_start_seq_get((mit_krb5_context)milcontext,
685				(mit_krb5_ccache)c->id,
686				(mit_krb5_cc_cursor *)&ci->cursor);
687    if (ret) {
688	free(ci);
689	return LOG_FAILURE(ccErrInvalidCCache, "start seq");
690    }
691    *out_credentials_iterator = (cc_credentials_iterator_t)ci;
692    return ccNoError;
693}
694
695static cc_int32
696ccache_move(cc_ccache_t io_source_ccache, cc_ccache_t io_destination_ccache)
697{
698    struct cc_ccache *s = (struct cc_ccache *)io_source_ccache;
699    struct cc_ccache *d = (struct cc_ccache *)io_destination_ccache;
700    krb5_error_code ret;
701
702    if (s->id == NULL)
703	return ccErrInvalidCCache;
704    if (d == NULL)
705	return ccErrBadParam;
706
707    if (d->id == NULL) {
708	ret = heim_krb5_cc_new_unique(milcontext,
709				      heim_krb5_cc_get_type(milcontext, s->id),
710				      NULL, &d->id);
711	if (ret)
712	    return ccErrInvalidCCache;
713    }
714
715    ret = heim_krb5_cc_move(milcontext, s->id, d->id);
716    if (ret)
717	return LOG_FAILURE(ret, "move cache");
718    s->id = NULL;
719
720    return ccNoError;
721}
722
723static cc_int32
724ccache_lock(cc_ccache_t io_ccache, cc_uint32   in_lock_type, cc_uint32   in_block)
725{
726    LOG_ENTRY();
727    return ccNoError;
728}
729
730static cc_int32
731ccache_unlock(cc_ccache_t io_ccache)
732{
733    LOG_ENTRY();
734    return ccNoError;
735}
736
737static cc_int32
738ccache_get_last_default_time(cc_ccache_t  in_ccache, cc_time_t   *out_last_default_time)
739{
740    struct cc_ccache *s = (struct cc_ccache *)in_ccache;
741    LOG_ENTRY();
742
743    if (out_last_default_time == NULL)
744	return ccErrBadParam;
745    if (s->id == NULL)
746	return ccErrInvalidCCache;
747    if (s->last_default_time == 0)
748	return ccErrNeverDefault;
749
750    *out_last_default_time = s->last_default_time;
751    return ccNoError;
752}
753
754static cc_int32
755ccache_get_change_time(cc_ccache_t  in_ccache, cc_time_t   *out_change_time)
756{
757    struct cc_ccache *s = (struct cc_ccache *)in_ccache;
758    LOG_ENTRY();
759
760    if (out_change_time == NULL)
761	return ccErrBadParam;
762    *out_change_time = s->change_time;
763    return ccNoError;
764}
765
766static cc_int32
767ccache_compare(cc_ccache_t  in_ccache, cc_ccache_t  in_compare_to_ccache, cc_uint32   *out_equal)
768{
769    struct cc_ccache *s1 = (struct cc_ccache *)in_ccache;
770    struct cc_ccache *s2 = (struct cc_ccache *)in_compare_to_ccache;
771    krb5_error_code ret;
772    char *n1, *n2;
773
774    LOG_ENTRY();
775
776    if (out_equal == NULL || s2 == NULL)
777	return ccErrBadParam;
778    if (s1 == s2) {
779	*out_equal = 1;
780	return ccNoError;
781    }
782    if (s1->id == NULL || s2->id == NULL)
783	return ccErrInvalidCCache;
784
785    ret = heim_krb5_cc_get_full_name(milcontext, s1->id, &n1);
786    if (ret)
787	return ccErrInvalidCCache;
788    ret = heim_krb5_cc_get_full_name(milcontext, s2->id, &n2);
789    if (ret) {
790	free(n1);
791	return ccErrInvalidCCache;
792    }
793
794    *out_equal = (strcmp(n1, n2) == 0);
795
796    free(n1);
797    free(n2);
798
799    return ccNoError;
800}
801
802static cc_int32
803ccache_get_kdc_time_offset(cc_ccache_t in_ccache,
804			   cc_uint32 in_credentials_version,
805			   cc_time_t *out_time_offset)
806{
807    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
808    krb5_deltat sec = 0;
809
810    LOG_ENTRY();
811
812    if (c->id == NULL)
813	return LOG_FAILURE(ccErrBadParam, "bad credential");
814    if (in_credentials_version != cc_credentials_v5)
815	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
816    if (out_time_offset == NULL)
817	return LOG_FAILURE(ccErrBadParam, "bad argument");
818
819    heim_krb5_cc_get_kdc_offset(milcontext, c->id, &sec);
820    *out_time_offset = sec;
821
822    return ccNoError;
823}
824
825static cc_int32
826ccache_set_kdc_time_offset(cc_ccache_t io_ccache, cc_uint32   in_credentials_version, cc_time_t   in_time_offset)
827{
828    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
829    LOG_ENTRY();
830
831    if (c->id == NULL)
832	return LOG_FAILURE(ccErrBadParam, "bad credential");
833
834    if (in_credentials_version != cc_credentials_v5)
835	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
836
837    heim_krb5_cc_set_kdc_offset(milcontext, c->id, in_time_offset);
838
839    return ccNoError;
840}
841
842static cc_int32
843ccache_clear_kdc_time_offset(cc_ccache_t io_ccache, cc_uint32   in_credentials_version)
844{
845    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
846    LOG_ENTRY();
847
848    if (c->id == NULL)
849	return LOG_FAILURE(ccErrBadParam, "bad credential");
850
851    heim_krb5_cc_set_kdc_offset(milcontext, c->id, 0);
852
853    return ccNoError;
854}
855
856static cc_int32
857ccache_wait_for_change(cc_ccache_t in_ccache)
858{
859    LOG_UNIMPLEMENTED();
860    return ccErrNoMem;
861}
862
863static cc_ccache_f ccache_functions = {
864    .release = ccache_release,
865    .destroy = ccache_destroy,
866    .set_default = ccache_set_default,
867    .get_credentials_version = ccache_get_credentials_version,
868    .get_name = ccache_get_name,
869    .get_principal = ccache_get_principal,
870    .set_principal = ccache_set_principal,
871    .store_credentials = ccache_store_credentials,
872    .remove_credentials = ccache_remove_credentials,
873    .new_credentials_iterator = ccache_new_credentials_iterator,
874    .move = ccache_move,
875    .lock = ccache_lock,
876    .unlock = ccache_unlock,
877    .get_last_default_time = ccache_get_last_default_time,
878    .get_change_time = ccache_get_change_time,
879    .compare = ccache_compare,
880    .get_kdc_time_offset = ccache_get_kdc_time_offset,
881    .set_kdc_time_offset = ccache_set_kdc_time_offset,
882    .clear_kdc_time_offset = ccache_clear_kdc_time_offset,
883    .wait_for_change = ccache_wait_for_change
884};
885
886static cc_ccache_t
887create_ccache(krb5_ccache id)
888{
889    struct cc_ccache *c;
890
891    c = mshim_malloc(sizeof(*c));
892    c->ccache.functions = &ccache_functions;
893    c->id = id;
894    update_time(&c->change_time);
895    return (cc_ccache_t)c;
896}
897
898struct cc_iter {
899    cc_ccache_iterator_d iterator;
900    mit_krb5_cccol_cursor cursor;
901};
902
903static cc_int32
904cc_iterator_release(cc_ccache_iterator_t io_ccache_iterator)
905{
906    struct cc_iter *c = (struct cc_iter *)io_ccache_iterator;
907    LOG_ENTRY();
908    krb5_cccol_cursor_free((mit_krb5_context)milcontext, &c->cursor);
909    free(c);
910    return ccNoError;
911}
912
913cc_int32
914cc_iterator_next(cc_ccache_iterator_t  in_ccache_iterator,
915		 cc_ccache_t *out_ccache)
916{
917    struct cc_iter *c = (struct cc_iter *)in_ccache_iterator;
918    krb5_error_code ret;
919    krb5_ccache id;
920
921    LOG_ENTRY();
922
923    if (out_ccache == NULL)
924	return ccErrBadParam;
925
926
927    while (1) {
928	ret = krb5_cccol_cursor_next((mit_krb5_context)milcontext, c->cursor, (mit_krb5_ccache *)&id);
929	if (ret == KRB5_CC_END || id == NULL)
930	    return ccIteratorEnd;
931	else if (ret)
932	    return LOG_FAILURE(ret, "ccol next cursor");
933
934	const char *type = heim_krb5_cc_get_type(milcontext, id);
935	if (strcmp(type, "API") == 0 || strcmp(type, "KCM") == 0)
936	    break;
937	heim_krb5_cc_close(milcontext, id);
938    }
939    *out_ccache = create_ccache(id);
940
941    return ccNoError;
942}
943
944static cc_int32
945cc_iterator_clone(cc_ccache_iterator_t  in_ccache_iterator,
946		  cc_ccache_iterator_t *out_ccache_iterator)
947{
948    LOG_UNIMPLEMENTED();
949    if (out_ccache_iterator == NULL)
950	return ccErrBadParam;
951    return ccErrNoMem;
952}
953
954static cc_ccache_iterator_f ccache_iterator_functions = {
955    .release = cc_iterator_release,
956    .next = cc_iterator_next,
957    .clone = cc_iterator_clone
958};
959
960
961
962
963static cc_int32
964context_release(cc_context_t io_context)
965{
966    LOG_ENTRY();
967    memset(io_context, 0, sizeof(*io_context));
968    free(io_context);
969
970    return ccNoError;
971}
972
973static cc_int32
974context_get_change_time(cc_context_t  in_context,
975			cc_time_t    *out_time)
976{
977    LOG_ENTRY();
978    if (out_time == NULL)
979	return ccErrBadParam;
980    *out_time = context_change_time;
981    return ccNoError;
982}
983
984static cc_int32
985context_get_default_ccache_name(cc_context_t  in_context,
986				cc_string_t  *out_name)
987{
988    const char *name;
989    name = krb5_cc_default_name((mit_krb5_context)milcontext);
990    if (name == NULL)
991	return ccErrNoMem; /* XXX */
992    if (out_name == NULL)
993	return ccErrBadParam;
994    if (strncmp("API:", name, 4) == 0)
995	name += 4;
996
997    *out_name = create_string(name);
998
999    return ccNoError;
1000}
1001
1002/*
1003 * Probe for client principal to make sure the cache really
1004 * exists.
1005 */
1006
1007static cc_int32
1008check_exists(krb5_ccache id)
1009{
1010    krb5_principal princ;
1011    int ret;
1012
1013    ret = heim_krb5_cc_get_principal(milcontext, id, &princ);
1014    if (ret)
1015	return 0;
1016    heim_krb5_free_principal(milcontext, princ);
1017
1018    return 1;
1019}
1020
1021
1022
1023static cc_int32
1024context_open_ccache (cc_context_t  in_context,
1025		     const char   *in_name,
1026		     cc_ccache_t  *out_ccache)
1027{
1028    char *name;
1029    krb5_error_code ret;
1030    krb5_ccache id;
1031
1032    if (out_ccache == NULL || in_name == NULL || in_context == NULL)
1033	return ccErrBadParam;
1034
1035    asprintf(&name, "API:%s", in_name);
1036
1037    ret = heim_krb5_cc_resolve(milcontext, name, &id);
1038    free(name);
1039    if (ret)
1040	return LOG_FAILURE(ret, "open cache");
1041
1042    if (!check_exists(id)) {
1043	heim_krb5_cc_close(milcontext, id);
1044	return ccErrCCacheNotFound;
1045    }
1046
1047    *out_ccache = create_ccache(id);
1048
1049    return ccNoError;
1050}
1051
1052static cc_int32
1053context_open_default_ccache(cc_context_t  in_context,
1054			    cc_ccache_t  *out_ccache)
1055{
1056    krb5_error_code ret;
1057    krb5_ccache id;
1058
1059    LOG_ENTRY();
1060
1061    if (out_ccache == NULL)
1062	return ccErrBadParam;
1063
1064    ret = heim_krb5_cc_default(milcontext, &id);
1065    if (ret)
1066	return LOG_FAILURE(ret, "cc default");
1067
1068    if (!check_exists(id)) {
1069	heim_krb5_cc_close(milcontext, id);
1070	return ccErrCCacheNotFound;
1071    }
1072
1073    *out_ccache = create_ccache(id);
1074
1075    return ccNoError;
1076}
1077
1078static cc_int32
1079context_create_ccache(cc_context_t  in_context,
1080		      const char   *in_name,
1081		      cc_uint32     in_cred_vers,
1082		      const char   *in_principal,
1083		      cc_ccache_t  *out_ccache)
1084{
1085    krb5_principal principal;
1086    krb5_error_code ret;
1087    krb5_ccache id;
1088
1089    if (in_cred_vers != cc_credentials_v5)
1090	return ccErrBadCredentialsVersion;
1091    if (out_ccache == NULL || in_name == NULL || in_context == NULL || in_principal == NULL)
1092	return ccErrBadParam;
1093
1094    update_time(&context_change_time);
1095
1096    ret = heim_krb5_parse_name(milcontext, in_principal, &principal);
1097    if (ret)
1098	return LOG_FAILURE(ret, "parse name");
1099
1100    ret = heim_krb5_cc_resolve(milcontext, in_name, &id);
1101    if (ret) {
1102	heim_krb5_free_principal(milcontext, principal);
1103	return LOG_FAILURE(ret, "open cache");
1104    }
1105
1106    ret = heim_krb5_cc_initialize(milcontext, id, principal);
1107    heim_krb5_free_principal(milcontext, principal);
1108    if (ret) {
1109	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)id);
1110	return LOG_FAILURE(ret, "cc init");
1111    }
1112
1113    *out_ccache = create_ccache(id);
1114
1115    return ccNoError;
1116}
1117
1118static cc_int32
1119context_create_default_ccache(cc_context_t  in_context,
1120			      cc_uint32     in_cred_vers,
1121			      const char   *in_principal,
1122			      cc_ccache_t  *out_ccache)
1123{
1124    krb5_principal principal;
1125    krb5_error_code ret;
1126    struct cc_ccache *c;
1127    krb5_ccache id;
1128
1129    LOG_ENTRY();
1130
1131    if (in_cred_vers != cc_credentials_v5)
1132	return ccErrBadCredentialsVersion;
1133    if (out_ccache == NULL || in_principal == NULL)
1134	return ccErrBadParam;
1135
1136    *out_ccache = NULL;
1137
1138    update_time(&context_change_time);
1139
1140    ret = heim_krb5_cc_default(milcontext, &id);
1141    if (ret)
1142	return LOG_FAILURE(ret, "cc default");
1143
1144    ret = heim_krb5_parse_name(milcontext, in_principal, &principal);
1145    if (ret) {
1146	heim_krb5_cc_close(milcontext, id);
1147	return LOG_FAILURE(ret, "parse name");
1148    }
1149
1150    ret = heim_krb5_cc_initialize(milcontext, id, principal);
1151    heim_krb5_free_principal(milcontext, principal);
1152    if (ret) {
1153	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)id);
1154	return LOG_FAILURE(ret, "cc init");
1155    }
1156
1157    c = (struct cc_ccache *)create_ccache(id);
1158
1159    update_time(&c->last_default_time);
1160
1161    *out_ccache = (cc_ccache_t)c;
1162
1163    return ccNoError;
1164}
1165
1166static cc_int32
1167context_create_new_ccache(cc_context_t in_context,
1168			  cc_uint32    in_cred_vers,
1169			  const char  *in_principal,
1170			  cc_ccache_t *out_ccache)
1171{
1172    krb5_principal principal;
1173    krb5_error_code ret;
1174    krb5_ccache id;
1175
1176    LOG_ENTRY();
1177
1178    if (in_cred_vers != cc_credentials_v5)
1179	return ccErrBadCredentialsVersion;
1180
1181    if (out_ccache == NULL || in_principal == NULL)
1182	return ccErrBadParam;
1183
1184    update_time(&context_change_time);
1185
1186    ret = heim_krb5_parse_name(milcontext, in_principal, &principal);
1187    if (ret)
1188	return LOG_FAILURE(ret, "parse name");
1189
1190    ret = heim_krb5_cc_new_unique(milcontext, NULL, NULL, &id);
1191    if (ret) {
1192	heim_krb5_free_principal(milcontext, principal);
1193	return LOG_FAILURE(ret, "new unique");
1194    }
1195
1196    ret = heim_krb5_cc_initialize(milcontext, id, principal);
1197    heim_krb5_free_principal(milcontext, principal);
1198    if (ret) {
1199	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)id);
1200	return LOG_FAILURE(ret, "cc init");
1201    }
1202
1203    *out_ccache = create_ccache(id);
1204
1205    return ccNoError;
1206}
1207
1208static cc_int32
1209context_new_ccache_iterator(cc_context_t in_context,
1210			    cc_ccache_iterator_t *out_iterator)
1211{
1212    LOG_ENTRY();
1213
1214    krb5_error_code ret;
1215    struct cc_iter *c;
1216
1217    if (out_iterator == NULL)
1218	return ccErrBadParam;
1219
1220    c = calloc(1, sizeof(*c));
1221    c->iterator.functions = &ccache_iterator_functions;
1222
1223    ret = krb5_cccol_cursor_new((mit_krb5_context)milcontext, &c->cursor);
1224    if (ret) {
1225	free(c);
1226	return ccErrNoMem;
1227    }
1228
1229    *out_iterator = (cc_ccache_iterator_t)c;
1230
1231    return ccNoError;
1232}
1233
1234static cc_int32
1235context_lock(cc_context_t in_context,
1236	     cc_uint32    in_lock_type,
1237	     cc_uint32    in_block)
1238{
1239    LOG_UNIMPLEMENTED();
1240    return ccNoError;
1241}
1242
1243static cc_int32
1244context_unlock(cc_context_t in_context)
1245{
1246    LOG_UNIMPLEMENTED();
1247    return ccNoError;
1248}
1249
1250static cc_int32
1251context_compare(cc_context_t  in_cc_context,
1252		cc_context_t  in_compare_to_context,
1253		cc_uint32    *out_equal)
1254{
1255    LOG_UNIMPLEMENTED();
1256    if (out_equal == NULL || in_compare_to_context == NULL)
1257	return ccErrBadParam;
1258    *out_equal = (in_cc_context == in_compare_to_context);
1259    return 0;
1260}
1261
1262static cc_int32
1263context_wait_for_change(cc_context_t in_cc_context)
1264{
1265    LOG_UNIMPLEMENTED();
1266    return ccErrNoMem;
1267}
1268
1269
1270
1271cc_context_f cc_functions = {
1272    .release = context_release,
1273    .get_change_time = context_get_change_time,
1274    .get_default_ccache_name = context_get_default_ccache_name,
1275    .open_ccache = context_open_ccache,
1276    .open_default_ccache = context_open_default_ccache,
1277    .create_ccache = context_create_ccache,
1278    .create_default_ccache = context_create_default_ccache,
1279    .create_new_ccache = context_create_new_ccache,
1280    .new_ccache_iterator = context_new_ccache_iterator,
1281    .lock = context_lock,
1282    .unlock = context_unlock,
1283    .compare = context_compare,
1284    .wait_for_change = context_wait_for_change
1285};
1286
1287
1288cc_int32
1289cc_initialize(cc_context_t  *out_context,
1290	      cc_int32       in_version,
1291	      cc_int32      *out_supported_version,
1292	      char const   **out_vendor)
1293{
1294    LOG_ENTRY();
1295
1296    update_time(&context_change_time);
1297
1298    if (in_version < ccapi_version_3 || in_version > ccapi_version_7)
1299	return ccErrBadAPIVersion;
1300    if (out_context == NULL)
1301	return ccErrBadParam;
1302
1303    *out_context = calloc(1, sizeof(**out_context));
1304    (*out_context)->functions = &cc_functions;
1305
1306    if (out_supported_version)
1307	*out_supported_version = ccapi_version_7;
1308    if (out_vendor)
1309	*out_vendor = "Apple Heimdal shim layer";
1310
1311    return 0;
1312}
1313
1314
1315