#include "k5-int.h"
#include "cc-int.h"
#include "ccapi_util.h"
#include <CredentialsCache.h>
#ifdef USE_CCAPI_MACOS
#include <sys/utsname.h>
#include <xpc/xpc.h>
const krb5_cc_ops krb5_api_macos_ops;
struct api_macos_cache_data {
char *residual;
cc_context_t cc_context;
cc_ccache_t cache;
};
struct api_macos_ptcursor {
krb5_boolean first;
char *primary;
cc_context_t cc_context;
cc_ccache_iterator_t iter;
};
static krb5_error_code
ccerr2mit(uint32_t err)
{
switch (err) {
case ccNoError:
return 0;
case ccIteratorEnd:
return KRB5_CC_END;
case ccErrNoMem:
return ENOMEM;
case ccErrCCacheNotFound:
return KRB5_FCC_NOFILE;
default:
return KRB5_FCC_INTERNAL;
}
}
static krb5_error_code
make_cache(const char *residual, cc_context_t cc_context,
krb5_ccache *ccache_out)
{
krb5_ccache cache = NULL;
char *residual_copy = NULL;
struct api_macos_cache_data *data = NULL;
uint32_t err;
*ccache_out = NULL;
if (cc_context == NULL) {
err = cc_initialize(&cc_context, ccapi_version_max, NULL, NULL);
if (err != ccNoError)
return KRB5_FCC_INTERNAL;
}
cache = malloc(sizeof(*cache));
if (cache == NULL)
goto oom;
data = calloc(1, sizeof(*data));
if (data == NULL)
goto oom;
residual_copy = strdup(residual);
if (residual_copy == NULL)
goto oom;
data->residual = residual_copy;
data->cc_context = cc_context;
cache->ops = &krb5_api_macos_ops;
cache->data = data;
cache->magic = KV5M_CCACHE;
*ccache_out = cache;
return 0;
oom:
free(cache);
free(data);
free(residual_copy);
if (cc_context)
cc_context_release(cc_context);
return ENOMEM;
}
static uint32_t
open_cache(struct api_macos_cache_data *data)
{
if (data->cache != NULL)
return ccNoError;
return cc_context_open_ccache(data->cc_context, data->residual,
&data->cache);
}
static const char *
api_macos_get_name(krb5_context context, krb5_ccache ccache)
{
struct api_macos_cache_data *data = ccache->data;
return data->residual;
}
static krb5_error_code
get_primary_name(krb5_context context, char **name_out)
{
krb5_error_code ret;
xpc_connection_t conn = NULL;
xpc_object_t request = NULL, reply = NULL;
const uint8_t *uuid;
uint64_t flags = XPC_CONNECTION_MACH_SERVICE_PRIVILEGED;
char uuidstr[37], *end;
struct utsname un;
long release;
*name_out = NULL;
if (uname(&un) == 0) {
release = strtol(un.release, &end, 10);
if (end != un.release && release < 20) {
ret = k5_kcm_primary_name(context, name_out);
goto cleanup;
}
}
conn = xpc_connection_create_mach_service("com.apple.GSSCred", NULL,
flags);
if (conn == NULL) {
ret = ENOMEM;
goto cleanup;
}
xpc_connection_set_event_handler(conn, ^(xpc_object_t o){ ; });
xpc_connection_resume(conn);
request = xpc_dictionary_create(NULL, NULL, 0);
if (request == NULL) {
ret = ENOMEM;
goto cleanup;
}
xpc_dictionary_set_string(request, "command", "default");
xpc_dictionary_set_string(request, "mech", "kHEIMTypeKerberos");
reply = xpc_connection_send_message_with_reply_sync(conn, request);
if (reply == NULL || xpc_get_type(reply) == XPC_TYPE_ERROR) {
ret = KRB5_CC_IO;
goto cleanup;
}
uuid = xpc_dictionary_get_uuid(reply, "default");
if (uuid == NULL) {
ret = KRB5_CC_IO;
goto cleanup;
}
uuid_unparse(uuid, uuidstr);
*name_out = strdup(uuidstr);
ret = (*name_out == NULL) ? ENOMEM : 0;
cleanup:
if (request != NULL)
xpc_release(request);
if (reply != NULL)
xpc_release(reply);
if (conn != NULL)
xpc_release(conn);
return ret;
}
static krb5_error_code
api_macos_resolve(krb5_context context, krb5_ccache *cache_out,
const char *residual)
{
krb5_error_code ret;
char *primary = NULL;
if (*residual == '\0') {
ret = get_primary_name(context, &primary);
if (ret)
return ret;
residual = primary;
}
ret = make_cache(residual, NULL, cache_out);
free(primary);
return ret;
}
static krb5_error_code
api_macos_gen_new(krb5_context context, krb5_ccache *cache_out)
{
krb5_error_code ret;
uint32_t err;
cc_context_t cc_context = NULL;
cc_ccache_t cc_ccache = NULL;
cc_string_t cachename = NULL;
struct api_macos_cache_data *data;
*cache_out = NULL;
err = cc_initialize(&cc_context, ccapi_version_max, NULL, NULL);
if (err)
goto cleanup;
err = cc_context_create_new_ccache(cc_context, cc_credentials_v5, "",
&cc_ccache);
if (err)
goto cleanup;
err = cc_ccache_get_name(cc_ccache, &cachename);
if (err)
goto cleanup;
ret = make_cache(cachename->data, cc_context, cache_out);
cc_context = NULL;
if (!ret) {
data = (*cache_out)->data;
data->cache = cc_ccache;
cc_ccache = NULL;
}
cleanup:
if (cc_context != NULL)
cc_context_release(cc_context);
if (cc_ccache != NULL)
cc_ccache_release(cc_ccache);
return err ? KRB5_FCC_INTERNAL : 0;
}
static krb5_error_code
api_macos_initialize(krb5_context context, krb5_ccache cache,
krb5_principal princ)
{
krb5_error_code ret;
struct api_macos_cache_data *data = cache->data;
uint32_t err;
char *princstr = NULL, *prefix_name = NULL;
if (asprintf(&prefix_name, "API:%s", data->residual) < 0)
return ENOMEM;
ret = krb5_unparse_name(context, princ, &princstr);
if (ret) {
free(prefix_name);
return ret;
}
if (data->cache != NULL) {
cc_ccache_release(data->cache);
data->cache = NULL;
}
err = cc_context_create_ccache(data->cc_context, prefix_name,
cc_credentials_v5, princstr,
&data->cache);
krb5_free_unparsed_name(context, princstr);
free(prefix_name);
return ccerr2mit(err);
}
static krb5_error_code
api_macos_close(krb5_context context, krb5_ccache cache)
{
struct api_macos_cache_data *data = cache->data;
if (data->cache != NULL)
cc_ccache_release(data->cache);
cc_context_release(data->cc_context);
free(data->residual);
free(data);
free(cache);
return 0;
}
static krb5_error_code
api_macos_destroy(krb5_context context, krb5_ccache cache)
{
struct api_macos_cache_data *data = cache->data;
open_cache(data);
if (data->cache != NULL) {
cc_ccache_destroy(data->cache);
data->cache = NULL;
}
return api_macos_close(context, cache);
}
static krb5_error_code
api_macos_store(krb5_context context, krb5_ccache cache, krb5_creds *creds)
{
struct api_macos_cache_data *data = cache->data;
cc_credentials_union *c_un = NULL;
krb5_error_code ret;
uint32_t err;
err = open_cache(data);
if (err)
return ccerr2mit(err);
ret = k5_krb5_to_ccapi_creds(context, creds, &c_un);
if (ret)
return ret;
err = cc_ccache_store_credentials(data->cache, c_un);
k5_release_ccapi_cred(c_un);
return ccerr2mit(err);
}
static krb5_error_code
api_macos_retrieve(krb5_context context, krb5_ccache cache,
krb5_flags whichfields, krb5_creds *mcreds,
krb5_creds *creds)
{
return k5_cc_retrieve_cred_default(context, cache, whichfields,
mcreds, creds);
}
static krb5_error_code
api_macos_get_princ(krb5_context context, krb5_ccache cache,
krb5_principal *princ)
{
struct api_macos_cache_data *data = cache->data;
krb5_error_code ret;
uint32_t err;
cc_string_t outprinc;
err = open_cache(data);
if (err)
return ccerr2mit(err);
err = cc_ccache_get_principal(data->cache, cc_credentials_v5, &outprinc);
if (err)
return ccerr2mit(err);
ret = krb5_parse_name(context, outprinc->data, princ);
cc_string_release(outprinc);
return ret;
}
static krb5_error_code
api_macos_start_seq_get(krb5_context context, krb5_ccache cache,
krb5_cc_cursor *cursor)
{
struct api_macos_cache_data *data = cache->data;
uint32_t err;
cc_credentials_iterator_t iter;
err = open_cache(data);
if (err)
return ccerr2mit(err);
err = cc_ccache_new_credentials_iterator(data->cache, &iter);
if (err)
return ccerr2mit(err);
*cursor = (krb5_cc_cursor)iter;
return 0;
}
static krb5_error_code
api_macos_next_cred(krb5_context context, krb5_ccache cache,
krb5_cc_cursor *cursor, krb5_creds *creds)
{
struct api_macos_cache_data *data = cache->data;
uint32_t err;
krb5_error_code ret;
cc_credentials_iterator_t iter = (cc_credentials_iterator_t) *cursor;
cc_credentials_t acreds;
err = open_cache(data);
if (err)
return ccerr2mit(err);
err = cc_credentials_iterator_next(iter, &acreds);
if (!err) {
ret = k5_ccapi_to_krb5_creds(context, acreds->data, creds);
cc_credentials_release(acreds);
} else {
ret = ccerr2mit(err);
}
return ret;
}
static krb5_error_code
api_macos_end_seq_get(krb5_context context, krb5_ccache cache,
krb5_cc_cursor *cursor)
{
cc_credentials_iterator_t iter = *cursor;
cc_credentials_iterator_release(iter);
*cursor = NULL;
return 0;
}
static krb5_error_code
api_macos_remove_cred(krb5_context context, krb5_ccache cache,
krb5_flags flags, krb5_creds *creds)
{
struct api_macos_cache_data *data = cache->data;
uint32_t err;
krb5_error_code ret = 0;
cc_credentials_iterator_t iter = NULL;
cc_credentials_t acreds;
krb5_creds mcreds;
krb5_boolean match;
err = open_cache(data);
if (err)
return ccerr2mit(err);
err = cc_ccache_new_credentials_iterator(data->cache, &iter);
if (err)
return ccerr2mit(err);
for (;;) {
err = cc_credentials_iterator_next(iter, &acreds);
if (err)
break;
ret = k5_ccapi_to_krb5_creds(context, acreds->data, &mcreds);
if (ret) {
cc_credentials_release(acreds);
break;
}
match = krb5int_cc_creds_match_request(context, flags, creds, &mcreds);
krb5_free_cred_contents(context, &mcreds);
if (match)
err = cc_ccache_remove_credentials(data->cache, acreds);
cc_credentials_release(acreds);
if (err)
break;
}
cc_credentials_iterator_release(iter);
if (ret)
return ret;
if (err != ccIteratorEnd)
return ccerr2mit(err);
return 0;
}
static krb5_error_code
api_macos_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
{
return 0;
}
static krb5_error_code
api_macos_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags)
{
*flags = 0;
return 0;
}
static krb5_error_code
api_macos_ptcursor_new(krb5_context context, krb5_cc_ptcursor *ptcursor_out)
{
krb5_cc_ptcursor ptcursor = NULL;
struct api_macos_ptcursor *apt = NULL;
apt = malloc(sizeof(*apt));
if (apt == NULL)
return ENOMEM;
apt->first = TRUE;
apt->primary = NULL;
apt->cc_context = NULL;
apt->iter = NULL;
ptcursor = malloc(sizeof(*ptcursor));
if (ptcursor == NULL) {
free(apt);
return ENOMEM;
}
ptcursor->ops = &krb5_api_macos_ops;
ptcursor->data = apt;
*ptcursor_out = ptcursor;
return 0;
}
static krb5_error_code
make_open_cache(const char *residual, krb5_ccache *cache_out)
{
krb5_error_code ret;
krb5_ccache cache;
uint32_t err;
*cache_out = NULL;
ret = make_cache(residual, NULL, &cache);
if (ret)
return ret;
err = open_cache(cache->data);
if (err) {
api_macos_close(NULL, cache);
return (err == ccErrCCacheNotFound) ? 0 : ccerr2mit(err);
}
*cache_out = cache;
return 0;
}
static krb5_error_code
api_macos_ptcursor_next(krb5_context context, krb5_cc_ptcursor ptcursor,
krb5_ccache *cache_out)
{
krb5_error_code ret;
uint32_t err;
struct api_macos_ptcursor *apt = ptcursor->data;
const char *defname, *defresidual;
cc_ccache_t cache;
cc_string_t residual;
struct api_macos_cache_data *data;
*cache_out = NULL;
defname = krb5_cc_default_name(context);
if (defname == NULL || strncmp(defname, "API:", 4) != 0)
return 0;
defresidual = defname + 4;
if (*defresidual != '\0') {
if (!apt->first)
return 0;
apt->first = FALSE;
return make_open_cache(defresidual, cache_out);
}
if (apt->first) {
apt->first = FALSE;
err = cc_initialize(&apt->cc_context, ccapi_version_max, NULL, NULL);
if (err)
return KRB5_FCC_INTERNAL;
err = cc_context_new_ccache_iterator(apt->cc_context, &apt->iter);
if (err)
return KRB5_FCC_INTERNAL;
ret = get_primary_name(context, &apt->primary);
if (ret)
return ret;
ret = make_open_cache(apt->primary, cache_out);
if (ret || *cache_out != NULL)
return ret;
}
for (;;) {
err = cc_ccache_iterator_next(apt->iter, &cache);
if (err)
return (err == ccIteratorEnd) ? 0 : ccerr2mit(err);
err = cc_ccache_get_name(cache, &residual);
if (err) {
cc_ccache_release(cache);
return ccerr2mit(err);
}
if (strcmp(residual->data, apt->primary) != 0)
break;
}
ret = make_cache(residual->data, NULL, cache_out);
cc_string_release(residual);
if (ret) {
cc_ccache_release(cache);
return ret;
}
data = (*cache_out)->data;
data->cache = cache;
return 0;
}
static krb5_error_code
api_macos_ptcursor_free(krb5_context context, krb5_cc_ptcursor *ptcursor)
{
struct api_macos_ptcursor *apt = (*ptcursor)->data;
if (apt != NULL) {
if (apt->iter != NULL)
cc_ccache_iterator_release(apt->iter);
if (apt->cc_context != NULL)
cc_context_release(apt->cc_context);
free(apt->primary);
free(apt);
}
free(*ptcursor);
*ptcursor = NULL;
return 0;
}
static krb5_error_code
api_macos_lock(krb5_context context, krb5_ccache cache)
{
struct api_macos_cache_data *data = cache->data;
uint32_t err;
err = open_cache(data);
if (err)
return ccerr2mit(err);
err = cc_ccache_lock(data->cache, cc_lock_write, cc_lock_block);
return ccerr2mit(err);
}
static krb5_error_code
api_macos_unlock(krb5_context context, krb5_ccache cache)
{
struct api_macos_cache_data *data = cache->data;
uint32_t err;
err = open_cache(data);
if (err)
return ccerr2mit(err);
err = cc_ccache_unlock(data->cache);
return ccerr2mit(err);
}
static krb5_error_code
api_macos_switch_to(krb5_context context, krb5_ccache cache)
{
struct api_macos_cache_data *data = cache->data;
uint32_t err;
err = open_cache(data);
if (err)
return ccerr2mit(err);
err = cc_ccache_set_default(data->cache);
return ccerr2mit(err);
}
const krb5_cc_ops krb5_api_macos_ops = {
0,
"API",
api_macos_get_name,
api_macos_resolve,
api_macos_gen_new,
api_macos_initialize,
api_macos_destroy,
api_macos_close,
api_macos_store,
api_macos_retrieve,
api_macos_get_princ,
api_macos_start_seq_get,
api_macos_next_cred,
api_macos_end_seq_get,
api_macos_remove_cred,
api_macos_set_flags,
api_macos_get_flags,
api_macos_ptcursor_new,
api_macos_ptcursor_next,
api_macos_ptcursor_free,
NULL,
NULL,
api_macos_lock,
api_macos_unlock,
api_macos_switch_to,
};
#endif