stdcc.c.orig   [plain text]


/*
 * stdcc.c - additions to the Kerberos 5 library to support the memory
 *	 credentical cache API
 *	
 * Written by Frank Dabek July 1998
 * Updated by Jeffrey Altman June 2006
 *
 * Copyright 1998, 1999, 2006 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 * 
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 * 
 */

#if defined(_WIN32) || defined(USE_CCAPI)

#include "k5-int.h"
#include "stdcc.h"
#include "stdcc_util.h"
#include "string.h"
#include <stdio.h>

#if defined(_WIN32)
#include "winccld.h"	
#endif

#ifndef CC_API_VER2
#define CC_API_VER2
#endif

#ifdef DEBUG
#if defined(_WIN32)
#include <io.h>
#define SHOW_DEBUG(buf)   MessageBox((HWND)NULL, (buf), "ccapi debug", MB_OK)
#endif
	/* XXX need macintosh debugging statement if we want to debug */
	/* on the mac */
#else
#define SHOW_DEBUG(buf)
#endif

#ifdef USE_CCAPI_V3
cc_context_t gCntrlBlock = NULL;
cc_int32 gCCVersion = 0;
#else
apiCB *gCntrlBlock = NULL;
#endif

/*
 * declare our global object wanna-be
 * must be installed in ccdefops.c
 */

krb5_cc_ops krb5_cc_stdcc_ops = {
      0,
      "API",
#ifdef USE_CCAPI_V3
      krb5_stdccv3_get_name,
      krb5_stdccv3_resolve,
      krb5_stdccv3_generate_new,
      krb5_stdccv3_initialize,
      krb5_stdccv3_destroy,
      krb5_stdccv3_close,
      krb5_stdccv3_store,
      krb5_stdccv3_retrieve,
      krb5_stdccv3_get_principal,
      krb5_stdccv3_start_seq_get,
      krb5_stdccv3_next_cred,
      krb5_stdccv3_end_seq_get,
      krb5_stdccv3_remove, 
      krb5_stdccv3_set_flags,
      krb5_stdccv3_get_flags,
      krb5_stdccv3_ptcursor_new,
      krb5_stdccv3_ptcursor_next,
      krb5_stdccv3_ptcursor_free,
      NULL, /* move */
      krb5_stdccv3_last_change_time, /* lastchange */
      NULL, /* wasdefault */
      krb5_stdccv3_lock,
      krb5_stdccv3_unlock,
#else
      krb5_stdcc_get_name,
      krb5_stdcc_resolve,
      krb5_stdcc_generate_new,
      krb5_stdcc_initialize,
      krb5_stdcc_destroy,
      krb5_stdcc_close,
      krb5_stdcc_store,
      krb5_stdcc_retrieve,
      krb5_stdcc_get_principal,
      krb5_stdcc_start_seq_get,
      krb5_stdcc_next_cred,
      krb5_stdcc_end_seq_get,
      krb5_stdcc_remove, 
      krb5_stdcc_set_flags,
      krb5_stdcc_get_flags,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
#endif
};

#if defined(_WIN32)
/*
 * cache_changed be called after the cache changes.
 * A notification message is is posted out to all top level
 * windows so that they may recheck the cache based on the
 * changes made.  We register a unique message type with which
 * we'll communicate to all other processes. 
 */
static void cache_changed()
{
	static unsigned int message = 0;
	
	if (message == 0)
		message = RegisterWindowMessage(WM_KERBEROS5_CHANGED);

	PostMessage(HWND_BROADCAST, message, 0, 0);
}
#else /* _WIN32 */

static void cache_changed()
{
	return;
}
#endif /* _WIN32 */

struct err_xlate
{
	int	cc_err;
	krb5_error_code	krb5_err;
};

static const struct err_xlate err_xlate_table[] =
{
#ifdef USE_CCAPI_V3
        { ccIteratorEnd,			KRB5_CC_END },
        { ccErrBadParam,			KRB5_FCC_INTERNAL },
        { ccErrNoMem, 				KRB5_CC_NOMEM },
        { ccErrInvalidContext, 			KRB5_FCC_NOFILE },
        { ccErrInvalidCCache,			KRB5_FCC_NOFILE },
        { ccErrInvalidString,			KRB5_FCC_INTERNAL },
        { ccErrInvalidCredentials,		KRB5_FCC_INTERNAL },
        { ccErrInvalidCCacheIterator,		KRB5_FCC_INTERNAL },
        { ccErrInvalidCredentialsIterator,	KRB5_FCC_INTERNAL },
        { ccErrInvalidLock,			KRB5_FCC_INTERNAL },
        { ccErrBadName,				KRB5_CC_BADNAME },
        { ccErrBadCredentialsVersion,		KRB5_FCC_INTERNAL },
        { ccErrBadAPIVersion,			KRB5_FCC_INTERNAL },
        { ccErrContextLocked,			KRB5_FCC_INTERNAL },
        { ccErrContextUnlocked,			KRB5_FCC_INTERNAL },
        { ccErrCCacheLocked,			KRB5_FCC_INTERNAL },
        { ccErrCCacheUnlocked,			KRB5_FCC_INTERNAL },
        { ccErrBadLockType,			KRB5_FCC_INTERNAL },
        { ccErrNeverDefault,			KRB5_FCC_INTERNAL },
        { ccErrCredentialsNotFound,		KRB5_CC_NOTFOUND },
        { ccErrCCacheNotFound,			KRB5_FCC_NOFILE },
        { ccErrContextNotFound,			KRB5_FCC_NOFILE },
        { ccErrServerUnavailable,		KRB5_CC_IO },
        { ccErrServerInsecure,			KRB5_CC_IO },
        { ccErrServerCantBecomeUID,		KRB5_CC_IO },
        { ccErrTimeOffsetNotSet,		KRB5_FCC_INTERNAL },
        { ccErrBadInternalMessage,		KRB5_FCC_INTERNAL },
        { ccErrNotImplemented,			KRB5_FCC_INTERNAL },
#else
	{ CC_BADNAME,				KRB5_CC_BADNAME },
	{ CC_NOTFOUND,				KRB5_CC_NOTFOUND },
	{ CC_END,				KRB5_CC_END },
	{ CC_IO,				KRB5_CC_IO },
	{ CC_WRITE,				KRB5_CC_WRITE },
	{ CC_NOMEM,				KRB5_CC_NOMEM },
	{ CC_FORMAT,				KRB5_CC_FORMAT },
	{ CC_WRITE,				KRB5_CC_WRITE },
	{ CC_LOCKED,				KRB5_FCC_INTERNAL /* XXX */ },
	{ CC_BAD_API_VERSION,			KRB5_FCC_INTERNAL /* XXX */ },
	{ CC_NO_EXIST,				KRB5_FCC_NOFILE },
	{ CC_NOT_SUPP,				KRB5_FCC_INTERNAL /* XXX */ },
	{ CC_BAD_PARM,				KRB5_FCC_INTERNAL /* XXX */ },
	{ CC_ERR_CACHE_ATTACH,			KRB5_FCC_INTERNAL /* XXX */ },
	{ CC_ERR_CACHE_RELEASE,			KRB5_FCC_INTERNAL /* XXX */ },
	{ CC_ERR_CACHE_FULL,			KRB5_FCC_INTERNAL /* XXX */ },
	{ CC_ERR_CRED_VERSION,			KRB5_FCC_INTERNAL /* XXX */ },
#endif
	{ 0,					0 }
};

