krb5 commit: Support macOS 11 native credential cache

ghudson at mit.edu ghudson at mit.edu
Wed May 18 17:38:19 EDT 2022


https://github.com/krb5/krb5/commit/3bb429b1d61dcd017537e27d7572dcf9114a5613
commit 3bb429b1d61dcd017537e27d7572dcf9114a5613
Author: Ken Hornstein <kenh at pobox.com>
Date:   Tue Aug 3 23:18:27 2021 -0400

    Support macOS 11 native credential cache
    
    Add an API credential cache implementation using the CCAPI stubs in
    the macOS Kerberos framework, tailored to access the native
    collections used by macOS 10.6 and later (KCM before macOS 11, XCACHE
    afterwards).  Make API: the default ccache name for macOS 10.6 and
    later.
    
    [ghudson at mit.edu: used shared CCAPI credential conversion functions;
    changed ptcursor behavior to match current Unix collection semantics;
    adjusted naming and code style]
    
    ticket: 9052 (new)

 NOTICE                             |  30 ++
 doc/notice.rst                     |  30 ++
 src/configure.ac                   |  53 ++-
 src/lib/krb5/Makefile.in           |   3 +-
 src/lib/krb5/ccache/Makefile.in    |   3 +
 src/lib/krb5/ccache/cc-int.h       |   3 +
 src/lib/krb5/ccache/cc_api_macos.c | 727 +++++++++++++++++++++++++++++++++++++
 src/lib/krb5/ccache/cc_kcm.c       |  48 ++-
 src/lib/krb5/ccache/ccapi_util.c   |   2 +-
 src/lib/krb5/ccache/ccbase.c       |   6 +
 src/lib/krb5/ccache/deps           |  12 +
 11 files changed, 892 insertions(+), 25 deletions(-)

diff --git a/NOTICE b/NOTICE
index b801eb3e0..8b7789d40 100644
--- a/NOTICE
+++ b/NOTICE
@@ -727,6 +727,36 @@ src/include/gssrpc have the following copyright and permission notice:
    DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
    RESULTING FROM THE USE OF THIS SOFTWARE.
 
+======================================================================
+
+   Copyright (C) 2022 United States Government as represented by the
+   Secretary of the Navy.  All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   * Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+
+   * Redistributions in binary form must reproduce the above
+     copyright notice, this list of conditions and the following
+     disclaimer in the documentation and/or other materials provided
+     with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+   FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+   COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+   OF THE POSSIBILITY OF SUCH DAMAGE.
+
 ======================================================================
 
    Copyright (C) 1991, 1992, 1994 by Cygnus Support.
diff --git a/doc/notice.rst b/doc/notice.rst
index 5cb67e9d2..87af3039b 100644
--- a/doc/notice.rst
+++ b/doc/notice.rst
@@ -688,6 +688,36 @@ have the following copyright and permission notice:
     DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
     RESULTING FROM THE USE OF THIS SOFTWARE.
 
+-------------------
+
+    Copyright |copy| 2022 United States Government as represented by the
+    Secretary of the Navy.  All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in
+      the documentation and/or other materials provided with the
+      distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+    COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+    OF THE POSSIBILITY OF SUCH DAMAGE.
+
 -------------------
 
     Copyright |copy| 1991, 1992, 1994 by Cygnus Support.
diff --git a/src/configure.ac b/src/configure.ac
index cd16b656e..392ac02d5 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -1406,12 +1406,39 @@ if test "${localedir+set}" != set; then
 fi
 AC_SUBST(localedir)
 
