[28494] in CVS-changelog-for-Kerberos-V5

home help back first fref pref prev next nref lref last post

krb5 commit: Add KCM credential cache type (client only)

daemon@ATHENA.MIT.EDU (Greg Hudson)
Wed Jul 30 13:41:26 2014

Date: Wed, 30 Jul 2014 13:39:17 -0400
From: Greg Hudson <ghudson@mit.edu>
Message-Id: <201407301739.s6UHdHPY015019@drugstore.mit.edu>
To: cvs-krb5@mit.edu
Reply-To: krbdev@mit.edu
Content-Type: multipart/mixed; boundary="===============0114147889=="
Errors-To: cvs-krb5-bounces@mit.edu

--===============0114147889==

https://github.com/krb5/krb5/commit/2fa226e13ee3e7a6fddbfb68b27ed6b2c14c8474
commit 2fa226e13ee3e7a6fddbfb68b27ed6b2c14c8474
Author: Greg Hudson <ghudson@mit.edu>
Date:   Tue Jul 1 11:49:07 2014 -0400

    Add KCM credential cache type (client only)
    
    Add a new credential cache type "KCM" which performs cache operations
    by speaking to a Heimdal or OS X KCM daemon, via either Unix domain
    sockets or (on OS X only) Mach RPC.  Add "kcm_socket" and
    "kcm_mach_service" profile variables to control the socket path and
    bootstrap service name respectively.  In ccmarshal.c, add
    k5_marshal_mcred to marshal matching credentials in the KCM protocol
    representation.
    
    This cache type is not currently supported on Windows, as Windows does
    not support Unix domain sockets.
    
    As with the keyring cache type, the lastchange method of this cache
    type is mostly useless, reporting only the time of the last change
    made through that cache handle.  The KCM protocol currently has no
    support for obtaining the last change time of the cache itself.
    
    ticket: 7964 (new)

 src/configure.in                      |    7 +
 src/include/k5-int.h                  |    2 +
 src/include/kcm.h                     |   95 +++
 src/lib/krb5/ccache/Makefile.in       |   20 +-
 src/lib/krb5/ccache/cc-int.h          |    3 +
 src/lib/krb5/ccache/cc_kcm.c          | 1015 +++++++++++++++++++++++++++++++++
 src/lib/krb5/ccache/ccbase.c          |    5 +
 src/lib/krb5/ccache/ccmarshal.c       |   69 +++
 src/lib/krb5/ccache/deps              |   11 +
 src/lib/krb5/ccache/kcmrpc.defs       |   56 ++
 src/lib/krb5/ccache/kcmrpc_types.h    |   39 ++
 src/lib/krb5/error_tables/k5e1_err.et |    4 +
 src/util/depfix.pl                    |    6 +
 13 files changed, 1330 insertions(+), 2 deletions(-)

diff --git a/src/configure.in b/src/configure.in
index 659c4f8..a8a633e 100644
--- a/src/configure.in
+++ b/src/configure.in
@@ -1288,6 +1288,13 @@ if test "${localedir+set}" != set; then
 fi
 AC_SUBST(localedir)
 
+# For KCM lib/krb5/ccache to build KCM Mach RPC support for OS X only.
+case $host in
+*-*-darwin* | *-*-rhapsody*) OSX=osx ;;
+*)                           OSX=no ;;
+esac
+AC_SUBST(OSX)
+
 # Build-time default ccache, keytab, and client keytab names.  These
 # can be given as variable arguments DEFCCNAME, DEFKTNAME, and
 # DEFCKTNAME.  Otherwise, we try to get the OS defaults from
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index d9cb5a4..d87b848 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -225,6 +225,8 @@ typedef unsigned char   u_char;
 #define KRB5_CONF_K5LOGIN_AUTHORITATIVE        "k5login_authoritative"
 #define KRB5_CONF_K5LOGIN_DIRECTORY            "k5login_directory"
 #define KRB5_CONF_KADMIND_PORT                 "kadmind_port"
+#define KRB5_CONF_KCM_MACH_SERVICE             "kcm_mach_service"
+#define KRB5_CONF_KCM_SOCKET                   "kcm_socket"
 #define KRB5_CONF_KDC                          "kdc"
 #define KRB5_CONF_KDCDEFAULTS                  "kdcdefaults"
 #define KRB5_CONF_KDC_DEFAULT_OPTIONS          "kdc_default_options"