/* Note: cc_err_xlate is NOT idempotent.  Don't call it multiple times.  */
static krb5_error_code cc_err_xlate(int err)
{
    const struct err_xlate *p;
    
#ifdef USE_CCAPI_V3
    if (err == ccNoError)
        return 0;
#else
    if (err == CC_NOERROR)
        return 0;
#endif
    
    for (p = err_xlate_table; p->cc_err; p++) {
        if (err == p->cc_err)
            return p->krb5_err;
    }
    
    return KRB5_FCC_INTERNAL;
}


#ifdef USE_CCAPI_V3

static krb5_error_code stdccv3_get_timeoffset (krb5_context in_context,
                                               cc_ccache_t  in_ccache)
{
    krb5_error_code err = 0;
    
    if (gCCVersion >= ccapi_version_5) {
        krb5_os_context os_ctx = (krb5_os_context) &in_context->os_context;
        cc_time_t time_offset = 0;
    
        err = cc_ccache_get_kdc_time_offset (in_ccache, cc_credentials_v5,
                                             &time_offset);
    
        if (!err) {
            os_ctx->time_offset = time_offset;
            os_ctx->usec_offset = 0;
            os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) |
                                KRB5_OS_TOFFSET_VALID);
        }
        
        if (err == ccErrTimeOffsetNotSet) {
            err = 0;  /* okay if there is no time offset */
        }
    }
    
    return err; /* Don't translate.  Callers will translate for us */
}

static krb5_error_code stdccv3_set_timeoffset (krb5_context in_context,
                                               cc_ccache_t  in_ccache)
{
    krb5_error_code err = 0;
    
    if (gCCVersion >= ccapi_version_5) {
        krb5_os_context os_ctx = (krb5_os_context) &in_context->os_context;
        
        if (!err && os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
            err = cc_ccache_set_kdc_time_offset (in_ccache, 
                                                 cc_credentials_v5,
                                                 os_ctx->time_offset);
        }
    }
    
    return err; /* Don't translate.  Callers will translate for us */
}

static krb5_error_code stdccv3_setup (krb5_context context,
                                      stdccCacheDataPtr ccapi_data)
{
    krb5_error_code err = 0;
    
    if (!err && !gCntrlBlock) {
        err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL);
    }
    
    if (!err && ccapi_data && !ccapi_data->NamedCache) {
        /* ccache has not been opened yet.  open it. */  
        err = cc_context_open_ccache (gCntrlBlock, ccapi_data->cache_name,
                                      &ccapi_data->NamedCache);
    }
    
    if (!err && ccapi_data && ccapi_data->NamedCache) {
        err = stdccv3_get_timeoffset (context, ccapi_data->NamedCache);
    }
    
    return err; /* Don't translate.  Callers will translate for us */
}

/* krb5_stdcc_shutdown is exported; use the old name */
void krb5_stdcc_shutdown()
{
    if (gCntrlBlock) { cc_context_release(gCntrlBlock); }
    gCntrlBlock = NULL;
    gCCVersion = 0;
}

/*
 * -- generate_new --------------------------------
 * 
 * create a new cache with a unique name, corresponds to creating a
 * named cache initialize the API here if we have to.
 */
krb5_error_code KRB5_CALLCONV  
krb5_stdccv3_generate_new (krb5_context context, krb5_ccache *id ) 
{
    krb5_error_code err = 0;
    krb5_ccache newCache = NULL;
    stdccCacheDataPtr ccapi_data = NULL;
    cc_ccache_t ccache = NULL;
    cc_string_t ccstring = NULL;
    char *name = NULL;
    
    if (!err) {
        err = stdccv3_setup(context, NULL);
    }
    
    if (!err) {
        newCache = (krb5_ccache) malloc (sizeof (*newCache));
        if (!newCache) { err = KRB5_CC_NOMEM; }
    }
    
    if (!err) {
        ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data));
        if (!ccapi_data) { err = KRB5_CC_NOMEM; }
    }
    
    if (!err) {
        err = cc_context_create_new_ccache (gCntrlBlock, cc_credentials_v5, "",
                                            &ccache);
    }
    
    if (!err) {
        err = stdccv3_set_timeoffset (context, ccache);
    }
    
    if (!err) {
        err = cc_ccache_get_name (ccache, &ccstring);
    }
    
    if (!err) {
        name = strdup (ccstring->data);
        if (!name) { err = KRB5_CC_NOMEM; }
    }
    
    if (!err) {
        ccapi_data->cache_name = name;
        name = NULL; /* take ownership */
        
        ccapi_data->NamedCache = ccache;
        ccache = NULL; /* take ownership */
        
        newCache->ops = &krb5_cc_stdcc_ops;
        newCache->data = ccapi_data;
        ccapi_data = NULL; /* take ownership */
        
        /* return a pointer to the new cache */
        *id = newCache;
        newCache = NULL;
    }
    
    if (ccstring)   { cc_string_release (ccstring); }
    if (name)       { free (name); }
    if (ccache)     { cc_ccache_release (ccache); }
    if (ccapi_data) { free (ccapi_data); }
    if (newCache)   { free (newCache); }
    
    return cc_err_xlate (err);
}
  