-# For KCM lib/krb5/ccache to build KCM Mach RPC support for macOS only.
-case $host in
-*-*-darwin* | *-*-rhapsody*) OSX=osx ;;
-*)                           OSX=no ;;
-esac
+# Determine the default macOS ccache type and whether to build the KCM
+# Mach RPC support.
+MACOS_FRAMEWORK=
+dnl The outer brackets around the case statement prevent m4 from
+dnl eating the brackets in the glob patterns, but also prevent us from
+dnl using AC_DEFINE within the body.
+[case $host in
+*-*-darwin[0-9].* | *-*-darwin10.*)
+  # Use the normal default cache type for macOS 10.6 (Darwin 10) and
+  # prior.  Build the KCM Mach RPC support.
+  OSX=osx
+  ;;
+*-*-darwin*)
+  # macOS 10.6 (Darwin 11) uses the KCM type by default.  macOS 11
+  # (Darwin 20) uses an xpc-based cache type called XCACHE by default.
+  # We can access either of these collections via a macos-specific
+  # implementation of the API cache type.  Build the KCM Mach RPC
+  # support.
+  OSX=osx
+  macos_defccname=API:
+  MACOS_FRAMEWORK="-framework Kerberos"
+  ;;
+*)
+  # This is not macOS; do not build the Mach RPC support and use the
+  # normal default cache type.
+  OSX=no
+  ;;
+esac]
+if test "$macos_defccname" = API:; then
+  AC_DEFINE(USE_CCAPI_MACOS, 1, [Define to build macOS CCAPI client])
+fi
 AC_SUBST(OSX)
+AC_SUBST(MACOS_FRAMEWORK)
 
 # Build-time default ccache, keytab, and client keytab names.  These
 # can be given as variable arguments DEFCCNAME, DEFKTNAME, and
@@ -1435,20 +1462,10 @@ if test "x$with_krb5_config" != xno; then
 		: "${DEFCKTNAME=`$with_krb5_config --defcktname`}"
 	fi
 fi
-dnl The outer brackets around the case statement prevent m4 from eating the
-dnl brackets in the glob patterns.
 if test "${DEFCCNAME+set}" != set; then
-	[case $host in
-	*-*-darwin[0-9].* | *-*-darwin10.*)
-		# Use the normal default for macOS 10.6 (Darwin 10) and prior.
-		;;
-	*-*-darwin*)
-		# For macOS 10.7 (Darwin 11) and later, the native ccache uses
-		# the KCM daemon.
-		DEFCCNAME=KCM:
-		;;
-	esac]
-	if test "${DEFCCNAME+set}" != set; then
+	if test "${macos_defccname+set}" = set; then
+		DEFCCNAME=$macos_defccname
+	else
 		DEFCCNAME=FILE:/tmp/krb5cc_%{uid}
 	fi
 fi
diff --git a/src/lib/krb5/Makefile.in b/src/lib/krb5/Makefile.in
index 3adaeeb22..4e9547c2f 100644
--- a/src/lib/krb5/Makefile.in
+++ b/src/lib/krb5/Makefile.in
@@ -56,7 +56,8 @@ RELDIR=krb5
 SHLIB_EXPDEPS = \
 	$(TOPLIBD)/libk5crypto$(SHLIBEXT) \
 	$(COM_ERR_DEPLIB) $(SUPPORT_DEPLIB)
-SHLIB_EXPLIBS=-lk5crypto $(COM_ERR_LIB) $(SUPPORT_LIB) @GEN_LIB@ $(LIBS)
+SHLIB_EXPLIBS=-lk5crypto $(COM_ERR_LIB) $(SUPPORT_LIB) @GEN_LIB@ \
+	@MACOS_FRAMEWORK@ $(LIBS)
 
 all-unix: all-liblinks
 
diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in
index 9d29ec51d..2864e92b9 100644
--- a/src/lib/krb5/ccache/Makefile.in
+++ b/src/lib/krb5/ccache/Makefile.in
@@ -36,6 +36,7 @@ STLIBOBJS= \
 	ccselect_hostname.o \
 	ccselect_k5identity.o \
 	ccselect_realm.o \
+	cc_api_macos.o \
 	cc_dir.o \
 	cc_retr.o \
 	cc_file.o \
@@ -56,6 +57,7 @@ OBJS=	$(OUTPRE)ccapi_util.$(OBJEXT) \
 	$(OUTPRE)ccselect_hostname.$(OBJEXT) \
 	$(OUTPRE)ccselect_k5identity.$(OBJEXT) \
 	$(OUTPRE)ccselect_realm.$(OBJEXT) \
+	$(OUTPRE)cc_api_macos.$(OBJEXT) \
 	$(OUTPRE)cc_dir.$(OBJEXT) \
 	$(OUTPRE)cc_retr.$(OBJEXT) \
 	$(OUTPRE)cc_file.$(OBJEXT) \
