ccache.c   [plain text]


/*
 * Copyright (c) 2004 - 2007 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "mit-CredentialsCache.h"
#include <string.h>
#include <syslog.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>

#include "heim.h"

static cc_time_t context_change_time = 0;

void
update_time(cc_time_t *change_time)
{
    cc_time_t now = (cc_time_t)time(NULL);
    if (*change_time >= now)
	*change_time += 1;
    else
	*change_time = now;
}

static cc_int32 
string_release(cc_string_t io_string)
{
    free((char *)io_string->data);
    free(io_string);
    return ccNoError;
}


cc_string_f string_functions =  {
    .release = string_release
};

static cc_string_t
create_string(const char *string)
{
    cc_string_t s;
    s = mshim_malloc(sizeof(*s));
    s->functions = &string_functions;
    s->data = strdup(string);
    return s;
}


#define	KRB5_CCAPI_TKT_FLG_FORWARDABLE			0x40000000
#define	KRB5_CCAPI_TKT_FLG_FORWARDED			0x20000000
#define	KRB5_CCAPI_TKT_FLG_PROXIABLE			0x10000000
#define	KRB5_CCAPI_TKT_FLG_PROXY			0x08000000
#define	KRB5_CCAPI_TKT_FLG_MAY_POSTDATE			0x04000000
#define	KRB5_CCAPI_TKT_FLG_POSTDATED			0x02000000
#define	KRB5_CCAPI_TKT_FLG_INVALID			0x01000000
#define	KRB5_CCAPI_TKT_FLG_RENEWABLE			0x00800000
#define	KRB5_CCAPI_TKT_FLG_INITIAL			0x00400000
#define	KRB5_CCAPI_TKT_FLG_PRE_AUTH			0x00200000
#define	KRB5_CCAPI_TKT_FLG_HW_AUTH			0x00100000
#define	KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED	0x00080000
#define	KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE		0x00040000
#define	KRB5_CCAPI_TKT_FLG_ANONYMOUS			0x00020000

static krb5_error_code
make_cred_from_ccred(krb5_context context,
		     const cc_credentials_v5_t *incred,
		     krb5_creds *cred)
{
    krb5_error_code ret;
    unsigned int i;

    memset(cred, 0, sizeof(*cred));

    ret = heim_krb5_parse_name(context, incred->client, &cred->client);
    if (ret)
	goto fail;

    ret = heim_krb5_parse_name(context, incred->server, &cred->server);
    if (ret)
	goto fail;

    cred->session.keytype = incred->keyblock.type;
    cred->session.keyvalue.length = incred->keyblock.length;
    cred->session.keyvalue.data = malloc(incred->keyblock.length);
    if (cred->session.keyvalue.data == NULL)
	goto nomem;
    memcpy(cred->session.keyvalue.data, incred->keyblock.data,
	   incred->keyblock.length);

    cred->times.authtime = incred->authtime;
    cred->times.starttime = incred->starttime;
    cred->times.endtime = incred->endtime;
    cred->times.renew_till = incred->renew_till;

    ret = heim_krb5_data_copy(&cred->ticket,
			      incred->ticket.data,
			      incred->ticket.length);
    if (ret)
	goto nomem;

    ret = heim_krb5_data_copy(&cred->second_ticket,
			      incred->second_ticket.data,
			      incred->second_ticket.length);
    if (ret)
	goto nomem;

    cred->authdata.val = NULL;
    cred->authdata.len = 0;

    cred->addresses.val = NULL;
    cred->addresses.len = 0;

    for (i = 0; incred->authdata && incred->authdata[i]; i++)
	;

    if (i) {
	cred->authdata.val = calloc(i, sizeof(cred->authdata.val[0]));
	if (cred->authdata.val == NULL)
	    goto nomem;
	cred->authdata.len = i;
	for (i = 0; i < cred->authdata.len; i++) {
	    cred->authdata.val[i].ad_type = incred->authdata[i]->type;
	    ret = heim_krb5_data_copy(&cred->authdata.val[i].ad_data,
				      incred->authdata[i]->data,
				      incred->authdata[i]->length);
	    if (ret)
		goto nomem;
	}
    }

    for (i = 0; incred->addresses && incred->addresses[i]; i++)
	;

    if (i) {
	cred->addresses.val = calloc(i, sizeof(cred->addresses.val[0]));
	if (cred->addresses.val == NULL)
	    goto nomem;
	cred->addresses.len = i;
	
	for (i = 0; i < cred->addresses.len; i++) {
	    cred->addresses.val[i].addr_type = incred->addresses[i]->type;
	    ret = heim_krb5_data_copy(&cred->addresses.val[i].address,
				      incred->addresses[i]->data,
				      incred->addresses[i]->length);
	    if (ret)
		goto nomem;
	}
    }

    cred->flags.i = 0;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_FORWARDABLE)
	cred->flags.b.forwardable = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_FORWARDED)
	cred->flags.b.forwarded = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PROXIABLE)
	cred->flags.b.proxiable = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PROXY)
	cred->flags.b.proxy = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_MAY_POSTDATE)
	cred->flags.b.may_postdate = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_POSTDATED)
	cred->flags.b.postdated = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_INVALID)
	cred->flags.b.invalid = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_RENEWABLE)
	cred->flags.b.renewable = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_INITIAL)
	cred->flags.b.initial = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PRE_AUTH)
	cred->flags.b.pre_authent = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_HW_AUTH)
	cred->flags.b.hw_authent = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED)
	cred->flags.b.transited_policy_checked = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE)
	cred->flags.b.ok_as_delegate = 1;
    if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_ANONYMOUS)
	cred->flags.b.anonymous = 1;

    return 0;

nomem:
    ret = ENOMEM;
    krb5_set_error_message((mit_krb5_context)context, ret, "malloc: out of memory");

fail:
    heim_krb5_free_cred_contents(context, cred);
    return ret;
}

static void
free_ccred(cc_credentials_v5_t *cred)
{
    int i;

    if (cred->addresses) {
	for (i = 0; cred->addresses[i] != 0; i++) {
	    if (cred->addresses[i]->data)
		free(cred->addresses[i]->data);
	    free(cred->addresses[i]);
	}
	free(cred->addresses);
    }
    if (cred->server)
	free(cred->server);
    if (cred->client)
	free(cred->client);
    memset(cred, 0, sizeof(*cred));
}

static krb5_error_code
make_ccred_from_cred(krb5_context context,
		     const krb5_creds *incred,
		     cc_credentials_v5_t *cred)
{
    krb5_error_code ret;
    int i;

    memset(cred, 0, sizeof(*cred));

    ret = heim_krb5_unparse_name(context, incred->client, &cred->client);
    if (ret)
	goto fail;

    ret = heim_krb5_unparse_name(context, incred->server, &cred->server);
    if (ret)
	goto fail;

    cred->keyblock.type = incred->session.keytype;
    cred->keyblock.length = (cc_uint32)incred->session.keyvalue.length;
    cred->keyblock.data = incred->session.keyvalue.data;

    cred->authtime = (cc_time_t)incred->times.authtime;
    cred->starttime = (cc_time_t)incred->times.starttime;
    cred->endtime = (cc_time_t)incred->times.endtime;
    cred->renew_till = (cc_time_t)incred->times.renew_till;

    cred->ticket.length = (cc_int32)incred->ticket.length;
    cred->ticket.data = incred->ticket.data;

    cred->second_ticket.length = (cc_int32)incred->second_ticket.length;
    cred->second_ticket.data = incred->second_ticket.data;

    /* XXX this one should also be filled in */
    cred->authdata = NULL;

    cred->addresses = calloc(incred->addresses.len + 1,
			     sizeof(cred->addresses[0]));
    if (cred->addresses == NULL) {

	ret = ENOMEM;
	goto fail;
    }

    for (i = 0; i < incred->addresses.len; i++) {
	cc_data *addr;
	addr = malloc(sizeof(*addr));
	if (addr == NULL) {
	    ret = ENOMEM;
	    goto fail;
	}
	addr->type = incred->addresses.val[i].addr_type;
	addr->length = (cc_int32)incred->addresses.val[i].address.length;
	addr->data = malloc(addr->length);
	if (addr->data == NULL) {
	    free(addr);
	    ret = ENOMEM;
	    goto fail;
	}
	memcpy(addr->data, incred->addresses.val[i].address.data,
	       addr->length);
	cred->addresses[i] = addr;
    }
    cred->addresses[i] = NULL;

    cred->ticket_flags = 0;
    if (incred->flags.b.forwardable)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_FORWARDABLE;
    if (incred->flags.b.forwarded)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_FORWARDED;
    if (incred->flags.b.proxiable)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PROXIABLE;
    if (incred->flags.b.proxy)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PROXY;
    if (incred->flags.b.may_postdate)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_MAY_POSTDATE;
    if (incred->flags.b.postdated)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_POSTDATED;
    if (incred->flags.b.invalid)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_INVALID;
    if (incred->flags.b.renewable)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_RENEWABLE;
    if (incred->flags.b.initial)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_INITIAL;
    if (incred->flags.b.pre_authent)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PRE_AUTH;
    if (incred->flags.b.hw_authent)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_HW_AUTH;
    if (incred->flags.b.transited_policy_checked)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED;
    if (incred->flags.b.ok_as_delegate)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE;
    if (incred->flags.b.anonymous)
	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_ANONYMOUS;

    return 0;