/*
 * resolve
 *
 * create a new cache with the name stored in residual
 */
krb5_error_code KRB5_CALLCONV  
krb5_stdccv3_resolve (krb5_context context, krb5_ccache *id , const char *residual ) 
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = NULL;
    krb5_ccache ccache = NULL;
    char *name = NULL;
    
    if (id == NULL) { err = KRB5_CC_NOMEM; }
    
    if (!err) {
        err = stdccv3_setup (context, NULL);
    }
    
    if (!err) {
        ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data));
        if (!ccapi_data) { err = KRB5_CC_NOMEM; }
    }
    
    if (!err) {
        ccache = (krb5_ccache ) malloc (sizeof (*ccache));
        if (!ccache) { err = KRB5_CC_NOMEM; }
    }
    
    if (!err) {
        name = strdup (residual);
        if (!name) { err = KRB5_CC_NOMEM; }
    }
    
    if (!err) {
        err = cc_context_open_ccache (gCntrlBlock, residual,
                                      &ccapi_data->NamedCache);
        if (err == ccErrCCacheNotFound) {
            ccapi_data->NamedCache = NULL;
            err = 0; /* ccache just doesn't exist yet */
        }
    }

    if (!err) {
	ccapi_data->cache_name = name;
        name = NULL; /* take ownership */

  	ccache->ops = &krb5_cc_stdcc_ops;
	ccache->data = ccapi_data;
        ccapi_data = NULL; /* take ownership */
        
        *id = ccache;
        ccache = NULL; /* take ownership */
    }
    
    if (ccache)     { free (ccache); }
    if (ccapi_data) { free (ccapi_data); }
    if (name)       { free (name); }
    
    return cc_err_xlate (err);
}
  
/*
 * initialize
 *
 * initialize the cache, check to see if one already exists for this
 * principal if not set our principal to this principal. This
 * searching enables ticket sharing
 */
krb5_error_code KRB5_CALLCONV  
krb5_stdccv3_initialize (krb5_context context, 
                         krb5_ccache id,  
                         krb5_principal princ) 
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    char *name = NULL;
    cc_ccache_t ccache = NULL;
    
    if (id == NULL) { err = KRB5_CC_NOMEM; }
    
    if (!err) {
        err = stdccv3_setup (context, NULL);
    }
    
    if (!err) {
        err = krb5_unparse_name(context, princ, &name);
    }
    
    if (!err) {
        err = cc_context_create_ccache (gCntrlBlock, ccapi_data->cache_name, 
                                        cc_credentials_v5, name,
                                        &ccache);
    }
    
    if (!err) {
        err = stdccv3_set_timeoffset (context, ccache);
    }
    
    if (!err) {
        if (ccapi_data->NamedCache) {
            err = cc_ccache_release (ccapi_data->NamedCache);
        }
        ccapi_data->NamedCache = ccache;
        ccache = NULL; /* take ownership */
        cache_changed ();
    }
    
    if (ccache) { cc_ccache_release (ccache); }
    if (name  ) { krb5_free_unparsed_name(context, name); }
    
    return cc_err_xlate(err);
}

/*
 * store
 *
 * store some credentials in our cache
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_store (krb5_context context, krb5_ccache id, krb5_creds *creds )
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    cc_credentials_union *cred_union = NULL;
    
    if (!err) {
        err = stdccv3_setup (context, ccapi_data);
    }
    
    if (!err) {
        /* copy the fields from the almost identical structures */
        err = copy_krb5_creds_to_cc_cred_union (context, creds, &cred_union);
    }
    
    if (!err) {
        err = cc_ccache_store_credentials (ccapi_data->NamedCache, cred_union);
    }
    
    if (!err) {
        cache_changed();
    }
    
    if (cred_union) { cred_union_release (cred_union); }
    
    return cc_err_xlate (err);
}

/*
 * start_seq_get
 *
 * begin an iterator call to get all of the credentials in the cache
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_start_seq_get (krb5_context context, 
                            krb5_ccache id, 
                            krb5_cc_cursor *cursor )
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    cc_credentials_iterator_t iterator = NULL;
    
    if (!err) {
        err = stdccv3_setup (context, ccapi_data);
    }
    
    if (!err) {
        err = cc_ccache_new_credentials_iterator(ccapi_data->NamedCache,
                                                 &iterator);
    }
    
    if (!err) {
        *cursor = iterator;
    }
    
    return cc_err_xlate (err);
}

/*
 * next cred
 * 
 * - get the next credential in the cache as part of an iterator call
 * - this maps to call to cc_seq_fetch_creds
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_next_cred (krb5_context context, 
                        krb5_ccache id, 
                        krb5_cc_cursor *cursor, 
                        krb5_creds *creds)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    cc_credentials_t credentials = NULL;
    cc_credentials_iterator_t iterator = *cursor;
    
    if (!iterator) { err = KRB5_CC_END; }
    
    if (!err) {
        err = stdccv3_setup (context, ccapi_data);
    }
    
    /* Note: CCAPI v3 ccaches can contain both v4 and v5 creds */
    while (!err) {
        err = cc_credentials_iterator_next (iterator, &credentials);

        if (!err && (credentials->data->version == cc_credentials_v5)) {
            copy_cc_cred_union_to_krb5_creds(context, credentials->data, creds);
            break;
        }
    }
    
    if (credentials) { cc_credentials_release (credentials); }
    if (err == ccIteratorEnd) {
        cc_credentials_iterator_release (iterator);
        *cursor = 0;
    }    
    
    return cc_err_xlate (err);
}