diff --git a/src/include/kcm.h b/src/include/kcm.h
new file mode 100644
index 0000000..5ea1447
--- /dev/null
+++ b/src/include/kcm.h
@@ -0,0 +1,95 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* include/kcm.h - Kerberos cache manager protocol declarations */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * 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.
+ */
+
+#ifndef KCM_H
+#define KCM_H
+
+#define KCM_PROTOCOL_VERSION_MAJOR 2
+#define KCM_PROTOCOL_VERSION_MINOR 0
+
+#define KCM_UUID_LEN 16
+
+/* This should ideally be in RUNSTATEDIR, but Heimdal uses a hardcoded
+ * /var/run, and we need to use the same default path. */
+#define DEFAULT_KCM_SOCKET_PATH "/var/run/.heim_org.h5l.kcm-socket"
+#define DEFAULT_KCM_MACH_SERVICE "org.h5l.kcm"
+
+/*
+ * All requests begin with:
+ *   major version (1 bytes)
+ *   minor version (1 bytes)
+ *   opcode (16-bit big-endian)
+ *
+ * All replies begin with a 32-bit big-endian reply code.
+ *
+ * Parameters are appended to the request or reply with no delimiters.  Flags
+ * and time offsets are stored as 32-bit big-endian integers.  Names are
+ * marshalled as zero-terminated strings.  Principals and credentials are
+ * marshalled in the v4 FILE ccache format.  UUIDs are 16 bytes.  UUID lists
+ * are not delimited, so nothing can come after them.
+ */
+
+/* Opcodes without comments are currently unused in the MIT client
+ * implementation. */
+typedef enum kcm_opcode {
+    KCM_OP_NOOP,
+    KCM_OP_GET_NAME,
+    KCM_OP_RESOLVE,
+    KCM_OP_GEN_NEW,             /*                     () -> (name)      */
+    KCM_OP_INITIALIZE,          /*          (name, princ) -> ()          */
+    KCM_OP_DESTROY,             /*                 (name) -> ()          */
+    KCM_OP_STORE,               /*           (name, cred) -> ()          */
+    KCM_OP_RETRIEVE,
+    KCM_OP_GET_PRINCIPAL,       /*                 (name) -> (princ)     */
+    KCM_OP_GET_CRED_UUID_LIST,  /*                 (name) -> (uuid, ...) */
+    KCM_OP_GET_CRED_BY_UUID,    /*           (name, uuid) -> (cred)      */
+    KCM_OP_REMOVE_CRED,         /* (name, flags, credtag) -> ()          */
+    KCM_OP_SET_FLAGS,
+    KCM_OP_CHOWN,
+    KCM_OP_CHMOD,
+    KCM_OP_GET_INITIAL_TICKET,
+    KCM_OP_GET_TICKET,
+    KCM_OP_MOVE_CACHE,
+    KCM_OP_GET_CACHE_UUID_LIST, /*                     () -> (uuid, ...) */
+    KCM_OP_GET_CACHE_BY_UUID,   /*                 (uuid) -> (name)      */
+    KCM_OP_GET_DEFAULT_CACHE,   /*                     () -> (name)      */
+    KCM_OP_SET_DEFAULT_CACHE,   /*                 (name) -> ()          */
+    KCM_OP_GET_KDC_OFFSET,      /*                 (name) -> (offset)    */
+    KCM_OP_SET_KDC_OFFSET,      /*         (name, offset) -> ()          */
+    KCM_OP_ADD_NTLM_CRED,
+    KCM_OP_HAVE_NTLM_CRED,
+    KCM_OP_DEL_NTLM_CRED,
+    KCM_OP_DO_NTLM_AUTH,
+    KCM_OP_GET_NTLM_USER_LIST
+} kcm_opcode;
+
+#endif /* KCM_H */
diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in
index 13b0f76..898dfe7 100644
--- a/src/lib/krb5/ccache/Makefile.in
+++ b/src/lib/krb5/ccache/Makefile.in
@@ -4,7 +4,7 @@ SUBDIRS = # ccapi
 WINSUBDIRS = ccapi
 ##WIN32##DEFINES = -DUSE_CCAPI -DUSE_CCAPI_V3
 
-LOCALINCLUDES = -I$(srcdir)$(S)ccapi $(WIN_INCLUDES)
+LOCALINCLUDES = -I$(srcdir)$(S)ccapi -I$(srcdir) -I. $(WIN_INCLUDES)
 
 ##DOS##WIN_INCLUDES = -I$(top_srcdir)\windows\lib
 
@@ -15,6 +15,11 @@ LOCALINCLUDES = -I$(srcdir)$(S)ccapi $(WIN_INCLUDES)
 ##WIN32##MSLSA_OBJ = $(OUTPRE)cc_mslsa.$(OBJEXT)
 ##WIN32##MSLSA_SRC = $(srcdir)/cc_mslsa.c
 
+KCMRPC_DEPS-osx = kcmrpc.h kcmrpc_types.h
+KCMRPC_OBJ-osx = kcmrpc.o
+KCMRPC_DEPS-no = # empty
+KCMRPC_OBJ-no = # empty
+
 STLIBOBJS= \
 	ccbase.o \
 	cccopy.o \
@@ -28,10 +33,11 @@ STLIBOBJS= \
 	cc_dir.o \
 	cc_retr.o \
 	cc_file.o \
+	cc_kcm.o \
 	cc_memory.o \
 	cc_keyring.o \
 	ccfns.o \
-	ser_cc.o
+	ser_cc.o $(KCMRPC_OBJ-@OSX@)
 
 OBJS=	$(OUTPRE)ccbase.$(OBJEXT) \
 	$(OUTPRE)cccopy.$(OBJEXT) \
@@ -45,6 +51,7 @@ OBJS=	$(OUTPRE)ccbase.$(OBJEXT) \
 	$(OUTPRE)cc_dir.$(OBJEXT) \
 	$(OUTPRE)cc_retr.$(OBJEXT) \
 	$(OUTPRE)cc_file.$(OBJEXT) \
+	$(OUTPRE)cc_kcm.$(OBJEXT) \
 	$(OUTPRE)cc_memory.$(OBJEXT) \
 	$(OUTPRE)cc_keyring.$(OBJEXT) \
 	$(OUTPRE)ccfns.$(OBJEXT) \
@@ -62,6 +69,7 @@ SRCS=	$(srcdir)/ccbase.c \
 	$(srcdir)/cc_dir.c \
 	$(srcdir)/cc_retr.c \
 	$(srcdir)/cc_file.c \
+	$(srcdir)/cc_kcm.c \
 	$(srcdir)/cc_memory.c \
 	$(srcdir)/cc_keyring.c \
 	$(srcdir)/ccfns.c \
@@ -92,6 +100,9 @@ all-windows:: subdirs $(OBJFILE)
 ##WIN32##	$(LIBECHO) -p $(PREFIXDIR)\ $(OUTPRE)*.obj \
 ##WIN32##		ccapi\$(OUTPRE)*.obj > $(OBJFILE)
 
+kcmrpc.h kcmrpc.c: kcmrpc.defs
+	mig -header kcmrpc.h -user kcmrpc.c -sheader /dev/null \
+		-server /dev/null -I$(srcdir) $(srcdir)/kcmrpc.defs
 
 clean-unix:: clean-libobjs
 
@@ -133,7 +144,12 @@ clean-unix::
 	$(RM) t_cc t_cc.o t_cccursor t_cccursor.o t_cccol t_cccol.o
 	$(RM) t_marshal t_marshal.o testcache
 
+depend:: $(KCMRPC_DEPS-@OSX@)
+
 ##WIN32## $(OUTPRE)cc_mslsa.$(OBJEXT): cc_mslsa.c $(top_srcdir)/include/k5-int.h $(BUILDTOP)/include/krb5/osconf.h $(BUILDTOP)/include/krb5/autoconf.h $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS)
 
+cc_kcm.so cc_kcm.o: $(KCMRPC_DEPS-@OSX@)
+kcmrpc.so kcmrpc.o: kcmrpc.h kcmrpc_types.h
+
 @libobj_frag@
 
diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h
index ef08688..ee9b5e0 100644
--- a/src/lib/krb5/ccache/cc-int.h
+++ b/src/lib/krb5/ccache/cc-int.h
@@ -143,6 +143,9 @@ void
 k5_marshal_cred(struct k5buf *buf, int version, krb5_creds *creds);
 
 void