fail:
    free_ccred(cred);

    krb5_clear_error_message((mit_krb5_context)context);
    return ret;
}

/*
 *
 */

struct cred {
    cc_credentials_union *data;
    cc_credentials_f *functions;
    cc_credentials_f *otherFunctions;
    krb5_creds cred;
};



static cc_int32
cred_release(cc_credentials_t io_credentials)
{
    struct cred *c = (struct cred *)io_credentials;
    heim_krb5_free_cred_contents(milcontext, &c->cred);
    free(c->data->credentials.credentials_v5);
    free(c->data);
    free(c);
    return ccNoError;
}
    
static cc_int32
cred_compare (cc_credentials_t  in_credentials,
	      cc_credentials_t  in_compare_to_credentials,
	      cc_uint32        *out_equal)
{
    *out_equal = 1;
    return ccErrNoMem;
}

cc_credentials_f credential_functions = {
    .release = cred_release,
    .compare = cred_compare
};



static cc_credentials_t
create_credentials(krb5_creds *cred)
{
    struct cred *c;

    c = calloc(1, sizeof(*c));
    c->data = calloc(1, sizeof(*c->data));
    c->data->version = cc_credentials_v5;
    c->data->credentials.credentials_v5 = calloc(1, sizeof(*c->data->credentials.credentials_v5));
    c->functions = &credential_functions;
    
    heim_krb5_copy_creds_contents(milcontext, cred, &c->cred);
    make_ccred_from_cred(milcontext, &c->cred, c->data->credentials.credentials_v5);

    return (cc_credentials_t)c;
}