/*
 * retrieve
 *
 * - try to find a matching credential in the cache
 */
krb5_error_code KRB5_CALLCONV
krb5_stdccv3_retrieve (krb5_context context, 
                       krb5_ccache id, 
                       krb5_flags whichfields, 
                       krb5_creds *mcreds, 
                       krb5_creds *creds)
{
    return krb5_cc_retrieve_cred_default (context, id, whichfields,
					  mcreds, creds);
}

/*
 *  end seq
 *
 * just free up the storage assoicated with the cursor (if we can)
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_end_seq_get (krb5_context context, 
                          krb5_ccache id, 
                          krb5_cc_cursor *cursor)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    cc_credentials_iterator_t iterator = *cursor;
    
    if (!iterator) { return 0; }
    
    if (!err) {
        err = stdccv3_setup (context, ccapi_data);
    }
    
    if (!err) {
        err = cc_credentials_iterator_release(iterator);
    }
    
    return cc_err_xlate(err);
}
     
/*
 * close
 *
 * - free our pointers to the NC
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_close(krb5_context context, 
                   krb5_ccache id)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    
    if (!err) {
        err = stdccv3_setup (context, NULL);
    }
    
    if (!err) {
	if (ccapi_data) {
            if (ccapi_data->cache_name) { 
                free (ccapi_data->cache_name); 
            }
            if (ccapi_data->NamedCache) { 
                err = cc_ccache_release (ccapi_data->NamedCache); 
            }
            free (ccapi_data);
            id->data = NULL;
	}
	free (id);        
    }
    
    return cc_err_xlate(err);
}

/*
 * destroy
 *
 * - free our storage and the cache
 */
krb5_error_code KRB5_CALLCONV
krb5_stdccv3_destroy (krb5_context context, 
                      krb5_ccache id)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
   
    if (!err) {
        err = stdccv3_setup(context, ccapi_data);
    }
    
    if (!err) {
	if (ccapi_data) {
            if (ccapi_data->cache_name) { 
                free(ccapi_data->cache_name); 
            }
            if (ccapi_data->NamedCache) {
                /* destroy the named cache */
                err = cc_ccache_destroy(ccapi_data->NamedCache);
                if (err == ccErrCCacheNotFound) { 
                    err = 0; /* ccache maybe already destroyed */
                }
                cache_changed();
            }
            free(ccapi_data);
            id->data = NULL;
	}
	free(id);        
    }
    
    return cc_err_xlate(err);
}

/*
 *  getname
 *
 * - return the name of the named cache
 */
const char * KRB5_CALLCONV 
krb5_stdccv3_get_name (krb5_context context, 
                       krb5_ccache id )
{
    stdccCacheDataPtr ccapi_data = id->data;
    
    if (!ccapi_data) {
        return NULL;
    } else {
        return (ccapi_data->cache_name);
    }
}


/* get_principal
 *
 * - return the principal associated with the named cache
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_get_principal (krb5_context context, 
                            krb5_ccache id , 
                            krb5_principal *princ) 
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    cc_string_t name = NULL;
    
    if (!err) {
        err = stdccv3_setup(context, ccapi_data);
    }
    
    if (!err) {
        err = cc_ccache_get_principal (ccapi_data->NamedCache, cc_credentials_v5, &name);
    }
    
    if (!err) {
        err = krb5_parse_name (context, name->data, princ);
    }
    
    if (name) { cc_string_release (name); }
    
    return cc_err_xlate (err);
}

/*
 * set_flags
 *
 * - currently a NOP since we don't store any flags in the NC
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_set_flags (krb5_context context, 
                        krb5_ccache id, 
                        krb5_flags flags)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    
    err = stdccv3_setup (context, ccapi_data);
    
    return cc_err_xlate (err);
}

/*
 * get_flags
 *
 * - currently a NOP since we don't store any flags in the NC
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_get_flags (krb5_context context, 
                        krb5_ccache id, 
                        krb5_flags *flags)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    
    err = stdccv3_setup (context, ccapi_data);
    
    return cc_err_xlate (err);
}

/*
 * remove
 *
 * - remove the specified credentials from the NC
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdccv3_remove (krb5_context context, 
                     krb5_ccache id,
                     krb5_flags flags, 
                     krb5_creds *in_creds)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    cc_credentials_iterator_t iterator = NULL;
    int found = 0;
    
    if (!err) {
        err = stdccv3_setup(context, ccapi_data);
    }
    
    
    if (!err) {
        err = cc_ccache_new_credentials_iterator(ccapi_data->NamedCache,
                                                 &iterator);
    }

    /* Note: CCAPI v3 ccaches can contain both v4 and v5 creds */
    while (!err && !found) {
        cc_credentials_t credentials = NULL;
        
        err = cc_credentials_iterator_next (iterator, &credentials);
        
        if (!err && (credentials->data->version == cc_credentials_v5)) {
            krb5_creds creds;
            
            err = copy_cc_cred_union_to_krb5_creds(context, 
                                                   credentials->data, &creds);

            if (!err) {
                found = krb5_creds_compare (context, in_creds, &creds);
                krb5_free_cred_contents (context, &creds);
            }
            
            if (!err && found) {
                err = cc_ccache_remove_credentials (ccapi_data->NamedCache, credentials);
            }
        }
        
        if (credentials) { cc_credentials_release (credentials); }
    }
    if (err == ccIteratorEnd) { err = ccErrCredentialsNotFound; }    

    if (!err) {
        cache_changed ();
    }
    
    return cc_err_xlate (err);
}

