]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Add KCM credential cache type (client only)
authorGreg Hudson <ghudson@mit.edu>
Tue, 1 Jul 2014 15:49:07 +0000 (11:49 -0400)
committerGreg Hudson <ghudson@mit.edu>
Wed, 30 Jul 2014 17:00:55 +0000 (13:00 -0400)
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)

13 files changed:
src/configure.in
src/include/k5-int.h
src/include/kcm.h [new file with mode: 0644]
src/lib/krb5/ccache/Makefile.in
src/lib/krb5/ccache/cc-int.h
src/lib/krb5/ccache/cc_kcm.c [new file with mode: 0644]
src/lib/krb5/ccache/ccbase.c
src/lib/krb5/ccache/ccmarshal.c
src/lib/krb5/ccache/deps
src/lib/krb5/ccache/kcmrpc.defs [new file with mode: 0644]
src/lib/krb5/ccache/kcmrpc_types.h [new file with mode: 0644]
src/lib/krb5/error_tables/k5e1_err.et
src/util/depfix.pl

index 659c4f8c79c8834ed761f4deb57c628243df3d63..a8a633e4fc607cd3f90faf5e164f53b3352c9134 100644 (file)
@@ -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
index d9cb5a4b2751d0d5926e9c18e1b894e3b1d7da49..d87b8481729b62f516a2f4cc36b3ba727b70b5a0 100644 (file)
@@ -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 (file)
index 0000000..5ea1447
--- /dev/null
@@ -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 */
index 13b0f7628325c0dabfe990e95b632c53cfae10e0..898dfe79cc40f04e06d796a8b7f1dea4e0d8013f 100644 (file)
@@ -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@
 
index ef08688817fa6249dc1b363d8f160a7830fe48da..ee9b5e0e97a1aa82ba62e52a0c3b341fb1bce79d 100644 (file)
@@ -142,6 +142,9 @@ k5_unmarshal_princ(const unsigned char *data, size_t len, int version,
 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 (file)
index 0000000..891ce3a
--- /dev/null
@@ -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 */
index 191a97a0d1eb4cc33b6d80865a8478d4c44b93f6..8198f2b9b9e3010e390165d1b5a8b42a3d96c6ee 100644 (file)
@@ -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 */
 
 
index 57fcd82d35f83e2ae0eda369fc7211402b4a814e..40eb6a8562bf8d0771c5067741ff183ff5b0d074 100644 (file)
@@ -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);
+}
index 677dd6e4bc8595f0dcd1435b2399c48c2433bca7..2f345622a079366bc78298aa75f39c5a13de4ebe 100644 (file)
@@ -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 (file)
index 0000000..997bae6
--- /dev/null
@@ -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 (file)
index 0000000..f43b41e
--- /dev/null
@@ -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
index 071b7f218e9bd05fc1f08e84477ab8046dd9e834..ade5caecfa46d2229d610b52c36a90a204e2d966 100644 (file)
@@ -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
index 47ab8107bae42232467e5d43bd81fb71ee8b0c9f..c8df54cbb29d8d1bdd434a0bc7b1858ef70b5a93 100644 (file)
@@ -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($_);