/*
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 .
*/
#include "config.h"
#include
#include
#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;
};
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) {
sss_erase_mem_securely(
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 confdb_ctx *cdb,
const char *confdb_service_path)
{
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;
errno_t ret;
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) {
/* 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
*/
state->cc = kcm_cc_dup(state, ccwrap->cc);
if (state->cc == 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;
}
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);
errno_t ret;
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) {
/* 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
*/
state->cc = kcm_cc_dup(state, ccwrap->cc);
if (state->cc == 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;
}
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_NO_CREDS;
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;
}
ret = kcm_mod_cc(ccwrap->cc, mod_cc);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Cannot modify 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_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,
};