#include "krb5_locl.h"
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <uuid/uuid.h>
typedef struct krb5_kcache{
char *name;
CFStringRef cname;
int version;
}krb5_kcache;
#define KCACHE(X) ((krb5_kcache*)(X)->data.data)
struct kcc_cursor {
CFArrayRef array;
CFIndex offset;
};
static CFTypeRef kGSSiTGT;
static CFTypeRef kGSSoTGT;
static CFTypeRef kGSSCreator;
#ifndef __APPLE_TARGET_EMBEDDED__
static SecAccessRef kGSSAnyAccess;
#endif
static void
create_constants(void)
{
static dispatch_once_t once;
dispatch_once(&once, ^{
int32_t num;
num = 'iTGT';
kGSSiTGT = CFNumberCreate(NULL, kCFNumberSInt32Type, &num);
num = 'oTGT';
kGSSoTGT = CFNumberCreate(NULL, kCFNumberSInt32Type, &num);
num = 'GSSL';
kGSSCreator = CFNumberCreate(NULL, kCFNumberSInt32Type, &num);
#ifndef __APPLE_TARGET_EMBEDDED__
OSStatus ret;
ret = SecAccessCreate(CFSTR("GSS-credential"), NULL, &kGSSAnyAccess);
if (ret)
return;
SecKeychainPromptSelector promptSelector;
CFStringRef promptDescription = NULL;
CFArrayRef appList = NULL, aclList = NULL;
SecACLRef acl;
aclList = SecAccessCopyMatchingACLList(kGSSAnyAccess, kSecACLAuthorizationDecrypt);
if (aclList == NULL)
return;
if (CFArrayGetCount(aclList) < 1) {
CFRelease(aclList);
return;
}
acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
ret = SecACLCopyContents(acl, &appList, &promptDescription, &promptSelector);
if (ret == 0) {
promptSelector &= ~kSecKeychainPromptRequirePassphase;
(void)SecACLSetContents(acl, NULL, promptDescription, promptSelector);
}
CFRelease(promptDescription);
CFRelease(appList);
CFRelease(aclList);
#endif
});
}
static void
delete_type(krb5_context context, krb5_kcache *k)
{
#ifndef __APPLE_TARGET_EMBEDDED__
#define USE_DELETE 1
#endif
const void *keys[] = {
#ifdef USE_DELETE
kSecReturnRef,
#endif
kSecClass,
kSecAttrCreator,
kSecAttrServer,
};
const void *values[] = {
#ifdef USE_DELETE
kCFBooleanTrue,
#endif
kSecClassInternetPassword,
kGSSCreator,
k->cname,
};
CFDictionaryRef query;
OSStatus ret;
query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), NULL, NULL);
heim_assert(query != NULL, "out of memory");
#ifdef USE_DELETE
CFArrayRef array;
ret = SecItemCopyMatching(query, (CFTypeRef *)&array);
if (ret == 0) {
CFIndex n, count = CFArrayGetCount(array);
for (n = 0; n < count; n++)
SecKeychainItemDelete((SecKeychainItemRef)CFArrayGetValueAtIndex(array, n));
CFRelease(array);
}
#else
ret = SecItemDelete(query);
if (ret)
_krb5_debugx(context, 5, "failed to delete credential %s: %d\n", k->name, (int)ret);
#endif
CFRelease(query);
}
static CFDictionaryRef
CreateSecItemFromCache(krb5_kcache *k)
{
CFDictionaryRef result = NULL, query = NULL;
OSStatus ret;
create_constants();
const void *keys[] = {
kSecClass,
kSecReturnAttributes,
kSecAttrType,
kSecAttrServer
};
const void *values[] = {
kSecClassInternetPassword,
kCFBooleanTrue,
kGSSiTGT,
k->cname
};
query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
CFRelease(query);
if (ret)
return NULL;
return result;
}
static const char*
kcc_get_name(krb5_context context,
krb5_ccache id)
{
krb5_kcache *k = KCACHE(id);
return k->name;
}
static krb5_error_code
kcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
{
krb5_kcache *k;
k = malloc(sizeof(*k));
if (k == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
k->name = strdup(res);
if (k->name == NULL){
free(k);
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
k->cname = CFStringCreateWithCString(NULL, k->name, kCFStringEncodingUTF8);
if (k->cname == NULL) {
free(k->name);
free(k);
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
(*id)->data.data = k;
(*id)->data.length = sizeof(*k);
return 0;
}
static krb5_error_code
kcc_gen_new(krb5_context context, krb5_ccache *id)
{
char *file = NULL;
krb5_error_code ret;
krb5_kcache *k;
uuid_t uuid;
uuid_string_t uuidstr;
k = malloc(sizeof(*k));
if (k == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
uuid_generate_random(uuid);
uuid_unparse(uuid, uuidstr);
ret = asprintf(&file, "unique-%s", uuidstr);
if (ret <= 0 || file == NULL) {
free(k);
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
k->name = file;
k->cname = CFStringCreateWithCString(NULL, k->name, kCFStringEncodingUTF8);
if (k->cname == NULL) {
free(k->name);
free(k);
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
(*id)->data.data = k;
(*id)->data.length = sizeof(*k);
return 0;
}
static CFStringRef
CFStringCreateFromPrincipal(CFAllocatorRef alloc, krb5_context context, krb5_principal principal)
{
CFStringRef str;
char *p;
if (krb5_unparse_name(context, principal, &p) != 0)
return NULL;
str = CFStringCreateWithCString(alloc, p, kCFStringEncodingUTF8);
krb5_xfree(p);
return str;
}
static krb5_error_code
kcc_initialize(krb5_context context,
krb5_ccache id,
krb5_principal primary_principal)
{
krb5_error_code ret = 0;
krb5_kcache *k = KCACHE(id);
CFDictionaryRef query;
CFTypeRef item = NULL;
CFStringRef principal = NULL;
CFStringRef label = NULL;
OSStatus osret;
create_constants();
delete_type(context, k);
principal = CFStringCreateFromPrincipal(NULL, context, primary_principal);
if (principal == NULL) {
ret = krb5_enomem(context);
goto out;
}
label = CFStringCreateWithFormat(NULL, NULL, CFSTR("Kerberos credentials for %@"), principal);
if (label == NULL) {
ret = krb5_enomem(context);
goto out;
}
const void *add_keys[] = {
kSecAttrCreator,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessible,
#else
kSecAttrAccess,
#endif
kSecClass,
kSecAttrType,
kSecAttrServer,
kSecAttrAccount,
kSecAttrLabel,
kSecAttrIsInvisible
};
const void *add_values[] = {
kGSSCreator,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
#else
kGSSAnyAccess,
#endif
kSecClassInternetPassword,
kGSSiTGT,
k->cname,
principal,
label,
kCFBooleanTrue
};
query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL);
heim_assert(query != NULL, "out of memory");
osret = SecItemAdd(query, &item);
CFRelease(query);
if (osret) {
_krb5_debugx(context, 5, "failed to store credential: %d\n", (int)osret);
ret = EINVAL;
krb5_set_error_message(context, ret, "failed to store credential: %d", (int)osret);
goto out;
}
out:
if (label)
CFRelease(label);
if (item)
CFRelease(item);
if (principal)
CFRelease(principal);
return ret;
}
static krb5_error_code
kcc_close(krb5_context context,
krb5_ccache id)
{
krb5_kcache *k = KCACHE(id);
if (k->cname)
CFRelease(k->cname);
free(k->name);
krb5_data_free(&id->data);
return 0;
}
static krb5_error_code
kcc_destroy(krb5_context context,
krb5_ccache id)
{
krb5_kcache *k = KCACHE(id);
create_constants();
delete_type(context, k);
return 0;
}
static krb5_error_code
kcc_store_cred(krb5_context context,
krb5_ccache id,
krb5_creds *creds)
{
krb5_kcache *k = KCACHE(id);
krb5_storage *sp = NULL;
CFDataRef dref = NULL;
krb5_data data;
CFStringRef principal = NULL;
CFStringRef label = NULL;
CFDictionaryRef query = NULL;
krb5_error_code ret;
CFTypeRef item = NULL;
create_constants();
krb5_data_zero(&data);
sp = krb5_storage_emem();
if (sp == NULL) {
ret = krb5_enomem(context);
goto out;
}
ret = krb5_store_creds(sp, creds);
if (ret)
goto out;
krb5_storage_to_data(sp, &data);
dref = CFDataCreateWithBytesNoCopy(NULL, data.data, data.length, kCFAllocatorNull);
if (dref == NULL) {
ret = krb5_enomem(context);
goto out;
}
principal = CFStringCreateFromPrincipal(NULL, context, creds->server);
if (principal == NULL) {
ret = krb5_enomem(context);
goto out;
}
label = CFStringCreateWithFormat(NULL, NULL, CFSTR("kerberos credentials for %@"), principal);
if (label == NULL) {
ret = krb5_enomem(context);
goto out;
}
const void *add_keys[] = {
kSecAttrCreator,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessible,
#else
kSecAttrAccess,
#endif
kSecClass,
kSecAttrType,
kSecAttrServer,
kSecAttrAccount,
kSecAttrLabel,
kSecValueData,
kSecAttrIsInvisible
};
const void *add_values[] = {
kGSSCreator,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
#else
kGSSAnyAccess,
#endif
kSecClassInternetPassword,
kGSSoTGT,
k->cname,
principal,
label,
dref,
kCFBooleanTrue
};
query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL);
heim_assert(query != NULL, "out of memory");
ret = SecItemAdd(query, &item);
CFRelease(query);
if (ret) {
_krb5_debugx(context, 5, "failed to add credential to %s: %d\n", k->name, (int)ret);
ret = EINVAL;
krb5_set_error_message(context, ret, "failed to store credential to %s", k->name);
goto out;
}
out:
if (item)
CFRelease(item);
if (sp)
krb5_storage_free(sp);
if (dref)
CFRelease(dref);
if (principal)
CFRelease(principal);
if (label)
CFRelease(label);
krb5_data_free(&data);
return ret;
}
static krb5_error_code
kcc_get_principal(krb5_context context,
krb5_ccache id,
krb5_principal *principal)
{
krb5_error_code ret = 0;
krb5_kcache *k = KCACHE(id);
CFDictionaryRef result;
result = CreateSecItemFromCache(k);
if (result == NULL) {
krb5_set_error_message(context, KRB5_CC_END,
"failed finding cache: %s", k->name);
ret = KRB5_CC_END;
goto out;
}
CFStringRef p = CFDictionaryGetValue(result, kSecAttrAccount);
if (p == NULL) {
ret = KRB5_CC_END;
goto out;
}
char *str = rk_cfstring2cstring(p);
if (str == NULL) {
ret = krb5_enomem(context);
goto out;
}
krb5_parse_name(context, str, principal);
free(str);
out:
if (result)
CFRelease(result);
return ret;
}
static void
free_cursor(struct kcc_cursor *c)
{
if (c->array)
CFRelease(c->array);
free(c);
}
static krb5_error_code
kcc_get_first (krb5_context context,
krb5_ccache id,
krb5_cc_cursor *cursor)
{
krb5_error_code ret;
krb5_kcache *k = KCACHE(id);
CFDictionaryRef query = NULL;
CFArrayRef result = NULL;
struct kcc_cursor *c;
create_constants();
c = calloc(1, sizeof(*c));
if (c == NULL)
return krb5_enomem(context);
const void *keys[] = {
kSecClass,
kSecReturnData,
kSecMatchLimit,
kSecAttrType,
kSecAttrServer
};
const void *values[] = {
kSecClassInternetPassword,
kCFBooleanTrue,
kSecMatchLimitAll,
kGSSoTGT,
k->cname
};
query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
if (ret) {
result = NULL;
ret = 0;
goto out;
}
if (CFGetTypeID(result) != CFArrayGetTypeID()) {
CFRelease(result);
ret = 0;
result = NULL;
}
c->array = result;
c->offset = 0;
out:
if (query)
CFRelease(query);
if (ret) {
free_cursor(c);
} else {
*cursor = c;
}
return ret;
}
static krb5_error_code
kcc_get_next (krb5_context context,
krb5_ccache id,
krb5_cc_cursor *cursor,
krb5_creds *creds)
{
struct kcc_cursor *c = *cursor;
krb5_error_code ret;
krb5_storage *sp;
CFDataRef data;
if (c->array == NULL)
return KRB5_CC_END;
if (c->offset >= CFArrayGetCount(c->array))
return KRB5_CC_END;
data = CFArrayGetValueAtIndex(c->array, c->offset++);
if (data == NULL)
return KRB5_CC_END;
sp = krb5_storage_from_readonly_mem(CFDataGetBytePtr(data), CFDataGetLength(data));
if (sp == NULL)
return KRB5_CC_END;
ret = krb5_ret_creds(sp, creds);
krb5_storage_free(sp);
return ret;
}
static krb5_error_code
kcc_end_get (krb5_context context,
krb5_ccache id,
krb5_cc_cursor *cursor)
{
free_cursor((struct kcc_cursor *)*cursor);
*cursor = NULL;
return 0;
}
static krb5_error_code
kcc_remove_cred(krb5_context context,
krb5_ccache id,
krb5_flags which,
krb5_creds *cred)
{
return 0;
}
static krb5_error_code
kcc_set_flags(krb5_context context,
krb5_ccache id,
krb5_flags flags)
{
return 0;
}
static int
kcc_get_version(krb5_context context,
krb5_ccache id)
{
return 0;
}
static krb5_error_code
kcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
{
struct kcc_cursor *c;
CFArrayRef result;
CFDictionaryRef query;
OSStatus osret;
create_constants();
c = calloc(1, sizeof(*c));
if (c == NULL)
return krb5_enomem(context);
const void *keys[] = {
kSecClass,
kSecReturnAttributes,
kSecMatchLimit,
kSecAttrType,
};
const void *values[] = {
kSecClassInternetPassword,
kCFBooleanTrue,
kSecMatchLimitAll,
kGSSiTGT,
};
query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
osret = SecItemCopyMatching(query, (CFTypeRef *)&result);
CFRelease(query);
if (osret)
result = NULL;
c->array = result;
c->offset = 0;
*cursor = c;
return 0;
}
static krb5_error_code
kcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
{
struct kcc_cursor *c = cursor;
CFDictionaryRef item;
krb5_error_code ret;
CFStringRef sref;
char *name;
if (c->array == NULL)
return KRB5_CC_END;
if (c->offset >= CFArrayGetCount(c->array))
return KRB5_CC_END;
item = CFArrayGetValueAtIndex(c->array, c->offset++);
if (item == NULL)
return KRB5_CC_END;
sref = CFDictionaryGetValue(item, kSecAttrServer);
if (sref == NULL)
return KRB5_CC_END;
name = rk_cfstring2cstring(sref);
if (name == NULL)
return KRB5_CC_NOMEM;
ret = _krb5_cc_allocate(context, &krb5_kcc_ops, id);
if (ret) {
free(name);
return ret;
}
ret = kcc_resolve(context, id, name);
free(name);
if (ret)
krb5_cc_close(context, *id);
return ret;
}
static krb5_error_code
kcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
{
free_cursor((struct kcc_cursor *)cursor);
return 0;
}
static krb5_error_code
kcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
{
krb5_kcache *kfrom = KCACHE(from), *kto = KCACHE(to);
CFDictionaryRef query = NULL, update = NULL;
krb5_error_code ret = 0;
OSStatus osret;
if (strcmp(kfrom->name, kto->name) == 0)
return 0;
create_constants();
delete_type(context, kto);
const void *keys[] = {
kSecClass,
kSecReturnRef,
kSecMatchLimit,
kSecAttrServer
};
const void *values[] = {
kSecClassInternetPassword,
kCFBooleanTrue,
kSecMatchLimitAll,
kfrom->cname
};
query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
const void *update_keys[] = {
kSecAttrServer
};
const void *update_values[] = {
kto->cname
};
update = CFDictionaryCreate(NULL, update_keys, update_values, sizeof(update_keys) / sizeof(update_keys[0]),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
osret = SecItemUpdate(query, update);
if (osret)
krb5_set_error_message(context, (ret = KRB5_CC_END), "Failed to rename credential: %s with error %d", kfrom->name, (int)osret);
CFRelease(query);
CFRelease(update);
return ret;
}
static void
set_default(CFStringRef name, bool clear_first)
{
CFDictionaryRef update = NULL, query = NULL;
const void *keys[] = {
kSecClass,
kSecMatchLimit,
kSecAttrCreator,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessible,
#endif
kSecAttrType,
kSecAttrServer
};
const void *values[] = {
kSecClassInternetPassword,
kSecMatchLimitAll,
kGSSCreator,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
#endif
kGSSiTGT,
name
};
const void *update_keys[] = {
kSecAttrIsNegative
};
const void *update_values_false[] = {
kCFBooleanFalse
};
const void *update_values_true[] = {
kCFBooleanTrue
};
if (clear_first) {
query = CFDictionaryCreate(NULL, keys, values, (sizeof(keys) / sizeof(keys[0])) - 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
update = CFDictionaryCreate(NULL, update_keys, update_values_false, sizeof(update_keys) / sizeof(update_keys[0]),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
heim_assert(update != NULL, "out of memory");
(void)SecItemUpdate(query, update);
CFRelease(query);
CFRelease(update);
}
query = CFDictionaryCreate(NULL, keys, values, (sizeof(keys) / sizeof(keys[0])),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
update = CFDictionaryCreate(NULL, update_keys, update_values_true, sizeof(update_keys) / sizeof(update_keys[0]),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
heim_assert(update != NULL, "out of memory");
(void)SecItemUpdate(query, update);
CFRelease(query);
CFRelease(update);
}
static krb5_error_code
kcc_get_default_name(krb5_context context, char **str)
{
CFDictionaryRef result = NULL, query = NULL;
OSStatus ret;
*str = NULL;
create_constants();
const void *keys[] = {
kSecClass,
kSecReturnAttributes,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessible,
#endif
kSecAttrType,
kSecAttrIsNegative
};
const void *values[] = {
kSecClassInternetPassword,
kCFBooleanTrue,
#ifdef __APPLE_TARGET_EMBEDDED__
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
#endif
kGSSiTGT,
kCFBooleanTrue
};
query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
CFRelease(query);
if (ret) {
query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]) - 1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
heim_assert(query != NULL, "out of memory");
ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
CFRelease(query);
if (ret == 0) {
CFStringRef name = CFDictionaryGetValue(result, kSecAttrServer);
set_default(name, false);
}
}
if (ret == 0) {
CFStringRef name = CFDictionaryGetValue(result, kSecAttrServer);
char *n;
if (name == NULL)
return krb5_enomem(context);
n = rk_cfstring2cstring(name);
if (n == NULL)
return ENOMEM;
asprintf(str, "KCC:%s", n);
free(n);
if (*str == NULL)
return krb5_enomem(context);
return 0;
} else {
*str = strdup("KCC:default-kcache");
if (*str == NULL)
return krb5_enomem(context);
}
return 0;
}
static krb5_error_code
kcc_set_default(krb5_context context, krb5_ccache id)
{
krb5_kcache *k = KCACHE(id);
create_constants();
set_default(k->cname, true);
return 0;
}
static krb5_error_code
kcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
{
*mtime = 0;
return 0;
}
static krb5_error_code
kcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
{
return 0;
}
static krb5_error_code
kcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
{
*kdc_offset = 0;
return 0;
}
KRB5_LIB_VARIABLE const krb5_cc_ops krb5_kcc_ops = {
KRB5_CC_OPS_VERSION,
"KCC",
kcc_get_name,
kcc_resolve,
kcc_gen_new,
kcc_initialize,
kcc_destroy,
kcc_close,
kcc_store_cred,
NULL,
kcc_get_principal,
kcc_get_first,
kcc_get_next,
kcc_end_get,
kcc_remove_cred,
kcc_set_flags,
kcc_get_version,
kcc_get_cache_first,
kcc_get_cache_next,
kcc_end_cache_get,
kcc_move,
kcc_get_default_name,
kcc_set_default,
kcc_lastchange,
kcc_set_kdc_offset,
kcc_get_kdc_offset
};
#endif