struct cred_iterator {
    cc_credentials_iterator_d iterator;
    krb5_ccache id;
    krb5_cc_cursor cursor;
};

static cc_int32
cred_iter_release(cc_credentials_iterator_t io_credentials_iterator)
{
    struct cred_iterator *ci = (struct cred_iterator *)io_credentials_iterator;
    LOG_ENTRY();
    if (ci->id)
	krb5_cc_end_seq_get ((mit_krb5_context)milcontext,
			     (mit_krb5_ccache)ci->id,
			     (mit_krb5_cc_cursor *)&ci->cursor);
    free(ci);
    return ccNoError;
}

cc_int32
cred_iter_next(cc_credentials_iterator_t  in_credentials_iterator, cc_credentials_t *out_credentials)
{
    struct cred_iterator *ci = (struct cred_iterator *)in_credentials_iterator;
    krb5_error_code ret;
    krb5_creds cred;
    LOG_ENTRY();

    ret = heim_krb5_cc_next_cred(milcontext, ci->id, &ci->cursor, &cred);
    if (ret == KRB5_CC_END)
	return ccIteratorEnd;
    else if (ret)
	return ret; /* XXX */

    *out_credentials = create_credentials(&cred);
    heim_krb5_free_cred_contents(milcontext, &cred);
    if (*out_credentials == NULL)
	return ccErrNoMem;

    return ccNoError;
}
    
cc_int32
cred_iter_clone (cc_credentials_iterator_t  in_credentials_iterator,
		 cc_credentials_iterator_t *out_credentials_iterator)
{
    LOG_UNIMPLEMENTED();
    return ccErrNoMem;
}


struct cc_credentials_iterator_f cred_iter_functions = {
    .release = cred_iter_release,
    .next = cred_iter_next,
    .clone = cred_iter_clone
};



struct cc_ccache {
    cc_ccache_d ccache;
    krb5_ccache id;
    cc_time_t change_time;
    cc_time_t last_default_time;
};

cc_int32
ccache_release(cc_ccache_t io_ccache)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    LOG_ENTRY();
    if (c->id)
	heim_krb5_cc_close(milcontext, c->id);
    free(c);
    return ccNoError;
}

static cc_int32
ccache_destroy(cc_ccache_t io_ccache)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    LOG_ENTRY();
    update_time(&context_change_time);
    if (c->id) {
	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)c->id);
	c->id = NULL;
    }
    return ccNoError;
}

static cc_int32
ccache_set_default(cc_ccache_t io_ccache)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    LOG_ENTRY();
    if (io_ccache == NULL || c->id == NULL)
	return ccErrBadParam;
    heim_krb5_cc_switch(milcontext, c->id);
    update_time(&c->change_time);
    update_time(&c->last_default_time);
    return ccNoError;
}

static cc_int32
ccache_get_credentials_version(cc_ccache_t  in_ccache, cc_uint32   *out_credentials_version)
{
    if (out_credentials_version == NULL)
	return ccErrBadParam;
    *out_credentials_version = cc_credentials_v5;
    return ccNoError;
}

