]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Support macOS 11 native credential cache 1221/head
authorKen Hornstein <kenh@pobox.com>
Wed, 4 Aug 2021 03:18:27 +0000 (23:18 -0400)
committerGreg Hudson <ghudson@mit.edu>
Wed, 18 May 2022 21:14:18 +0000 (17:14 -0400)
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@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
doc/notice.rst
src/configure.ac
src/lib/krb5/Makefile.in
src/lib/krb5/ccache/Makefile.in
src/lib/krb5/ccache/cc-int.h
src/lib/krb5/ccache/cc_api_macos.c [new file with mode: 0644]
src/lib/krb5/ccache/cc_kcm.c
src/lib/krb5/ccache/ccapi_util.c
src/lib/krb5/ccache/ccbase.c
src/lib/krb5/ccache/deps

diff --git a/NOTICE b/NOTICE
index b801eb3e0de032d3c11fb3647223192e39576435..8b7789d409aa5d306d9b5c5353ecf9485d1bac5f 100644 (file)
--- 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.
index 5cb67e9d21aced2faeca9e757799f8f62c4e9e9d..87af3039b633c4b760916032dffad1904acfe065 100644 (file)
@@ -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.
index cd16b656ea46ac5eb3f8c9e4b4d9c50a78699277..392ac02d5bd2efb4bead44c7bee5e11eeedc90f6 100644 (file)
@@ -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
index 3adaeeb220bf8e35b6f5878e357133e0930c8879..4e9547c2fcd61ecb65ec6ee0f42adce05368120a 100644 (file)
@@ -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
 
index 9d29ec51df0ba0b0f6e03b2d80709dd01f1e9444..2864e92b9667435902bf8fc27d4ca2ff5f359faf 100644 (file)
@@ -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 \
index 6039a1f5bb0673688eff7d0fe7db7d93b7363ae2..51c6df2175d181c8e9da527a1df6fc326c3f027f 100644 (file)
@@ -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 (file)
index 0000000..3bf30c9
--- /dev/null
@@ -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 */
index 204454d66953d19125b8f482759ded61f4e5527f..c93e7c78e5a2031cc186413448d802b737c0044a 100644 (file)
@@ -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)
 {
index 76f686c9f5f7e92c22ae901c883491610ca4c2e4..b035c7eebd831693c34948cea6d35b6deb6679a7 100644 (file)
@@ -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)
index 43da47e5bd48b70c5200c04785186032e82d4b48..5a013208322564a1ee56738bdee7b96ee7a5170d 100644 (file)
@@ -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;
index 1f69e0f93d1293fee11ccb9ea5d757314b25862c..6429e92846593dae7cd67455004d02e06e409a55 100644 (file)
@@ -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 \