+k5_marshal_mcred(struct k5buf *buf, krb5_creds *mcred);
+
+void
 k5_marshal_princ(struct k5buf *buf, int version, krb5_principal princ);
 
 /*
diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c
new file mode 100644
index 0000000..891ce3a
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_kcm.c
@@ -0,0 +1,1015 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_kcm.c - KCM cache type (client side) */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * 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 cache type contacts a daemon for each cache operation, using Heimdal's
+ * KCM protocol.  On OS X, the preferred transport is Mach RPC; on other
+ * Unix-like platforms or if the daemon is not available via RPC, Unix domain
+ * sockets are used instead.
+ */
+
+#include "k5-int.h"
+#include "k5-input.h"
+#include "cc-int.h"
+#include "kcm.h"
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef __APPLE__
+#include <mach/mach.h>
+#include <servers/bootstrap.h>
+#include "kcmrpc.h"
+#endif
+
+#ifndef _WIN32
+
+#define MAX_REPLY_SIZE (10 * 1024 * 1024)
+
+const krb5_cc_ops krb5_kcm_ops;
+
+struct uuid_list {
+    unsigned char *uuidbytes;   /* all of the uuids concatenated together */
+    size_t count;
+    size_t pos;
+};
+
+struct kcmio {
+    int fd;
+#ifdef __APPLE__
+    mach_port_t mport;
+#endif
+};
+
+/* This structure bundles together a KCM request and reply, to minimize how
+ * much we have to declare and clean up in each method. */
+struct kcmreq {
+    struct k5buf reqbuf;
+    struct k5input reply;
+    void *reply_mem;
+};
+#define EMPTY_KCMREQ { EMPTY_K5BUF }
+
+struct kcm_cache_data {
+    char *residual;             /* immutable; may be accessed without lock */
+    k5_cc_mutex lock;           /* protects io and changetime */
+    struct kcmio *io;
+    krb5_timestamp changetime;
+};
+
+struct kcm_ptcursor {
+    struct uuid_list *uuids;
+    struct kcmio *io;
+};
+
+/* Map EINVAL or KRB5_CC_FORMAT to KRB5_KCM_MALFORMED_REPLY; pass through all
+ * other codes. */
+static inline krb5_error_code
+map_invalid(krb5_error_code code)
+{
+    return (code == EINVAL || code == KRB5_CC_FORMAT) ?
+        KRB5_KCM_MALFORMED_REPLY : code;
+}
+
+/* Begin a request for the given opcode.  If cache is non-null, supply the
+ * cache name as a request parameter. */
+static void
+kcmreq_init(struct kcmreq *req, kcm_opcode opcode, krb5_ccache cache)
+{
+    unsigned char bytes[4];
+    const char *name;
+
+    memset(req, 0, sizeof(*req));
+
+    bytes[0] = KCM_PROTOCOL_VERSION_MAJOR;
+    bytes[1] = KCM_PROTOCOL_VERSION_MINOR;
+    store_16_be(opcode, bytes + 2);
+
+    k5_buf_init_dynamic(&req->reqbuf);
+    k5_buf_add_len(&req->reqbuf, bytes, 4);
+    if (cache != NULL) {
+        name = ((struct kcm_cache_data *)cache->data)->residual;
+        k5_buf_add_len(&req->reqbuf, name, strlen(name) + 1);
+    }
+}
+
+/* Add a 32-bit value to the request in big-endian byte order. */
+static void
+kcmreq_put32(struct kcmreq *req, uint32_t val)
+{
+    unsigned char bytes[4];
+
+    store_32_be(val, bytes);
+    k5_buf_add_len(&req->reqbuf, bytes, 4);
+}
+
+#ifdef __APPLE__
+
+/* The maximum length of an in-band request or reply as defined by the RPC
+ * protocol. */
+#define MAX_INBAND_SIZE 2048
+
+/* Connect or reconnect to the KCM daemon via Mach RPC, if possible. */
+static krb5_error_code
+kcmio_mach_connect(krb5_context context, struct kcmio *io)
+{
+    krb5_error_code ret;
+    kern_return_t st;
+    mach_port_t mport;
+    char *service;
+
+    ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
+                             KRB5_CONF_KCM_MACH_SERVICE, NULL,
+                             DEFAULT_KCM_MACH_SERVICE, &service);
+    if (ret)
+        return ret;
+    if (strcmp(service, "-") == 0) {
+        profile_release_string(service);
+        return KRB5_KCM_NO_SERVER;
+    }
+
+    st = bootstrap_look_up(bootstrap_port, service, &mport);
+    profile_release_string(service);
+    if (st)
+        return KRB5_KCM_NO_SERVER;
+    if (io->mport != MACH_PORT_NULL)
+        mach_port_deallocate(mach_task_self(), io->mport);
+    io->mport = mport;
+    return 0;
+}
+
+/* Invoke the Mach RPC to get a reply from the KCM daemon. */
+static krb5_error_code
+kcmio_mach_call(krb5_context context, struct kcmio *io, void *data,
+                size_t len, void **reply_out, size_t *len_out)
+{
+    krb5_error_code ret;
+    size_t inband_req_len = 0, outband_req_len = 0, reply_len;
+    char *inband_req = NULL, *outband_req = NULL, *outband_reply, *copy;
+    char inband_reply[MAX_INBAND_SIZE];
+    mach_msg_type_number_t inband_reply_len, outband_reply_len;
+    const void *reply;
+    kern_return_t st;
+    int code;
+
+    *reply_out = NULL;
+    *len_out = 0;
+
+    /* Use the in-band or out-of-band request buffer depending on len. */
+    if (len <= MAX_INBAND_SIZE) {
+        inband_req = data;
+        inband_req_len = len;
+    } else {
+        outband_req = data;
+        outband_req_len = len;
+    }
+
+    st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
+                        outband_req_len, &code, inband_reply,
+                        &inband_reply_len, &outband_reply, &outband_reply_len);
+    if (st == MACH_SEND_INVALID_DEST) {
+        /* Get a new port and try again. */
+        st = kcmio_mach_connect(context, io);
+        if (st)
+            return KRB5_KCM_RPC_ERROR;
+        st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
+                            outband_req_len, &code, inband_reply,
+                            &inband_reply_len, &outband_reply,
+                            &outband_reply_len);
+    }
+    if (st)
+        return KRB5_KCM_RPC_ERROR;
+
+    if (code) {
+        ret = code;
+        goto cleanup;
+    }
+
+    /* The reply could be in the in-band or out-of-band reply buffer. */
+    reply = outband_reply_len ? outband_reply : inband_reply;
+    reply_len = outband_reply_len ? outband_reply_len : inband_reply_len;
+    copy = k5memdup(reply, reply_len, &ret);
+    if (copy == NULL)
+        goto cleanup;
+
+    *reply_out = copy;
+    *len_out = reply_len;
+
+cleanup:
+    if (outband_reply_len) {
+        vm_deallocate(mach_task_self(), (vm_address_t)outband_reply,
+                      outband_reply_len);
+    }
+    return ret;
+}
+
+/* Release any Mach RPC state within io. */
+static void
+kcmio_mach_close(struct kcmio *io)
+{
+    if (io->mport != MACH_PORT_NULL)
+        mach_port_deallocate(mach_task_self(), io->mport);
+}
+
+#else /* __APPLE__ */
+
+#define kcmio_mach_connect(context, io) EINVAL
+#define kcmio_mach_call(context, io, data, len, reply_out, len_out) EINVAL
+#define kcmio_mach_close(io)
+
+#endif
+
+/* Connect to the KCM daemon via a Unix domain socket. */
+static krb5_error_code
+kcmio_unix_socket_connect(krb5_context context, struct kcmio *io)
+{
+    krb5_error_code ret;
+    int fd = -1;
+    struct sockaddr_un addr;
+    char *path = NULL;
+
+    ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
+                             KRB5_CONF_KCM_SOCKET, NULL,
+                             DEFAULT_KCM_SOCKET_PATH, &path);
+    if (ret)
+        goto cleanup;
+    if (strcmp(path, "-") == 0) {
+        ret = KRB5_KCM_NO_SERVER;
+        goto cleanup;
+    }
+
+    fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (fd == -1) {
+        ret = errno;
+        goto cleanup;
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+        ret = (errno == ENOENT) ? KRB5_KCM_NO_SERVER : errno;
+        goto cleanup;
+    }
+
+    io->fd = fd;
+    fd = -1;
+
+cleanup:
+    if (fd != -1)
+        close(fd);
+    profile_release_string(path);
+    return ret;
+}
+
+/* Write a KCM request: 4-byte big-endian length, then the marshalled
+ * request. */
+static krb5_error_code
+kcmio_unix_socket_write(krb5_context context, struct kcmio *io, void *request,
+                        size_t len)
+{
+    char lenbytes[4];
+
+    store_32_be(len, lenbytes);
+    if (krb5_net_write(context, io->fd, lenbytes, 4) < 0)
+        return errno;
+    if (krb5_net_write(context, io->fd, request, len) < 0)
+        return errno;
+    return 0;
+}
+
+/* Read a KCM reply: 4-byte big-endian length, 4-byte big-endian status code,
+ * then the marshalled reply. */
+static krb5_error_code
+kcmio_unix_socket_read(krb5_context context, struct kcmio *io,
+                       void **reply_out, size_t *len_out)
+{
+    krb5_error_code code;
+    char lenbytes[4], codebytes[4], *reply;
+    size_t len;
+    int st;
+
+    *reply_out = NULL;
+    *len_out = 0;
+
+    st = krb5_net_read(context, io->fd, lenbytes, 4);
+    if (st != 4)
+        return (st == -1) ? errno : KRB5_CC_IO;
+    len = load_32_be(lenbytes);
+    if (len > MAX_REPLY_SIZE)
+        return KRB5_KCM_REPLY_TOO_BIG;
+
+    st = krb5_net_read(context, io->fd, codebytes, 4);
+    if (st != 4)
+        return (st == -1) ? errno : KRB5_CC_IO;
+    code = load_32_be(codebytes);
+    if (code != 0)
+        return code;
+
+    reply = malloc(len);
+    if (reply == NULL)
+        return ENOMEM;
+    st = krb5_net_read(context, io->fd, reply, len);
+    if (st == -1 || (size_t)st != len) {
+        free(reply);
+        return (st < 0) ? errno : KRB5_CC_IO;
+    }
+
+    *reply_out = reply;
+    *len_out = len;
+    return 0;
+}
+
+static krb5_error_code
+kcmio_connect(krb5_context context, struct kcmio **io_out)
+{
+    krb5_error_code ret;
+    struct kcmio *io;
+
+    *io_out = NULL;
+    io = calloc(1, sizeof(*io));
+    if (io == NULL)
+        return ENOMEM;
+    io->fd = -1;
+
+    /* Try Mach RPC (OS X only), then fall back to Unix domain sockets */
+    ret = kcmio_mach_connect(context, io);
+    if (ret)
+        ret = kcmio_unix_socket_connect(context, io);
+    if (ret) {
+        free(io);
+        return ret;
+    }
+
+    *io_out = io;
+    return 0;
+}
+
+/* Check req->reqbuf for an error condition and return it.  Otherwise, send the
+ * request to the KCM daemon and get a response. */
+static krb5_error_code
+kcmio_call(krb5_context context, struct kcmio *io, struct kcmreq *req)
+{
+    krb5_error_code ret;
+    size_t reply_len;
+
+    if (k5_buf_status(&req->reqbuf) != 0)
+        return ENOMEM;
+
+    if (io->fd != -1) {
+        ret = kcmio_unix_socket_write(context, io, req->reqbuf.data,
+                                      req->reqbuf.len);
+        if (ret)
+            return ret;
+        ret = kcmio_unix_socket_read(context, io, &req->reply_mem, &reply_len);
+        if (ret)
+            return ret;
+    } else {
+        /* We must be using Mach RPC. */
+        ret = kcmio_mach_call(context, io, req->reqbuf.data, req->reqbuf.len,
+                              &req->reply_mem, &reply_len);
+        if (ret)
+            return ret;
+    }
+
+    /* Read the status code from the marshalled reply. */
+    k5_input_init(&req->reply, req->reply_mem, reply_len);
+    ret = k5_input_get_uint32_be(&req->reply);
+    return req->reply.status ? KRB5_KCM_MALFORMED_REPLY : ret;
+}
+
+static void
+kcmio_close(struct kcmio *io)
+{
+    if (io != NULL) {
+        kcmio_mach_close(io);
+        if (io->fd != -1)
+            close(io->fd);
+        free(io);
+    }
+}
+
+/* Fetch a zero-terminated name string from req->reply.  The returned pointer
+ * is an alias and must not be freed by the caller. */
+static krb5_error_code
+kcmreq_get_name(struct kcmreq *req, const char **name_out)
+{
+    const unsigned char *end;
+    struct k5input *in = &req->reply;
+
+    *name_out = NULL;
+    end = memchr(in->ptr, '\0', in->len);
+    if (end == NULL)
+        return KRB5_KCM_MALFORMED_REPLY;
+    *name_out = (const char *)in->ptr;
+    (void)k5_input_get_bytes(in, end + 1 - in->ptr);
+    return 0;
+}
+
+/* Fetch a UUID list from req->reply.  UUID lists are not delimited, so we
+ * consume the rest of the input. */
+static krb5_error_code
+kcmreq_get_uuid_list(struct kcmreq *req, struct uuid_list **uuids_out)
+{
+    struct uuid_list *uuids;
+
+    *uuids_out = NULL;
+
+    if (req->reply.len % KCM_UUID_LEN != 0)
+        return KRB5_KCM_MALFORMED_REPLY;
+
+    uuids = malloc(sizeof(*uuids));
+    if (uuids == NULL)
+        return ENOMEM;
+    uuids->count = req->reply.len / KCM_UUID_LEN;
+    uuids->pos = 0;
+
+    if (req->reply.len > 0) {
+        uuids->uuidbytes = malloc(req->reply.len);
+        if (uuids->uuidbytes == NULL) {
+            free(uuids);
+            return ENOMEM;
+        }
+        memcpy(uuids->uuidbytes, req->reply.ptr, req->reply.len);
+        (void)k5_input_get_bytes(&req->reply, req->reply.len);
+    } else {
+        uuids->uuidbytes = NULL;
+    }
+
+    *uuids_out = uuids;
+    return 0;
+}
+
+static void
+free_uuid_list(struct uuid_list *uuids)
+{
+    if (uuids != NULL)
+        free(uuids->uuidbytes);
+    free(uuids);
+}
+
+static void
+kcmreq_free(struct kcmreq *req)
+{
+    k5_buf_free(&req->reqbuf);
+    free(req->reply_mem);
+}
+
+/* Create a krb5_ccache structure.  Always take ownership of io. */
+static krb5_error_code
+make_cache(const char *residual, struct kcmio *io, krb5_ccache *cache_out)
+{
+    krb5_ccache cache = NULL;
+    struct kcm_cache_data *data = NULL;
+    char *residual_copy = NULL;
+
+    *cache_out = NULL;
+    assert(io != NULL);
+
+    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;
+    if (k5_cc_mutex_init(&data->lock) != 0)
+        goto oom;
+
+    data->residual = residual_copy;
+    data->io = io;
+    data->changetime = 0;
+    cache->ops = &krb5_kcm_ops;
+    cache->data = data;
+    cache->magic = KV5M_CCACHE;
+    *cache_out = cache;
+    return 0;
+
+oom:
+    free(cache);
+    free(data);
+    free(residual_copy);
+    kcmio_close(io);
+    return ENOMEM;
+}
+
+/* Lock cache's I/O structure and use it to call the KCM daemon.  If modify is
+ * true, update the last change time. */
+static krb5_error_code
+cache_call(krb5_context context, krb5_ccache cache, struct kcmreq *req,
+           krb5_boolean modify)
+{
+    krb5_error_code ret;
+    struct kcm_cache_data *data = cache->data;
+
+    k5_cc_mutex_lock(context, &data->lock);
+    ret = kcmio_call(context, data->io, req);
+    if (modify && !ret)
+        data->changetime = time(NULL);
+    k5_cc_mutex_unlock(context, &data->lock);
+    return ret;
+}
+
+/* Try to propagate the KDC time offset from the cache to the krb5 context. */
+static void
+get_kdc_offset(krb5_context context, krb5_ccache cache)
+{
+    struct kcmreq req = EMPTY_KCMREQ;
+    int32_t time_offset;
+
+    kcmreq_init(&req, KCM_OP_GET_KDC_OFFSET, cache);
+    if (cache_call(context, cache, &req, FALSE) != 0)
+        goto cleanup;
+    time_offset = k5_input_get_uint32_be(&req.reply);
+    if (!req.reply.status)
+        goto cleanup;
+    context->os_context.time_offset = time_offset;
+    context->os_context.usec_offset = 0;
+    context->os_context.os_flags &= ~KRB5_OS_TOFFSET_TIME;
+    context->os_context.os_flags |= KRB5_OS_TOFFSET_VALID;
+
+cleanup:
+    kcmreq_free(&req);
+}
+
+/* Try to propagate the KDC offset from the krb5 context to the cache. */
+static void
+set_kdc_offset(krb5_context context, krb5_ccache cache)
+{
+    struct kcmreq req;
+
+    if (context->os_context.os_flags & KRB5_OS_TOFFSET_VALID) {
+        kcmreq_init(&req, KCM_OP_SET_KDC_OFFSET, cache);
+        kcmreq_put32(&req, context->os_context.time_offset);
+        (void)cache_call(context, cache, &req, TRUE);
+        kcmreq_free(&req);
+    }
+}
+
+static const char * KRB5_CALLCONV
+kcm_get_name(krb5_context context, krb5_ccache cache)
+{
+    return ((struct kcm_cache_data *)cache->data)->residual;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
+{
+    krb5_error_code ret;
+    struct kcmreq req = EMPTY_KCMREQ;
+    struct kcmio *io = NULL;
+    const char *defname = NULL;
+
+    *cache_out = NULL;
+
+    ret = kcmio_connect(context, &io);
+    if (ret)
+        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);
+        if (ret)
+            goto cleanup;
+        residual = defname;
+    }
+
+    ret = make_cache(residual, io, cache_out);
+    io = NULL;
+
+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)
+{
+    krb5_error_code ret;
+    struct kcmreq req = EMPTY_KCMREQ;
+    struct kcmio *io = NULL;
+    const char *name;
+
+    *cache_out = NULL;
+
+    ret = kcmio_connect(context, &io);
+    if (ret)
+        goto cleanup;
+    kcmreq_init(&req, KCM_OP_GEN_NEW, NULL);
+    ret = kcmio_call(context, io, &req);
+    if (ret)
+        goto cleanup;
+    ret = kcmreq_get_name(&req, &name);
+    if (ret)
+        goto cleanup;
+    ret = make_cache(name, io, cache_out);
+    io = NULL;
+
+cleanup:
+    kcmreq_free(&req);
+    kcmio_close(io);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_initialize(krb5_context context, krb5_ccache cache, krb5_principal princ)
+{
+    krb5_error_code ret;
+    struct kcmreq req;
+
+    kcmreq_init(&req, KCM_OP_INITIALIZE, cache);
+    k5_marshal_princ(&req.reqbuf, 4, princ);
+    ret = cache_call(context, cache, &req, TRUE);
+    kcmreq_free(&req);
+    set_kdc_offset(context, cache);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_close(krb5_context context, krb5_ccache cache)
+{
+    struct kcm_cache_data *data = cache->data;
+
+    k5_cc_mutex_destroy(&data->lock);
+    kcmio_close(data->io);
+    free(data->residual);
+    free(data);
+    free(cache);
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_destroy(krb5_context context, krb5_ccache cache)
+{
+    krb5_error_code ret;
+    struct kcmreq req;
+
+    kcmreq_init(&req, KCM_OP_DESTROY, cache);
+    ret = cache_call(context, cache, &req, TRUE);
+    kcmreq_free(&req);
+    (void)kcm_close(context, cache);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_store(krb5_context context, krb5_ccache cache, krb5_creds *cred)
+{
+    krb5_error_code ret;
+    struct kcmreq req;
+
+    kcmreq_init(&req, KCM_OP_STORE, cache);
+    k5_marshal_cred(&req.reqbuf, 4, cred);
+    ret = cache_call(context, cache, &req, TRUE);
+    kcmreq_free(&req);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
+             krb5_creds *mcred, krb5_creds *cred_out)
+{
+    /* There is a KCM opcode for retrieving creds, but Heimdal's client doesn't
+     * use it.  It causes the KCM daemon to actually make a TGS request. */
+    return k5_cc_retrieve_cred_default(context, cache, flags, mcred, cred_out);
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_get_princ(krb5_context context, krb5_ccache cache,
+              krb5_principal *princ_out)
+{
+    krb5_error_code ret;
+    struct kcmreq req;
+
+    kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, cache);
+    ret = cache_call(context, cache, &req, FALSE);
+    /* Heimdal KCM can respond with code 0 and no principal. */
+    if (!ret && req.reply.len == 0)
+        ret = KRB5_FCC_NOFILE;
+    if (!ret)
+        ret = k5_unmarshal_princ(req.reply.ptr, req.reply.len, 4, princ_out);
+    kcmreq_free(&req);
+    return map_invalid(ret);
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_start_seq_get(krb5_context context, krb5_ccache cache,
+                  krb5_cc_cursor *cursor_out)
+{
+    krb5_error_code ret;
+    struct kcmreq req = EMPTY_KCMREQ;
+    struct uuid_list *uuids;
+
+    *cursor_out = NULL;
+
+    get_kdc_offset(context, cache);
+
+    kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache);
+    ret = cache_call(context, cache, &req, FALSE);
+    if (ret)
+        goto cleanup;
+    ret = kcmreq_get_uuid_list(&req, &uuids);
+    if (ret)
+        goto cleanup;
+    *cursor_out = (krb5_cc_cursor)uuids;
+
+cleanup:
+    kcmreq_free(&req);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
+              krb5_creds *cred_out)
+{
+    krb5_error_code ret;
+    struct kcmreq req;
+    struct uuid_list *uuids = (struct uuid_list *)*cursor;
+
+    memset(cred_out, 0, sizeof(*cred_out));
+
+    if (uuids->pos >= uuids->count)
+        return KRB5_CC_END;
+
+    kcmreq_init(&req, KCM_OP_GET_CRED_BY_UUID, cache);
+    k5_buf_add_len(&req.reqbuf, uuids->uuidbytes + (uuids->pos * KCM_UUID_LEN),
+                   KCM_UUID_LEN);
+    uuids->pos++;
+    ret = cache_call(context, cache, &req, FALSE);
+    if (!ret)
+        ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, cred_out);
+    kcmreq_free(&req);
+    return map_invalid(ret);
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_end_seq_get(krb5_context context, krb5_ccache cache,
+                krb5_cc_cursor *cursor)
+{
+    free_uuid_list((struct uuid_list *)*cursor);
+    *cursor = NULL;
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
+                krb5_creds *mcred)
+{
+    krb5_error_code ret;
+    struct kcmreq req;
+
+    kcmreq_init(&req, KCM_OP_REMOVE_CRED, cache);
+    kcmreq_put32(&req, flags);
+    k5_marshal_mcred(&req.reqbuf, mcred);
+    ret = cache_call(context, cache, &req, TRUE);
+    kcmreq_free(&req);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
+{
+    /* We don't currently care about any flags for this type. */
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out)
+{
+    /* We don't currently have any operational flags for this type. */
+    *flags_out = 0;
+    return 0;
+}
+
+/* Construct a per-type cursor, always taking ownership of io and uuids. */
+static krb5_error_code
+make_ptcursor(struct uuid_list *uuids, struct kcmio *io,
+              krb5_cc_ptcursor *cursor_out)
+{
+    krb5_cc_ptcursor cursor = NULL;
+    struct kcm_ptcursor *data = NULL;
+
+    *cursor_out = NULL;
+
+    cursor = malloc(sizeof(*cursor));
+    if (cursor == NULL)
+        goto oom;
+    data = malloc(sizeof(*data));
+    if (data == NULL)
+        goto oom;
+
+    data->uuids = uuids;
+    data->io = io;
+    cursor->ops = &krb5_kcm_ops;
+    cursor->data = data;
+    *cursor_out = cursor;
+    return 0;
+
+oom:
+    kcmio_close(io);
+    free_uuid_list(uuids);
+    free(data);
+    free(cursor);
+    return ENOMEM;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
+{
+    krb5_error_code ret;
+    struct kcmreq req = EMPTY_KCMREQ;
+    struct kcmio *io = NULL;
+    struct uuid_list *uuids;
+    const char *defname;
+
+    *cursor_out = NULL;
+
+    /* Don't try to use KCM for the cache collection unless the default cache
+     * name has the KCM type. */
+    defname = krb5_cc_default_name(context);
+    if (defname == NULL || strncmp(defname, "KCM:", 4) != 0)
+        return make_ptcursor(NULL, NULL, cursor_out);
+
+    ret = kcmio_connect(context, &io);
+    if (ret)
+        goto cleanup;
+
+    kcmreq_init(&req, KCM_OP_GET_CACHE_UUID_LIST, NULL);
+    ret = kcmio_call(context, io, &req);
+    if (ret == KRB5_FCC_NOFILE) {
+        /* There are no accessible caches; return an empty cursor. */
+        ret = make_ptcursor(NULL, NULL, cursor_out);
+        goto cleanup;
+    }
+    if (ret)
+        goto cleanup;
+
+    ret = kcmreq_get_uuid_list(&req, &uuids);
+    if (ret)
+        goto cleanup;
+
+    ret = make_ptcursor(uuids, io, cursor_out);
+    io = NULL;
+
+cleanup:
+    kcmio_close(io);
+    kcmreq_free(&req);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
+                  krb5_ccache *cache_out)
+{
+    krb5_error_code ret;
+    struct kcmreq req = EMPTY_KCMREQ;
+    struct kcmio *io;
+    struct kcm_ptcursor *data = cursor->data;
+    struct uuid_list *uuids = data->uuids;
+    const char *name;
+
+    *cache_out = NULL;
+
+    if (uuids == NULL || uuids->pos >= uuids->count)
+        return 0;
+
+    kcmreq_init(&req, KCM_OP_GET_CACHE_BY_UUID, NULL);
+    k5_buf_add_len(&req.reqbuf, uuids->uuidbytes + (uuids->pos * KCM_UUID_LEN),
+                   KCM_UUID_LEN);
+    uuids->pos++;
+    ret = kcmio_call(context, data->io, &req);
+    if (ret)
+        goto cleanup;
+
+    ret = kcmreq_get_name(&req, &name);
+    if (ret)
+        goto cleanup;
+
+    ret = kcmio_connect(context, &io);
+    if (ret)
+        goto cleanup;
+    ret = make_cache(name, io, cache_out);
+
+cleanup:
+    kcmreq_free(&req);
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+    struct kcm_ptcursor *data = (*cursor)->data;
+
+    free_uuid_list(data->uuids);
+    kcmio_close(data->io);
+    free(data);
+    free(*cursor);
+    *cursor = NULL;
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_lastchange(krb5_context context, krb5_ccache cache,
+               krb5_timestamp *time_out)
+{
+    struct kcm_cache_data *data = cache->data;
+
+    /*
+     * KCM has no support for retrieving the last change time.  Return the time
+     * of the last change made through this handle, which isn't very useful,
+     * but is the best we can do for now.
+     */
+    k5_cc_mutex_lock(context, &data->lock);
+    *time_out = data->changetime;
+    k5_cc_mutex_unlock(context, &data->lock);
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_lock(krb5_context context, krb5_ccache cache)
+{
+    k5_cc_mutex_lock(context, &((struct kcm_cache_data *)cache->data)->lock);
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_unlock(krb5_context context, krb5_ccache cache)
+{
+    k5_cc_mutex_unlock(context, &((struct kcm_cache_data *)cache->data)->lock);
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_switch_to(krb5_context context, krb5_ccache cache)
+{
+    krb5_error_code ret;
+    struct kcmreq req;
+
+    kcmreq_init(&req, KCM_OP_SET_DEFAULT_CACHE, cache);
+    ret = cache_call(context, cache, &req, FALSE);
+    kcmreq_free(&req);
+    return ret;
+}
+
+const krb5_cc_ops krb5_kcm_ops = {
+    0,
+    "KCM",
+    kcm_get_name,
+    kcm_resolve,
+    kcm_gen_new,
+    kcm_initialize,
+    kcm_destroy,
+    kcm_close,
+    kcm_store,
+    kcm_retrieve,
+    kcm_get_princ,
+    kcm_start_seq_get,
+    kcm_next_cred,
+    kcm_end_seq_get,
+    kcm_remove_cred,
+    kcm_set_flags,
+    kcm_get_flags,
+    kcm_ptcursor_new,
+    kcm_ptcursor_next,
+    kcm_ptcursor_free,
+    NULL, /* move */
+    kcm_lastchange,
+    NULL, /* wasdefault */
+    kcm_lock,
+    kcm_unlock,
+    kcm_switch_to,
+};
+
+#endif /* not _WIN32 */
diff --git a/src/lib/krb5/ccache/ccbase.c b/src/lib/krb5/ccache/ccbase.c
index 191a97a..8198f2b 100644
--- a/src/lib/krb5/ccache/ccbase.c
+++ b/src/lib/krb5/ccache/ccbase.c
@@ -80,6 +80,11 @@ extern const krb5_cc_ops krb5_dcc_ops;
 static struct krb5_cc_typelist cc_dcc_entry = { &krb5_dcc_ops, NEXT };
 #undef NEXT
 #define NEXT &cc_dcc_entry
+
+extern const krb5_cc_ops krb5_kcm_ops;
+static struct krb5_cc_typelist cc_kcm_entry = { &krb5_kcm_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_kcm_entry
 #endif /* not _WIN32 */
 
 
diff --git a/src/lib/krb5/ccache/ccmarshal.c b/src/lib/krb5/ccache/ccmarshal.c
index 57fcd82..40eb6a8 100644
--- a/src/lib/krb5/ccache/ccmarshal.c
+++ b/src/lib/krb5/ccache/ccmarshal.c
@@ -445,3 +445,72 @@ k5_marshal_cred(struct k5buf *buf, int version, krb5_creds *creds)
     put_data(buf, version, &creds->ticket);
     put_data(buf, version, &creds->second_ticket);
 }
+
+#define SC_CLIENT_PRINCIPAL  0x0001
+#define SC_SERVER_PRINCIPAL  0x0002
+#define SC_SESSION_KEY       0x0004
+#define SC_TICKET            0x0008
+#define SC_SECOND_TICKET     0x0010
+#define SC_AUTHDATA          0x0020
+#define SC_ADDRESSES         0x0040
+
+/* Construct the header flags field for a matching credential for the Heimdal
+ * KCM format. */
+static uint32_t
+mcred_header(krb5_creds *mcred)
+{
+    uint32_t header = 0;
+
+    if (mcred->client != NULL)
+        header |= SC_CLIENT_PRINCIPAL;
+    if (mcred->server != NULL)
+        header |= SC_SERVER_PRINCIPAL;
+    if (mcred->keyblock.enctype != ENCTYPE_NULL)
+        header |= SC_SESSION_KEY;
+    if (mcred->ticket.length > 0)
+        header |= SC_TICKET;
+    if (mcred->second_ticket.length > 0)
+        header |= SC_SECOND_TICKET;
+    if (mcred->authdata != NULL && *mcred->authdata != NULL)
+        header |= SC_AUTHDATA;
+    if (mcred->addresses != NULL && *mcred->addresses != NULL)
+        header |= SC_ADDRESSES;
+    return header;
+}
+
+/*
+ * Marshal a matching credential in the Heimdal KCM format.  Matching
+ * credentials are used to identify an existing credential to retrieve or
+ * remove from a cache.
+ */
+void
+k5_marshal_mcred(struct k5buf *buf, krb5_creds *mcred)
+{
+    const int version = 4;      /* subfields use v4 file format */
+    uint32_t header;
+    char is_skey;
+
+    header = mcred_header(mcred);
+    put32(buf, version, header);
+    if (mcred->client != NULL)
+        k5_marshal_princ(buf, version, mcred->client);
+    if (mcred->server != NULL)
+        k5_marshal_princ(buf, version, mcred->server);
+    if (mcred->keyblock.enctype != ENCTYPE_NULL)
+        marshal_keyblock(buf, version, &mcred->keyblock);
+    put32(buf, version, mcred->times.authtime);
+    put32(buf, version, mcred->times.starttime);
+    put32(buf, version, mcred->times.endtime);
+    put32(buf, version, mcred->times.renew_till);
+    is_skey = mcred->is_skey;
+    k5_buf_add_len(buf, &is_skey, 1);
+    put32(buf, version, mcred->ticket_flags);
+    if (mcred->addresses != NULL && *mcred->addresses != NULL)
+        marshal_addrs(buf, version, mcred->addresses);
+    if (mcred->authdata != NULL && *mcred->authdata != NULL)
+        marshal_authdata(buf, version, mcred->authdata);
+    if (mcred->ticket.length > 0)
+        put_data(buf, version, &mcred->ticket);
+    if (mcred->second_ticket.length > 0)
+        put_data(buf, version, &mcred->second_ticket);
+}
diff --git a/src/lib/krb5/ccache/deps b/src/lib/krb5/ccache/deps
index 677dd6e..2f34562 100644
--- a/src/lib/krb5/ccache/deps
+++ b/src/lib/krb5/ccache/deps
@@ -131,6 +131,17 @@ cc_file.so cc_file.po $(OUTPRE)cc_file.$(OBJEXT): $(BUILDTOP)/include/autoconf.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_file.c
+cc_kcm.so cc_kcm.po $(OUTPRE)cc_kcm.$(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 \
+  $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+  $(top_srcdir)/include/k5-input.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/kcm.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_kcm.c
 cc_memory.so cc_memory.po $(OUTPRE)cc_memory.$(OBJEXT): \
   $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
   $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
diff --git a/src/lib/krb5/ccache/kcmrpc.defs b/src/lib/krb5/ccache/kcmrpc.defs
new file mode 100644
index 0000000..997bae6
--- /dev/null
+++ b/src/lib/krb5/ccache/kcmrpc.defs
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2009 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
+ */
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+
+type k5_kcm_inband_msg = array [ * : 2048 ] of char;
+type k5_kcm_outband_msg = array [] of char;
+
+import "kcmrpc_types.h";
+
+subsystem mheim_ipc 1;
+userprefix k5_kcmrpc_;
+serverprefix k5_kcmrpc_server_;
+
+routine call(
+                                server_port     : mach_port_t;
+                ServerAuditToken client_creds   : audit_token_t;
+                sreplyport      reply_port      : mach_port_make_send_once_t;
+                in              requestin       : k5_kcm_inband_msg;
+                in              requestout      : k5_kcm_outband_msg;
+                out             returnvalue     : int;
+                out             replyin         : k5_kcm_inband_msg;
+                out             replyout        : k5_kcm_outband_msg, dealloc);
diff --git a/src/lib/krb5/ccache/kcmrpc_types.h b/src/lib/krb5/ccache/kcmrpc_types.h
new file mode 100644
index 0000000..f43b41e
--- /dev/null
+++ b/src/lib/krb5/ccache/kcmrpc_types.h
@@ -0,0 +1,39 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/kcmrpc_types.h - KCM RPC type definitions */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * 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.
+ */
+
+#ifndef KCMRPC_H
+#define KCMRPC_H
+
+typedef char k5_kcm_inband_msg[2048];
+typedef char *k5_kcm_outband_msg;
+
+#endif
diff --git a/src/lib/krb5/error_tables/k5e1_err.et b/src/lib/krb5/error_tables/k5e1_err.et
index 071b7f2..ade5cae 100644
--- a/src/lib/krb5/error_tables/k5e1_err.et
+++ b/src/lib/krb5/error_tables/k5e1_err.et
@@ -38,4 +38,8 @@ error_code KRB5_DCC_CANNOT_CREATE, "Can't create new subsidiary cache"
 error_code KRB5_KCC_INVALID_ANCHOR, "Invalid keyring anchor name"
 error_code KRB5_KCC_UNKNOWN_VERSION, "Unknown keyring collection version"
 error_code KRB5_KCC_INVALID_UID, "Invalid UID in persistent keyring name"
+error_code KRB5_KCM_MALFORMED_REPLY, "Malformed reply from KCM daemon"
+error_code KRB5_KCM_RPC_ERROR, "Mach RPC error communicating with KCM daemon"
+error_code KRB5_KCM_REPLY_TOO_BIG, "KCM daemon reply too big"
+error_code KRB5_KCM_NO_SERVER, "No KCM server found"
 end
diff --git a/src/util/depfix.pl b/src/util/depfix.pl
index 47ab810..c8df54c 100644
--- a/src/util/depfix.pl
+++ b/src/util/depfix.pl
@@ -146,6 +146,12 @@ sub do_subs_2 {
 	# Here com_err.h is used from the current directory.
 	s;com_err.h ;\$(COM_ERR_DEPS) ;g;
     }
+    if ($thisdir eq "lib/krb5/ccache") {
+	# These files are only used (and kcmrpc.h only generated) on OS X.
+	# There are conditional dependencies in Makefile.in.
+	s;kcmrpc.h ;;g;
+	s;kcmrpc_types.h ;;g;
+    }
 
     $_ = &uniquify($_);
 

--===============0114147889==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
cvs-krb5 mailing list
cvs-krb5@mit.edu
https://mailman.mit.edu/mailman/listinfo/cvs-krb5

--===============0114147889==--

home help back first fref pref prev next nref lref last post