static cc_int32
ccache_get_name(cc_ccache_t  in_ccache, cc_string_t *out_name)
{
    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
    const char *name;
    LOG_ENTRY();

    if (out_name == NULL)
	return ccErrBadParam;
    if (c->id == NULL)
	return ccErrInvalidCCache;

    name = heim_krb5_cc_get_name(milcontext, c->id);
    if (name == NULL)
	return ccErrInvalidCCache;
    *out_name = create_string(name);

    return ccNoError;
}

static cc_int32
ccache_get_principal(cc_ccache_t  in_ccache, cc_uint32    in_credentials_version, cc_string_t *out_principal)
{
    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
    krb5_principal princ;
    krb5_error_code ret;
    char *name;
    
    LOG_ENTRY();

    if (out_principal == NULL)
	return ccErrBadParam;
    if (in_credentials_version != cc_credentials_v5)
	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
    if (c->id == NULL)
	return ccErrInvalidCCache;

    ret = heim_krb5_cc_get_principal(milcontext, c->id, &princ);
    if (ret)
	return LOG_FAILURE(ret, "get principal");
    ret = heim_krb5_unparse_name(milcontext, princ, &name);
    heim_krb5_free_principal(milcontext, princ);
    if (ret)
	return LOG_FAILURE(ret, "unparse name");
    *out_principal = create_string(name);
    free(name);

    return ccNoError;
}

static cc_int32
ccache_set_principal(cc_ccache_t  io_ccache, cc_uint32    in_credentials_version, const char  *in_principal)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    krb5_error_code ret;
    krb5_principal p;
    LOG_ENTRY();

    if (in_principal == NULL)
	return ccErrBadParam;
    if (in_credentials_version != cc_credentials_v5)
	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");

    update_time(&c->change_time);
    update_time(&context_change_time);

    ret = heim_krb5_parse_name(milcontext, in_principal, &p);
    if (ret)
	return LOG_FAILURE(ccErrBadParam, "parse name");
	
    ret = heim_krb5_cc_initialize(milcontext, c->id, p);
    heim_krb5_free_principal(milcontext, p);
    if (ret)
	return LOG_FAILURE(ccErrInvalidCCache, "init cache");
    
    return ccNoError;
}

static cc_int32
ccache_store_credentials(cc_ccache_t io_ccache, const cc_credentials_union *in_credentials_union)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    krb5_error_code ret;
    krb5_creds hcred;
    LOG_ENTRY();

    if (in_credentials_union == NULL)
	return ccErrBadParam;
    if (in_credentials_union->version != cc_credentials_v5)
	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
    if (in_credentials_union->credentials.credentials_v5->client  == NULL)
	return ccErrBadParam;

    update_time(&c->change_time);
    update_time(&context_change_time);

    make_cred_from_ccred(milcontext, in_credentials_union->credentials.credentials_v5, &hcred);

    ret = heim_krb5_cc_store_cred(milcontext, c->id, &hcred);
    heim_krb5_free_cred_contents(milcontext, &hcred);
    if (ret)
	return LOG_FAILURE(ccErrInvalidCCache, "store cred");

    return ccNoError;
}

static cc_int32
ccache_remove_credentials(cc_ccache_t io_ccache, cc_credentials_t in_credentials)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    const cc_credentials_v5_t *incred;
    krb5_creds cred;
    krb5_error_code ret;

    LOG_ENTRY();

    update_time(&c->change_time);
    update_time(&context_change_time);

    memset(&cred, 0, sizeof(cred));

    if (c->id == NULL)
	return LOG_FAILURE(ccErrBadParam, "bad argument");

    if (in_credentials == NULL || in_credentials->data == NULL)
	return LOG_FAILURE(ccErrBadParam, "remove with no cred?");
    if (in_credentials->data->version != cc_credentials_v5)
	return LOG_FAILURE(ccErrBadParam, "wrong version");

    incred = in_credentials->data->credentials.credentials_v5;
    if (incred->client == NULL)
	return LOG_FAILURE(ccErrBadParam, "no client to remove");
    if (incred->server  == NULL)
	return LOG_FAILURE(ccErrBadParam, "no server to remove");

    ret = heim_krb5_parse_name(milcontext, incred->client, &cred.client);
    if (ret)
	goto fail;
    ret = heim_krb5_parse_name(milcontext, incred->server, &cred.server);
    if (ret)
	goto fail;

    ret = heim_krb5_cc_remove_cred(milcontext, c->id, 0, &cred);

    update_time(&context_change_time);
 fail:
    heim_krb5_free_cred_contents(milcontext, &cred);
    if (ret)
	return ccErrCredentialsNotFound;
    return ccNoError;
}