@@ -76,6 +78,7 @@ SRCS=	$(srcdir)/ccapi_util.c \
 	$(srcdir)/ccselect_hostname.c \
 	$(srcdir)/ccselect_k5identity.c \
 	$(srcdir)/ccselect_realm.c \
+	$(srcdir)/cc_api_macos.c \
 	$(srcdir)/cc_dir.c \
 	$(srcdir)/cc_retr.c \
 	$(srcdir)/cc_file.c \
diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h
index 6039a1f5b..51c6df217 100644
--- a/src/lib/krb5/ccache/cc-int.h
+++ b/src/lib/krb5/ccache/cc-int.h
@@ -166,6 +166,9 @@ k5_marshal_mcred(struct k5buf *buf, krb5_creds *mcred);
 void
 k5_marshal_princ(struct k5buf *buf, int version, krb5_principal princ);
 
+krb5_error_code
+k5_kcm_primary_name(krb5_context context, char **name_out);
+
 /*
  * Per-type ccache cursor.
  */
diff --git a/src/lib/krb5/ccache/cc_api_macos.c b/src/lib/krb5/ccache/cc_api_macos.c
new file mode 100644
index 000000000..3bf30c9fd
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_api_macos.c
@@ -0,0 +1,727 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_api_macos.c - Native MacOS X ccache code */
+/*
+ * Copyright (C) 2022 United States Government as represented by the
+ * Secretary of the Navy.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This ccache module provides compatibility with the default native ccache
+ * type for macOS, by linking against the native Kerberos framework and calling
+ * the CCAPI stubs.  Due to workarounds for specific behaviors of the CCAPI
+ * stubs, this implementation is separate from the API ccache implementation
+ * used on Windows.
+ */
+
+#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;
+};
+
+/* Map a CCAPI error code to a com_err code. */
+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;
+    }
+}
+
+/* Construct a ccache handle for residual.  Use cc_context if it is not null,
+ * or initialize a new one if it is. */
+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;
+}
+
+/*
+ * We would like to use cc_context_get_default_ccache_name() for this, but that
+ * doesn't work on macOS if the default cache name is set by the environment or
+ * configuration.  So we have to do what the underlying macOS Heimdal API cache
+ * type does to fetch the primary name.
+ *
+ * For macOS 11 (Darwin 20) and later, implement just enough of the XCACHE
+ * protocol to fetch the primary UUID.  For earlier versions, query the KCM
+ * daemon.
+ */
+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) {
+            /* Query the KCM daemon for macOS 10 and earlier. */
+            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_connection_cancel(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;
+
+    /* Apple's cc_context_create_ccache() requires a name with type prefix. */
+    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;
+}
+
+/* Create a cache object and open it to ensure that it exists in the
+ * collection.  If it does not, return success but set *cache_out to NULL. */
+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 the default cache name is a subsidiary cache, yield that cache if it
+     * exists and stop. */
+    if (*defresidual != '\0') {
+        if (!apt->first)
+            return 0;
+        apt->first = FALSE;
+        return make_open_cache(defresidual, cache_out);
+    }
+
+    if (apt->first) {
+        apt->first = FALSE;
+
+        /* Prepare to iterate over the collection. */
+        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;
+
+        /* Yield the primary cache first if it exists. */
+        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);
+        }
+
+        /* Skip the primary cache since we yielded it first. */
+        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, /* move */
+    NULL, /* wasdefault */
+    api_macos_lock,
+    api_macos_unlock,
+    api_macos_switch_to,
+};
+
+#endif /* TARGET_OS_MAC */
diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c
index 204454d66..c93e7c78e 100644
--- a/src/lib/krb5/ccache/cc_kcm.c
+++ b/src/lib/krb5/ccache/cc_kcm.c
@@ -720,6 +720,23 @@ kcm_get_name(krb5_context context, krb5_ccache cache)
     return ((struct kcm_cache_data *)cache->data)->residual;
 }
 