krb5_error_code KRB5_CALLCONV
krb5_stdccv3_ptcursor_new(krb5_context context,
                          krb5_cc_ptcursor *cursor)
{
	krb5_error_code err = 0;
	krb5_cc_ptcursor ptcursor = NULL;
	cc_ccache_iterator_t iterator = NULL;
	
	ptcursor = malloc(sizeof(*ptcursor));
	if (ptcursor == NULL) {
		err = ENOMEM;
	}
	else {
		memset(ptcursor, 0, sizeof(*ptcursor));
	}
	
	if (!err) {
		err = stdccv3_setup(context, NULL);
	}
	if (!err) {
		ptcursor->ops = &krb5_cc_stdcc_ops;
		err = cc_context_new_ccache_iterator(gCntrlBlock, &iterator);
	}
	
	if (!err) {
		ptcursor->data = iterator;
	}
	
	if (err) {
		if (ptcursor) { krb5_stdccv3_ptcursor_free(context, &ptcursor); }
		// krb5_stdccv3_ptcursor_free sets ptcursor to NULL for us
	}
	
	*cursor = ptcursor;
	
	return err;
}

krb5_error_code KRB5_CALLCONV
krb5_stdccv3_ptcursor_next(
    krb5_context context,
    krb5_cc_ptcursor cursor,
    krb5_ccache *ccache)
{
	krb5_error_code err = 0;
	cc_ccache_iterator_t iterator = NULL;
	
	krb5_ccache newCache = NULL;
	stdccCacheDataPtr ccapi_data = NULL;
	cc_ccache_t ccCache = NULL;
	cc_string_t ccstring = NULL;
	char *name = NULL;
	
	if (!cursor || !cursor->data) {
		err = ccErrInvalidContext;
	}
	
	*ccache = NULL;
	
	if (!err) {
	    newCache = (krb5_ccache) malloc (sizeof (*newCache));
	    if (!newCache) { err = KRB5_CC_NOMEM; }
	}

	if (!err) {
	    ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data));
	    if (!ccapi_data) { err = KRB5_CC_NOMEM; }
	}
	
	if (!err) {
		iterator = cursor->data;
		err = cc_ccache_iterator_next(iterator, &ccCache);
	}
	
	if (!err) {
	    err = cc_ccache_get_name (ccCache, &ccstring);
	}

	if (!err) {
	    name = strdup (ccstring->data);
	    if (!name) { err = KRB5_CC_NOMEM; }
	}
	
	if (!err) {
	    ccapi_data->cache_name = name;
	    name = NULL; /* take ownership */
    
	    ccapi_data->NamedCache = ccCache;
	    ccCache = NULL; /* take ownership */
    
	    newCache->ops = &krb5_cc_stdcc_ops;
	    newCache->data = ccapi_data;
	    ccapi_data = NULL; /* take ownership */
    
	    /* return a pointer to the new cache */
	    *ccache = newCache;
	    newCache = NULL;
	}
 
	if (name)       { free (name); }
	if (ccstring)   { cc_string_release (ccstring); }
	if (ccCache)    { cc_ccache_release (ccCache); }
	if (ccapi_data) { free (ccapi_data); }
	if (newCache)   { free (newCache); }
	
	if (err == ccIteratorEnd) {
		err = ccNoError;
	}
	
	return err;
}

krb5_error_code KRB5_CALLCONV
krb5_stdccv3_ptcursor_free(
    krb5_context context,
    krb5_cc_ptcursor *cursor)
{
    if (*cursor != NULL) {
		if ((*cursor)->data != NULL) {
			cc_ccache_iterator_release((cc_ccache_iterator_t)((*cursor)->data));
		}
	    free(*cursor);
	    *cursor = NULL;
	}
    return 0;
}

krb5_error_code KRB5_CALLCONV krb5_stdccv3_last_change_time
		(krb5_context context, krb5_ccache id,
           krb5_timestamp *change_time)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    cc_time_t ccapi_change_time = 0;

    *change_time = 0;
    
    if (!err) {
        err = stdccv3_setup(context, ccapi_data);
    }
    if (!err) {
        err = cc_ccache_get_change_time (ccapi_data->NamedCache, &ccapi_change_time);
    }
    if (!err) {
        *change_time = ccapi_change_time;
    }
    
    return cc_err_xlate (err);
}

krb5_error_code KRB5_CALLCONV krb5_stdccv3_lock
(krb5_context context, krb5_ccache id)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    
    if (!err) {
        err = stdccv3_setup(context, ccapi_data);
    }
    if (!err) {
        err = cc_ccache_lock(ccapi_data->NamedCache, cc_lock_write, cc_lock_block);
    }
    return cc_err_xlate(err);    
}

krb5_error_code KRB5_CALLCONV krb5_stdccv3_unlock
(krb5_context context, krb5_ccache id)
{
    krb5_error_code err = 0;
    stdccCacheDataPtr ccapi_data = id->data;
    
    if (!err) {
        err = stdccv3_setup(context, ccapi_data);
    }
    if (!err) {
        err = cc_ccache_unlock(ccapi_data->NamedCache);
    }
    return cc_err_xlate(err);    
}

krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_lock
(krb5_context context)
{
    krb5_error_code err = 0;

    if (!err && !gCntrlBlock) {
        err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL);
    }
    if (!err) {
        err = cc_context_lock(gCntrlBlock, cc_lock_write, cc_lock_block);
    }
    return cc_err_xlate(err);    
}

krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_unlock
(krb5_context context)
{
    krb5_error_code err = 0;

    if (!err && !gCntrlBlock) {
        err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL);
    }
    if (!err) {
        err = cc_context_unlock(gCntrlBlock);
    }
    return cc_err_xlate(err);    
}

#else /* !USE_CCAPI_V3 */