static cc_int32
ccache_new_credentials_iterator(cc_ccache_t in_ccache, cc_credentials_iterator_t *out_credentials_iterator)
{
    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
    struct cred_iterator *ci;
    krb5_error_code ret;
    LOG_ENTRY();

    if (c == NULL || c->id == NULL)
	return ccErrInvalidCCache;
    if (out_credentials_iterator == NULL)
	return ccErrBadParam;

    ci = calloc(1, sizeof(*ci));
    ci->iterator.functions = &cred_iter_functions;
    ci->id = c->id;
    ret = krb5_cc_start_seq_get((mit_krb5_context)milcontext, 
				(mit_krb5_ccache)c->id,
				(mit_krb5_cc_cursor *)&ci->cursor);
    if (ret) {
	free(ci);
	return LOG_FAILURE(ccErrInvalidCCache, "start seq");
    }
    *out_credentials_iterator = (cc_credentials_iterator_t)ci;
    return ccNoError;
}

static cc_int32
ccache_move(cc_ccache_t io_source_ccache, cc_ccache_t io_destination_ccache)
{
    struct cc_ccache *s = (struct cc_ccache *)io_source_ccache;
    struct cc_ccache *d = (struct cc_ccache *)io_destination_ccache;
    krb5_error_code ret;

    if (s->id == NULL)
	return ccErrInvalidCCache;
    if (d == NULL)
	return ccErrBadParam;

    if (d->id == NULL) {
	ret = heim_krb5_cc_new_unique(milcontext,
				      heim_krb5_cc_get_type(milcontext, s->id),
				      NULL, &d->id);
	if (ret)
	    return ccErrInvalidCCache;
    }

    ret = heim_krb5_cc_move(milcontext, s->id, d->id);
    if (ret)
	return LOG_FAILURE(ret, "move cache");
    s->id = NULL;

    return ccNoError;
}

static cc_int32
ccache_lock(cc_ccache_t io_ccache, cc_uint32   in_lock_type, cc_uint32   in_block)
{
    LOG_ENTRY();
    return ccNoError;
}

static cc_int32
ccache_unlock(cc_ccache_t io_ccache)
{
    LOG_ENTRY();
    return ccNoError;
}

static cc_int32
ccache_get_last_default_time(cc_ccache_t  in_ccache, cc_time_t   *out_last_default_time)
{
    struct cc_ccache *s = (struct cc_ccache *)in_ccache;
    LOG_ENTRY();

    if (out_last_default_time == NULL)
	return ccErrBadParam;
    if (s->id == NULL)
	return ccErrInvalidCCache;
    if (s->last_default_time == 0)
	return ccErrNeverDefault;

    *out_last_default_time = s->last_default_time;
    return ccNoError;
}

static cc_int32
ccache_get_change_time(cc_ccache_t  in_ccache, cc_time_t   *out_change_time)
{
    struct cc_ccache *s = (struct cc_ccache *)in_ccache;
    LOG_ENTRY();

    if (out_change_time == NULL)
	return ccErrBadParam;
    *out_change_time = s->change_time;
    return ccNoError;
}

static cc_int32
ccache_compare(cc_ccache_t  in_ccache, cc_ccache_t  in_compare_to_ccache, cc_uint32   *out_equal)
{
    struct cc_ccache *s1 = (struct cc_ccache *)in_ccache;
    struct cc_ccache *s2 = (struct cc_ccache *)in_compare_to_ccache;
    krb5_error_code ret;
    char *n1, *n2;

    LOG_ENTRY();

    if (out_equal == NULL || s2 == NULL)
	return ccErrBadParam;
    if (s1 == s2) {
	*out_equal = 1;
	return ccNoError;
    }
    if (s1->id == NULL || s2->id == NULL)
	return ccErrInvalidCCache;

    ret = heim_krb5_cc_get_full_name(milcontext, s1->id, &n1);
    if (ret)
	return ccErrInvalidCCache;
    ret = heim_krb5_cc_get_full_name(milcontext, s2->id, &n2);
    if (ret) {
	free(n1);
	return ccErrInvalidCCache;
    }

    *out_equal = (strcmp(n1, n2) == 0);
    
    free(n1);
    free(n2);

    return ccNoError;
}

static cc_int32
ccache_get_kdc_time_offset(cc_ccache_t in_ccache,
			   cc_uint32 in_credentials_version,
			   cc_time_t *out_time_offset)
{
    struct cc_ccache *c = (struct cc_ccache *)in_ccache;
    krb5_deltat sec = 0;

    LOG_ENTRY();

    if (c->id == NULL)
	return LOG_FAILURE(ccErrBadParam, "bad credential");
    if (in_credentials_version != cc_credentials_v5)
	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");
    if (out_time_offset == NULL)
	return LOG_FAILURE(ccErrBadParam, "bad argument");

    heim_krb5_cc_get_kdc_offset(milcontext, c->id, &sec);
    *out_time_offset = (cc_time_t)sec;

    return ccNoError;
}

