/* * 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) */