static krb5_error_code stdcc_setup(krb5_context context,
				   stdccCacheDataPtr ccapi_data)
{
	int	err;

  	/* make sure the API has been intialized */
  	if (gCntrlBlock == NULL) {
#ifdef CC_API_VER2
		err = cc_initialize(&gCntrlBlock, CC_API_VER_2, NULL, NULL);
#else
		err = cc_initialize(&gCntrlBlock, CC_API_VER_1, NULL, NULL);
#endif
		if (err != CC_NOERROR)
			return cc_err_xlate(err);
	}

	/*
	 * No ccapi_data structure, so we don't need to make sure the
	 * ccache exists.
	 */
	if (!ccapi_data)
		return 0;

	/*
	 * The ccache already exists
	 */
	if (ccapi_data->NamedCache)
		return 0;

	err = cc_open(gCntrlBlock, ccapi_data->cache_name,
		      CC_CRED_V5, 0L, &ccapi_data->NamedCache);
	if (err == CC_NOTFOUND)
	  err = CC_NO_EXIST;
	if (err == CC_NOERROR)
		return 0;

	ccapi_data->NamedCache = NULL;
	return cc_err_xlate(err);
}

void krb5_stdcc_shutdown()
{
	if (gCntrlBlock)
		cc_shutdown(&gCntrlBlock);
	gCntrlBlock = NULL;
}

/*
 * -- generate_new --------------------------------
 * 
 * create a new cache with a unique name, corresponds to creating a
 * named cache iniitialize the API here if we have to.
 */
krb5_error_code KRB5_CALLCONV  krb5_stdcc_generate_new 
	(krb5_context context, krb5_ccache *id ) 
{
  	krb5_ccache 		newCache = NULL;
	krb5_error_code		retval;
	stdccCacheDataPtr	ccapi_data = NULL;
	char 			*name = NULL;
	cc_time_t 		change_time;
	int 			err;

	if ((retval = stdcc_setup(context, NULL)))
		return retval;
	
	retval = KRB5_CC_NOMEM;
	if (!(newCache = (krb5_ccache) malloc(sizeof(struct _krb5_ccache))))
		goto errout;
  	if (!(ccapi_data = (stdccCacheDataPtr)malloc(sizeof(stdccCacheData))))
		goto errout;
	if (!(name = malloc(256)))
		goto errout;
	
  	/* create a unique name */
  	cc_get_change_time(gCntrlBlock, &change_time);
  	snprintf(name, 256, "gen_new_cache%d", change_time);
  	
  	/* create the new cache */
  	err = cc_create(gCntrlBlock, name, name, CC_CRED_V5, 0L,
			&ccapi_data->NamedCache);
	if (err != CC_NOERROR) {
		retval = cc_err_xlate(err);
		goto errout;
	}

  	/* setup some fields */
  	newCache->ops = &krb5_cc_stdcc_ops;
  	newCache->data = ccapi_data;
	ccapi_data->cache_name = name;
  	
  	/* return a pointer to the new cache */
	*id = newCache;
	  	
	return 0;

errout:
	if (newCache)
		free(newCache);
	if (ccapi_data)
		free(ccapi_data);
	if (name)
		free(name);
	return retval;
}
  
/*
 * resolve
 *
 * create a new cache with the name stored in residual
 */
krb5_error_code KRB5_CALLCONV  krb5_stdcc_resolve 
        (krb5_context context, krb5_ccache *id , const char *residual ) 
{
	krb5_ccache 		newCache = NULL;
	stdccCacheDataPtr	ccapi_data = NULL;
	int 			err;
	krb5_error_code		retval;
	char 			*cName = NULL;
	
	if ((retval = stdcc_setup(context, NULL)))
		return retval;
	
	retval = KRB5_CC_NOMEM;
	if (!(newCache = (krb5_ccache) malloc(sizeof(struct _krb5_ccache))))
		goto errout;
  	
  	if (!(ccapi_data = (stdccCacheDataPtr)malloc(sizeof(stdccCacheData))))
		goto errout;

	if (!(cName = strdup(residual)))
		goto errout;
	
  	newCache->ops = &krb5_cc_stdcc_ops;
	newCache->data = ccapi_data;
	ccapi_data->cache_name = cName;

 	err = cc_open(gCntrlBlock, cName, CC_CRED_V5, 0L,
		      &ccapi_data->NamedCache);
        if (err != CC_NOERROR) {
	        ccapi_data->NamedCache = NULL;
	        if (err != CC_NO_EXIST) {
	                retval = cc_err_xlate(err);
	                goto errout;
		}
        }
        
  	/* return new cache structure */
	*id = newCache;
	
  	return 0;
	
errout:
	if (newCache)
		free(newCache);
	if (ccapi_data)
		free(ccapi_data);
	if (cName)
		free(cName);
	return retval;
}
  
/*
 * initialize
 *
 * initialize the cache, check to see if one already exists for this
 * principal if not set our principal to this principal. This
 * searching enables ticket sharing
 */
krb5_error_code KRB5_CALLCONV  krb5_stdcc_initialize 
       (krb5_context context, krb5_ccache id,  krb5_principal princ) 
{
	stdccCacheDataPtr	ccapi_data = NULL;
  	int 			err;
  	char 			*cName = NULL;
	krb5_error_code		retval;
  	
	if ((retval = stdcc_setup(context, NULL)))
		return retval;
	
  	/* test id for null */
  	if (id == NULL) return KRB5_CC_NOMEM;
  	
	if ((retval = krb5_unparse_name(context, princ, &cName)))
		return retval;

	ccapi_data = id->data;


	if (ccapi_data->NamedCache)
		cc_close(gCntrlBlock, &ccapi_data->NamedCache);

	err = cc_create(gCntrlBlock, ccapi_data->cache_name, cName,
			CC_CRED_V5, 0L, &ccapi_data->NamedCache);
	if (err != CC_NOERROR) {
		krb5_free_unparsed_name(context, cName);
		return cc_err_xlate(err);
	}

#if 0
	/*
	 * Some implementations don't set the principal name
	 * correctly, so we force set it to the correct value.
	 */
	err = cc_set_principal(gCntrlBlock, ccapi_data->NamedCache,
			       CC_CRED_V5, cName);
#endif
	krb5_free_unparsed_name(context, cName);
	cache_changed();
	
	return cc_err_xlate(err);
}

