/*
   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 "src/providers/krb5/krb5_ccache.h"
#include "responder/kcm/kcm_renew.h"
#include "responder/kcm/kcmsrv_ccache.h"
#include "responder/kcm/kcmsrv_ccache_pvt.h"
#include "responder/kcm/kcmsrv_ccache_be.h"


static struct kcm_cred *kcm_cred_dup(TALLOC_CTX *mem_ctx,
                                     struct kcm_cred *crd);

static int kcm_cc_destructor(struct kcm_ccache *cc)
{
    if (cc == NULL) {
        return 0;
    }

    if (cc->client != NULL) {
        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 = NULL;
    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);
        goto done;
    }

    cc->name = talloc_strdup(cc, name);
    if (cc->name == NULL) {
        ret = ENOMEM;
        goto done;
    }

    uuid_generate(cc->uuid);

    if (princ) {
        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);
            ret = ERR_INTERNAL;
            goto done;
        }
    }

    cc->owner.uid = cli_creds_get_uid(owner);
    cc->owner.gid = cli_creds_get_gid(owner);
    cc->kdc_offset = 0;

    talloc_set_destructor(cc, kcm_cc_destructor);
    *_cc = cc;
    ret = EOK;
done:
    if (ret != EOK) {
        talloc_free(cc);
    }
    return ret;
}

struct kcm_ccache *kcm_cc_dup(TALLOC_CTX *mem_ctx,
                              const struct kcm_ccache *cc)
{
    struct kcm_ccache *dup;
    struct kcm_cred *crd_dup;
    struct kcm_cred *crd;

    dup = talloc_zero(mem_ctx, struct kcm_ccache);
    if (dup == NULL) {
        return NULL;
    }
    memcpy(dup, cc, sizeof(struct kcm_ccache));

    dup->creds = NULL;
    DLIST_FOR_EACH(crd, cc->creds) {
        crd_dup = kcm_cred_dup(dup, crd);
        if (crd_dup == NULL) {
            talloc_free(dup);
            return NULL;
        }

        DLIST_ADD(dup->creds, crd_dup);
    }

    return dup;
}

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;
}

static struct kcm_cred *kcm_cred_dup(TALLOC_CTX *mem_ctx,
                                     struct kcm_cred *crd)
{
    struct kcm_cred *dup;

    dup = talloc_zero(mem_ctx, struct kcm_cred);
    if (dup == NULL) {
        return NULL;
    }

    uuid_copy(dup->uuid, crd->uuid);
    dup->cred_blob = crd->cred_blob;

    return dup;
}

#ifdef HAVE_KRB5_UNMARSHAL_CREDENTIALS
static krb5_creds *kcm_cred_to_krb5(krb5_context kctx, struct kcm_cred *kcm_crd)
{
    krb5_error_code kerr;
    krb5_creds *kcrd;
    krb5_data data;

    get_krb5_data_from_cred(kcm_crd->cred_blob, &data);

    kerr = krb5_unmarshal_credentials(kctx, &data, &kcrd);
    if (kerr != 0) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unmarshal credentials\n");
        return NULL;
    }

    return kcrd;
}
#endif

static errno_t
kcm_cc_remove_duplicates(struct kcm_ccache *cc,
                         struct kcm_cred *kcm_crd)
{
#ifdef HAVE_KRB5_UNMARSHAL_CREDENTIALS
    struct kcm_cred *p, *q;
    krb5_error_code kerr;
    krb5_context kctx;
    krb5_creds *kcrd_cc;
    krb5_creds *kcrd;
    errno_t ret;
    bool bret;

    kerr = krb5_init_context(&kctx);
    if (kerr != 0) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n");
        return EIO;
    }

    kcrd = kcm_cred_to_krb5(kctx, kcm_crd);
    if (kcrd == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n");
        ret = ERR_INTERNAL;
        goto done;
    }

    DLIST_FOR_EACH_SAFE(p, q, cc->creds) {
        kcrd_cc = kcm_cred_to_krb5(kctx, p);
        if (kcrd_cc == NULL) {
            DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n");
            ret = ERR_INTERNAL;
            goto done;
        }

        bret = sss_krb5_creds_compare(kctx, kcrd, kcrd_cc);
        krb5_free_creds(kctx, kcrd_cc);
        if (!bret) {
            continue;
        }

        /* This cred is the same ticket. We will replace it with the new one. */
        DLIST_REMOVE(cc->creds, p);
    }

    ret = EOK;

done:
    krb5_free_creds(kctx, kcrd);
    krb5_free_context(kctx);

    return ret;
#else
    return EOK;
#endif
}

/* Add a cred to ccache */
errno_t kcm_cc_store_creds(struct kcm_ccache *cc,
                           struct kcm_cred *crd)
{
    errno_t ret;

    ret = kcm_cc_remove_duplicates(cc, crd);
    if (ret != EOK) {
        DEBUG(SSSDBG_MINOR_FAILURE, "Unable to remove duplicate credentials "
              "[%d]: %s\n", ret, sss_strerror(ret));
    }

    DLIST_ADD(cc->creds, crd);
    talloc_steal(cc, crd);
    return EOK;
}

errno_t kcm_cc_set_header(struct kcm_ccache *cc,
                          const char *sec_key,
                          struct cli_creds *client)
{
    errno_t ret;

