/* SSSD KCM Server - the KCM server operations Copyright (C) Red Hat, 2016 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include "util/sss_iobuf.h" #include "util/sss_krb5.h" #include "util/sss_ptr_hash.h" #include "util/util_creds.h" #include "responder/kcm/kcmsrv_pvt.h" #include "responder/kcm/kcmsrv_ops.h" #include "responder/kcm/kcmsrv_ccache.h" struct kcm_op_ctx { struct kcm_resp_ctx *kcm_data; struct kcm_conn_data *conn_data; struct cli_creds *client; struct sss_iobuf *input; struct sss_iobuf *reply; }; /* Each operation follows the same pattern and is implemented using * functions with this prototype. The operation receives an op_ctx * that serves as a state of the operation and can be used to keep * track of any temporary data. The operation writes its output data * into the op_ctx reply IO buffer and returns the op_ret status code * separately. * * The operation always returns EOK unless an internal error occurs, * the result of the operation is stored in the op_ret variable */ typedef struct tevent_req* (*kcm_srv_send_method)(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx); typedef errno_t (*kcm_srv_recv_method)(struct tevent_req *req, uint32_t *_op_ret); struct kcm_op { const char *name; kcm_srv_send_method fn_send; kcm_srv_recv_method fn_recv; }; struct kcm_cmd_state { struct kcm_op *op; struct tevent_context *ev; struct kcm_ops_queue_entry *queue_entry; struct kcm_op_ctx *op_ctx; struct sss_iobuf *reply; uint32_t op_ret; }; static void kcm_cmd_queue_done(struct tevent_req *subreq); static void kcm_cmd_done(struct tevent_req *subreq); struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_ops_queue_ctx *qctx, struct kcm_resp_ctx *kcm_data, struct kcm_conn_data *conn_data, struct cli_creds *client, struct kcm_data *input, struct kcm_op *op) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_cmd_state *state = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct kcm_cmd_state); if (req == NULL) { return NULL; } state->op = op; state->ev = ev; if (op == NULL) { ret = EINVAL; goto immediate; } DEBUG(SSSDBG_TRACE_FUNC, "KCM operation %s\n", op->name); DEBUG(SSSDBG_TRACE_LIBS, "%zu bytes on KCM input\n", input->length); state->reply = sss_iobuf_init_empty(state, KCM_PACKET_INITIAL_SIZE, KCM_PACKET_MAX_SIZE); if (state->reply == NULL) { ret = ENOMEM; goto immediate; } if (op->fn_send == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "KCM op %s has no handler\n", kcm_opt_name(op)); ret = ERR_KCM_OP_NOT_IMPLEMENTED; goto immediate; } /* Allocating op_ctx on the heap makes it possible for operations to use * op_ctx as their temporary context and avoid tmp_ctx altogether */ state->op_ctx = talloc_zero(state, struct kcm_op_ctx); if (state->op_ctx == NULL) { ret = ENOMEM; goto immediate; } state->op_ctx->kcm_data = kcm_data; state->op_ctx->conn_data = conn_data; state->op_ctx->client = client; state->op_ctx->input = sss_iobuf_init_readonly(state->op_ctx, input->data, input->length); if (state->op_ctx->input == NULL) { ret = ENOMEM; goto immediate; } /* * The internal operation returns the opcode and the buffer separately. * The KCM server reply to the client also always contains zero if the * operation ran to completion, both are uint32_t. * FIXME: * Alternatively, we could extend iobuf API so that we can just pass * the reply's buffer+sizeof(2*uint32_t) and avoid the useless allocations */ state->op_ctx->reply = sss_iobuf_init_empty( state, KCM_PACKET_INITIAL_SIZE, KCM_PACKET_MAX_SIZE - 2*sizeof(uint32_t)); if (state->op_ctx->reply == NULL) { ret = ENOMEM; goto immediate; } subreq = kcm_op_queue_send(state, ev, qctx, client); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_cmd_queue_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_cmd_queue_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_cmd_state *state = tevent_req_data(req, struct kcm_cmd_state); errno_t ret; /* When this request finishes, it frees the queue_entry which unblocks * other requests by the same UID */ ret = kcm_op_queue_recv(subreq, state, &state->queue_entry); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot acquire queue slot\n"); tevent_req_error(req, ret); return; } subreq = state->op->fn_send(state, state->ev, state->op_ctx); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_cmd_done, req); } static void kcm_cmd_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_cmd_state *state = tevent_req_data(req, struct kcm_cmd_state); errno_t ret; krb5_error_code kerr; ret = state->op->fn_recv(subreq, &state->op_ret); talloc_free(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "op receive function failed [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } DEBUG(SSSDBG_TRACE_FUNC, "KCM operation %s returned [%d]: %s\n", kcm_opt_name(state->op), state->op_ret, sss_strerror(state->op_ret)); kerr = sss2krb5_error(state->op_ret); /* The first four bytes of the reply is the operation status code */ ret = sss_iobuf_write_uint32(state->reply, htobe32(kerr)); if (ret != EOK) { tevent_req_error(req, ret); return; } ret = sss_iobuf_write_len(state->reply, sss_iobuf_get_data(state->op_ctx->reply), sss_iobuf_get_len(state->op_ctx->reply)); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } errno_t kcm_cmd_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct sss_iobuf **_reply) { struct kcm_cmd_state *state = NULL; TEVENT_REQ_RETURN_ON_ERROR(req); state = tevent_req_data(req, struct kcm_cmd_state); *_reply = talloc_steal(mem_ctx, state->reply); return EOK; } /* ======= KCM operations ======= */ /* Operations that don't return any extra information except for the op_ret * can use this macro in the _recv function to avoid code duplication */ #define KCM_OP_RET_FROM_TYPE(req, state_type, _op_ret_out) do { \ state_type *state = NULL; \ state = tevent_req_data(req, state_type); \ TEVENT_REQ_RETURN_ON_ERROR(req); \ *_op_ret_out = state->op_ret; \ return EOK; \ } while(0); struct kcm_op_common_state { uint32_t op_ret; struct kcm_op_ctx *op_ctx; struct tevent_context *ev; }; static errno_t kcm_op_common_recv(struct tevent_req *req, uint32_t *_op_ret) { struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_op_ret = state->op_ret; return EOK; } /* () -> (name) */ static void kcm_op_gen_new_done(struct tevent_req *subreq); static struct tevent_req *kcm_op_gen_new_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_common_state *state = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; subreq = kcm_ccdb_nextid_send(state, ev, op_ctx->kcm_data->db, op_ctx->client); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_gen_new_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_gen_new_done(struct tevent_req *subreq) { errno_t ret; char *newid; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); ret = kcm_ccdb_nextid_recv(subreq, state, &newid); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot generate a new ID [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } DEBUG(SSSDBG_TRACE_LIBS, "Generated a new ID %s\n", newid); ret = sss_iobuf_write_stringz(state->op_ctx->reply, newid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot write generated ID %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } /* (princ) -> () */ struct kcm_op_initialize_state { uint32_t op_ret; struct kcm_op_ctx *op_ctx; struct tevent_context *ev; struct kcm_ccache *new_cc; const char *name; krb5_principal princ; }; static void kcm_op_initialize_got_byname(struct tevent_req *subreq); static void kcm_op_initialize_cc_create_done(struct tevent_req *subreq); static void kcm_op_initialize_cc_delete_done(struct tevent_req *subreq); static void kcm_op_initialize_fill_princ_step(struct tevent_req *req); static void kcm_op_initialize_fill_princ_done(struct tevent_req *subreq); static void kcm_op_initialize_create_step(struct tevent_req *req); static void kcm_op_initialize_got_default(struct tevent_req *subreq); static void kcm_op_initialize_set_default_done(struct tevent_req *subreq); static struct tevent_req *kcm_op_initialize_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_initialize_state *state = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct kcm_op_initialize_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; state->ev = ev; ret = sss_iobuf_read_stringz(op_ctx->input, &state->name); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot read input name [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Initializing ccache %s\n", state->name); ret = kcm_check_name(state->name, op_ctx->client); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Name %s is malformed [%d]: %s\n", state->name, ret, sss_strerror(ret)); goto immediate; } ret = sss_krb5_unmarshal_princ(op_ctx, op_ctx->input, &state->princ); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot unmarshal principal [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } subreq = kcm_ccdb_getbyname_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, state->name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_initialize_got_byname, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_initialize_got_byname(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_initialize_state *state = tevent_req_data(req, struct kcm_op_initialize_state); bool ok; uuid_t uuid; ret = kcm_ccdb_getbyname_recv(subreq, state, &state->new_cc); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (state->new_cc != NULL) { if (kcm_cc_get_client_principal(state->new_cc) == NULL) { /* This is a cache that was pre-created w/o a principal (sshd does this), * let's fill in the principal and set the cache as default if not * already */ kcm_op_initialize_fill_princ_step(req); return; } ok = kcm_cc_access(state->new_cc, state->op_ctx->client); if (!ok) { state->op_ret = EACCES; tevent_req_done(req); return; } /* `uuid` is output arg and isn't read in kcm_cc_get_uuid() but * since libuuid is opaque for cppcheck it generates false positive here */ /* cppcheck-suppress uninitvar */ ret = kcm_cc_get_uuid(state->new_cc, uuid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get new ccache UUID [%d]: %s\n", ret, sss_strerror(ret)); return; } /* Nuke any previous cache and its contents during initialization */ subreq = kcm_ccdb_delete_cc_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, uuid); if (subreq == NULL) { tevent_req_error(req, ret); return; } tevent_req_set_callback(subreq, kcm_op_initialize_cc_delete_done, req); return; } kcm_op_initialize_create_step(req); } static void kcm_op_initialize_cc_delete_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); errno_t ret; ret = kcm_ccdb_delete_cc_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot delete ccache from the db %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } kcm_op_initialize_create_step(req); } static void kcm_op_initialize_fill_princ_step(struct tevent_req *req) { struct tevent_req *subreq; struct kcm_op_initialize_state *state = tevent_req_data(req, struct kcm_op_initialize_state); errno_t ret; struct kcm_mod_ctx *mod_ctx; uuid_t uuid; mod_ctx = kcm_mod_ctx_new(state); if (mod_ctx == NULL) { tevent_req_error(req, ENOMEM); return; } mod_ctx->client = state->princ; /* `uuid` is output arg and isn't read in kcm_cc_get_uuid() but * since libuuid is opaque for cppcheck it generates false positive here */ /* cppcheck-suppress uninitvar */ ret = kcm_cc_get_uuid(state->new_cc, uuid); if (ret != EOK) { tevent_req_error(req, ret); return; } subreq = kcm_ccdb_mod_cc_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, uuid, mod_ctx); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_initialize_fill_princ_done, req); } static void kcm_op_initialize_fill_princ_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_initialize_state *state = tevent_req_data(req, struct kcm_op_initialize_state); errno_t ret; ret = kcm_ccdb_mod_cc_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot modify ccache [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } /* Make sure the cache we just initialized is the default one */ subreq = kcm_ccdb_get_default_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client); if (subreq == NULL) { tevent_req_error(req, ret); return; } tevent_req_set_callback(subreq, kcm_op_initialize_got_default, req); } static void kcm_op_initialize_create_step(struct tevent_req *req) { struct tevent_req *subreq; struct kcm_op_initialize_state *state = tevent_req_data(req, struct kcm_op_initialize_state); errno_t ret; ret = kcm_cc_new(state->op_ctx, state->op_ctx->kcm_data->k5c, state->op_ctx->client, state->name, state->princ, &state->new_cc); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot create new ccache %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } subreq = kcm_ccdb_create_cc_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, state->new_cc); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_initialize_cc_create_done, req); } static void kcm_op_initialize_cc_create_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_initialize_state *state = tevent_req_data(req, struct kcm_op_initialize_state); errno_t ret; ret = kcm_ccdb_create_cc_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot add ccache to db %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } /* If there was no previous default ccache, set this one as default */ subreq = kcm_ccdb_get_default_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client); if (subreq == NULL) { tevent_req_error(req, ret); return; } tevent_req_set_callback(subreq, kcm_op_initialize_got_default, req); } static void kcm_op_initialize_got_default(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_initialize_state *state = tevent_req_data(req, struct kcm_op_initialize_state); errno_t ret; uuid_t dfl_uuid; uuid_t old_dfl_uuid; ret = kcm_ccdb_get_default_recv(subreq, &old_dfl_uuid); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get default ccache [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (uuid_is_null(old_dfl_uuid)) { /* If there was no previous default ccache, switch to the initialized * one by default */ /* `dfl_uuid` is output arg and isn't read in kcm_cc_get_uuid() but * since libuuid is opaque for cppcheck it generates false positive here */ /* cppcheck-suppress uninitvar */ ret = kcm_cc_get_uuid(state->new_cc, dfl_uuid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get new ccache UUID [%d]: %s\n", ret, sss_strerror(ret)); return; } DEBUG(SSSDBG_TRACE_FUNC, "The default ccached was not set, switching to the " "initialized\n"); subreq = kcm_ccdb_set_default_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, dfl_uuid); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_initialize_set_default_done, req); return; } /* ENOENT, done */ state->op_ret = EOK; tevent_req_done(req); } static void kcm_op_initialize_set_default_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_initialize_state *state = tevent_req_data(req, struct kcm_op_initialize_state); errno_t ret; ret = kcm_ccdb_set_default_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set default ccache %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } static errno_t kcm_op_initialize_recv(struct tevent_req *req, uint32_t *_op_ret) { KCM_OP_RET_FROM_TYPE(req, struct kcm_op_initialize_state, _op_ret); } /* (name) -> () */ static void kcm_op_destroy_getbyname_done(struct tevent_req *subreq); static void kcm_op_destroy_delete_done(struct tevent_req *subreq); static struct tevent_req *kcm_op_destroy_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_common_state *state = NULL; errno_t ret; const char *name; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; state->ev = ev; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot unmarshall input name [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Destroying credentials of %s\n", name); subreq = kcm_ccdb_uuid_by_name_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_destroy_getbyname_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_destroy_getbyname_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); uuid_t uuid; /* `uuid` is output arg and isn't read in kcm_ccdb_uuid_by_name_recv() but * since libuuid is opaque for cppcheck it generates false positive here */ /* cppcheck-suppress uninitvar */ ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get matching ccache [%d]: %s\n", ret, sss_strerror(ret)); ret = ERR_NO_MATCHING_CREDS; tevent_req_error(req, ret); return; } subreq = kcm_ccdb_delete_cc_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, uuid); if (subreq == NULL) { tevent_req_error(req, ret); return; } tevent_req_set_callback(subreq, kcm_op_destroy_delete_done, req); } static void kcm_op_destroy_delete_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); ret = kcm_ccdb_delete_cc_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot delete ccache from the db [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } /* (name, cred) -> () */ struct kcm_op_store_state { uint32_t op_ret; struct kcm_op_ctx *op_ctx; struct tevent_context *ev; struct sss_iobuf *cred_blob; }; static void kcm_op_store_getbyname_done(struct tevent_req *subreq); static void kcm_op_store_done(struct tevent_req *subreq); static struct tevent_req *kcm_op_store_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_store_state *state = NULL; errno_t ret; const char *name; size_t creds_len; req = tevent_req_create(mem_ctx, &state, struct kcm_op_store_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; state->ev = ev; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot unmarshall input name [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Storing credentials for %s\n", name); creds_len = sss_iobuf_get_size(op_ctx->input) - strlen(name) -1; if (creds_len > KCM_PACKET_MAX_SIZE) { /* Protects against underflows and in general adds sanity */ ret = E2BIG; goto immediate; } state->cred_blob = sss_iobuf_init_empty(state, creds_len, creds_len); if (state->cred_blob == NULL) { ret = ENOMEM; goto immediate; } ret = sss_iobuf_read(op_ctx->input, creds_len, sss_iobuf_get_data(state->cred_blob), NULL); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot unmarshall input cred blob [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } subreq = kcm_ccdb_uuid_by_name_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_store_getbyname_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_store_getbyname_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_store_state *state = tevent_req_data(req, struct kcm_op_store_state); uuid_t uuid; /* `uuid` is output arg and isn't read in kcm_ccdb_uuid_by_name_recv() but * since libuuid is opaque for cppcheck it generates false positive here */ /* cppcheck-suppress uninitvar */ ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } subreq = kcm_ccdb_store_cred_blob_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, uuid, state->cred_blob); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_store_done, req); } static void kcm_op_store_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_store_state *state = tevent_req_data(req, struct kcm_op_store_state); ret = kcm_ccdb_store_cred_blob_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot store credentials [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } static errno_t kcm_op_store_recv(struct tevent_req *req, uint32_t *_op_ret) { KCM_OP_RET_FROM_TYPE(req, struct kcm_op_store_state, _op_ret); } /* (name) -> (princ) */ static void kcm_op_get_principal_getbyname_done(struct tevent_req *subreq); static struct tevent_req *kcm_op_get_principal_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_common_state *state = NULL; errno_t ret; const char *name; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Requested principal %s\n", name); subreq = kcm_ccdb_getbyname_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_principal_getbyname_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_get_principal_getbyname_done(struct tevent_req *subreq) { errno_t ret; struct kcm_ccache *cc; krb5_principal princ; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (cc == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that name\n"); state->op_ret = ERR_NO_MATCHING_CREDS; tevent_req_done(req); return; } /* Marshall the principal to the reply */ princ = kcm_cc_get_client_principal(cc); if (princ == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Credentials with no principal?\n"); tevent_req_error(req, EIO); return; } ret = sss_krb5_marshal_princ(princ, state->op_ctx->reply); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot marshall principal [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } static void kcm_creds_table_delete_cb(hash_entry_t *item, hash_destroy_enum deltype, void *pvt) { /* Delete the old credential if it is being overwritten. */ talloc_free(item->value.ptr); } /* Store credentials in a hash table. * * If the table already exist we add the new credentials to the table and * overwrite the ones that already exist. This allows us to correctly serve * also parallel GET_CRED_UUID_LIST requests from the same connection since * it will have its own uuid list and cursor on the client side and we make * all uuid (old, updated and newly added) available. */ static errno_t kcm_creds_to_table(TALLOC_CTX *mem_ctx, struct kcm_cred *creds, hash_table_t **_table) { char str[UUID_STR_SIZE]; uuid_t uuid; errno_t ret; if (*_table == NULL) { *_table = sss_ptr_hash_create(mem_ctx, kcm_creds_table_delete_cb, NULL); if (*_table == NULL) { return ENOMEM; } } for (struct kcm_cred *crd = creds; crd != NULL; crd = kcm_cc_next_cred(crd)) { ret = kcm_cred_get_uuid(crd, uuid); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Credential has no UUID, skipping\n"); continue; } uuid_unparse(uuid, str); ret = sss_ptr_hash_add_or_override(*_table, str, crd, struct kcm_cred); if (ret != EOK) { return ret; } talloc_steal(*_table, crd); } return EOK; } static struct kcm_cred * kcm_creds_lookup(hash_table_t *table, uuid_t uuid) { char str[UUID_STR_SIZE]; if (uuid == NULL) { return NULL; } uuid_unparse(uuid, str); return sss_ptr_hash_lookup(table, str, struct kcm_cred); } /* (name) -> (uuid, ...) */ static void kcm_op_get_cred_uuid_list_getbyname_done(struct tevent_req *subreq); static struct tevent_req * kcm_op_get_cred_uuid_list_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_common_state *state = NULL; errno_t ret; const char *name; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Returning UUID list for %s\n", name); subreq = kcm_ccdb_getbyname_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_cred_uuid_list_getbyname_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_get_cred_uuid_list_getbyname_done(struct tevent_req *subreq) { errno_t ret; struct kcm_ccache *cc; struct kcm_cred *crd; struct kcm_conn_data *conn_data; uuid_t uuid; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); conn_data = state->op_ctx->conn_data; ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (cc == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "No ccache by that name\n"); state->op_ret = ERR_NO_CREDS; tevent_req_done(req); return; } ret = kcm_creds_to_table(conn_data, kcm_cc_get_cred(cc), &conn_data->creds); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Unable to build credentials hash table " "[%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } for (crd = kcm_cc_get_cred(cc); crd != NULL; crd = kcm_cc_next_cred(crd)) { ret = kcm_cred_get_uuid(crd, uuid); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Credential has no UUID, skipping\n"); continue; } kcm_debug_uuid(uuid); ret = sss_iobuf_write_len(state->op_ctx->reply, uuid, UUID_BYTES); if (ret != EOK) { char uuid_errbuf[UUID_STR_SIZE]; uuid_parse(uuid_errbuf, uuid); DEBUG(SSSDBG_MINOR_FAILURE, "Cannot marshall UUID %s [%d]: %s\n", uuid_errbuf, ret, sss_strerror(ret)); continue; } } state->op_ret = EOK; tevent_req_done(req); } static errno_t kcm_op_get_cred_by_uuid_reply(struct kcm_cred *crd, struct sss_iobuf *reply) { struct sss_iobuf *cred_blob; errno_t ret; cred_blob = kcm_cred_get_creds(crd); if (cred_blob == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Credentials lack the creds blob\n"); return ERR_NO_CREDS; } ret = sss_iobuf_write_len(reply, sss_iobuf_get_data(cred_blob), sss_iobuf_get_size(cred_blob)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot write ccache blob [%d]: %s\n", ret, sss_strerror(ret)); } return ret; } struct kcm_op_get_cred_by_uuid_state { struct kcm_op_common_state common; uuid_t uuid; }; /* (name, uuid) -> (cred) */ static void kcm_op_get_cred_by_uuid_getbyname_done(struct tevent_req *subreq); static struct tevent_req * kcm_op_get_cred_by_uuid_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_get_cred_by_uuid_state *state; struct kcm_cred *crd; errno_t ret; const char *name; req = tevent_req_create(mem_ctx, &state, struct kcm_op_get_cred_by_uuid_state); if (req == NULL) { return NULL; } state->common.op_ctx = op_ctx; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { goto immediate; } ret = sss_iobuf_read_len(state->common.op_ctx->input, UUID_BYTES, state->uuid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot read input UUID [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } if (op_ctx->conn_data->creds != NULL) { crd = kcm_creds_lookup(op_ctx->conn_data->creds, state->uuid); if (crd == NULL) { /* This should not happen, it can only happen if wrong UUID was * requested which suggests bug in the caller application. */ DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that UUID\n"); kcm_debug_uuid(state->uuid); state->common.op_ret = ERR_KCM_CC_END; ret = EOK; goto immediate; } else { ret = kcm_op_get_cred_by_uuid_reply(crd, op_ctx->reply); if (ret == ERR_NO_CREDS) { state->common.op_ret = ret; ret = EOK; } goto immediate; } } DEBUG(SSSDBG_TRACE_LIBS, "Returning creds by UUID for %s\n", name); subreq = kcm_ccdb_getbyname_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_cred_by_uuid_getbyname_done, req); return req; immediate: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static void kcm_op_get_cred_by_uuid_getbyname_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_get_cred_by_uuid_state *state = tevent_req_data(req, struct kcm_op_get_cred_by_uuid_state); errno_t ret; struct kcm_ccache *cc; struct kcm_cred *crd; struct kcm_conn_data *conn_data; conn_data = state->common.op_ctx->conn_data; ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } ret = kcm_creds_to_table(conn_data, kcm_cc_get_cred(cc), &conn_data->creds); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Unable to build credentials hash table " "[%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (conn_data->creds != NULL) { crd = kcm_creds_lookup(conn_data->creds, state->uuid); if (crd == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that UUID\n"); kcm_debug_uuid(state->uuid); state->common.op_ret = ERR_KCM_CC_END; } else { ret = kcm_op_get_cred_by_uuid_reply(crd, state->common.op_ctx->reply); if (ret != EOK && ret != ERR_NO_CREDS) { tevent_req_error(req, ret); return; } state->common.op_ret = ret; } } tevent_req_done(req); } static errno_t kcm_op_get_cred_by_uuid_recv(struct tevent_req *req, uint32_t *_op_ret) { struct kcm_op_get_cred_by_uuid_state *state; state = tevent_req_data(req, struct kcm_op_get_cred_by_uuid_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_op_ret = state->common.op_ret; return EOK; } /* (name, flags, credtag) -> () */ /* FIXME */ static struct tevent_req * kcm_op_remove_cred_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct kcm_op_common_state *state = NULL; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; state->op_ret = ERR_KCM_OP_NOT_IMPLEMENTED; tevent_req_post(req, ev); tevent_req_done(req); return req; } /* () -> (uuid, ...) */ static void kcm_op_get_cache_uuid_list_done(struct tevent_req *subreq); static struct tevent_req * kcm_op_get_cache_uuid_list_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_common_state *state = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; DEBUG(SSSDBG_TRACE_LIBS, "Returning full UUID list\n"); subreq = kcm_ccdb_list_send(state, ev, op_ctx->kcm_data->db, op_ctx->client); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_cache_uuid_list_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_get_cache_uuid_list_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); errno_t ret; uuid_t *uuid_list; ret = kcm_ccdb_list_recv(subreq, state, &uuid_list); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot list the ccache DB [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (uuid_list == NULL || uuid_is_null(uuid_list[0])) { DEBUG(SSSDBG_MINOR_FAILURE, "Nothing to list\n"); state->op_ret = ERR_NO_MATCHING_CREDS; tevent_req_done(req); return; } for (int i = 0; uuid_is_null(uuid_list[i]) == false; i++) { kcm_debug_uuid(uuid_list[i]); ret = sss_iobuf_write_len(state->op_ctx->reply, uuid_list[i], UUID_BYTES); if (ret != EOK) { char uuid_errbuf[UUID_STR_SIZE]; uuid_parse(uuid_errbuf, uuid_list[i]); DEBUG(SSSDBG_MINOR_FAILURE, "Cannot marshall UUID %s [%d]: %s\n", uuid_errbuf, ret, sss_strerror(ret)); tevent_req_done(req); return; } } tevent_req_done(req); } /* (uuid) -> (name) */ static void kcm_op_get_cache_by_uuid_done(struct tevent_req *subreq); static struct tevent_req * kcm_op_get_cache_by_uuid_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_common_state *state = NULL; errno_t ret; uuid_t uuid_in; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; DEBUG(SSSDBG_TRACE_LIBS, "Retrieving cache by UUID\n"); ret = sss_iobuf_read_len(op_ctx->input, UUID_BYTES, uuid_in); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot read input UUID [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } kcm_debug_uuid(uuid_in); subreq = kcm_ccdb_getbyuuid_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, uuid_in); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_cache_by_uuid_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_get_cache_by_uuid_done(struct tevent_req *subreq) { errno_t ret; struct kcm_ccache *cc; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); const char *name; ret = kcm_ccdb_getbyuuid_recv(subreq, state, &cc); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by UUID [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (cc == NULL) { state->op_ret = ERR_KCM_CC_END; tevent_req_done(req); return; } name = kcm_cc_get_name(cc); DEBUG(SSSDBG_TRACE_INTERNAL, "Found %s by UUID\n", name); ret = sss_iobuf_write_stringz(state->op_ctx->reply, name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot write output name [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } /* () -> (name) */ struct kcm_op_get_default_ccache_state { uint32_t op_ret; struct kcm_op_ctx *op_ctx; struct tevent_context *ev; const char *name; }; static void kcm_op_get_get_default_done(struct tevent_req *subreq); static void kcm_op_get_default_ccache_byuuid_done(struct tevent_req *subreq); static void kcm_op_get_default_ccache_list_done(struct tevent_req *subreq); static errno_t kcm_op_get_default_ccache_reply_step(struct kcm_op_get_default_ccache_state *state); static struct tevent_req * kcm_op_get_default_ccache_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_get_default_ccache_state *state = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct kcm_op_get_default_ccache_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; state->ev = ev; DEBUG(SSSDBG_TRACE_LIBS, "Getting client's default ccache\n"); subreq = kcm_ccdb_get_default_send(state, ev, state->op_ctx->kcm_data->db, state->op_ctx->client); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_get_default_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_get_get_default_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_get_default_ccache_state *state = tevent_req_data(req, struct kcm_op_get_default_ccache_state); errno_t ret; uuid_t dfl_uuid; ret = kcm_ccdb_get_default_recv(subreq, &dfl_uuid); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get default ccache [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (uuid_is_null(dfl_uuid) == true) { /* No cache marked as default -- get an existing ccache for ID * and treat the default as simply the first one */ subreq = kcm_ccdb_list_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_get_default_ccache_list_done, req); return; } /* Existing default */ subreq = kcm_ccdb_name_by_uuid_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, dfl_uuid); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_get_default_ccache_byuuid_done, req); return; } static void kcm_op_get_default_ccache_byuuid_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_get_default_ccache_state *state = tevent_req_data(req, struct kcm_op_get_default_ccache_state); errno_t ret; ret = kcm_ccdb_name_by_uuid_recv(subreq, state, &state->name); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccahe by UUID [%d]: %s\n", ret, sss_strerror(ret)); /* Instead of failing the whole operation, return the first * ccache as a fallback */ subreq = kcm_ccdb_list_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_get_default_ccache_list_done, req); return; } ret = kcm_op_get_default_ccache_reply_step(state); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static void kcm_op_get_default_ccache_list_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_get_default_ccache_state *state = tevent_req_data(req, struct kcm_op_get_default_ccache_state); errno_t ret; uuid_t *uuid_list; ret = kcm_ccdb_list_recv(subreq, state, &uuid_list); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot list ccaches [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (uuid_list == NULL || uuid_is_null(uuid_list[0])) { /* No cache at all, just send back a reply */ ret = kcm_op_get_default_ccache_reply_step(state); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); return; } /* Otherwise resolve the first cache and use it as a default */ subreq = kcm_ccdb_name_by_uuid_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, uuid_list[0]); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_get_default_ccache_byuuid_done, req); return; } static errno_t kcm_op_get_default_ccache_reply_step(struct kcm_op_get_default_ccache_state *state) { errno_t ret; if (state->name == NULL) { state->name = talloc_asprintf(state, "%"SPRIuid, cli_creds_get_uid(state->op_ctx->client)); if (state->name == NULL) { return ENOMEM; } } DEBUG(SSSDBG_TRACE_INTERNAL, "The default ccache is %s\n", state->name); ret = sss_iobuf_write_stringz(state->op_ctx->reply, state->name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot write output name [%d]: %s\n", ret, sss_strerror(ret)); return ret; } return EOK; } static errno_t kcm_op_get_default_ccache_recv(struct tevent_req *req, uint32_t *_op_ret) { KCM_OP_RET_FROM_TYPE(req, struct kcm_op_get_default_ccache_state, _op_ret); } /* (name) -> () */ static void kcm_op_set_default_ccache_getbyname_done(struct tevent_req *subreq); static void kcm_op_set_default_create_step(struct tevent_req *req); static void kcm_op_set_default_create_step_done(struct tevent_req *subreq); static void kcm_op_set_default_step(struct tevent_req *req); static void kcm_op_set_default_done(struct tevent_req *subreq); struct kcm_op_set_default_ccache_state { uint32_t op_ret; struct kcm_op_ctx *op_ctx; struct tevent_context *ev; const char *name; uuid_t dfl_uuid; struct kcm_ccache *new_cc; }; static struct tevent_req * kcm_op_set_default_ccache_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_set_default_ccache_state *state = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct kcm_op_set_default_ccache_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; state->ev = ev; ret = sss_iobuf_read_stringz(op_ctx->input, &state->name); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot read input name [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Setting default ccache %s\n", state->name); subreq = kcm_ccdb_uuid_by_name_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, state->name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_set_default_ccache_getbyname_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_set_default_ccache_getbyname_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, struct kcm_op_set_default_ccache_state); ret = kcm_ccdb_uuid_by_name_recv(subreq, state, state->dfl_uuid); talloc_zfree(subreq); if (ret == ERR_NO_CREDS) { DEBUG(SSSDBG_TRACE_LIBS, "The ccache does not exist, creating a new one\n"); kcm_op_set_default_create_step(req); return; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } kcm_op_set_default_step(req); } static void kcm_op_set_default_create_step(struct tevent_req *req) { errno_t ret; struct tevent_req *subreq; struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, struct kcm_op_set_default_ccache_state); /* Only allow to create ccaches for 'self' */ ret = kcm_check_name(state->name, state->op_ctx->client); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Name %s is malformed [%d]: %s\n", state->name, ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } ret = kcm_cc_new(state->op_ctx, state->op_ctx->kcm_data->k5c, state->op_ctx->client, state->name, NULL, &state->new_cc); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot create new ccache %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } subreq = kcm_ccdb_create_cc_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, state->new_cc); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_set_default_create_step_done, req); } static void kcm_op_set_default_create_step_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, struct kcm_op_set_default_ccache_state); ret = kcm_ccdb_create_cc_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot add ccache to db %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } DEBUG(SSSDBG_TRACE_FUNC, "The ccache was created, switching to it"); ret = kcm_cc_get_uuid(state->new_cc, state->dfl_uuid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get new ccache UUID [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } kcm_op_set_default_step(req); } static void kcm_op_set_default_step(struct tevent_req *req) { struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, struct kcm_op_set_default_ccache_state); struct tevent_req *subreq; subreq = kcm_ccdb_set_default_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, state->dfl_uuid); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_set_default_done, req); return; } static void kcm_op_set_default_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, struct kcm_op_set_default_ccache_state); ret = kcm_ccdb_set_default_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set default ccache %d: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } static errno_t kcm_op_set_default_ccache_recv(struct tevent_req *req, uint32_t *_op_ret) { KCM_OP_RET_FROM_TYPE(req, struct kcm_op_set_default_ccache_state, _op_ret); } /* (name) -> (offset) */ static void kcm_op_get_kdc_offset_getbyname_done(struct tevent_req *subreq); static struct tevent_req * kcm_op_get_kdc_offset_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_common_state *state = NULL; errno_t ret; const char *name; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot read input name [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Requested offset for principal %s\n", name); subreq = kcm_ccdb_getbyname_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_kdc_offset_getbyname_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_get_kdc_offset_getbyname_done(struct tevent_req *subreq) { errno_t ret; struct kcm_ccache *cc; int32_t offset; int32_t offset_be; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_common_state *state = tevent_req_data(req, struct kcm_op_common_state); ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get matching ccache [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } if (cc == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "No matching credentials\n"); state->op_ret = ERR_NO_MATCHING_CREDS; tevent_req_done(req); return; } offset = kcm_cc_get_offset(cc); DEBUG(SSSDBG_TRACE_LIBS, "KDC offset: %"PRIi32"\n", offset); offset_be = htobe32(offset); ret = sss_iobuf_write_int32(state->op_ctx->reply, offset_be); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot write KDC offset [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } /* (name, offset) -> () */ /* () -> (name) */ struct kcm_op_set_kdc_offset_state { uint32_t op_ret; struct kcm_op_ctx *op_ctx; struct tevent_context *ev; }; static void kcm_op_set_kdc_offset_getbyname_done(struct tevent_req *subreq); static void kcm_op_set_kdc_offset_mod_done(struct tevent_req *subreq); static struct tevent_req * kcm_op_set_kdc_offset_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct kcm_op_set_kdc_offset_state *state = NULL; errno_t ret; const char *name; req = tevent_req_create(mem_ctx, &state, struct kcm_op_set_kdc_offset_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; state->ev = ev; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot read input name [%d]: %s\n", ret, sss_strerror(ret)); goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Setting offset for principal %s\n", name); subreq = kcm_ccdb_uuid_by_name_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_set_kdc_offset_getbyname_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_set_kdc_offset_getbyname_done(struct tevent_req *subreq) { errno_t ret; struct kcm_mod_ctx *mod_ctx; int32_t offset_be; uuid_t uuid; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_set_kdc_offset_state *state = tevent_req_data(req, struct kcm_op_set_kdc_offset_state); /* `uuid` is output arg and isn't read in kcm_ccdb_uuid_by_name_recv() but * since libuuid is opaque for cppcheck it generates false positive here */ /* cppcheck-suppress uninitvar */ ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get matching ccache [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } ret = sss_iobuf_read_int32(state->op_ctx->input, &offset_be); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot read KDC offset [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } mod_ctx = kcm_mod_ctx_new(state); if (mod_ctx == NULL) { tevent_req_error(req, ENOMEM); return; } mod_ctx->kdc_offset = be32toh(offset_be); subreq = kcm_ccdb_mod_cc_send(state, state->ev, state->op_ctx->kcm_data->db, state->op_ctx->client, uuid, mod_ctx); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, kcm_op_set_kdc_offset_mod_done, req); } static void kcm_op_set_kdc_offset_mod_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct kcm_op_set_kdc_offset_state *state = tevent_req_data(req, struct kcm_op_set_kdc_offset_state); ret = kcm_ccdb_mod_cc_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot modify ccache [%d]: %s\n", ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } state->op_ret = EOK; tevent_req_done(req); } static errno_t kcm_op_set_kdc_offset_recv(struct tevent_req *req, uint32_t *_op_ret) { KCM_OP_RET_FROM_TYPE(req, struct kcm_op_set_kdc_offset_state, _op_ret); } static void kcm_op_get_cred_list_done(struct tevent_req *subreq); static struct tevent_req * kcm_op_get_cred_list_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct kcm_op_ctx *op_ctx) { struct kcm_op_common_state *state; struct tevent_req *subreq; struct tevent_req *req; const char *name; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); if (req == NULL) { return NULL; } state->op_ctx = op_ctx; ret = sss_iobuf_read_stringz(op_ctx->input, &name); if (ret != EOK) { goto immediate; } DEBUG(SSSDBG_TRACE_LIBS, "Returning credentials for %s\n", name); subreq = kcm_ccdb_getbyname_send(state, ev, op_ctx->kcm_data->db, op_ctx->client, name); if (subreq == NULL) { ret = ENOMEM; goto immediate; } tevent_req_set_callback(subreq, kcm_op_get_cred_list_done, req); return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void kcm_op_get_cred_list_done(struct tevent_req *subreq) { struct tevent_req *req; struct kcm_op_common_state *state; struct kcm_ccache *cc; struct kcm_cred *crd; uint32_t num_creds; struct sss_iobuf *crd_blob; uint8_t *crd_data; uint32_t crd_size; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct kcm_op_common_state); ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", ret, sss_strerror(ret)); goto done; } if (cc == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "No ccache by that name\n"); state->op_ret = ERR_NO_CREDS; ret = EOK; goto done; } num_creds = 0; for (crd = kcm_cc_get_cred(cc); crd != NULL; crd = kcm_cc_next_cred(crd)) { num_creds++; } ret = sss_iobuf_write_uint32(state->op_ctx->reply, htobe32(num_creds)); if (ret != EOK) { goto done; } for (crd = kcm_cc_get_cred(cc); crd != NULL; crd = kcm_cc_next_cred(crd)) { crd_blob = kcm_cred_get_creds(crd); if (crd_blob == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Credentials lack the creds blob\n"); ret = ERR_NO_CREDS; goto done; } crd_data = sss_iobuf_get_data(crd_blob); crd_size = sss_iobuf_get_size(crd_blob); ret = sss_iobuf_write_uint32(state->op_ctx->reply, htobe32(crd_size)); if (ret != EOK) { goto done; } ret = sss_iobuf_write_len(state->op_ctx->reply, crd_data, crd_size); if (ret != EOK) { goto done; } } state->op_ret = EOK; ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static struct kcm_op kcm_optable[] = { { "NOOP", NULL, NULL }, { "GET_NAME", NULL, NULL }, { "RESOLVE", NULL, NULL }, { "GEN_NEW", kcm_op_gen_new_send, NULL }, { "INITIALIZE", kcm_op_initialize_send, kcm_op_initialize_recv }, { "DESTROY", kcm_op_destroy_send, NULL }, { "STORE", kcm_op_store_send, kcm_op_store_recv }, { "RETRIEVE", NULL, NULL }, { "GET_PRINCIPAL", kcm_op_get_principal_send, NULL }, { "GET_CRED_UUID_LIST", kcm_op_get_cred_uuid_list_send, NULL }, { "GET_CRED_BY_UUID", kcm_op_get_cred_by_uuid_send, kcm_op_get_cred_by_uuid_recv }, { "REMOVE_CRED", kcm_op_remove_cred_send, NULL }, { "SET_FLAGS", NULL, NULL }, { "CHOWN", NULL, NULL }, { "CHMOD", NULL, NULL }, { "GET_INITIAL_TICKET", NULL, NULL }, { "GET_TICKET", NULL, NULL }, { "MOVE_CACHE", NULL, NULL }, { "GET_CACHE_UUID_LIST", kcm_op_get_cache_uuid_list_send, NULL }, { "GET_CACHE_BY_UUID", kcm_op_get_cache_by_uuid_send, NULL }, { "GET_DEFAULT_CACHE", kcm_op_get_default_ccache_send, kcm_op_get_default_ccache_recv }, { "SET_DEFAULT_CACHE", kcm_op_set_default_ccache_send, kcm_op_set_default_ccache_recv }, { "GET_KDC_OFFSET", kcm_op_get_kdc_offset_send, NULL }, { "SET_KDC_OFFSET", kcm_op_set_kdc_offset_send, kcm_op_set_kdc_offset_recv }, { "ADD_NTLM_CRED", NULL, NULL }, { "HAVE_NTLM_CRED", NULL, NULL }, { "DEL_NTLM_CRED", NULL, NULL }, { "DO_NTLM_AUTH", NULL, NULL }, { "GET_NTLM_USER_LIST", NULL, NULL }, { NULL, NULL, NULL } }; /* MIT EXTENSIONS, see private header src/include/kcm.h in krb5 sources */ #define KCM_MIT_OFFSET 13001 static struct kcm_op kcm_mit_optable[] = { { "GET_CRED_LIST", kcm_op_get_cred_list_send, NULL }, { NULL, NULL, NULL } }; struct kcm_op *kcm_get_opt(uint16_t opcode) { struct kcm_op *table; struct kcm_op *op; size_t len; DEBUG(SSSDBG_TRACE_INTERNAL, "The client requested operation %"PRIu16"\n", opcode); table = kcm_optable; len = sizeof(kcm_optable) / sizeof(struct kcm_op); if (opcode >= KCM_MIT_OFFSET) { opcode -= KCM_MIT_OFFSET; table = kcm_mit_optable; len = sizeof(kcm_mit_optable) / sizeof(struct kcm_op); } if (opcode >= len) { return NULL; } op = &table[opcode]; if (op->fn_recv == NULL) { op->fn_recv = kcm_op_common_recv; } return op; } const char *kcm_opt_name(struct kcm_op *op) { if (op == NULL || op->name == NULL) { return "Unknown operation"; } return op->name; }