/*
 * store
 *
 * store some credentials in our cache
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_store 
        (krb5_context context, krb5_ccache id, krb5_creds *creds )
{
	krb5_error_code	retval;
	stdccCacheDataPtr	ccapi_data = id->data;
	cred_union *cu = NULL;
	int err;

	if ((retval = stdcc_setup(context, ccapi_data)))
		return retval;
	
	/* copy the fields from the almost identical structures */
	dupK5toCC(context, creds, &cu);
			
	/*
	 * finally store the credential
	 * store will copy (that is duplicate) everything
	 */
	err = cc_store(gCntrlBlock,
		       ((stdccCacheDataPtr)(id->data))->NamedCache, *cu);
	if (err != CC_NOERROR)
		return cc_err_xlate(err);
		
	/* free the cred union using our local version of cc_free_creds()
	   since we allocated it locally */
	err = krb5int_free_cc_cred_union(&cu);
		 
	cache_changed();
	return err;
}

/*
 * start_seq_get
 *
 * begin an iterator call to get all of the credentials in the cache
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_start_seq_get 
(krb5_context context, krb5_ccache id , krb5_cc_cursor *cursor )
{
	stdccCacheDataPtr	ccapi_data = id->data;
	krb5_error_code	retval;
	int	err;
	ccache_cit	*iterator;

	if ((retval = stdcc_setup(context, ccapi_data)))
		return retval;

#ifdef CC_API_VER2
	err = cc_seq_fetch_creds_begin(gCntrlBlock, ccapi_data->NamedCache,
				       &iterator);
	if (err != CC_NOERROR)
		return cc_err_xlate(err);
	*cursor = iterator;
#else
	/* all we have to do is initialize the cursor */
	*cursor = NULL;
#endif
	return 0;
}

/*
 * next cred
 * 
 * - get the next credential in the cache as part of an iterator call
 * - this maps to call to cc_seq_fetch_creds
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_next_cred 
        (krb5_context context, krb5_ccache id,  krb5_cc_cursor *cursor, 
	 krb5_creds *creds)
{
	krb5_error_code	retval;
	stdccCacheDataPtr	ccapi_data = id->data;
	int err;
	cred_union *credU = NULL;
	ccache_cit	*iterator;
	
	if ((retval = stdcc_setup(context, ccapi_data)))
		return retval;
	
#ifdef CC_API_VER2
	iterator = *cursor;
	if (iterator == 0)
		return KRB5_CC_END;
	err = cc_seq_fetch_creds_next(gCntrlBlock, &credU, iterator);

	if (err == CC_END) {
		cc_seq_fetch_creds_end(gCntrlBlock, &iterator);
		*cursor = 0;
	}
#else
	err = cc_seq_fetch_creds(gCntrlBlock, ccapi_data->NamedCache,
				 &credU, (ccache_cit **)cursor); 
#endif

	if (err != CC_NOERROR)
		return cc_err_xlate(err);
	
	/* copy data	(with translation) */
	dupCCtoK5(context, credU->cred.pV5Cred, creds);
	
	/* free our version of the cred - okay to use cc_free_creds() here
	   because we got it from the CCache library */
	cc_free_creds(gCntrlBlock, &credU);
	
	return 0;
}


/*
 * retreive
 *
 * - try to find a matching credential in the cache
 */
#if 0
krb5_error_code KRB5_CALLCONV krb5_stdcc_retrieve 
       		(krb5_context context, 
		   krb5_ccache id, 
		   krb5_flags whichfields, 
		   krb5_creds *mcreds, 
		   krb5_creds *creds )
{
	krb5_error_code	retval;
	krb5_cc_cursor curs = NULL;
	krb5_creds *fetchcreds;
		
	if ((retval = stdcc_setup(context, NULL)))
		return retval;
	
	fetchcreds = (krb5_creds *)malloc(sizeof(krb5_creds));
  	if (fetchcreds == NULL) return KRB5_CC_NOMEM;
	
	/* we're going to use the iterators */
	krb5_stdcc_start_seq_get(context, id, &curs);
	
	while (!krb5_stdcc_next_cred(context, id, &curs, fetchcreds)) {
		/*
		 * look at each credential for a match
		 * use this match routine since it takes the
		 * whichfields and the API doesn't
		 */
		if (stdccCredsMatch(context, fetchcreds,
				    mcreds, whichfields)) {
			/* we found it, copy and exit */
			*creds = *fetchcreds;
			krb5_stdcc_end_seq_get(context, id, &curs);
			return 0;
		}
		/* free copy allocated by next_cred */
		krb5_free_cred_contents(context, fetchcreds);
	}
		
	/* no luck, end get and exit */
	krb5_stdcc_end_seq_get(context, id, &curs);
	
	/* we're not using this anymore so we should get rid of it! */
	free(fetchcreds);
	
	return KRB5_CC_NOTFOUND;
}
#else

krb5_error_code KRB5_CALLCONV
krb5_stdcc_retrieve(context, id, whichfields, mcreds, creds)
   krb5_context context;
   krb5_ccache id;
   krb5_flags whichfields;
   krb5_creds *mcreds;
   krb5_creds *creds;
{
    return krb5_cc_retrieve_cred_default (context, id, whichfields,
					  mcreds, creds);
}

#endif