    ret = sec_key_parse(cc, sec_key, &cc->name, cc->uuid);
    if (ret != EOK) {
        return ret;
    }

    /* 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);

    return EOK;
}

#ifdef HAVE_KCM_RENEWAL
static int kcm_cc_unmarshal_destructor(krb5_creds **creds)
{
    krb5_error_code kerr;
    krb5_context krb_ctx;
    int i;

    kerr = krb5_init_context(&krb_ctx);
    if (kerr != 0) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n");
        return 1;
    }

    for (i = 0; creds[i] != NULL; i++) {
        krb5_free_creds(krb_ctx, creds[i]);
    }

    krb5_free_context(krb_ctx);

    return 0;
}
#endif

krb5_creds **kcm_cc_unmarshal(TALLOC_CTX *mem_ctx,
                              krb5_context krb_context,
                              struct kcm_ccache *cc)
{
#ifndef HAVE_KCM_RENEWAL
    return NULL;
#else
    TALLOC_CTX *tmp_ctx;
    struct kcm_cred *cred;
    krb5_creds **cred_list;
    int i = 0;
    int count = 0;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        goto done;
    }

    for (cred = kcm_cc_get_cred(cc); cred != NULL; cred = kcm_cc_next_cred(cred)) {
        count++;
    }

    cred_list = talloc_array(tmp_ctx, krb5_creds *, count + 1);

    for (cred = kcm_cc_get_cred(cc); cred != NULL; cred = kcm_cc_next_cred(cred), i++) {
        cred_list[i] = kcm_cred_to_krb5(krb_context, cred);
        if (cred_list[i] == NULL) {
            DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n");
            goto done;
        }
    }

    cred_list[count] = NULL;
    talloc_set_destructor(cred_list, kcm_cc_unmarshal_destructor);

    talloc_steal(mem_ctx, cred_list);

    return cred_list;
done:
    talloc_free(tmp_ctx);
    return NULL;
#endif
}

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;
}

errno_t kcm_ccdb_renew_tgts(TALLOC_CTX *mem_ctx,
                            struct krb5_ctx *krb5_ctx,
                            struct tevent_context *ev,
                            struct kcm_ccdb *ccdb,
                            struct kcm_ccache ***_cc_list)
{
    struct kcm_ccache **cc;
    errno_t ret;

    if (krb5_ctx == NULL || ev == NULL || ccdb == NULL) {
        ret = EINVAL;
        return ret;
    }

    if (ccdb->ops->list_all_cc == NULL) {
        ret = EINVAL;
        DEBUG(SSSDBG_TRACE_INTERNAL, "List all cc function not available\n");
        goto done;
    }

    ret = ccdb->ops->list_all_cc(mem_ctx, krb5_ctx, ev, ccdb, &cc);
    if (ret != EOK && ret != ENOENT) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failure to retrieve list of ccaches"
                                   "[%d]: %s\n", ret, sss_strerror(ret));
        return ret;
    } else if (ret == ENOENT) {
        goto done;
    }

    *_cc_list = talloc_steal(mem_ctx, cc);

    ret = EOK;
done:

    return ret;
}

struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx,
                               struct tevent_context *ev,
                               struct confdb_ctx *cdb,
                               const char *confdb_service_path,
                               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");
        ccdb->ops = &ccdb_mem_ops;
        break;
    case CCDB_BE_SECDB:
        DEBUG(SSSDBG_FUNC_DATA, "KCM back end: libsss_secrets\n");
        ccdb->ops = &ccdb_secdb_ops;
        break;
    default:
        DEBUG(SSSDBG_CRIT_FAILURE, "Unknown ccache database backend\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, cdb, confdb_service_path);
    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(state, 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(state,
                                       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(state, 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(state,
                                         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;
}

static void kcm_mod_ctx_clear(struct kcm_mod_ctx *mod_ctx)
{
    if (mod_ctx == NULL) {
        return;
    }

    mod_ctx->kdc_offset = INT32_MAX;
    if (mod_ctx->client != NULL) {
        krb5_free_principal(NULL, mod_ctx->client);
        mod_ctx->client = NULL;
    }

    return;
}

struct kcm_mod_ctx *kcm_mod_ctx_new(TALLOC_CTX *mem_ctx)
{
    struct kcm_mod_ctx *mod_ctx;

    mod_ctx = talloc_zero(mem_ctx, struct kcm_mod_ctx);
    if (mod_ctx == NULL) {
        return NULL;
    }

    kcm_mod_ctx_clear(mod_ctx);
    return mod_ctx;
}

errno_t kcm_mod_cc(struct kcm_ccache *cc, struct kcm_mod_ctx *mod_ctx)
{
    if (cc == NULL || mod_ctx == NULL) {
        return EINVAL;
    }

    if (mod_ctx->kdc_offset != INT32_MAX) {
        cc->kdc_offset = mod_ctx->kdc_offset;
    }

    if (mod_ctx->client != NULL) {
        krb5_error_code kret;

        kret = krb5_copy_principal(NULL, mod_ctx->client, &cc->client);
        if (kret != 0) {
            DEBUG(SSSDBG_OP_FAILURE,
                "krb5_copy_principal failed: %d\n", kret);
            return ERR_INTERNAL;
        }
    }

    return EOK;
}

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(state,
                                      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(state,
                                             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);
    if (subreq == NULL) {
        ret = ENOMEM;
        goto immediate;
    }
    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;
}