static cc_int32
ccache_set_kdc_time_offset(cc_ccache_t io_ccache, cc_uint32   in_credentials_version, cc_time_t   in_time_offset)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    LOG_ENTRY();

    if (c->id == NULL)
	return LOG_FAILURE(ccErrBadParam, "bad credential");

    if (in_credentials_version != cc_credentials_v5)
	return LOG_FAILURE(ccErrBadCredentialsVersion, "wrong version");

    heim_krb5_cc_set_kdc_offset(milcontext, c->id, in_time_offset);

    return ccNoError;
}

static cc_int32
ccache_clear_kdc_time_offset(cc_ccache_t io_ccache, cc_uint32   in_credentials_version)
{
    struct cc_ccache *c = (struct cc_ccache *)io_ccache;
    LOG_ENTRY();

    if (c->id == NULL)
	return LOG_FAILURE(ccErrBadParam, "bad credential");

    heim_krb5_cc_set_kdc_offset(milcontext, c->id, 0);

    return ccNoError;
}

static cc_int32
ccache_wait_for_change(cc_ccache_t in_ccache)
{
    LOG_UNIMPLEMENTED();
    return ccErrNoMem;
}

static cc_ccache_f ccache_functions = {
    .release = ccache_release,
    .destroy = ccache_destroy,
    .set_default = ccache_set_default,
    .get_credentials_version = ccache_get_credentials_version,
    .get_name = ccache_get_name,
    .get_principal = ccache_get_principal,
    .set_principal = ccache_set_principal,
    .store_credentials = ccache_store_credentials,
    .remove_credentials = ccache_remove_credentials,
    .new_credentials_iterator = ccache_new_credentials_iterator,
    .move = ccache_move,
    .lock = ccache_lock,
    .unlock = ccache_unlock,
    .get_last_default_time = ccache_get_last_default_time,
    .get_change_time = ccache_get_change_time,
    .compare = ccache_compare,
    .get_kdc_time_offset = ccache_get_kdc_time_offset,
    .set_kdc_time_offset = ccache_set_kdc_time_offset,
    .clear_kdc_time_offset = ccache_clear_kdc_time_offset,
    .wait_for_change = ccache_wait_for_change
};

static cc_ccache_t
create_ccache(krb5_ccache id)
{
    struct cc_ccache *c;

    c = mshim_malloc(sizeof(*c));
    c->ccache.functions = &ccache_functions;
    c->id = id;
    update_time(&c->change_time);
    return (cc_ccache_t)c;
}

struct cc_iter {
    cc_ccache_iterator_d iterator;
    mit_krb5_cccol_cursor cursor;
};

static cc_int32
cc_iterator_release(cc_ccache_iterator_t io_ccache_iterator)
{
    struct cc_iter *c = (struct cc_iter *)io_ccache_iterator;
    LOG_ENTRY();
    krb5_cccol_cursor_free((mit_krb5_context)milcontext, &c->cursor);
    free(c);
    return ccNoError;
}

cc_int32
cc_iterator_next(cc_ccache_iterator_t  in_ccache_iterator,
		 cc_ccache_t *out_ccache)
{
    struct cc_iter *c = (struct cc_iter *)in_ccache_iterator;
    krb5_error_code ret;
    krb5_ccache id;

    LOG_ENTRY();

    if (out_ccache == NULL)
	return ccErrBadParam;


    while (1) {
	ret = krb5_cccol_cursor_next((mit_krb5_context)milcontext, c->cursor, (mit_krb5_ccache *)&id);
	if (ret == KRB5_CC_END || id == NULL)
	    return ccIteratorEnd;
	else if (ret)
	    return LOG_FAILURE(ret, "ccol next cursor");

	const char *type = heim_krb5_cc_get_type(milcontext, id);
	if (strcmp(type, "API") == 0 || strcmp(type, "KCM") == 0)
	    break;
	heim_krb5_cc_close(milcontext, id);
    }
    *out_ccache = create_ccache(id);

    return ccNoError;
}

static cc_int32
cc_iterator_clone(cc_ccache_iterator_t  in_ccache_iterator,
		  cc_ccache_iterator_t *out_ccache_iterator)
{
    LOG_UNIMPLEMENTED();
    if (out_ccache_iterator == NULL)
	return ccErrBadParam;
    return ccErrNoMem;
}

static cc_ccache_iterator_f ccache_iterator_functions = {
    .release = cc_iterator_release,
    .next = cc_iterator_next,
    .clone = cc_iterator_clone
};




