From 76123e2e1044b464a9853b3de8627f1962a7876f Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 15 Mar 2017 13:32:42 +0100
Subject: [PATCH 01/21] UTIL: iobuf: Make input parameter for the readonly
 operation const

---
 src/util/sss_iobuf.c | 2 +-
 src/util/sss_iobuf.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/util/sss_iobuf.c b/src/util/sss_iobuf.c
index 7c72ea9..900418b 100644
--- a/src/util/sss_iobuf.c
+++ b/src/util/sss_iobuf.c
@@ -49,7 +49,7 @@ struct sss_iobuf *sss_iobuf_init_empty(TALLOC_CTX *mem_ctx,
 }
 
 struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx,
-                                          uint8_t *data,
+                                          const uint8_t *data,
                                           size_t size)
 {
     struct sss_iobuf *iobuf;
diff --git a/src/util/sss_iobuf.h b/src/util/sss_iobuf.h
index eae357a..900faaa 100644
--- a/src/util/sss_iobuf.h
+++ b/src/util/sss_iobuf.h
@@ -47,7 +47,7 @@ struct sss_iobuf *sss_iobuf_init_empty(TALLOC_CTX *mem_ctx,
  * @return The newly created buffer on success or NULL on an error.
  */
 struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx,
-                                          uint8_t *data,
+                                          const uint8_t *data,
                                           size_t size);
 
 /*

From a64d7c8fe8d34b9e3ac3f054a81f329642a3a5fc Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 15 Mar 2017 13:42:03 +0100
Subject: [PATCH 02/21] UTIL: Fix a typo in the tcurl test tool

---
 src/tests/tcurl_test_tool.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/tests/tcurl_test_tool.c b/src/tests/tcurl_test_tool.c
index 35ea979..38cea43 100644
--- a/src/tests/tcurl_test_tool.c
+++ b/src/tests/tcurl_test_tool.c
@@ -204,8 +204,8 @@ int main(int argc, const char *argv[])
                               urls[i],
                               headers,
                               inbufs[i],
-                              10);
-        if (ctx == NULL) {
+                              5);
+        if (req == NULL) {
             DEBUG(SSSDBG_FATAL_FAILURE, "Could not create request\n");
             talloc_zfree(tool_ctx);
             return 1;

From ba2fc36817c0f7146b1132517ab03037a9cb7a00 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Thu, 23 Feb 2017 20:55:05 +0100
Subject: [PATCH 03/21] UTIL: Add SAFEALIGN_COPY_UINT8_CHECK

This macro will be used later in the KCM code
---
 src/util/util_safealign.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/util/util_safealign.h b/src/util/util_safealign.h
index 0d9a579..57f04a1 100644
--- a/src/util/util_safealign.h
+++ b/src/util/util_safealign.h
@@ -130,6 +130,12 @@ safealign_memcpy(void *dest, const void *src, size_t n, size_t *counter)
     safealign_memcpy(dest, src, srclen, pctr); \
 } while(0)
 
+#define SAFEALIGN_COPY_UINT8_CHECK(dest, src, len, pctr) do { \
+    if ((*(pctr) + sizeof(uint8_t)) > (len) || \
+        SIZE_T_OVERFLOW(*(pctr), sizeof(uint8_t))) { return EINVAL; } \
+    safealign_memcpy(dest, src, sizeof(uint8_t), pctr); \
+} while(0)
+
 /* Aliases for backward compatibility. */
 #define SAFEALIGN_SET_VALUE SAFEALIGN_SETMEM_VALUE
 #define SAFEALIGN_SET_INT64 SAFEALIGN_SETMEM_INT64

From 50dd2b67a0af7915838b7caa9c80d21939b13180 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 20 Sep 2016 22:00:27 +0200
Subject: [PATCH 04/21] UTIL: Add utility macro cli_creds_get_gid()

The KCM responder checks the owneship of the ccache based on both UID
and GID of the peer. In order to reuse the already existing creds
structure, let's just add a new macro that returns the GID from the
creds structure.
---
 src/util/util_creds.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/util/util_creds.h b/src/util/util_creds.h
index 65468fa..936b996 100644
--- a/src/util/util_creds.h
+++ b/src/util/util_creds.h
@@ -71,6 +71,7 @@ struct cli_creds {
 };
 
 #define cli_creds_get_uid(x) x->ucred.uid
+#define cli_creds_get_gid(x) x->ucred.gid
 
 #else /* not HAVE_UCRED */
 struct cli_creds {

From 8946a2fb98bb08c93f82991aace12209497102d2 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Thu, 23 Feb 2017 21:57:13 +0100
Subject: [PATCH 05/21] UTIL: Add type-specific getsetters to sss_iobuf

The KCM responder receives its input as unstructured data. To make the
parsing easier, this commit adds several type-specific getsetters to the
iobuf module.
---
 src/util/sss_iobuf.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/util/sss_iobuf.h |  33 ++++++++++++++++
 2 files changed, 141 insertions(+)

diff --git a/src/util/sss_iobuf.c b/src/util/sss_iobuf.c
index 900418b..fc288d2 100644
--- a/src/util/sss_iobuf.c
+++ b/src/util/sss_iobuf.c
@@ -184,6 +184,25 @@ errno_t sss_iobuf_read(struct sss_iobuf *iobuf,
     return EOK;
 }
 
+errno_t sss_iobuf_read_len(struct sss_iobuf *iobuf,
+                           size_t len,
+                           uint8_t *_buf)
+{
+    size_t read;
+    errno_t ret;
+
+    ret = sss_iobuf_read(iobuf, len, _buf, &read);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    if (read != len) {
+        return ENOBUFS;
+    }
+
+    return EOK;
+}
+
 errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf,
                             uint8_t *buf,
                             size_t len)
@@ -203,3 +222,92 @@ errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf,
 
     return EOK;
 }
+
+errno_t sss_iobuf_read_uint32(struct sss_iobuf *iobuf,
+                              uint32_t *_val)
+{
+    SAFEALIGN_COPY_UINT32_CHECK(_val, iobuf_ptr(iobuf),
+                                iobuf->capacity, &iobuf->dp);
+    return EOK;
+}
+
+errno_t sss_iobuf_read_int32(struct sss_iobuf *iobuf,
+                             int32_t *_val)
+{
+    SAFEALIGN_COPY_INT32_CHECK(_val, iobuf_ptr(iobuf),
+                               iobuf->capacity, &iobuf->dp);
+    return EOK;
+}
+
+errno_t sss_iobuf_write_uint32(struct sss_iobuf *iobuf,
+                               uint32_t val)
+{
+    errno_t ret;
+
+    ret = ensure_bytes(iobuf, sizeof(uint32_t));
+    if (ret != EOK) {
+        return ret;
+    }
+
+    SAFEALIGN_SETMEM_UINT32(iobuf_ptr(iobuf), val, &iobuf->dp);
+    return EOK;
+}
+
+errno_t sss_iobuf_write_int32(struct sss_iobuf *iobuf,
+                              int32_t val)
+{
+    errno_t ret;
+
+    ret = ensure_bytes(iobuf, sizeof(int32_t));
+    if (ret != EOK) {
+        return ret;
+    }
+
+    SAFEALIGN_SETMEM_INT32(iobuf_ptr(iobuf), val, &iobuf->dp);
+    return EOK;
+}
+
+errno_t sss_iobuf_read_stringz(struct sss_iobuf *iobuf,
+                               const char **_out)
+{
+    uint8_t *end;
+    size_t len;
+
+    if (iobuf == NULL) {
+        return EINVAL;
+    }
+
+    if (_out == NULL) {
+        return EINVAL;
+    }
+
+    *_out = NULL;
+
+    end = memchr(iobuf_ptr(iobuf), '\0', sss_iobuf_get_size(iobuf));
+    if (end == NULL) {
+        return EINVAL;
+    }
+
+    len = end + 1 - iobuf_ptr(iobuf);
+    if (sss_iobuf_get_size(iobuf) < len) {
+        return EINVAL;
+    }
+
+    *_out = (const char *) iobuf_ptr(iobuf);
+    iobuf->dp += len;
+    return EOK;
+}
+
+errno_t sss_iobuf_write_stringz(struct sss_iobuf *iobuf,
+                                const char *str)
+{
+    if (iobuf == NULL || str == NULL) {
+        return EINVAL;
+    }
+
+    SAFEALIGN_MEMCPY_CHECK(iobuf_ptr(iobuf),
+                           str, strlen(str)+1,
+                           sss_iobuf_get_size(iobuf),
+                           &iobuf->dp);
+    return EOK;
+}
diff --git a/src/util/sss_iobuf.h b/src/util/sss_iobuf.h
index 900faaa..cc3dfd1 100644
--- a/src/util/sss_iobuf.h
+++ b/src/util/sss_iobuf.h
@@ -96,6 +96,22 @@ errno_t sss_iobuf_read(struct sss_iobuf *iobuf,
                        size_t *_read);
 
 /*
+ * @brief Read an exact number of bytes from an IO buffer
+ *
+ * Read exactly len bytes from an IO buffer. If the buffer contains fewer
+ * bytes than len, ENOBUFS is returned.
+ *
+ * @param[in]  iobuf        The IO buffer to read from
+ * @param[in]  len          The maximum number of bytes to read
+ * @param[out] _buf         The buffer to read data into from iobuf
+ *
+ * @return EOK on success, errno otherwise
+ */
+errno_t sss_iobuf_read_len(struct sss_iobuf *iobuf,
+                           size_t len,
+                           uint8_t *_buf);
+
+/*
  * @brief Write into an IO buffer
  *
  * Attempts to write len bytes into the iobuf. If the capacity is exceeded,
@@ -115,4 +131,21 @@ errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf,
                             uint8_t *buf,
                             size_t len);
 
+errno_t sss_iobuf_read_uint32(struct sss_iobuf *iobuf,
+                              uint32_t *_val);
+
+errno_t sss_iobuf_write_uint32(struct sss_iobuf *iobuf,
+                               uint32_t val);
+
+errno_t sss_iobuf_read_int32(struct sss_iobuf *iobuf,
+                             int32_t *_val);
+
+errno_t sss_iobuf_write_int32(struct sss_iobuf *iobuf,
+                              int32_t val);
+
+errno_t sss_iobuf_read_stringz(struct sss_iobuf *iobuf,
+                               const char **_out);
+
+errno_t sss_iobuf_write_stringz(struct sss_iobuf *iobuf,
+                                const char *str);
 #endif /* __SSS_IOBUF_H_ */

From 9cb9a3a60beb0150860e3ce947aff97e1727ce5a Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 20 Sep 2016 22:03:30 +0200
Subject: [PATCH 06/21] UTIL: krb5 principal (un)marshalling

The KCM responder needs to read the contents of the principal blob that
the Kerberos library sends. Since libkrb5 doesn't export any API to do
so, we need to implement marshalling and unmarshalling of the principal
ourselves.

In future, when the KCM server also supports renewals, we will also need
to unmarshall the credentials, but until that is not really needed, the
credentials will be stored as a blob.
---
 src/util/sss_krb5.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/util/sss_krb5.h |   9 +++
 2 files changed, 204 insertions(+)

diff --git a/src/util/sss_krb5.c b/src/util/sss_krb5.c
index 4808a77..d461cf8 100644
--- a/src/util/sss_krb5.c
+++ b/src/util/sss_krb5.c
@@ -24,6 +24,7 @@
 
 #include "config.h"
 
+#include "util/sss_iobuf.h"
 #include "util/util.h"
 #include "util/sss_krb5.h"
 
@@ -1128,3 +1129,197 @@ bool sss_krb5_realm_has_proxy(const char *realm)
 
     return res;
 }
+
+static errno_t iobuf_read_uint32be(struct sss_iobuf *iobuf,
+                                   uint32_t *_val)
+{
+    uint32_t beval;
+    errno_t ret;
+
+    ret = sss_iobuf_read_uint32(iobuf, &beval);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    *_val = be32toh(beval);
+    return EOK;
+}
+
+static errno_t iobuf_write_uint32be(struct sss_iobuf *iobuf,
+                                    uint32_t val)
+{
+    uint32_t beval;
+
+    beval = htobe32(val);
+    return sss_iobuf_write_uint32(iobuf, beval);
+}
+
+static errno_t iobuf_get_len_bytes(TALLOC_CTX *mem_ctx,
+                                   struct sss_iobuf *iobuf,
+                                   uint32_t *_nbytes,
+                                   uint8_t **_bytes)
+{
+    errno_t ret;
+    uint32_t nbytes;
+    uint8_t *bytes = NULL;
+
+    ret = iobuf_read_uint32be(iobuf, &nbytes);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    bytes = talloc_zero_size(mem_ctx, nbytes);
+    if (bytes == NULL) {
+        return ENOMEM;
+    }
+
+    ret = sss_iobuf_read_len(iobuf, nbytes, bytes);
+    if (ret != EOK) {
+        talloc_free(bytes);
+        return ret;
+    }
+
+    *_bytes = bytes;
+    *_nbytes = nbytes;
+    return EOK;
+}
+
+static errno_t get_krb5_data(TALLOC_CTX *mem_ctx,
+                             struct sss_iobuf *iobuf,
+                             krb5_data *k5data)
+{
+    errno_t ret;
+    uint32_t nbytes;
+    uint8_t *bytes = NULL;
+
+    ret = iobuf_get_len_bytes(mem_ctx, iobuf,  &nbytes, &bytes);
+    if (ret != EOK) {
+        talloc_free(bytes);
+        return ret;
+    }
+
+    k5data->data = (char *) bytes; /* FIXME - the cast is ugly */
+    k5data->length = nbytes;
+    return EOK;
+}
+
+static errno_t set_krb5_data(struct sss_iobuf *iobuf,
+                             krb5_data *k5data)
+{
+    errno_t ret;
+
+    ret = iobuf_write_uint32be(iobuf, k5data->length);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    if (k5data->length > 0) {
+        ret = sss_iobuf_write_len(iobuf,
+                                  (uint8_t *) k5data->data,
+                                  k5data->length);
+        if (ret != EOK) {
+            return ret;
+        }
+    }
+
+    return EOK;
+}
+
+/* FIXME - it would be nice if Kerberos exported these APIs.. */
+krb5_error_code sss_krb5_unmarshal_princ(TALLOC_CTX *mem_ctx,
+                                         struct sss_iobuf *iobuf,
+                                         krb5_principal *_princ)
+{
+    krb5_principal princ = NULL;
+    krb5_error_code ret;
+    uint32_t ncomps;
+
+    if (iobuf == NULL || _princ == NULL) {
+        return EINVAL;
+    }
+
+    princ = talloc_zero(mem_ctx, struct krb5_principal_data);
+    if (princ == NULL) {
+        return ENOMEM;
+    }
+
+    princ->magic = KV5M_PRINCIPAL;
+
+    ret = iobuf_read_uint32be(iobuf, (uint32_t *) &princ->type);
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    ret = iobuf_read_uint32be(iobuf, &ncomps);
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    if (ncomps > sss_iobuf_get_capacity(iobuf)) {
+        /* Sanity check to avoid large allocations */
+        ret = EINVAL;
+        goto fail;
+    }
+
+    if (ncomps != 0) {
+        princ->data = talloc_zero_array(princ, krb5_data, ncomps);
+        if (princ->data == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        princ->length = ncomps;
+    }
+
+    ret = get_krb5_data(princ, iobuf, &princ->realm);
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    for (size_t i = 0; i < ncomps; i++) {
+        ret = get_krb5_data(princ->data, iobuf, &princ->data[i]);
+        if (ret != EOK) {
+            goto fail;
+        }
+    }
+
+    *_princ = princ;
+    return 0;
+
+fail:
+    talloc_free(princ);
+    return ret;
+}
+
+krb5_error_code sss_krb5_marshal_princ(krb5_principal princ,
+                                       struct sss_iobuf *iobuf)
+{
+    krb5_error_code ret;
+
+    if (iobuf == NULL || princ == NULL) {
+        return EINVAL;
+    }
+
+    ret = iobuf_write_uint32be(iobuf, princ->type);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    ret = iobuf_write_uint32be(iobuf, princ->length);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    ret = set_krb5_data(iobuf, &princ->realm);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    for (int i = 0; i < princ->length; i++) {
+        ret = set_krb5_data(iobuf, &princ->data[i]);
+        if (ret != EOK) {
+            return ret;
+        }
+    }
+    return EOK;
+}
diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h
index ac0f608..0d9043b 100644
--- a/src/util/sss_krb5.h
+++ b/src/util/sss_krb5.h
@@ -32,6 +32,7 @@
 #include <krb5.h>
 #endif
 
+#include "util/sss_iobuf.h"
 #include "util/util.h"
 
 #define KRB5_CHILD_LOG_FILE     "krb5_child"
@@ -186,4 +187,12 @@ krb5_error_code sss_krb5_kt_have_content(krb5_context context,
                                          krb5_keytab keytab);
 
 bool sss_krb5_realm_has_proxy(const char *realm);
+
+krb5_error_code sss_krb5_marshal_princ(krb5_principal princ,
+                                       struct sss_iobuf *iobuf);
+
+krb5_error_code sss_krb5_unmarshal_princ(TALLOC_CTX *mem_ctx,
+                                         struct sss_iobuf *iobuf,
+                                         krb5_principal *_princ);
+
 #endif /* __SSS_KRB5_H__ */

From 7ffdb7b0f58dd59040f2021d463df956c7882947 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 1 Aug 2016 12:52:07 +0200
Subject: [PATCH 07/21] KCM: Initial responder build and packaging

Adds the initial build of the Kerberos Cache Manager responder (KCM).

This is a deamon that is capable of holding and storing Kerberos
ccaches. When KCM is used, the kerberos libraries (invoked through e.g.
kinit) are referred to as a 'client' and the KCM deamon is referred to
as 'server'.

At the moment, only the Heimdal implementation of Kerberos implements the
KCM server:
    https://www.h5l.org/manual/HEAD/info/heimdal/Credential-cache-server-_002d-KCM.html
This patch adds a KCM server to SSSD.

In MIT, only the 'client-side' support was added:
    http://k5wiki.kerberos.org/wiki/Projects/KCM_client
This page also describes the protocol between the client and the server.

The client is capable of talking to the server over either UNIX sockets
(Linux, most Unixes) or Mach RPC (macOS). Our server only implements the
UNIX sockets way and should be socket-activated by systemd, although can
in theory be also ran explicitly.

The KCM server only builds if the configuration option "--with-kcm" is
enabled. It is packaged in a new subpackage sssd-kcm in order to allow
distributions to enable the KCM credential caches by installing this
subpackage only, without the rest of the SSSD. The sssd-kcm subpackage
also includes a krb5.conf.d snippet that allows the admin to just uncomment
the KCM defaults and instructs them to start the socket.

The server can be configured in sssd.conf in the "[kcm]" section.
By default, the server only listens on the same socket path the Heimdal
server uses, which is "/var/run/.heim_org.h5l.kcm-socket". This is,
however, configurable.

The file src/responder/kcm/kcm.h is more or less directly imported from
the MIT Kerberos tree, with an additional sentinel code and some
comments. Not all KCM operations are implemented, only those that also
the MIT client implements. That said, this KCM server should also be
usable with a Heimdal client, although no special testing was with this
hybrid.

The patch also adds several error codes that will be used in later
patches.

Related to:
    https://pagure.io/SSSD/sssd/issue/2887
---
 Makefile.am                          |  53 ++++++++
 configure.ac                         |  10 +-
 contrib/kcm_default_ccache           |  12 ++
 contrib/sssd.spec.in                 |  41 ++++++
 src/conf_macros.m4                   |  16 +++
 src/confdb/confdb.h                  |   3 +
 src/config/cfg_rules.ini             |  19 +++
 src/external/libcurl.m4              |   6 +-
 src/responder/kcm/kcm.c              | 254 +++++++++++++++++++++++++++++++++++
 src/responder/kcm/kcm.h              |  97 +++++++++++++
 src/responder/kcm/kcmsrv_cmd.c       |  65 +++++++++
 src/responder/kcm/kcmsrv_pvt.h       |  58 ++++++++
 src/sysv/systemd/sssd-kcm.service.in |   9 ++
 src/sysv/systemd/sssd-kcm.socket.in  |  10 ++
 src/util/util_errors.c               |   5 +
 src/util/util_errors.h               |   5 +
 16 files changed, 658 insertions(+), 5 deletions(-)
 create mode 100644 contrib/kcm_default_ccache
 create mode 100644 src/responder/kcm/kcm.c
 create mode 100644 src/responder/kcm/kcm.h
 create mode 100644 src/responder/kcm/kcmsrv_cmd.c
 create mode 100644 src/responder/kcm/kcmsrv_pvt.h
 create mode 100644 src/sysv/systemd/sssd-kcm.service.in
 create mode 100644 src/sysv/systemd/sssd-kcm.socket.in

diff --git a/Makefile.am b/Makefile.am
index 45b04de..6fdbcfb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -87,6 +87,7 @@ sudolibdir = @sudolibpath@
 polkitdir = @polkitdir@
 pamconfdir = $(sysconfdir)/pam.d
 systemtap_tapdir = @tapset_dir@
+krb5sysincludedir = $(sysconfdir)/krb5.conf.d
 
 if HAVE_SYSTEMD_UNIT
 ifp_exec_cmd = $(sssdlibexecdir)/sssd_ifp --uid 0 --gid 0 --debug-to-files --dbus-activated
@@ -186,6 +187,11 @@ endif
 if BUILD_SECRETS
 sssdlibexec_PROGRAMS += sssd_secrets
 endif
+if BUILD_KCM
+sssdlibexec_PROGRAMS += sssd_kcm
+dist_krb5sysinclude_DATA = contrib/kcm_default_ccache
+endif
+
 
 if BUILD_PAC_RESPONDER
     sssdlibexec_PROGRAMS += sssd_pac
@@ -698,6 +704,8 @@ dist_noinst_HEADERS = \
     src/responder/secrets/secsrv_private.h \
     src/responder/secrets/secsrv_local.h \
     src/responder/secrets/secsrv_proxy.h \
+    src/responder/kcm/kcm.h \
+    src/responder/kcm/kcmsrv_pvt.h \
     src/sbus/sbus_client.h \
     src/sbus/sssd_dbus.h \
     src/sbus/sssd_dbus_meta.h \
@@ -1466,6 +1474,24 @@ sssd_secrets_LDADD = \
     $(NULL)
 endif
 
+if BUILD_KCM
+sssd_kcm_SOURCES = \
+    src/responder/kcm/kcm.c \
+    src/responder/kcm/kcmsrv_cmd.c \
+    src/util/sss_sockets.c \
+    $(SSSD_RESPONDER_OBJ) \
+    $(NULL)
+sssd_kcm_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(KRB5_CFLAGS) \
+    $(NULL)
+sssd_kcm_LDADD = \
+    $(KRB5_LIBS) \
+    $(SSSD_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    $(NULL)
+endif
+
 sssd_be_SOURCES = \
     src/providers/data_provider_be.c \
     src/providers/data_provider_req.c \
@@ -4157,6 +4183,12 @@ if BUILD_SUDO
         src/sysv/systemd/sssd-sudo.service \
         $(NULL)
 endif
+if BUILD_KCM
+    systemdunit_DATA += \
+        src/sysv/systemd/sssd-kcm.socket \
+        src/sysv/systemd/sssd-kcm.service \
+        $(NULL)
+endif
 if WITH_JOURNALD
     systemdconf_DATA += \
         src/sysv/systemd/journal.conf
@@ -4248,6 +4280,12 @@ EXTRA_DIST += \
     src/sysv/systemd/sssd-sudo.service.in \
     $(NULL)
 endif
+if BUILD_KCM
+EXTRA_DIST += \
+    src/sysv/systemd/sssd-kcm.socket.in \
+    src/sysv/systemd/sssd-kcm.service.in \
+    $(NULL)
+endif
 
 src/sysv/systemd/sssd.service: src/sysv/systemd/sssd.service.in Makefile
 	@$(MKDIR_P) src/sysv/systemd/
@@ -4331,6 +4369,16 @@ src/sysv/systemd/sssd-sudo.service: src/sysv/systemd/sssd-sudo.service.in Makefi
 	$(replace_script)
 endif
 
+if BUILD_KCM
+src/sysv/systemd/sssd-kcm.socket: src/sysv/systemd/sssd-kcm.socket.in Makefile
+	@$(MKDIR_P) src/sysv/systemd/
+	$(replace_script)
+
+src/sysv/systemd/sssd-kcm.service: src/sysv/systemd/sssd-kcm.service.in Makefile
+	@$(MKDIR_P) src/sysv/systemd/
+	$(replace_script)
+endif
+
 SSSD_USER_DIRS = \
     $(DESTDIR)$(dbpath) \
     $(DESTDIR)$(keytabdir) \
@@ -4493,6 +4541,9 @@ install-data-hook:
 if BUILD_SAMBA
 	mv $(DESTDIR)/$(winbindplugindir)/winbind_idmap_sss.so $(DESTDIR)/$(winbindplugindir)/sss.so
 endif
+if BUILD_KCM
+	$(MKDIR_P) $(DESTDIR)/$(krb5sysincludedir)
+endif
 
 uninstall-hook:
 	if [ -f $(abs_builddir)/src/config/.files2 ]; then \
@@ -4567,6 +4618,8 @@ endif
 	rm -f $(builddir)/src/sysv/systemd/sssd-sudo.service
 	rm -f $(builddir)/src/sysv/systemd/sssd-secrets.socket
 	rm -f $(builddir)/src/sysv/systemd/sssd-secrets.service
+	rm -f $(builddir)/src/sysv/systemd/sssd-kcm.socket
+	rm -f $(builddir)/src/sysv/systemd/sssd-kcm.service
 	rm -f $(builddir)/src/sysv/systemd/journal.conf
 
 CLEANFILES += *.X */*.X */*/*.X
diff --git a/configure.ac b/configure.ac
index e6a3b4e..42f2796 100644
--- a/configure.ac
+++ b/configure.ac
@@ -155,6 +155,7 @@ WITH_SSSD_USER
 SSSD_RUNSTATEDIR
 WITH_SECRETS
 WITH_SECRETS_DB_PATH
+WITH_KCM
 
 m4_include([src/external/pkg.m4])
 m4_include([src/external/libpopt.m4])
@@ -193,13 +194,20 @@ m4_include([src/external/libresolv.m4])
 m4_include([src/external/intgcheck.m4])
 m4_include([src/external/systemtap.m4])
 m4_include([src/external/service.m4])
-m4_include([src/external/libcurl.m4])
 
 if test x$with_secrets = xyes; then
     m4_include([src/external/libhttp_parser.m4])
     m4_include([src/external/libjansson.m4])
 fi
 
+if test x$with_kcm = xyes; then
+    m4_include([src/external/libcurl.m4])
+fi
+# This variable is defined by external/libcurl.m4, but conditionals
+# must be always evaluated
+AM_CONDITIONAL([BUILD_WITH_LIBCURL],
+               [test x"$have_curlopt_unix_sockpath" = xyes])
+
 WITH_UNICODE_LIB
 if test x$unicode_lib = xlibunistring; then
     m4_include([src/external/libunistring.m4])
diff --git a/contrib/kcm_default_ccache b/contrib/kcm_default_ccache
new file mode 100644
index 0000000..ac88fca
--- /dev/null
+++ b/contrib/kcm_default_ccache
@@ -0,0 +1,12 @@
+# This file should normally be installed by your distribution into a
+# directory that is included from the Kerberos configuration file (/etc/krb5.conf)
+# On Fedora/RHEL/CentOS, this is /etc/krb5.conf.d/
+#
+# To enable the KCM credential cache, uncomment the following lines and
+# enable the KCM socket and the service:
+#   systemctl enable sssd-kcm.socket
+#   systemctl start sssd-kcm.socket
+#   systemctl enable sssd-kcm.service
+
+#[libdefaults]
+#    default_ccache_name = KCM:
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index 5bd2beb..bf1c69d 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -112,6 +112,13 @@
     %global enable_systemtap_opt --enable-systemtap
 %endif
 
+%if (0%{?fedora} >= 23 || 0%{?rhel} >= 7)
+    %global with_kcm 1
+    %global with_kcm_option --with-kcm
+%else
+    %global with_kcm_option --without-kcm
+%endif
+
 Name: @PACKAGE_NAME@
 Version: @PACKAGE_VERSION@
 Release: 0@PRERELEASE_VERSION@%{?dist}
@@ -658,6 +665,18 @@ The libnfsidmap sssd module provides a way for rpc.idmapd to call SSSD to map
 UIDs/GIDs to names and vice versa. It can be also used for mapping principal
 (user) name to IDs(UID or GID) or to obtain groups which user are member of.
 
+%if (0%{?with_kcm} == 1)
+%package kcm
+Summary: An implementation of a Kerberos KCM server
+Group:  Applications/System
+License: GPLv3+
+Requires: sssd-common = %{version}-%{release}
+
+%description kcm
+An implementation of a Kerberos KCM server. Use this package if you want to
+use the KCM: Kerberos credentials cache.
+%endif
+
 %prep
 %setup -q -n %{name}-%{version}
 
@@ -687,6 +706,7 @@ autoreconf -ivf
     %{?with_python3_option} \
     %{?enable_polkit_rules_option} \
     %{?enable_systemtap_opt} \
+    %{?with_kcm_option} \
     %{?experimental}
 
 make %{?_smp_mflags} all
@@ -1146,6 +1166,15 @@ done
 %files nfs-idmap
 %{_libdir}/libnfsidmap/sss.so
 
+%if (0%{?with_kcm} == 1)
+%files kcm
+%{_libexecdir}/%{servicename}/sssd_kcm
+%dir %{_sysconfdir}/krb5.conf.d
+%config(noreplace) %{_sysconfdir}/krb5.conf.d/kcm_default_ccache
+%{_unitdir}/sssd-kcm.socket
+%{_unitdir}/sssd-kcm.service
+%endif
+
 %pre common
 getent group sssd >/dev/null || groupadd -r sssd
 getent passwd sssd >/dev/null || useradd -r -g sssd -d / -s /sbin/nologin -c "User for sssd" sssd
@@ -1242,6 +1271,18 @@ fi
 
 %postun -n libsss_simpleifp -p /sbin/ldconfig
 
+%if (0%{?with_kcm} == 1)
+%post kcm
+%systemd_post sssd-kcm.socket
+
+%preun kcm
+%systemd_preun sssd-kcm.socket
+
+%postun kcm
+%systemd_postun_with_restart sssd-kcm.socket
+%systemd_postun_with_restart sssd-kcm.service
+%endif
+
 %changelog
 * Mon Mar 15 2010 Stephen Gallagher <sgallagh@redhat.com> - @PACKAGE_VERSION@-0@PRERELEASE_VERSION@
 - Automated build of the SSSD
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index 749e769..4209972 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -887,6 +887,22 @@ AC_DEFUN([WITH_SECRETS],
     AM_CONDITIONAL([BUILD_SECRETS], [test x"$with_secrets" = xyes])
   ])
 
+AC_DEFUN([WITH_KCM],
+  [ AC_ARG_WITH([kcm],
+                [AC_HELP_STRING([--with-kcm],
+                                [Whether to build with KCM server support [yes]]
+                               )
+                ],
+                [with_kcm=$withval],
+                with_kcm=yes
+               )
+
+    if test x"$with_kcm" = xyes; then
+        AC_DEFINE(BUILD_KCM, 1, [whether to build with KCM server support])
+    fi
+    AM_CONDITIONAL([BUILD_KCM], [test x"$with_kcm" = xyes])
+  ])
+
 AC_DEFUN([WITH_SECRETS_DB_PATH],
   [ AC_ARG_WITH([secrets-db-path],
                 [AC_HELP_STRING([--with-secrets-db-path=PATH],
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index c05b1ce..c443e86 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -231,6 +231,9 @@
 #define CONFDB_SEC_MAX_SECRETS "max_secrets"
 #define CONFDB_SEC_MAX_PAYLOAD_SIZE "max_payload_size"
 
+/* KCM Service */
+#define CONFDB_KCM_CONF_ENTRY "config/kcm"
+#define CONFDB_KCM_SOCKET "socket_path"
 
 struct confdb_ctx;
 struct config_file_ctx;
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index c287328..5e789c5 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -9,6 +9,7 @@ section = ssh
 section = pac
 section = ifp
 section = secrets
+section = kcm
 section_re = ^secrets/users/[0-9]\+$
 section_re = ^domain/.*$
 
@@ -262,6 +263,24 @@ option = forward_headers
 option = username
 option = password
 
+# KCM responder
+[rule/allowed_kcm_options]
+validator = ini_allowed_options
+section_re = ^kcm$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_to_files
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = socket_path
+
 [rule/allowed_domain_options]
 validator = ini_allowed_options
 section_re = ^domain/.*$
diff --git a/src/external/libcurl.m4 b/src/external/libcurl.m4
index 3bc303c..b420b04 100644
--- a/src/external/libcurl.m4
+++ b/src/external/libcurl.m4
@@ -9,8 +9,8 @@ AS_IF([test x$enable_libcurl = xyes],
       [PKG_CHECK_MODULES([CURL],
                          [libcurl],
                          [found_libcurl=yes],
-                         [AC_MSG_WARN([
-The libcurl development library was not found. Some features will be disabled.])
+                         [AC_MSG_ERROR([
+The libcurl development library was not found.])
       ])])
 
 AS_IF([test x"$found_libcurl" = xyes],
@@ -32,7 +32,5 @@ AS_IF([test x"$found_libcurl" = xyes],
 AC_SUBST(CURL_LIBS)
 AC_SUBST(CURL_CFLAGS)
 
-AM_CONDITIONAL([BUILD_WITH_LIBCURL],
-               [test x"$have_curlopt_unix_sockpath" = xyes])
 AM_COND_IF([BUILD_WITH_LIBCURL],
            [AC_DEFINE_UNQUOTED(HAVE_LIBCURL, 1, [Build with libcurl support])])
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
new file mode 100644
index 0000000..90a6999
--- /dev/null
+++ b/src/responder/kcm/kcm.c
@@ -0,0 +1,254 @@
+/*
+   SSSD
+
+   KCM Server - the mainloop and server setup
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <popt.h>
+#include <krb5/krb5.h>
+
+#include "responder/kcm/kcm.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+#include "responder/common/responder.h"
+#include "util/util.h"
+
+#define DEFAULT_KCM_FD_LIMIT 2048
+
+#ifndef SSS_KCM_SOCKET_NAME
+#define SSS_KCM_SOCKET_NAME DEFAULT_KCM_SOCKET_PATH
+#endif
+
+static int kcm_responder_ctx_destructor(void *ptr)
+{
+    struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx);
+
+    /* mark that we are shutting down the responder, so it is propagated
+     * into underlying contexts that are freed right before rctx */
+    DEBUG(SSSDBG_TRACE_FUNC, "Responder is being shut down\n");
+    rctx->shutting_down = true;
+
+    return 0;
+}
+
+static int kcm_get_config(struct kcm_ctx *kctx)
+{
+    int ret;
+    char *sock_name;
+
+    ret = confdb_get_int(kctx->rctx->cdb,
+                         CONFDB_KCM_CONF_ENTRY,
+                         CONFDB_SERVICE_FD_LIMIT,
+                         DEFAULT_KCM_FD_LIMIT,
+                         &kctx->fd_limit);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to get file descriptors limit\n");
+        goto done;
+    }
+
+    ret = confdb_get_int(kctx->rctx->cdb,
+                         kctx->rctx->confdb_service_path,
+                         CONFDB_RESPONDER_CLI_IDLE_TIMEOUT,
+                         CONFDB_RESPONDER_CLI_IDLE_DEFAULT_TIMEOUT,
+                         &kctx->rctx->client_idle_timeout);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get the client idle timeout [%d]: %s\n",
+               ret, strerror(ret));
+        goto done;
+    }
+
+    /* Ensure that the client timeout is at least ten seconds */
+    if (kctx->rctx->client_idle_timeout < 10) {
+        kctx->rctx->client_idle_timeout = 10;
+    }
+
+    ret = confdb_get_string(kctx->rctx->cdb,
+                            kctx->rctx,
+                            kctx->rctx->confdb_service_path,
+                            CONFDB_KCM_SOCKET,
+                            SSS_KCM_SOCKET_NAME,
+                            &sock_name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get the client idle timeout [%d]: %s\n",
+               ret, strerror(ret));
+        goto done;
+    }
+    kctx->rctx->sock_name = sock_name;
+
+    ret = EOK;
+
+done:
+    return ret;
+}
+
+static int kcm_data_destructor(void *ptr)
+{
+    struct kcm_resp_ctx *kcm_data = talloc_get_type(ptr, struct kcm_resp_ctx);
+
+    if (kcm_data != NULL) {
+        krb5_free_context(kcm_data->k5c);
+    }
+    return 0;
+}
+
+static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx)
+{
+    struct kcm_resp_ctx *kcm_data;
+    krb5_error_code kret;
+
+    kcm_data = talloc_zero(mem_ctx, struct kcm_resp_ctx);
+    if (kcm_data == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing kcm data\n");
+        return NULL;
+    }
+
+    kret = krb5_init_context(&kcm_data->k5c);
+    if (kret != EOK) {
+        talloc_free(kcm_data);
+        return NULL;
+    }
+    talloc_set_destructor((TALLOC_CTX*)kcm_data, kcm_data_destructor);
+
+    return kcm_data;
+}
+
+static int kcm_process_init(TALLOC_CTX *mem_ctx,
+                            struct tevent_context *ev,
+                            struct confdb_ctx *cdb)
+{
+    struct resp_ctx *rctx;
+    struct kcm_ctx *kctx;
+    int ret;
+
+    rctx = talloc_zero(mem_ctx, struct resp_ctx);
+    if (rctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing resp_ctx\n");
+        return ENOMEM;
+    }
+    rctx->ev = ev;
+    rctx->cdb = cdb;
+    rctx->confdb_service_path = CONFDB_KCM_CONF_ENTRY;
+    rctx->shutting_down = false;
+    rctx->lfd = -1;
+    rctx->priv_lfd = -1;
+
+    talloc_set_destructor((TALLOC_CTX*)rctx, kcm_responder_ctx_destructor);
+
+    kctx = talloc_zero(rctx, struct kcm_ctx);
+    if (kctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing kcm_ctx\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    kctx->rctx = rctx;
+    kctx->rctx->pvt_ctx = kctx;
+
+    ret = kcm_get_config(kctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting KCM config\n");
+        goto fail;
+    }
+
+    kctx->kcm_data = kcm_data_setup(kctx);
+    if (kctx->kcm_data == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "fatal error initializing responder data\n");
+        ret = EIO;
+        goto fail;
+    }
+
+    /* Set up file descriptor limits */
+    responder_set_fd_limit(kctx->fd_limit);
+
+    ret = activate_unix_sockets(rctx, kcm_connection_setup);
+    if (ret != EOK) goto fail;
+
+    DEBUG(SSSDBG_TRACE_FUNC, "KCM Initialization complete\n");
+
+    return EOK;
+
+fail:
+    talloc_free(rctx);
+    return ret;
+}
+
+int main(int argc, const char *argv[])
+{
+    int opt;
+    poptContext pc;
+    struct main_context *main_ctx;
+    int ret;
+    uid_t uid;
+    gid_t gid;
+
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_MAIN_OPTS
+        SSSD_SERVER_OPTS(uid, gid)
+        POPT_TABLEEND
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    umask(DFL_RSP_UMASK);
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                  poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+
+    poptFreeContext(pc);
+
+    DEBUG_INIT(debug_level);
+
+    /* set up things like debug, signals, daemonization, etc... */
+    debug_log_file = "sssd_kcm";
+
+    ret = server_setup("sssd[kcm]", 0, uid, gid, CONFDB_KCM_CONF_ENTRY,
+                       &main_ctx);
+    if (ret != EOK) return 2;
+
+    ret = die_if_parent_died();
+    if (ret != EOK) {
+        /* This is not fatal, don't return */
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Could not set up to exit when parent process does\n");
+    }
+
+    ret = kcm_process_init(main_ctx,
+                           main_ctx->event_ctx,
+                           main_ctx->confdb_ctx);
+    if (ret != EOK) return 3;
+
+    /* loop on main */
+    server_loop(main_ctx);
+
+    return 0;
+}
diff --git a/src/responder/kcm/kcm.h b/src/responder/kcm/kcm.h
new file mode 100644
index 0000000..1ea7e9b
--- /dev/null
+++ b/src/responder/kcm/kcm.h
@@ -0,0 +1,97 @@
+/* -*- 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,             /* 0x3                 () -> (name)      */
+    KCM_OP_INITIALIZE,          /* 0x4      (name, princ) -> ()          */
+    KCM_OP_DESTROY,             /* 0x4             (name) -> ()          */
+    KCM_OP_STORE,               /* 0x6       (name, cred) -> ()          */
+    KCM_OP_RETRIEVE,
+    KCM_OP_GET_PRINCIPAL,       /* 0x8             (name) -> (princ)     */
+    KCM_OP_GET_CRED_UUID_LIST,  /* 0x9             (name) -> (uuid, ...) */
+    KCM_OP_GET_CRED_BY_UUID,    /* 0xa       (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, /* 0x12                () -> (uuid, ...) */
+    KCM_OP_GET_CACHE_BY_UUID,   /* 0x13            (uuid) -> (name)      */
+    KCM_OP_GET_DEFAULT_CACHE,   /* 0x14                () -> (name)      */
+    KCM_OP_SET_DEFAULT_CACHE,   /* 0x15            (name) -> ()          */
+    KCM_OP_GET_KDC_OFFSET,      /* 0x16            (name) -> (offset)    */
+    KCM_OP_SET_KDC_OFFSET,      /* 0x17    (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_OP_SENTINEL,            /* SSSD addition, not in the MIT header */
+} kcm_opcode;
+
+#endif /* KCM_H */
diff --git a/src/responder/kcm/kcmsrv_cmd.c b/src/responder/kcm/kcmsrv_cmd.c
new file mode 100644
index 0000000..e9a03cb
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_cmd.c
@@ -0,0 +1,65 @@
+/*
+   SSSD
+
+   KCM Server - the KCM server request and reply parsing and dispatching
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include "util/util.h"
+#include "responder/common/responder.h"
+
+struct kcm_proto_ctx {
+    void *unused;
+};
+
+static void kcm_fd_handler(struct tevent_context *ev,
+                           struct tevent_fd *fde,
+                           uint16_t flags, void *ptr)
+{
+    errno_t ret;
+    struct cli_ctx *cctx = talloc_get_type(ptr, struct cli_ctx);
+
+    /* Always reset the idle timer on any activity */
+    ret = reset_client_idle_timer(cctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Could not create idle timer for client. "
+               "This connection may not auto-terminate\n");
+        /* Non-fatal, continue */
+    }
+}
+
+int kcm_connection_setup(struct cli_ctx *cctx)
+{
+    struct kcm_proto_ctx *protocol_ctx;
+
+    protocol_ctx = talloc_zero(cctx, struct kcm_proto_ctx);
+    if (protocol_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    cctx->protocol_ctx = protocol_ctx;
+    cctx->cfd_handler = kcm_fd_handler;
+    return EOK;
+}
+
+/* Dummy, not used here but required to link to other responder files */
+struct cli_protocol_version *register_cli_protocol_version(void)
+{
+    return NULL;
+}
diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h
new file mode 100644
index 0000000..a7c9d06
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_pvt.h
@@ -0,0 +1,58 @@
+/*
+   SSSD
+
+   KCM Server - private header file
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __KCMSRV_PVT_H__
+#define __KCMSRV_PVT_H__
+
+#include "config.h"
+
+#include <sys/types.h>
+#include "responder/common/responder.h"
+
+/* KCM IO structure */
+struct kcm_data {
+    uint8_t *data;
+    size_t length;
+};
+
+/* To avoid leaking the sssd-specific responder data to other
+ * modules, the ccache databases and other KCM specific data
+ * are kept separately
+ */
+struct kcm_resp_ctx {
+    krb5_context k5c;
+};
+
+/* responder context that contains both the responder data,
+ * like the ccaches and the sssd-specific stuff like the
+ * generic responder ctx
+ */
+struct kcm_ctx {
+    struct resp_ctx *rctx;
+    int fd_limit;
+    char *socket_path;
+
+    struct kcm_resp_ctx *kcm_data;
+};
+
+int kcm_connection_setup(struct cli_ctx *cctx);
+
+#endif /* __KCMSRV_PVT_H__ */
diff --git a/src/sysv/systemd/sssd-kcm.service.in b/src/sysv/systemd/sssd-kcm.service.in
new file mode 100644
index 0000000..1e2bee1
--- /dev/null
+++ b/src/sysv/systemd/sssd-kcm.service.in
@@ -0,0 +1,9 @@
+[Unit]
+Description=SSSD Kerberos Cache Manager
+Documentation=man:sssd-kcm(5)
+
+[Install]
+Also=sssd-kcm.socket
+
+[Service]
+ExecStart=@libexecdir@/sssd/sssd_kcm --uid 0 --gid 0 --debug-to-files
diff --git a/src/sysv/systemd/sssd-kcm.socket.in b/src/sysv/systemd/sssd-kcm.socket.in
new file mode 100644
index 0000000..80ec1c0
--- /dev/null
+++ b/src/sysv/systemd/sssd-kcm.socket.in
@@ -0,0 +1,10 @@
+[Unit]
+Description=SSSD Secrets Service responder socket
+Documentation=man:sssd-kcm(8)
+Requires=sssd-secrets.socket
+
+[Socket]
+ListenStream=@localstatedir@/run/.heim_org.h5l.kcm-socket
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 17388c9..23cfdf9 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -40,6 +40,7 @@ struct err_string error_to_str[] = {
     { "Credentials are expired, old ccache was removed" }, /* ERR_CREDS_EXPIRED_CCACHE */
     { "Failure setting user credentials"}, /* ERR_CREDS_INVALID */
     { "No cached credentials available" }, /* ERR_NO_CACHED_CREDS */
+    { "No matching credentials found" }, /* ERR_NO_MATCHING_CREDS */
     { "Cached credentials are expired" }, /* ERR_CACHED_CREDS_EXPIRED */
     { "Authentication Denied" }, /* ERR_AUTH_DENIED */
     { "Authentication Failed" }, /* ERR_AUTH_FAILED */
@@ -104,6 +105,10 @@ struct err_string error_to_str[] = {
     { "The secret payload size is too large" }, /* ERR_SEC_PAYLOAD_SIZE_IS_TOO_LARGE */
     { "No authentication methode available" }, /* ERR_NO_AUTH_METHOD_AVAILABLE */
     { "Smartcard authentication not supported" }, /* ERR_SC_AUTH_NOT_SUPPORTED */
+    { "Malformed input KCM packet" }, /* ERR_KCM_MALFORMED_IN_PKT */
+    { "KCM operation not implemented" }, /* ERR_KCM_OP_NOT_IMPLEMENTED */
+    { "End of credential cache reached" }, /* ERR_KCM_CC_END */
+    { "Credential cache name not allowed" }, /* ERR_KCM_WRONG_CCNAME_FORMAT */
     { "ERR_LAST" } /* ERR_LAST */
 };
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 7aacad2..387d481 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -62,6 +62,7 @@ enum sssd_errors {
     ERR_CREDS_EXPIRED_CCACHE,
     ERR_CREDS_INVALID,
     ERR_NO_CACHED_CREDS,
+    ERR_NO_MATCHING_CREDS,
     ERR_CACHED_CREDS_EXPIRED,
     ERR_AUTH_DENIED,
     ERR_AUTH_FAILED,
@@ -126,6 +127,10 @@ enum sssd_errors {
     ERR_SEC_PAYLOAD_SIZE_IS_TOO_LARGE,
     ERR_NO_AUTH_METHOD_AVAILABLE,
     ERR_SC_AUTH_NOT_SUPPORTED,
+    ERR_KCM_MALFORMED_IN_PKT,
+    ERR_KCM_OP_NOT_IMPLEMENTED,
+    ERR_KCM_CC_END,
+    ERR_KCM_WRONG_CCNAME_FORMAT,
     ERR_LAST            /* ALWAYS LAST */
 };
 

From 02b7b862dc956ccdb79e94501eded04f0d8ce752 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 23 Sep 2016 14:00:10 +0200
Subject: [PATCH 08/21] KCM: request parsing and sending a reply

Implements parsing the KCM client request into per-client buffers and
sending a response for both the failure case and for success.

The protocol is documented at:
    http://k5wiki.kerberos.org/wiki/Projects/KCM_client

Several places don't use the sss_iobuf structure, because they don't
parse variable-length data from the buffer and it's much more efficient
to just allocate the needed request and reply structure on the stack.
---
 src/responder/kcm/kcmsrv_cmd.c | 465 ++++++++++++++++++++++++++++++++++++++++-
 src/responder/kcm/kcmsrv_pvt.h |  21 +-
 2 files changed, 473 insertions(+), 13 deletions(-)

diff --git a/src/responder/kcm/kcmsrv_cmd.c b/src/responder/kcm/kcmsrv_cmd.c
index e9a03cb..cbf7035 100644
--- a/src/responder/kcm/kcmsrv_cmd.c
+++ b/src/responder/kcm/kcmsrv_cmd.c
@@ -19,14 +19,430 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <krb5/krb5.h>
+
 #include "config.h"
 #include "util/util.h"
+#include "util/sss_iobuf.h"
 #include "responder/common/responder.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+#include "responder/kcm/kcm.h"
+
+/* The first four bytes of a message is always the size */
+#define KCM_MSG_LEN_SIZE 4
+
+/* The return code is 32bits */
+#define KCM_RETCODE_SIZE 4
+
+/* The maximum length of a request or reply as defined by the RPC
+ * protocol. This is the same constant size as MIT KRB5 uses
+ */
+#define KCM_PACKET_MAX_SIZE 2048
+
+/* KCM operation, its raw input and raw output and result */
+struct kcm_op_io {
+    struct kcm_op *op;
+    struct kcm_data request;
+    struct sss_iobuf *reply;
+};
+
+/**
+ * KCM IO-vector operations
+ */
+struct kcm_iovec {
+    /* We don't use iovec b/c void pointers don't allow for
+     * pointer arithmetics and it's convenient to keep track
+     * of processed bytes
+     */
+    uint8_t *kiov_base;
+    size_t kiov_len;
+    size_t nprocessed;
+};
+
+static errno_t kcm_iovec_op(int fd, struct kcm_iovec *kiov, bool do_read)
+{
+    ssize_t len;
+    struct iovec iov[1];
+
+    iov[0].iov_base = kiov->kiov_base + kiov->nprocessed;
+    iov[0].iov_len = kiov->kiov_len - kiov->nprocessed;
+    if (iov[0].iov_len == 0) {
+        /* This iovec is full (read) or depleted (write), proceed to the next one */
+        return EOK;
+    }
+
+    if (do_read) {
+        len = readv(fd, iov, 1);
+    } else {
+        len = writev(fd, iov, 1);
+    }
+
+    if (len == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+            return EAGAIN;
+        } else {
+            return errno;
+        }
+    }
+
+    if (len == 0) {
+        /* Read event on fd that doesn't yield data? error */
+        return ENODATA;
+    }
+
+    /* Decrease the amount of available free space in the iovec */
+    kiov->nprocessed += len;
+    return EOK;
+}
+
+static errno_t kcm_read_iovec(int fd, struct kcm_iovec *kiov)
+{
+    return kcm_iovec_op(fd, kiov, true);
+}
+
+static errno_t kcm_write_iovec(int fd, struct kcm_iovec *kiov)
+{
+    return kcm_iovec_op(fd, kiov, false);
+}
+
+/**
+ * Parsing KCM input
+ *
+ * The request is received as two IO vectors:
+ *
+ * first iovec:
+ *  length                      32-bit big-endian integer
+ *
+ * second iovec:
+ *  major protocol number       8-bit big-endian integer
+ *  minor protocol number       8-bit big-endian integer
+ *  opcode                      16-bit big-endian integer
+ *  message payload             buffer
+ */
+struct kcm_reqbuf {
+    uint8_t lenbuf[KCM_MSG_LEN_SIZE];
+    struct kcm_iovec v_len;
+
+    /* Includes the major, minor versions etc */
+    uint8_t msgbuf[KCM_PACKET_MAX_SIZE];
+    struct kcm_iovec v_msg;
+};
+
+static errno_t kcm_input_parse(struct kcm_reqbuf *reqbuf,
+                               struct kcm_op_io *op_io)
+{
+    size_t lc = 0;
+    size_t mc = 0;
+    uint16_t opcode = 0;
+    uint16_t opcode_be = 0;
+    uint32_t len_be = 0;
+    uint32_t msglen;
+    uint8_t proto_maj = 0;
+    uint8_t proto_min = 0;
+
+    /* The first 4 bytes before the payload is message length */
+    SAFEALIGN_COPY_UINT32_CHECK(&len_be,
+                                reqbuf->v_len.kiov_base,
+                                reqbuf->v_len.kiov_len,
+                                &lc);
+    msglen = be32toh(len_be);
+    DEBUG(SSSDBG_TRACE_LIBS,
+          "Received message with length %"PRIu32"\n", msglen);
+
+    if (msglen == 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Illegal zero-length message\n");
+        return EBADMSG;
+    }
+
+    if (msglen != reqbuf->v_msg.nprocessed) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Sender claims the message is %"PRIu32" bytes, "
+              "but received %zu\n",
+              msglen, reqbuf->v_msg.nprocessed);
+        return EBADMSG;
+    }
+
+    /* First 16 bits are 8 bit major and 8bit major protocol version */
+    SAFEALIGN_COPY_UINT8_CHECK(&proto_maj,
+                               reqbuf->v_msg.kiov_base + mc,
+                               reqbuf->v_msg.kiov_len,
+                               &mc);
+    SAFEALIGN_COPY_UINT8_CHECK(&proto_min,
+                               reqbuf->v_msg.kiov_base + mc,
+                               reqbuf->v_msg.kiov_len,
+                               &mc);
+
+    if (proto_maj != KCM_PROTOCOL_VERSION_MAJOR) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Expected major version %d, got %"PRIu16"\n",
+              KCM_PROTOCOL_VERSION_MAJOR, (uint16_t) proto_maj);
+        return ERR_KCM_MALFORMED_IN_PKT;
+    }
+
+    if (proto_min != KCM_PROTOCOL_VERSION_MINOR) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Expected minor version %d, got %"PRIu16"\n",
+              KCM_PROTOCOL_VERSION_MINOR, (uint16_t) proto_maj);
+        return ERR_KCM_MALFORMED_IN_PKT;
+    }
+
+    SAFEALIGN_COPY_UINT16_CHECK(&opcode_be,
+                                reqbuf->v_msg.kiov_base + mc,
+                                reqbuf->v_msg.kiov_len,
+                                &mc);
+
+    opcode = be16toh(opcode_be);
+    DEBUG(SSSDBG_TRACE_LIBS, "Received operation code %"PRIu16"\n", opcode);
+
+    return EOK;
+}
+
+/**
+ * Constructing a reply for failure and success
+ *
+ * The reply consists of three IO vectors:
+ * 1) length iovec:
+ *  length:     32-bit big-endian
+ *
+ * 2) return code iovec:
+ *  retcode:    32-bit big-endian. Non-zero on failure in the KCM server,
+ *              zero if the KCM operation ran (even if the operation itself
+ *              failed)
+ *
+ * 3) reply iovec
+ *  message:    buffer, first 32-bits of the buffer is the return code of
+ *              the KCM operation, the rest depends on the operation itself.
+ *              The buffer's length is specified by the first integer in the
+ *              reply (very intuitive, right?)
+ *
+ *  The client always reads the length and return code iovectors. However, the
+ *  client reads the reply iovec only if retcode is 0 in the return code iovector
+ *  (see kcmio_unix_socket_read() in the MIT tree)
+ */
+struct kcm_repbuf {
+    uint8_t lenbuf[KCM_MSG_LEN_SIZE];
+    struct kcm_iovec v_len;
+
+    uint8_t rcbuf[KCM_RETCODE_SIZE];
+    struct kcm_iovec v_rc;
+
+    uint8_t msgbuf[KCM_PACKET_MAX_SIZE];
+    struct kcm_iovec v_msg;
+};
+
+static errno_t kcm_failbuf_construct(errno_t ret,
+                                     struct kcm_repbuf *repbuf)
+{
+    size_t c;
+
+    c = 0;
+    SAFEALIGN_SETMEM_UINT32(repbuf->lenbuf, 0, &c);
+    c = 0;
+    SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, htobe32(ret), &c);
+
+    return EOK;
+}
+
+/**
+ * Construct a reply buffer and send it to the KCM client
+ */
+static void kcm_reply_error(struct cli_ctx *cctx,
+                            errno_t retcode,
+                            struct kcm_repbuf *repbuf)
+{
+    errno_t ret;
+    krb5_error_code kerr;
+
+    DEBUG(SSSDBG_OP_FAILURE,
+          "KCM operation returs failure [%d]: %s\n",
+          retcode, sss_strerror(retcode));
+    kerr = sss2krb5_error(retcode);
+
+    ret = kcm_failbuf_construct(kerr, repbuf);
+    if (ret != EOK) {
+        /* If we can't construct the reply buffer, just terminate the client */
+        talloc_free(cctx);
+        return;
+    }
+
+    TEVENT_FD_WRITEABLE(cctx->cfde);
+}
+
+/**
+ * Request-reply dispatcher
+ */
+struct kcm_req_ctx {
+    /* client context owns per-client buffers including this one */
+    struct cli_ctx *cctx;
+
+    /* raw IO buffers */
+    struct kcm_reqbuf reqbuf;
+    struct kcm_repbuf repbuf;
 
-struct kcm_proto_ctx {
-    void *unused;
+    /* long-lived responder structures */
+    struct kcm_ctx *kctx;
+
+    struct kcm_op_io op_io;
 };
 
+static errno_t kcm_recv_data(int fd, struct kcm_reqbuf *reqbuf)
+{
+    errno_t ret;
+
+    ret = kcm_read_iovec(fd, &reqbuf->v_len);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    ret = kcm_read_iovec(fd, &reqbuf->v_msg);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    return EOK;
+}
+
+static struct kcm_req_ctx *kcm_new_req(TALLOC_CTX *mem_ctx,
+                                       struct cli_ctx *cctx,
+                                       struct kcm_ctx *kctx)
+{
+    struct kcm_req_ctx *req;
+
+    req = talloc_zero(cctx, struct kcm_req_ctx);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    req->reqbuf.v_len.kiov_base = req->reqbuf.lenbuf;
+    req->reqbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE;
+
+    req->reqbuf.v_msg.kiov_base = req->reqbuf.msgbuf;
+    req->reqbuf.v_msg.kiov_len = KCM_PACKET_MAX_SIZE;
+
+    req->repbuf.v_len.kiov_base = req->repbuf.lenbuf;
+    req->repbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE;
+
+    req->repbuf.v_rc.kiov_base = req->repbuf.rcbuf;
+    req->repbuf.v_rc.kiov_len = KCM_RETCODE_SIZE;
+
+    req->repbuf.v_msg.kiov_base = req->repbuf.msgbuf;
+    /* Length of the msg iobuf will be adjusted later, so far use the full
+     * length so that constructing the reply can use that capacity
+     */
+    req->repbuf.v_msg.kiov_len = KCM_PACKET_MAX_SIZE;
+
+    req->cctx = cctx;
+    req->kctx = kctx;
+
+    return req;
+}
+
+static void kcm_recv(struct cli_ctx *cctx)
+{
+    struct kcm_req_ctx *req;
+    struct kcm_ctx *kctx;
+    int ret;
+
+    kctx = talloc_get_type(cctx->rctx->pvt_ctx, struct kcm_ctx);
+    req = talloc_get_type(cctx->state_ctx, struct kcm_req_ctx);
+    if (req == NULL) {
+        /* A new request comes in, setup data structures */
+        req = kcm_new_req(cctx, cctx, kctx);
+        if (req == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot set up client connection\n");
+            talloc_free(cctx);
+            return;
+        }
+
+        cctx->state_ctx = req;
+    }
+
+    ret = kcm_recv_data(cctx->cfd, &req->reqbuf);
+    switch (ret) {
+    case ENODATA:
+        DEBUG(SSSDBG_TRACE_ALL, "Client closed connection.\n");
+        talloc_free(cctx);
+        return;
+    case EAGAIN:
+        DEBUG(SSSDBG_TRACE_ALL, "Retry later\n");
+        return;
+    case EOK:
+        /* all fine */
+        break;
+    default:
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to receive data (%d, %s), aborting client\n",
+              ret, sss_strerror(ret));
+        talloc_free(cctx);
+        return;
+    }
+
+    ret = kcm_input_parse(&req->reqbuf, &req->op_io);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+            "Failed to parse data (%d, %s), aborting client\n",
+            ret, sss_strerror(ret));
+        goto fail;
+    }
+
+    /* do not read anymore, client is done sending */
+    TEVENT_FD_NOT_READABLE(cctx->cfde);
+
+    kcm_reply_error(cctx, ret, &req->repbuf);
+    return;
+
+fail:
+    /* Fail with reply */
+    kcm_reply_error(cctx, ret, &req->repbuf);
+}
+
+static int kcm_send_data(struct cli_ctx *cctx)
+{
+    struct kcm_req_ctx *req;
+    errno_t ret;
+
+    req = talloc_get_type(cctx->state_ctx, struct kcm_req_ctx);
+
+    ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_len);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_rc);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_msg);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    return EOK;
+}
+
+static void kcm_send(struct cli_ctx *cctx)
+{
+    errno_t ret;
+
+    ret = kcm_send_data(cctx);
+    if (ret == EAGAIN) {
+        /* not all data was sent, loop again */
+        return;
+    } else if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n");
+        talloc_free(cctx);
+        return;
+    }
+
+    /* ok all sent */
+    TEVENT_FD_NOT_WRITEABLE(cctx->cfde);
+    TEVENT_FD_READABLE(cctx->cfde);
+    talloc_zfree(cctx->state_ctx);
+    return;
+}
+
 static void kcm_fd_handler(struct tevent_context *ev,
                            struct tevent_fd *fde,
                            uint16_t flags, void *ptr)
@@ -39,23 +455,52 @@ static void kcm_fd_handler(struct tevent_context *ev,
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE,
               "Could not create idle timer for client. "
-               "This connection may not auto-terminate\n");
+              "This connection may not auto-terminate\n");
         /* Non-fatal, continue */
     }
+
+    if (flags & TEVENT_FD_READ) {
+        kcm_recv(cctx);
+        return;
+    }
+    if (flags & TEVENT_FD_WRITE) {
+        kcm_send(cctx);
+        return;
+    }
 }
 
 int kcm_connection_setup(struct cli_ctx *cctx)
 {
-    struct kcm_proto_ctx *protocol_ctx;
+    cctx->cfd_handler = kcm_fd_handler;
+    return EOK;
+}
 
-    protocol_ctx = talloc_zero(cctx, struct kcm_proto_ctx);
-    if (protocol_ctx == NULL) {
-        return ENOMEM;
+krb5_error_code sss2krb5_error(errno_t err)
+{
+    switch (err) {
+    case EOK:
+        return 0;
+    case ENOMEM:
+        return KRB5_CC_NOMEM;
+    case EACCES:
+        return KRB5_FCC_PERM;
+    case ERR_KCM_OP_NOT_IMPLEMENTED:
+        return KRB5_CC_NOSUPP;
+    case ERR_WRONG_NAME_FORMAT:
+        return KRB5_CC_BADNAME;
+    case ERR_NO_MATCHING_CREDS:
+        return KRB5_FCC_NOFILE;
+    case ERR_NO_CREDS:
+        return KRB5_CC_NOTFOUND;
+    case ERR_KCM_CC_END:
+        return KRB5_CC_END;
+    case ERR_KCM_MALFORMED_IN_PKT:
+    case EINVAL:
+    case EIO:
+        return KRB5_CC_IO;
     }
 
-    cctx->protocol_ctx = protocol_ctx;
-    cctx->cfd_handler = kcm_fd_handler;
-    return EOK;
+    return KRB5_FCC_INTERNAL;
 }
 
 /* Dummy, not used here but required to link to other responder files */
diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h
index a7c9d06..fd1fd9f 100644
--- a/src/responder/kcm/kcmsrv_pvt.h
+++ b/src/responder/kcm/kcmsrv_pvt.h
@@ -27,13 +27,20 @@
 #include <sys/types.h>
 #include "responder/common/responder.h"
 
-/* KCM IO structure */
+/*
+ * KCM IO structure
+ *
+ * In theory we cold use sss_iobuf there, but since iobuf was
+ * made opaque, this allows it to allocate the structures on
+ * the stack in one go.
+ * */
 struct kcm_data {
     uint8_t *data;
     size_t length;
 };
 
-/* To avoid leaking the sssd-specific responder data to other
+/*
+ * To avoid leaking the sssd-specific responder data to other
  * modules, the ccache databases and other KCM specific data
  * are kept separately
  */
@@ -41,7 +48,8 @@ struct kcm_resp_ctx {
     krb5_context k5c;
 };
 
-/* responder context that contains both the responder data,
+/*
+ * responder context that contains both the responder data,
  * like the ccaches and the sssd-specific stuff like the
  * generic responder ctx
  */
@@ -55,4 +63,11 @@ struct kcm_ctx {
 
 int kcm_connection_setup(struct cli_ctx *cctx);
 
+/*
+ * Internally in SSSD-KCM we use SSSD-internal error codes so that we
+ * can always the same sss_strerror() functions to format the errors
+ * nicely, but the client expects libkrb5 error codes.
+ */
+krb5_error_code sss2krb5_error(errno_t err);
+
 #endif /* __KCMSRV_PVT_H__ */

From a5a434ee9921c0880c1339c39381859c4ae458ae Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 7 Mar 2017 13:49:21 +0100
Subject: [PATCH 09/21] KCM: Implement an internal ccache storage and retrieval
 API

In order for the KCM server to work with ccaches stored in different
locations, implement a middle-man between the KCM server and the ccache
storage.

This module has asynchronous API because we can't assume anything about
where the ccaches are stored.
---
 Makefile.am                           |    9 +
 configure.ac                          |    1 +
 contrib/sssd.spec.in                  |    1 +
 src/external/libuuid.m4               |   17 +
 src/responder/kcm/kcmsrv_ccache.c     | 1423 +++++++++++++++++++++++++++++++++
 src/responder/kcm/kcmsrv_ccache.h     |  306 +++++++
 src/responder/kcm/kcmsrv_ccache_be.h  |  204 +++++
 src/responder/kcm/kcmsrv_ccache_pvt.h |   62 ++
 src/responder/kcm/kcmsrv_pvt.h        |    1 +
 9 files changed, 2024 insertions(+)
 create mode 100644 src/external/libuuid.m4
 create mode 100644 src/responder/kcm/kcmsrv_ccache.c
 create mode 100644 src/responder/kcm/kcmsrv_ccache.h
 create mode 100644 src/responder/kcm/kcmsrv_ccache_be.h
 create mode 100644 src/responder/kcm/kcmsrv_ccache_pvt.h

diff --git a/Makefile.am b/Makefile.am
index 6fdbcfb..5463f2a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -706,6 +706,9 @@ dist_noinst_HEADERS = \
     src/responder/secrets/secsrv_proxy.h \
     src/responder/kcm/kcm.h \
     src/responder/kcm/kcmsrv_pvt.h \
+    src/responder/kcm/kcmsrv_ccache.h \
+    src/responder/kcm/kcmsrv_ccache_pvt.h \
+    src/responder/kcm/kcmsrv_ccache_be.h \
     src/sbus/sbus_client.h \
     src/sbus/sssd_dbus.h \
     src/sbus/sssd_dbus_meta.h \
@@ -1478,16 +1481,22 @@ if BUILD_KCM
 sssd_kcm_SOURCES = \
     src/responder/kcm/kcm.c \
     src/responder/kcm/kcmsrv_cmd.c \
+    src/responder/kcm/kcmsrv_ccache.c \
     src/util/sss_sockets.c \
+    src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
     $(SSSD_RESPONDER_OBJ) \
     $(NULL)
 sssd_kcm_CFLAGS = \
     $(AM_CFLAGS) \
     $(KRB5_CFLAGS) \
+    $(UUID_CFLAGS) \
     $(NULL)
 sssd_kcm_LDADD = \
     $(KRB5_LIBS) \
     $(SSSD_LIBS) \
+    $(UUID_LIBS) \
+    $(SYSTEMD_DAEMON_LIBS) \
     $(SSSD_INTERNAL_LTLIBS) \
     $(NULL)
 endif
diff --git a/configure.ac b/configure.ac
index 42f2796..424e212 100644
--- a/configure.ac
+++ b/configure.ac
@@ -202,6 +202,7 @@ fi
 
 if test x$with_kcm = xyes; then
     m4_include([src/external/libcurl.m4])
+    m4_include([src/external/libuuid.m4])
 fi
 # This variable is defined by external/libcurl.m4, but conditionals
 # must be always evaluated
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index bf1c69d..c63e45c 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -222,6 +222,7 @@ BuildRequires: systemtap-sdt-devel
 %endif
 BuildRequires: http-parser-devel
 BuildRequires: jansson-devel
+BuildRequires: libuuid-devel
 
 %description
 Provides a set of daemons to manage access to remote directories and
diff --git a/src/external/libuuid.m4 b/src/external/libuuid.m4
new file mode 100644
index 0000000..55411a2
--- /dev/null
+++ b/src/external/libuuid.m4
@@ -0,0 +1,17 @@
+AC_SUBST(UUID_LIBS)
+AC_SUBST(UUID_CFLAGS)
+
+PKG_CHECK_MODULES([UUID], [uuid], [found_uuid=yes], [found_uuid=no])
+
+SSS_AC_EXPAND_LIB_DIR()
+AS_IF([test x"$found_uuid" = xyes],
+    [AC_CHECK_HEADERS([uuid/uuid.h],
+        [AC_CHECK_LIB([uuid],
+                      [uuid_generate],
+                      [UUID_LIBS="-L$sss_extra_libdir -luuid"],
+                      [AC_MSG_ERROR([libuuid missing uuid_generate])],
+                      [-L$sss_extra_libdir -luuid])],
+        [AC_MSG_ERROR([
+You must have the header file uuid.h installed to build sssd
+with KCM responder. If you want to build sssd without KCM responder
+then specify --without-kcm when running configure.])])])
diff --git a/src/responder/kcm/kcmsrv_ccache.c b/src/responder/kcm/kcmsrv_ccache.c
new file mode 100644
index 0000000..2c565b8
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ccache.c
@@ -0,0 +1,1423 @@
+/*
+   SSSD
+
+   KCM Server - the KCM ccache operations
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include "util/crypto/sss_crypto.h"
+#include "util/util.h"
+#include "util/sss_krb5.h"
+#include "responder/kcm/kcmsrv_ccache.h"
+#include "responder/kcm/kcmsrv_ccache_pvt.h"
+#include "responder/kcm/kcmsrv_ccache_be.h"
+
+static int kcm_cc_destructor(struct kcm_ccache *cc)
+{
+    if (cc == NULL) {
+        return 0;
+    }
+
+    krb5_free_principal(NULL, cc->client);
+    return 0;
+}
+
+errno_t kcm_cc_new(TALLOC_CTX *mem_ctx,
+                   krb5_context k5c,
+                   struct cli_creds *owner,
+                   const char *name,
+                   krb5_principal princ,
+                   struct kcm_ccache **_cc)
+{
+    struct kcm_ccache *cc;
+    krb5_error_code kret;
+    errno_t ret;
+
+    cc = talloc_zero(mem_ctx, struct kcm_ccache);
+    if (cc == NULL) {
+        return ENOMEM;
+    }
+
+    ret = kcm_check_name(name, owner);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Name %s is malformed\n", name);
+        return ret;
+    }
+
+    cc->name = talloc_strdup(cc, name);
+    if (cc->name == NULL) {
+        talloc_free(cc);
+        return ENOMEM;
+    }
+
+    uuid_generate(cc->uuid);
+
+    kret = krb5_copy_principal(k5c, princ, &cc->client);
+    if (kret != 0) {
+        const char *err_msg = sss_krb5_get_error_message(k5c, kret);
+        DEBUG(SSSDBG_OP_FAILURE,
+              "krb5_copy_principal failed: [%d][%s]\n", kret, err_msg);
+        sss_krb5_free_error_message(k5c, err_msg);
+        talloc_free(cc);
+        return ERR_INTERNAL;
+    }
+
+    cc->owner.uid = cli_creds_get_uid(owner);
+    cc->owner.gid = cli_creds_get_gid(owner);
+    cc->kdc_offset = INT32_MAX;
+
+    talloc_set_destructor(cc, kcm_cc_destructor);
+    *_cc = cc;
+    return EOK;
+}
+
+const char *kcm_cc_get_name(struct kcm_ccache *cc)
+{
+    return cc ? cc->name : NULL;
+}
+
+errno_t kcm_cc_get_uuid(struct kcm_ccache *cc, uuid_t _uuid)
+{
+    if (cc == NULL) {
+        return EINVAL;
+    }
+    uuid_copy(_uuid, cc->uuid);
+    return EOK;
+}
+
+krb5_principal kcm_cc_get_client_principal(struct kcm_ccache *cc)
+{
+    return cc ? cc->client : NULL;
+}
+
+bool kcm_cc_access(struct kcm_ccache *cc,
+                   struct cli_creds *client)
+{
+    bool ok;
+    uid_t uid = cli_creds_get_uid(client);
+    gid_t gid = cli_creds_get_gid(client);
+
+    if (cc == NULL) {
+        return false;
+    }
+
+    if (uid == 0 && gid == 0) {
+        /* root can access any ccache */
+        return true;
+    }
+
+    ok = ((cc->owner.uid == uid) && (cc->owner.gid == gid));
+    if (!ok) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Client %"SPRIuid":%"SPRIgid" has no access to ccache %s\n",
+              cli_creds_get_uid(client),
+              cli_creds_get_gid(client),
+              cc->name);
+    }
+    return ok;
+}
+
+int32_t kcm_cc_get_offset(struct kcm_ccache *cc)
+{
+    return cc ? cc->kdc_offset : INT32_MAX;
+}
+
+errno_t kcm_cc_store_cred_blob(struct kcm_ccache *cc,
+                               struct sss_iobuf *cred_blob)
+{
+    struct kcm_cred *kcreds;
+    uuid_t uuid;
+    errno_t ret;
+
+    if (cc == NULL || cred_blob == NULL) {
+        return EINVAL;
+    }
+
+    uuid_generate(uuid);
+    kcreds = kcm_cred_new(cc, uuid, cred_blob);
+    if (kcreds == NULL) {
+        return ENOMEM;
+    }
+
+    ret = kcm_cc_store_creds(cc, kcreds);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    return EOK;
+}
+
+struct kcm_cred *kcm_cc_get_cred(struct kcm_ccache *cc)
+{
+    if (cc == NULL) {
+        return NULL;
+    }
+
+    return cc->creds;
+}
+
+struct kcm_cred *kcm_cc_next_cred(struct kcm_cred *crd)
+{
+    if (crd == NULL) {
+        return NULL;
+    }
+
+    return crd->next;
+}
+
+struct kcm_cred *kcm_cred_new(TALLOC_CTX *mem_ctx,
+                              uuid_t uuid,
+                              struct sss_iobuf *cred_blob)
+{
+    struct kcm_cred *kcreds;
+
+    kcreds = talloc_zero(mem_ctx, struct kcm_cred);
+    if (kcreds == NULL) {
+        return NULL;
+    }
+
+    uuid_copy(kcreds->uuid, uuid);
+    kcreds->cred_blob = talloc_steal(kcreds, cred_blob);
+    return kcreds;
+}
+
+/* Add a cred to ccache */
+errno_t kcm_cc_store_creds(struct kcm_ccache *cc,
+                           struct kcm_cred *crd)
+{
+    DLIST_ADD(cc->creds, crd);
+    talloc_steal(cc, crd);
+    return EOK;
+}
+
+errno_t kcm_cred_get_uuid(struct kcm_cred *crd, uuid_t _uuid)
+{
+    if (crd == NULL) {
+        return EINVAL;
+    }
+    uuid_copy(_uuid, crd->uuid);
+    return EOK;
+}
+
+struct sss_iobuf *kcm_cred_get_creds(struct kcm_cred *crd)
+{
+    return crd ? crd->cred_blob : NULL;
+}
+
+struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               enum kcm_ccdb_be cc_be)
+{
+    errno_t ret;
+    struct kcm_ccdb *ccdb = NULL;
+
+    if (ev == NULL) {
+        return NULL;
+    }
+
+    ccdb = talloc_zero(mem_ctx, struct kcm_ccdb);
+    if (ccdb == NULL) {
+        return NULL;
+    }
+    ccdb->ev = ev;
+
+    switch (cc_be) {
+    case CCDB_BE_MEMORY:
+        DEBUG(SSSDBG_FUNC_DATA, "KCM back end: memory\n");
+        /* Not implemented yet */
+        break;
+    case CCDB_BE_SECRETS:
+        DEBUG(SSSDBG_FUNC_DATA, "KCM back end: sssd-secrets\n");
+        /* Not implemented yet */
+        break;
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unknown ccache database\n");
+        break;
+    }
+
+    if (ccdb->ops == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Ccache database not initialized\n");
+        talloc_free(ccdb);
+        return NULL;
+    }
+
+    ret = ccdb->ops->init(ccdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot initialize ccache database\n");
+        talloc_free(ccdb);
+        return NULL;
+    }
+
+    return ccdb;
+}
+
+struct kcm_ccdb_nextid_state {
+    char *next_cc;
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+};
+
+static void kcm_ccdb_nextid_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_nextid_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct kcm_ccdb *db,
+                                        struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_nextid_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_nextid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->client = client;
+
+    if (ev == NULL || db == NULL || client == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = state->db->ops->nextid_send(mem_ctx, ev, state->db, client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_nextid_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_nextid_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_nextid_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_nextid_state);
+    errno_t ret;
+    unsigned int nextid;
+
+    ret = state->db->ops->nextid_recv(subreq, &nextid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to generate next UID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->next_cc = talloc_asprintf(state, "%"SPRIuid":%u",
+                                     cli_creds_get_uid(state->client),
+                                     nextid);
+    if (state->next_cc == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed\n");
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS, "generated %s\n", state->next_cc);
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_nextid_recv(struct tevent_req *req,
+                             TALLOC_CTX *mem_ctx,
+                             char **_next_cc)
+{
+    struct kcm_ccdb_nextid_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_nextid_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_next_cc = talloc_steal(mem_ctx, state->next_cc);
+    return EOK;
+}
+
+struct kcm_ccdb_list_state {
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+
+    uuid_t *uuid_list;
+};
+
+static void kcm_ccdb_list_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_list_send(TALLOC_CTX *mem_ctx,
+                                      struct tevent_context *ev,
+                                      struct kcm_ccdb *db,
+                                      struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_list_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_list_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->client = client;
+
+    if (ev == NULL || db == NULL || client == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = state->db->ops->list_send(mem_ctx,
+                                       ev,
+                                       state->db,
+                                       client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_list_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_list_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_list_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_list_state);
+    errno_t ret;
+
+    ret = state->db->ops->list_recv(subreq, state, &state->uuid_list);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to list all ccaches [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_list_recv(struct tevent_req *req,
+                           TALLOC_CTX *mem_ctx,
+                           uuid_t **_uuid_list)
+{
+    struct kcm_ccdb_list_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_list_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_uuid_list = talloc_steal(mem_ctx, state->uuid_list);
+    return EOK;
+}
+
+struct kcm_ccdb_get_default_state {
+    struct kcm_ccdb *db;
+    uuid_t uuid;
+};
+
+static void kcm_ccdb_get_default_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_get_default_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct kcm_ccdb *db,
+                                             struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_get_default_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_get_default_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+
+    if (ev == NULL || db == NULL || client == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = db->ops->get_default_send(mem_ctx, ev, db, client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_get_default_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_get_default_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_get_default_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_get_default_state);
+    errno_t ret;
+
+    ret = state->db->ops->get_default_recv(subreq, state->uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to get the default ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_get_default_recv(struct tevent_req *req,
+                                  uuid_t *uuid)
+{
+    struct kcm_ccdb_get_default_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_get_default_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (uuid != NULL) {
+        /* The caller might supply a NULL dfl to just check if there is
+         * some default ccache
+         */
+        uuid_copy(*uuid, state->uuid);
+    }
+
+    return EOK;
+}
+
+struct kcm_ccdb_set_default_state {
+    struct tevent_context *ev;
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+    uuid_t uuid;
+};
+
+static void kcm_ccdb_set_default_uuid_resolved(struct tevent_req *subreq);
+static void kcm_ccdb_set_default_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_set_default_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct kcm_ccdb *db,
+                                             struct cli_creds *client,
+                                             uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_set_default_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_set_default_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->ev = ev;
+    state->client = client;
+    uuid_copy(state->uuid, uuid);
+
+    if (ev == NULL || db == NULL || client == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    if (uuid_is_null(uuid)) {
+        /* NULL UUID means to just reset the default to 'no default' */
+        subreq = state->db->ops->set_default_send(state,
+                                                state->ev,
+                                                state->db,
+                                                state->client,
+                                                state->uuid);
+        if (subreq == NULL) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+        tevent_req_set_callback(subreq, kcm_ccdb_set_default_done, req);
+    } else {
+        /* Otherwise we need to check if the client can access the UUID
+         * about to be set as default
+         */
+        subreq = db->ops->getbyuuid_send(state, ev, db, client, uuid);
+        if (subreq == NULL) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+        tevent_req_set_callback(subreq, kcm_ccdb_set_default_uuid_resolved, req);
+    }
+
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_set_default_uuid_resolved(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_set_default_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_set_default_state);
+    errno_t ret;
+    bool ok;
+    struct kcm_ccache *cc;
+
+    ret = state->db->ops->getbyuuid_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to get cache by UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (cc == NULL) {
+        DEBUG(SSSDBG_TRACE_LIBS, "No cache found by UUID\n");
+        tevent_req_error(req, ERR_KCM_CC_END);
+        return;
+    }
+
+    ok = kcm_cc_access(cc, state->client);
+    if (!ok) {
+        tevent_req_error(req, EACCES);
+        return;
+    }
+
+    subreq = state->db->ops->set_default_send(state,
+                                              state->ev,
+                                              state->db,
+                                              state->client,
+                                              state->uuid);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_set_default_done, req);
+}
+
+static void kcm_ccdb_set_default_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_set_default_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_set_default_state);
+    errno_t ret;
+
+    ret = state->db->ops->set_default_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to set the default ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_set_default_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+struct kcm_ccdb_getbyname_state {
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+
+    struct kcm_ccache *cc;
+};
+
+static void kcm_ccdb_getbyname_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_getbyname_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           const char *name)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_getbyname_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_getbyname_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->client = client;
+
+    if (ev == NULL || db == NULL || client == NULL || name == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = db->ops->getbyname_send(state, ev, db, client, name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_getbyname_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_getbyname_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_getbyname_state);
+    errno_t ret;
+    bool ok;
+
+    ret = state->db->ops->getbyname_recv(subreq, state, &state->cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to get cache by name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->cc == NULL) {
+        DEBUG(SSSDBG_TRACE_LIBS, "No cache found by name\n");
+        tevent_req_done(req);
+        return;
+    }
+
+    ok = kcm_cc_access(state->cc, state->client);
+    if (!ok) {
+        tevent_req_error(req, EACCES);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_getbyname_recv(struct tevent_req *req,
+                                TALLOC_CTX *mem_ctx,
+                                struct kcm_ccache **_cc)
+{
+    struct kcm_ccdb_getbyname_state *state = tevent_req_data(req,
+                                            struct kcm_ccdb_getbyname_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+struct kcm_ccdb_getbyuuid_state {
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+
+    struct kcm_ccache *cc;
+};
+
+static void kcm_ccdb_getbyuuid_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_getbyuuid_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_getbyuuid_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_getbyuuid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->client = client;
+
+    if (ev == NULL || db == NULL || client == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = db->ops->getbyuuid_send(state, ev, db, client, uuid);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_getbyuuid_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_getbyuuid_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_getbyuuid_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_getbyuuid_state);
+    errno_t ret;
+    bool ok;
+
+    ret = state->db->ops->getbyuuid_recv(subreq, state, &state->cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to get cache by UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->cc == NULL) {
+        DEBUG(SSSDBG_TRACE_LIBS, "No cache found by UUID\n");
+        tevent_req_done(req);
+        return;
+    }
+
+    ok = kcm_cc_access(state->cc, state->client);
+    if (!ok) {
+        tevent_req_error(req, EACCES);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_getbyuuid_recv(struct tevent_req *req,
+                                TALLOC_CTX *mem_ctx,
+                                struct kcm_ccache **_cc)
+{
+    struct kcm_ccdb_getbyuuid_state *state = tevent_req_data(req,
+                                            struct kcm_ccdb_getbyuuid_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+struct kcm_ccdb_name_by_uuid_state {
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+
+    const char *name;
+};
+
+static void kcm_ccdb_name_by_uuid_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_name_by_uuid_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_name_by_uuid_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx,
+                            &state,
+                            struct kcm_ccdb_name_by_uuid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->client = client;
+
+    if (ev == NULL || db == NULL || client == NULL || uuid_is_null(uuid)) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = db->ops->name_by_uuid_send(state, ev, db, client, uuid);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_name_by_uuid_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_name_by_uuid_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_name_by_uuid_state *state = tevent_req_data(req,
+                                        struct kcm_ccdb_name_by_uuid_state);
+    errno_t ret;
+
+    ret = state->db->ops->name_by_uuid_recv(subreq, state, &state->name);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to resolve cache by UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_name_by_uuid_recv(struct tevent_req *req,
+                                   TALLOC_CTX *mem_ctx,
+                                   const char **_name)
+{
+    struct kcm_ccdb_name_by_uuid_state *state = tevent_req_data(req,
+                                        struct kcm_ccdb_name_by_uuid_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_name = talloc_steal(mem_ctx, state->name);
+    return EOK;
+}
+
+struct kcm_ccdb_uuid_by_name_state {
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+
+    uuid_t uuid;
+};
+
+static void kcm_ccdb_uuid_by_name_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_uuid_by_name_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              const char *name)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_uuid_by_name_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx,
+                            &state,
+                            struct kcm_ccdb_uuid_by_name_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->client = client;
+
+    if (ev == NULL || db == NULL || client == NULL || name == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = db->ops->uuid_by_name_send(state, ev, db, client, name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_uuid_by_name_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_uuid_by_name_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_uuid_by_name_state *state = tevent_req_data(req,
+                                        struct kcm_ccdb_uuid_by_name_state);
+    errno_t ret;
+
+    ret = state->db->ops->uuid_by_name_recv(subreq, state, state->uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to resolve cache by UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_uuid_by_name_recv(struct tevent_req *req,
+                                   TALLOC_CTX *mem_ctx,
+                                   uuid_t _uuid)
+{
+    struct kcm_ccdb_uuid_by_name_state *state = tevent_req_data(req,
+                                        struct kcm_ccdb_uuid_by_name_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    uuid_copy(_uuid, state->uuid);
+    return EOK;
+}
+
+struct kcm_ccdb_create_cc_state {
+    struct kcm_ccdb *db;
+};
+
+static void kcm_ccdb_create_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_create_cc_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           struct kcm_ccache *cc)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_create_cc_state *state = NULL;
+    errno_t ret;
+    bool ok;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_create_cc_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+
+    if (ev == NULL || db == NULL || client == NULL || cc == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    ok = kcm_cc_access(cc, client);
+    if (!ok) {
+        ret = EACCES;
+        goto immediate;
+    }
+
+    subreq = state->db->ops->create_send(mem_ctx,
+                                         ev,
+                                         state->db,
+                                         client,
+                                         cc);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_create_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_create_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_create_cc_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_create_cc_state);
+    errno_t ret;
+
+    ret = state->db->ops->create_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to create ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_create_cc_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+void kcm_mod_ctx_clear(struct kcm_mod_ctx *mod_ctx)
+{
+    if (mod_ctx == NULL) {
+        return;
+    }
+
+    mod_ctx->kdc_offset = INT32_MAX;
+}
+
+void kcm_mod_cc(struct kcm_ccache *cc, struct kcm_mod_ctx *mod_ctx)
+{
+    if (cc == NULL || mod_ctx == NULL) {
+        return;
+    }
+
+    if (mod_ctx->kdc_offset != INT32_MAX) {
+        cc->kdc_offset = mod_ctx->kdc_offset;
+    }
+
+}
+
+struct kcm_ccdb_mod_cc_state {
+    struct kcm_ccdb *db;
+};
+
+static void kcm_ccdb_mod_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_mod_cc_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct kcm_ccdb *db,
+                                        struct cli_creds *client,
+                                        uuid_t uuid,
+                                        struct kcm_mod_ctx *mod_cc)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_mod_cc_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_mod_cc_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+
+    if (ev == NULL || db == NULL || client == NULL || mod_cc == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = state->db->ops->mod_send(mem_ctx,
+                                      ev,
+                                      state->db,
+                                      client,
+                                      uuid,
+                                      mod_cc);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_mod_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_mod_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_mod_cc_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_mod_cc_state);
+    errno_t ret;
+
+    ret = state->db->ops->mod_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to create ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_mod_cc_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+struct kcm_ccdb_store_cred_blob_state {
+    struct kcm_ccdb *db;
+};
+
+static void kcm_ccdb_store_cred_blob_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_store_cred_blob_send(TALLOC_CTX *mem_ctx,
+                                                 struct tevent_context *ev,
+                                                 struct kcm_ccdb *db,
+                                                 struct cli_creds *client,
+                                                 uuid_t uuid,
+                                                 struct sss_iobuf *cred_blob)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_store_cred_blob_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_store_cred_blob_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+
+    if (ev == NULL || db == NULL || client == NULL || cred_blob == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = state->db->ops->store_cred_send(mem_ctx,
+                                             ev,
+                                             state->db,
+                                             client,
+                                             uuid,
+                                             cred_blob);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_store_cred_blob_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_store_cred_blob_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_store_cred_blob_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_store_cred_blob_state);
+    errno_t ret;
+
+    ret = state->db->ops->store_cred_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to create ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_store_cred_blob_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+struct kcm_ccdb_delete_cc_state {
+    struct tevent_context *ev;
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+    uuid_t uuid;
+};
+
+static void kcm_ccdb_delete_done(struct tevent_req *subreq);
+static void kcm_ccdb_delete_get_default_done(struct tevent_req *subreq);
+static void kcm_ccdb_delete_default_reset_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_ccdb_delete_cc_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_ccdb_delete_cc_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_delete_cc_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->db = db;
+    state->ev = ev;
+    state->client = client;
+    uuid_copy(state->uuid, uuid);
+
+    if (ev == NULL || db == NULL || client == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = state->db->ops->delete_send(state,
+                                         state->ev,
+                                         state->db,
+                                         state->client,
+                                         state->uuid);
+    tevent_req_set_callback(subreq, kcm_ccdb_delete_done, req);
+
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_ccdb_delete_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_delete_cc_state);
+    errno_t ret;
+
+    ret = state->db->ops->delete_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to delete ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    /* The delete operation must also check if the deleted ccache was
+     * the default and reset the default if it was
+     */
+    subreq = state->db->ops->get_default_send(state,
+                                              state->ev,
+                                              state->db,
+                                              state->client);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_delete_get_default_done, req);
+}
+
+static void kcm_ccdb_delete_get_default_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_delete_cc_state);
+    errno_t ret;
+    uuid_t dfl_uuid;
+    uuid_t null_uuid;
+
+    ret = state->db->ops->get_default_recv(subreq, dfl_uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to get the default ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (uuid_compare(dfl_uuid, state->uuid) != 0) {
+        /* The ccache about to be deleted was not the default, quit */
+        tevent_req_done(req);
+        return;
+    }
+
+    /* If we deleted the default ccache, reset the default ccache to 'none' */
+    uuid_clear(null_uuid);
+
+    subreq = state->db->ops->set_default_send(state,
+                                              state->ev,
+                                              state->db,
+                                              state->client,
+                                              null_uuid);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_ccdb_delete_default_reset_done, req);
+}
+
+static void kcm_ccdb_delete_default_reset_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req,
+                                                struct kcm_ccdb_delete_cc_state);
+    errno_t ret;
+
+    ret = state->db->ops->set_default_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to NULL the default ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_ccdb_delete_cc_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+void kcm_debug_uuid(uuid_t uuid)
+{
+    char dbgbuf[UUID_STR_SIZE];
+
+    if (!(debug_level & SSSDBG_TRACE_ALL) || uuid == NULL) {
+        return;
+    }
+
+    uuid_unparse(uuid, dbgbuf);
+    DEBUG(SSSDBG_TRACE_ALL, "UUID: %s\n", dbgbuf);
+}
+
+errno_t kcm_check_name(const char *name, struct cli_creds *client)
+{
+    char prefix[64];
+    size_t prefix_len;
+
+    prefix_len = snprintf(prefix, sizeof(prefix),
+                          "%"SPRIuid, cli_creds_get_uid(client));
+
+    if (strncmp(name, prefix, prefix_len) != 0) {
+        return ERR_KCM_WRONG_CCNAME_FORMAT;
+    }
+    return EOK;
+}
diff --git a/src/responder/kcm/kcmsrv_ccache.h b/src/responder/kcm/kcmsrv_ccache.h
new file mode 100644
index 0000000..130ae48
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ccache.h
@@ -0,0 +1,306 @@
+/*
+   SSSD
+
+   KCM Server - the KCM ccache operations
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _KCMSRV_CCACHE_H_
+#define _KCMSRV_CCACHE_H_
+
+#include "config.h"
+
+#include <krb5/krb5.h>
+#include <uuid/uuid.h>
+
+#include "util/util.h"
+#include "util/sss_iobuf.h"
+#include "util/util_creds.h"
+
+#define UUID_BYTES    16
+#define UUID_STR_SIZE 37
+
+/*
+ * Credentials are opaque to the KCM server
+ *
+ * Each ccache has a unique UUID.
+ */
+struct kcm_cred;
+
+/*
+ * An opaque ccache type and its operations
+ *
+ * Contains zero or some KCM credentials. One credential in the cache
+ * is marked as the default one. The client can set and get the default
+ * cache (e.g. with kswitch) but one cache is always the default -- we
+ * fall back to the one created first.
+ *
+ * Each cache has a name and a UUID. Heimdal allows the name to be changed,
+ * we don't (yet, because the MIT client doesn't allow that either)
+ *
+ * Each ccache also stores a client principal.
+ */
+struct kcm_ccache;
+
+/*
+ * Create a new KCM ccache owned by mem_ctx on the
+ * memory level.
+ *
+ * When created, the ccache contains no credendials
+ */
+errno_t kcm_cc_new(TALLOC_CTX *mem_ctx,
+                   krb5_context k5c,
+                   struct cli_creds *owner,
+                   const char *name,
+                   krb5_principal princ,
+                   struct kcm_ccache **_cc);
+
+/*
+ * Returns true if a client can access a ccache.
+ *
+ * Note that root can access any ccache */
+bool kcm_cc_access(struct kcm_ccache *cc,
+                   struct cli_creds *client);
+
+/*
+ * Since the kcm_ccache structure is opaque, the kcmsrv_ccache
+ * layer contains a number of getsetters to read and write
+ * properties of the kcm_ccache structure
+ */
+const char *kcm_cc_get_name(struct kcm_ccache *cc);
+errno_t kcm_cc_get_uuid(struct kcm_ccache *cc, uuid_t _uuid);
+krb5_principal kcm_cc_get_client_principal(struct kcm_ccache *cc);
+int32_t kcm_cc_get_offset(struct kcm_ccache *cc);
+
+/* Mainly useful for creating a cred structure from a persistent
+ * storage
+ */
+struct kcm_cred *kcm_cred_new(TALLOC_CTX *mem_ctx,
+                              uuid_t uuid,
+                              struct sss_iobuf *cred_blob);
+
+/* Add a cred to ccache */
+errno_t kcm_cc_store_creds(struct kcm_ccache *cc,
+                           struct kcm_cred *crd);
+
+errno_t kcm_cred_get_uuid(struct kcm_cred *crd, uuid_t uuid);
+
+/*
+ * At the moment, the credentials are stored without unmarshalling
+ * them, just as the clients sends the credentials.
+ */
+struct sss_iobuf *kcm_cred_get_creds(struct kcm_cred *crd);
+errno_t kcm_cc_store_cred_blob(struct kcm_ccache *cc,
+                               struct sss_iobuf *cred_blob);
+ /*
+ * The KCM server can call kcm_cred_get_creds to fetch the first
+ * credential, then iterate over the credentials with
+ * kcm_cc_next_cred until it returns NULL
+ */
+struct kcm_cred *kcm_cc_get_cred(struct kcm_ccache *cc);
+struct kcm_cred *kcm_cc_next_cred(struct kcm_cred *crd);
+
+enum kcm_ccdb_be {
+    CCDB_BE_MEMORY,
+    CCDB_BE_SECRETS,
+};
+
+/* An opaque database that contains all the ccaches */
+struct kcm_ccdb;
+
+/*
+ * Initialize a ccache database of type cc_be
+ */
+struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               enum kcm_ccdb_be cc_be);
+
+/*
+ * In KCM, each ccache name is usually in the form of "UID:<num>
+ *
+ * The <num> is generated by the KCM ccache database. Use this function
+ * to retrieve the next number
+ */
+struct tevent_req *kcm_ccdb_nextid_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct kcm_ccdb *db,
+                                        struct cli_creds *client);
+errno_t kcm_ccdb_nextid_recv(struct tevent_req *req,
+                             TALLOC_CTX *mem_ctx,
+                             char **_nextid);
+
+/*
+ * List all ccaches that belong to a given client
+ *
+ * The cc_list the recv function returns is NULL-terminated.
+ *
+ * NOTE: Contrary to how Heimdal behaves, root CAN NOT list all ccaches
+ * of all users. This is a deliberate decision to treat root as any other
+ * user, except it can access a ccache of another user by name, just not
+ * list them.
+ *
+ * If a client has no ccaches, the function returns OK, but an empty list
+ * containing just the NULL sentinel.
+ */
+struct tevent_req *kcm_ccdb_list_send(TALLOC_CTX *mem_ctx,
+                                      struct tevent_context *ev,
+                                      struct kcm_ccdb *db,
+                                      struct cli_creds *client);
+errno_t kcm_ccdb_list_recv(struct tevent_req *req,
+                           TALLOC_CTX *mem_ctx,
+                           uuid_t **_uuid_list);
+
+/*
+ * Retrieve a ccache by name.
+ *
+ * If there is no such ccache, return EOK, but a NULL _cc pointer
+ */
+struct tevent_req *kcm_ccdb_getbyname_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           const char *name);
+errno_t kcm_ccdb_getbyname_recv(struct tevent_req *req,
+                                TALLOC_CTX *mem_ctx,
+                                struct kcm_ccache **_cc);
+
+/*
+ * Retrieve a ccache by UUID
+ *
+ * If there is no such ccache, return EOK, but a NULL _cc pointer
+ */
+struct tevent_req *kcm_ccdb_getbyuuid_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           uuid_t uuid);
+errno_t kcm_ccdb_getbyuuid_recv(struct tevent_req *req,
+                                TALLOC_CTX *mem_ctx,
+                                struct kcm_ccache **_cc);
+
+/*
+ * Retrieve the default ccache. If there is no default cache,
+ * return EOK, but a NULL UUID.
+ */
+struct tevent_req *kcm_ccdb_get_default_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct kcm_ccdb *db,
+                                             struct cli_creds *client);
+errno_t kcm_ccdb_get_default_recv(struct tevent_req *req,
+                                  uuid_t *uuid);
+
+/*
+ * Translating name to UUID is often considerably faster than doing a full
+ * CC retrieval, hence this function and the converse. If the UUID cannot
+ * be found in the database, return ERR_KCM_CC_END
+ */
+struct tevent_req *kcm_ccdb_name_by_uuid_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              uuid_t uuid);
+errno_t kcm_ccdb_name_by_uuid_recv(struct tevent_req *req,
+                                   TALLOC_CTX *mem_ctx,
+                                   const char **_name);
+
+/*
+ * Translating UUID to name is often considerably faster than doing a full
+ * CC retrieval, hence this function and the converse. If the UUID cannot
+ * be found in the database, return ERR_KCM_CC_END
+ */
+struct tevent_req *kcm_ccdb_uuid_by_name_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              const char *name);
+errno_t kcm_ccdb_uuid_by_name_recv(struct tevent_req *req,
+                                   TALLOC_CTX *mem_ctx,
+                                   uuid_t _uuid);
+
+/*
+ * Set the default ccache. Passing a NULL UUID is a legal operation
+ * that 'unsets' the default ccache.
+ */
+struct tevent_req *kcm_ccdb_set_default_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct kcm_ccdb *db,
+                                             struct cli_creds *client,
+                                             uuid_t uuid);
+errno_t kcm_ccdb_set_default_recv(struct tevent_req *req);
+
+/*
+ * Add a ccache to the database.
+ */
+struct tevent_req *kcm_ccdb_create_cc_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           struct kcm_ccache *cc);
+errno_t kcm_ccdb_create_cc_recv(struct tevent_req *req);
+
+/*
+ * Modify cache properties in a db
+ */
+struct kcm_mod_ctx {
+    int32_t kdc_offset;
+    /* More settable properties (like name, when we support renames
+     * will be added later
+     */
+};
+
+void kcm_mod_ctx_clear(struct kcm_mod_ctx *mod_ctx);
+void kcm_mod_cc(struct kcm_ccache *cc, struct kcm_mod_ctx *mod_ctx);
+
+struct tevent_req *kcm_ccdb_mod_cc_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct kcm_ccdb *db,
+                                        struct cli_creds *client,
+                                        uuid_t uuid,
+                                        struct kcm_mod_ctx *mod_cc);
+errno_t kcm_ccdb_mod_cc_recv(struct tevent_req *req);
+
+/*
+ * Store a credential in a cache
+ */
+struct tevent_req *kcm_ccdb_store_cred_blob_send(TALLOC_CTX *mem_ctx,
+                                                 struct tevent_context *ev,
+                                                 struct kcm_ccdb *db,
+                                                 struct cli_creds *client,
+                                                 uuid_t uuid,
+                                                 struct sss_iobuf *cred_blob);
+errno_t kcm_ccdb_store_cred_blob_recv(struct tevent_req *req);
+
+/*
+ * Delete a ccache from the database
+ */
+struct tevent_req *kcm_ccdb_delete_cc_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct kcm_ccdb *db,
+                                           struct cli_creds *client,
+                                           uuid_t uuid);
+errno_t kcm_ccdb_delete_cc_recv(struct tevent_req *req);
+
+void kcm_debug_uuid(uuid_t uuid);
+
+/*
+ * The KCM clients are not allowed (except root) to create ccaches
+ * with arbitrary names. Instead, we assert that the ccache name
+ * begins with UID where UID is the stringified representation of
+ * the client's UID number
+ */
+errno_t kcm_check_name(const char *name, struct cli_creds *client);
+
+#endif /* _KCMSRV_CCACHE_H_ */
diff --git a/src/responder/kcm/kcmsrv_ccache_be.h b/src/responder/kcm/kcmsrv_ccache_be.h
new file mode 100644
index 0000000..1bd2b69
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ccache_be.h
@@ -0,0 +1,204 @@
+/*
+   SSSD
+
+   KCM Server - the KCM ccache database interface
+
+   This file should only be included from the ccache.c module.
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _KCMSRV_CCACHE_BE_
+#define _KCMSRV_CCACHE_BE_
+
+#include "config.h"
+
+#include <talloc.h>
+#include "responder/kcm/kcmsrv_ccache.h"
+
+typedef errno_t
+(*ccdb_init_fn)(struct kcm_ccdb *db);
+
+typedef struct tevent_req *
+(*ccdb_nextid_send_fn)(TALLOC_CTX *mem_ctx,
+                       struct tevent_context *ev,
+                       struct kcm_ccdb *db,
+                       struct cli_creds *client);
+typedef errno_t
+(*ccdb_nextid_recv_fn)(struct tevent_req *req,
+                       unsigned int *_nextid);
+
+typedef struct tevent_req *
+(*ccdb_set_default_send_fn)(TALLOC_CTX *mem_ctx,
+                            struct tevent_context *ev,
+                            struct kcm_ccdb *db,
+                            struct cli_creds *client,
+                            uuid_t uuid);
+typedef errno_t
+(*ccdb_set_default_recv_fn)(struct tevent_req *req);
+
+typedef struct tevent_req *
+(*ccdb_get_default_send_fn)(TALLOC_CTX *mem_ctx,
+                            struct tevent_context *ev,
+                            struct kcm_ccdb *db,
+                            struct cli_creds *client);
+typedef errno_t
+(*ccdb_get_default_recv_fn)(struct tevent_req *req,
+                            uuid_t dfl);
+
+
+typedef struct tevent_req *
+(*ccdb_list_send_fn)(TALLOC_CTX *mem_ctx,
+                     struct tevent_context *ev,
+                     struct kcm_ccdb *db,
+                     struct cli_creds *client);
+typedef errno_t
+(*ccdb_list_recv_fn)(struct tevent_req *req,
+                     TALLOC_CTX *mem_ctx,
+                     uuid_t **_uuid_list);
+
+typedef struct tevent_req *
+(*ccdb_getbyname_send_fn)(TALLOC_CTX *mem_ctx,
+                          struct tevent_context *ev,
+                          struct kcm_ccdb *db,
+                          struct cli_creds *client,
+                          const char *name);
+typedef errno_t
+(*ccdb_getbyname_recv_fn)(struct tevent_req *req,
+                          TALLOC_CTX *mem_ctx,
+                          struct kcm_ccache **_cc);
+
+typedef struct tevent_req *
+(*ccdb_getbyuuid_send_fn)(TALLOC_CTX *mem_ctx,
+                          struct tevent_context *ev,
+                          struct kcm_ccdb *db,
+                          struct cli_creds *client,
+                          uuid_t uuid);
+typedef errno_t
+(*ccdb_getbyuuid_recv_fn)(struct tevent_req *req,
+                          TALLOC_CTX *mem_ctx,
+                          struct kcm_ccache **_cc);
+
+typedef struct tevent_req *
+(*ccdb_name_by_uuid_send_fn)(TALLOC_CTX *mem_ctx,
+                             struct tevent_context *ev,
+                             struct kcm_ccdb *db,
+                             struct cli_creds *client,
+                             uuid_t uuid);
+typedef errno_t
+(*ccdb_name_by_uuid_recv_fn)(struct tevent_req *req,
+                             TALLOC_CTX *mem_ctx,
+                             const char **_name);
+
+typedef struct tevent_req *
+(*ccdb_uuid_by_name_send_fn)(TALLOC_CTX *mem_ctx,
+                             struct tevent_context *ev,
+                             struct kcm_ccdb *db,
+                             struct cli_creds *client,
+                             const char *name);
+typedef errno_t
+(*ccdb_uuid_by_name_recv_fn)(struct tevent_req *req,
+                             TALLOC_CTX *mem_ctx,
+                             uuid_t _uuid);
+
+typedef struct tevent_req *
+(*ccdb_create_send_fn)(TALLOC_CTX *mem_ctx,
+                       struct tevent_context *ev,
+                       struct kcm_ccdb *db,
+                       struct cli_creds *client,
+                       struct kcm_ccache *cc);
+typedef errno_t
+(*ccdb_create_recv_fn)(struct tevent_req *req);
+
+typedef struct tevent_req *
+(*ccdb_mod_send_fn)(TALLOC_CTX *mem_ctx,
+                    struct tevent_context *ev,
+                    struct kcm_ccdb *db,
+                    struct cli_creds *client,
+                    uuid_t uuid,
+                    struct kcm_mod_ctx *mod_cc);
+typedef errno_t
+(*ccdb_mod_recv_fn)(struct tevent_req *req);
+
+typedef struct tevent_req *
+(*kcm_ccdb_store_cred_blob_send_fn)(TALLOC_CTX *mem_ctx,
+                                    struct tevent_context *ev,
+                                    struct kcm_ccdb *db,
+                                    struct cli_creds *client,
+                                    uuid_t uuid,
+                                    struct sss_iobuf *cred_blob);
+typedef errno_t
+(*kcm_ccdb_store_cred_blob_recv_fn)(struct tevent_req *req);
+
+typedef struct tevent_req *
+(*ccdb_delete_send_fn)(TALLOC_CTX *mem_ctx,
+                      struct tevent_context *ev,
+                      struct kcm_ccdb *db,
+                      struct cli_creds *client,
+                      uuid_t uuid);
+typedef errno_t
+(*ccdb_delete_recv_fn)(struct tevent_req *req);
+
+/*
+ * Each ccache back end (for example memory or secrets) must implement
+ * all these functions. The functions are wrapped by the kcm_ccdb
+ * interface that performs additional sanity checks or contains shared
+ * logic such as access checks but in general doesn't assume anything
+ * about how the operations work.
+ */
+struct kcm_ccdb_ops {
+    ccdb_init_fn init;
+
+    ccdb_nextid_send_fn nextid_send;
+    ccdb_nextid_recv_fn nextid_recv;
+
+    ccdb_set_default_send_fn set_default_send;
+    ccdb_set_default_recv_fn set_default_recv;
+
+    ccdb_get_default_send_fn get_default_send;
+    ccdb_get_default_recv_fn get_default_recv;
+
+    ccdb_list_send_fn list_send;
+    ccdb_list_recv_fn list_recv;
+
+    ccdb_getbyname_send_fn getbyname_send;
+    ccdb_getbyname_recv_fn getbyname_recv;
+
+    ccdb_getbyuuid_send_fn getbyuuid_send;
+    ccdb_getbyuuid_recv_fn getbyuuid_recv;
+
+    ccdb_name_by_uuid_send_fn name_by_uuid_send;
+    ccdb_name_by_uuid_recv_fn name_by_uuid_recv;
+
+    ccdb_uuid_by_name_send_fn uuid_by_name_send;
+    ccdb_uuid_by_name_recv_fn uuid_by_name_recv;
+
+    ccdb_create_send_fn create_send;
+    ccdb_create_recv_fn create_recv;
+
+    ccdb_mod_send_fn mod_send;
+    ccdb_mod_recv_fn mod_recv;
+
+    kcm_ccdb_store_cred_blob_send_fn store_cred_send;
+    kcm_ccdb_store_cred_blob_recv_fn store_cred_recv;
+
+    ccdb_delete_send_fn delete_send;
+    ccdb_delete_recv_fn delete_recv;
+};
+
+extern const struct kcm_ccdb_ops ccdb_mem_ops;
+
+#endif /* _KCMSRV_CCACHE_BE_ */
diff --git a/src/responder/kcm/kcmsrv_ccache_pvt.h b/src/responder/kcm/kcmsrv_ccache_pvt.h
new file mode 100644
index 0000000..0cc24c2
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ccache_pvt.h
@@ -0,0 +1,62 @@
+/*
+   SSSD
+
+   KCM Server - the KCM ccache operations - private structures
+
+   Should be accessed only from the ccache layer.
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _KCMSRV_CCACHE_PVT_H
+#define _KCMSRV_CCACHE_PVT_H
+
+#include "responder/kcm/kcmsrv_ccache.h"
+#include "responder/kcm/kcmsrv_ccache_be.h"
+
+struct kcm_ccache_owner {
+    uid_t uid;
+    gid_t gid;
+};
+
+struct kcm_cred {
+    struct sss_iobuf *cred_blob;
+    /* Randomly generated 16 bytes */
+    uuid_t uuid;
+
+    struct kcm_cred *next;
+    struct kcm_cred *prev;
+};
+
+struct kcm_ccdb {
+    enum kcm_ccdb_be cc_be_type;
+    struct tevent_context *ev;
+
+    void *db_handle;
+    const struct kcm_ccdb_ops *ops;
+};
+
+struct kcm_ccache {
+    const char *name;
+    struct kcm_ccache_owner owner;
+    uuid_t uuid;
+
+    krb5_principal client;
+    int32_t kdc_offset;
+
+    struct kcm_cred *creds;
+};
+
+#endif /* _KCMSRV_CCACHE_PVT_H */
diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h
index fd1fd9f..a296802 100644
--- a/src/responder/kcm/kcmsrv_pvt.h
+++ b/src/responder/kcm/kcmsrv_pvt.h
@@ -46,6 +46,7 @@ struct kcm_data {
  */
 struct kcm_resp_ctx {
     krb5_context k5c;
+    struct kcm_ccdb *db;
 };
 
 /*

From 5c6d2d782e89c5c96dcdbb073d1325de9f965ca5 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 7 Mar 2017 13:49:43 +0100
Subject: [PATCH 10/21] KCM: Add a in-memory credential storage

Implements a simple back end for the ccache module that lets the KCM
server store credentials directly in memory.
---
 Makefile.am                           |   1 +
 src/responder/kcm/kcm.c               |  13 +-
 src/responder/kcm/kcmsrv_ccache.c     |   2 +-
 src/responder/kcm/kcmsrv_ccache_mem.c | 805 ++++++++++++++++++++++++++++++++++
 4 files changed, 817 insertions(+), 4 deletions(-)
 create mode 100644 src/responder/kcm/kcmsrv_ccache_mem.c

diff --git a/Makefile.am b/Makefile.am
index 5463f2a..8c8813e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1482,6 +1482,7 @@ sssd_kcm_SOURCES = \
     src/responder/kcm/kcm.c \
     src/responder/kcm/kcmsrv_cmd.c \
     src/responder/kcm/kcmsrv_ccache.c \
+    src/responder/kcm/kcmsrv_ccache_mem.c \
     src/util/sss_sockets.c \
     src/util/sss_krb5.c \
     src/util/sss_iobuf.c \
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index 90a6999..2c12ef2 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -22,9 +22,9 @@
 #include "config.h"
 
 #include <popt.h>
-#include <krb5/krb5.h>
 
 #include "responder/kcm/kcm.h"
+#include "responder/kcm/kcmsrv_ccache.h"
 #include "responder/kcm/kcmsrv_pvt.h"
 #include "responder/common/responder.h"
 #include "util/util.h"
@@ -110,7 +110,8 @@ static int kcm_data_destructor(void *ptr)
     return 0;
 }
 
-static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx)
+static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev)
 {
     struct kcm_resp_ctx *kcm_data;
     krb5_error_code kret;
@@ -121,6 +122,12 @@ static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx)
         return NULL;
     }
 
+    kcm_data->db = kcm_ccdb_init(kcm_data, ev, CCDB_BE_MEMORY);
+    if (kcm_data->db == NULL) {
+        talloc_free(kcm_data);
+        return NULL;
+    }
+
     kret = krb5_init_context(&kcm_data->k5c);
     if (kret != EOK) {
         talloc_free(kcm_data);
@@ -169,7 +176,7 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
-    kctx->kcm_data = kcm_data_setup(kctx);
+    kctx->kcm_data = kcm_data_setup(kctx, ev);
     if (kctx->kcm_data == NULL) {
         DEBUG(SSSDBG_FATAL_FAILURE,
               "fatal error initializing responder data\n");
diff --git a/src/responder/kcm/kcmsrv_ccache.c b/src/responder/kcm/kcmsrv_ccache.c
index 2c565b8..2ae1202 100644
--- a/src/responder/kcm/kcmsrv_ccache.c
+++ b/src/responder/kcm/kcmsrv_ccache.c
@@ -240,7 +240,7 @@ struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx,
     switch (cc_be) {
     case CCDB_BE_MEMORY:
         DEBUG(SSSDBG_FUNC_DATA, "KCM back end: memory\n");
-        /* Not implemented yet */
+        ccdb->ops = &ccdb_mem_ops;
         break;
     case CCDB_BE_SECRETS:
         DEBUG(SSSDBG_FUNC_DATA, "KCM back end: sssd-secrets\n");
diff --git a/src/responder/kcm/kcmsrv_ccache_mem.c b/src/responder/kcm/kcmsrv_ccache_mem.c
new file mode 100644
index 0000000..1c4f3df
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ccache_mem.c
@@ -0,0 +1,805 @@
+/*
+   SSSD
+
+   KCM Server - ccache in-memory storage
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <talloc.h>
+#include <stdio.h>
+
+#include "util/util.h"
+#include "responder/kcm/kcmsrv_ccache_pvt.h"
+#include "responder/kcm/kcmsrv_ccache_be.h"
+
+struct ccdb_mem;
+
+/*
+ * The KCM memory database is just a double-linked list of kcm_ccache structures
+ */
+struct ccache_mem_wrap {
+    struct kcm_ccache *cc;
+    bool is_default;
+
+    struct ccache_mem_wrap *next;
+    struct ccache_mem_wrap *prev;
+
+    struct ccdb_mem *mem_be;
+};
+
+struct ccdb_mem {
+    /* Both ccaches and the next-id are kept in memory */
+    struct ccache_mem_wrap *head;
+    unsigned int nextid;
+};
+
+/* In order to provide a consistent interface, we need to let the caller
+ * of getbyXXX own the ccache, therefore the memory back end returns a shallow
+ * copy of the ccache
+ */
+static struct kcm_ccache *kcm_ccache_dup(TALLOC_CTX *mem_ctx,
+                                         struct kcm_ccache *in)
+{
+    struct kcm_ccache *out;
+
+    out = talloc_zero(mem_ctx, struct kcm_ccache);
+    if (out == NULL) {
+        return NULL;
+    }
+    memcpy(out, in, sizeof(struct kcm_ccache));
+
+    return out;
+}
+
+static struct ccache_mem_wrap *memdb_get_by_uuid(struct ccdb_mem *memdb,
+                                                 struct cli_creds *client,
+                                                 uuid_t uuid)
+{
+    uid_t uid;
+    struct ccache_mem_wrap *ccwrap = NULL;
+    struct ccache_mem_wrap *out = NULL;
+
+    uid = cli_creds_get_uid(client);
+
+    DLIST_FOR_EACH(ccwrap, memdb->head) {
+        if (ccwrap->cc == NULL) {
+            /* since KCM stores ccaches, better not crash.. */
+            DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n");
+            continue;
+        }
+
+        if (ccwrap->cc->owner.uid == uid) {
+            if (uuid_compare(uuid, ccwrap->cc->uuid) == 0) {
+                out = ccwrap;
+                break;
+            }
+        }
+    }
+
+    return out;
+}
+
+static struct ccache_mem_wrap *memdb_get_by_name(struct ccdb_mem *memdb,
+                                                 struct cli_creds *client,
+                                                 const char *name)
+{
+    uid_t uid;
+    struct ccache_mem_wrap *ccwrap = NULL;
+    struct ccache_mem_wrap *out = NULL;
+
+    uid = cli_creds_get_uid(client);
+
+    DLIST_FOR_EACH(ccwrap, memdb->head) {
+        if (ccwrap->cc == NULL) {
+            /* since KCM stores ccaches, better not crash.. */
+            DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n");
+            continue;
+        }
+
+        if (ccwrap->cc->owner.uid == uid) {
+            if (strcmp(ccwrap->cc->name, name) == 0) {
+                out = ccwrap;
+                break;
+            }
+        }
+    }
+
+    return out;
+}
+
+/* Since with the in-memory database, the database operations are just
+ * fake-async wrappers around otherwise sync operations, we don't often
+ * need any state, so we use this empty structure instead
+ */
+struct ccdb_mem_dummy_state {
+};
+
+static int ccwrap_destructor(void *ptr)
+{
+    struct ccache_mem_wrap *ccwrap = talloc_get_type(ptr, struct ccache_mem_wrap);
+
+    if (ccwrap == NULL) {
+        return 0;
+    }
+
+    if (ccwrap->cc != NULL) {
+        if (ccwrap->cc->creds) {
+            safezero(sss_iobuf_get_data(ccwrap->cc->creds->cred_blob),
+                     sss_iobuf_get_size(ccwrap->cc->creds->cred_blob));
+        }
+    }
+
+
+    DLIST_REMOVE(ccwrap->mem_be->head, ccwrap);
+
+    return 0;
+}
+
+static errno_t ccdb_mem_init(struct kcm_ccdb *db)
+{
+    struct ccdb_mem *memdb = NULL;
+
+    memdb = talloc_zero(db, struct ccdb_mem);
+    if (memdb == NULL) {
+        return ENOMEM;
+    }
+    db->db_handle = memdb;
+
+    return EOK;
+}
+
+struct ccdb_mem_nextid_state {
+    unsigned int nextid;
+};
+
+static struct tevent_req *ccdb_mem_nextid_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct kcm_ccdb *db,
+                                               struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_nextid_state *state = NULL;
+    struct ccdb_mem *memdb = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_nextid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    if (memdb == NULL) {
+        ret = EIO;
+        goto immediate;
+    }
+
+    state->nextid = memdb->nextid++;
+
+    ret = EOK;
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_nextid_recv(struct tevent_req *req,
+                                    unsigned int *_nextid)
+{
+    struct ccdb_mem_nextid_state *state = tevent_req_data(req,
+                                                struct ccdb_mem_nextid_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_nextid = state->nextid;
+    return EOK;
+}
+
+struct ccdb_mem_list_state {
+    uuid_t *uuid_list;
+};
+
+static struct tevent_req *ccdb_mem_list_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct kcm_ccdb *db,
+                                             struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct ccache_mem_wrap *ccwrap = NULL;
+    struct ccdb_mem_list_state *state = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    size_t num_ccaches = 0;
+    size_t cc_index = 0;
+    errno_t ret;
+    uid_t uid;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_list_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    uid = cli_creds_get_uid(client);
+
+    DLIST_FOR_EACH(ccwrap, memdb->head) {
+        if (ccwrap->cc->owner.uid == uid) {
+            num_ccaches++;
+        }
+    }
+
+    state->uuid_list = talloc_zero_array(state, uuid_t, num_ccaches+1);
+    if (state->uuid_list == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    cc_index = 0;
+    DLIST_FOR_EACH(ccwrap, memdb->head) {
+        if (ccwrap->cc->owner.uid == uid) {
+            uuid_copy(state->uuid_list[cc_index], ccwrap->cc->uuid);
+            cc_index++;
+        }
+    }
+    uuid_clear(state->uuid_list[num_ccaches]);
+
+    ret = EOK;
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_list_recv(struct tevent_req *req,
+                                  TALLOC_CTX *mem_ctx,
+                                  uuid_t **_uuid_list)
+{
+    struct ccdb_mem_list_state *state = tevent_req_data(req,
+                                                struct ccdb_mem_list_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_uuid_list = talloc_steal(mem_ctx, state->uuid_list);
+    return EOK;
+}
+
+static struct tevent_req *ccdb_mem_set_default_send(TALLOC_CTX *mem_ctx,
+                                                    struct tevent_context *ev,
+                                                    struct kcm_ccdb *db,
+                                                    struct cli_creds *client,
+                                                    uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_dummy_state *state = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    struct ccache_mem_wrap *ccwrap = NULL;
+    uid_t uid = cli_creds_get_uid(client);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    /* Reset all ccache defaults first */
+    DLIST_FOR_EACH(ccwrap, memdb->head) {
+        if (ccwrap->cc == NULL) {
+            /* since KCM stores ccaches, better not crash.. */
+            DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n");
+            continue;
+        }
+
+        if (ccwrap->cc->owner.uid == uid) {
+            ccwrap->is_default = false;
+        }
+    }
+
+    /* Then set the default for the right ccache. This also allows to
+     * pass a null uuid to just reset the old ccache (for example after
+     * deleting the default
+     */
+    ccwrap = memdb_get_by_uuid(memdb, client, uuid);
+    if (ccwrap != NULL) {
+        ccwrap->is_default = true;
+    }
+
+    tevent_req_done(req);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_set_default_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+struct ccdb_mem_get_default_state {
+    uuid_t dfl_uuid;
+};
+
+static struct tevent_req *ccdb_mem_get_default_send(TALLOC_CTX *mem_ctx,
+                                                    struct tevent_context *ev,
+                                                    struct kcm_ccdb *db,
+                                                    struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_get_default_state *state = NULL;
+    struct ccache_mem_wrap *ccwrap = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    uid_t uid = cli_creds_get_uid(client);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_get_default_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+
+    /* Reset all ccache defaults first */
+    DLIST_FOR_EACH(ccwrap, memdb->head) {
+        if (ccwrap->cc == NULL) {
+            /* since KCM stores ccaches, better not crash.. */
+            DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n");
+            continue;
+        }
+
+        if (ccwrap->cc->owner.uid == uid && ccwrap->is_default == true) {
+            break;
+        }
+    }
+
+    if (ccwrap == NULL) {
+        DEBUG(SSSDBG_TRACE_FUNC,
+               "No ccache marked as default, returning null ccache\n");
+        uuid_clear(state->dfl_uuid);
+    } else {
+        uuid_copy(state->dfl_uuid, ccwrap->cc->uuid);
+    }
+
+    tevent_req_done(req);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_get_default_recv(struct tevent_req *req,
+                                         uuid_t dfl)
+{
+    struct ccdb_mem_get_default_state *state = tevent_req_data(req,
+                                                struct ccdb_mem_get_default_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    uuid_copy(dfl, state->dfl_uuid);
+    return EOK;
+}
+
+struct ccdb_mem_getbyuuid_state {
+    struct kcm_ccache *cc;
+};
+
+static struct tevent_req *ccdb_mem_getbyuuid_send(TALLOC_CTX *mem_ctx,
+                                                  struct tevent_context *ev,
+                                                  struct kcm_ccdb *db,
+                                                  struct cli_creds *client,
+                                                  uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_getbyuuid_state *state = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    struct ccache_mem_wrap *ccwrap = NULL;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_getbyuuid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    ccwrap = memdb_get_by_uuid(memdb, client, uuid);
+    if (ccwrap != NULL) {
+        state->cc = kcm_ccache_dup(state, ccwrap->cc);
+    }
+
+    tevent_req_done(req);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_getbyuuid_recv(struct tevent_req *req,
+                                       TALLOC_CTX *mem_ctx,
+                                       struct kcm_ccache **_cc)
+{
+    struct ccdb_mem_getbyuuid_state *state = tevent_req_data(req,
+                                                struct ccdb_mem_getbyuuid_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+struct ccdb_mem_getbyname_state {
+    struct kcm_ccache *cc;
+};
+
+static struct tevent_req *ccdb_mem_getbyname_send(TALLOC_CTX *mem_ctx,
+                                                  struct tevent_context *ev,
+                                                  struct kcm_ccdb *db,
+                                                  struct cli_creds *client,
+                                                  const char *name)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_getbyname_state *state = NULL;
+    struct ccache_mem_wrap *ccwrap = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_getbyname_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    ccwrap = memdb_get_by_name(memdb, client, name);
+    if (ccwrap != NULL) {
+        state->cc = kcm_ccache_dup(state, ccwrap->cc);
+    }
+
+    tevent_req_done(req);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_getbyname_recv(struct tevent_req *req,
+                                       TALLOC_CTX *mem_ctx,
+                                       struct kcm_ccache **_cc)
+{
+    struct ccdb_mem_getbyname_state *state = tevent_req_data(req,
+                                                struct ccdb_mem_getbyname_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+struct ccdb_mem_name_by_uuid_state {
+    const char *name;
+};
+
+struct tevent_req *ccdb_mem_name_by_uuid_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_name_by_uuid_state *state = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    struct ccache_mem_wrap *ccwrap = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_name_by_uuid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    ccwrap = memdb_get_by_uuid(memdb, client, uuid);
+    if (ccwrap == NULL) {
+        ret = ERR_KCM_CC_END;
+        goto immediate;
+    }
+
+    state->name = talloc_strdup(state, ccwrap->cc->name);
+    if (state->name == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    ret = EOK;
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+errno_t ccdb_mem_name_by_uuid_recv(struct tevent_req *req,
+                                   TALLOC_CTX *mem_ctx,
+                                   const char **_name)
+{
+    struct ccdb_mem_name_by_uuid_state *state = tevent_req_data(req,
+                                                struct ccdb_mem_name_by_uuid_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_name = talloc_steal(mem_ctx, state->name);
+    return EOK;
+}
+
+struct ccdb_mem_uuid_by_name_state {
+    uuid_t uuid;
+};
+
+struct tevent_req *ccdb_mem_uuid_by_name_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              const char *name)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_uuid_by_name_state *state = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    struct ccache_mem_wrap *ccwrap = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_uuid_by_name_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    ccwrap = memdb_get_by_name(memdb, client, name);
+    if (ccwrap == NULL) {
+        ret = ERR_KCM_CC_END;
+        goto immediate;
+    }
+
+    uuid_copy(state->uuid, ccwrap->cc->uuid);
+
+    ret = EOK;
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+errno_t ccdb_mem_uuid_by_name_recv(struct tevent_req *req,
+                                   TALLOC_CTX *mem_ctx,
+                                   uuid_t _uuid)
+{
+    struct ccdb_mem_uuid_by_name_state *state = tevent_req_data(req,
+                                                struct ccdb_mem_uuid_by_name_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    uuid_copy(_uuid, state->uuid);
+    return EOK;
+}
+
+static struct tevent_req *ccdb_mem_create_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct kcm_ccdb *db,
+                                               struct cli_creds *client,
+                                               struct kcm_ccache *cc)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_dummy_state *state = NULL;
+    struct ccache_mem_wrap *ccwrap;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    ccwrap = talloc_zero(memdb, struct ccache_mem_wrap);
+    if (ccwrap == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    ccwrap->cc = cc;
+    ccwrap->mem_be = memdb;
+    talloc_steal(ccwrap, cc);
+
+    DLIST_ADD(memdb->head, ccwrap);
+    talloc_set_destructor((TALLOC_CTX *) ccwrap, ccwrap_destructor);
+
+    ret = EOK;
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_create_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+static struct tevent_req *ccdb_mem_mod_send(TALLOC_CTX *mem_ctx,
+                                            struct tevent_context *ev,
+                                            struct kcm_ccdb *db,
+                                            struct cli_creds *client,
+                                            uuid_t uuid,
+                                            struct kcm_mod_ctx *mod_cc)
+{
+    errno_t ret;
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_dummy_state *state = NULL;
+    struct ccache_mem_wrap *ccwrap = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    /* UUID is immutable, so search by that */
+    ccwrap = memdb_get_by_uuid(memdb, client, uuid);
+    if (ccwrap == NULL) {
+        ret = ERR_KCM_CC_END;
+        goto immediate;
+    }
+
+    kcm_mod_cc(ccwrap->cc, mod_cc);
+
+    ret = EOK;
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_mod_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+static struct tevent_req *ccdb_mem_store_cred_send(TALLOC_CTX *mem_ctx,
+                                                   struct tevent_context *ev,
+                                                   struct kcm_ccdb *db,
+                                                   struct cli_creds *client,
+                                                   uuid_t uuid,
+                                                   struct sss_iobuf *cred_blob)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_dummy_state *state = NULL;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    struct ccache_mem_wrap *ccwrap = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    ccwrap = memdb_get_by_uuid(memdb, client, uuid);
+    if (ccwrap == NULL) {
+        ret = ERR_KCM_CC_END;
+        goto immediate;
+    }
+
+    ret = kcm_cc_store_cred_blob(ccwrap->cc, cred_blob);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot store credentials to ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    ret = EOK;
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_store_cred_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+static struct tevent_req *ccdb_mem_delete_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct kcm_ccdb *db,
+                                               struct cli_creds *client,
+                                               uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_mem_dummy_state *state = NULL;
+    struct ccache_mem_wrap *ccwrap;
+    struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem);
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    ccwrap = memdb_get_by_uuid(memdb, client, uuid);
+    if (ccwrap == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "BUG: Attempting to free unknown ccache\n");
+        ret = ERR_KCM_CC_END;
+        goto immediate;
+    }
+
+    ret = EOK;
+    /* Destructor takes care of everything */
+    talloc_free(ccwrap);
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_mem_delete_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+const struct kcm_ccdb_ops ccdb_mem_ops = {
+    .init = ccdb_mem_init,
+
+    .nextid_send = ccdb_mem_nextid_send,
+    .nextid_recv = ccdb_mem_nextid_recv,
+
+    .set_default_send = ccdb_mem_set_default_send,
+    .set_default_recv = ccdb_mem_set_default_recv,
+
+    .get_default_send = ccdb_mem_get_default_send,
+    .get_default_recv = ccdb_mem_get_default_recv,
+
+    .list_send = ccdb_mem_list_send,
+    .list_recv = ccdb_mem_list_recv,
+
+    .getbyname_send = ccdb_mem_getbyname_send,
+    .getbyname_recv = ccdb_mem_getbyname_recv,
+
+    .getbyuuid_send = ccdb_mem_getbyuuid_send,
+    .getbyuuid_recv = ccdb_mem_getbyuuid_recv,
+
+    .name_by_uuid_send = ccdb_mem_name_by_uuid_send,
+    .name_by_uuid_recv = ccdb_mem_name_by_uuid_recv,
+
+    .uuid_by_name_send = ccdb_mem_uuid_by_name_send,
+    .uuid_by_name_recv = ccdb_mem_uuid_by_name_recv,
+
+    .create_send = ccdb_mem_create_send,
+    .create_recv = ccdb_mem_create_recv,
+
+    .mod_send = ccdb_mem_mod_send,
+    .mod_recv = ccdb_mem_mod_recv,
+
+    .store_cred_send = ccdb_mem_store_cred_send,
+    .store_cred_recv = ccdb_mem_store_cred_recv,
+
+    .delete_send = ccdb_mem_delete_send,
+    .delete_recv = ccdb_mem_delete_recv,
+};

From 746a2f223cafb4a4cb135cb0fcb972778ee32f9e Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 23 Sep 2016 14:49:09 +0200
Subject: [PATCH 11/21] KCM: Implement KCM server operations

Implements the actual KCM server operations. On a high level, each
operation unmarhalls the needed data from the input buffer, calls into
the ccache db and marshalls a response.

Only the operations that are also implemented by the MIT client are
implemented by our KCM server.

Resolves:
    https://pagure.io/SSSD/sssd/issue/2887
---
 Makefile.am                    |    2 +
 src/responder/kcm/kcmsrv_cmd.c |  151 +++-
 src/responder/kcm/kcmsrv_ops.c | 1954 ++++++++++++++++++++++++++++++++++++++++
 src/responder/kcm/kcmsrv_ops.h |   45 +
 4 files changed, 2143 insertions(+), 9 deletions(-)
 create mode 100644 src/responder/kcm/kcmsrv_ops.c
 create mode 100644 src/responder/kcm/kcmsrv_ops.h

diff --git a/Makefile.am b/Makefile.am
index 8c8813e..eb46828 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -709,6 +709,7 @@ dist_noinst_HEADERS = \
     src/responder/kcm/kcmsrv_ccache.h \
     src/responder/kcm/kcmsrv_ccache_pvt.h \
     src/responder/kcm/kcmsrv_ccache_be.h \
+    src/responder/kcm/kcmsrv_ops.h \
     src/sbus/sbus_client.h \
     src/sbus/sssd_dbus.h \
     src/sbus/sssd_dbus_meta.h \
@@ -1483,6 +1484,7 @@ sssd_kcm_SOURCES = \
     src/responder/kcm/kcmsrv_cmd.c \
     src/responder/kcm/kcmsrv_ccache.c \
     src/responder/kcm/kcmsrv_ccache_mem.c \
+    src/responder/kcm/kcmsrv_ops.c \
     src/util/sss_sockets.c \
     src/util/sss_krb5.c \
     src/util/sss_iobuf.c \
diff --git a/src/responder/kcm/kcmsrv_cmd.c b/src/responder/kcm/kcmsrv_cmd.c
index cbf7035..537e889 100644
--- a/src/responder/kcm/kcmsrv_cmd.c
+++ b/src/responder/kcm/kcmsrv_cmd.c
@@ -23,10 +23,10 @@
 
 #include "config.h"
 #include "util/util.h"
-#include "util/sss_iobuf.h"
 #include "responder/common/responder.h"
 #include "responder/kcm/kcmsrv_pvt.h"
 #include "responder/kcm/kcm.h"
+#include "responder/kcm/kcmsrv_ops.h"
 
 /* The first four bytes of a message is always the size */
 #define KCM_MSG_LEN_SIZE 4
@@ -133,7 +133,6 @@ static errno_t kcm_input_parse(struct kcm_reqbuf *reqbuf,
 {
     size_t lc = 0;
     size_t mc = 0;
-    uint16_t opcode = 0;
     uint16_t opcode_be = 0;
     uint32_t len_be = 0;
     uint32_t msglen;
@@ -162,7 +161,7 @@ static errno_t kcm_input_parse(struct kcm_reqbuf *reqbuf,
         return EBADMSG;
     }
 
-    /* First 16 bits are 8 bit major and 8bit major protocol version */
+    /* First 16 bits are 8 bit major and 8bit minor protocol version */
     SAFEALIGN_COPY_UINT8_CHECK(&proto_maj,
                                reqbuf->v_msg.kiov_base + mc,
                                reqbuf->v_msg.kiov_len,
@@ -191,8 +190,16 @@ static errno_t kcm_input_parse(struct kcm_reqbuf *reqbuf,
                                 reqbuf->v_msg.kiov_len,
                                 &mc);
 
-    opcode = be16toh(opcode_be);
-    DEBUG(SSSDBG_TRACE_LIBS, "Received operation code %"PRIu16"\n", opcode);
+    op_io->op = kcm_get_opt(be16toh(opcode_be));
+    if (op_io->op == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Did not find a KCM operation handler for the requested opcode\n");
+        return ERR_KCM_MALFORMED_IN_PKT;
+    }
+
+    /* The operation only receives the payload, not the opcode or the protocol info */
+    op_io->request.data = reqbuf->v_msg.kiov_base + mc;
+    op_io->request.length = reqbuf->v_msg.nprocessed - mc;
 
     return EOK;
 }
@@ -240,6 +247,46 @@ static errno_t kcm_failbuf_construct(errno_t ret,
     c = 0;
     SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, htobe32(ret), &c);
 
+    DEBUG(SSSDBG_TRACE_LIBS, "Sent reply with error %d\n", ret);
+    return EOK;
+}
+
+/* retcode is 0 if the operation at least ran, non-zero if there
+ * was some kind of internal KCM error, like input couldn't be parsed
+ */
+static errno_t kcm_output_construct(struct kcm_op_io *op_io,
+                                    struct kcm_repbuf *repbuf)
+{
+    size_t c;
+    size_t replen;
+
+    replen = sss_iobuf_get_len(op_io->reply);
+    if (replen > KCM_PACKET_MAX_SIZE) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Reply exceeds the KCM protocol limit, aborting\n");
+        return E2BIG;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS,
+          "Sending a reply with %zu bytes of payload\n", replen);
+    c = 0;
+    SAFEALIGN_SETMEM_UINT32(repbuf->lenbuf, htobe32(replen), &c);
+
+    c = 0;
+    SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, 0, &c);
+
+    if (replen > 0) {
+        c = 0;
+        SAFEALIGN_MEMCPY_CHECK(repbuf->msgbuf,
+                               sss_iobuf_get_data(op_io->reply),
+                               replen,
+                               repbuf->v_msg.kiov_len,
+                               &c);
+
+        /* Length of the buffer to send to KCM client */
+        repbuf->v_msg.kiov_len = replen;
+    }
+
     return EOK;
 }
 
@@ -260,7 +307,8 @@ static void kcm_reply_error(struct cli_ctx *cctx,
 
     ret = kcm_failbuf_construct(kerr, repbuf);
     if (ret != EOK) {
-        /* If we can't construct the reply buffer, just terminate the client */
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot construct the reply buffer, terminating client\n");
         talloc_free(cctx);
         return;
     }
@@ -268,6 +316,24 @@ static void kcm_reply_error(struct cli_ctx *cctx,
     TEVENT_FD_WRITEABLE(cctx->cfde);
 }
 
+static void kcm_send_reply(struct cli_ctx *cctx,
+                           struct kcm_op_io *op_io,
+                           struct kcm_repbuf *repbuf)
+{
+    errno_t ret;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Sending a reply\n");
+    ret = kcm_output_construct(op_io, repbuf);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot construct the reply buffer, terminating client\n");
+        kcm_reply_error(cctx, ret, repbuf);
+        return;
+    }
+
+    TEVENT_FD_WRITEABLE(cctx->cfde);
+}
+
 /**
  * Request-reply dispatcher
  */
@@ -285,17 +351,67 @@ struct kcm_req_ctx {
     struct kcm_op_io op_io;
 };
 
+static void kcm_cmd_request_done(struct tevent_req *req);
+
+static errno_t kcm_cmd_dispatch(struct kcm_req_ctx *req_ctx)
+{
+    struct tevent_req *req;
+    struct cli_ctx *cctx;
+
+    cctx = req_ctx->cctx;
+
+    req = kcm_cmd_send(req_ctx, cctx->ev, req_ctx->kctx->kcm_data,
+                       req_ctx->cctx->creds,
+                       &req_ctx->op_io.request,
+                       req_ctx->op_io.op);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to schedule KCM operation.\n");
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(req, kcm_cmd_request_done, req_ctx);
+    return EOK;
+}
+
+static void kcm_cmd_request_done(struct tevent_req *req)
+{
+    struct kcm_req_ctx *req_ctx;
+    struct cli_ctx *cctx;
+    errno_t ret;
+
+    req_ctx = tevent_req_callback_data(req, struct kcm_req_ctx);
+    cctx = req_ctx->cctx;
+
+    ret = kcm_cmd_recv(req_ctx, req,
+                       &req_ctx->op_io.reply);
+    talloc_free(req);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "KCM operation failed [%d]: %s\n", ret, sss_strerror(ret));
+        kcm_reply_error(cctx, ret, &req_ctx->repbuf);
+        return;
+    }
+
+    kcm_send_reply(cctx, &req_ctx->op_io, &req_ctx->repbuf);
+}
+
 static errno_t kcm_recv_data(int fd, struct kcm_reqbuf *reqbuf)
 {
     errno_t ret;
 
     ret = kcm_read_iovec(fd, &reqbuf->v_len);
     if (ret != EOK) {
+        /* Not all errors are fatal, hence we don't print DEBUG messages
+         * here, but in the caller
+         */
         return ret;
     }
 
     ret = kcm_read_iovec(fd, &reqbuf->v_msg);
     if (ret != EOK) {
+        /* Not all errors are fatal, hence we don't print DEBUG messages
+         * here, but in the caller
+         */
         return ret;
     }
 
@@ -389,7 +505,15 @@ static void kcm_recv(struct cli_ctx *cctx)
     /* do not read anymore, client is done sending */
     TEVENT_FD_NOT_READABLE(cctx->cfde);
 
-    kcm_reply_error(cctx, ret, &req->repbuf);
+    ret = kcm_cmd_dispatch(req);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to dispatch KCM operation [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto fail;
+    }
+
+    /* Dispatched request resumes in kcm_cmd_request_done */
     return;
 
 fail:
@@ -406,16 +530,25 @@ static int kcm_send_data(struct cli_ctx *cctx)
 
     ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_len);
     if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to write the length iovec [%d]: %s\n",
+              ret, sss_strerror(ret));
         return ret;
     }
 
     ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_rc);
     if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to write the retcode iovec [%d]: %s\n",
+              ret, sss_strerror(ret));
         return ret;
     }
 
     ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_msg);
     if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to write the msg iovec [%d]: %s\n",
+              ret, sss_strerror(ret));
         return ret;
     }
 
@@ -428,7 +561,7 @@ static void kcm_send(struct cli_ctx *cctx)
 
     ret = kcm_send_data(cctx);
     if (ret == EAGAIN) {
-        /* not all data was sent, loop again */
+        DEBUG(SSSDBG_TRACE_ALL, "Sending data again..\n");
         return;
     } else if (ret != EOK) {
         DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n");
@@ -436,7 +569,7 @@ static void kcm_send(struct cli_ctx *cctx)
         return;
     }
 
-    /* ok all sent */
+    DEBUG(SSSDBG_TRACE_INTERNAL, "All data sent!\n");
     TEVENT_FD_NOT_WRITEABLE(cctx->cfde);
     TEVENT_FD_READABLE(cctx->cfde);
     talloc_zfree(cctx->state_ctx);
diff --git a/src/responder/kcm/kcmsrv_ops.c b/src/responder/kcm/kcmsrv_ops.c
new file mode 100644
index 0000000..50e8cc6
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ops.c
@@ -0,0 +1,1954 @@
+/*
+   SSSD
+
+   KCM Server - the KCM server operations
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <krb5/krb5.h>
+
+#include "util/sss_iobuf.h"
+#include "util/sss_krb5.h"
+#include "util/util_creds.h"
+#include "responder/kcm/kcm.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+#include "responder/kcm/kcmsrv_ops.h"
+#include "responder/kcm/kcmsrv_ccache.h"
+
+#define KCM_REPLY_MAX   2048
+
+struct kcm_op_ctx {
+    struct kcm_resp_ctx *kcm_data;
+    struct cli_creds *client;
+
+    struct sss_iobuf *input;
+    struct sss_iobuf *reply;
+};
+
+/* Each operation follows the same pattern and is implemented using
+ * functions with this prototype. The operation receives an op_ctx
+ * that serves as a state of the operation and can be used to keep
+ * track of any temporary data. The operation writes its output data
+ * into the op_ctx reply IO buffer and returns the op_ret status code
+ * separately.
+ *
+ * The operation always returns EOK unless an internal error occurs,
+ * the result of the operation is stored in the op_ret variable
+ */
+typedef struct tevent_req*
+(*kcm_srv_send_method)(TALLOC_CTX *mem_ctx,
+                       struct tevent_context *ev,
+                       struct kcm_op_ctx *op_ctx);
+typedef errno_t
+(*kcm_srv_recv_method)(struct tevent_req *req,
+                       uint32_t *_op_ret);
+
+struct kcm_op {
+    const char *name;
+    kcm_srv_send_method fn_send;
+    kcm_srv_recv_method fn_recv;
+};
+
+struct kcm_cmd_state {
+    struct kcm_op *op;
+
+    struct kcm_op_ctx *op_ctx;
+    struct sss_iobuf *reply;
+
+    uint32_t op_ret;
+};
+
+static void kcm_cmd_done(struct tevent_req *subreq);
+
+struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx,
+                                struct tevent_context *ev,
+                                struct kcm_resp_ctx *kcm_data,
+                                struct cli_creds *client,
+                                struct kcm_data *input,
+                                struct kcm_op *op)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_cmd_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_cmd_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op = op;
+
+    if (op == NULL) {
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "KCM operation %s\n", op->name);
+    DEBUG(SSSDBG_TRACE_LIBS, "%zu bytes on KCM input\n", input->length);
+
+    state->reply = sss_iobuf_init_empty(state,
+                                        KCM_REPLY_MAX,
+                                        KCM_REPLY_MAX);
+    if (state->reply == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    if (op->fn_send == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "KCM op %s has no handler\n", kcm_opt_name(op));
+        ret = ERR_KCM_OP_NOT_IMPLEMENTED;
+        goto immediate;
+    }
+
+    /* Allocating op_ctx on the heap makes it possible for operations to use
+     * op_ctx as their temporary context and avoid tmp_ctx altogether
+     */
+    state->op_ctx = talloc_zero(state, struct kcm_op_ctx);
+    if (state->op_ctx == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    state->op_ctx->kcm_data = kcm_data;
+    state->op_ctx->client = client;
+
+    state->op_ctx->input = sss_iobuf_init_readonly(state->op_ctx,
+                                                   input->data,
+                                                   input->length);
+    if (state->op_ctx->input == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    /*
+     * The internal operation returns the opcode and the buffer separately.
+     * The KCM server reply to the client also always contains zero if the
+     * operation ran to completion, both are uint32_t.
+     * FIXME:
+     * Alternatively, we could extend iobuf API so that we can just pass
+     * the reply's buffer+sizeof(2*uint32_t) and avoid the useless allocations
+     */
+    state->op_ctx->reply = sss_iobuf_init_empty(
+                                        state,
+                                        KCM_REPLY_MAX - 2*sizeof(uint32_t),
+                                        KCM_REPLY_MAX - 2*sizeof(uint32_t));
+    if (state->reply == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    subreq = op->fn_send(state, ev, state->op_ctx);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_cmd_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_cmd_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct kcm_cmd_state *state = tevent_req_data(req, struct kcm_cmd_state);
+    errno_t ret;
+    krb5_error_code kerr;
+
+    ret = state->op->fn_recv(subreq, &state->op_ret);
+    talloc_free(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "op receive function failed [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          "KCM operation %s returned [%d]: %s\n",
+          kcm_opt_name(state->op), state->op_ret, sss_strerror(state->op_ret));
+
+    kerr = sss2krb5_error(state->op_ret);
+
+    /* The first four bytes of the reply is the operation status code */
+    ret = sss_iobuf_write_uint32(state->reply, htobe32(kerr));
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = sss_iobuf_write_len(state->reply,
+                              sss_iobuf_get_data(state->op_ctx->reply),
+                              sss_iobuf_get_len(state->op_ctx->reply));
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t kcm_cmd_recv(TALLOC_CTX *mem_ctx,
+                     struct tevent_req *req,
+                     struct sss_iobuf **_reply)
+{
+    struct kcm_cmd_state *state = NULL;
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    state = tevent_req_data(req, struct kcm_cmd_state);
+
+    *_reply = talloc_steal(mem_ctx, state->reply);
+    return EOK;
+}
+
+/* ======= KCM operations ======= */
+
+/* Operations that don't return any extra information except for the op_ret
+ * can use this macro in the _recv function to avoid code duplication
+ */
+#define KCM_OP_RET_FROM_TYPE(req, state_type, _op_ret_out) do {    \
+    state_type *state = NULL;                                    \
+    state = tevent_req_data(req, state_type);                    \
+    TEVENT_REQ_RETURN_ON_ERROR(req);                             \
+    *_op_ret_out = state->op_ret;                                \
+    return EOK;                                                  \
+} while(0);
+
+struct kcm_op_common_state {
+    uint32_t op_ret;
+    struct kcm_op_ctx *op_ctx;
+    struct tevent_context *ev;
+};
+
+static errno_t kcm_op_common_recv(struct tevent_req *req,
+                                  uint32_t *_op_ret)
+{
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_op_ret = state->op_ret;
+    return EOK;
+}
+
+/* () -> (name) */
+static void kcm_op_gen_new_done(struct tevent_req *subreq);
+
+static struct tevent_req *kcm_op_gen_new_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    subreq = kcm_ccdb_nextid_send(state, ev,
+                                  op_ctx->kcm_data->db,
+                                  op_ctx->client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_gen_new_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_gen_new_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    char *newid;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+
+    ret = kcm_ccdb_nextid_recv(subreq, state, &newid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot generate a new ID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Generated a new ID %s\n", newid);
+
+    ret = sss_iobuf_write_stringz(state->op_ctx->reply, newid);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot write generated ID %d: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* (princ) -> () */
+struct kcm_op_initialize_state {
+    uint32_t op_ret;
+    struct kcm_op_ctx *op_ctx;
+    struct tevent_context *ev;
+
+    struct kcm_ccache *new_cc;
+    const char *name;
+    krb5_principal princ;
+};
+
+static void kcm_op_initialize_got_byname(struct tevent_req *subreq);
+static void kcm_op_initialize_cc_create_done(struct tevent_req *subreq);
+static void kcm_op_initialize_cc_delete_done(struct tevent_req *subreq);
+static void kcm_op_initialize_create_step(struct tevent_req *req);
+static void kcm_op_initialize_got_default(struct tevent_req *subreq);
+static void kcm_op_initialize_set_default_done(struct tevent_req *subreq);
+
+static struct tevent_req *kcm_op_initialize_send(TALLOC_CTX *mem_ctx,
+                                                 struct tevent_context *ev,
+                                                 struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_initialize_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_initialize_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+    state->ev = ev;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &state->name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot read input name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Initializing ccache %s\n", state->name);
+
+    ret = kcm_check_name(state->name, op_ctx->client);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Name %s is malformed [%d]: %s\n",
+              state->name, ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    ret = sss_krb5_unmarshal_princ(op_ctx, op_ctx->input, &state->princ);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot unmarshal principal [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    subreq = kcm_ccdb_getbyname_send(state, ev,
+                                     op_ctx->kcm_data->db,
+                                     op_ctx->client,
+                                     state->name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_initialize_got_byname, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_initialize_got_byname(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_initialize_state *state = tevent_req_data(req,
+                                            struct kcm_op_initialize_state);
+    bool ok;
+    uuid_t uuid;
+
+    ret = kcm_ccdb_getbyname_recv(subreq, state, &state->new_cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccache by name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->new_cc != NULL) {
+        ok = kcm_cc_access(state->new_cc, state->op_ctx->client);
+        if (!ok) {
+            state->op_ret = EACCES;
+            tevent_req_done(req);
+            return;
+        }
+
+        ret = kcm_cc_get_uuid(state->new_cc, uuid);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get new ccache UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+            return;
+        }
+
+        /* Nuke any previous cache and its contents during initialization */
+        subreq = kcm_ccdb_delete_cc_send(state,
+                                         state->ev,
+                                         state->op_ctx->kcm_data->db,
+                                         state->op_ctx->client,
+                                         uuid);
+        if (subreq == NULL) {
+            tevent_req_error(req, ret);
+            return;
+        }
+        tevent_req_set_callback(subreq, kcm_op_initialize_cc_delete_done, req);
+        return;
+    }
+
+    kcm_op_initialize_create_step(req);
+}
+
+static void kcm_op_initialize_cc_delete_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    errno_t ret;
+
+    ret = kcm_ccdb_delete_cc_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot delete ccache from the db %d: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    kcm_op_initialize_create_step(req);
+}
+
+static void kcm_op_initialize_create_step(struct tevent_req *req)
+{
+    struct tevent_req *subreq;
+    struct kcm_op_initialize_state *state = tevent_req_data(req,
+                                            struct kcm_op_initialize_state);
+    errno_t ret;
+
+    ret = kcm_cc_new(state->op_ctx,
+                     state->op_ctx->kcm_data->k5c,
+                     state->op_ctx->client,
+                     state->name,
+                     state->princ,
+                     &state->new_cc);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot create new ccache %d: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = kcm_ccdb_create_cc_send(state,
+                                     state->ev,
+                                     state->op_ctx->kcm_data->db,
+                                     state->op_ctx->client,
+                                     state->new_cc);
+    if (subreq == NULL) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_initialize_cc_create_done, req);
+}
+
+static void kcm_op_initialize_cc_create_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_initialize_state *state = tevent_req_data(req,
+                                            struct kcm_op_initialize_state);
+    errno_t ret;
+
+    ret = kcm_ccdb_create_cc_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot add ccache to db %d: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    /* If there was no previous default ccache, set this one as default */
+    subreq = kcm_ccdb_get_default_send(state, state->ev,
+                                       state->op_ctx->kcm_data->db,
+                                       state->op_ctx->client);
+    if (subreq == NULL) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_initialize_got_default, req);
+}
+
+static void kcm_op_initialize_got_default(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_initialize_state *state = tevent_req_data(req,
+                                            struct kcm_op_initialize_state);
+    errno_t ret;
+    uuid_t dfl_uuid;
+    uuid_t old_dfl_uuid;
+
+    ret = kcm_ccdb_get_default_recv(subreq, &old_dfl_uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get default ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (uuid_is_null(old_dfl_uuid) == false) {
+        /* If there was a previous default ccache, switch to the initialized
+         * one by default
+         */
+        ret = kcm_cc_get_uuid(state->new_cc, dfl_uuid);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get new ccache UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+            return;
+        }
+
+        subreq = kcm_ccdb_set_default_send(state,
+                                           state->ev,
+                                           state->op_ctx->kcm_data->db,
+                                           state->op_ctx->client,
+                                           dfl_uuid);
+        if (subreq == NULL) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+        tevent_req_set_callback(subreq, kcm_op_initialize_set_default_done, req);
+        return;
+    }
+
+    /* ENOENT, done */
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+static void kcm_op_initialize_set_default_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_initialize_state *state = tevent_req_data(req,
+                                            struct kcm_op_initialize_state);
+    errno_t ret;
+
+    ret = kcm_ccdb_set_default_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot set default ccache %d: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+static errno_t kcm_op_initialize_recv(struct tevent_req *req,
+                                      uint32_t *_op_ret)
+{
+    KCM_OP_RET_FROM_TYPE(req, struct kcm_op_initialize_state, _op_ret);
+}
+
+/* (name) -> () */
+static void kcm_op_destroy_getbyname_done(struct tevent_req *subreq);
+static void kcm_op_destroy_delete_done(struct tevent_req *subreq);
+
+static struct tevent_req *kcm_op_destroy_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+    const char *name;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+    state->ev = ev;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot unmarshall input name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Destroying credentials of %s\n", name);
+
+    subreq = kcm_ccdb_uuid_by_name_send(state, ev,
+                                        op_ctx->kcm_data->db,
+                                        op_ctx->client,
+                                        name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_destroy_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_destroy_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+    uuid_t uuid;
+
+    ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get matching ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = kcm_ccdb_delete_cc_send(state,
+                                     state->ev,
+                                     state->op_ctx->kcm_data->db,
+                                     state->op_ctx->client,
+                                     uuid);
+    if (subreq == NULL) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_destroy_delete_done, req);
+}
+
+static void kcm_op_destroy_delete_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+
+    ret = kcm_ccdb_delete_cc_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot delete ccache from the db [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* (name, cred) -> () */
+struct kcm_op_store_state {
+    uint32_t op_ret;
+    struct kcm_op_ctx *op_ctx;
+    struct tevent_context *ev;
+
+    struct sss_iobuf *cred_blob;
+};
+
+static void kcm_op_store_getbyname_done(struct tevent_req *subreq);
+static void kcm_op_store_done(struct tevent_req *subreq);
+
+static struct tevent_req *kcm_op_store_send(TALLOC_CTX *mem_ctx,
+                                            struct tevent_context *ev,
+                                            struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_store_state *state = NULL;
+    errno_t ret;
+    const char *name;
+    size_t creds_len;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_store_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+    state->ev = ev;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot unmarshall input name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Storing credentials for %s\n", name);
+
+    creds_len = sss_iobuf_get_size(op_ctx->input) - strlen(name) -1;
+    if (creds_len > KCM_REPLY_MAX) {
+        /* Protects against underflows and in general adds sanity */
+        ret = E2BIG;
+        goto immediate;
+    }
+
+    state->cred_blob = sss_iobuf_init_empty(state,
+                                            creds_len,
+                                            creds_len);
+    if (state->cred_blob == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    ret = sss_iobuf_read(op_ctx->input,
+                         creds_len,
+                         sss_iobuf_get_data(state->cred_blob),
+                         NULL);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot unmarshall input cred blob [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    subreq = kcm_ccdb_uuid_by_name_send(state, ev,
+                                        op_ctx->kcm_data->db,
+                                        op_ctx->client,
+                                        name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_store_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_store_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_store_state *state = tevent_req_data(req,
+                                                struct kcm_op_store_state);
+    uuid_t uuid;
+
+    ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccache by name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = kcm_ccdb_store_cred_blob_send(state, state->ev,
+                                           state->op_ctx->kcm_data->db,
+                                           state->op_ctx->client,
+                                           uuid,
+                                           state->cred_blob);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_store_done, req);
+}
+
+static void kcm_op_store_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_store_state *state = tevent_req_data(req,
+                                                struct kcm_op_store_state);
+
+    ret = kcm_ccdb_store_cred_blob_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot store credentials [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+static errno_t kcm_op_store_recv(struct tevent_req *req,
+                                 uint32_t *_op_ret)
+{
+    KCM_OP_RET_FROM_TYPE(req, struct kcm_op_store_state, _op_ret);
+}
+
+/* (name) -> (princ) */
+static void kcm_op_get_principal_getbyname_done(struct tevent_req *subreq);
+
+static struct tevent_req *kcm_op_get_principal_send(TALLOC_CTX *mem_ctx,
+                                                    struct tevent_context *ev,
+                                                    struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+    const char *name;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        goto immediate;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Requested principal %s\n", name);
+
+    subreq = kcm_ccdb_getbyname_send(state, ev,
+                                     op_ctx->kcm_data->db,
+                                     op_ctx->client,
+                                     name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_principal_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_get_principal_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct kcm_ccache *cc;
+    krb5_principal princ;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+
+    ret = kcm_ccdb_getbyname_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccache by name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (cc == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that name\n");
+        state->op_ret = ERR_NO_MATCHING_CREDS;
+        tevent_req_done(req);
+        return;
+    }
+
+    /* Marshall the principal to the reply */
+    princ = kcm_cc_get_client_principal(cc);
+    if (princ == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Credentials with no principal?\n");
+        tevent_req_error(req, EIO);
+        return;
+    }
+
+    ret = sss_krb5_marshal_princ(princ, state->op_ctx->reply);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot marshall principal [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* (name) -> (uuid, ...) */
+static void kcm_op_get_cred_uuid_getbyname_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+kcm_op_get_cred_uuid_list_send(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+    const char *name;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        goto immediate;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Returning UUID list for %s\n", name);
+
+    subreq = kcm_ccdb_getbyname_send(state, ev,
+                                     op_ctx->kcm_data->db,
+                                     op_ctx->client,
+                                     name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_cred_uuid_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_get_cred_uuid_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct kcm_ccache *cc;
+    struct kcm_cred *crd;
+    uuid_t uuid;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+
+    ret = kcm_ccdb_getbyname_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccache by name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (cc == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that UUID\n");
+        state->op_ret = ERR_NO_CREDS;
+        tevent_req_done(req);
+        return;
+    }
+
+    for (crd = kcm_cc_get_cred(cc);
+         crd != NULL;
+         crd = kcm_cc_next_cred(crd)) {
+        ret = kcm_cred_get_uuid(crd, uuid);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE, "Credential has no UUID, skipping\n");
+            continue;
+        }
+
+        kcm_debug_uuid(uuid);
+
+        ret = sss_iobuf_write_len(state->op_ctx->reply,
+                                  uuid, UUID_BYTES);
+        if (ret != EOK) {
+            char uuid_errbuf[UUID_STR_SIZE];
+            uuid_parse(uuid_errbuf, uuid);
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "Cannot marshall UUID %s [%d]: %s\n",
+                  uuid_errbuf, ret, sss_strerror(ret));
+            continue;
+        }
+    }
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* (name, uuid) -> (cred) */
+static void kcm_op_get_cred_by_uuid_getbyname_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+kcm_op_get_cred_by_uuid_send(TALLOC_CTX *mem_ctx,
+                             struct tevent_context *ev,
+                             struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+    const char *name;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        goto immediate;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Returning creds by UUID for %s\n", name);
+
+    subreq = kcm_ccdb_getbyname_send(state, ev,
+                                     op_ctx->kcm_data->db,
+                                     op_ctx->client,
+                                     name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_cred_by_uuid_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_get_cred_by_uuid_getbyname_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+    errno_t ret;
+    struct kcm_ccache *cc;
+    struct kcm_cred *crd;
+    uuid_t uuid_in;
+    uuid_t uuid;
+    struct sss_iobuf *cred_blob;
+
+    ret = kcm_ccdb_getbyname_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccache by name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (cc == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that name\n");
+        state->op_ret = ERR_NO_MATCHING_CREDS;
+        tevent_req_done(req);
+        return;
+    }
+
+    ret = sss_iobuf_read_len(state->op_ctx->input,
+                             UUID_BYTES, uuid_in);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot read input UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    for (crd = kcm_cc_get_cred(cc);
+         crd != NULL;
+         crd = kcm_cc_next_cred(crd)) {
+        ret = kcm_cred_get_uuid(crd, uuid);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "Cannot get UUID from creds, skipping\n");
+            continue;
+        }
+
+        if (uuid_compare(uuid, uuid_in) == 0) {
+            break;
+        }
+        kcm_debug_uuid(uuid);
+    }
+
+    if (crd == NULL) {
+        state->op_ret = ERR_KCM_CC_END;
+        DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that UUID\n");
+        tevent_req_done(req);
+        return;
+    }
+
+    cred_blob = kcm_cred_get_creds(crd);
+    if (cred_blob == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Credentials lack the creds blob\n");
+        state->op_ret = ERR_NO_CREDS;
+        tevent_req_done(req);
+        return;
+    }
+
+    ret = sss_iobuf_write_len(state->op_ctx->reply,
+                              sss_iobuf_get_data(cred_blob),
+                              sss_iobuf_get_size(cred_blob));
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot write ccache blob [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* (name, flags, credtag) -> () */
+/* FIXME */
+static struct tevent_req *
+kcm_op_remove_cred_send(TALLOC_CTX *mem_ctx,
+                        struct tevent_context *ev,
+                        struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct kcm_op_common_state *state = NULL;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    state->op_ret = ERR_KCM_OP_NOT_IMPLEMENTED;
+    tevent_req_post(req, ev);
+    tevent_req_done(req);
+    return req;
+}
+
+/* () -> (uuid, ...) */
+static void kcm_op_get_cache_uuid_list_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+kcm_op_get_cache_uuid_list_send(TALLOC_CTX *mem_ctx,
+                                struct tevent_context *ev,
+                                struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Returning full UUID list\n");
+
+    subreq = kcm_ccdb_list_send(state, ev,
+                                op_ctx->kcm_data->db,
+                                op_ctx->client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_cache_uuid_list_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_get_cache_uuid_list_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+    errno_t ret;
+    uuid_t *uuid_list;
+
+    ret = kcm_ccdb_list_recv(subreq, state, &uuid_list);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot list the ccache DB [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (uuid_list == NULL || uuid_list[0] == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Nothing to list\n");
+        state->op_ret = ERR_NO_MATCHING_CREDS;
+        tevent_req_done(req);
+        return;
+    }
+
+    for (int i = 0;
+         uuid_is_null(uuid_list[i]) == false;
+         i++) {
+        kcm_debug_uuid(uuid_list[i]);
+
+        ret = sss_iobuf_write_len(state->op_ctx->reply,
+                                  uuid_list[i],
+                                  UUID_BYTES);
+        if (ret != EOK) {
+            char uuid_errbuf[UUID_STR_SIZE];
+            uuid_parse(uuid_errbuf, uuid_list[i]);
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "Cannot marshall UUID %s [%d]: %s\n",
+                  uuid_errbuf, ret, sss_strerror(ret));
+            tevent_req_done(req);
+            return;
+        }
+    }
+
+    tevent_req_done(req);
+}
+
+/* (uuid) -> (name) */
+static void kcm_op_get_cache_by_uuid_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+kcm_op_get_cache_by_uuid_send(TALLOC_CTX *mem_ctx,
+                              struct tevent_context *ev,
+                              struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+    uuid_t uuid_in;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Retrieving cache by UUID\n");
+
+    ret = sss_iobuf_read_len(op_ctx->input,
+                             UUID_BYTES, uuid_in);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot read input UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+    kcm_debug_uuid(uuid_in);
+
+    subreq = kcm_ccdb_getbyuuid_send(state, ev,
+                                     op_ctx->kcm_data->db,
+                                     op_ctx->client,
+                                     uuid_in);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_cache_by_uuid_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_get_cache_by_uuid_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct kcm_ccache *cc;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+    const char *name;
+
+    ret = kcm_ccdb_getbyuuid_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccahe by UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (cc == NULL) {
+        state->op_ret = ERR_KCM_CC_END;
+        tevent_req_done(req);
+        return;
+    }
+
+    name = kcm_cc_get_name(cc);
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Found %s by UUID\n", name);
+
+    ret = sss_iobuf_write_stringz(state->op_ctx->reply,
+                                  name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot write output name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* () -> (name) */
+struct kcm_op_get_default_ccache_state {
+    uint32_t op_ret;
+    struct kcm_op_ctx *op_ctx;
+    struct tevent_context *ev;
+
+    const char *name;
+};
+
+static void kcm_op_get_get_default_done(struct tevent_req *subreq);
+static void kcm_op_get_default_ccache_byuuid_done(struct tevent_req *subreq);
+static void kcm_op_get_default_ccache_list_done(struct tevent_req *subreq);
+static errno_t
+kcm_op_get_default_ccache_reply_step(struct kcm_op_get_default_ccache_state *state);
+
+static struct tevent_req *
+kcm_op_get_default_ccache_send(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_get_default_ccache_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state,
+                            struct kcm_op_get_default_ccache_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+    state->ev = ev;
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Getting client's default ccache\n");
+
+    subreq = kcm_ccdb_get_default_send(state, ev,
+                                       state->op_ctx->kcm_data->db,
+                                       state->op_ctx->client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_get_default_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_get_get_default_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct kcm_op_get_default_ccache_state *state = tevent_req_data(req,
+                                    struct kcm_op_get_default_ccache_state);
+    errno_t ret;
+    uuid_t dfl_uuid;
+
+    ret = kcm_ccdb_get_default_recv(subreq, &dfl_uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get default ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (uuid_is_null(dfl_uuid) == true) {
+        /* No cache marked as default -- get an existing ccache for ID
+         * and treat the default as simply the first one
+         */
+        subreq = kcm_ccdb_list_send(state, state->ev,
+                                    state->op_ctx->kcm_data->db,
+                                    state->op_ctx->client);
+        if (subreq == NULL) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+        tevent_req_set_callback(subreq, kcm_op_get_default_ccache_list_done, req);
+        return;
+    }
+
+    /* Existing default */
+    subreq = kcm_ccdb_name_by_uuid_send(state,
+                                        state->ev,
+                                        state->op_ctx->kcm_data->db,
+                                        state->op_ctx->client,
+                                        dfl_uuid);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_default_ccache_byuuid_done, req);
+    return;
+}
+
+static void kcm_op_get_default_ccache_byuuid_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct kcm_op_get_default_ccache_state *state = tevent_req_data(req,
+                                    struct kcm_op_get_default_ccache_state);
+    errno_t ret;
+
+    ret = kcm_ccdb_name_by_uuid_recv(subreq, state, &state->name);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccahe by UUID [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = kcm_op_get_default_ccache_reply_step(state);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+static void kcm_op_get_default_ccache_list_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct kcm_op_get_default_ccache_state *state = tevent_req_data(req,
+                                    struct kcm_op_get_default_ccache_state);
+    errno_t ret;
+    uuid_t *uuid_list;
+
+    ret = kcm_ccdb_list_recv(subreq, state, &uuid_list);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot list ccaches [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (uuid_list == NULL || uuid_is_null(uuid_list[0])) {
+        /* No cache at all, just send back a reply */
+        ret = kcm_op_get_default_ccache_reply_step(state);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+
+        tevent_req_done(req);
+        return;
+    }
+
+    /* Otherwise resolve the first cache and use it as a default */
+    subreq = kcm_ccdb_name_by_uuid_send(state,
+                                        state->ev,
+                                        state->op_ctx->kcm_data->db,
+                                        state->op_ctx->client,
+                                        uuid_list[0]);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_default_ccache_byuuid_done, req);
+    return;
+}
+
+static errno_t
+kcm_op_get_default_ccache_reply_step(struct kcm_op_get_default_ccache_state *state)
+{
+    errno_t ret;
+
+    if (state->name == NULL) {
+        state->name = talloc_asprintf(state,
+                                      "%"SPRIuid,
+                                      cli_creds_get_uid(state->op_ctx->client));
+        if (state->name == NULL) {
+            return ENOMEM;
+        }
+    }
+    DEBUG(SSSDBG_TRACE_INTERNAL, "The default ccache is %s\n", state->name);
+
+    ret = sss_iobuf_write_stringz(state->op_ctx->reply, state->name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot write output name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        return ret;
+    }
+
+    return EOK;
+}
+
+static errno_t kcm_op_get_default_ccache_recv(struct tevent_req *req,
+                                              uint32_t *_op_ret)
+{
+    KCM_OP_RET_FROM_TYPE(req, struct kcm_op_get_default_ccache_state, _op_ret);
+}
+
+/* (name) -> () */
+static void kcm_op_set_default_ccache_getbyname_done(struct tevent_req *subreq);
+static void kcm_op_set_default_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+kcm_op_set_default_ccache_send(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+    const char *name;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+    state->ev = ev;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot read input name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Setting default ccache %s\n", name);
+
+    subreq = kcm_ccdb_uuid_by_name_send(state, ev,
+                                        op_ctx->kcm_data->db,
+                                        op_ctx->client,
+                                        name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_set_default_ccache_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_set_default_ccache_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+    uuid_t dfl_uuid;
+
+    ret = kcm_ccdb_uuid_by_name_recv(subreq, state, dfl_uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get ccache by name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = kcm_ccdb_set_default_send(state,
+                                       state->ev,
+                                       state->op_ctx->kcm_data->db,
+                                       state->op_ctx->client,
+                                       dfl_uuid);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_set_default_done, req);
+    return;
+}
+
+static void kcm_op_set_default_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+
+    ret = kcm_ccdb_set_default_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot set default ccache %d: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* (name) -> (offset) */
+static void kcm_op_get_kdc_offset_getbyname_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+kcm_op_get_kdc_offset_send(TALLOC_CTX *mem_ctx,
+                           struct tevent_context *ev,
+                           struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_common_state *state = NULL;
+    errno_t ret;
+    const char *name;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot read input name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Requested offset for principal %s\n", name);
+
+    subreq = kcm_ccdb_getbyname_send(state, ev,
+                                     op_ctx->kcm_data->db,
+                                     op_ctx->client,
+                                     name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_get_kdc_offset_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_get_kdc_offset_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct kcm_ccache *cc;
+    int32_t offset;
+    int32_t offset_be;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_common_state *state = tevent_req_data(req,
+                                                struct kcm_op_common_state);
+
+    ret = kcm_ccdb_getbyname_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get matching ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (cc == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "No matching credentials\n");
+        state->op_ret = ERR_NO_MATCHING_CREDS;
+        tevent_req_done(req);
+        return;
+    }
+
+    offset = kcm_cc_get_offset(cc);
+    DEBUG(SSSDBG_TRACE_LIBS, "KDC offset: %"PRIu32"\n", offset);
+
+    offset_be = htobe32(offset);
+    ret = sss_iobuf_write_int32(state->op_ctx->reply, offset_be);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot write KDC offset [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+/* (name, offset) -> () */
+/* () -> (name) */
+struct kcm_op_set_kdc_offset_state {
+    uint32_t op_ret;
+    struct kcm_op_ctx *op_ctx;
+    struct tevent_context *ev;
+};
+
+static void kcm_op_set_kdc_offset_getbyname_done(struct tevent_req *subreq);
+static void kcm_op_set_kdc_offset_mod_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+kcm_op_set_kdc_offset_send(TALLOC_CTX *mem_ctx,
+                           struct tevent_context *ev,
+                           struct kcm_op_ctx *op_ctx)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct kcm_op_set_kdc_offset_state *state = NULL;
+    errno_t ret;
+    const char *name;
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_set_kdc_offset_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->op_ctx = op_ctx;
+    state->ev = ev;
+
+    ret = sss_iobuf_read_stringz(op_ctx->input, &name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot read input name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto immediate;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Setting offset for principal %s\n", name);
+
+    subreq = kcm_ccdb_uuid_by_name_send(state, ev,
+                                        op_ctx->kcm_data->db,
+                                        op_ctx->client,
+                                        name);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, kcm_op_set_kdc_offset_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void kcm_op_set_kdc_offset_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct kcm_mod_ctx *mod_ctx;
+    int32_t offset_be;
+    uuid_t uuid;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_set_kdc_offset_state *state = tevent_req_data(req,
+                                                struct kcm_op_set_kdc_offset_state);
+
+    ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get matching ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = sss_iobuf_read_int32(state->op_ctx->input, &offset_be);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot read KDC offset [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    mod_ctx = talloc(state, struct kcm_mod_ctx);
+    if (mod_ctx == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    kcm_mod_ctx_clear(mod_ctx);
+    mod_ctx->kdc_offset = be32toh(offset_be);
+
+    subreq = kcm_ccdb_mod_cc_send(state,
+                                  state->ev,
+                                  state->op_ctx->kcm_data->db,
+                                  state->op_ctx->client,
+                                  uuid,
+                                  mod_ctx);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_op_set_kdc_offset_mod_done, req);
+}
+
+static void kcm_op_set_kdc_offset_mod_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct kcm_op_set_kdc_offset_state *state = tevent_req_data(req,
+                                                struct kcm_op_set_kdc_offset_state);
+
+    ret = kcm_ccdb_mod_cc_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot modify ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->op_ret = EOK;
+    tevent_req_done(req);
+}
+
+static errno_t kcm_op_set_kdc_offset_recv(struct tevent_req *req,
+                                          uint32_t *_op_ret)
+{
+    KCM_OP_RET_FROM_TYPE(req, struct kcm_op_set_kdc_offset_state, _op_ret);
+}
+
+static struct kcm_op kcm_optable[] = {
+    { "NOOP",                NULL, NULL },
+    { "GET_NAME",            NULL, NULL },
+    { "RESOLVE",             NULL, NULL },
+    { "GEN_NEW",             kcm_op_gen_new_send, NULL },
+    { "INITIALIZE",          kcm_op_initialize_send, kcm_op_initialize_recv },
+    { "DESTROY",             kcm_op_destroy_send, NULL },
+    { "STORE",               kcm_op_store_send, kcm_op_store_recv },
+    { "RETRIEVE",            NULL, NULL },
+    { "GET_PRINCIPAL",       kcm_op_get_principal_send, NULL },
+    { "GET_CRED_UUID_LIST",  kcm_op_get_cred_uuid_list_send, NULL },
+    { "GET_CRED_BY_UUID",    kcm_op_get_cred_by_uuid_send, NULL },
+    { "REMOVE_CRED",         kcm_op_remove_cred_send, NULL },
+    { "SET_FLAGS",           NULL, NULL },
+    { "CHOWN",               NULL, NULL },
+    { "CHMOD",               NULL, NULL },
+    { "GET_INITIAL_TICKET",  NULL, NULL },
+    { "GET_TICKET",          NULL, NULL },
+    { "MOVE_CACHE",          NULL, NULL },
+    { "GET_CACHE_UUID_LIST", kcm_op_get_cache_uuid_list_send, NULL },
+    { "GET_CACHE_BY_UUID",   kcm_op_get_cache_by_uuid_send, NULL },
+    { "GET_DEFAULT_CACHE",   kcm_op_get_default_ccache_send, kcm_op_get_default_ccache_recv },
+    { "SET_DEFAULT_CACHE",   kcm_op_set_default_ccache_send, NULL },
+    { "GET_KDC_OFFSET",      kcm_op_get_kdc_offset_send, NULL },
+    { "SET_KDC_OFFSET",      kcm_op_set_kdc_offset_send, kcm_op_set_kdc_offset_recv },
+    { "ADD_NTLM_CRED",       NULL, NULL },
+    { "HAVE_NTLM_CRED",      NULL, NULL },
+    { "DEL_NTLM_CRED",       NULL, NULL },
+    { "DO_NTLM_AUTH",        NULL, NULL },
+    { "GET_NTLM_USER_LIST",  NULL, NULL },
+
+    { NULL, NULL, NULL }
+};
+
+struct kcm_op *kcm_get_opt(uint16_t opcode)
+{
+    struct kcm_op *op;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL,
+          "The client requested operation %"PRIu16"\n", opcode);
+
+    if (opcode >= KCM_OP_SENTINEL) {
+        return NULL;
+    }
+
+    op = &kcm_optable[opcode];
+    if (op->fn_recv == NULL) {
+        op->fn_recv = kcm_op_common_recv;
+    }
+    return op;
+}
+
+const char *kcm_opt_name(struct kcm_op *op)
+{
+    if (op == NULL || op->name == NULL) {
+        return "Unknown operation";
+    }
+
+    return op->name;
+}
diff --git a/src/responder/kcm/kcmsrv_ops.h b/src/responder/kcm/kcmsrv_ops.h
new file mode 100644
index 0000000..8e6feaf
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ops.h
@@ -0,0 +1,45 @@
+/*
+   SSSD
+
+   KCM Server - private header file
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __KCMSRV_OPS_H__
+#define __KCMSRV_OPS_H__
+
+#include "config.h"
+
+#include <sys/types.h>
+#include "util/sss_iobuf.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+
+struct kcm_op;
+struct kcm_op *kcm_get_opt(uint16_t opcode);
+const char *kcm_opt_name(struct kcm_op *op);
+
+struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx,
+                                struct tevent_context *ev,
+                                struct kcm_resp_ctx *kcm_data,
+                                struct cli_creds *client,
+                                struct kcm_data *input,
+                                struct kcm_op *op);
+errno_t kcm_cmd_recv(TALLOC_CTX *mem_ctx,
+                     struct tevent_req *req,
+                     struct sss_iobuf **_reply);
+
+#endif /* __KCMSRV_OPS_H__ */

From 8d546e736a783b1ed3d7fa75e535374e419a4815 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 8 Mar 2017 17:46:09 +0100
Subject: [PATCH 12/21] MAN: Add a manual page for sssd-kcm

---
 contrib/sssd.spec.in   |   1 +
 src/man/Makefile.am    |   9 ++-
 src/man/po/po4a.cfg    |   1 +
 src/man/sssd-kcm.8.xml | 193 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 203 insertions(+), 1 deletion(-)
 create mode 100644 src/man/sssd-kcm.8.xml

diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index c63e45c..0cd139b 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -1174,6 +1174,7 @@ done
 %config(noreplace) %{_sysconfdir}/krb5.conf.d/kcm_default_ccache
 %{_unitdir}/sssd-kcm.socket
 %{_unitdir}/sssd-kcm.service
+%{_mandir}/man8/sssd-kcm.8*
 %endif
 
 %pre common
diff --git a/src/man/Makefile.am b/src/man/Makefile.am
index 215ce69..d129fa5 100644
--- a/src/man/Makefile.am
+++ b/src/man/Makefile.am
@@ -27,6 +27,9 @@ endif
 if BUILD_SECRETS
 SEC_CONDS = ;with_secrets
 endif
+if BUILD_SECRETS
+KCM_CONDS = ;with_kcm
+endif
 if GPO_DEFAULT_ENFORCING
 GPO_CONDS = ;gpo_default_enforcing
 else
@@ -40,7 +43,7 @@ FILES_CONDS = ;enable_files_domain
 else
 FILES_CONDS = ;no_enable_files_domain
 endif
-CONDS = with_false$(SUDO_CONDS)$(AUTOFS_CONDS)$(SSH_CONDS)$(PAC_RESPONDER_CONDS)$(IFP_CONDS)$(GPO_CONDS)$(SEC_CONDS)$(SYSTEMD_CONDS)$(FILES_CONDS)
+CONDS = with_false$(SUDO_CONDS)$(AUTOFS_CONDS)$(SSH_CONDS)$(PAC_RESPONDER_CONDS)$(IFP_CONDS)$(GPO_CONDS)$(SEC_CONDS)$(SYSTEMD_CONDS)$(FILES_CONDS)$(KCM_CONDS)
 
 
 #Special Rules:
@@ -85,6 +88,10 @@ if BUILD_SECRETS
 man_MANS += sssd-secrets.5
 endif
 
+if BUILD_KCM
+man_MANS += sssd-kcm.8
+endif
+
 if BUILD_NFS_IDMAP
 man_MANS += sss_rpcidmapd.5
 endif
diff --git a/src/man/po/po4a.cfg b/src/man/po/po4a.cfg
index ffcf9a2..d719b73 100644
--- a/src/man/po/po4a.cfg
+++ b/src/man/po/po4a.cfg
@@ -30,6 +30,7 @@
 [type:docbook] sssctl.8.xml $lang:$(builddir)/$lang/sssctl.8.xml
 [type:docbook] sssd-files.5.xml $lang:$(builddir)/$lang/sssd-files.5.xml
 [type:docbook] sssd-secrets.5.xml $lang:$(builddir)/$lang/sssd-secrets.5.xml
+[type:docbook] sssd-kcm.8.xml $lang:$(builddir)/$lang/sssd-kcm.8.xml
 [type:docbook] include/service_discovery.xml $lang:$(builddir)/$lang/include/service_discovery.xml opt:"-k 0"
 [type:docbook] include/upstream.xml $lang:$(builddir)/$lang/include/upstream.xml opt:"-k 0"
 [type:docbook] include/failover.xml $lang:$(builddir)/$lang/include/failover.xml opt:"-k 0"
diff --git a/src/man/sssd-kcm.8.xml b/src/man/sssd-kcm.8.xml
new file mode 100644
index 0000000..5dc9383
--- /dev/null
+++ b/src/man/sssd-kcm.8.xml
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+    <refmeta>
+        <refentrytitle>sssd-kcm</refentrytitle>
+        <manvolnum>8</manvolnum>
+        <refmiscinfo class="manual">File Formats and Conventions</refmiscinfo>
+    </refmeta>
+
+    <refnamediv id='name'>
+        <refname>sssd-kcm</refname>
+        <refpurpose>SSSD Kerberos Cache Manager</refpurpose>
+    </refnamediv>
+
+    <refsect1 id='description'>
+        <title>DESCRIPTION</title>
+        <para>
+            This manual page describes the configuration of the SSSD Kerberos
+            Cache Manager (KCM). KCM is a process that stores, tracks and
+            manages Kerberos credential caches. It originates in the Heimdal
+            Kerberos project, although the MIT Kerberos library also provides
+            client side (more details on that below) support for the KCM
+            credential cache.
+        </para>
+        <para>
+            In a setup where Kerberos caches are managed by KCM, the
+            Kerberos library (typically used through an application, like
+            e.g.,
+            <citerefentry>
+                <refentrytitle>kinit</refentrytitle><manvolnum>1</manvolnum>
+            </citerefentry>,
+            is a <quote>"KCM client"</quote> and the KCM daemon
+            is being referred to as a <quote>"KCM server"</quote>. The client
+            and server communicate over a UNIX socket.
+        </para>
+        <para>
+            The KCM server keeps track of each credential caches's owner and
+            performs access check control based on the UID and GID of the
+            KCM client. The root user has access to all credential caches.
+        </para>
+        <para>
+            The KCM credential cache has several interesting properties:
+            <itemizedlist>
+                <listitem>
+                    <para>
+                        since the process runs in userspace, it is subject to UID namespacing, ulike the kernel keyring
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        unlike the kernel keyring-based cache, which is shared between all containers, the KCM server is a separate process whose entry point is a UNIX socket
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        the SSSD implementation stores the ccaches in the SSSD
+                        <citerefentry>
+                            <refentrytitle>sssd-secrets</refentrytitle><manvolnum>5</manvolnum>
+                        </citerefentry>
+                        secrets store, allowing the ccaches to survive KCM server restarts or machine reboots.
+                    </para>
+                </listitem>
+            </itemizedlist>
+            This allows the system to use a collection-aware credential
+            cache, yet share the credential cache between some or no
+            containers by bind-mounting the socket.
+        </para>
+    </refsect1>
+
+    <refsect1 id='usage'>
+        <title>USING THE KCM CREDENTIAL CACHE</title>
+        <para>
+            In order to use KCM credential cache, it must be selected as the default
+            credential type in
+            <citerefentry>
+                <refentrytitle>krb5.conf</refentrytitle><manvolnum>5</manvolnum>
+            </citerefentry>,
+            The credentials cache name must be only <quote>KCM:</quote>
+            without any template expansions.  For example:
+            <programlisting>
+[libdefaults]
+    default_ccache_name = KCM:
+            </programlisting>
+        </para>
+        <para>
+            Next, make sure the Kerberos client libraries and the KCM server must agree
+            on the UNIX socket path. By default, both use the same path
+            <replaceable>/var/run/.heim_org.h5l.kcm-socket</replaceable>. To configure
+            the Kerberos library, change its <quote>kcm_socket</quote> option which
+            is described in the
+            <citerefentry>
+                <refentrytitle>krb5.conf</refentrytitle><manvolnum>5</manvolnum>
+            </citerefentry>
+            manual page.
+        </para>
+        <para>
+            Finally, make sure the SSSD KCM server can be contacted.
+            The KCM service is typically socket-activated by
+            <citerefentry>
+                <refentrytitle>systemd</refentrytitle>
+                <manvolnum>1</manvolnum>
+            </citerefentry>.
+            Unlike
+            other SSSD services, it cannot be started by adding the
+            <quote>kcm</quote> string to the <quote>service</quote>
+            directive.
+            <programlisting>
+systemctl start sssd-kcm.socket
+systemctl enable sssd-kcm.socket
+systemctl enable sssd-kcm.service
+            </programlisting>
+            Please note your distribution may already configure the units
+            for you.
+        </para>
+    </refsect1>
+
+    <refsect1 id='storage'>
+        <title>THE CREDENTIAL CACHE STORAGE</title>
+        <para>
+            The credential caches are stored in the SSSD secrets service (see
+            <citerefentry>
+                <refentrytitle>sssd-secrets</refentrytitle><manvolnum>5</manvolnum>
+            </citerefentry>
+            for more details). Therefore it is important that also the sssd-secrets
+            service is enabled and its socket is started:
+            <programlisting>
+systemctl start sssd-secrets.socket
+systemctl enable sssd-secrets.socket
+systemctl enable sssd-secrets.service
+            </programlisting>
+            Your distribution should already set the dependencies between the services.
+        </para>
+    </refsect1>
+
+    <refsect1 id='options'>
+        <title>CONFIGURATION OPTIONS</title>
+        <para>
+            The KCM service is configured in the <quote>kcm</quote>
+            section of the sssd.conf file. Please note that currently,
+            is it not sufficient to restart the sssd-kcm service, because
+            the sssd configuration is only parsed and read to an internal
+            configuration database by the sssd service. Therefore you
+            must restart the sssd service if you change anything in the
+            <quote>kcm</quote> section of sssd.conf.
+            For a detailed syntax reference, refer to the <quote>FILE FORMAT</quote> section of the
+            <citerefentry>
+                <refentrytitle>sssd.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+            </citerefentry> manual page.
+        </para>
+        <para>
+            The generic SSSD service options such as
+            <quote>debug_level</quote> or <quote>fd_limit</quote> are
+            accepted by the kcm service.  Please refer to the
+            <citerefentry>
+                <refentrytitle>sssd.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+            </citerefentry> manual page for a complete list. In addition,
+            there are some KCM-specific options as well.
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term>socket_path (string)</term>
+                <listitem>
+                    <para>
+                        The socket the KCM service will listen on.
+                    </para>
+                    <para>
+                        Default: <replaceable>/var/run/.heim_org.h5l.kcm-socket</replaceable>
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+    </refsect1>
+
+    <refsect1 id='see_also'>
+        <title>SEE ALSO</title>
+        <para>
+            <citerefentry>
+                <refentrytitle>sssd</refentrytitle><manvolnum>8</manvolnum>
+            </citerefentry>,
+            <citerefentry>
+                <refentrytitle>sssd.conf</refentrytitle><manvolnum>5</manvolnum>
+            </citerefentry>,
+        </para>
+    </refsect1>
+</refentry>
+</reference>

From abb7fbad4d3b7ddfbe3a8f324b8e5ebd4b3008c7 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 13 Dec 2016 17:17:16 +0100
Subject: [PATCH 13/21] TESTS: Add integration tests for the KCM responder

---
 contrib/ci/configure.sh     |   7 +
 contrib/ci/deps.sh          |   6 +
 src/tests/intg/Makefile.am  |   4 +
 src/tests/intg/kdc.py       | 176 +++++++++++++++++++++
 src/tests/intg/krb5utils.py | 156 +++++++++++++++++++
 src/tests/intg/test_kcm.py  | 361 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 710 insertions(+)
 create mode 100644 src/tests/intg/kdc.py
 create mode 100644 src/tests/intg/krb5utils.py
 create mode 100644 src/tests/intg/test_kcm.py

diff --git a/contrib/ci/configure.sh b/contrib/ci/configure.sh
index 8e779cf..7590743 100644
--- a/contrib/ci/configure.sh
+++ b/contrib/ci/configure.sh
@@ -38,6 +38,13 @@ if [[ "$DISTRO_BRANCH" == -redhat-redhatenterprise*-6.*- ||
         "--disable-cifs-idmap-plugin"
         "--with-syslog=syslog"
         "--without-python3-bindings"
+        "--without-kcm"
+    )
+fi
+
+if [[ "$DISTRO_BRANCH" == -redhat-fedora-2[0-2]* ]]; then
+    CONFIGURE_ARG_LIST+=(
+        "--without-kcm"
     )
 fi
 
diff --git a/contrib/ci/deps.sh b/contrib/ci/deps.sh
index c525e62..4467e11 100644
--- a/contrib/ci/deps.sh
+++ b/contrib/ci/deps.sh
@@ -47,6 +47,8 @@ if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then
         uid_wrapper
         python-requests
         curl-devel
+        krb5-server
+        krb5-workstation
     )
     _DEPS_LIST_SPEC=`
         sed -e 's/@PACKAGE_VERSION@/0/g' \
@@ -122,6 +124,10 @@ if [[ "$DISTRO_BRANCH" == -debian-* ]]; then
         libhttp-parser-dev
         libjansson-dev
         libcurl4-openssl-dev
+        krb5-kdc
+        krb5-admin-server
+        krb5-user
+        uuid-dev
     )
     DEPS_INTGCHECK_SATISFIED=true
 fi
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index 1d36fa0..8526bea 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -26,6 +26,9 @@ dist_noinst_DATA = \
     files_ops.py \
     test_files_ops.py \
     test_files_provider.py \
+    kdc.py \
+    krb5utils.py \
+    test_kcm.py \
     $(NULL)
 
 config.py: config.py.m4
@@ -80,5 +83,6 @@ intgcheck-installed: config.py passwd group
 	NSS_WRAPPER_MODULE_FN_PREFIX="sss" \
 	UID_WRAPPER=1 \
 	UID_WRAPPER_ROOT=1 \
+	NON_WRAPPED_UID=$$(echo $$UID) \
 	    fakeroot $(PYTHON2) $(PYTEST) -v --tb=native $(INTGCHECK_PYTEST_ARGS) .
 	rm -f $(DESTDIR)$(logpath)/*
diff --git a/src/tests/intg/kdc.py b/src/tests/intg/kdc.py
new file mode 100644
index 0000000..5916712
--- /dev/null
+++ b/src/tests/intg/kdc.py
@@ -0,0 +1,176 @@
+#
+# MIT Kerberos server class
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# This is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+import os
+import signal
+import shutil
+import subprocess
+
+from util import *
+
+
+class KDC(object):
+    """
+    MIT Kerberos KDC instance
+    """
+
+    def __init__(self, basedir, realm,
+                 includedir=None,
+                 kdc_port=10088,
+                 kadmin_port=10749,
+                 master_key='master'):
+        self.basedir = basedir
+        self.realm = realm
+        self.kdc_port = kdc_port
+        self.kadmin_port = kadmin_port
+        self.master_key = master_key
+
+        self.kdc_basedir = self.basedir + "/var/krb5kdc"
+        self.includedir = includedir or (self.kdc_basedir + "/include")
+        self.kdc_logdir = self.kdc_basedir + "/log"
+        self.kdc_conf_path = self.kdc_basedir + "/kdc.conf"
+        self.krb5_conf_path = self.kdc_basedir + "/krb5.conf"
+
+        self.kdc_pid_file = self.kdc_basedir + "/kdc.pid"
+
+        self.acl_file = self.kdc_basedir + "/kadm5.acl"
+
+        self.admin_princ = "admin/admin@" + self.realm
+
+    def start_kdc(self, extra_args=[]):
+        args = ["krb5kdc", '-P', self.kdc_pid_file] + extra_args
+        return self._run_in_env(args, self.get_krb5_env())
+
+    def stop_kdc(self):
+        try:
+            with open(self.kdc_pid_file, "r") as pid_file:
+                os.kill(int(pid_file.read()), signal.SIGTERM)
+        except IOError as ioex:
+            if ioex.errno == 2:
+                pass
+            else:
+                raise ioex
+
+    def teardown(self):
+        self.stop_kdc()
+        shutil.rmtree(self.kdc_basedir)
+
+    def set_up(self):
+        self._create_config()
+        self._create_acl()
+        self._create_kdb()
+
+    def get_krb5_env(self):
+        my_env = os.environ
+        my_env['KRB5_CONFIG'] = self.krb5_conf_path
+        my_env['KRB5_KDC_PROFILE'] = self.kdc_conf_path
+        return my_env
+
+    def add_config(self, include_files):
+        for name, contents in include_files.items():
+            include_fpath = os.path.join(self.includedir, name)
+            with open(include_fpath, 'w') as include_file:
+                include_file.write(contents)
+
+    def add_principal(self, princ, password=None):
+        args = ["kadmin.local", "-q"]
+        if password is None:
+            args += ["addprinc -randkey %s" % (princ)]
+        else:
+            args += ["addprinc -pw %s %s" % (password, princ)]
+        print args
+        return self._run_in_env(args, self.get_krb5_env())
+
+    def _run_in_env(self, args, env):
+        cmd = subprocess.Popen(args, env=env)
+        out, err = cmd.communicate()
+        return cmd.returncode, out, err
+
+    def _create_config(self):
+        try:
+            os.makedirs(self.kdc_basedir)
+            os.makedirs(self.kdc_logdir)
+            os.makedirs(self.includedir)
+        except OSError as osex:
+            if osex.errno == 17:
+                pass
+
+        kdc_conf = self._format_kdc_conf()
+        with open(self.kdc_conf_path, 'w') as kdc_conf_file:
+            kdc_conf_file.write(kdc_conf)
+
+        krb5_conf = self._format_krb5_conf()
+        with open(self.krb5_conf_path, 'w') as krb5_conf_file:
+            krb5_conf_file.write(krb5_conf)
+
+    def _create_acl(self):
+        with open(self.acl_file, 'w') as acl_fobject:
+            acl_fobject.write(self.admin_princ)
+
+    def _create_kdb(self):
+        self._run_in_env(
+            ['kdb5_util', 'create', '-W', '-s', '-P', self.master_key],
+            self.get_krb5_env()
+        )
+
+    def _format_kdc_conf(self):
+        database_path = self.kdc_basedir + "/principal"
+        key_stash = self.kdc_basedir + "/stash." + self.realm
+
+        kdc_logfile = "FILE:" + self.kdc_logdir + "/krb5kdc.log"
+        kadmin_logfile = "FILE:" + self.kdc_logdir + "/kadmin.log"
+        libkrb5_logfile = "FILE:" + self.kdc_logdir + "/libkrb5.log"
+
+        kdc_conf = unindent("""
+        [kdcdefaults]
+        kdc_ports = {self.kdc_port}
+        kdc_tcp_ports = {self.kdc_port}
+
+        [realms]
+            {self.realm} = {{
+                kadmind_port = {self.kadmin_port}
+                database_name = {database_path}
+                key_stash_file = {key_stash}
+                acl_file = {self.acl_file}
+            }}
+
+        [logging]
+            kdc = {kdc_logfile}
+            admin_server = {kadmin_logfile}
+            default = {libkrb5_logfile}
+        """).format(**locals())
+        return kdc_conf
+
+    def _format_krb5_conf(self):
+        kdc_uri = "localhost:%d" % self.kdc_port
+        kadmin_uri = "localhost:%d" % self.kadmin_port
+
+        krb5_conf = unindent("""
+        includedir {self.includedir}
+
+        [libdefaults]
+        default_realm = {self.realm}
+        dns_lookup_kdc = false
+        dns_lookup_realm = false
+
+        [realms]
+            {self.realm} = {{
+                kdc = {kdc_uri}
+                admin_server = {kadmin_uri}
+            }}
+        """).format(**locals())
+        return krb5_conf
diff --git a/src/tests/intg/krb5utils.py b/src/tests/intg/krb5utils.py
new file mode 100644
index 0000000..8dc05ba
--- /dev/null
+++ b/src/tests/intg/krb5utils.py
@@ -0,0 +1,156 @@
+#
+# MIT Kerberos server class
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# This is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+import os
+import subprocess
+
+
+class NoPrincipals(Exception):
+    def __init__(self):
+        Exception.__init__(self, 'No principals in the collection')
+
+
+class PrincNotFound(Exception):
+    def __init__(self, principal):
+        Exception.__init__(self, 'Principal %s not found' % principal)
+
+
+class Krb5Utils(object):
+    """
+    Helper class to test Kerberos command line utilities
+    """
+    def __init__(self, krb5_conf_path):
+        self.krb5_conf_path = krb5_conf_path
+
+    def _run_in_env(self, args, stdin=None, extra_env=None):
+        my_env = os.environ
+        my_env['KRB5_CONFIG'] = self.krb5_conf_path
+
+        if 'KRB5CCNAME' in my_env:
+            del my_env['KRB5CCNAME']
+        if extra_env is not None:
+            my_env.update(extra_env)
+
+        cmd = subprocess.Popen(args,
+                               env=my_env,
+                               stdin=subprocess.PIPE,
+                               stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+        out, err = cmd.communicate(stdin)
+        return cmd.returncode, out, err
+
+    def kinit(self, principal, password, env=None):
+        args = ["kinit", principal]
+        return self._run_in_env(args, password, env)
+
+    def kvno(self, principal, env=None):
+        args = ["kvno", principal]
+        return self._run_in_env(args, env)
+
+    def kdestroy(self, all_ccaches=False, env=None):
+        args = ["kdestroy"]
+        if all_ccaches is True:
+            args += ["-A"]
+        out, _, _ = self._run_in_env(args, env)
+        return out
+
+    def kswitch(self, principal, env=None):
+        args = ["kswitch", '-p', principal]
+        out, _, _ = self._run_in_env(args, env)
+        return out
+
+    def _check_klist_l(self, line, exp_principal, exp_cache):
+        try:
+            princ, cache = line.split()
+        except ValueError:
+            return False
+
+        if exp_cache is not None and cache != exp_cache:
+            return False
+
+        if exp_principal != princ:
+            return False
+
+        return True
+
+    def num_princs(self, env=None):
+        args = ["klist", "-l"]
+        retval, out, err = self._run_in_env(args, extra_env=env)
+        if retval != 0:
+            return 0
+
+        outlines = [l for l in out.split('\n') if len(l) > 1]
+        return len(outlines) - 2
+
+    def list_princs(self, env=None):
+        args = ["klist", "-l"]
+        retval, out, err = self._run_in_env(args, extra_env=env)
+        if retval == 1:
+            raise NoPrincipals
+        elif retval != 0:
+            raise Exception("klist failed: %d: %s\n", retval, err)
+
+        outlines = out.split('\n')
+        if len(outlines) < 2:
+            raise Exception("Not enough output from klist -l")
+
+        return [l for l in outlines[2:] if len(l) > 0]
+
+    def has_principal(self, exp_principal, exp_cache=None, env=None):
+        try:
+            princlist = self.list_princs(env)
+        except NoPrincipals:
+            return False
+
+        for line in princlist:
+            matches = self._check_klist_l(line, exp_principal, exp_cache)
+            if matches is True:
+                return True
+
+        return False
+
+    def default_principal(self, env=None):
+        principals = self.list_princs(env)
+        return principals[0].split()[0]
+
+    def _parse_klist_a(self, out):
+        dflprinc = None
+        thisrealm = None
+        ccache_dict = dict()
+
+        for line in [l for l in out.split('\n') if len(l) > 0]:
+            if line.startswith("Default principal"):
+                dflprinc = line.split()[2]
+                thisrealm = '@' + dflprinc.split('@')[1]
+            elif thisrealm is not None and line.endswith(thisrealm):
+                svc = line.split()[-1]
+                if dflprinc in ccache_dict:
+                    ccache_dict[dflprinc].append(svc)
+                else:
+                    ccache_dict[dflprinc] = [svc]
+
+        return ccache_dict
+
+    def list_all_princs(self, env=None):
+        args = ["klist", "-A"]
+        retval, out, err = self._run_in_env(args, extra_env=env)
+        if retval == 1:
+            raise NoPrincipals
+        elif retval != 0:
+            raise Exception("klist -A failed: %d: %s\n", retval, err)
+
+        return self._parse_klist_a(out)
diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py
new file mode 100644
index 0000000..ad1e492
--- /dev/null
+++ b/src/tests/intg/test_kcm.py
@@ -0,0 +1,361 @@
+#
+# KCM responder integration tests
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# This is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+import os
+import os.path
+import stat
+import subprocess
+import pytest
+import socket
+import time
+import signal
+
+import kdc
+import krb5utils
+import config
+from util import unindent, run_shell
+
+class KcmTestEnv(object):
+    def __init__(self, k5kdc, k5util):
+        self.k5kdc = k5kdc
+        self.k5util = k5util
+        self.counter = 0
+
+    def my_uid(self):
+        s_myuid = os.environ['NON_WRAPPED_UID']
+        return int(s_myuid)
+
+    def ccname(self, my_uid=None):
+        if my_uid is None:
+            my_uid = self.my_uid()
+
+        return "KCM:%d" % my_uid
+
+
+@pytest.fixture(scope="module")
+def kdc_instance(request):
+    """Kerberos server instance fixture"""
+    kdc_instance = kdc.KDC(config.PREFIX, "KCMTEST")
+    try:
+        kdc_instance.set_up()
+        kdc_instance.start_kdc()
+    except:
+        kdc_instance.teardown()
+        raise
+    request.addfinalizer(kdc_instance.teardown)
+    return kdc_instance
+
+
+def create_conf_fixture(request, contents):
+    """Generate sssd.conf and add teardown for removing it"""
+    with open(config.CONF_PATH, "w") as conf:
+        conf.write(contents)
+    os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+    request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+
+
+def create_sssd_kcm_fixture(sock_path, request):
+    if subprocess.call(['sssd', "--genconf"]) != 0:
+        raise Exception("failed to regenerate confdb")
+
+    resp_path = os.path.join(config.LIBEXEC_PATH, "sssd", "sssd_kcm")
+    if not os.access(resp_path, os.X_OK):
+        # It would be cleaner to use pytest.mark.skipif on the package level
+        # but upstream insists on supporting RHEL-6..
+        pytest.skip("No KCM responder, skipping")
+
+    kcm_pid = os.fork()
+    assert kcm_pid >= 0
+
+    if kcm_pid == 0:
+        if subprocess.call([resp_path, "--uid=0", "--gid=0"]) != 0:
+            print("sssd_kcm failed to start")
+            sys.exit(99)
+    else:
+        abs_sock_path = os.path.join(config.RUNSTATEDIR, sock_path)
+        sck = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        for _ in range(1, 10):
+            try:
+                sck.connect(abs_sock_path)
+            except:
+                time.sleep(0.1)
+            else:
+                break
+        sck.close()
+        assert os.path.exists(abs_sock_path)
+
+    def kcm_teardown():
+        if kcm_pid == 0:
+            return
+        os.kill(kcm_pid, signal.SIGTERM)
+
+    request.addfinalizer(kcm_teardown)
+    return kcm_pid
+
+
+@pytest.fixture
+def setup_for_kcm(request, kdc_instance):
+    """
+    Just set up the local provider for tests and enable the KCM
+    responder
+    """
+    kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
+
+    sssd_conf = unindent("""\
+        [sssd]
+        domains = local
+        services = nss
+
+        [domain/local]
+        id_provider = local
+
+        [kcm]
+        socket_path = {kcm_path}
+    """).format(**locals())
+
+    kcm_socket_include = unindent("""
+    [libdefaults]
+    default_ccache_name = KCM:
+    kcm_socket = {kcm_path}
+    """).format(**locals())
+    kdc_instance.add_config({'kcm_socket': kcm_socket_include})
+
+    create_conf_fixture(request, sssd_conf)
+    create_sssd_kcm_fixture(kcm_path, request)
+
+    k5util = krb5utils.Krb5Utils(kdc_instance.krb5_conf_path)
+
+    return KcmTestEnv(kdc_instance, k5util)
+
+
+def test_kcm_init_list_destroy(setup_for_kcm):
+    """
+    Test that kinit, kdestroy and klist work with KCM
+    """
+    testenv = setup_for_kcm
+    testenv.k5kdc.add_principal("kcmtest", "Secret123")
+
+    ok = testenv.k5util.has_principal("kcmtest@KCMTEST")
+    assert ok is False
+    nprincs = testenv.k5util.num_princs()
+    assert nprincs == 0
+
+    out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
+    assert out == 0
+    nprincs = testenv.k5util.num_princs()
+    assert nprincs == 1
+
+    exp_ccname = testenv.ccname()
+    ok = testenv.k5util.has_principal("kcmtest@KCMTEST", exp_ccname)
+    assert ok is True
+
+    out = testenv.k5util.kdestroy()
+    assert out == 0
+
+    ok = testenv.k5util.has_principal("kcmtest@KCMTEST")
+    assert ok is False
+    nprincs = testenv.k5util.num_princs()
+    assert nprincs == 0
+
+
+def test_kcm_overwrite(setup_for_kcm):
+    """
+    That that reusing a ccache reinitializes the cache and doesn't
+    add the same principal twice
+    """
+    testenv = setup_for_kcm
+    testenv.k5kdc.add_principal("kcmtest", "Secret123")
+    exp_ccache = {'kcmtest@KCMTEST': ['krbtgt/KCMTEST@KCMTEST']}
+
+    assert testenv.k5util.num_princs() == 0
+
+    out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
+    assert out == 0
+    assert exp_ccache == testenv.k5util.list_all_princs()
+
+    out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
+    assert out == 0
+    assert exp_ccache == testenv.k5util.list_all_princs()
+
+
+def test_collection_init_list_destroy(setup_for_kcm):
+    """
+    Test that multiple principals and service tickets can be stored
+    in a collection.
+    """
+    testenv = setup_for_kcm
+    testenv.k5kdc.add_principal("alice", "alicepw")
+    testenv.k5kdc.add_principal("bob", "bobpw")
+    testenv.k5kdc.add_principal("carol", "carolpw")
+    testenv.k5kdc.add_principal("host/somehostname")
+
+    assert testenv.k5util.num_princs() == 0
+
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+    assert out == 0
+    assert testenv.k5util.default_principal() == 'alice@KCMTEST'
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 1
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert 'bob@KCMTEST' not in cc_coll
+    assert 'carol@KCMTEST' not in cc_coll
+
+    out, _, _ = testenv.k5util.kinit("bob", "bobpw")
+    assert out == 0
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 2
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert 'carol@KCMTEST' not in cc_coll
+
+    out, _, _ = testenv.k5util.kinit("carol", "carolpw")
+    assert out == 0
+    assert testenv.k5util.default_principal() == 'carol@KCMTEST'
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 3
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert cc_coll['carol@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+
+    out, _, _ = testenv.k5util.kvno('host/somehostname')
+    assert out == 0
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 3
+    assert set(cc_coll['carol@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+                                                 'host/somehostname@KCMTEST'])
+
+    out = testenv.k5util.kdestroy()
+    assert out == 0
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 2
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert 'carol@KCMTEST' not in cc_coll
+
+    # FIXME - a bug in libkrb5?
+    #out = testenv.k5util.kdestroy(all_ccaches=True)
+    #assert out == 0
+    #cc_coll = testenv.k5util.list_all_princs()
+    #assert len(cc_coll) == 0
+
+
+def test_kswitch(setup_for_kcm):
+    """
+    Test switching between principals
+    """
+    testenv = setup_for_kcm
+    testenv.k5kdc.add_principal("alice", "alicepw")
+    testenv.k5kdc.add_principal("bob", "bobpw")
+    testenv.k5kdc.add_principal("host/somehostname")
+    testenv.k5kdc.add_principal("host/differenthostname")
+
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+    assert out == 0
+    assert testenv.k5util.default_principal() == 'alice@KCMTEST'
+
+    out, _, _ = testenv.k5util.kinit("bob", "bobpw")
+    assert out == 0
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 2
+    assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+
+    out = testenv.k5util.kswitch("alice@KCMTEST")
+    assert testenv.k5util.default_principal() == 'alice@KCMTEST'
+    out, _, _ = testenv.k5util.kvno('host/somehostname')
+    assert out == 0
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 2
+    assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+                                                 'host/somehostname@KCMTEST'])
+    assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+
+    out = testenv.k5util.kswitch("bob@KCMTEST")
+    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+    out, _, _ = testenv.k5util.kvno('host/differenthostname')
+    assert out == 0
+    cc_coll = testenv.k5util.list_all_princs()
+    assert len(cc_coll) == 2
+    assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+                                                 'host/somehostname@KCMTEST'])
+    assert set(cc_coll['bob@KCMTEST']) == set([
+                                    'krbtgt/KCMTEST@KCMTEST',
+                                    'host/differenthostname@KCMTEST'])
+
+
+def test_subsidiaries(setup_for_kcm):
+    """
+    Test that subsidiary caches are usable and KCM: without specifying UID
+    can be used to identify the collection
+    """
+    testenv = setup_for_kcm
+    testenv.k5kdc.add_principal("alice", "alicepw")
+    testenv.k5kdc.add_principal("bob", "bobpw")
+    testenv.k5kdc.add_principal("host/somehostname")
+    testenv.k5kdc.add_principal("host/differenthostname")
+
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+    assert out == 0
+    out, _, _ = testenv.k5util.kvno('host/somehostname')
+
+    out, _, _ = testenv.k5util.kinit("bob", "bobpw")
+    assert out == 0
+    out, _, _ = testenv.k5util.kvno('host/differenthostname')
+
+    exp_cc_coll = dict()
+    exp_cc_coll['alice@KCMTEST'] = 'host/somehostname@KCMTEST'
+    exp_cc_coll['bob@KCMTEST'] = 'host/differenthostname@KCMTEST'
+
+    klist_l = testenv.k5util.list_princs()
+    princ_ccache = dict()
+    for line in klist_l:
+        princ, subsidiary = line.split()
+        princ_ccache[princ] = subsidiary
+
+    for princ, subsidiary in princ_ccache.items():
+        env = {'KRB5CCNAME': subsidiary}
+        cc_coll = testenv.k5util.list_all_princs(env=env)
+        assert len(cc_coll) == 1
+        assert princ in cc_coll
+        assert exp_cc_coll[princ] in cc_coll[princ]
+
+    cc_coll = testenv.k5util.list_all_princs(env={'KRB5CCNAME': 'KCM:'})
+    assert len(cc_coll) == 2
+    assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+                                                 'host/somehostname@KCMTEST'])
+    assert set(cc_coll['bob@KCMTEST']) == set([
+                                            'krbtgt/KCMTEST@KCMTEST',
+                                            'host/differenthostname@KCMTEST'])
+
+
+def test_kdestroy_nocache(setup_for_kcm):
+    """
+    Destroying a non-existing ccache should not throw an error
+    """
+    testenv = setup_for_kcm
+    testenv.k5kdc.add_principal("alice", "alicepw")
+    out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+    assert out == 0
+
+    testenv.k5util.kdestroy()
+    assert out == 0
+    out = testenv.k5util.kdestroy()
+    assert out == 0

From 7c08491162a7eb604bc83094bc7aed381ea9e110 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 3 Jan 2017 16:00:38 +0100
Subject: [PATCH 14/21] SECRETS: Create DB path before the operation itself

This is a refactoring where instead of creating the ldb path in the
operation itself, we create the ldb path when creating the local db request
and pass the path to the operation.

This would allow us to store different kind of objects in the secrets
storage later.
---
 src/responder/secrets/local.c | 168 +++++++++++++++++++++---------------------
 1 file changed, 83 insertions(+), 85 deletions(-)

diff --git a/src/responder/secrets/local.c b/src/responder/secrets/local.c
index ed70193..9dcdd99 100644
--- a/src/responder/secrets/local.c
+++ b/src/responder/secrets/local.c
@@ -199,39 +199,36 @@ static char *local_dn_to_path(TALLOC_CTX *mem_ctx,
     return path;
 }
 
+struct local_db_req {
+    char *path;
+    struct ldb_dn *basedn;
+};
+
 #define LOCAL_SIMPLE_FILTER "(type=simple)"
 #define LOCAL_CONTAINER_FILTER "(type=container)"
 
 static int local_db_get_simple(TALLOC_CTX *mem_ctx,
                                struct local_context *lctx,
-                               const char *req_path,
+                               struct local_db_req *lc_req,
                                char **secret)
 {
     TALLOC_CTX *tmp_ctx;
     static const char *attrs[] = { "secret", "enctype", NULL };
     struct ldb_result *res;
-    struct ldb_dn *dn;
     const char *attr_secret;
     const char *attr_enctype;
     int ret;
 
-    DEBUG(SSSDBG_TRACE_FUNC, "Retrieving a secret from [%s]\n", req_path);
+    DEBUG(SSSDBG_TRACE_FUNC, "Retrieving a secret from [%s]\n", lc_req->path);
 
     tmp_ctx = talloc_new(mem_ctx);
     if (!tmp_ctx) return ENOMEM;
 
-    ret = local_db_dn(tmp_ctx, lctx->ldb, req_path, &dn);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "local_db_dn failed [%d]: %s\n", ret, sss_strerror(ret));
-        goto done;
-    }
-
     DEBUG(SSSDBG_TRACE_INTERNAL,
           "Searching for [%s] at [%s] with scope=base\n",
-          LOCAL_SIMPLE_FILTER, ldb_dn_get_linearized(dn));
+          LOCAL_SIMPLE_FILTER, ldb_dn_get_linearized(lc_req->basedn));
 
-    ret = ldb_search(lctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
+    ret = ldb_search(lctx->ldb, tmp_ctx, &res, lc_req->basedn, LDB_SCOPE_BASE,
                      attrs, "%s", LOCAL_SIMPLE_FILTER);
     if (ret != EOK) {
         DEBUG(SSSDBG_TRACE_LIBS,
@@ -278,34 +275,26 @@ static int local_db_get_simple(TALLOC_CTX *mem_ctx,
 
 static int local_db_list_keys(TALLOC_CTX *mem_ctx,
                               struct local_context *lctx,
-                              const char *req_path,
+                              struct local_db_req *lc_req,
                               char ***_keys,
                               int *num_keys)
 {
     TALLOC_CTX *tmp_ctx;
     static const char *attrs[] = { "secret", NULL };
     struct ldb_result *res;
-    struct ldb_dn *dn;
     char **keys;
     int ret;
 
     tmp_ctx = talloc_new(mem_ctx);
     if (!tmp_ctx) return ENOMEM;
 
-    DEBUG(SSSDBG_TRACE_FUNC, "Listing keys at [%s]\n", req_path);
-
-    ret = local_db_dn(tmp_ctx, lctx->ldb, req_path, &dn);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "local_db_dn failed [%d]: %s\n", ret, sss_strerror(ret));
-        goto done;
-    }
+    DEBUG(SSSDBG_TRACE_FUNC, "Listing keys at [%s]\n", lc_req->path);
 
     DEBUG(SSSDBG_TRACE_INTERNAL,
           "Searching for [%s] at [%s] with scope=subtree\n",
-          LOCAL_SIMPLE_FILTER, ldb_dn_get_linearized(dn));
+          LOCAL_SIMPLE_FILTER, ldb_dn_get_linearized(lc_req->basedn));
 
-    ret = ldb_search(lctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
+    ret = ldb_search(lctx->ldb, tmp_ctx, &res, lc_req->basedn, LDB_SCOPE_SUBTREE,
                      attrs, "%s", LOCAL_SIMPLE_FILTER);
     if (ret != EOK) {
         DEBUG(SSSDBG_TRACE_LIBS,
@@ -327,7 +316,7 @@ static int local_db_list_keys(TALLOC_CTX *mem_ctx,
     }
 
     for (unsigned i = 0; i < res->count; i++) {
-        keys[i] = local_dn_to_path(keys, dn, res->msgs[i]->dn);
+        keys[i] = local_dn_to_path(keys, lc_req->basedn, res->msgs[i]->dn);
         if (!keys[i]) {
             ret = ENOMEM;
             goto done;
@@ -474,7 +463,7 @@ static int local_check_max_payload_size(struct local_context *lctx,
 
 static int local_db_put_simple(TALLOC_CTX *mem_ctx,
                                struct local_context *lctx,
-                               const char *req_path,
+                               struct local_db_req *lc_req,
                                const char *secret)
 {
     struct ldb_message *msg;
@@ -482,20 +471,14 @@ static int local_db_put_simple(TALLOC_CTX *mem_ctx,
     char *enc_secret;
     int ret;
 
+    DEBUG(SSSDBG_TRACE_FUNC, "Adding a secret to [%s]\n", lc_req->path);
+
     msg = ldb_msg_new(mem_ctx);
     if (!msg) {
         ret = ENOMEM;
         goto done;
     }
-
-    DEBUG(SSSDBG_TRACE_FUNC, "Adding a secret to [%s]\n", req_path);
-
-    ret = local_db_dn(msg, lctx->ldb, req_path, &msg->dn);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "local_db_dn failed [%d]: %s\n", ret, sss_strerror(ret));
-        goto done;
-    }
+    msg->dn = lc_req->basedn;
 
     /* make sure containers exist */
     ret = local_db_check_containers(msg, lctx, msg->dn);
@@ -585,32 +568,24 @@ static int local_db_put_simple(TALLOC_CTX *mem_ctx,
 
 static int local_db_delete(TALLOC_CTX *mem_ctx,
                            struct local_context *lctx,
-                           const char *req_path)
+                           struct local_db_req *lc_req)
 {
     TALLOC_CTX *tmp_ctx;
-    struct ldb_dn *dn;
     static const char *attrs[] = { NULL };
     struct ldb_result *res;
     int ret;
 
-    DEBUG(SSSDBG_TRACE_FUNC, "Removing a secret from [%s]\n", req_path);
+    DEBUG(SSSDBG_TRACE_FUNC, "Removing a secret from [%s]\n", lc_req->path);
 
     tmp_ctx = talloc_new(mem_ctx);
     if (!tmp_ctx) return ENOMEM;
 
-    ret = local_db_dn(mem_ctx, lctx->ldb, req_path, &dn);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "local_db_dn failed [%d]: %s\n", ret, sss_strerror(ret));
-        goto done;
-    }
-
     DEBUG(SSSDBG_TRACE_INTERNAL,
           "Searching for [%s] at [%s] with scope=base\n",
-          LOCAL_CONTAINER_FILTER, ldb_dn_get_linearized(dn));
+          LOCAL_CONTAINER_FILTER, ldb_dn_get_linearized(lc_req->basedn));
 
-    ret = ldb_search(lctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
-                    attrs, LOCAL_CONTAINER_FILTER);
+    ret = ldb_search(lctx->ldb, tmp_ctx, &res, lc_req->basedn, LDB_SCOPE_BASE,
+                     attrs, LOCAL_CONTAINER_FILTER);
     if (ret != EOK) {
         DEBUG(SSSDBG_TRACE_LIBS,
               "ldb_search returned %d: %s\n", ret, ldb_strerror(ret));
@@ -619,8 +594,8 @@ static int local_db_delete(TALLOC_CTX *mem_ctx,
 
     if (res->count == 1) {
         DEBUG(SSSDBG_TRACE_INTERNAL,
-              "Searching for children of [%s]\n", ldb_dn_get_linearized(dn));
-        ret = ldb_search(lctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_ONELEVEL,
+              "Searching for children of [%s]\n", ldb_dn_get_linearized(lc_req->basedn));
+        ret = ldb_search(lctx->ldb, tmp_ctx, &res, lc_req->basedn, LDB_SCOPE_ONELEVEL,
                          attrs, NULL);
         if (ret != EOK) {
             DEBUG(SSSDBG_TRACE_LIBS,
@@ -632,13 +607,13 @@ static int local_db_delete(TALLOC_CTX *mem_ctx,
             ret = EEXIST;
             DEBUG(SSSDBG_OP_FAILURE,
                   "Failed to remove '%s': Container is not empty\n",
-                  ldb_dn_get_linearized(dn));
+                  ldb_dn_get_linearized(lc_req->basedn));
 
             goto done;
         }
     }
 
-    ret = ldb_delete(lctx->ldb, dn);
+    ret = ldb_delete(lctx->ldb, lc_req->basedn);
     if (ret != EOK) {
         DEBUG(SSSDBG_TRACE_LIBS,
               "ldb_delete returned %d: %s\n", ret, ldb_strerror(ret));
@@ -653,25 +628,19 @@ static int local_db_delete(TALLOC_CTX *mem_ctx,
 
 static int local_db_create(TALLOC_CTX *mem_ctx,
                            struct local_context *lctx,
-                           const char *req_path)
+                           struct local_db_req *lc_req)
 {
     struct ldb_message *msg;
     int ret;
 
+    DEBUG(SSSDBG_TRACE_FUNC, "Creating a container at [%s]\n", lc_req->path);
+
     msg = ldb_msg_new(mem_ctx);
     if (!msg) {
         ret = ENOMEM;
         goto done;
     }
-
-    DEBUG(SSSDBG_TRACE_FUNC, "Creating a container at [%s]\n", req_path);
-
-    ret = local_db_dn(msg, lctx->ldb, req_path, &msg->dn);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE,
-              "local_db_dn failed [%d]: %s\n", ret, sss_strerror(ret));
-        goto done;
-    }
+    msg->dn = lc_req->basedn;
 
     /* make sure containers exist */
     ret = local_db_check_containers(msg, lctx, msg->dn);
@@ -724,10 +693,13 @@ static int local_db_create(TALLOC_CTX *mem_ctx,
 }
 
 static int local_secrets_map_path(TALLOC_CTX *mem_ctx,
+                                  struct ldb_context *ldb,
                                   struct sec_req_ctx *secreq,
-                                  char **local_db_path)
+                                  struct local_db_req **_lc_req)
 {
     int ret;
+    struct local_db_req *lc_req;
+    const char *basedn;
 
     /* be strict for now */
     if (secreq->parsed_url.fragment != NULL) {
@@ -755,19 +727,45 @@ static int local_secrets_map_path(TALLOC_CTX *mem_ctx,
         }
     }
 
-    /* drop SEC_BASEPATH prefix */
-    *local_db_path =
-        talloc_strdup(mem_ctx, &secreq->mapped_path[sizeof(SEC_BASEPATH) - 1]);
-    if (!*local_db_path) {
+    lc_req = talloc(mem_ctx, struct local_db_req);
+    if (lc_req == NULL) {
+        return ENOMEM;
+    }
+
+    /* drop the prefix and select a basedn instead */
+    if (strncmp(secreq->mapped_path,
+                SEC_BASEPATH, sizeof(SEC_BASEPATH) - 1) == 0) {
+        lc_req->path = talloc_strdup(lc_req,
+                                     secreq->mapped_path + (sizeof(SEC_BASEPATH) - 1));
+        basedn = SECRETS_BASEDN;
+    } else {
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (lc_req->path == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE,
               "Failed to map request to local db path\n");
-        return ENOMEM;
+        ret = ENOMEM;
+        goto done;
     }
 
-    DEBUG(SSSDBG_TRACE_LIBS, "Local DB path is %s\n", *local_db_path);
-    return EOK;
-}
+    ret = local_db_dn(mem_ctx, ldb, basedn, lc_req->path, &lc_req->basedn);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to map request to local db DN\n");
+        goto done;
+    }
 
+    DEBUG(SSSDBG_TRACE_LIBS, "Local DB path is %s\n", lc_req->path);
+    ret = EOK;
+    *_lc_req = lc_req;
+done:
+    if (ret != EOK) {
+        talloc_free(lc_req);
+    }
+    return ret;
+}
 
 struct local_secret_state {
     struct tevent_context *ev;
@@ -785,7 +783,7 @@ static struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx,
     struct sec_data body = { 0 };
     const char *content_type;
     bool body_is_json;
-    char *req_path;
+    struct local_db_req *lc_req;
     char *secret;
     char **keys;
     int nkeys;
@@ -821,14 +819,14 @@ static struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx,
     }
     DEBUG(SSSDBG_TRACE_LIBS, "Content-Type: %s\n", content_type);
 
-    ret = local_secrets_map_path(state, secreq, &req_path);
+    ret = local_secrets_map_path(state, lctx->ldb, secreq, &lc_req);
     if (ret) goto done;
 
     switch (secreq->method) {
     case HTTP_GET:
-        DEBUG(SSSDBG_TRACE_LIBS, "Processing HTTP GET at [%s]\n", req_path);
-        if (req_path[strlen(req_path) - 1] == '/') {
-            ret = local_db_list_keys(state, lctx, req_path, &keys, &nkeys);
+        DEBUG(SSSDBG_TRACE_LIBS, "Processing HTTP GET at [%s]\n", lc_req->path);
+        if (lc_req->path[strlen(lc_req->path) - 1] == '/') {
+            ret = local_db_list_keys(state, lctx, lc_req, &keys, &nkeys);
             if (ret) goto done;
 
             ret = sec_array_to_json(state, keys, nkeys, &body.data);
@@ -838,7 +836,7 @@ static struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx,
             break;
         }
 
-        ret = local_db_get_simple(state, lctx, req_path, &secret);
+        ret = local_db_get_simple(state, lctx, lc_req, &secret);
         if (ret) goto done;
 
         if (body_is_json) {
@@ -855,7 +853,7 @@ static struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx,
         break;
 
     case HTTP_PUT:
-        DEBUG(SSSDBG_TRACE_LIBS, "Processing HTTP PUT at [%s]\n", req_path);
+        DEBUG(SSSDBG_TRACE_LIBS, "Processing HTTP PUT at [%s]\n", lc_req->path);
         if (body_is_json) {
             ret = sec_json_to_simple_secret(state, secreq->body.data,
                                             &secret);
@@ -866,27 +864,27 @@ static struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx,
         }
         if (ret) goto done;
 
-        ret = local_db_put_simple(state, lctx, req_path, secret);
+        ret = local_db_put_simple(state, lctx, lc_req, secret);
         if (ret) goto done;
         break;
 
     case HTTP_DELETE:
-        ret = local_db_delete(state, lctx, req_path);
+        ret = local_db_delete(state, lctx, lc_req);
         if (ret) goto done;
         break;
 
     case HTTP_POST:
-        DEBUG(SSSDBG_TRACE_LIBS, "Processing HTTP POST at [%s]\n", req_path);
-        plen = strlen(req_path);
+        DEBUG(SSSDBG_TRACE_LIBS, "Processing HTTP POST at [%s]\n", lc_req->path);
+        plen = strlen(lc_req->path);
 
-        if (req_path[plen - 1] != '/') {
+        if (lc_req->path[plen - 1] != '/') {
             ret = EINVAL;
             goto done;
         }
 
-        req_path[plen - 1] = '\0';
+        lc_req->path[plen - 1] = '\0';
 
-        ret = local_db_create(state, lctx, req_path);
+        ret = local_db_create(state, lctx, lc_req);
         if (ret) goto done;
         break;
 

From 03765b48106c9cd19d5233d6030f688e03da0a4e Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 3 Feb 2017 14:33:47 +0100
Subject: [PATCH 15/21] SECRETS: Return a nicer error message on request with
 no PUT data

I managed to create this pathological situation with the tcurl tool
which didn't send any PUT data. The error in sssd-secrets was quite
strange (ENOMEM). This patch just adds a safeguard sooner so that we
return a graceful error.
---
 src/responder/secrets/local.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/responder/secrets/local.c b/src/responder/secrets/local.c
index 9dcdd99..26c97a2 100644
--- a/src/responder/secrets/local.c
+++ b/src/responder/secrets/local.c
@@ -853,6 +853,12 @@ static struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx,
         break;
 
     case HTTP_PUT:
+        if (secreq->body.length == 0) {
+            DEBUG(SSSDBG_OP_FAILURE, "PUT with no data\n");
+            ret = EINVAL;
+            goto done;
+        }
+
         DEBUG(SSSDBG_TRACE_LIBS, "Processing HTTP PUT at [%s]\n", lc_req->path);
         if (body_is_json) {
             ret = sec_json_to_simple_secret(state, secreq->body.data,

From c0baa07f573241f767998e6c91fbac8abbf0d532 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 21 Mar 2017 14:14:42 +0100
Subject: [PATCH 16/21] SECRETS: Store ccaches in secrets for the KCM responder

Adds a new "hive" to the secrets responder whose base path is /kcm. Only
root can contact the /kcm hive, because the KCM responder only runs as
root and it must impersonate other users and store ccaches on their behalf.
---
 src/responder/secrets/local.c          | 16 +++++++-
 src/responder/secrets/providers.c      | 71 ++++++++++++++++++++++++++++++----
 src/responder/secrets/secsrv_private.h | 10 ++++-
 3 files changed, 86 insertions(+), 11 deletions(-)

diff --git a/src/responder/secrets/local.c b/src/responder/secrets/local.c
index 26c97a2..02007ad 100644
--- a/src/responder/secrets/local.c
+++ b/src/responder/secrets/local.c
@@ -26,6 +26,9 @@
 
 #define MKEY_SIZE (256 / 8)
 
+#define SECRETS_BASEDN  "cn=secrets"
+#define KCM_BASEDN      "cn=kcm"
+
 struct local_context {
     struct ldb_context *ldb;
     struct sec_data master_key;
@@ -119,6 +122,7 @@ static int local_encrypt(struct local_context *lctx, TALLOC_CTX *mem_ctx,
 
 static int local_db_dn(TALLOC_CTX *mem_ctx,
                        struct ldb_context *ldb,
+                       const char *basedn,
                        const char *req_path,
                        struct ldb_dn **req_dn)
 {
@@ -126,7 +130,7 @@ static int local_db_dn(TALLOC_CTX *mem_ctx,
     const char *s, *e;
     int ret;
 
-    dn = ldb_dn_new(mem_ctx, ldb, "cn=secrets");
+    dn = ldb_dn_new(mem_ctx, ldb, basedn);
     if (!dn) {
         ret = ENOMEM;
         goto done;
@@ -738,6 +742,11 @@ static int local_secrets_map_path(TALLOC_CTX *mem_ctx,
         lc_req->path = talloc_strdup(lc_req,
                                      secreq->mapped_path + (sizeof(SEC_BASEPATH) - 1));
         basedn = SECRETS_BASEDN;
+    } else if (strncmp(secreq->mapped_path,
+               SEC_KCM_BASEPATH, sizeof(SEC_KCM_BASEPATH) - 1) == 0) {
+        lc_req->path = talloc_strdup(lc_req,
+                                     secreq->mapped_path + (sizeof(SEC_KCM_BASEPATH) - 1));
+        basedn = KCM_BASEDN;
     } else {
         ret = EINVAL;
         goto done;
@@ -820,7 +829,10 @@ static struct tevent_req *local_secret_req(TALLOC_CTX *mem_ctx,
     DEBUG(SSSDBG_TRACE_LIBS, "Content-Type: %s\n", content_type);
 
     ret = local_secrets_map_path(state, lctx->ldb, secreq, &lc_req);
-    if (ret) goto done;
+    if (ret) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot map request path to local path\n");
+        goto done;
+    }
 
     switch (secreq->method) {
     case HTTP_GET:
diff --git a/src/responder/secrets/providers.c b/src/responder/secrets/providers.c
index eba555d..e787de9 100644
--- a/src/responder/secrets/providers.c
+++ b/src/responder/secrets/providers.c
@@ -24,6 +24,14 @@
 #include "responder/secrets/secsrv_proxy.h"
 #include <jansson.h>
 
+typedef int (*url_mapper_fn)(struct sec_req_ctx *secreq,
+                             char **mapped_path);
+
+struct url_pfx_router {
+    const char *prefix;
+    url_mapper_fn mapper_fn;
+};
+
 static int sec_map_url_to_user_path(struct sec_req_ctx *secreq,
                                     char **mapped_path)
 {
@@ -42,10 +50,43 @@ static int sec_map_url_to_user_path(struct sec_req_ctx *secreq,
         return ENOMEM;
     }
 
-    DEBUG(SSSDBG_TRACE_LIBS, "User-specific path is [%s]\n", *mapped_path);
+    DEBUG(SSSDBG_TRACE_LIBS,
+          "User-specific secrets path is [%s]\n", *mapped_path);
     return EOK;
 }
 
+static int kcm_map_url_to_user_path(struct sec_req_ctx *secreq,
+                                    char **mapped_path)
+{
+    uid_t c_euid;
+
+    c_euid = client_euid(secreq->cctx->creds);
+    if (c_euid != KCM_PEER_UID) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "UID %"SPRIuid" is not allowed to access "
+              "the "SEC_KCM_BASEPATH" hive\n",
+              c_euid);
+        return EPERM;
+    }
+
+    *mapped_path = talloc_strdup(secreq, secreq->parsed_url.path );
+    if (!*mapped_path) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to map request to user specific url\n");
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS,
+          "User-specific KCM path is [%s]\n", *mapped_path);
+    return EOK;
+}
+
+static struct url_pfx_router secrets_url_mapping[] = {
+    { SEC_BASEPATH, sec_map_url_to_user_path },
+    { SEC_KCM_BASEPATH, kcm_map_url_to_user_path },
+    { NULL, NULL },
+};
+
 int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq,
                     struct provider_handle **handle)
 {
@@ -55,21 +96,35 @@ int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq,
     char *provider;
     int num_sections;
     int ret;
+    url_mapper_fn mapper_fn = NULL;
 
     sctx = talloc_get_type(secreq->cctx->rctx->pvt_ctx, struct sec_ctx);
 
-    /* patch must start with /secrets/ for now */
-    ret = strncasecmp(secreq->parsed_url.path,
-                      SEC_BASEPATH, sizeof(SEC_BASEPATH) - 1);
-    if (ret != 0) {
+    for (int i = 0; secrets_url_mapping[i].prefix != NULL; i++) {
+        if (strncasecmp(secreq->parsed_url.path,
+                        secrets_url_mapping[i].prefix,
+                        strlen(secrets_url_mapping[i].prefix)) == 0) {
+            DEBUG(SSSDBG_TRACE_LIBS,
+                  "Mapping prefix %s\n", secrets_url_mapping[i].prefix);
+            mapper_fn = secrets_url_mapping[i].mapper_fn;
+            break;
+        }
+    }
+
+    if (mapper_fn == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE,
-              "Path [%s] does not start with "SEC_BASEPATH"\n",
+              "Path [%s] does not start with any allowed prefix\n",
               secreq->parsed_url.path);
         return EPERM;
     }
 
-    ret = sec_map_url_to_user_path(secreq, &secreq->mapped_path);
-    if (ret) return ret;
+    ret = mapper_fn(secreq, &secreq->mapped_path);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to map the user path [%d]: %s\n",
+              ret, sss_strerror(ret));
+        return ret;
+    }
 
     /* source default provider */
     ret = confdb_get_string(secreq->cctx->rctx->cdb, mem_ctx,
diff --git a/src/responder/secrets/secsrv_private.h b/src/responder/secrets/secsrv_private.h
index 1c3fbd8..a8544f6 100644
--- a/src/responder/secrets/secsrv_private.h
+++ b/src/responder/secrets/secsrv_private.h
@@ -101,7 +101,15 @@ int sec_get_provider(struct sec_ctx *sctx, const char *name,
                      struct provider_handle **out_handle);
 int sec_add_provider(struct sec_ctx *sctx, struct provider_handle *handle);
 
-#define SEC_BASEPATH "/secrets/"
+#define SEC_BASEPATH            "/secrets/"
+#define SEC_KCM_BASEPATH        "/kcm/"
+
+/* The KCM responder must "impersonate" the owner of the credentials.
+ * Only a trusted UID can do that -- root by default, but unit
+ * tests might choose otherwise */
+#ifndef KCM_PEER_UID
+#define KCM_PEER_UID            0
+#endif /* KCM_PEER_UID */
 
 /* providers.c */
 int sec_req_routing(TALLOC_CTX *mem_ctx, struct sec_req_ctx *secreq,

From 643cc572ad209d92ef54469457b774d419006f35 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 14 Mar 2017 15:34:57 +0100
Subject: [PATCH 17/21] TCURL: Support HTTP POST for creating containers

The curl integration must allow us to create containers, therefore we
also add support of the POST HTTP request type.
---
 src/tests/intg/test_secrets.py | 28 ++++++++++++++++++++++++++++
 src/tests/tcurl_test_tool.c    |  5 +++++
 src/util/tev_curl.c            |  7 +++++++
 src/util/tev_curl.h            |  1 +
 4 files changed, 41 insertions(+)

diff --git a/src/tests/intg/test_secrets.py b/src/tests/intg/test_secrets.py
index cbc1a1f..d71c190 100644
--- a/src/tests/intg/test_secrets.py
+++ b/src/tests/intg/test_secrets.py
@@ -271,6 +271,34 @@ def test_curlwrap_crd_ops(setup_for_secrets,
                        'http://localhost/secrets/foo'],
                       404)
 
+    # Create a container
+    run_curlwrap_tool([curlwrap_tool, '-o',
+                       '-v', '-s', sock_path,
+                       'http://localhost/secrets/cont/'],
+                      200)
+
+    # set a secret foo:bar
+    run_curlwrap_tool([curlwrap_tool, '-p',
+                       '-v', '-s', sock_path,
+                       'http://localhost/secrets/cont/cfoo',
+                       'foo_under_cont'],
+                      200)
+
+    # list secrets
+    output = run_curlwrap_tool([curlwrap_tool,
+                                '-v', '-s', sock_path,
+                                'http://localhost/secrets/cont/'],
+                               200)
+    assert "cfoo" in output
+
+    # get the foo secret
+    output = run_curlwrap_tool([curlwrap_tool,
+                                '-v', '-s', sock_path,
+                                'http://localhost/secrets/cont/cfoo'],
+                               200)
+    assert "foo_under_cont" in output
+
+
 
 def test_curlwrap_parallel(setup_for_secrets,
                            curlwrap_tool):
diff --git a/src/tests/tcurl_test_tool.c b/src/tests/tcurl_test_tool.c
index 38cea43..2af950e 100644
--- a/src/tests/tcurl_test_tool.c
+++ b/src/tests/tcurl_test_tool.c
@@ -88,6 +88,7 @@ int main(int argc, const char *argv[])
         { "get", 'g', POPT_ARG_NONE, NULL, 'g', "Perform a HTTP GET (default)", NULL },
         { "put", 'p', POPT_ARG_NONE, NULL, 'p', "Perform a HTTP PUT", NULL },
         { "del", 'd', POPT_ARG_NONE, NULL, 'd', "Perform a HTTP DELETE", NULL },
+        { "post", 'o', POPT_ARG_NONE, NULL, 'o', "Perform a HTTP POST", NULL },
         { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', "Print response code and body", NULL },
         POPT_TABLEEND
     };
@@ -118,6 +119,9 @@ int main(int argc, const char *argv[])
         case 'd':
             req_type = TCURL_HTTP_DELETE;
             break;
+        case 'o':
+            req_type = TCURL_HTTP_POST;
+            break;
         case 'v':
             pc_verbose = 1;
             break;
@@ -145,6 +149,7 @@ int main(int argc, const char *argv[])
         switch (req_type) {
         case TCURL_HTTP_GET:
         case TCURL_HTTP_DELETE:
+        case TCURL_HTTP_POST:
             urls[n_reqs++] = extra_arg_ptr;
             break;
         case TCURL_HTTP_PUT:
diff --git a/src/util/tev_curl.c b/src/util/tev_curl.c
index fd43665..645d118 100644
--- a/src/util/tev_curl.c
+++ b/src/util/tev_curl.c
@@ -154,6 +154,8 @@ static const char *http_req2str(enum tcurl_http_request req)
         return "PUT";
     case TCURL_HTTP_DELETE:
         return "DELETE";
+    case TCURL_HTTP_POST:
+        return "POST";
     }
 
     return "Uknown request type";
@@ -815,6 +817,11 @@ static errno_t tcurl_set_options(struct tcurl_http_state *state,
     }
 
     switch (req_type) {
+    case TCURL_HTTP_POST:
+        crv = curl_easy_setopt(state->http_handle,
+                               CURLOPT_CUSTOMREQUEST,
+                               "POST");
+        break;
     case TCURL_HTTP_PUT:
         /* CURLOPT_UPLOAD enables HTTP_PUT */
         crv = curl_easy_setopt(state->http_handle,
diff --git a/src/util/tev_curl.h b/src/util/tev_curl.h
index de0601d..444eb28 100644
--- a/src/util/tev_curl.h
+++ b/src/util/tev_curl.h
@@ -34,6 +34,7 @@ enum tcurl_http_request {
     TCURL_HTTP_GET,
     TCURL_HTTP_PUT,
     TCURL_HTTP_DELETE,
+    TCURL_HTTP_POST,
 };
 
 /**

From 053843988b2378143ade692841162fe5ada7b814 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 20 Mar 2017 11:49:43 +0100
Subject: [PATCH 18/21] KCM: Store ccaches in secrets

Adds a new KCM responder ccache back end that forwards all requests to
sssd-secrets.
---
 Makefile.am                                  |   39 +-
 contrib/sssd.spec.in                         |    1 +
 src/responder/kcm/kcmsrv_ccache.h            |   49 +
 src/responder/kcm/kcmsrv_ccache_json.c       |  922 +++++++++++
 src/responder/kcm/kcmsrv_ccache_secrets.c    | 2169 ++++++++++++++++++++++++++
 src/tests/cmocka/test_kcm_json_marshalling.c |  234 +++
 src/tests/intg/test_kcm.py                   |  132 +-
 src/util/util_errors.c                       |    2 +
 src/util/util_errors.h                       |    2 +
 9 files changed, 3526 insertions(+), 24 deletions(-)
 create mode 100644 src/responder/kcm/kcmsrv_ccache_json.c
 create mode 100644 src/responder/kcm/kcmsrv_ccache_secrets.c
 create mode 100644 src/tests/cmocka/test_kcm_json_marshalling.c

diff --git a/Makefile.am b/Makefile.am
index eb46828..3ef89a6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -298,6 +298,10 @@ if HAVE_INOTIFY
 non_interactive_cmocka_based_tests += test_inotify
 endif   # HAVE_INOTIFY
 
+if BUILD_KCM
+non_interactive_cmocka_based_tests += test_kcm_json
+endif   # BUILD_KCM
+
 if BUILD_SAMBA
 non_interactive_cmocka_based_tests += \
     ad_access_filter_tests \
@@ -1484,19 +1488,26 @@ sssd_kcm_SOURCES = \
     src/responder/kcm/kcmsrv_cmd.c \
     src/responder/kcm/kcmsrv_ccache.c \
     src/responder/kcm/kcmsrv_ccache_mem.c \
+    src/responder/kcm/kcmsrv_ccache_json.c \
+    src/responder/kcm/kcmsrv_ccache_secrets.c \
     src/responder/kcm/kcmsrv_ops.c \
     src/util/sss_sockets.c \
     src/util/sss_krb5.c \
     src/util/sss_iobuf.c \
+    src/util/tev_curl.c \
     $(SSSD_RESPONDER_OBJ) \
     $(NULL)
 sssd_kcm_CFLAGS = \
     $(AM_CFLAGS) \
     $(KRB5_CFLAGS) \
     $(UUID_CFLAGS) \
+    $(CURL_CFLAGS) \
+    $(JANSSON_CFLAGS) \
     $(NULL)
 sssd_kcm_LDADD = \
     $(KRB5_LIBS) \
+    $(CURL_LIBS) \
+    $(JANSSON_LIBS) \
     $(SSSD_LIBS) \
     $(UUID_LIBS) \
     $(SYSTEMD_DAEMON_LIBS) \
@@ -3277,6 +3288,27 @@ test_inotify_LDADD = \
     libsss_test_common.la \
     $(NULL)
 
+test_kcm_json_SOURCES = \
+    src/tests/cmocka/test_kcm_json_marshalling.c \
+    src/responder/kcm/kcmsrv_ccache_json.c \
+    src/responder/kcm/kcmsrv_ccache.c \
+    src/util/sss_krb5.c \
+    src/util/sss_iobuf.c \
+    $(NULL)
+test_kcm_json_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(UUID_CFLAGS) \
+    $(NULL)
+test_kcm_json_LDADD = \
+    $(JANSSON_LIBS) \
+    $(UUID_LIBS) \
+    $(KRB5_LIBS) \
+    $(CMOCKA_LIBS) \
+    $(SSSD_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    libsss_test_common.la \
+    $(NULL)
+
 endif # HAVE_CMOCKA
 
 noinst_PROGRAMS = pam_test_client
@@ -3323,6 +3355,11 @@ endif
 # Integration tests #
 #####################
 
+INTGCHECK_MAKEFLAGS = \
+	$(AM_MAKEFLAGS) \
+	CFLAGS="$(AM_CFLAGS) -DKCM_PEER_UID=$$(id -u)" \
+	$(NULL)
+
 intgcheck-prepare:
 	set -e; \
 	rm -Rf intg; \
@@ -3340,7 +3377,7 @@ intgcheck-prepare:
 	    --without-semanage \
 	    --enable-files-domain \
 	    $(INTGCHECK_CONFIGURE_FLAGS); \
-	$(MAKE) $(AM_MAKEFLAGS); \
+	$(MAKE) $(INTGCHECK_MAKEFLAGS) ; \
 	: Force single-thread install to workaround concurrency issues; \
 	$(MAKE) $(AM_MAKEFLAGS) -j1 install; \
 	: Remove .la files from LDB module directory to avoid loader warnings; \
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index 0cd139b..586f4cf 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -223,6 +223,7 @@ BuildRequires: systemtap-sdt-devel
 BuildRequires: http-parser-devel
 BuildRequires: jansson-devel
 BuildRequires: libuuid-devel
+BuildRequires: libcurl-devel
 
 %description
 Provides a set of daemons to manage access to remote directories and
diff --git a/src/responder/kcm/kcmsrv_ccache.h b/src/responder/kcm/kcmsrv_ccache.h
index 130ae48..18c8c47 100644
--- a/src/responder/kcm/kcmsrv_ccache.h
+++ b/src/responder/kcm/kcmsrv_ccache.h
@@ -303,4 +303,53 @@ void kcm_debug_uuid(uuid_t uuid);
  */
 errno_t kcm_check_name(const char *name, struct cli_creds *client);
 
+/*
+ * ccahe marshalling to and from JSON. This is used when the ccaches
+ * are stored in the secrets store
+ */
+
+/*
+ * The secrets store is a key-value store at heart. We store the UUID
+ * and the name in the key to allow easy lookups be either key
+ */
+bool sec_key_match_name(const char *sec_key,
+                        const char *name);
+
+bool sec_key_match_uuid(const char *sec_key,
+                        uuid_t uuid);
+
+const char *sec_key_get_name(const char *sec_key);
+
+errno_t sec_key_get_uuid(const char *sec_key,
+                         uuid_t uuid);
+
+/* Create a URL for the default client's ccache */
+const char *sec_dfl_url_create(TALLOC_CTX *mem_ctx,
+                               struct cli_creds *client);
+
+/* Create a URL for the client's ccache container */
+const char *sec_container_url_create(TALLOC_CTX *mem_ctx,
+                                     struct cli_creds *client);
+
+const char *sec_cc_url_create(TALLOC_CTX *mem_ctx,
+                              struct cli_creds *client,
+                              const char *sec_key);
+
+/*
+ * sec_key is a concatenation of the ccache's UUID and name
+ * sec_value is the JSON dump of the ccache contents
+ */
+errno_t sec_kv_to_ccache(TALLOC_CTX *mem_ctx,
+                         const char *sec_key,
+                         const char *sec_value,
+                         struct cli_creds *client,
+                         struct kcm_ccache **_cc);
+
+/* Convert a kcm_ccache to a key-value pair to be stored in secrets */
+errno_t kcm_ccache_to_sec_input(TALLOC_CTX *mem_ctx,
+                                struct kcm_ccache *cc,
+                                struct cli_creds *client,
+                                const char **_url,
+                                struct sss_iobuf **_payload);
+
 #endif /* _KCMSRV_CCACHE_H_ */
diff --git a/src/responder/kcm/kcmsrv_ccache_json.c b/src/responder/kcm/kcmsrv_ccache_json.c
new file mode 100644
index 0000000..97ddae8
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ccache_json.c
@@ -0,0 +1,922 @@
+/*
+   SSSD
+
+   KCM Server - ccache JSON (un)marshalling for storing ccaches in
+                sssd-secrets
+
+   Copyright (C) Red Hat, 2017
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <talloc.h>
+#include <jansson.h>
+
+#include "util/util.h"
+#include "util/util_creds.h"
+#include "util/crypto/sss_crypto.h"
+#include "responder/kcm/kcmsrv_ccache_pvt.h"
+
+/* The base for storing secrets is:
+ *  http://localhost/kcm/persistent/$uid
+ *
+ * Under $base, there are two containers:
+ *  /ccache     - stores the ccaches
+ *  /ntlm       - stores NTLM creds [Not implement yet]
+ *
+ * There is also a special entry that contains the UUID of the default
+ * cache for this UID:
+ *  /default    - stores the UUID of the default ccache for this UID
+ *
+ * Each ccache has a name and an UUID. On the secrets level, the 'secret'
+ * is a concatenation of the stringified UUID and the name separated
+ * by a plus-sign.
+ */
+#define KCM_SEC_URL        "http://localhost/kcm/persistent"
+#define KCM_SEC_BASE_FMT    KCM_SEC_URL"/%"SPRIuid"/"
+#define KCM_SEC_CCACHE_FMT  KCM_SEC_BASE_FMT"ccache/"
+#define KCM_SEC_DFL_FMT     KCM_SEC_BASE_FMT"default"
+
+
+/*
+ * We keep the JSON representation of the ccache versioned to allow
+ * us to modify the format in a future version
+ */
+#define KS_JSON_VERSION     1
+
+/*
+ * The secrets store is a key-value store at heart. We store the UUID
+ * and the name in the key to allow easy lookups be either key
+ */
+#define SEC_KEY_SEPARATOR   '-'
+
+/* Compat definition of json_array_foreach for older systems */
+#ifndef json_array_foreach
+#define json_array_foreach(array, idx, value) \
+    for(idx = 0; \
+            idx < json_array_size(array) && (value = json_array_get(array, idx)); \
+            idx++)
+#endif
+
+const char *sec_container_url_create(TALLOC_CTX *mem_ctx,
+                                     struct cli_creds *client)
+{
+    return talloc_asprintf(mem_ctx,
+                           KCM_SEC_CCACHE_FMT,
+                           cli_creds_get_uid(client));
+}
+
+const char *sec_cc_url_create(TALLOC_CTX *mem_ctx,
+                              struct cli_creds *client,
+                              const char *sec_key)
+{
+    return talloc_asprintf(mem_ctx,
+                           KCM_SEC_CCACHE_FMT"%s",
+                           cli_creds_get_uid(client),
+                           sec_key);
+}
+
+const char *sec_dfl_url_create(TALLOC_CTX *mem_ctx,
+                               struct cli_creds *client)
+{
+    return talloc_asprintf(mem_ctx,
+                           KCM_SEC_DFL_FMT,
+                           cli_creds_get_uid(client));
+}
+
+static const char *sec_key_create(TALLOC_CTX *mem_ctx,
+                                  const char *name,
+                                  uuid_t uuid)
+{
+    char uuid_str[UUID_STR_SIZE];
+
+    uuid_unparse(uuid, uuid_str);
+    return talloc_asprintf(mem_ctx,
+                           "%s%c%s", uuid_str, SEC_KEY_SEPARATOR, name);
+}
+
+static errno_t sec_key_parse(TALLOC_CTX *mem_ctx,
+                             const char *sec_key,
+                             const char **_name,
+                             uuid_t uuid)
+{
+    char uuid_str[UUID_STR_SIZE];
+
+    if (strlen(sec_key) < UUID_STR_SIZE + 2) {
+        /* One char for separator and at least one for the name */
+        DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key);
+        return EINVAL;
+    }
+
+    strncpy(uuid_str, sec_key, sizeof(uuid_str)-1);
+    if (sec_key[UUID_STR_SIZE - 1] != SEC_KEY_SEPARATOR) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n");
+        return EINVAL;
+    }
+    uuid_str[UUID_STR_SIZE-1] = '\0';
+
+    *_name = talloc_strdup(mem_ctx, sec_key + UUID_STR_SIZE);
+    if (*_name == NULL) {
+        return ENOMEM;
+    }
+    uuid_parse(uuid_str, uuid);
+
+    return EOK;
+}
+
+errno_t sec_key_get_uuid(const char *sec_key,
+                         uuid_t uuid)
+{
+    char uuid_str[UUID_STR_SIZE];
+
+    if (strlen(sec_key) < UUID_STR_SIZE + 2) {
+        /* One char for separator and at least one for the name */
+        DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key);
+        return EINVAL;
+    }
+
+    if (sec_key[UUID_STR_SIZE-1] != SEC_KEY_SEPARATOR) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n");
+        return EINVAL;
+    }
+
+    strncpy(uuid_str, sec_key, UUID_STR_SIZE-1);
+    uuid_str[UUID_STR_SIZE-1] = '\0';
+    uuid_parse(uuid_str, uuid);
+    return EOK;
+}
+
+const char *sec_key_get_name(const char *sec_key)
+{
+    if (strlen(sec_key) < UUID_STR_SIZE + 2) {
+        /* One char for separator and at least one for the name */
+        DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key);
+        return NULL;
+    }
+
+    return sec_key + UUID_STR_SIZE;
+}
+
+bool sec_key_match_name(const char *sec_key,
+                        const char *name)
+{
+    if (strlen(sec_key) < UUID_STR_SIZE + 2) {
+        /* One char for separator and at least one for the name */
+        DEBUG(SSSDBG_MINOR_FAILURE, "Key %s is too short\n", sec_key);
+        return false;
+    }
+
+    return strcmp(sec_key + UUID_STR_SIZE, name) == 0;
+}
+
+bool sec_key_match_uuid(const char *sec_key,
+                        uuid_t uuid)
+{
+    errno_t ret;
+    uuid_t key_uuid;
+
+    ret = sec_key_get_uuid(sec_key, key_uuid);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Cannot convert key to UUID\n");
+        return false;
+    }
+
+    return uuid_compare(key_uuid, uuid) == 0;
+}
+
+/*
+ * Creates an array of principal elements that will be used later
+ * in the form of:
+ *          "componenets": [ "elem1", "elem2", ...]
+ */
+static json_t *princ_data_to_json(TALLOC_CTX *mem_ctx,
+                                  krb5_principal princ)
+{
+    json_t *jdata = NULL;
+    json_t *data_array = NULL;
+    int ret;
+    char *str_princ_data;
+
+    data_array = json_array();
+    if (data_array == NULL) {
+        return NULL;
+    }
+
+    for (ssize_t i = 0; i < princ->length; i++) {
+        /* FIXME - it might be cleaner to use stringn here, but the libjansson
+         * version on RHEL-7 doesn't support that
+         */
+        str_princ_data = talloc_zero_array(mem_ctx,
+                                           char,
+                                           princ->data[i].length + 1);
+        if (str_princ_data == NULL) {
+            return NULL;
+        }
+        memcpy(str_princ_data, princ->data[i].data, princ->data[i].length);
+        str_princ_data[princ->data[i].length] = '\0';
+
+        jdata = json_string(str_princ_data);
+        talloc_free(str_princ_data);
+        if (jdata == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot convert principal data to string\n");
+            json_decref(data_array);
+            return NULL;
+        }
+
+        ret = json_array_append_new(data_array, jdata);
+        if (ret != 0) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot append principal data to array\n");
+            json_decref(jdata);
+            json_decref(data_array);
+            return NULL;
+        }
+        /* data_array now owns the reference to jdata */
+    }
+
+    return data_array;
+}
+
+/* Creates:
+ *      {
+ *          "type": "number",
+ *          "realm": "string",
+ *          "componenents": [ "elem1", "elem2", ...]
+ *      }
+ */
+static json_t *princ_to_json(TALLOC_CTX *mem_ctx,
+                             krb5_principal princ)
+{
+    json_t *jprinc = NULL;
+    json_t *components = NULL;
+    json_error_t error;
+    char *str_realm_data;
+
+    components = princ_data_to_json(mem_ctx, princ);
+    if (components == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot convert principal data to JSON\n");
+        return NULL;
+    }
+
+    /* FIXME - it might be cleaner to use the s% specifier here, but the libjansson
+     * version on RHEL-7 doesn't support that
+     */
+    str_realm_data = talloc_zero_array(mem_ctx,
+                                        char,
+                                        princ->realm.length + 1);
+    if (str_realm_data == NULL) {
+        return NULL;
+    }
+    memcpy(str_realm_data, princ->realm.data, princ->realm.length);
+    str_realm_data[princ->realm.length] = '\0';
+
+    jprinc = json_pack_ex(&error,
+                          JSON_STRICT,
+                          "{s:i, s:s, s:o}",
+                          "type", princ->type,
+                          "realm", str_realm_data,
+                          "components", components);
+    talloc_free(str_realm_data);
+    if (jprinc == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to pack JSON princ structure on line %d: %s\n",
+              error.line, error.text);
+        json_decref(components);
+        return NULL;
+    }
+
+    return jprinc;
+}
+
+/* Creates:
+ *          {
+ *              "uuid": <data>,
+ *              "payload": <data>,
+ *          },
+ */
+static json_t *cred_to_json(struct kcm_cred *crd)
+{
+    char uuid_str[UUID_STR_SIZE];
+    uint8_t *cred_blob_data;
+    size_t cred_blob_size;
+    json_t *jcred;
+    json_error_t error;
+    char *base64_cred_blob;
+
+    uuid_unparse(crd->uuid, uuid_str);
+    cred_blob_data = sss_iobuf_get_data(crd->cred_blob);
+    cred_blob_size = sss_iobuf_get_size(crd->cred_blob);
+
+    base64_cred_blob = sss_base64_encode(crd, cred_blob_data, cred_blob_size);
+    if (base64_cred_blob == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot base64 encode the certificate blob\n");
+        return NULL;
+    }
+
+    jcred = json_pack_ex(&error,
+                         JSON_STRICT,
+                         "{s:s, s:s}",
+                         "uuid", uuid_str,
+                         "payload", base64_cred_blob);
+    talloc_free(base64_cred_blob);
+    if (jcred == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to pack JSON cred structure on line %d: %s\n",
+              error.line, error.text);
+        return NULL;
+    }
+    return jcred;
+}
+
+/*
+ * Creates:
+ *      [
+ *          {
+ *              "uuid": <data>,
+ *              "payload": <data>,
+ *          },
+ *          ...
+ *      ]
+ */
+static json_t *creds_to_json_array(struct kcm_cred *creds)
+{
+    struct kcm_cred *crd;
+    json_t *array;
+    json_t *jcred;
+
+    array = json_array();
+    if (array == NULL) {
+        return NULL;
+    }
+
+    DLIST_FOR_EACH(crd, creds) {
+        jcred = cred_to_json(crd);
+        if (jcred == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot convert credentials to JSON\n");
+            json_decref(array);
+            return NULL;
+        }
+
+        json_array_append_new(array, jcred);
+        /* array now owns jcred */
+        jcred = NULL;
+    }
+
+    return array;
+}
+
+/*
+ * The ccache is formatted in JSON as:
+ * {
+ *      version: number
+ *      kdc_offset: number
+ *      principal : {
+ *          "type": "number",
+ *          "realm": "string",
+ *          "componenents": [ "elem1", "elem2", ...]
+ *      }
+ *      creds : [
+ *                  {
+ *                      "uuid": <data>,
+ *                      "payload": <data>,
+ *                  },
+ *                  {
+ *                      ...
+ *                  }
+ *             ]
+ *      }
+ * }
+ */
+static json_t *ccache_to_json(struct kcm_ccache *cc)
+{
+    json_t *princ = NULL;
+    json_t *creds = NULL;
+    json_t *jcc = NULL;
+    json_error_t error;
+
+    princ = princ_to_json(cc, cc->client);
+    if (princ == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot convert princ to JSON\n");
+        return NULL;
+    }
+
+    creds = creds_to_json_array(cc->creds);
+    if (creds == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot convert creds to JSON array\n");
+        json_decref(princ);
+        return NULL;
+    }
+
+    jcc = json_pack_ex(&error,
+                       JSON_STRICT,
+                       "{s:i, s:i, s:o, s:o}",
+                       "version", KS_JSON_VERSION,
+                       "kdc_offset", cc->kdc_offset,
+                       "principal", princ,
+                       "creds", creds);
+    if (jcc == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to pack JSON ccache structure on line %d: %s\n",
+              error.line, error.text);
+        json_decref(creds);
+        json_decref(princ);
+        return NULL;
+    }
+
+    return jcc;
+}
+
+static errno_t ccache_to_sec_kv(TALLOC_CTX *mem_ctx,
+                                struct kcm_ccache *cc,
+                                const char **_sec_key,
+                                const char **_sec_value)
+{
+    json_t *jcc = NULL;
+    char *jdump;
+
+    jcc = ccache_to_json(cc);
+    if (jcc == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot convert ccache to JSON\n");
+        return ERR_JSON_ENCODING;
+    }
+
+    /* it would be more efficient to learn the size with json_dumpb and
+     * a NULL buffer, but that's only available since 2.10
+     */
+    jdump = json_dumps(jcc, JSON_INDENT(4) | JSON_ENSURE_ASCII);
+    if (jdump == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot dump JSON\n");
+        return ERR_JSON_ENCODING;
+    }
+
+    *_sec_key = sec_key_create(mem_ctx, cc->name, cc->uuid);
+    *_sec_value = talloc_strdup(mem_ctx, jdump);
+    free(jdump);
+    json_decref(jcc);
+    if (*_sec_key == NULL || *_sec_value == NULL) {
+        return ENOMEM;
+    }
+
+    return EOK;
+}
+
+errno_t kcm_ccache_to_sec_input(TALLOC_CTX *mem_ctx,
+                                struct kcm_ccache *cc,
+                                struct cli_creds *client,
+                                const char **_url,
+                                struct sss_iobuf **_payload)
+{
+    errno_t ret;
+    const char *key;
+    const char *value;
+    const char *url;
+    struct sss_iobuf *payload;
+    TALLOC_CTX *tmp_ctx;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    ret = ccache_to_sec_kv(mem_ctx, cc, &key, &value);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot convert cache %s to JSON [%d]: %s\n",
+              cc->name, ret, sss_strerror(ret));
+        goto done;
+    }
+
+    url = sec_cc_url_create(tmp_ctx, client, key);
+    if (url == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    payload = sss_iobuf_init_readonly(tmp_ctx,
+                                      (const uint8_t *) value,
+                                      strlen(value)+1);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot create payload buffer\n");
+        goto done;
+    }
+
+    ret = EOK;
+    *_url = talloc_steal(mem_ctx, url);
+    *_payload = talloc_steal(mem_ctx, payload);
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t sec_value_to_json(const char *input,
+                                 json_t **_root)
+{
+    json_t *root = NULL;
+    json_error_t error;
+    int ok;
+
+    root = json_loads(input, 0, &error);
+    if (root == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to parse JSON payload on line %d: %s\n",
+              error.line, error.text);
+        return ERR_JSON_DECODING;
+    }
+
+    ok = json_is_object(root);
+    if (!ok) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Json data is not an object.\n");
+        json_decref(root);
+        return ERR_JSON_DECODING;
+    }
+
+    *_root = root;
+    return EOK;
+}
+
+/*
+ * ccache unmarshalling from JSON
+ */
+static errno_t json_element_to_krb5_data(TALLOC_CTX *mem_ctx,
+                                         json_t *element,
+                                         krb5_data *data)
+{
+    const char *str_value;
+    size_t str_len;
+
+    /* FIXME - it might be cleaner to use stringn here, but the libjansson
+     * version on RHEL-7 doesn't support that
+     */
+    str_value = json_string_value(element);
+    if (str_value == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "JSON element not a string\n");
+        return EINVAL;
+    }
+    str_len = strlen(str_value);
+
+    data->data = talloc_strndup(mem_ctx, str_value, str_len);
+    if (data->data == NULL) {
+        return ENOMEM;
+    }
+    data->length = str_len;
+
+    return EOK;
+}
+
+static errno_t json_array_to_krb5_data(TALLOC_CTX *mem_ctx,
+                                       json_t *array,
+                                       krb5_data **_data,
+                                       size_t *_len)
+{
+    errno_t ret;
+    int ok;
+    size_t len;
+    size_t idx;
+    json_t *element;
+    krb5_data *data;
+
+    ok = json_is_array(array);
+    if (!ok) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Json object is not an array.\n");
+        return ERR_JSON_DECODING;
+    }
+
+    len = json_array_size(array);
+    if (len == 0) {
+        *_data = NULL;
+        *_len = 0;
+        return EOK;
+    }
+
+    data = talloc_zero_array(mem_ctx, krb5_data, len);
+    if (data == NULL) {
+        return ENOMEM;
+    }
+
+    json_array_foreach(array, idx, element) {
+        ret = json_element_to_krb5_data(data, element, &data[idx]);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot convert krb5 data element from JSON");
+            talloc_free(data);
+            return ret;
+        }
+    }
+
+    *_data = data;
+    *_len = len;
+    return EOK;
+}
+
+static errno_t json_to_princ(TALLOC_CTX *mem_ctx,
+                             json_t *js_princ,
+                             krb5_principal *_princ)
+{
+    errno_t ret;
+    json_t *components = NULL;
+    int ok;
+    krb5_principal princ = NULL;
+    TALLOC_CTX *tmp_ctx = NULL;
+    char *realm_str;
+    size_t realm_size;
+    json_error_t error;
+
+    ok = json_is_object(js_princ);
+    if (!ok) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Json principal is not an object.\n");
+        ret = ERR_JSON_DECODING;
+        goto done;
+    }
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    princ = talloc_zero(tmp_ctx, struct krb5_principal_data);
+    if (princ == NULL) {
+        return ENOMEM;
+    }
+    princ->magic = KV5M_PRINCIPAL;
+
+    /* FIXME - it might be cleaner to use the s% specifier here, but the libjansson
+     * version on RHEL-7 doesn't support that
+     */
+    ret = json_unpack_ex(js_princ,
+                         &error,
+                         JSON_STRICT,
+                         "{s:i, s:s, s:o}",
+                         "type", &princ->type,
+                         "realm", &realm_str,
+                         "components", &components);
+    if (ret != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to unpack JSON princ structure on line %d: %s\n",
+              error.line, error.text);
+        ret = EINVAL;
+        goto done;
+    }
+
+    realm_size = strlen(realm_str);
+
+    princ->realm.data = talloc_strndup(mem_ctx, realm_str, realm_size);
+    if (princ->realm.data == NULL) {
+        return ENOMEM;
+    }
+    princ->realm.length = realm_size;
+    princ->realm.magic = 0;
+
+    ret = json_array_to_krb5_data(princ, components,
+                                  &princ->data,
+                                  (size_t *) &princ->length);
+    if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot convert principal from JSON");
+        ret = EINVAL;
+        goto done;
+    }
+
+    *_princ = talloc_steal(mem_ctx, princ);
+    ret = EOK;
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t json_elem_to_cred(TALLOC_CTX *mem_ctx,
+                                 json_t *element,
+                                 struct kcm_cred **_crd)
+{
+    errno_t ret;
+    char *uuid_str;
+    json_error_t error;
+    uuid_t uuid;
+    struct sss_iobuf *cred_blob;
+    const char *base64_cred_blob;
+    struct kcm_cred *crd;
+    uint8_t *outbuf;
+    size_t outbuf_size;
+    TALLOC_CTX *tmp_ctx = NULL;
+
+    ret = json_unpack_ex(element,
+                         &error,
+                         JSON_STRICT,
+                         "{s:s, s:s}",
+                         "uuid", &uuid_str,
+                         "payload", &base64_cred_blob);
+    if (ret != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to unpack JSON cred structure on line %d: %s\n",
+              error.line, error.text);
+        return EINVAL;
+    }
+
+    uuid_parse(uuid_str, uuid);
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    outbuf = sss_base64_decode(tmp_ctx, base64_cred_blob, &outbuf_size);
+    if (outbuf == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot decode cred blob\n");
+        ret = EIO;
+        goto done;
+    }
+
+    cred_blob = sss_iobuf_init_readonly(tmp_ctx, outbuf, outbuf_size);
+    if (cred_blob == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    crd = kcm_cred_new(tmp_ctx, uuid, cred_blob);
+    if (crd == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = EOK;
+    *_crd = talloc_steal(mem_ctx, crd);
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t json_to_creds(struct kcm_ccache *cc,
+                             json_t *jcreds)
+{
+    errno_t ret;
+    int ok;
+    size_t idx;
+    json_t *value;
+    struct kcm_cred *crd;
+
+    ok = json_is_array(jcreds);
+    if (!ok) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Json creds object is not an array.\n");
+        return ERR_JSON_DECODING;
+    }
+
+    json_array_foreach(jcreds, idx, value) {
+        ret = json_elem_to_cred(cc, value, &crd);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot convert JSON cred element [%d]: %s\n",
+                  ret, sss_strerror(ret));
+            return ret;
+        }
+
+        ret = kcm_cc_store_creds(cc, crd);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Cannot store creds in ccache [%d]: %s\n",
+                  ret, sss_strerror(ret));
+            return ret;
+        }
+    }
+
+    return EOK;
+}
+
+static errno_t sec_json_value_to_ccache(struct kcm_ccache *cc,
+                                        json_t *root)
+{
+    errno_t ret;
+    json_t *princ = NULL;
+    json_t *creds = NULL;
+    json_error_t error;
+    int version;
+
+    ret = json_unpack_ex(root,
+                         &error,
+                         JSON_STRICT,
+                         "{s:i, s:i, s:o, s:o}",
+                         "version", &version,
+                         "kdc_offset", &cc->kdc_offset,
+                         "principal", &princ,
+                         "creds", &creds);
+    if (ret != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to unpack JSON creds structure on line %d: %s\n",
+              error.line, error.text);
+        return EINVAL;
+    }
+
+    if (version != KS_JSON_VERSION) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Expected version %d, received version %d\n",
+              KS_JSON_VERSION, version);
+        return EINVAL;
+    }
+
+    ret = json_to_princ(cc, princ, &cc->client);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot store JSON to principal [%d]: %s\n",
+              ret, sss_strerror(ret));
+        return ret;
+    }
+
+    ret = json_to_creds(cc, creds);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot store JSON to creds [%d]: %s\n",
+              ret, sss_strerror(ret));
+        return EOK;
+    }
+
+    return EOK;
+}
+
+/*
+ * sec_key is a concatenation of the ccache's UUID and name
+ * sec_value is the JSON dump of the ccache contents
+ */
+errno_t sec_kv_to_ccache(TALLOC_CTX *mem_ctx,
+                         const char *sec_key,
+                         const char *sec_value,
+                         struct cli_creds *client,
+                         struct kcm_ccache **_cc)
+{
+    errno_t ret;
+    json_t *root = NULL;
+    struct kcm_ccache *cc = NULL;
+    TALLOC_CTX *tmp_ctx = NULL;
+
+    ret = sec_value_to_json(sec_value, &root);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot store secret to JSN [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto done;
+    }
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    cc = talloc_zero(tmp_ctx, struct kcm_ccache);
+    if (cc == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* We rely on sssd-secrets only searching the user's subtree so we
+     * set the ownership to the client
+     */
+    cc->owner.uid = cli_creds_get_uid(client);
+    cc->owner.gid = cli_creds_get_gid(client);
+
+    ret = sec_key_parse(cc, sec_key, &cc->name, cc->uuid);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannt parse secret key [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto done;
+    }
+
+    ret = sec_json_value_to_ccache(cc, root);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannt parse secret value [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto done;
+    }
+
+    ret = EOK;
+    *_cc = talloc_steal(mem_ctx, cc);
+done:
+    talloc_free(tmp_ctx);
+    json_decref(root);
+    return ret;
+}
+
diff --git a/src/responder/kcm/kcmsrv_ccache_secrets.c b/src/responder/kcm/kcmsrv_ccache_secrets.c
new file mode 100644
index 0000000..8be7dae
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_ccache_secrets.c
@@ -0,0 +1,2169 @@
+/*
+   SSSD
+
+   KCM Server - ccache storage in sssd-secrets
+
+   Copyright (C) Red Hat, 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <talloc.h>
+#include <jansson.h>
+
+#include "util/util.h"
+#include "util/crypto/sss_crypto.h"
+#include "util/tev_curl.h"
+#include "responder/kcm/kcmsrv_ccache_pvt.h"
+#include "responder/kcm/kcmsrv_ccache_be.h"
+
+#ifndef SSSD_SECRETS_SOCKET
+#define SSSD_SECRETS_SOCKET VARDIR"/run/secrets.socket"
+#endif  /* SSSD_SECRETS_SOCKET */
+
+#ifndef SEC_TIMEOUT
+#define SEC_TIMEOUT         5
+#endif /* SEC_TIMEOUT */
+
+/* Just to keep the name of the ccache readable */
+#define MAX_CC_NUM          99999
+
+/* Compat definition of json_array_foreach for older systems */
+#ifndef json_array_foreach
+#define json_array_foreach(array, idx, value) \
+    for(idx = 0; \
+            idx < json_array_size(array) && (value = json_array_get(array, idx)); \
+            idx++)
+#endif
+
+static const char *find_by_name(const char **sec_key_list,
+                                const char *name)
+{
+    const char *sec_name = NULL;
+
+    if (sec_key_list == NULL) {
+        return NULL;
+    }
+
+    for (int i = 0; sec_key_list[i]; i++) {
+        if (sec_key_match_name(sec_key_list[i], name)) {
+            sec_name = sec_key_list[i];
+            break;
+        }
+    }
+
+    return sec_name;
+}
+
+static const char *find_by_uuid(const char **sec_key_list,
+                                uuid_t uuid)
+{
+    const char *sec_name = NULL;
+
+    if (sec_key_list == NULL) {
+        return NULL;
+    }
+
+    for (int i = 0; sec_key_list[i]; i++) {
+        if (sec_key_match_uuid(sec_key_list[i], uuid)) {
+            sec_name = sec_key_list[i];
+            break;
+        }
+    }
+
+    return sec_name;
+}
+
+static const char *sec_headers[] = {
+    "Content-type: application/octet-stream",
+    NULL,
+};
+
+struct ccdb_sec {
+    struct tcurl_ctx *tctx;
+};
+
+static errno_t http2errno(int http_code)
+{
+    if (http_code != 200) {
+        DEBUG(SSSDBG_OP_FAILURE, "HTTP request returned %d\n", http_code);
+    }
+
+    switch (http_code) {
+    case 200:
+        return EOK;
+    case 404:
+        return ERR_NO_CREDS;
+    case 400:
+        return ERR_INPUT_PARSE;
+    case 403:
+        return EACCES;
+    case 409:
+        return EEXIST;
+    case 413:
+        return E2BIG;
+    case 507:
+        return ENOSPC;
+    }
+
+    return EIO;
+}
+
+/*
+ * Helper request to list all UUID+name pairs
+ */
+struct sec_list_state {
+    const char **sec_key_list;
+    size_t sec_key_list_len;
+};
+
+static void sec_list_done(struct tevent_req *subreq);
+static errno_t sec_list_parse(struct sss_iobuf *outbuf,
+                              TALLOC_CTX *mem_ctx,
+                              const char ***_list,
+                              size_t *_list_len);
+
+static struct tevent_req *sec_list_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct ccdb_sec *secdb,
+                                        struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct sec_list_state *state = NULL;
+    errno_t ret;
+    const char *container_url;
+
+    req = tevent_req_create(mem_ctx, &state, struct sec_list_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all ccaches in the secrets store\n");
+    container_url = sec_container_url_create(state, client);
+    if (container_url == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    subreq = tcurl_http_send(state, ev, secdb->tctx,
+                             TCURL_HTTP_GET,
+                             SSSD_SECRETS_SOCKET,
+                             container_url,
+                             sec_headers,
+                             NULL,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, sec_list_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void sec_list_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct sec_list_state *state = tevent_req_data(req,
+                                                struct sec_list_state);
+    struct sss_iobuf *outbuf;
+    int http_code;
+
+    ret = tcurl_http_recv(state, subreq, &http_code, &outbuf);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "list HTTP request failed [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code == 404) {
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Nothing to list\n");
+        /* If no ccaches are found, return an empty list */
+        state->sec_key_list = talloc_zero_array(state, const char *, 1);
+        if (state->sec_key_list == NULL) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+    } else if (http_code == 200) {
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu items\n", state->sec_key_list_len);
+        ret = sec_list_parse(outbuf, state,
+                             &state->sec_key_list,
+                             &state->sec_key_list_len);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+    } else {
+        tevent_req_error(req, http2errno(http_code));
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "list done\n");
+    tevent_req_done(req);
+}
+
+static errno_t sec_list_parse(struct sss_iobuf *outbuf,
+                              TALLOC_CTX *mem_ctx,
+                              const char ***_list,
+                              size_t *_list_len)
+{
+    json_t *root;
+    uint8_t *sec_http_list;
+    json_error_t error;
+    json_t *element;
+    errno_t ret;
+    int ok;
+    size_t idx;
+    const char **list = NULL;
+    size_t list_len;
+
+    sec_http_list = sss_iobuf_get_data(outbuf);
+    if (sec_http_list == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "No data in output buffer?\n");
+        return EINVAL;
+    }
+
+    root = json_loads((const char *) sec_http_list, 0, &error);
+    if (root == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+                "Failed to parse JSON payload on line %d: %s\n",
+                error.line, error.text);
+        return ERR_JSON_DECODING;
+    }
+
+    ok = json_is_array(root);
+    if (!ok) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "list reply is not an object.\n");
+        ret = ERR_JSON_DECODING;
+        goto done;
+    }
+
+    list_len = json_array_size(root);
+    list = talloc_zero_array(mem_ctx, const char *, list_len + 1);
+    if (list == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    json_array_foreach(root, idx, element) {
+        list[idx] = talloc_strdup(list, json_string_value(element));
+        if (list[idx] == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    ret = EOK;
+    *_list = list;
+    *_list_len = list_len;
+done:
+    if (ret != EOK) {
+        talloc_free(list);
+    }
+    json_decref(root);
+    return ret;
+}
+
+static errno_t sec_list_recv(struct tevent_req *req,
+                             TALLOC_CTX *mem_ctx,
+                             const char ***_sec_key_list,
+                             size_t *_sec_key_list_len)
+
+{
+    struct sec_list_state *state = tevent_req_data(req,
+                                                struct sec_list_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (_sec_key_list != NULL) {
+        *_sec_key_list = talloc_steal(mem_ctx, state->sec_key_list);
+    }
+    if (_sec_key_list_len != NULL) {
+        *_sec_key_list_len = state->sec_key_list_len;
+    }
+    return EOK;
+}
+
+/*
+ * Helper request to get a ccache by key
+ */
+struct sec_get_state {
+    const char *sec_key;
+    struct cli_creds *client;
+
+    struct kcm_ccache *cc;
+};
+
+static void sec_get_done(struct tevent_req *subreq);
+
+static struct tevent_req *sec_get_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       struct ccdb_sec *secdb,
+                                       struct cli_creds *client,
+                                       const char *sec_key)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct sec_get_state *state = NULL;
+    errno_t ret;
+    const char *cc_url;
+
+    req = tevent_req_create(mem_ctx, &state, struct sec_get_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->sec_key = sec_key;
+    state->client = client;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Retrieving ccache %s\n", sec_key);
+
+    cc_url = sec_cc_url_create(state, state->client, state->sec_key);
+    if (cc_url == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    subreq = tcurl_http_send(state,
+                             ev,
+                             secdb->tctx,
+                             TCURL_HTTP_GET,
+                             SSSD_SECRETS_SOCKET,
+                             cc_url,
+                             sec_headers,
+                             NULL,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, sec_get_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void sec_get_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct sec_get_state *state = tevent_req_data(req,
+                                                struct sec_get_state);
+    struct sss_iobuf *outbuf;
+    const char *sec_value;
+    int http_code;
+
+    ret = tcurl_http_recv(state, subreq, &http_code, &outbuf);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "GET HTTP request failed [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code != 200) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "GET operation returned HTTP error %d\n", http_code);
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    sec_value = (const char *) sss_iobuf_get_data(outbuf);
+    if (sec_value == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "No data in output buffer\n");
+        tevent_req_error(req, EINVAL);
+        return;
+    }
+
+    ret = sec_kv_to_ccache(state,
+                           state->sec_key,
+                           sec_value,
+                           state->client,
+                           &state->cc);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot convert JSON keyval to ccache blob [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "GET done\n");
+    tevent_req_done(req);
+}
+
+static errno_t sec_get_recv(struct tevent_req *req,
+                            TALLOC_CTX *mem_ctx,
+                            struct kcm_ccache **_cc)
+{
+    struct sec_get_state *state = tevent_req_data(req, struct sec_get_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+/*
+ * Helper request to get a ccache name or ID
+ */
+struct sec_get_ccache_state {
+    struct tevent_context *ev;
+    struct ccdb_sec *secdb;
+    struct cli_creds *client;
+    const char *name;
+    uuid_t uuid;
+
+    const char *sec_key;
+
+    struct kcm_ccache *cc;
+};
+
+static void sec_get_ccache_list_done(struct tevent_req *subreq);
+static void sec_get_ccache_done(struct tevent_req *subreq);
+
+static struct tevent_req *sec_get_ccache_send(TALLOC_CTX *mem_ctx,
+                                              struct tevent_context *ev,
+                                              struct ccdb_sec *secdb,
+                                              struct cli_creds *client,
+                                              const char *name,
+                                              uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct sec_get_ccache_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct sec_get_ccache_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->secdb = secdb;
+    state->client = client;
+    state->name = name;
+    uuid_copy(state->uuid, uuid);
+
+    if ((name == NULL && uuid_is_null(uuid))
+            || (name != NULL && !uuid_is_null(uuid))) {
+        DEBUG(SSSDBG_OP_FAILURE, "Expected one of name, uuid to be set\n");
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    subreq = sec_list_send(state, ev, secdb, client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, sec_get_ccache_list_done, req);
+    return req;
+
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void sec_get_ccache_list_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct sec_get_ccache_state *state = tevent_req_data(req,
+                                                struct sec_get_ccache_state);
+    const char **sec_key_list;
+
+    ret = sec_list_recv(subreq, state, &sec_key_list, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot list keys [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->name != NULL) {
+        state->sec_key = find_by_name(sec_key_list, state->name);
+    } else {
+        state->sec_key = find_by_uuid(sec_key_list, state->uuid);
+    }
+
+    if (state->sec_key == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Cannot find item in the ccache list\n");
+        /* Don't error out, just return an empty list */
+        tevent_req_done(req);
+        return;
+    }
+
+    subreq = sec_get_send(state,
+                          state->ev,
+                          state->secdb,
+                          state->client,
+                          state->sec_key);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, sec_get_ccache_done, req);
+}
+
+static void sec_get_ccache_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                struct tevent_req);
+    struct sec_get_ccache_state *state = tevent_req_data(req,
+                                                struct sec_get_ccache_state);
+
+    ret = sec_get_recv(subreq, state, &state->cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot resolve key to ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+static errno_t sec_get_ccache_recv(struct tevent_req *req,
+                                   TALLOC_CTX *mem_ctx,
+                                   struct kcm_ccache **_cc)
+{
+    struct sec_get_ccache_state *state = tevent_req_data(req,
+                                                struct sec_get_ccache_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+/*
+ * The actual sssd-secrets back end
+ */
+static errno_t ccdb_sec_init(struct kcm_ccdb *db)
+{
+    struct ccdb_sec *secdb = NULL;
+
+    secdb = talloc_zero(db, struct ccdb_sec);
+    if (secdb == NULL) {
+        return ENOMEM;
+    }
+
+    secdb->tctx = tcurl_init(secdb, db->ev);
+    if (secdb->tctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Cannot initialize tcurl\n");
+        talloc_zfree(secdb);
+        return ENOMEM;
+   }
+
+    /* We just need the random numbers to generate pseudo-random ccache names
+     * and avoid conflicts */
+    srand(time(NULL));
+
+    db->db_handle = secdb;
+    return EOK;
+}
+
+/*
+ * Helper request to get a ccache by key
+ */
+struct sec_patch_state {
+    struct tevent_context *ev;
+    struct ccdb_sec *secdb;
+    struct cli_creds *client;
+
+    const char *sec_key_url;
+    struct sss_iobuf *sec_value;
+};
+
+static void sec_patch_del_done(struct tevent_req *subreq);
+static void sec_patch_put_done(struct tevent_req *subreq);
+
+static struct tevent_req *sec_patch_send(TALLOC_CTX *mem_ctx,
+                                         struct tevent_context *ev,
+                                         struct ccdb_sec *secdb,
+                                         struct cli_creds *client,
+                                         const char *sec_key_url,
+                                         struct sss_iobuf *sec_value)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct sec_patch_state *state = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct sec_patch_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->secdb = secdb;
+    state->client = client;
+    state->sec_key_url = sec_key_url;
+    state->sec_value = sec_value;
+
+    subreq = tcurl_http_send(state, state->ev,
+                             state->secdb->tctx,
+                             TCURL_HTTP_DELETE,
+                             SSSD_SECRETS_SOCKET,
+                             state->sec_key_url,
+                             sec_headers,
+                             NULL,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, sec_patch_del_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void sec_patch_del_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sec_patch_state *state = tevent_req_data(req,
+                                                struct sec_patch_state);
+    int http_code;
+
+    ret = tcurl_http_recv(state, subreq, &http_code, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot delete key [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code == 404) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              "Key %s does not exist, moving on\n", state->sec_key_url);
+    } else if (http_code != 200) {
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Adding new payload\n");
+
+    subreq = tcurl_http_send(state,
+                             state->ev,
+                             state->secdb->tctx,
+                             TCURL_HTTP_PUT,
+                             SSSD_SECRETS_SOCKET,
+                             state->sec_key_url,
+                             sec_headers,
+                             state->sec_value,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, sec_patch_put_done, req);
+}
+
+static void sec_patch_put_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sec_patch_state *state = tevent_req_data(req,
+                                                struct sec_patch_state);
+    int http_code;
+
+    ret = tcurl_http_recv(state, subreq, &http_code, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot put new value [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code != 200) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to add the payload\n");
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "payload created\n");
+    tevent_req_done(req);
+}
+
+
+static errno_t sec_patch_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+/* The operations between the KCM and sssd-secrets */
+
+struct ccdb_sec_nextid_state {
+    struct tevent_context *ev;
+    struct ccdb_sec *secdb;
+    struct cli_creds *client;
+
+    unsigned int nextid;
+    char *nextid_name;
+
+    int maxtries;
+    int numtry;
+};
+
+static errno_t ccdb_sec_nextid_generate(struct tevent_req *req);
+static void ccdb_sec_nextid_list_done(struct tevent_req *subreq);
+
+/* Generate a unique ID */
+/* GET the name from secrets, if doesn't exist, OK, if exists, try again */
+static struct tevent_req *ccdb_sec_nextid_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct kcm_ccdb *db,
+                                               struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct ccdb_sec_nextid_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_nextid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->secdb = secdb;
+    state->client = client;
+
+    state->maxtries = 3;
+    state->numtry = 0;
+
+    ret = ccdb_sec_nextid_generate(req);
+    if (ret != EOK) {
+        goto immediate;
+    }
+
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t ccdb_sec_nextid_generate(struct tevent_req *req)
+{
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_nextid_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_nextid_state);
+
+    if (state->numtry >= state->maxtries) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to find a random ccache in %d tries\n", state->numtry);
+        return EBUSY;
+    }
+
+    state->nextid = rand() % MAX_CC_NUM;
+    state->nextid_name = talloc_asprintf(state, "%"SPRIuid":%u",
+                                         cli_creds_get_uid(state->client),
+                                         state->nextid);
+    if (state->nextid_name == NULL) {
+        return ENOMEM;
+    }
+
+    subreq = sec_list_send(state, state->ev, state->secdb, state->client);
+    if (subreq == NULL) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_nextid_list_done, req);
+
+    state->numtry++;
+    return EOK;
+}
+
+static void ccdb_sec_nextid_list_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_nextid_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_nextid_state);
+    const char **sec_key_list;
+    size_t sec_key_list_len;
+    size_t i;
+
+    ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot list keys [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    for (i = 0; i < sec_key_list_len; i++) {
+        if (sec_key_match_name(sec_key_list[i], state->nextid_name) == true) {
+            break;
+        }
+    }
+
+    DEBUG(SSSDBG_MINOR_FAILURE,
+          "Failed to find a random key, trying again..\n");
+    if (i < sec_key_list_len) {
+        /* Try again */
+        ret = ccdb_sec_nextid_generate(req);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL,
+          "Generated new ccache name %u\n", state->nextid);
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_nextid_recv(struct tevent_req *req,
+                                    unsigned int *_nextid)
+{
+    struct ccdb_sec_nextid_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_nextid_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_nextid = state->nextid;
+    return EOK;
+}
+
+/* IN:  HTTP PUT $base/default -d 'uuid' */
+/* We chose only UUID here to avoid issues later with renaming */
+struct ccdb_sec_set_default_state {
+};
+
+static void ccdb_sec_set_default_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_set_default_send(TALLOC_CTX *mem_ctx,
+                                                    struct tevent_context *ev,
+                                                    struct kcm_ccdb *db,
+                                                    struct cli_creds *client,
+                                                    uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_set_default_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    struct sss_iobuf *uuid_iobuf;
+    errno_t ret;
+    const char *url;
+    char uuid_str[UUID_STR_SIZE];
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_set_default_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    uuid_unparse(uuid, uuid_str);
+    DEBUG(SSSDBG_TRACE_INTERNAL,
+          "Setting the default ccache to %s\n", uuid_str);
+
+    url = sec_dfl_url_create(state, client);
+    if (url == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    uuid_iobuf = sss_iobuf_init_readonly(state,
+                                         (uint8_t *) uuid_str,
+                                         UUID_STR_SIZE);
+    if (uuid_iobuf == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    subreq = sec_patch_send(state, ev, secdb, client, url, uuid_iobuf);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_set_default_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_set_default_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+
+    ret = sec_patch_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sec_patch request failed [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Set the default ccache\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_set_default_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+/* IN:  HTTP GET $base/default  */
+/* OUT: uuid */
+struct ccdb_sec_get_default_state {
+    uuid_t uuid;
+};
+
+static void ccdb_sec_get_default_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_get_default_send(TALLOC_CTX *mem_ctx,
+                                                    struct tevent_context *ev,
+                                                    struct kcm_ccdb *db,
+                                                    struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_get_default_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    const char *url;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_get_default_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Getting the default ccache\n");
+    url = sec_dfl_url_create(state, client);
+    if (url == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    subreq = tcurl_http_send(state, ev, secdb->tctx,
+                             TCURL_HTTP_GET,
+                             SSSD_SECRETS_SOCKET,
+                             url,
+                             sec_headers,
+                             NULL,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_get_default_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_get_default_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    int http_code;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct ccdb_sec_get_default_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_get_default_state);
+    struct sss_iobuf *outbuf;
+    size_t uuid_size;
+
+    ret = tcurl_http_recv(state, subreq, &http_code, &outbuf);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Communication with the secrets responder failed [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code == 404) {
+        /* Return a NULL uuid */
+        uuid_clear(state->uuid);
+        tevent_req_done(req);
+        return;
+    } else if (http_code != 200) {
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    uuid_size = sss_iobuf_get_len(outbuf);
+    if (uuid_size != UUID_STR_SIZE) {
+        DEBUG(SSSDBG_OP_FAILURE, "Unexpected UUID size %zu\n", uuid_size);
+        tevent_req_error(req, EIO);
+        return;
+    }
+
+    uuid_parse((const char *) sss_iobuf_get_data(outbuf), state->uuid);
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Got the default ccache\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_get_default_recv(struct tevent_req *req,
+                                         uuid_t uuid)
+{
+    struct ccdb_sec_get_default_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_get_default_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    uuid_copy(uuid, state->uuid);
+    return EOK;
+}
+
+/* HTTP GET $base/ccache/  */
+/* OUT: a list of <uuid:name, uuid:name> */
+struct ccdb_sec_list_state {
+    uuid_t *uuid_list;
+};
+
+static void ccdb_sec_list_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_list_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct kcm_ccdb *db,
+                                             struct cli_creds *client)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_list_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_list_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all ccaches\n");
+
+    subreq = sec_list_send(state, ev, secdb, client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_list_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_list_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_list_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_list_state);
+    const char **sec_key_list;
+    size_t sec_key_list_len;
+
+    ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Communication with the secrets responder failed [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu ccaches\n", sec_key_list_len);
+
+    state->uuid_list = talloc_array(state, uuid_t, sec_key_list_len + 1);
+    if (state->uuid_list == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    for (size_t i = 0; i < sec_key_list_len; i++) {
+        ret = sec_key_get_uuid(sec_key_list[i],
+                               state->uuid_list[i]);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+    }
+    /* Sentinel */
+    uuid_clear(state->uuid_list[sec_key_list_len]);
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all caches done\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_list_recv(struct tevent_req *req,
+                                  TALLOC_CTX *mem_ctx,
+                                  uuid_t **_uuid_list)
+{
+    struct ccdb_sec_list_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_list_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_uuid_list = talloc_steal(mem_ctx, state->uuid_list);
+    return EOK;
+}
+
+struct ccdb_sec_getbyuuid_state {
+    struct kcm_ccache *cc;
+};
+
+
+/* HTTP GET $base/ccache/  */
+/* OUT: a list of <uuid:name, uuid:name> */
+/* for each item in list, compare with the uuid: portion */
+/* HTTP GET $base/ccache/uuid:name  */
+/* return result */
+static void ccdb_sec_getbyuuid_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_getbyuuid_send(TALLOC_CTX *mem_ctx,
+                                                  struct tevent_context *ev,
+                                                  struct kcm_ccdb *db,
+                                                  struct cli_creds *client,
+                                                  uuid_t uuid)
+{
+    errno_t ret;
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_getbyuuid_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_getbyuuid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by UUID\n");
+
+    subreq = sec_get_ccache_send(state, ev, secdb, client, NULL, uuid);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_getbyuuid_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_getbyuuid_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_getbyuuid_state *state = tevent_req_data(req,
+                                            struct ccdb_sec_getbyuuid_state);
+
+    ret = sec_get_ccache_recv(subreq, state, &state->cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot retrieve the ccache [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_getbyuuid_recv(struct tevent_req *req,
+                                       TALLOC_CTX *mem_ctx,
+                                       struct kcm_ccache **_cc)
+{
+    struct ccdb_sec_getbyuuid_state *state = tevent_req_data(req,
+                                            struct ccdb_sec_getbyuuid_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+/* HTTP GET $base/ccache/  */
+/* OUT: a list of <uuid:name, uuid:name> */
+/* for each item in list, compare with the :name portion */
+/* HTTP GET $base/ccache/uuid:name  */
+/* return result */
+struct ccdb_sec_getbyname_state {
+    struct kcm_ccache *cc;
+};
+
+static void ccdb_sec_getbyname_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_getbyname_send(TALLOC_CTX *mem_ctx,
+                                                  struct tevent_context *ev,
+                                                  struct kcm_ccdb *db,
+                                                  struct cli_creds *client,
+                                                  const char *name)
+{
+    errno_t ret;
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_getbyname_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    uuid_t null_uuid;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_getbyname_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    uuid_clear(null_uuid);
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by name\n");
+
+    subreq = sec_get_ccache_send(state, ev, secdb, client, name, null_uuid);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_getbyname_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_getbyname_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_getbyname_state *state = tevent_req_data(req,
+                                            struct ccdb_sec_getbyname_state);
+
+    ret = sec_get_ccache_recv(subreq, state, &state->cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot retrieve the ccache [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_getbyname_recv(struct tevent_req *req,
+                                       TALLOC_CTX *mem_ctx,
+                                       struct kcm_ccache **_cc)
+{
+    struct ccdb_sec_getbyname_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_getbyname_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_cc = talloc_steal(mem_ctx, state->cc);
+    return EOK;
+}
+
+struct ccdb_sec_name_by_uuid_state {
+    struct tevent_context *ev;
+    struct ccdb_sec *secdb;
+    struct cli_creds *client;
+
+    uuid_t uuid;
+
+    const char *name;
+};
+
+static void ccdb_sec_name_by_uuid_done(struct tevent_req *subreq);
+
+struct tevent_req *ccdb_sec_name_by_uuid_send(TALLOC_CTX *sec_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              uuid_t uuid)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_name_by_uuid_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    errno_t ret;
+
+    req = tevent_req_create(sec_ctx, &state, struct ccdb_sec_name_by_uuid_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->secdb = secdb;
+    state->client = client;
+    uuid_copy(state->uuid, uuid);
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Translating UUID to name\n");
+
+    subreq = sec_list_send(state, state->ev, state->secdb, state->client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_name_by_uuid_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_name_by_uuid_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_name_by_uuid_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_name_by_uuid_state);
+    const char **sec_key_list;
+    const char *name;
+    size_t sec_key_list_len;
+    size_t i;
+
+    ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    for (i = 0; i < sec_key_list_len; i++) {
+        if (sec_key_match_uuid(sec_key_list[i], state->uuid) == true) {
+            /* Match, copy name */
+            name = sec_key_get_name(sec_key_list[i]);
+            if (name == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                       "Malformed key, cannot get name\n");
+                tevent_req_error(req, EINVAL);
+                return;
+            }
+
+            state->name = talloc_strdup(state, name);
+            if (state->name == NULL) {
+                tevent_req_error(req, ENOMEM);
+                return;
+            }
+
+            DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n");
+            tevent_req_done(req);
+            return;
+        }
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "No such UUID\n");
+    tevent_req_error(req, ERR_NO_CREDS);
+    return;
+}
+
+errno_t ccdb_sec_name_by_uuid_recv(struct tevent_req *req,
+                                   TALLOC_CTX *sec_ctx,
+                                   const char **_name)
+{
+    struct ccdb_sec_name_by_uuid_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_name_by_uuid_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_name = talloc_steal(sec_ctx, state->name);
+    return EOK;
+}
+
+struct ccdb_sec_uuid_by_name_state {
+    struct tevent_context *ev;
+    struct ccdb_sec *secdb;
+    struct cli_creds *client;
+
+    const char *name;
+
+    uuid_t uuid;
+};
+
+static void ccdb_sec_uuid_by_name_done(struct tevent_req *subreq);
+
+struct tevent_req *ccdb_sec_uuid_by_name_send(TALLOC_CTX *sec_ctx,
+                                              struct tevent_context *ev,
+                                              struct kcm_ccdb *db,
+                                              struct cli_creds *client,
+                                              const char *name)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_uuid_by_name_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    errno_t ret;
+
+    req = tevent_req_create(sec_ctx, &state, struct ccdb_sec_uuid_by_name_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->secdb = secdb;
+    state->client = client;
+    state->name = name;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Translating name to UUID\n");
+
+    subreq = sec_list_send(state, state->ev, state->secdb, state->client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_uuid_by_name_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_uuid_by_name_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_uuid_by_name_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_uuid_by_name_state);
+    const char **sec_key_list;
+    size_t sec_key_list_len;
+    size_t i;
+
+    ret = sec_list_recv(subreq, state, &sec_key_list, &sec_key_list_len);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    for (i = 0; i < sec_key_list_len; i++) {
+        if (sec_key_match_name(sec_key_list[i], state->name) == true) {
+            /* Match, copy UUID */
+            ret = sec_key_get_uuid(sec_key_list[i], state->uuid);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                       "Malformed key, cannot get UUID\n");
+                tevent_req_error(req, ret);
+                return;
+            }
+
+            DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by name\n");
+            tevent_req_done(req);
+            return;
+        }
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "No such name\n");
+    tevent_req_error(req, ERR_NO_CREDS);
+    return;
+}
+
+errno_t ccdb_sec_uuid_by_name_recv(struct tevent_req *req,
+                                   TALLOC_CTX *sec_ctx,
+                                   uuid_t _uuid)
+{
+    struct ccdb_sec_uuid_by_name_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_uuid_by_name_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    uuid_copy(_uuid, state->uuid);
+    return EOK;
+}
+
+/* HTTP POST $base to create the container */
+/* HTTP PUT $base to create the container. Since PUT errors out on duplicates, at least
+ * we fail consistently here and don't overwrite the ccache on concurrent requests
+ */
+struct ccdb_sec_create_state {
+    struct tevent_context *ev;
+    struct ccdb_sec *secdb;
+
+    const char *key_url;
+    struct sss_iobuf *ccache_payload;
+};
+
+static void ccdb_sec_container_done(struct tevent_req *subreq);
+static void ccdb_sec_ccache_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_create_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct kcm_ccdb *db,
+                                               struct cli_creds *client,
+                                               struct kcm_ccache *cc)
+{
+    struct tevent_req *subreq = NULL;
+    struct tevent_req *req = NULL;
+    struct ccdb_sec_create_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+    errno_t ret;
+    const char *container_url;
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_create_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->secdb = secdb;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Creating ccache storage for %s\n", cc->name);
+
+    /* Do the encoding asap so that if we fail, we don't even attempt any
+     * writes */
+    ret = kcm_ccache_to_sec_input(state, cc, client, &state->key_url, &state->ccache_payload);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot convert cache %s to JSON [%d]: %s\n",
+              cc->name, ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    container_url = sec_container_url_create(state, client);
+    if (container_url == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Creating the ccache container\n");
+    subreq = tcurl_http_send(state, ev, secdb->tctx,
+                             TCURL_HTTP_POST,
+                             SSSD_SECRETS_SOCKET,
+                             container_url,
+                             sec_headers,
+                             NULL,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_container_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_container_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    int http_code;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct ccdb_sec_create_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_create_state);
+
+    ret = tcurl_http_recv(state, subreq, &http_code, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Communication with the secrets responder failed [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    /* Conflict is not an error as multiple ccaches are under the same
+     * container */
+    if (http_code == 409) {
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Container already exists, ignoring\n");
+    } else if (http_code != 200) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to create the ccache container\n");
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "ccache container created\n");
+    DEBUG(SSSDBG_TRACE_INTERNAL, "creating empty ccache payload\n");
+
+    subreq = tcurl_http_send(state,
+                             state->ev,
+                             state->secdb->tctx,
+                             TCURL_HTTP_PUT,
+                             SSSD_SECRETS_SOCKET,
+                             state->key_url,
+                             sec_headers,
+                             state->ccache_payload,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_ccache_done, req);
+}
+
+static void ccdb_sec_ccache_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    int http_code;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct ccdb_sec_create_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_create_state);
+
+    ret = tcurl_http_recv(state, subreq, &http_code, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Communication with the secrets responder failed [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code != 200) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to add the payload\n");
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "payload created\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_create_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+struct ccdb_sec_mod_cred_state {
+    struct tevent_context *ev;
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+    struct kcm_mod_ctx *mod_cc;
+
+    struct ccdb_sec *secdb;
+};
+
+static void ccdb_sec_mod_cred_get_done(struct tevent_req *subreq);
+static void ccdb_sec_mod_cred_patch_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_mod_send(TALLOC_CTX *mem_ctx,
+                                            struct tevent_context *ev,
+                                            struct kcm_ccdb *db,
+                                            struct cli_creds *client,
+                                            uuid_t uuid,
+                                            struct kcm_mod_ctx *mod_cc)
+{
+    errno_t ret;
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_mod_cred_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_mod_cred_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->db =db;
+    state->client = client;
+    state->secdb = secdb;
+    state->mod_cc = mod_cc;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Modifying ccache\n");
+
+    subreq = sec_get_ccache_send(state, ev, secdb, client, NULL, uuid);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, *ccdb_sec_mod_cred_get_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_mod_cred_get_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_mod_cred_state *state = tevent_req_data(req,
+                                            struct ccdb_sec_mod_cred_state);
+    struct kcm_ccache *cc;
+    const char *url;
+    struct sss_iobuf *payload;
+
+    ret = sec_get_ccache_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot retrieve the ccache [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (cc == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "No such ccache\n");
+        tevent_req_error(req, ERR_NO_CREDS);
+        return;
+    }
+
+    kcm_mod_cc(cc, state->mod_cc);
+
+    ret = kcm_ccache_to_sec_input(state, cc, state->client, &url, &payload);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to marshall modified ccache to payload [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sec_patch_send(state,
+                            state->ev,
+                            state->secdb,
+                            state->client,
+                            url,
+                            payload);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_mod_cred_patch_done, req);
+}
+
+static void ccdb_sec_mod_cred_patch_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+
+    ret = sec_patch_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sec_patch request failed [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "ccache modified\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_mod_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+struct ccdb_sec_store_cred_state {
+    struct tevent_context *ev;
+    struct kcm_ccdb *db;
+    struct cli_creds *client;
+    struct sss_iobuf *cred_blob;
+
+    struct ccdb_sec *secdb;
+};
+
+static void ccdb_sec_store_cred_get_done(struct tevent_req *subreq);
+static void ccdb_sec_store_cred_patch_done(struct tevent_req *subreq);
+
+/* HTTP DEL/PUT $base/ccache/uuid:name */
+static struct tevent_req *ccdb_sec_store_cred_send(TALLOC_CTX *mem_ctx,
+                                                   struct tevent_context *ev,
+                                                   struct kcm_ccdb *db,
+                                                   struct cli_creds *client,
+                                                   uuid_t uuid,
+                                                   struct sss_iobuf *cred_blob)
+{
+    errno_t ret;
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_store_cred_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_store_cred_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->db =db;
+    state->client = client;
+    state->cred_blob = cred_blob;
+    state->secdb = secdb;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Storing creds in ccache\n");
+
+    subreq = sec_get_ccache_send(state, ev, secdb, client, NULL, uuid);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, *ccdb_sec_store_cred_get_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_store_cred_get_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_store_cred_state *state = tevent_req_data(req,
+                                            struct ccdb_sec_store_cred_state);
+    struct kcm_ccache *cc;
+    const char *url;
+    struct sss_iobuf *payload;
+
+    ret = sec_get_ccache_recv(subreq, state, &cc);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = kcm_cc_store_cred_blob(cc, state->cred_blob);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot store credentials to ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = kcm_ccache_to_sec_input(state, cc, state->client, &url, &payload);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to marshall modified ccache to payload [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sec_patch_send(state,
+                            state->ev,
+                            state->secdb,
+                            state->client,
+                            url,
+                            payload);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_store_cred_patch_done, req);
+}
+
+static void ccdb_sec_store_cred_patch_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+
+    ret = sec_patch_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sec_patch request failed [%d]: %s\n", ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "ccache creds stored\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_store_cred_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+/* HTTP DELETE $base/ccache/uuid:name */
+struct ccdb_sec_delete_state {
+    struct tevent_context *ev;
+    struct ccdb_sec *secdb;
+    struct cli_creds *client;
+    uuid_t uuid;
+
+    size_t sec_key_list_len;
+};
+
+static void ccdb_sec_delete_list_done(struct tevent_req *subreq);
+static void ccdb_sec_delete_cc_done(struct tevent_req *subreq);
+static void ccdb_sec_delete_container_done(struct tevent_req *subreq);
+
+static struct tevent_req *ccdb_sec_delete_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct kcm_ccdb *db,
+                                               struct cli_creds *client,
+                                               uuid_t uuid)
+{
+    errno_t ret;
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct ccdb_sec_delete_state *state = NULL;
+    struct ccdb_sec *secdb = talloc_get_type(db->db_handle, struct ccdb_sec);
+
+    req = tevent_req_create(mem_ctx, &state, struct ccdb_sec_delete_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->secdb = secdb;
+    state->client = client;
+    uuid_copy(state->uuid, uuid);
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Deleting ccache\n");
+
+    subreq = sec_list_send(state, ev, secdb, client);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_delete_list_done, req);
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ccdb_sec_delete_list_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_delete_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_delete_state);
+    const char **sec_key_list;
+    const char *sec_key;
+    const char *cc_url;
+
+    ret = sec_list_recv(subreq,
+                        state,
+                        &sec_key_list,
+                        &state->sec_key_list_len);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (sec_key_list == 0) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "No ccaches to delete\n");
+        tevent_req_done(req);
+        return;
+    }
+
+    sec_key = find_by_uuid(sec_key_list, state->uuid);
+    if (sec_key == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find ccache by UUID\n");
+        tevent_req_done(req);
+        return;
+    }
+
+    cc_url = sec_cc_url_create(state, state->client, sec_key);
+    if (cc_url == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    subreq = tcurl_http_send(state, state->ev,
+                             state->secdb->tctx,
+                             TCURL_HTTP_DELETE,
+                             SSSD_SECRETS_SOCKET,
+                             cc_url,
+                             sec_headers,
+                             NULL,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_delete_cc_done, req);
+}
+
+static void ccdb_sec_delete_cc_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_delete_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_delete_state);
+    int http_code;
+    const char *container_url;
+
+    ret = tcurl_http_recv(state, subreq, &http_code, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot delete ccache [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code != 200) {
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->sec_key_list_len != 1) {
+        DEBUG(SSSDBG_TRACE_INTERNAL, "There are other ccaches, done\n");
+        tevent_req_done(req);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Removing ccache container\n");
+
+    container_url = sec_container_url_create(state, state->client);
+    if (container_url == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    subreq = tcurl_http_send(state, state->ev,
+                             state->secdb->tctx,
+                             TCURL_HTTP_DELETE,
+                             SSSD_SECRETS_SOCKET,
+                             container_url,
+                             sec_headers,
+                             NULL,
+                             SEC_TIMEOUT);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, ccdb_sec_delete_container_done, req);
+}
+
+static void ccdb_sec_delete_container_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ccdb_sec_delete_state *state = tevent_req_data(req,
+                                                struct ccdb_sec_delete_state);
+    int http_code;
+
+    ret = tcurl_http_recv(state, subreq, &http_code, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot delete ccache container [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (http_code != 200) {
+        ret = http2errno(http_code);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Removed ccache container\n");
+    tevent_req_done(req);
+}
+
+static errno_t ccdb_sec_delete_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+const struct kcm_ccdb_ops ccdb_sec_ops = {
+    .init = ccdb_sec_init,
+
+    .nextid_send = ccdb_sec_nextid_send,
+    .nextid_recv = ccdb_sec_nextid_recv,
+
+    .set_default_send = ccdb_sec_set_default_send,
+    .set_default_recv = ccdb_sec_set_default_recv,
+
+    .get_default_send = ccdb_sec_get_default_send,
+    .get_default_recv = ccdb_sec_get_default_recv,
+
+    .list_send = ccdb_sec_list_send,
+    .list_recv = ccdb_sec_list_recv,
+
+    .getbyname_send = ccdb_sec_getbyname_send,
+    .getbyname_recv = ccdb_sec_getbyname_recv,
+
+    .getbyuuid_send = ccdb_sec_getbyuuid_send,
+    .getbyuuid_recv = ccdb_sec_getbyuuid_recv,
+
+    .name_by_uuid_send = ccdb_sec_name_by_uuid_send,
+    .name_by_uuid_recv = ccdb_sec_name_by_uuid_recv,
+
+    .uuid_by_name_send = ccdb_sec_uuid_by_name_send,
+    .uuid_by_name_recv = ccdb_sec_uuid_by_name_recv,
+
+    .create_send = ccdb_sec_create_send,
+    .create_recv = ccdb_sec_create_recv,
+
+    .mod_send = ccdb_sec_mod_send,
+    .mod_recv = ccdb_sec_mod_recv,
+
+    .store_cred_send = ccdb_sec_store_cred_send,
+    .store_cred_recv = ccdb_sec_store_cred_recv,
+
+    .delete_send = ccdb_sec_delete_send,
+    .delete_recv = ccdb_sec_delete_recv,
+};
diff --git a/src/tests/cmocka/test_kcm_json_marshalling.c b/src/tests/cmocka/test_kcm_json_marshalling.c
new file mode 100644
index 0000000..8eff2f5
--- /dev/null
+++ b/src/tests/cmocka/test_kcm_json_marshalling.c
@@ -0,0 +1,234 @@
+/*
+    Copyright (C) 2017 Red Hat
+
+    SSSD tests: Test KCM JSON marshalling
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <popt.h>
+
+#include "util/util_creds.h"
+#include "responder/kcm/kcmsrv_ccache.h"
+#include "responder/kcm/kcmsrv_ccache_be.h"
+#include "tests/cmocka/common_mock.h"
+
+#define TEST_REALM                "TESTREALM"
+#define TEST_PRINC_COMPONENT      "PRINC_NAME"
+
+#define TEST_CREDS                "TESTCREDS"
+
+const struct kcm_ccdb_ops ccdb_mem_ops;
+const struct kcm_ccdb_ops ccdb_sec_ops;
+
+struct kcm_marshalling_test_ctx {
+    krb5_context kctx;
+    krb5_principal princ;
+};
+
+static int setup_kcm_marshalling(void **state)
+{
+    struct kcm_marshalling_test_ctx *test_ctx;
+    krb5_error_code kerr;
+
+    test_ctx = talloc_zero(NULL, struct kcm_marshalling_test_ctx);
+    assert_non_null(test_ctx);
+
+    kerr = krb5_init_context(&test_ctx->kctx);
+    assert_int_equal(kerr, 0);
+
+    kerr = krb5_build_principal(test_ctx->kctx,
+                                &test_ctx->princ,
+                                sizeof(TEST_REALM)-1, TEST_REALM,
+                                TEST_PRINC_COMPONENT, NULL);
+    assert_int_equal(kerr, 0);
+
+    *state = test_ctx;
+    return 0;
+}
+
+static int teardown_kcm_marshalling(void **state)
+{
+    struct kcm_marshalling_test_ctx *test_ctx = talloc_get_type(*state,
+                                        struct kcm_marshalling_test_ctx);
+    assert_non_null(test_ctx);
+
+    krb5_free_principal(test_ctx->kctx, test_ctx->princ);
+    krb5_free_context(test_ctx->kctx);
+    talloc_free(test_ctx);
+    return 0;
+}
+
+static void assert_cc_name_equal(struct kcm_ccache *cc1,
+                                 struct kcm_ccache *cc2)
+{
+    const char *name1, *name2;
+
+    name1 = kcm_cc_get_name(cc1);
+    name2 = kcm_cc_get_name(cc2);
+    assert_string_equal(name1, name2);
+}
+
+static void assert_cc_uuid_equal(struct kcm_ccache *cc1,
+                                 struct kcm_ccache *cc2)
+{
+    uuid_t u1, u2;
+    errno_t ret;
+
+    ret = kcm_cc_get_uuid(cc1, u1);
+    assert_int_equal(ret, EOK);
+    ret = kcm_cc_get_uuid(cc2, u2);
+    assert_int_equal(ret, EOK);
+    ret = uuid_compare(u1, u2);
+    assert_int_equal(ret, 0);
+}
+
+static void assert_cc_princ_equal(struct kcm_ccache *cc1,
+                                  struct kcm_ccache *cc2)
+{
+    krb5_principal p1;
+    krb5_principal p2;
+    char *name1;
+    char *name2;
+    krb5_error_code kerr;
+
+    p1 = kcm_cc_get_client_principal(cc1);
+    p2 = kcm_cc_get_client_principal(cc1);
+
+    kerr = krb5_unparse_name(NULL, p1, &name1);
+    assert_int_equal(kerr, 0);
+    kerr = krb5_unparse_name(NULL, p2, &name2);
+    assert_int_equal(kerr, 0);
+
+    assert_string_equal(name1, name2);
+    krb5_free_unparsed_name(NULL, name1);
+    krb5_free_unparsed_name(NULL, name2);
+}
+
+static void assert_cc_offset_equal(struct kcm_ccache *cc1,
+                                   struct kcm_ccache *cc2)
+{
+    int32_t off1;
+    int32_t off2;
+
+    off1 = kcm_cc_get_offset(cc1);
+    off2 = kcm_cc_get_offset(cc2);
+    assert_int_equal(off1, off2);
+}
+
+static void assert_cc_equal(struct kcm_ccache *cc1,
+                            struct kcm_ccache *cc2)
+{
+    assert_cc_name_equal(cc1, cc2);
+    assert_cc_uuid_equal(cc1, cc2);
+    assert_cc_princ_equal(cc1, cc2);
+    assert_cc_offset_equal(cc1, cc2);
+}
+
+static void test_kcm_ccache_marshall_unmarshall(void **state)
+{
+    struct kcm_marshalling_test_ctx *test_ctx = talloc_get_type(*state,
+                                        struct kcm_marshalling_test_ctx);
+    errno_t ret;
+    struct cli_creds owner;
+    struct kcm_ccache *cc;
+    struct kcm_ccache *cc2;
+    const char *url;
+    struct sss_iobuf *payload;
+    const char *name;
+    const char *key;
+    uint8_t *data;
+
+    owner.ucred.uid = getuid();
+    owner.ucred.gid = getuid();
+
+    name = talloc_asprintf(test_ctx, "%"SPRIuid, getuid());
+    assert_non_null(name);
+
+    ret = kcm_cc_new(test_ctx,
+                     test_ctx->kctx,
+                     &owner,
+                     name,
+                     test_ctx->princ,
+                     &cc);
+    assert_int_equal(ret, EOK);
+
+    ret = kcm_ccache_to_sec_input(test_ctx,
+                                  cc,
+                                  &owner,
+                                  &url,
+                                  &payload);
+    assert_int_equal(ret, EOK);
+
+    key = strrchr(url, '/') + 1;
+    assert_non_null(key);
+
+    data = sss_iobuf_get_data(payload);
+    assert_non_null(data);
+
+    ret = sec_kv_to_ccache(test_ctx,
+                           key,
+                           (const char *) data,
+                           &owner,
+                           &cc2);
+    assert_int_equal(ret, EOK);
+
+    assert_cc_equal(cc, cc2);
+}
+
+int main(int argc, const char *argv[])
+{
+    poptContext pc;
+    int opt;
+    int rv;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_kcm_ccache_marshall_unmarshall,
+                                        setup_kcm_marshalling,
+                                        teardown_kcm_marshalling),
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old db to be sure */
+    tests_set_cwd();
+
+    rv = cmocka_run_group_tests(tests, NULL, NULL);
+
+    return rv;
+}
diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py
index ad1e492..11f80a1 100644
--- a/src/tests/intg/test_kcm.py
+++ b/src/tests/intg/test_kcm.py
@@ -27,7 +27,8 @@
 import kdc
 import krb5utils
 import config
-from util import unindent, run_shell
+from util import unindent
+from test_secrets import create_sssd_secrets_fixture
 
 class KcmTestEnv(object):
     def __init__(self, k5kdc, k5util):
@@ -107,15 +108,8 @@ def kcm_teardown():
     return kcm_pid
 
 
-@pytest.fixture
-def setup_for_kcm(request, kdc_instance):
-    """
-    Just set up the local provider for tests and enable the KCM
-    responder
-    """
-    kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
-
-    sssd_conf = unindent("""\
+def create_sssd_conf(kcm_path, ccache_storage):
+    return unindent("""\
         [sssd]
         domains = local
         services = nss
@@ -125,8 +119,11 @@ def setup_for_kcm(request, kdc_instance):
 
         [kcm]
         socket_path = {kcm_path}
+        ccache_storage = {ccache_storage}
     """).format(**locals())
 
+
+def common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf):
     kcm_socket_include = unindent("""
     [libdefaults]
     default_ccache_name = KCM:
@@ -142,11 +139,35 @@ def setup_for_kcm(request, kdc_instance):
     return KcmTestEnv(kdc_instance, k5util)
 
 
-def test_kcm_init_list_destroy(setup_for_kcm):
+@pytest.fixture
+def setup_for_kcm_mem(request, kdc_instance):
+    """
+    Just set up the local provider for tests and enable the KCM
+    responder
+    """
+    kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
+    sssd_conf = create_sssd_conf(kcm_path, "memory")
+    return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf)
+
+@pytest.fixture
+def setup_secrets(request):
+    create_sssd_secrets_fixture(request)
+
+@pytest.fixture
+def setup_for_kcm_sec(request, kdc_instance):
+    """
+    Just set up the local provider for tests and enable the KCM
+    responder
+    """
+    kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
+    sssd_conf = create_sssd_conf(kcm_path, "secrets")
+    return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf)
+
+
+def kcm_init_list_destroy(testenv):
     """
     Test that kinit, kdestroy and klist work with KCM
     """
-    testenv = setup_for_kcm
     testenv.k5kdc.add_principal("kcmtest", "Secret123")
 
     ok = testenv.k5util.has_principal("kcmtest@KCMTEST")
@@ -172,12 +193,22 @@ def test_kcm_init_list_destroy(setup_for_kcm):
     assert nprincs == 0
 
 
-def test_kcm_overwrite(setup_for_kcm):
+def test_kcm_mem_init_list_destroy(setup_for_kcm_mem):
+    testenv = setup_for_kcm_mem
+    kcm_init_list_destroy(testenv)
+
+
+def test_kcm_sec_init_list_destroy(setup_for_kcm_sec,
+                                   setup_secrets):
+    testenv = setup_for_kcm_sec
+    kcm_init_list_destroy(testenv)
+
+
+def kcm_overwrite(testenv):
     """
     That that reusing a ccache reinitializes the cache and doesn't
     add the same principal twice
     """
-    testenv = setup_for_kcm
     testenv.k5kdc.add_principal("kcmtest", "Secret123")
     exp_ccache = {'kcmtest@KCMTEST': ['krbtgt/KCMTEST@KCMTEST']}
 
@@ -192,12 +223,22 @@ def test_kcm_overwrite(setup_for_kcm):
     assert exp_ccache == testenv.k5util.list_all_princs()
 
 
-def test_collection_init_list_destroy(setup_for_kcm):
+def test_kcm_mem_overwrite(setup_for_kcm_mem):
+    testenv = setup_for_kcm_mem
+    kcm_overwrite(testenv)
+
+
+def test_kcm_sec_overwrite(setup_for_kcm_sec,
+                           setup_secrets):
+    testenv = setup_for_kcm_sec
+    kcm_overwrite(testenv)
+
+
+def collection_init_list_destroy(testenv):
     """
     Test that multiple principals and service tickets can be stored
     in a collection.
     """
-    testenv = setup_for_kcm
     testenv.k5kdc.add_principal("alice", "alicepw")
     testenv.k5kdc.add_principal("bob", "bobpw")
     testenv.k5kdc.add_principal("carol", "carolpw")
@@ -241,7 +282,11 @@ def test_collection_init_list_destroy(setup_for_kcm):
 
     out = testenv.k5util.kdestroy()
     assert out == 0
-    assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+    # If the default is removed, KCM just uses whetever is the first entry
+    # in the collection as the default. And sine the KCM back ends don't
+    # guarantee if they are FIFO or LIFO, just check for either alice or bob
+    assert testenv.k5util.default_principal() in \
+            ['alice@KCMTEST', 'bob@KCMTEST']
     cc_coll = testenv.k5util.list_all_princs()
     assert len(cc_coll) == 2
     assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
@@ -255,11 +300,21 @@ def test_collection_init_list_destroy(setup_for_kcm):
     #assert len(cc_coll) == 0
 
 
-def test_kswitch(setup_for_kcm):
+def test_kcm_mem_collection_init_list_destroy(setup_for_kcm_mem):
+    testenv = setup_for_kcm_mem
+    collection_init_list_destroy(testenv)
+
+
+def test_kcm_sec_collection_init_list_destroy(setup_for_kcm_sec,
+                                              setup_secrets):
+    testenv = setup_for_kcm_sec
+    collection_init_list_destroy(testenv)
+
+
+def exercise_kswitch(testenv):
     """
     Test switching between principals
     """
-    testenv = setup_for_kcm
     testenv.k5kdc.add_principal("alice", "alicepw")
     testenv.k5kdc.add_principal("bob", "bobpw")
     testenv.k5kdc.add_principal("host/somehostname")
@@ -301,12 +356,22 @@ def test_kswitch(setup_for_kcm):
                                     'host/differenthostname@KCMTEST'])
 
 
-def test_subsidiaries(setup_for_kcm):
+def test_kcm_mem_kswitch(setup_for_kcm_mem):
+    testenv = setup_for_kcm_mem
+    exercise_kswitch(testenv)
+
+
+def test_kcm_sec_kswitch(setup_for_kcm_sec,
+                         setup_secrets):
+    testenv = setup_for_kcm_sec
+    exercise_kswitch(testenv)
+
+
+def exercise_subsidiaries(testenv):
     """
     Test that subsidiary caches are usable and KCM: without specifying UID
     can be used to identify the collection
     """
-    testenv = setup_for_kcm
     testenv.k5kdc.add_principal("alice", "alicepw")
     testenv.k5kdc.add_principal("bob", "bobpw")
     testenv.k5kdc.add_principal("host/somehostname")
@@ -346,11 +411,21 @@ def test_subsidiaries(setup_for_kcm):
                                             'host/differenthostname@KCMTEST'])
 
 
-def test_kdestroy_nocache(setup_for_kcm):
+def test_kcm_mem_subsidiaries(setup_for_kcm_mem):
+    testenv = setup_for_kcm_mem
+    exercise_subsidiaries(testenv)
+
+
+def test_kcm_sec_subsidiaries(setup_for_kcm_sec,
+                              setup_secrets):
+    testenv = setup_for_kcm_sec
+    exercise_subsidiaries(testenv)
+
+
+def kdestroy_nocache(testenv):
     """
     Destroying a non-existing ccache should not throw an error
     """
-    testenv = setup_for_kcm
     testenv.k5kdc.add_principal("alice", "alicepw")
     out, _, _ = testenv.k5util.kinit("alice", "alicepw")
     assert out == 0
@@ -359,3 +434,14 @@ def test_kdestroy_nocache(setup_for_kcm):
     assert out == 0
     out = testenv.k5util.kdestroy()
     assert out == 0
+
+
+def test_kcm_mem_kdestroy_nocache(setup_for_kcm_mem):
+    testenv = setup_for_kcm_mem
+    exercise_subsidiaries(testenv)
+
+
+def test_kcm_sec_kdestroy_nocache(setup_for_kcm_sec,
+                                  setup_secrets):
+    testenv = setup_for_kcm_sec
+    exercise_subsidiaries(testenv)
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 23cfdf9..60c2f43 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -109,6 +109,8 @@ struct err_string error_to_str[] = {
     { "KCM operation not implemented" }, /* ERR_KCM_OP_NOT_IMPLEMENTED */
     { "End of credential cache reached" }, /* ERR_KCM_CC_END */
     { "Credential cache name not allowed" }, /* ERR_KCM_WRONG_CCNAME_FORMAT */
+    { "Cannot encode a JSON object to string" }, /* ERR_JSON_ENCODING */
+    { "Cannot decode a JSON object from string" }, /* ERR_JSON_DECODING */
     { "ERR_LAST" } /* ERR_LAST */
 };
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 387d481..4e9da81 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -131,6 +131,8 @@ enum sssd_errors {
     ERR_KCM_OP_NOT_IMPLEMENTED,
     ERR_KCM_CC_END,
     ERR_KCM_WRONG_CCNAME_FORMAT,
+    ERR_JSON_ENCODING,
+    ERR_JSON_DECODING,
     ERR_LAST            /* ALWAYS LAST */
 };
 

From ccbb9afb4cc7deef38c3116f0e9c7cc800335aec Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 14 Mar 2017 11:17:05 +0100
Subject: [PATCH 19/21] KCM: Make the secrets ccache back end configurable,
 make secrets the default

Adds a new option 'ccache_storage' that allows to select either the
memory back end or the secrets back end. The secrets back end is the
default one and this option is even undocumented.
---
 src/confdb/confdb.h                  |  1 +
 src/config/cfg_rules.ini             |  1 +
 src/responder/kcm/kcm.c              | 49 ++++++++++++++++++++++++++++++++----
 src/responder/kcm/kcmsrv_ccache.c    |  2 +-
 src/responder/kcm/kcmsrv_ccache.h    |  6 +----
 src/responder/kcm/kcmsrv_ccache_be.h |  1 +
 src/responder/kcm/kcmsrv_pvt.h       |  7 ++++++
 7 files changed, 56 insertions(+), 11 deletions(-)

diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index c443e86..fb60675 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -234,6 +234,7 @@
 /* KCM Service */
 #define CONFDB_KCM_CONF_ENTRY "config/kcm"
 #define CONFDB_KCM_SOCKET "socket_path"
+#define CONFDB_KCM_DB "ccache_storage" /* Undocumented on purpose */
 
 struct confdb_ctx;
 struct config_file_ctx;
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index 5e789c5..67a5d1f 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -280,6 +280,7 @@ option = fd_limit
 option = client_idle_timeout
 option = description
 option = socket_path
+option = ccache_storage
 
 [rule/allowed_domain_options]
 validator = ini_allowed_options
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index 2c12ef2..063c27b 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -47,6 +47,37 @@ static int kcm_responder_ctx_destructor(void *ptr)
     return 0;
 }
 
+static errno_t kcm_get_ccdb_be(struct kcm_ctx *kctx)
+{
+    errno_t ret;
+    char *str_db;
+
+    ret = confdb_get_string(kctx->rctx->cdb,
+                            kctx->rctx,
+                            kctx->rctx->confdb_service_path,
+                            CONFDB_KCM_DB,
+                            "secrets",
+                            &str_db);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get the KCM database type [%d]: %s\n",
+               ret, strerror(ret));
+        return ret;
+    }
+
+    DEBUG(SSSDBG_CONF_SETTINGS, "KCM database type: %s\n", str_db);
+    if (strcasecmp(str_db, "memory") == 0) {
+        kctx->cc_be = CCDB_BE_MEMORY;
+        return EOK;
+    } else if (strcasecmp(str_db, "secrets") == 0) {
+        kctx->cc_be = CCDB_BE_SECRETS;
+        return EOK;
+    }
+
+    DEBUG(SSSDBG_FATAL_FAILURE, "Unexpected KCM database type %s\n", str_db);
+    return EOK;
+}
+
 static int kcm_get_config(struct kcm_ctx *kctx)
 {
     int ret;
@@ -88,14 +119,21 @@ static int kcm_get_config(struct kcm_ctx *kctx)
                             &sock_name);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE,
-              "Cannot get the client idle timeout [%d]: %s\n",
+              "Cannot get KCM socket path [%d]: %s\n",
                ret, strerror(ret));
         goto done;
     }
     kctx->rctx->sock_name = sock_name;
 
-    ret = EOK;
+    ret = kcm_get_ccdb_be(kctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot get KCM ccache DB [%d]: %s\n",
+               ret, strerror(ret));
+        goto done;
+    }
 
+    ret = EOK;
 done:
     return ret;
 }
@@ -111,7 +149,8 @@ static int kcm_data_destructor(void *ptr)
 }
 
 static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx,
-                                           struct tevent_context *ev)
+                                           struct tevent_context *ev,
+                                           enum kcm_ccdb_be cc_be)
 {
     struct kcm_resp_ctx *kcm_data;
     krb5_error_code kret;
@@ -122,7 +161,7 @@ static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx,
         return NULL;
     }
 
-    kcm_data->db = kcm_ccdb_init(kcm_data, ev, CCDB_BE_MEMORY);
+    kcm_data->db = kcm_ccdb_init(kcm_data, ev, cc_be);
     if (kcm_data->db == NULL) {
         talloc_free(kcm_data);
         return NULL;
@@ -176,7 +215,7 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
-    kctx->kcm_data = kcm_data_setup(kctx, ev);
+    kctx->kcm_data = kcm_data_setup(kctx, ev, kctx->cc_be);
     if (kctx->kcm_data == NULL) {
         DEBUG(SSSDBG_FATAL_FAILURE,
               "fatal error initializing responder data\n");
diff --git a/src/responder/kcm/kcmsrv_ccache.c b/src/responder/kcm/kcmsrv_ccache.c
index 2ae1202..a22184e 100644
--- a/src/responder/kcm/kcmsrv_ccache.c
+++ b/src/responder/kcm/kcmsrv_ccache.c
@@ -244,7 +244,7 @@ struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx,
         break;
     case CCDB_BE_SECRETS:
         DEBUG(SSSDBG_FUNC_DATA, "KCM back end: sssd-secrets\n");
-        /* Not implemented yet */
+        ccdb->ops = &ccdb_sec_ops;
         break;
     default:
         DEBUG(SSSDBG_CRIT_FAILURE, "Unknown ccache database\n");
diff --git a/src/responder/kcm/kcmsrv_ccache.h b/src/responder/kcm/kcmsrv_ccache.h
index 18c8c47..36c481c 100644
--- a/src/responder/kcm/kcmsrv_ccache.h
+++ b/src/responder/kcm/kcmsrv_ccache.h
@@ -29,6 +29,7 @@
 #include "util/util.h"
 #include "util/sss_iobuf.h"
 #include "util/util_creds.h"
+#include "responder/kcm/kcmsrv_pvt.h"
 
 #define UUID_BYTES    16
 #define UUID_STR_SIZE 37
@@ -113,11 +114,6 @@ errno_t kcm_cc_store_cred_blob(struct kcm_ccache *cc,
 struct kcm_cred *kcm_cc_get_cred(struct kcm_ccache *cc);
 struct kcm_cred *kcm_cc_next_cred(struct kcm_cred *crd);
 
-enum kcm_ccdb_be {
-    CCDB_BE_MEMORY,
-    CCDB_BE_SECRETS,
-};
-
 /* An opaque database that contains all the ccaches */
 struct kcm_ccdb;
 
diff --git a/src/responder/kcm/kcmsrv_ccache_be.h b/src/responder/kcm/kcmsrv_ccache_be.h
index 1bd2b69..a0796c2 100644
--- a/src/responder/kcm/kcmsrv_ccache_be.h
+++ b/src/responder/kcm/kcmsrv_ccache_be.h
@@ -200,5 +200,6 @@ struct kcm_ccdb_ops {
 };
 
 extern const struct kcm_ccdb_ops ccdb_mem_ops;
+extern const struct kcm_ccdb_ops ccdb_sec_ops;
 
 #endif /* _KCMSRV_CCACHE_BE_ */
diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h
index a296802..74f30c0 100644
--- a/src/responder/kcm/kcmsrv_pvt.h
+++ b/src/responder/kcm/kcmsrv_pvt.h
@@ -49,6 +49,12 @@ struct kcm_resp_ctx {
     struct kcm_ccdb *db;
 };
 
+/* Supported ccache back ends */
+enum kcm_ccdb_be {
+    CCDB_BE_MEMORY,
+    CCDB_BE_SECRETS,
+};
+
 /*
  * responder context that contains both the responder data,
  * like the ccaches and the sssd-specific stuff like the
@@ -58,6 +64,7 @@ struct kcm_ctx {
     struct resp_ctx *rctx;
     int fd_limit;
     char *socket_path;
+    enum kcm_ccdb_be cc_be;
 
     struct kcm_resp_ctx *kcm_data;
 };

From c37b6a5d5454127c9c2c101bd12fd664862a7809 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 21 Mar 2017 13:25:11 +0100
Subject: [PATCH 20/21] KCM: Queue requests by the same UID

In order to avoid race conditions, we queue requests towards the KCM
responder coming from the same client UID.
---
 Makefile.am                         |  23 ++-
 src/responder/kcm/kcm.c             |   7 +
 src/responder/kcm/kcmsrv_cmd.c      |  10 +-
 src/responder/kcm/kcmsrv_op_queue.c | 264 ++++++++++++++++++++++++++
 src/responder/kcm/kcmsrv_ops.c      |  34 +++-
 src/responder/kcm/kcmsrv_ops.h      |   1 +
 src/responder/kcm/kcmsrv_pvt.h      |  20 ++
 src/tests/cmocka/test_kcm_queue.c   | 364 ++++++++++++++++++++++++++++++++++++
 8 files changed, 717 insertions(+), 6 deletions(-)
 create mode 100644 src/responder/kcm/kcmsrv_op_queue.c
 create mode 100644 src/tests/cmocka/test_kcm_queue.c

diff --git a/Makefile.am b/Makefile.am
index 3ef89a6..bb952fd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -299,7 +299,10 @@ non_interactive_cmocka_based_tests += test_inotify
 endif   # HAVE_INOTIFY
 
 if BUILD_KCM
-non_interactive_cmocka_based_tests += test_kcm_json
+non_interactive_cmocka_based_tests += \
+	test_kcm_json \
+	test_kcm_queue \
+        $(NULL)
 endif   # BUILD_KCM
 
 if BUILD_SAMBA
@@ -1491,6 +1494,7 @@ sssd_kcm_SOURCES = \
     src/responder/kcm/kcmsrv_ccache_json.c \
     src/responder/kcm/kcmsrv_ccache_secrets.c \
     src/responder/kcm/kcmsrv_ops.c \
+    src/responder/kcm/kcmsrv_op_queue.c \
     src/util/sss_sockets.c \
     src/util/sss_krb5.c \
     src/util/sss_iobuf.c \
@@ -3288,6 +3292,7 @@ test_inotify_LDADD = \
     libsss_test_common.la \
     $(NULL)
 
+if BUILD_KCM
 test_kcm_json_SOURCES = \
     src/tests/cmocka/test_kcm_json_marshalling.c \
     src/responder/kcm/kcmsrv_ccache_json.c \
@@ -3309,6 +3314,22 @@ test_kcm_json_LDADD = \
     libsss_test_common.la \
     $(NULL)
 
+test_kcm_queue_SOURCES = \
+    src/tests/cmocka/test_kcm_queue.c \
+    src/responder/kcm/kcmsrv_op_queue.c \
+    $(NULL)
+test_kcm_queue_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+test_kcm_queue_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(SSSD_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    libsss_test_common.la \
+    $(NULL)
+
+endif # BUILD_KCM
+
 endif # HAVE_CMOCKA
 
 noinst_PROGRAMS = pam_test_client
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index 063c27b..3ee9780 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -133,6 +133,13 @@ static int kcm_get_config(struct kcm_ctx *kctx)
         goto done;
     }
 
+    kctx->qctx = kcm_ops_queue_create(kctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot create KCM request queue [%d]: %s\n",
+               ret, strerror(ret));
+        goto done;
+    }
     ret = EOK;
 done:
     return ret;
diff --git a/src/responder/kcm/kcmsrv_cmd.c b/src/responder/kcm/kcmsrv_cmd.c
index 537e889..81015de 100644
--- a/src/responder/kcm/kcmsrv_cmd.c
+++ b/src/responder/kcm/kcmsrv_cmd.c
@@ -353,14 +353,18 @@ struct kcm_req_ctx {
 
 static void kcm_cmd_request_done(struct tevent_req *req);
 
-static errno_t kcm_cmd_dispatch(struct kcm_req_ctx *req_ctx)
+static errno_t kcm_cmd_dispatch(struct kcm_ctx *kctx,
+                                struct kcm_req_ctx *req_ctx)
 {
     struct tevent_req *req;
     struct cli_ctx *cctx;
 
     cctx = req_ctx->cctx;
 
-    req = kcm_cmd_send(req_ctx, cctx->ev, req_ctx->kctx->kcm_data,
+    req = kcm_cmd_send(req_ctx,
+                       cctx->ev,
+                       kctx->qctx,
+                       req_ctx->kctx->kcm_data,
                        req_ctx->cctx->creds,
                        &req_ctx->op_io.request,
                        req_ctx->op_io.op);
@@ -505,7 +509,7 @@ static void kcm_recv(struct cli_ctx *cctx)
     /* do not read anymore, client is done sending */
     TEVENT_FD_NOT_READABLE(cctx->cfde);
 
-    ret = kcm_cmd_dispatch(req);
+    ret = kcm_cmd_dispatch(kctx, req);
     if (ret != EOK) {
         DEBUG(SSSDBG_FATAL_FAILURE,
               "Failed to dispatch KCM operation [%d]: %s\n",
diff --git a/src/responder/kcm/kcmsrv_op_queue.c b/src/responder/kcm/kcmsrv_op_queue.c
new file mode 100644
index 0000000..f6c425d
--- /dev/null
+++ b/src/responder/kcm/kcmsrv_op_queue.c
@@ -0,0 +1,264 @@
+/*
+   SSSD
+
+   KCM Server - the KCM operations wait queue
+
+   Copyright (C) Red Hat, 2017
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "util/util_creds.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+
+#define QUEUE_HASH_SIZE      32
+
+struct kcm_ops_queue_entry {
+    struct tevent_req *req;
+    uid_t uid;
+
+    hash_table_t *wait_queue_hash;
+
+    struct kcm_ops_queue_entry *head;
+    struct kcm_ops_queue_entry *next;
+    struct kcm_ops_queue_entry *prev;
+};
+
+struct kcm_ops_queue_ctx {
+    /* UID: dlist of kcm_ops_queue_entry */
+    hash_table_t *wait_queue_hash;
+};
+
+/*
+ * Per-UID wait queue
+ *
+ * They key in the hash table is the UID of the peer. The value of each
+ * hash table entry is a linked list of kcm_ops_queue_entry structures
+ * which primarily hold the tevent request being queued.
+ */
+struct kcm_ops_queue_ctx *kcm_ops_queue_create(TALLOC_CTX *mem_ctx)
+{
+    errno_t ret;
+    struct kcm_ops_queue_ctx *queue_ctx;
+
+    queue_ctx = talloc_zero(mem_ctx, struct kcm_ops_queue_ctx);
+    if (queue_ctx == NULL) {
+        return NULL;
+    }
+
+    ret = sss_hash_create_ex(mem_ctx, QUEUE_HASH_SIZE,
+                             &queue_ctx->wait_queue_hash, 0, 0, 0, 0,
+                             NULL, NULL);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "sss_hash_create failed [%d]: %s\n", ret, sss_strerror(ret));
+        talloc_free(queue_ctx);
+        return NULL;
+    }
+
+    return queue_ctx;
+}
+
+static int kcm_op_queue_entry_destructor(struct kcm_ops_queue_entry *entry)
+{
+    int ret;
+    struct kcm_ops_queue_entry *next_entry;
+    hash_key_t key;
+
+    if (entry == NULL) {
+        return 1;
+    }
+
+    /* Take the next entry from the queue */
+    next_entry = entry->next;
+
+    /* Remove the current entry from the queue */
+    DLIST_REMOVE(entry->head, entry);
+
+    if (next_entry == NULL) {
+        key.type = HASH_KEY_ULONG;
+        key.ul = entry->uid;
+
+        /* If this was the last entry, remove the key (the UID) from the
+         * hash table to signal the queue is empty
+         */
+        ret = hash_delete(entry->wait_queue_hash, &key);
+        if (ret != HASH_SUCCESS) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Failed to remove wait queue for user %"SPRIuid"\n",
+                  entry->uid);
+            return 1;
+        }
+        return 0;
+    }
+
+    /* Otherwise, mark the current head as done to run the next request */
+    tevent_req_done(next_entry->req);
+    return 0;
+}
+
+static errno_t kcm_op_queue_add(hash_table_t *wait_queue_hash,
+                                struct kcm_ops_queue_entry *entry,
+                                uid_t uid)
+{
+    errno_t ret;
+    hash_key_t key;
+    hash_value_t value;
+    struct kcm_ops_queue_entry *head = NULL;
+
+    key.type = HASH_KEY_ULONG;
+    key.ul = uid;
+
+    ret = hash_lookup(wait_queue_hash, &key, &value);
+    switch (ret) {
+    case HASH_SUCCESS:
+        /* The key with this UID already exists. Its value is request queue
+         * for the UID, so let's just add the current request to the end
+         * of the queue and wait for the previous requests to finish
+         */
+        if (value.type != HASH_VALUE_PTR) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n");
+            return EINVAL;
+        }
+
+        head = talloc_get_type(value.ptr, struct kcm_ops_queue_entry);
+        if (head == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Invalid queue pointer\n");
+            return EINVAL;
+        }
+
+        entry->head = head;
+        DLIST_ADD_END(head, entry, struct kcm_ops_queue_entry *);
+
+        DEBUG(SSSDBG_TRACE_LIBS, "Waiting in queue\n");
+        ret = EAGAIN;
+        break;
+
+    case HASH_ERROR_KEY_NOT_FOUND:
+        /* No request for this UID yet. Enqueue this request in case
+         * another one comes in and return EOK to run the current request
+         * immediatelly
+         */
+        entry->head = entry;
+
+        value.type = HASH_VALUE_PTR;
+        value.ptr = entry;
+
+        ret = hash_enter(wait_queue_hash, &key, &value);
+        if (ret != HASH_SUCCESS) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n");
+            return EIO;
+        }
+
+        DEBUG(SSSDBG_TRACE_LIBS,
+              "Added a first request to the queue, running immediately\n");
+        ret = EOK;
+        break;
+
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n");
+        return EIO;
+    }
+
+    talloc_steal(wait_queue_hash, entry);
+    talloc_set_destructor(entry, kcm_op_queue_entry_destructor);
+    return ret;
+}
+
+struct kcm_op_queue_state {
+    struct kcm_ops_queue_entry *entry;
+};
+
+/*
+ * Enqueue a request.
+ *
+ * If the request queue /for the given ID/ is empty, that is, if this
+ * request is the first one in the queue, run the request immediatelly.
+ *
+ * Otherwise just add it to the queue and wait until the previous request
+ * finishes and only at that point mark the current request as done, which
+ * will trigger calling the recv function and allow the request to continue.
+ */
+struct tevent_req *kcm_op_queue_send(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     struct kcm_ops_queue_ctx *qctx,
+                                     struct cli_creds *client)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct kcm_op_queue_state *state;
+    uid_t uid;
+
+    uid = cli_creds_get_uid(client);
+
+    req = tevent_req_create(mem_ctx, &state, struct kcm_op_queue_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    state->entry = talloc_zero(state, struct kcm_ops_queue_entry);
+    if (state->entry == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    state->entry->req = req;
+    state->entry->uid = uid;
+    state->entry->wait_queue_hash = qctx->wait_queue_hash;
+
+    DEBUG(SSSDBG_FUNC_DATA,
+          "Adding request by %"SPRIuid" to the wait queue\n", uid);
+
+    ret = kcm_op_queue_add(qctx->wait_queue_hash, state->entry, uid);
+    if (ret == EOK) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              "Wait queue was empty, running immediately\n");
+        goto immediate;
+    } else if (ret != EAGAIN) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot enqueue request [%d]: %s\n", ret, sss_strerror(ret));
+        goto immediate;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS, "Waiting our turn in the queue\n");
+    return req;
+
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+/*
+ * The queue recv function is called when this request is 'activated'. The queue
+ * entry should be allocated on the same memory context as the enqueued request
+ * to trigger freeing the kcm_ops_queue_entry structure destructor when the
+ * parent request is done and its tevent_req freed. This would in turn unblock
+ * the next request in the queue
+ */
+errno_t kcm_op_queue_recv(struct tevent_req *req,
+                          TALLOC_CTX *mem_ctx,
+                          struct kcm_ops_queue_entry **_entry)
+{
+    struct kcm_op_queue_state *state = tevent_req_data(req,
+                                                struct kcm_op_queue_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *_entry = talloc_steal(mem_ctx, state->entry);
+    return EOK;
+}
diff --git a/src/responder/kcm/kcmsrv_ops.c b/src/responder/kcm/kcmsrv_ops.c
index 50e8cc6..2feaf51 100644
--- a/src/responder/kcm/kcmsrv_ops.c
+++ b/src/responder/kcm/kcmsrv_ops.c
@@ -67,17 +67,21 @@ struct kcm_op {
 
 struct kcm_cmd_state {
     struct kcm_op *op;
+    struct tevent_context *ev;
 
+    struct kcm_ops_queue_entry *queue_entry;
     struct kcm_op_ctx *op_ctx;
     struct sss_iobuf *reply;
 
     uint32_t op_ret;
 };
 
+static void kcm_cmd_queue_done(struct tevent_req *subreq);
 static void kcm_cmd_done(struct tevent_req *subreq);
 
 struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx,
                                 struct tevent_context *ev,
+                                struct kcm_ops_queue_ctx *qctx,
                                 struct kcm_resp_ctx *kcm_data,
                                 struct cli_creds *client,
                                 struct kcm_data *input,
@@ -93,6 +97,7 @@ struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx,
         return NULL;
     }
     state->op = op;
+    state->ev = ev;
 
     if (op == NULL) {
         ret = EINVAL;
@@ -154,12 +159,12 @@ struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx,
         goto immediate;
     }
 
-    subreq = op->fn_send(state, ev, state->op_ctx);
+    subreq = kcm_op_queue_send(state, ev, qctx, client);
     if (subreq == NULL) {
         ret = ENOMEM;
         goto immediate;
     }
-    tevent_req_set_callback(subreq, kcm_cmd_done, req);
+    tevent_req_set_callback(subreq, kcm_cmd_queue_done, req);
     return req;
 
 immediate:
@@ -168,6 +173,31 @@ struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx,
     return req;
 }
 
+static void kcm_cmd_queue_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct kcm_cmd_state *state = tevent_req_data(req, struct kcm_cmd_state);
+    errno_t ret;
+
+    /* When this request finishes, it frees the queue_entry which unblocks
+     * other requests by the same UID
+     */
+    ret = kcm_op_queue_recv(subreq, state, &state->queue_entry);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot acquire queue slot\n");
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = state->op->fn_send(state, state->ev, state->op_ctx);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, kcm_cmd_done, req);
+}
+
 static void kcm_cmd_done(struct tevent_req *subreq)
 {
     struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
diff --git a/src/responder/kcm/kcmsrv_ops.h b/src/responder/kcm/kcmsrv_ops.h
index 8e6feaf..67d9f86 100644
--- a/src/responder/kcm/kcmsrv_ops.h
+++ b/src/responder/kcm/kcmsrv_ops.h
@@ -34,6 +34,7 @@ const char *kcm_opt_name(struct kcm_op *op);
 
 struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx,
                                 struct tevent_context *ev,
+                                struct kcm_ops_queue_ctx *qctx,
                                 struct kcm_resp_ctx *kcm_data,
                                 struct cli_creds *client,
                                 struct kcm_data *input,
diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h
index 74f30c0..f081a6b 100644
--- a/src/responder/kcm/kcmsrv_pvt.h
+++ b/src/responder/kcm/kcmsrv_pvt.h
@@ -25,6 +25,7 @@
 #include "config.h"
 
 #include <sys/types.h>
+#include <krb5/krb5.h>
 #include "responder/common/responder.h"
 
 /*
@@ -65,6 +66,7 @@ struct kcm_ctx {
     int fd_limit;
     char *socket_path;
     enum kcm_ccdb_be cc_be;
+    struct kcm_ops_queue_ctx *qctx;
 
     struct kcm_resp_ctx *kcm_data;
 };
@@ -78,4 +80,22 @@ int kcm_connection_setup(struct cli_ctx *cctx);
  */
 krb5_error_code sss2krb5_error(errno_t err);
 
+/* We enqueue all requests by the same UID to avoid concurrency issues
+ * especially when performing multiple round-trips to sssd-secrets. In
+ * future, we should relax the queue to allow multiple read-only operations
+ * if no write operations are in progress.
+ */
+struct kcm_ops_queue_entry;
+
+struct kcm_ops_queue_ctx *kcm_ops_queue_create(TALLOC_CTX *mem_ctx);
+
+struct tevent_req *kcm_op_queue_send(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     struct kcm_ops_queue_ctx *qctx,
+                                     struct cli_creds *client);
+
+errno_t kcm_op_queue_recv(struct tevent_req *req,
+                          TALLOC_CTX *mem_ctx,
+                          struct kcm_ops_queue_entry **_entry);
+
 #endif /* __KCMSRV_PVT_H__ */
diff --git a/src/tests/cmocka/test_kcm_queue.c b/src/tests/cmocka/test_kcm_queue.c
new file mode 100644
index 0000000..7c389b0
--- /dev/null
+++ b/src/tests/cmocka/test_kcm_queue.c
@@ -0,0 +1,364 @@
+/*
+    Copyright (C) 2017 Red Hat
+
+    SSSD tests: Test KCM wait queue
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <popt.h>
+
+#include "util/util.h"
+#include "util/util_creds.h"
+#include "tests/cmocka/common_mock.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+
+#define FAST_REQ_ID     0
+#define SLOW_REQ_ID     1
+
+#define FAST_REQ_DELAY  1
+#define SLOW_REQ_DELAY  2
+
+struct timed_request_state {
+    struct tevent_context *ev;
+    struct kcm_ops_queue_ctx *qctx;
+    struct cli_creds *client;
+    int delay;
+    int req_id;
+
+    struct kcm_ops_queue_entry *queue_entry;
+};
+
+static void timed_request_start(struct tevent_req *subreq);
+static void timed_request_done(struct tevent_context *ev,
+                               struct tevent_timer *te,
+                               struct timeval current_time,
+                               void *pvt);
+
+static struct tevent_req *timed_request_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct kcm_ops_queue_ctx *qctx,
+                                             struct cli_creds *client,
+                                             int delay,
+                                             int req_id)
+{
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct timed_request_state *state;
+
+    req = tevent_req_create(mem_ctx, &state, struct timed_request_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->qctx = qctx;
+    state->client = client;
+    state->delay = delay;
+    state->req_id = req_id;
+
+    DEBUG(SSSDBG_TRACE_ALL, "Request %p with delay %d\n", req, delay);
+
+    subreq = kcm_op_queue_send(state, ev, qctx, client);
+    if (subreq == NULL) {
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, timed_request_start, req);
+
+    return req;
+}
+
+static void timed_request_start(struct tevent_req *subreq)
+{
+    struct timeval tv;
+    struct tevent_timer *timeout = NULL;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct timed_request_state *state = tevent_req_data(req,
+                                                struct timed_request_state);
+    errno_t ret;
+
+    ret = kcm_op_queue_recv(subreq, state, &state->queue_entry);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tv = tevent_timeval_current_ofs(state->delay, 0);
+    timeout = tevent_add_timer(state->ev, state, tv, timed_request_done, req);
+    if (timeout == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    return;
+}
+
+static void timed_request_done(struct tevent_context *ev,
+                               struct tevent_timer *te,
+                               struct timeval current_time,
+                               void *pvt)
+{
+    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+    DEBUG(SSSDBG_TRACE_ALL, "Request %p done\n", req);
+    tevent_req_done(req);
+}
+
+static errno_t timed_request_recv(struct tevent_req *req,
+                                  int *req_id)
+{
+    struct timed_request_state *state = tevent_req_data(req,
+                                                struct timed_request_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *req_id = state->req_id;
+    return EOK;
+}
+
+struct test_ctx {
+    struct kcm_ops_queue_ctx *qctx;
+    struct tevent_context *ev;
+
+    int *req_ids;
+
+    int num_requests;
+    int finished_requests;
+    bool done;
+    errno_t error;
+};
+
+static int setup_kcm_queue(void **state)
+{
+    struct test_ctx *tctx;
+
+    tctx = talloc_zero(NULL, struct test_ctx);
+    assert_non_null(tctx);
+
+    tctx->ev = tevent_context_init(tctx);
+    assert_non_null(tctx->ev);
+
+    tctx->qctx = kcm_ops_queue_create(tctx);
+    assert_non_null(tctx->qctx);
+
+    *state = tctx;
+    return 0;
+}
+
+static int teardown_kcm_queue(void **state)
+{
+    struct test_ctx *tctx = talloc_get_type(*state, struct test_ctx);
+    talloc_free(tctx);
+    return 0;
+}
+
+static void test_kcm_queue_done(struct tevent_req *req)
+{
+    struct test_ctx *test_ctx = tevent_req_callback_data(req,
+                                                struct test_ctx);
+    int req_id;
+
+    test_ctx->error = timed_request_recv(req, &req_id);
+    talloc_zfree(req);
+    if (test_ctx->error != EOK) {
+        test_ctx->done = true;
+        return;
+    }
+
+    if (test_ctx->req_ids[test_ctx->finished_requests] != req_id) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Request %d finished, expected %d\n",
+              req_id, test_ctx->req_ids[test_ctx->finished_requests]);
+        test_ctx->error = EIO;
+        test_ctx->done = true;
+        return;
+    }
+
+    test_ctx->finished_requests++;
+    if (test_ctx->finished_requests == test_ctx->num_requests) {
+        test_ctx->done = true;
+        return;
+    }
+}
+
+/*
+ * Just make sure that a single pass through the queue works
+ */
+static void test_kcm_queue_single(void **state)
+{
+    struct test_ctx *test_ctx = talloc_get_type(*state, struct test_ctx);
+    struct tevent_req *req;
+    struct cli_creds client;
+    static int req_ids[] = { 0 };
+
+    client.ucred.uid = getuid();
+    client.ucred.gid = getgid();
+
+    req = timed_request_send(test_ctx,
+                             test_ctx->ev,
+                             test_ctx->qctx,
+                             &client, 1, 0);
+    assert_non_null(req);
+    tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
+
+    test_ctx->num_requests = 1;
+    test_ctx->req_ids = req_ids;
+
+    while (test_ctx->done == false) {
+        tevent_loop_once(test_ctx->ev);
+    }
+    assert_int_equal(test_ctx->error, EOK);
+}
+
+/*
+ * Test that multiple requests from the same ID wait for one another
+ */
+static void test_kcm_queue_multi_same_id(void **state)
+{
+    struct test_ctx *test_ctx = talloc_get_type(*state, struct test_ctx);
+    struct tevent_req *req;
+    struct cli_creds client;
+    /* The slow request will finish first because request from
+     * the same ID are serialized
+     */
+    static int req_ids[] = { SLOW_REQ_ID, FAST_REQ_ID };
+
+    client.ucred.uid = getuid();
+    client.ucred.gid = getgid();
+
+    req = timed_request_send(test_ctx,
+                             test_ctx->ev,
+                             test_ctx->qctx,
+                             &client,
+                             SLOW_REQ_DELAY,
+                             SLOW_REQ_ID);
+    assert_non_null(req);
+    tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
+
+    req = timed_request_send(test_ctx,
+                             test_ctx->ev,
+                             test_ctx->qctx,
+                             &client,
+                             FAST_REQ_DELAY,
+                             FAST_REQ_ID);
+    assert_non_null(req);
+    tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
+
+    test_ctx->num_requests = 2;
+    test_ctx->req_ids = req_ids;
+
+    while (test_ctx->done == false) {
+        tevent_loop_once(test_ctx->ev);
+    }
+    assert_int_equal(test_ctx->error, EOK);
+}
+
+/*
+ * Test that multiple requests from different IDs don't wait for one
+ * another and can run concurrently
+ */
+static void test_kcm_queue_multi_different_id(void **state)
+{
+    struct test_ctx *test_ctx = talloc_get_type(*state, struct test_ctx);
+    struct tevent_req *req;
+    struct cli_creds client;
+    /* In this test, the fast request will finish sooner because
+     * both requests are from different IDs, allowing them to run
+     * concurrently
+     */
+    static int req_ids[] = { FAST_REQ_ID, SLOW_REQ_ID };
+
+    client.ucred.uid = getuid();
+    client.ucred.gid = getgid();
+
+    req = timed_request_send(test_ctx,
+                             test_ctx->ev,
+                             test_ctx->qctx,
+                             &client,
+                             SLOW_REQ_DELAY,
+                             SLOW_REQ_ID);
+    assert_non_null(req);
+    tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
+
+    client.ucred.uid = getuid() + 1;
+    client.ucred.gid = getgid() + 1;
+
+    req = timed_request_send(test_ctx,
+                             test_ctx->ev,
+                             test_ctx->qctx,
+                             &client,
+                             FAST_REQ_DELAY,
+                             FAST_REQ_ID);
+    assert_non_null(req);
+    tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
+
+    test_ctx->num_requests = 2;
+    test_ctx->req_ids = req_ids;
+
+    while (test_ctx->done == false) {
+        tevent_loop_once(test_ctx->ev);
+    }
+    assert_int_equal(test_ctx->error, EOK);
+}
+
+int main(int argc, const char *argv[])
+{
+    poptContext pc;
+    int opt;
+    int rv;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_kcm_queue_single,
+                                        setup_kcm_queue,
+                                        teardown_kcm_queue),
+        cmocka_unit_test_setup_teardown(test_kcm_queue_multi_same_id,
+                                        setup_kcm_queue,
+                                        teardown_kcm_queue),
+        cmocka_unit_test_setup_teardown(test_kcm_queue_multi_different_id,
+                                        setup_kcm_queue,
+                                        teardown_kcm_queue),
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old db to be sure */
+    tests_set_cwd();
+
+    rv = cmocka_run_group_tests(tests, NULL, NULL);
+
+    return rv;
+}

From 028c13bcdf202a92a1aad6a213a8ffd4d089c578 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 21 Mar 2017 14:26:54 +0100
Subject: [PATCH 21/21] KCM: Idle-terminate the responder if the secrets back
 end is used

Existing with memory database would be fatal as we keep the ccaches in
memory then, but if the ccaches are stored in sssd-secrets, we can just
exit on idle.
---
 src/config/cfg_rules.ini | 1 +
 src/responder/kcm/kcm.c  | 9 +++++++++
 2 files changed, 10 insertions(+)

diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index 67a5d1f..933ebcc 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -281,6 +281,7 @@ option = client_idle_timeout
 option = description
 option = socket_path
 option = ccache_storage
+option = responder_idle_timeout
 
 [rule/allowed_domain_options]
 validator = ini_allowed_options
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index 3ee9780..2202f96 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -133,6 +133,15 @@ static int kcm_get_config(struct kcm_ctx *kctx)
         goto done;
     }
 
+    if (kctx->cc_be == CCDB_BE_SECRETS) {
+        ret = responder_setup_idle_timeout_config(kctx->rctx);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "Cannot set up idle responder timeout\n");
+            /* Not fatal */
+        }
+    }
+
     kctx->qctx = kcm_ops_queue_create(kctx);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE,