/*
 *  end seq
 *
 * just free up the storage assoicated with the cursor (if we could)
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_end_seq_get 
        (krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
{
	krb5_error_code		retval;
	stdccCacheDataPtr	ccapi_data = NULL;
	int			err;
#ifndef CC_API_VER2
	cred_union 		*credU = NULL;
#endif

	ccapi_data = id->data;
	
	if ((retval = stdcc_setup(context, ccapi_data)))
		return retval;

	if (*cursor == NULL)
		return 0;

#ifdef CC_API_VER2
	err = cc_seq_fetch_creds_end(gCntrlBlock, (ccache_cit **)cursor);
	if (err != CC_NOERROR)
		return cc_err_xlate(err);
#else	
	/*
	 * Finish calling cc_seq_fetch_creds to clear out the cursor
	 */
	while (*cursor) {
		err = cc_seq_fetch_creds(gCntrlBlock, ccapi_data->NamedCache,
				 &credU, (ccache_cit **)cursor);
		if (err)
			break;
		
		/* okay to call cc_free_creds() here because we got credU from CCache lib */
		cc_free_creds(gCntrlBlock, &credU);
	}
#endif
	
	return(0);
}
     
/*
 * close
 *
 * - free our pointers to the NC
 */
krb5_error_code KRB5_CALLCONV 
krb5_stdcc_close(krb5_context context, krb5_ccache id)
{
	krb5_error_code	retval;
	stdccCacheDataPtr	ccapi_data = id->data;

	if ((retval = stdcc_setup(context, NULL)))
		return retval;
	
	/* free it */
	
	if (ccapi_data) {
		if (ccapi_data->cache_name)
			free(ccapi_data->cache_name);
		if (ccapi_data->NamedCache)
			cc_close(gCntrlBlock, &ccapi_data->NamedCache);
		free(ccapi_data);
		id->data = NULL;
	}
	free(id);
	
	return 0;
}

/*
 * destroy
 *
 * - free our storage and the cache
 */
krb5_error_code KRB5_CALLCONV
krb5_stdcc_destroy (krb5_context context, krb5_ccache id)
{
	int err;
	krb5_error_code	retval;
	stdccCacheDataPtr	ccapi_data = id->data;

	if ((retval = stdcc_setup(context, ccapi_data))) {
		return retval;
	}

	/* free memory associated with the krb5_ccache */
	if (ccapi_data) {
		if (ccapi_data->cache_name)
			free(ccapi_data->cache_name);
		if (ccapi_data->NamedCache) {
			/* destroy the named cache */
			err = cc_destroy(gCntrlBlock, &ccapi_data->NamedCache);
			retval = cc_err_xlate(err);
			cache_changed();
		}
		free(ccapi_data);
		id->data = NULL;
	}
	free(id);

	/* If the cache does not exist when we tried to destroy it,
	   that's fine.  That means someone else destryoed it since
	   we resolved it. */
	if (retval == KRB5_FCC_NOFILE)
		return 0;
	return retval;
}

/*
 *  getname
 *
 * - return the name of the named cache
 */
const char * KRB5_CALLCONV krb5_stdcc_get_name 
        (krb5_context context, krb5_ccache id )
{
	stdccCacheDataPtr	ccapi_data = id->data;

	if (!ccapi_data)
		return 0;

	return (ccapi_data->cache_name);
}


/* get_principal
 *
 * - return the principal associated with the named cache
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_get_principal
	(krb5_context context, krb5_ccache id , krb5_principal *princ) 
{
	int 			err;
	char 	  		*name = NULL;
	stdccCacheDataPtr	ccapi_data = id->data;
	krb5_error_code		retval;
	
	if ((retval = stdcc_setup(context, ccapi_data)))
		return retval;

	/* another wrapper */
	err = cc_get_principal(gCntrlBlock, ccapi_data->NamedCache,
			       &name);

	if (err != CC_NOERROR) 
		return cc_err_xlate(err);
		
	/* turn it into a krb principal */
	err = krb5_parse_name(context, name, princ);

	cc_free_principal(gCntrlBlock, &name);
	
	return err;	
}

/*
 * set_flags
 *
 * - currently a NOP since we don't store any flags in the NC
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_set_flags 
        (krb5_context context, krb5_ccache id , krb5_flags flags)
{
	stdccCacheDataPtr	ccapi_data = id->data;
	krb5_error_code		retval;
	
	if ((retval = stdcc_setup(context, ccapi_data)))
		return retval;

	return 0;
}

/*
 * get_flags
 *
 * - currently a NOP since we don't store any flags in the NC
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_get_flags 
        (krb5_context context, krb5_ccache id , krb5_flags *flags)
{
	stdccCacheDataPtr	ccapi_data = id->data;
	krb5_error_code		retval;
	
	if ((retval = stdcc_setup(context, ccapi_data)))
		return retval;

	return 0;
}

/*
 * remove
 *
 * - remove the specified credentials from the NC
 */
krb5_error_code KRB5_CALLCONV krb5_stdcc_remove 
        (krb5_context context, krb5_ccache id,
	 krb5_flags flags, krb5_creds *creds)
{
    	cred_union *cu = NULL;
    	int err;
	stdccCacheDataPtr	ccapi_data = id->data;
	krb5_error_code		retval;
        
	if ((retval = stdcc_setup(context, ccapi_data))) {
		if (retval == KRB5_FCC_NOFILE)
			return 0;
		return retval;
	}
    	
    	/* convert to a cred union */
    	dupK5toCC(context, creds, &cu);
    	
    	/* remove it */
    	err = cc_remove_cred(gCntrlBlock, ccapi_data->NamedCache, *cu);
    	if (err != CC_NOERROR)
		return cc_err_xlate(err);
    	
    	/* free the cred union using our local version of cc_free_creds()
	       since we allocated it locally */
    	err = krb5int_free_cc_cred_union(&cu);
	cache_changed();
    	if (err != CC_NOERROR)
		return cc_err_xlate(err);

        return 0;
}
#endif /* !USE_CCAPI_V3 */

#endif /* defined(_WIN32) || defined(USE_CCAPI) */