static cc_int32
context_release(cc_context_t io_context)
{
    LOG_ENTRY();
    memset(io_context, 0, sizeof(*io_context));
    free(io_context);

    return ccNoError;
}

static cc_int32
context_get_change_time(cc_context_t  in_context,
			cc_time_t    *out_time)
{
    LOG_ENTRY();
    if (out_time == NULL)
	return ccErrBadParam;
    *out_time = context_change_time;
    return ccNoError;
}

static cc_int32
context_get_default_ccache_name(cc_context_t  in_context,
				cc_string_t  *out_name)
{
    const char *name;
    name = krb5_cc_default_name((mit_krb5_context)milcontext);
    if (name == NULL)
	return ccErrNoMem; /* XXX */
    if (out_name == NULL)
	return ccErrBadParam;
    if (strncmp("API:", name, 4) == 0)
	name += 4;
    
    *out_name = create_string(name);

    return ccNoError;
}

/*
 * Probe for client principal to make sure the cache really
 * exists.
 */

static cc_int32
check_exists(krb5_ccache id)
{
    krb5_principal princ;
    int ret;

    ret = heim_krb5_cc_get_principal(milcontext, id, &princ);
    if (ret)
	return 0;
    heim_krb5_free_principal(milcontext, princ);

    return 1;
}



static cc_int32 
context_open_ccache (cc_context_t  in_context,
		     const char   *in_name,
		     cc_ccache_t  *out_ccache)
{
    char *name;
    krb5_error_code ret;
    krb5_ccache id;

    if (out_ccache == NULL || in_name == NULL || in_context == NULL)
	return ccErrBadParam;

    asprintf(&name, "API:%s", in_name);

    ret = heim_krb5_cc_resolve(milcontext, name, &id);
    free(name);
    if (ret)
	return LOG_FAILURE(ret, "open cache");

    if (!check_exists(id)) {
	heim_krb5_cc_close(milcontext, id);
	return ccErrCCacheNotFound;
    }

    *out_ccache = create_ccache(id);

    return ccNoError;
}
    
static cc_int32
context_open_default_ccache(cc_context_t  in_context,
			    cc_ccache_t  *out_ccache)
{
    krb5_error_code ret;
    krb5_ccache id;

    LOG_ENTRY();

    if (out_ccache == NULL)
	return ccErrBadParam;

    ret = heim_krb5_cc_default(milcontext, &id);
    if (ret)
	return LOG_FAILURE(ret, "cc default");

    if (!check_exists(id)) {
	heim_krb5_cc_close(milcontext, id);
	return ccErrCCacheNotFound;
    }

    *out_ccache = create_ccache(id);

    return ccNoError;
}

static cc_int32 
context_create_ccache(cc_context_t  in_context,
		      const char   *in_name,
		      cc_uint32     in_cred_vers,
		      const char   *in_principal, 
		      cc_ccache_t  *out_ccache)
{
    krb5_principal principal;
    krb5_error_code ret;
    krb5_ccache id;

    if (in_cred_vers != cc_credentials_v5)
	return ccErrBadCredentialsVersion;
    if (out_ccache == NULL || in_name == NULL || in_context == NULL || in_principal == NULL)
	return ccErrBadParam;

    update_time(&context_change_time);

    ret = heim_krb5_parse_name(milcontext, in_principal, &principal);
    if (ret)
	return LOG_FAILURE(ret, "parse name");

    ret = heim_krb5_cc_resolve(milcontext, in_name, &id);
    if (ret) {
	heim_krb5_free_principal(milcontext, principal);
	return LOG_FAILURE(ret, "open cache");
    }

    ret = heim_krb5_cc_initialize(milcontext, id, principal);
    heim_krb5_free_principal(milcontext, principal);
    if (ret) {
	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)id);
	return LOG_FAILURE(ret, "cc init");
    }

    *out_ccache = create_ccache(id);

    return ccNoError;
}

static cc_int32
context_create_default_ccache(cc_context_t  in_context,
			      cc_uint32     in_cred_vers,
			      const char   *in_principal, 
			      cc_ccache_t  *out_ccache)
{
    krb5_principal principal;
    krb5_error_code ret;
    struct cc_ccache *c;
    krb5_ccache id;

    LOG_ENTRY();

    if (in_cred_vers != cc_credentials_v5)
	return ccErrBadCredentialsVersion;
    if (out_ccache == NULL || in_principal == NULL)
	return ccErrBadParam;

    *out_ccache = NULL;

    update_time(&context_change_time);

    ret = heim_krb5_cc_default(milcontext, &id);
    if (ret)
	return LOG_FAILURE(ret, "cc default");

    ret = heim_krb5_parse_name(milcontext, in_principal, &principal);
    if (ret) {
	heim_krb5_cc_close(milcontext, id);
	return LOG_FAILURE(ret, "parse name");
    }
    
    ret = heim_krb5_cc_initialize(milcontext, id, principal);
    heim_krb5_free_principal(milcontext, principal);
    if (ret) {
	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)id);
	return LOG_FAILURE(ret, "cc init");
    }

    c = (struct cc_ccache *)create_ccache(id);

    update_time(&c->last_default_time);

    *out_ccache = (cc_ccache_t)c;

    return ccNoError;
}

