/* SSSD KCM Server - ccache storage using libsss_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 . */ #include "config.h" #include #include #include "util/util.h" #include "secrets/secrets.h" #include "util/crypto/sss_crypto.h" #include "util/sss_krb5.h" #include "util/strtonum.h" #include "responder/kcm/kcmsrv_ccache_pvt.h" #include "responder/kcm/kcmsrv_ccache_be.h" #include "responder/kcm/kcm_renew.h" #include "providers/krb5/krb5_ccache.h" #define KCM_SECDB_URL "persistent" #define KCM_SECDB_BASE_FMT KCM_SECDB_URL"/%"SPRIuid"/" #define KCM_SECDB_CCACHE_FMT KCM_SECDB_BASE_FMT"ccache/" #define KCM_SECDB_DFL_FMT KCM_SECDB_BASE_FMT"default" static errno_t sec_get(TALLOC_CTX *mem_ctx, struct sss_sec_req *req, struct sss_iobuf **_buf) { errno_t ret; TALLOC_CTX *tmp_ctx; uint8_t *data; size_t len; struct sss_iobuf *buf; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } ret = sss_sec_get(tmp_ctx, req, &data, &len); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot retrieve the secret [%d]: %s\n", ret, sss_strerror(ret)); goto done; } buf = sss_iobuf_init_steal(tmp_ctx, data, len); if (buf == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot init the iobuf\n"); ret = EIO; goto done; } *_buf = talloc_steal(mem_ctx, buf); ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t sec_put(TALLOC_CTX *mem_ctx, struct sss_sec_req *req, struct sss_iobuf *buf) { errno_t ret; ret = sss_sec_put(req, sss_iobuf_get_data(buf), sss_iobuf_get_size(buf)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot write the secret [%d]: %s\n", ret, sss_strerror(ret)); } return ret; } static errno_t sec_update(TALLOC_CTX *mem_ctx, struct sss_sec_req *req, struct sss_iobuf *buf) { errno_t ret; ret = sss_sec_update(req, sss_iobuf_get_data(buf), sss_iobuf_get_size(buf)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot write the secret [%d]: %s\n", ret, sss_strerror(ret)); } return ret; } static const char *secdb_container_url_create(TALLOC_CTX *mem_ctx, struct cli_creds *client) { return talloc_asprintf(mem_ctx, KCM_SECDB_CCACHE_FMT, cli_creds_get_uid(client)); } static const char *secdb_cc_url_create(TALLOC_CTX *mem_ctx, struct cli_creds *client, const char *secdb_key) { return talloc_asprintf(mem_ctx, KCM_SECDB_CCACHE_FMT"%s", cli_creds_get_uid(client), secdb_key); } static const char *secdb_dfl_url_create(TALLOC_CTX *mem_ctx, struct cli_creds *client) { return talloc_asprintf(mem_ctx, KCM_SECDB_DFL_FMT, cli_creds_get_uid(client)); } static errno_t kcm_ccache_to_secdb_kv(TALLOC_CTX *mem_ctx, struct kcm_ccache *cc, struct cli_creds *client, const char **_url, struct sss_iobuf **_payload) { errno_t ret; const char *url; const char *key; TALLOC_CTX *tmp_ctx; struct sss_iobuf *payload; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } key = sec_key_create(tmp_ctx, cc->name, cc->uuid); if (key == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create key for %s\n", cc->name); ret = ENOMEM; goto done; } url = secdb_cc_url_create(tmp_ctx, client, key); if (url == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create URL from %s\n", key); ret = ENOMEM; goto done; } ret = kcm_ccache_to_sec_input_binary(mem_ctx, cc, &payload); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot convert ccache to a secret [%d][%s]\n", ret, sss_strerror(ret)); goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Created URL %s\n", url); *_url = talloc_steal(mem_ctx, url); *_payload = talloc_steal(mem_ctx, payload); done: talloc_free(tmp_ctx); return ret; } struct ccdb_secdb { struct sss_sec_ctx *sctx; }; /* Since with the synchronous database, the database operations are just * fake-async wrappers around otherwise sync operations, we don't often * need any state structure, unless the _recv() function returns anything, * so we use this empty structure instead */ struct ccdb_secdb_state { }; static errno_t secdb_container_url_req(TALLOC_CTX *mem_ctx, struct sss_sec_ctx *sctx, struct cli_creds *client, struct sss_sec_req **_sreq) { const char *url; struct sss_sec_req *sreq; errno_t ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } url = secdb_container_url_create(tmp_ctx, client); if (url == NULL) { ret = ENOMEM; goto done; } ret = sss_sec_new_req(tmp_ctx, sctx, url, geteuid(), &sreq); if (ret != EOK) { goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", url); *_sreq = talloc_steal(mem_ctx, sreq); done: talloc_free(tmp_ctx); return ret; } static errno_t secdb_cc_url_req(TALLOC_CTX *mem_ctx, struct sss_sec_ctx *sctx, struct cli_creds *client, const char *secdb_url, struct sss_sec_req **_sreq) { struct sss_sec_req *sreq; errno_t ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } ret = sss_sec_new_req(tmp_ctx, sctx, secdb_url, geteuid(), &sreq); if (ret != EOK) { goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", secdb_url); *_sreq = talloc_steal(mem_ctx, sreq); done: talloc_free(tmp_ctx); return ret; } static errno_t secdb_cc_key_req(TALLOC_CTX *mem_ctx, struct sss_sec_ctx *sctx, struct cli_creds *client, const char *secdb_key, struct sss_sec_req **_sreq) { const char *url; struct sss_sec_req *sreq; errno_t ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } url = secdb_cc_url_create(tmp_ctx, client, secdb_key); if (url == NULL) { ret = ENOMEM; goto done; } ret = secdb_cc_url_req(tmp_ctx, sctx, client, url, &sreq); if (ret != EOK) { goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", url); *_sreq = talloc_steal(mem_ctx, sreq); done: talloc_free(tmp_ctx); return ret; } static errno_t secdb_dfl_url_req(TALLOC_CTX *mem_ctx, struct sss_sec_ctx *sctx, struct cli_creds *client, struct sss_sec_req **_sreq) { const char *url; struct sss_sec_req *sreq; errno_t ret; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } url = secdb_dfl_url_create(tmp_ctx, client); if (url == NULL) { ret = ENOMEM; goto done; } ret = sss_sec_new_req(tmp_ctx, sctx, url, geteuid(), &sreq); if (ret != EOK) { goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", url); *_sreq = talloc_steal(mem_ctx, sreq); done: talloc_free(tmp_ctx); return ret; } static errno_t key_by_uuid(TALLOC_CTX *mem_ctx, struct sss_sec_ctx *sctx, struct cli_creds *client, uuid_t uuid, char **_key) { TALLOC_CTX *tmp_ctx; errno_t ret; char *key_match = NULL; char **keys = NULL; size_t nkeys; struct sss_sec_req *sreq = NULL; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } ret = secdb_container_url_req(tmp_ctx, sctx, client, &sreq); if (ret != EOK) { goto done; } ret = sss_sec_list(tmp_ctx, sreq, &keys, &nkeys); if (ret == ENOENT) { DEBUG(SSSDBG_MINOR_FAILURE, "The container was not found\n"); goto done; } else if (ret != EOK) { goto done; } for (size_t i = 0; i < nkeys; i++) { if (sec_key_match_uuid(keys[i], uuid)) { key_match = keys[i]; break; } } if (key_match == NULL) { DEBUG(SSSDBG_TRACE_INTERNAL, "No key matched\n"); ret = ENOENT; goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Found key %s\n", key_match); *_key = talloc_steal(mem_ctx, key_match); done: talloc_free(tmp_ctx); return ret; } static errno_t key_by_name(TALLOC_CTX *mem_ctx, struct sss_sec_ctx *sctx, struct cli_creds *client, const char *name, char **_key) { TALLOC_CTX *tmp_ctx; errno_t ret; char *key_match = NULL; char **keys = NULL; size_t nkeys; struct sss_sec_req *sreq = NULL; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } ret = secdb_container_url_req(tmp_ctx, sctx, client, &sreq); if (ret != EOK) { goto done; } ret = sss_sec_list(tmp_ctx, sreq, &keys, &nkeys); if (ret == ENOENT) { DEBUG(SSSDBG_MINOR_FAILURE, "The container was not found\n"); goto done; } else if (ret != EOK) { goto done; } for (size_t i = 0; i < nkeys; i++) { if (sec_key_match_name(keys[i], name)) { key_match = keys[i]; break; } } if (key_match == NULL) { DEBUG(SSSDBG_TRACE_INTERNAL, "No key matched\n"); ret = ENOENT; goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Found key %s\n", key_match); *_key = talloc_steal(mem_ctx, key_match); done: talloc_free(tmp_ctx); return ret; } static errno_t secdb_get_cc(TALLOC_CTX *mem_ctx, struct sss_sec_ctx *sctx, const char *secdb_key, struct cli_creds *client, struct kcm_ccache **_cc) { errno_t ret; TALLOC_CTX *tmp_ctx = NULL; struct kcm_ccache *cc = NULL; struct sss_sec_req *sreq = NULL; struct sss_iobuf *ccbuf; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } ret = secdb_cc_key_req(tmp_ctx, sctx, client, secdb_key, &sreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot create secdb request [%d][%s]\n", ret, sss_strerror(ret)); goto done; } ret = sec_get(tmp_ctx, sreq, &ccbuf); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get the secret [%d][%s]\n", ret, sss_strerror(ret)); goto done; } ret = sec_kv_to_ccache_binary(tmp_ctx, secdb_key, ccbuf, client, &cc); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot convert data to ccache [%d]: %s, " "deleting this entry\n", ret, sss_strerror(ret)); ret = sss_sec_delete(sreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to delete entry: [%d]: %s", ret, sss_strerror(ret)); } ret = ENOENT; goto done; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Fetched the ccache\n"); *_cc = talloc_steal(mem_ctx, cc); done: talloc_free(tmp_ctx); return ret; } static errno_t ccdb_secdb_init(struct kcm_ccdb *db, struct confdb_ctx *cdb, const char *confdb_service_path) { struct ccdb_secdb *secdb = NULL; errno_t ret; struct sss_sec_quota *kcm_quota; struct sss_sec_quota_opt dfl_kcm_nest_level = { .opt_name = CONFDB_KCM_CONTAINERS_NEST_LEVEL, .default_value = DEFAULT_SEC_CONTAINERS_NEST_LEVEL, }; struct sss_sec_quota_opt dfl_kcm_max_secrets = { .opt_name = CONFDB_KCM_MAX_CCACHES, .default_value = DEFAULT_SEC_KCM_MAX_SECRETS, }; struct sss_sec_quota_opt dfl_kcm_max_uid_secrets = { .opt_name = CONFDB_KCM_MAX_UID_CCACHES, .default_value = DEFAULT_SEC_KCM_MAX_UID_SECRETS, }; struct sss_sec_quota_opt dfl_kcm_max_payload_size = { .opt_name = CONFDB_KCM_MAX_CCACHE_SIZE, .default_value = DEFAULT_SEC_KCM_MAX_PAYLOAD_SIZE, }; secdb = talloc_zero(db, struct ccdb_secdb); if (secdb == NULL) { return ENOMEM; } kcm_quota = talloc_zero(secdb, struct sss_sec_quota); if (kcm_quota == NULL) { talloc_free(secdb); return ENOMEM; } ret = sss_sec_get_quota(cdb, confdb_service_path, &dfl_kcm_nest_level, &dfl_kcm_max_secrets, &dfl_kcm_max_uid_secrets, &dfl_kcm_max_payload_size, kcm_quota); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get KCM global quotas [%d]: %s\n", ret, sss_strerror(ret)); talloc_free(secdb); return ret; } if (kcm_quota->max_uid_secrets > 0) { kcm_quota->max_uid_secrets += KCM_MAX_UID_EXTRA_SECRETS; } ret = sss_sec_init(db, kcm_quota, &secdb->sctx); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot initialize the security database\n"); talloc_free(secdb); return ret; } DEBUG(SSSDBG_TRACE_INTERNAL, "secdb initialized\n"); db->db_handle = secdb; return EOK; } struct ccdb_secdb_nextid_state { unsigned int nextid; }; static bool is_in_use(char **keys, size_t nkeys, const char *nextid_name) { for (size_t i = 0; i < nkeys; i++) { if (sec_key_match_name(keys[i], nextid_name) == true) { return true; } } return false; } static struct tevent_req *ccdb_secdb_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_secdb_nextid_state *state = NULL; struct ccdb_secdb *secdb = NULL; const int maxtries = 3; int numtry; errno_t ret; struct sss_sec_req *sreq = NULL; char **keys = NULL; size_t nkeys; char *nextid_name = NULL; DEBUG(SSSDBG_TRACE_LIBS, "Generating a new ID\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_nextid_state); if (req == NULL) { return NULL; } secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); if (secdb == NULL) { ret = EIO; goto immediate; } ret = secdb_container_url_req(state, secdb->sctx, client, &sreq); if (ret != EOK) { goto immediate; } ret = sss_sec_list(state, sreq, &keys, &nkeys); if (ret == ENOENT) { keys = NULL; nkeys = 0; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot list keys [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } for (numtry = 0; numtry < maxtries; numtry++) { state->nextid = sss_rand() % MAX_CC_NUM; nextid_name = talloc_asprintf(state, "%"SPRIuid":%u", cli_creds_get_uid(client), state->nextid); if (nextid_name == NULL) { ret = ENOMEM; goto immediate; } if (!is_in_use(keys, nkeys, nextid_name)) { break; } } if (numtry >= maxtries) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to find a random ccache in %d tries\n", numtry); ret = EBUSY; goto immediate; } ret = EOK; DEBUG(SSSDBG_TRACE_LIBS, "Generated next ID %d\n", state->nextid); 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_secdb_nextid_recv(struct tevent_req *req, unsigned int *_nextid) { struct ccdb_secdb_nextid_state *state = tevent_req_data(req, struct ccdb_secdb_nextid_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_nextid = state->nextid; return EOK; } static struct tevent_req *ccdb_secdb_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_secdb_state *state = NULL; struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); errno_t ret; char uuid_str[UUID_STR_SIZE]; struct sss_sec_req *sreq = NULL; struct sss_iobuf *iobuf; char *cur_default; uuid_unparse(uuid, uuid_str); DEBUG(SSSDBG_TRACE_INTERNAL, "Setting the default ccache to %s\n", uuid_str); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); if (req == NULL) { return NULL; } ret = secdb_dfl_url_req(state, secdb->sctx, client, &sreq); if (ret != EOK) { goto immediate; } iobuf = sss_iobuf_init_readonly(state, (const uint8_t *) uuid_str, UUID_STR_SIZE); if (iobuf == NULL) { ret = ENOMEM; goto immediate; } ret = sss_sec_get(state, sreq, (uint8_t**)&cur_default, NULL); if (ret == ENOENT) { ret = sec_put(state, sreq, iobuf); } else if (ret == EOK) { ret = sec_update(state, sreq, iobuf); } if (ret != EOK) { goto immediate; } ret = EOK; DEBUG(SSSDBG_TRACE_INTERNAL, "Set the default ccache\n"); 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_secdb_set_default_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct ccdb_secdb_get_default_state { uuid_t uuid; }; static struct tevent_req *ccdb_secdb_get_default_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct cli_creds *client) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_get_default_state *state = NULL; errno_t ret; struct sss_sec_req *sreq = NULL; struct sss_iobuf *dfl_iobuf = NULL; size_t uuid_size; DEBUG(SSSDBG_TRACE_INTERNAL, "Getting the default ccache\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_get_default_state); if (req == NULL) { return NULL; } ret = secdb_dfl_url_req(state, secdb->sctx, client, &sreq); if (ret != EOK) { goto immediate; } ret = sec_get(state, sreq, &dfl_iobuf); if (ret == ENOENT) { uuid_clear(state->uuid); ret = EOK; goto immediate; } else if (ret != EOK) { goto immediate; } uuid_size = sss_iobuf_get_size(dfl_iobuf); if (uuid_size != UUID_STR_SIZE) { DEBUG(SSSDBG_OP_FAILURE, "Unexpected UUID size %zu, deleting this entry\n", uuid_size); ret = sss_sec_delete(sreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to delete entry: [%d]: %s, " "consider manual removal of "SECRETS_DB_PATH"/secrets.ldb\n", ret, sss_strerror(ret)); sss_log(SSS_LOG_CRIT, "Can't delete an entry from "SECRETS_DB_PATH"/secrets.ldb, " "content seems to be corrupted. Consider file removal. " "(Take a note, this will delete all credentials managed " "via sssd_kcm)"); } uuid_clear(state->uuid); ret = EOK; goto immediate; } uuid_parse((const char *) sss_iobuf_get_data(dfl_iobuf), state->uuid); DEBUG(SSSDBG_TRACE_INTERNAL, "Got the default ccache\n"); 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_secdb_get_default_recv(struct tevent_req *req, uuid_t uuid) { struct ccdb_secdb_get_default_state *state = tevent_req_data(req, struct ccdb_secdb_get_default_state); TEVENT_REQ_RETURN_ON_ERROR(req); uuid_copy(uuid, state->uuid); return EOK; } static errno_t ccdb_secdb_get_cc_for_uuid(TALLOC_CTX *mem_ctx, size_t uuid_list_count, const char **uuid_list, const char **uid_list, struct ccdb_secdb *secdb, struct kcm_ccache ***_cc_list) { TALLOC_CTX *tmp_ctx; errno_t ret; uid_t uid; char **list; uuid_t uuid; char *uuid_str; char *secdb_key; struct cli_creds cli_cred; struct kcm_ccache **cc_list; int real_count = 0; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; return ret; } cc_list = talloc_zero_array(tmp_ctx, struct kcm_ccache *, uuid_list_count + 1); if (cc_list == NULL) { ret = ENOMEM; goto done; } for (size_t i = 0; i < uuid_list_count; i++) { struct passwd *pwd; ret = split_on_separator(tmp_ctx, uuid_list[i], ':', true, true, &list, NULL); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "split on separator failed [%d]: %s\n", ret, sss_strerror(ret)); goto done; } uuid_str = list[0]; uuid_str[UUID_STR_SIZE - 1] = '\0'; ret = uuid_parse(uuid_str, uuid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "uuid parse of [%s] failed [%d]: %s\n", list[0], ret, sss_strerror(ret)); goto done; } uid = strtouint32(uid_list[i], NULL, 10); ret = errno; if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid UID [%s] conversion to uint32 " "[%d]: %s\n", uid_list[i], ret, sss_strerror(ret)); goto done; } errno = 0; pwd = getpwuid(uid); if (pwd == NULL) { ret = errno; DEBUG(SSSDBG_MINOR_FAILURE, "Unable to resolve user [%d] who " "is the owner of an existing ccache [%d]: %s\n", uid, ret, sss_strerror(ret)); /* Not fatal */ continue; } cli_cred.ucred.uid = pwd->pw_uid; cli_cred.ucred.gid = pwd->pw_gid; ret = key_by_uuid(tmp_ctx, secdb->sctx, &cli_cred, uuid, &secdb_key); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "key_by_uuid() failed for uuid = '%s'", uuid_str); goto done; } ret = secdb_get_cc(cc_list, secdb->sctx, secdb_key, &cli_cred, &cc_list[real_count]); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get ccache [%d]: %s\n", ret, sss_strerror(ret)); /* probably ccache in old format was met and purged, just skip */ continue; } if (cc_list[real_count] == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get cc for uuid = '%s' and uid = %s\n", uuid_list[i], uid_list[i]); ret = EIO; goto done; } DEBUG(SSSDBG_TRACE_INTERNAL, "Retrieved ccache [%s]\n", cc_list[real_count]->name); real_count++; } cc_list = talloc_realloc(tmp_ctx, cc_list, struct kcm_ccache *, real_count + 1); if (cc_list == NULL) { ret = ENOMEM; goto done; } cc_list[real_count] = NULL; *_cc_list = talloc_steal(mem_ctx, cc_list); return EOK; done: talloc_free(tmp_ctx); return ret; } struct ccdb_secdb_list_state { uuid_t *uuid_list; }; static errno_t ccdb_secdb_list_all_cc(TALLOC_CTX *mem_ctx, struct krb5_ctx *krb5_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct kcm_ccache ***_cc_list) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); TALLOC_CTX *tmp_ctx; errno_t ret; const char **uid_list; const char **uuid_list; size_t uuid_list_count; DEBUG(SSSDBG_TRACE_INTERNAL, "Retrieving all ccaches\n"); tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; return ret; } ret = sss_sec_list_cc_uuids(tmp_ctx, secdb->sctx, &uuid_list, &uid_list, &uuid_list_count); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_TRACE_INTERNAL, "Error retrieving ccache uuid list " "[%d]: %s\n", ret, sss_strerror(ret)); goto done; } else if (ret == ENOENT) { goto done; } DEBUG(SSSDBG_TRACE_INTERNAL, "Found [%zu] ccache uuids\n", uuid_list_count); /* New count is full cc list size minus getpwuid() failures */ ret = ccdb_secdb_get_cc_for_uuid(mem_ctx, uuid_list_count, uuid_list, uid_list, secdb, _cc_list); if (ret != EOK) { DEBUG(SSSDBG_TRACE_INTERNAL, "Error getting cc list from uuid list " "[%d]: %s\n", ret, sss_strerror(ret)); goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Retrieving all caches done\n"); ret = EOK; done: talloc_free(tmp_ctx); return ret; } static struct tevent_req *ccdb_secdb_list_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct cli_creds *client) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_list_state *state = NULL; errno_t ret; char **keys = NULL; size_t nkeys; struct sss_sec_req *sreq = NULL; DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all ccaches\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_list_state); if (req == NULL) { return NULL; } ret = secdb_container_url_req(state, secdb->sctx, client, &sreq); if (ret != EOK) { goto immediate; } ret = sss_sec_list(state, sreq, &keys, &nkeys); if (ret == ENOENT) { nkeys = 0; /* Fall through and return an empty list */ } else if (ret != EOK) { goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu ccaches\n", nkeys); state->uuid_list = talloc_array(state, uuid_t, nkeys + 1); if (state->uuid_list == NULL) { ret = ENOMEM; goto immediate; } for (size_t i = 0; i < nkeys; i++) { ret = sec_key_get_uuid(keys[i], state->uuid_list[i]); if (ret != EOK) { goto immediate; } } /* Sentinel */ uuid_clear(state->uuid_list[nkeys]); DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all caches done\n"); 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_secdb_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, uuid_t **_uuid_list) { struct ccdb_secdb_list_state *state = tevent_req_data(req, struct ccdb_secdb_list_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_uuid_list = talloc_steal(mem_ctx, state->uuid_list); return EOK; } struct ccdb_secdb_getbyuuid_state { struct kcm_ccache *cc; }; static struct tevent_req *ccdb_secdb_getbyuuid_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct cli_creds *client, uuid_t uuid) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_getbyuuid_state *state = NULL; errno_t ret; char *secdb_key = NULL; DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by UUID\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_getbyuuid_state); if (req == NULL) { return NULL; } ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); if (ret == ENOENT) { state->cc = NULL; ret = EOK; goto immediate; } else if (ret != EOK) { goto immediate; } ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &state->cc); if (ret == ENOENT) { state->cc = NULL; ret = EOK; goto immediate; } else if (ret != EOK) { goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); 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_secdb_getbyuuid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, struct kcm_ccache **_cc) { struct ccdb_secdb_getbyuuid_state *state = tevent_req_data(req, struct ccdb_secdb_getbyuuid_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_cc = talloc_steal(mem_ctx, state->cc); return EOK; } struct ccdb_secdb_getbyname_state { struct kcm_ccache *cc; }; static struct tevent_req *ccdb_secdb_getbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct cli_creds *client, const char *name) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_getbyname_state *state = NULL; errno_t ret; char *secdb_key = NULL; DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by name\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_getbyname_state); if (req == NULL) { return NULL; } ret = key_by_name(state, secdb->sctx, client, name, &secdb_key); if (ret == ENOENT) { state->cc = NULL; ret = EOK; goto immediate; } else if (ret != EOK) { goto immediate; } ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &state->cc); if (ret == ENOENT) { state->cc = NULL; ret = EOK; goto immediate; } else if (ret != EOK) { goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by name\n"); 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_secdb_getbyname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, struct kcm_ccache **_cc) { struct ccdb_secdb_getbyname_state *state = tevent_req_data(req, struct ccdb_secdb_getbyname_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_cc = talloc_steal(mem_ctx, state->cc); return EOK; } struct ccdb_secdb_name_by_uuid_state { const char *name; }; struct tevent_req *ccdb_secdb_name_by_uuid_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct cli_creds *client, uuid_t uuid) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_name_by_uuid_state *state = NULL; errno_t ret; char *key; const char *name; DEBUG(SSSDBG_TRACE_INTERNAL, "Translating UUID to name\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_name_by_uuid_state); if (req == NULL) { return NULL; } ret = key_by_uuid(state, secdb->sctx, client, uuid, &key); if (ret == ENOENT) { ret = ERR_NO_CREDS; goto immediate; } else if (ret != EOK) { goto immediate; } name = sec_key_get_name(key); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Malformed key, cannot get name\n"); goto immediate; } state->name = talloc_strdup(state, name); if (state->name == NULL) { ret = ENOMEM; goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); 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_secdb_name_by_uuid_recv(struct tevent_req *req, TALLOC_CTX *sec_ctx, const char **_name) { struct ccdb_secdb_name_by_uuid_state *state = tevent_req_data(req, struct ccdb_secdb_name_by_uuid_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_name = talloc_steal(sec_ctx, state->name); return EOK; } struct ccdb_secdb_uuid_by_name_state { uuid_t uuid; }; struct tevent_req *ccdb_secdb_uuid_by_name_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct cli_creds *client, const char *name) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_uuid_by_name_state *state = NULL; errno_t ret; char *key; DEBUG(SSSDBG_TRACE_INTERNAL, "Translating name to UUID\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_uuid_by_name_state); if (req == NULL) { return NULL; } ret = key_by_name(state, secdb->sctx, client, name, &key); if (ret == ENOENT) { ret = ERR_NO_CREDS; goto immediate; } else if (ret != EOK) { goto immediate; } ret = sec_key_get_uuid(key, state->uuid); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Malformed key, cannot get UUID\n"); goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); 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_secdb_uuid_by_name_recv(struct tevent_req *req, TALLOC_CTX *sec_ctx, uuid_t _uuid) { struct ccdb_secdb_uuid_by_name_state *state = tevent_req_data(req, struct ccdb_secdb_uuid_by_name_state); TEVENT_REQ_RETURN_ON_ERROR(req); uuid_copy(_uuid, state->uuid); return EOK; } static struct tevent_req *ccdb_secdb_create_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ccdb *db, struct cli_creds *client, struct kcm_ccache *cc) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_state *state = NULL; errno_t ret; struct sss_sec_req *container_req = NULL; struct sss_sec_req *ccache_req = NULL; const char *url; struct sss_iobuf *ccache_payload; DEBUG(SSSDBG_TRACE_INTERNAL, "Creating ccache storage for %s\n", cc->name); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); if (req == NULL) { return NULL; } /* Do the encoding asap so that if we fail, we don't even attempt any * writes */ ret = kcm_ccache_to_secdb_kv(state, cc, client, &url, &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; } DEBUG(SSSDBG_TRACE_INTERNAL, "Creating the ccache container\n"); ret = secdb_container_url_req(state, secdb->sctx, client, &container_req); if (ret != EOK) { goto immediate; } ret = sss_sec_create_container(container_req); if (ret == EEXIST) { DEBUG(SSSDBG_TRACE_INTERNAL, "Container already exists, ignoring\n"); } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to create the ccache container\n"); goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "ccache container created\n"); DEBUG(SSSDBG_TRACE_INTERNAL, "creating empty ccache payload\n"); ret = secdb_cc_url_req(state, secdb->sctx, client, url, &ccache_req); if (ret != EOK) { goto immediate; } ret = sec_put(state, ccache_req, ccache_payload); if (ret != EOK) { goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "payload created\n"); 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_secdb_create_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } static struct tevent_req *ccdb_secdb_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) { struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_state *state = NULL; errno_t ret; char *secdb_key = NULL; struct kcm_ccache *cc = NULL; struct sss_iobuf *payload = NULL; struct sss_sec_req *sreq = NULL; DEBUG(SSSDBG_TRACE_INTERNAL, "Modifying ccache\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); if (req == NULL) { return NULL; } ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); if (ret == ENOENT) { ret = ERR_NO_CREDS; goto immediate; } else if (ret != EOK) { goto immediate; } ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &cc); if (ret == ENOENT) { ret = ERR_NO_CREDS; goto immediate; } else if (ret != EOK) { goto immediate; } ret = kcm_mod_cc(cc, mod_cc); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot modify ccache [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } ret = kcm_ccache_to_sec_input_binary(state, cc, &payload); if (ret != EOK) { goto immediate; } ret = secdb_cc_key_req(state, secdb->sctx, client, secdb_key, &sreq); if (ret != EOK) { goto immediate; } ret = sec_update(state, sreq, payload); if (ret != EOK) { 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_secdb_mod_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } static struct tevent_req *ccdb_secdb_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 ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct tevent_req *req = NULL; struct ccdb_secdb_state *state = NULL; char *secdb_key = NULL; struct kcm_ccache *cc = NULL; struct sss_iobuf *payload = NULL; struct sss_sec_req *sreq = NULL; errno_t ret; DEBUG(SSSDBG_TRACE_INTERNAL, "Storing creds in ccache\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); if (req == NULL) { return NULL; } ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); if (ret == ENOENT) { ret = ERR_NO_CREDS; goto immediate; } else if (ret != EOK) { goto immediate; } ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &cc); if (ret == ENOENT) { ret = ERR_NO_CREDS; goto immediate; } else if (ret != EOK) { goto immediate; } ret = kcm_cc_store_cred_blob(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 = kcm_ccache_to_sec_input_binary(state, cc, &payload); if (ret != EOK) { goto immediate; } ret = secdb_cc_key_req(state, secdb->sctx, client, secdb_key, &sreq); if (ret != EOK) { goto immediate; } ret = sec_update(state, sreq, payload); if (ret != EOK) { 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_secdb_store_cred_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } static struct tevent_req *ccdb_secdb_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_secdb_state *state = NULL; struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); struct sss_sec_req *container_req = NULL; struct sss_sec_req *sreq = NULL; char *secdb_key = NULL; char **keys = NULL; size_t nkeys; errno_t ret; DEBUG(SSSDBG_TRACE_INTERNAL, "Deleting ccache\n"); req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); if (req == NULL) { return NULL; } ret = secdb_container_url_req(state, secdb->sctx, client, &container_req); if (ret != EOK) { goto immediate; } ret = sss_sec_list(state, container_req, &keys, &nkeys); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "No ccaches to delete\n"); goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu ccaches\n", nkeys); if (nkeys == 0) { ret = EOK; goto immediate; } ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); if (ret == ENOENT) { ret = ERR_NO_CREDS; goto immediate; } else if (ret != EOK) { goto immediate; } ret = secdb_cc_key_req(state, secdb->sctx, client, secdb_key, &sreq); if (ret != EOK) { goto immediate; } ret = sss_sec_delete(sreq); if (ret != EOK) { goto immediate; } if (nkeys > 1) { DEBUG(SSSDBG_TRACE_INTERNAL, "There are other ccaches, done\n"); ret = EOK; goto immediate; } DEBUG(SSSDBG_TRACE_INTERNAL, "Removing ccache container\n"); ret = sss_sec_delete(container_req); if (ret != EOK) { 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_secdb_delete_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } const struct kcm_ccdb_ops ccdb_secdb_ops = { .init = ccdb_secdb_init, .nextid_send = ccdb_secdb_nextid_send, .nextid_recv = ccdb_secdb_nextid_recv, .set_default_send = ccdb_secdb_set_default_send, .set_default_recv = ccdb_secdb_set_default_recv, .get_default_send = ccdb_secdb_get_default_send, .get_default_recv = ccdb_secdb_get_default_recv, .list_send = ccdb_secdb_list_send, .list_recv = ccdb_secdb_list_recv, .list_all_cc = ccdb_secdb_list_all_cc, .getbyname_send = ccdb_secdb_getbyname_send, .getbyname_recv = ccdb_secdb_getbyname_recv, .getbyuuid_send = ccdb_secdb_getbyuuid_send, .getbyuuid_recv = ccdb_secdb_getbyuuid_recv, .name_by_uuid_send = ccdb_secdb_name_by_uuid_send, .name_by_uuid_recv = ccdb_secdb_name_by_uuid_recv, .uuid_by_name_send = ccdb_secdb_uuid_by_name_send, .uuid_by_name_recv = ccdb_secdb_uuid_by_name_recv, .create_send = ccdb_secdb_create_send, .create_recv = ccdb_secdb_create_recv, .mod_send = ccdb_secdb_mod_send, .mod_recv = ccdb_secdb_mod_recv, .store_cred_send = ccdb_secdb_store_cred_send, .store_cred_recv = ccdb_secdb_store_cred_recv, .delete_send = ccdb_secdb_delete_send, .delete_recv = ccdb_secdb_delete_recv, };