+/* Fetch the primary name within the collection.  The result is only valid for
+ * the lifetime of req and should not be freed. */
+static krb5_error_code
+get_primary_name(krb5_context context, struct kcmreq *req, struct kcmio *io,
+                 const char **name_out)
+{
+    krb5_error_code ret;
+
+    *name_out = NULL;
+
+    kcmreq_init(req, KCM_OP_GET_DEFAULT_CACHE, NULL);
+    ret = kcmio_call(context, io, req);
+    if (ret)
+        return ret;
+    return kcmreq_get_name(req, name_out);
+}
+
 static krb5_error_code KRB5_CALLCONV
 kcm_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
 {
@@ -735,11 +752,7 @@ kcm_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
         goto cleanup;
 
     if (*residual == '\0') {
-        kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL);
-        ret = kcmio_call(context, io, &req);
-        if (ret)
-            goto cleanup;
-        ret = kcmreq_get_name(&req, &defname);
+        ret = get_primary_name(context, &req, io, &defname);
         if (ret)
             goto cleanup;
         residual = defname;
@@ -754,6 +767,31 @@ cleanup:
     return ret;
 }
 
+krb5_error_code
+k5_kcm_primary_name(krb5_context context, char **name_out)
+{
+    krb5_error_code ret;
+    struct kcmreq req = EMPTY_KCMREQ;
+    struct kcmio *io = NULL;
+    const char *name;
+
+    *name_out = NULL;
+
+    ret = kcmio_connect(context, &io);
+    if (ret)
+        goto cleanup;
+    ret = get_primary_name(context, &req, io, &name);
+    if (ret)
+        goto cleanup;
+    *name_out = strdup(name);
+    ret = (*name_out == NULL) ? ENOMEM : 0;
+
+cleanup:
+    kcmio_close(io);
+    kcmreq_free(&req);
+    return ret;
+}
+
 static krb5_error_code KRB5_CALLCONV
 kcm_gen_new(krb5_context context, krb5_ccache *cache_out)
 {
diff --git a/src/lib/krb5/ccache/ccapi_util.c b/src/lib/krb5/ccache/ccapi_util.c
index 76f686c9f..b035c7eeb 100644
--- a/src/lib/krb5/ccache/ccapi_util.c
+++ b/src/lib/krb5/ccache/ccapi_util.c
@@ -33,7 +33,7 @@
 #include "cc-int.h"
 #include "ccapi_util.h"
 
-#if defined(USE_CCAPI)
+#if defined(USE_CCAPI) || defined(USE_CCAPI_MACOS)
 
 static void
 free_cc_data_list(cc_data **list)
diff --git a/src/lib/krb5/ccache/ccbase.c b/src/lib/krb5/ccache/ccbase.c
index 43da47e5b..5a0132083 100644
--- a/src/lib/krb5/ccache/ccbase.c
+++ b/src/lib/krb5/ccache/ccbase.c
@@ -87,6 +87,12 @@ static struct krb5_cc_typelist cc_kcm_entry = { &krb5_kcm_ops, NEXT };
 #define NEXT &cc_kcm_entry
 #endif /* not _WIN32 */
 
+#ifdef USE_CCAPI_MACOS
+extern const krb5_cc_ops krb5_api_macos_ops;
+static struct krb5_cc_typelist cc_macos_entry = { &krb5_api_macos_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_macos_entry
+#endif /* USE_CCAPI_MACOS */
 
 #define INITIAL_TYPEHEAD (NEXT)
 static struct krb5_cc_typelist *cc_typehead = INITIAL_TYPEHEAD;
diff --git a/src/lib/krb5/ccache/deps b/src/lib/krb5/ccache/deps
index 1f69e0f93..6429e9284 100644
--- a/src/lib/krb5/ccache/deps
+++ b/src/lib/krb5/ccache/deps
@@ -124,6 +124,18 @@ ccselect_realm.so ccselect_realm.po $(OUTPRE)ccselect_realm.$(OBJEXT): \
   $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/ccselect_plugin.h \
   $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
   $(top_srcdir)/include/socket-utils.h cc-int.h ccselect_realm.c
+cc_api_macos.so cc_api_macos.po $(OUTPRE)cc_api_macos.$(OBJEXT): \
+  $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+  $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+  $(COM_ERR_DEPS) $(top_srcdir)/include/CredentialsCache.h \
+  $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+  $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+  $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+  $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+  $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+  $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+  $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+  cc-int.h cc_api_macos.c ccapi_util.h
 cc_dir.so cc_dir.po $(OUTPRE)cc_dir.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
   $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
   $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \


More information about the cvs-krb5 mailing list