static cc_int32
context_create_new_ccache(cc_context_t in_context,
			  cc_uint32    in_cred_vers,
			  const char  *in_principal, 
			  cc_ccache_t *out_ccache)
{
    krb5_principal principal;
    krb5_error_code ret;
    krb5_ccache id;

    LOG_ENTRY();
    
    if (in_cred_vers != cc_credentials_v5)
	return ccErrBadCredentialsVersion;

    if (out_ccache == NULL || in_principal == NULL)
	return ccErrBadParam;

    update_time(&context_change_time);

    ret = heim_krb5_parse_name(milcontext, in_principal, &principal);
    if (ret)
	return LOG_FAILURE(ret, "parse name");

    ret = heim_krb5_cc_new_unique(milcontext, NULL, NULL, &id);
    if (ret) {
	heim_krb5_free_principal(milcontext, principal);
	return LOG_FAILURE(ret, "new unique");
    }
    
    ret = heim_krb5_cc_initialize(milcontext, id, principal);
    heim_krb5_free_principal(milcontext, principal);
    if (ret) {
	krb5_cc_destroy((mit_krb5_context)milcontext, (mit_krb5_ccache)id);
	return LOG_FAILURE(ret, "cc init");
    }

    *out_ccache = create_ccache(id);

    return ccNoError;
}

static cc_int32
context_new_ccache_iterator(cc_context_t in_context,
			    cc_ccache_iterator_t *out_iterator)
{
    LOG_ENTRY();

    krb5_error_code ret;
    struct cc_iter *c;

    if (out_iterator == NULL)
	return ccErrBadParam;

    c = calloc(1, sizeof(*c));
    c->iterator.functions = &ccache_iterator_functions;

    ret = krb5_cccol_cursor_new((mit_krb5_context)milcontext, &c->cursor);
    if (ret) {
	free(c);
	return ccErrNoMem;
    }

    *out_iterator = (cc_ccache_iterator_t)c;

    return ccNoError;
}

static cc_int32
context_lock(cc_context_t in_context,
	     cc_uint32    in_lock_type,
	     cc_uint32    in_block)
{
    LOG_UNIMPLEMENTED();
    return ccNoError;
}
    
static cc_int32
context_unlock(cc_context_t in_context)
{
    LOG_UNIMPLEMENTED();
    return ccNoError;
}

static cc_int32
context_compare(cc_context_t  in_cc_context,
		cc_context_t  in_compare_to_context,
		cc_uint32    *out_equal)
{
    LOG_UNIMPLEMENTED();
    if (out_equal == NULL || in_compare_to_context == NULL)
	return ccErrBadParam;
    *out_equal = (in_cc_context == in_compare_to_context);
    return 0;
}

static cc_int32
context_wait_for_change(cc_context_t in_cc_context)
{
    LOG_UNIMPLEMENTED();
    return ccErrNoMem;
}



cc_context_f cc_functions = {
    .release = context_release,
    .get_change_time = context_get_change_time,
    .get_default_ccache_name = context_get_default_ccache_name,
    .open_ccache = context_open_ccache,
    .open_default_ccache = context_open_default_ccache,
    .create_ccache = context_create_ccache,
    .create_default_ccache = context_create_default_ccache,
    .create_new_ccache = context_create_new_ccache,
    .new_ccache_iterator = context_new_ccache_iterator,
    .lock = context_lock,
    .unlock = context_unlock,
    .compare = context_compare,
    .wait_for_change = context_wait_for_change
};


cc_int32
cc_initialize(cc_context_t  *out_context,
	      cc_int32       in_version,
	      cc_int32      *out_supported_version,
	      char const   **out_vendor)
{
    LOG_ENTRY();

    update_time(&context_change_time);

    if (in_version < ccapi_version_3 || in_version > ccapi_version_7)
	return ccErrBadAPIVersion;
    if (out_context == NULL)
	return ccErrBadParam;

    *out_context = calloc(1, sizeof(**out_context));
    (*out_context)->functions = &cc_functions;

    if (out_supported_version)
	*out_supported_version = ccapi_version_7;
    if (out_vendor)
	*out_vendor = "Apple Heimdal shim layer";

    return 0;
}