diff options
Diffstat (limited to 'src/responder/kcm')
-rw-r--r-- | src/responder/kcm/kcm.c | 374 | ||||
-rw-r--r-- | src/responder/kcm/kcm_renew.c | 775 | ||||
-rw-r--r-- | src/responder/kcm/kcm_renew.h | 53 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache.c | 1724 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache.h | 380 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache_be.h | 216 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache_binary.c | 308 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache_key.c | 144 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache_mem.c | 826 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache_pvt.h | 61 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ccache_secdb.c | 1671 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_cmd.c | 667 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_op_queue.c | 332 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ops.c | 2458 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_ops.h | 67 | ||||
-rw-r--r-- | src/responder/kcm/kcmsrv_pvt.h | 106 | ||||
-rw-r--r-- | src/responder/kcm/secrets/config.c | 92 | ||||
-rw-r--r-- | src/responder/kcm/secrets/sec_pvt.h | 47 | ||||
-rw-r--r-- | src/responder/kcm/secrets/secrets.c | 1229 | ||||
-rw-r--r-- | src/responder/kcm/secrets/secrets.h | 114 |
20 files changed, 11644 insertions, 0 deletions
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c new file mode 100644 index 0000000..f61d478 --- /dev/null +++ b/src/responder/kcm/kcm.c @@ -0,0 +1,374 @@ +/* + SSSD + + KCM Server - the mainloop and server setup + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <popt.h> + +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "responder/kcm/kcm_renew.h" +#include "responder/common/responder.h" +#include "providers/krb5/krb5_common.h" +#include "util/util.h" +#include "util/sss_krb5.h" + +#define DEFAULT_KCM_FD_LIMIT 2048 +#define DEFAULT_KCM_CLI_IDLE_TIMEOUT 300 + +#ifndef SSS_KCM_SOCKET_NAME +#define SSS_KCM_SOCKET_NAME DEFAULT_KCM_SOCKET_PATH +#endif + +static int kcm_responder_ctx_destructor(void *ptr) +{ + struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx); + + /* mark that we are shutting down the responder, so it is propagated + * into underlying contexts that are freed right before rctx */ + DEBUG(SSSDBG_TRACE_FUNC, "Responder is being shut down\n"); + rctx->shutting_down = true; + + return 0; +} + +static errno_t kcm_renewals_init(struct tevent_context *ev, + struct resp_ctx *rctx, + struct kcm_ctx *kctx, + struct krb5_ctx *krb5_ctx, + time_t renew_intv, + bool *_renewal_enabled) +{ +#ifndef HAVE_KCM_RENEWAL + return EOK; +#else + errno_t ret; + + ret = kcm_get_renewal_config(kctx, &krb5_ctx, &renew_intv); + if (ret == ENOTSUP) { + DEBUG(SSSDBG_TRACE_FUNC, "TGT Renewals support disabled\n"); + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to read TGT renewal configuration [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (renew_intv > 0) { + *_renewal_enabled = true; + + ret = kcm_renewal_setup(rctx, krb5_ctx, ev, kctx->kcm_data->db, renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to setup TGT renewals [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + return EOK; +#endif +} + +static errno_t kcm_get_ccdb_be(struct kcm_ctx *kctx) +{ + errno_t ret; + char *str_db; + + ret = confdb_get_string(kctx->rctx->cdb, + kctx->rctx, + kctx->rctx->confdb_service_path, + CONFDB_KCM_DB, + "secdb", + &str_db); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the KCM database type [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "KCM database type: %s\n", str_db); + if (strcasecmp(str_db, "memory") == 0) { + kctx->cc_be = CCDB_BE_MEMORY; + } else if (strcasecmp(str_db, "secdb") == 0) { + kctx->cc_be = CCDB_BE_SECDB; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Unexpected KCM database type %s\n", str_db); + } + + return EOK; +} + +static int kcm_get_config(struct kcm_ctx *kctx) +{ + int ret; + char *sock_name; + + ret = confdb_get_int(kctx->rctx->cdb, + CONFDB_KCM_CONF_ENTRY, + CONFDB_SERVICE_FD_LIMIT, + DEFAULT_KCM_FD_LIMIT, + &kctx->fd_limit); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get file descriptors limit\n"); + goto done; + } + + ret = confdb_get_int(kctx->rctx->cdb, + kctx->rctx->confdb_service_path, + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, + DEFAULT_KCM_CLI_IDLE_TIMEOUT, + &kctx->rctx->client_idle_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the client idle timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, ret, strerror(ret)); + goto done; + } + + /* Ensure that the client timeout is at least ten seconds */ + if (kctx->rctx->client_idle_timeout < 10) { + kctx->rctx->client_idle_timeout = 10; + } + + ret = confdb_get_string(kctx->rctx->cdb, + kctx->rctx, + kctx->rctx->confdb_service_path, + CONFDB_KCM_SOCKET, + SSS_KCM_SOCKET_NAME, + &sock_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get KCM socket path [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + kctx->rctx->sock_name = sock_name; + + ret = kcm_get_ccdb_be(kctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get KCM ccache DB [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + kctx->qctx = kcm_ops_queue_create(kctx, kctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create KCM request queue [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + ret = EOK; +done: + return ret; +} + +static int kcm_data_destructor(void *ptr) +{ + struct kcm_resp_ctx *kcm_data = talloc_get_type(ptr, struct kcm_resp_ctx); + + if (kcm_data != NULL) { + krb5_free_context(kcm_data->k5c); + } + return 0; +} + +static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + const char *confdb_service_path, + enum kcm_ccdb_be cc_be) +{ + struct kcm_resp_ctx *kcm_data; + krb5_error_code kret; + + kcm_data = talloc_zero(mem_ctx, struct kcm_resp_ctx); + if (kcm_data == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing kcm data\n"); + return NULL; + } + + kcm_data->db = kcm_ccdb_init(kcm_data, + ev, + cdb, + confdb_service_path, + cc_be); + if (kcm_data->db == NULL) { + talloc_free(kcm_data); + return NULL; + } + + kret = sss_krb5_init_context(&kcm_data->k5c); + if (kret != EOK) { + talloc_free(kcm_data); + return NULL; + } + talloc_set_destructor((TALLOC_CTX*)kcm_data, kcm_data_destructor); + + return kcm_data; +} + +static int kcm_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct kcm_ctx *kctx; + bool renewal_enabled = false; + struct krb5_ctx *krb5_ctx = NULL; + time_t renew_intv = 0; + int ret; + + rctx = talloc_zero(mem_ctx, struct resp_ctx); + if (rctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing resp_ctx\n"); + return ENOMEM; + } + rctx->ev = ev; + rctx->cdb = cdb; + rctx->confdb_service_path = CONFDB_KCM_CONF_ENTRY; + rctx->shutting_down = false; + rctx->lfd = -1; + rctx->priv_lfd = -1; + + talloc_set_destructor((TALLOC_CTX*)rctx, kcm_responder_ctx_destructor); + + kctx = talloc_zero(rctx, struct kcm_ctx); + if (kctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing kcm_ctx\n"); + ret = ENOMEM; + goto fail; + } + + kctx->rctx = rctx; + kctx->rctx->pvt_ctx = kctx; + + ret = kcm_get_config(kctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting KCM config\n"); + goto fail; + } + + kctx->kcm_data = kcm_data_setup(kctx, + ev, + kctx->rctx->cdb, + kctx->rctx->confdb_service_path, + kctx->cc_be); + if (kctx->kcm_data == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing responder data\n"); + ret = EIO; + goto fail; + } + + ret = kcm_renewals_init(ev, rctx, kctx, krb5_ctx, renew_intv, &renewal_enabled); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize TGT Renewals [%d]: %s\n", + ret, sss_strerror(ret)); + goto fail; + } + + if (renewal_enabled) { + /* Disable resp idle timeout to allow renewals */ + rctx->idle_timeout = 0; + } else { + responder_setup_idle_timeout_config(kctx->rctx); + } + + /* Set up file descriptor limits */ + responder_set_fd_limit(kctx->fd_limit); + + ret = activate_unix_sockets(rctx, kcm_connection_setup); + if (ret != EOK) goto fail; + + DEBUG(SSSDBG_TRACE_FUNC, "KCM Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_kcm"; + DEBUG_INIT(debug_level, opt_logger); + + ret = server_setup("kcm", true, 0, uid, gid, CONFDB_KCM_CONF_ENTRY, + &main_ctx, true); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = kcm_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/kcm/kcm_renew.c b/src/responder/kcm/kcm_renew.c new file mode 100644 index 0000000..39e9470 --- /dev/null +++ b/src/responder/kcm/kcm_renew.c @@ -0,0 +1,775 @@ +/* + SSSD + + KCM Kerberos renewals -- Renew a TGT automatically + + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2020 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include "util/util.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_ccache.h" +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcm_renew.h" + +extern struct dp_option default_krb5_opts[]; + +struct kcm_renew_auth_ctx { + struct tevent_context *ev; + struct krb5child_req *kr; + + struct krb5_ctx *krb5_ctx; + struct kcm_auth_data *auth_data; + + uint8_t *buf; + ssize_t len; +}; + +struct kcm_auth_data { + struct kcm_renew_auth_ctx *auth_ctx; + struct krb5_ctx *krb5_ctx; + uid_t uid; + gid_t gid; + const char *ccname; + const char *upn; +}; + +static void kcm_renew_tgt_done(struct tevent_req *req); + +static errno_t kcm_set_options(struct krb5_ctx *krb5_ctx, + char *lifetime, + char *rtime, + bool validate, + bool canonicalize, + int timeout, + char *renew_intv, + time_t *_renew_intv_tm) +{ + errno_t ret; + krb5_error_code kerr; + krb5_deltat renew_interval_delta; + + if (renew_intv != NULL) { + kerr = krb5_string_to_deltat(renew_intv, &renew_interval_delta); + if (kerr != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "krb5_string_to_deltat failed\n"); + ret = ENOMEM; + goto done; + } + + *_renew_intv_tm = renew_interval_delta; + } else { + *_renew_intv_tm = 0; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_RENEW_INTERVAL, + renew_intv == NULL ? "none" : renew_intv); + + if (lifetime != NULL) { + ret = krb5_string_to_deltat(lifetime, &krb5_ctx->lifetime); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to convert lifetime string [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + krb5_ctx->lifetime_str = lifetime; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_LIFETIME, + krb5_ctx->lifetime_str == NULL ? "none" : krb5_ctx->lifetime_str); + + if (rtime != 0) { + ret = krb5_string_to_deltat(rtime, &krb5_ctx->rlife); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to convert renewable lifetime " + "string [%d]: %s.\n", ret, sss_strerror(ret)); + goto done; + } + } + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_RENEWABLE_LIFETIME, + rtime == NULL ? "none" : rtime); + + ret = dp_opt_set_bool(krb5_ctx->opts, KRB5_VALIDATE, validate); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set krb5 child timeout [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_VALIDATE, + validate ? "true" : "false"); + + krb5_ctx->canonicalize = canonicalize; + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_CANONICALIZE, + canonicalize ? "true" : "false"); + + if (timeout > 0) { + ret = dp_opt_set_int(krb5_ctx->opts, KRB5_AUTH_TIMEOUT, timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set krb5 child timeout [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%d]\n", + CONFDB_KCM_KRB5_AUTH_TIMEOUT, + timeout); + + ret = EOK; +done: + return ret; +} + +static errno_t kcm_read_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *cpath, + char **_lifetime, + char **_rtime, + bool *_validate, + bool *_canonicalize, + int *_timeout, + char **_renew_intv) +{ + TALLOC_CTX *tmp_ctx; + char *lifetime; + char *rtime; + bool validate; + bool canonicalize; + int timeout; + char *renew_intv; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = confdb_get_string(cdb, tmp_ctx, cpath, + CONFDB_KCM_KRB5_LIFETIME, NULL, + &lifetime); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_LIFETIME, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(cdb, tmp_ctx, cpath, + CONFDB_KCM_KRB5_RENEWABLE_LIFETIME, NULL, + &rtime); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_RENEWABLE_LIFETIME, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_bool(cdb, cpath, + CONFDB_KCM_KRB5_VALIDATE, false, + &validate); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_VALIDATE, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_bool(cdb, cpath, + CONFDB_KCM_KRB5_CANONICALIZE, false, + &canonicalize); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_CANONICALIZE, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_int(cdb, cpath, + CONFDB_KCM_KRB5_AUTH_TIMEOUT, 0, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_AUTH_TIMEOUT, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(cdb, tmp_ctx, cpath, + CONFDB_KCM_KRB5_RENEW_INTERVAL, NULL, + &renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_AUTH_TIMEOUT, ret, sss_strerror(ret)); + goto done; + } + + + *_lifetime = talloc_steal(mem_ctx, lifetime); + *_rtime = talloc_steal(mem_ctx, rtime); + *_validate = validate; + *_canonicalize = canonicalize; + *_timeout = timeout; + *_renew_intv = renew_intv; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int kcm_get_renewal_config(struct kcm_ctx *kctx, + struct krb5_ctx **_krb5_ctx, + time_t *_renew_intv) +{ + int ret; + struct krb5_ctx *krb5_ctx; + char *lifetime; + char *rtime; + bool validate; + bool canonicalize; + int timeout; + char *renew_intv; + time_t renew_intv_tm; + bool tgt_renewal; + char *tgt_renewal_inherit; + const char *conf_path; + int i; + + krb5_ctx = talloc_zero(kctx->rctx, struct krb5_ctx); + if (krb5_ctx == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error allocating krb5_ctx\n"); + goto done; + } + + /* Set default Kerberos options */ + krb5_ctx->opts = talloc_zero_array(krb5_ctx, struct dp_option, KRB5_OPTS); + if (krb5_ctx->opts == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error allocating krb5_ctx opts\n"); + goto done; + } + + for (i = 0; i < KRB5_OPTS; i++) { + krb5_ctx->opts[i].opt_name = default_krb5_opts[i].opt_name; + krb5_ctx->opts[i].type = default_krb5_opts[i].type; + krb5_ctx->opts[i].def_val = default_krb5_opts[i].def_val; + switch (krb5_ctx->opts[i].type) { + case DP_OPT_STRING: + ret = dp_opt_set_string(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.string); + break; + case DP_OPT_BLOB: + ret = dp_opt_set_blob(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.blob); + break; + case DP_OPT_NUMBER: + ret = dp_opt_set_int(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.number); + break; + case DP_OPT_BOOL: + ret = dp_opt_set_bool(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.boolean); + break; + default: + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed setting default renewal kerberos " + "options [%d]: %s\n", ret, sss_strerror(ret)); + talloc_free(krb5_ctx->opts); + goto done; + } + } + + ret = confdb_get_bool(kctx->rctx->cdb, + kctx->rctx->confdb_service_path, + CONFDB_KCM_TGT_RENEWAL, false, + &tgt_renewal); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve TGT Renewal confdb value " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_TGT_RENEWAL, + tgt_renewal ? "true" : "false"); + if (tgt_renewal == false) { + ret = ENOTSUP; + goto done; + } + + ret = confdb_get_string(kctx->rctx->cdb, + kctx->rctx, + kctx->rctx->confdb_service_path, + CONFDB_KCM_TGT_RENEWAL_INHERIT, + NULL, + &tgt_renewal_inherit); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve TGT Renewal inherit confdb " + "valule [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_TGT_RENEWAL_INHERIT, + tgt_renewal_inherit == NULL ? "none" : tgt_renewal_inherit); + + /* Override with config options */ + if (tgt_renewal_inherit == NULL) { + ret = kcm_read_options(kctx, kctx->rctx->cdb, kctx->rctx->confdb_service_path, + &lifetime, &rtime, &validate, &canonicalize, + &timeout, &renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read krb5 options from " + "[kcm] section [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } else { + conf_path = talloc_asprintf(kctx->rctx, CONFDB_DOMAIN_PATH_TMPL, + tgt_renewal_inherit); + if (conf_path == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error allocating conf_path\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Inherit krb5 options for domain [%s] for renewals\n", + conf_path); + ret = kcm_read_options(kctx, kctx->rctx->cdb, conf_path, + &lifetime, &rtime, &validate, &canonicalize, + &timeout, &renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed reading domain [%s] inherit krb5 options " + "[%d]: %s\n", conf_path, ret, sss_strerror(ret)); + goto done; + } + } + + ret = kcm_set_options(krb5_ctx, lifetime, rtime, validate, canonicalize, + timeout, renew_intv, &renew_intv_tm); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed setting krb5 options for renewal " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + *_renew_intv = renew_intv_tm; + *_krb5_ctx = krb5_ctx; + ret = EOK; +done: + if (ret != EOK) { + talloc_free(krb5_ctx); + } + return ret; +} + +static errno_t kcm_child_req_setup(TALLOC_CTX *mem_ctx, + struct kcm_auth_data *auth_data, + struct krb5_ctx *krb5_ctx, + struct krb5child_req **_req) +{ + struct krb5child_req *krreq; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Setup for renewal of [%s] " \ + "for principal name [%s]\n", + auth_data->upn, + auth_data->ccname); + + krreq = talloc_zero(mem_ctx, struct krb5child_req); + if (krreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to alloc krreq [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + krreq->krb5_ctx = krb5_ctx; + + /* Set uid and gid */ + krreq->uid = auth_data->uid; + krreq->gid = auth_data->gid; + + krreq->upn = talloc_strdup(krreq, auth_data->upn); + if (krreq->upn == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to strdup krreq->upn [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + krreq->ccname = talloc_asprintf(krreq, "KCM:%s", auth_data->ccname); + if (krreq->ccname == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to strdup krreq->ccname [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* Set PAM Data */ + krreq->pd = create_pam_data(krreq); + if (krreq->pd == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed creating pam data on krreq->pd " + "[%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + krreq->pd->cmd = SSS_CMD_RENEW; + krreq->pd->user = talloc_strdup(krreq->pd, auth_data->upn); + if (krreq->pd->user == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to strdup krreq->pd->user " + "[%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + /* Set authtok values */ + sss_authtok_set_empty(krreq->pd->newauthtok); + + ret = sss_authtok_set_ccfile(krreq->pd->authtok, krreq->ccname, 0); + if (ret != EOK) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed setting authtok krreq->ccname" + "[%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + krreq->old_ccname = krreq->ccname; + + *_req = krreq; + + return EOK; +fail: + talloc_zfree(krreq); + return ret; +} + +static void kcm_renew_tgt(struct tevent_context *ev, + struct tevent_immediate *imm, + void *private_data) +{ + struct kcm_auth_data *auth_data; + struct tevent_req *req; + struct kcm_renew_auth_ctx *ctx; + errno_t ret; + + auth_data = talloc_get_type(private_data, struct kcm_auth_data); + + ctx = talloc_zero(auth_data, struct kcm_renew_auth_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to allocate renew auth ctx\n"); + return; + } + auth_data->auth_ctx = ctx; + + ret = kcm_child_req_setup(ctx, auth_data, auth_data->krb5_ctx, &ctx->kr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to setup krb5 child for renewal [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(auth_data); + return; + } + + req = handle_child_send(ctx, ev, ctx->kr); + if (req == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to trigger krb5 child process request" + "[%d]: %s\n", ret, sss_strerror(ret)); + talloc_free(auth_data); + return; + } + + tevent_req_set_callback(req, kcm_renew_tgt_done, auth_data); + + return; +} + +static void kcm_renew_tgt_done(struct tevent_req *req) +{ + struct kcm_auth_data *auth_data; + struct kcm_renew_auth_ctx *ctx; + int ret; + struct krb5_child_response *res; + + auth_data = tevent_req_callback_data(req, struct kcm_auth_data); + ctx = auth_data->auth_ctx; + + ret = handle_child_recv(req, ctx, &ctx->buf, &ctx->len); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to receive krb5 child process request" + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + ret = parse_krb5_child_response(ctx, ctx->buf, ctx->len, ctx->kr->pd, + 0, &res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Krb5 child returned error! Please " \ + "inspect the krb5_child.log file. " + " Error [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + if (res->msg_status != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Renewal failed - krb5_child [%d]\n", + res->msg_status); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successfully renewed [%s]\n", res->ccname); +done: + talloc_zfree(ctx); + talloc_zfree(auth_data); + return; +} + +static errno_t kcm_creds_check_times(TALLOC_CTX *mem_ctx, + struct kcm_renew_tgt_ctx *renew_tgt_ctx, + krb5_creds *creds, + struct kcm_ccache *cc, + const char *client_name) +{ + struct tgt_times tgtt; + time_t now; + time_t start_renew; + struct kcm_auth_data *auth_data; + struct tevent_immediate *imm; + int ret; + + memset(&tgtt, 0, sizeof(tgtt)); + tgtt.authtime = creds->times.authtime; + tgtt.starttime = creds->times.starttime; + tgtt.endtime = creds->times.endtime; + tgtt.renew_till = creds->times.renew_till; + + now = time(NULL); + /* Attempt renewal only after half of the ticket lifetime has exceeded */ + start_renew = (time_t) (tgtt.starttime + 0.5 * (tgtt.endtime - tgtt.starttime)); + if (tgtt.renew_till >= tgtt.endtime && tgtt.renew_till >= now + && tgtt.endtime >= now && start_renew <= now) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Renewal cred ready!\n"); + auth_data = talloc_zero(renew_tgt_ctx, struct kcm_auth_data); + if (auth_data == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate auth_data for renewals\n"); + goto done; + } + + auth_data->krb5_ctx = renew_tgt_ctx->krb5_ctx; + auth_data->upn = talloc_strdup(auth_data, client_name); + auth_data->uid = cc->owner.uid; + auth_data->gid = cc->owner.gid; + auth_data->ccname = cc->name; + if (auth_data->upn == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate auth_data->upn for renewals\n"); + goto done; + } + + imm = tevent_create_immediate(auth_data); + if (imm == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed\n"); + goto done; + } + + tevent_schedule_immediate(imm, renew_tgt_ctx->ev, kcm_renew_tgt, + auth_data); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Time not applicable\n"); + } + + ret = EOK; +done: + return ret; +} + +errno_t kcm_renew_all_tgts(TALLOC_CTX *mem_ctx, + struct kcm_renew_tgt_ctx *renew_tgt_ctx, + struct kcm_ccache **cc_list) +{ + TALLOC_CTX *tmp_ctx; + size_t count = 0; + int ret; + struct kcm_ccache *cc; + char *client_name; + krb5_context krb_context; + krb5_creds **extracted_creds; + krb5_error_code kerr; + + if (cc_list == NULL) { + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create tmp talloc_ctx\n"); + return ENOMEM; + } + + kerr = krb5_init_context(&krb_context); + if (kerr != 0) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n"); + goto done; + } + + count = talloc_array_length(cc_list); + if (count <= 1) { + DEBUG(SSSDBG_TRACE_FUNC, "No renewal entries found.\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Found [%zu] renewal entries.\n", count - 1); + for (int i = 0; i < count - 1; i++) { + cc = cc_list[i]; + DEBUG(SSSDBG_TRACE_FUNC, + "Checking ccache [%s] for creds to renew\n", cc->name); + + extracted_creds = kcm_cc_unmarshal(tmp_ctx, krb_context, cc); + if (extracted_creds == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed unmarshaling creds\n"); + goto done; + } + + for (int j = 0; extracted_creds[j] != NULL; j++) { + kerr = krb5_unparse_name(tmp_ctx, extracted_creds[j]->client, + &client_name); + if (kerr != 0) { + ret = EIO; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed unparsing name\n"); + goto done; + } + + kcm_creds_check_times(tmp_ctx, renew_tgt_ctx, extracted_creds[j], + cc, client_name); + } + } + + ret = EOK; +done: + if (tmp_ctx != NULL) { + talloc_free(tmp_ctx); + } + krb5_free_context(krb_context); + return ret; +} + +static void kcm_renew_tgt_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data) +{ + struct kcm_renew_tgt_ctx *renew_tgt_ctx; + errno_t ret; + struct timeval next; + struct kcm_ccache **cc_list; + TALLOC_CTX *tmp_ctx; + + renew_tgt_ctx = talloc_get_type(data, struct kcm_renew_tgt_ctx); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failure in tmp_ctx talloc_new\n"); + return; + } + + /* forget the timer event, it will be freed by the tevent timer loop */ + renew_tgt_ctx->te = NULL; + + /* Prepare KCM ccache list for renewals */ + ret = kcm_ccdb_renew_tgts(tmp_ctx, renew_tgt_ctx->krb5_ctx, + ev, renew_tgt_ctx->db, &cc_list); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "No ccache renewal entries to prepare.\n"); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve list of TGTs for renewal " + "preparation [%d]: %s\n", ret, sss_strerror(ret)); + } + + if (ret == EOK) { + ret = kcm_renew_all_tgts(tmp_ctx, renew_tgt_ctx, cc_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to successfully execute renewal of TGT list" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + /* Reschedule timer */ + next = sss_tevent_timeval_current_ofs_time_t(renew_tgt_ctx->timer_interval); + renew_tgt_ctx->te = tevent_add_timer(ev, renew_tgt_ctx, + next, kcm_renew_tgt_timer_handler, + renew_tgt_ctx); + if (renew_tgt_ctx->te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup timer, renewals will be " + "disabled until the next interval triggers\n"); + talloc_zfree(renew_tgt_ctx); + } + + talloc_free(tmp_ctx); + return; +} + +errno_t kcm_renewal_setup(struct resp_ctx *rctx, + struct krb5_ctx *krb5_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + time_t renew_intv) +{ + int ret; + struct timeval next; + + krb5_ctx->kcm_renew_tgt_ctx = talloc_zero(krb5_ctx, struct kcm_renew_tgt_ctx); + if (krb5_ctx->kcm_renew_tgt_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + krb5_ctx->kcm_renew_tgt_ctx->rctx = rctx; + krb5_ctx->kcm_renew_tgt_ctx->krb5_ctx = krb5_ctx; + krb5_ctx->kcm_renew_tgt_ctx->db = db, + krb5_ctx->kcm_renew_tgt_ctx->ev = ev; + krb5_ctx->kcm_renew_tgt_ctx->timer_interval = renew_intv; + + /* Check KCM for tickets to renew */ + next = sss_tevent_timeval_current_ofs_time_t( + krb5_ctx->kcm_renew_tgt_ctx->timer_interval); + krb5_ctx->kcm_renew_tgt_ctx->te = tevent_add_timer(ev, krb5_ctx->kcm_renew_tgt_ctx, + next, + kcm_renew_tgt_timer_handler, + krb5_ctx->kcm_renew_tgt_ctx); + if (krb5_ctx->kcm_renew_tgt_ctx->te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup renewal timer\n"); + ret = ENOMEM; + goto fail; + } + + return EOK; + +fail: + talloc_zfree(krb5_ctx->renew_tgt_ctx); + return ret; +} diff --git a/src/responder/kcm/kcm_renew.h b/src/responder/kcm/kcm_renew.h new file mode 100644 index 0000000..0d69f42 --- /dev/null +++ b/src/responder/kcm/kcm_renew.h @@ -0,0 +1,53 @@ +/* + SSSD + + KCM Renewal, private header file + + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2020 Red Hat + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __KCM_RENEW_H__ +#define __KCM_RENEW_H__ + +#include "providers/krb5/krb5_common.h" +#include "src/providers/krb5/krb5_ccache.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "util/sss_ptr_hash.h" + +struct kcm_renew_tgt_ctx { + struct kcm_ccache **cc_list; + struct tevent_context *ev; + struct krb5_ctx *krb5_ctx; + struct resp_ctx *rctx; + struct kcm_ccdb *db; + time_t timer_interval; + struct tevent_timer *te; +}; + + +int kcm_get_renewal_config(struct kcm_ctx *kctx, + struct krb5_ctx **_krb5_ctx, + time_t *_renew_intv); + +errno_t kcm_renewal_setup(struct resp_ctx *rctx, struct krb5_ctx *kctx, + struct tevent_context *ev, struct kcm_ccdb *db, + time_t renew_intv); + +#endif /* __KCM_RENEW_H__ */ diff --git a/src/responder/kcm/kcmsrv_ccache.c b/src/responder/kcm/kcmsrv_ccache.c new file mode 100644 index 0000000..6e4ea64 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache.c @@ -0,0 +1,1724 @@ +/* + SSSD + + KCM Server - the KCM ccache operations + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include "util/crypto/sss_crypto.h" +#include "util/util.h" +#include "util/sss_krb5.h" +#include "src/providers/krb5/krb5_ccache.h" +#include "responder/kcm/kcm_renew.h" +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcmsrv_ccache_be.h" + + +static struct kcm_cred *kcm_cred_dup(TALLOC_CTX *mem_ctx, + struct kcm_cred *crd); + +static int kcm_cc_destructor(struct kcm_ccache *cc) +{ + if (cc == NULL) { + return 0; + } + + if (cc->client != NULL) { + krb5_free_principal(NULL, cc->client); + } + return 0; +} + +errno_t kcm_cc_new(TALLOC_CTX *mem_ctx, + krb5_context k5c, + struct cli_creds *owner, + const char *name, + krb5_principal princ, + struct kcm_ccache **_cc) +{ + struct kcm_ccache *cc = NULL; + krb5_error_code kret; + errno_t ret; + + cc = talloc_zero(mem_ctx, struct kcm_ccache); + if (cc == NULL) { + return ENOMEM; + } + + ret = kcm_check_name(name, owner); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Name %s is malformed\n", name); + goto done; + } + + cc->name = talloc_strdup(cc, name); + if (cc->name == NULL) { + ret = ENOMEM; + goto done; + } + + uuid_generate(cc->uuid); + + if (princ) { + kret = krb5_copy_principal(k5c, princ, &cc->client); + if (kret != 0) { + const char *err_msg = sss_krb5_get_error_message(k5c, kret); + DEBUG(SSSDBG_OP_FAILURE, + "krb5_copy_principal failed: [%d][%s]\n", kret, err_msg); + sss_krb5_free_error_message(k5c, err_msg); + ret = ERR_INTERNAL; + goto done; + } + } + + cc->owner.uid = cli_creds_get_uid(owner); + cc->owner.gid = cli_creds_get_gid(owner); + cc->kdc_offset = 0; + + talloc_set_destructor(cc, kcm_cc_destructor); + *_cc = cc; + ret = EOK; +done: + if (ret != EOK) { + talloc_free(cc); + } + return ret; +} + +struct kcm_ccache *kcm_cc_dup(TALLOC_CTX *mem_ctx, + const struct kcm_ccache *cc) +{ + struct kcm_ccache *dup; + struct kcm_cred *crd_dup; + struct kcm_cred *crd; + + dup = talloc_zero(mem_ctx, struct kcm_ccache); + if (dup == NULL) { + return NULL; + } + memcpy(dup, cc, sizeof(struct kcm_ccache)); + + dup->creds = NULL; + DLIST_FOR_EACH(crd, cc->creds) { + crd_dup = kcm_cred_dup(dup, crd); + if (crd_dup == NULL) { + talloc_free(dup); + return NULL; + } + + DLIST_ADD(dup->creds, crd_dup); + } + + return dup; +} + +const char *kcm_cc_get_name(struct kcm_ccache *cc) +{ + return cc ? cc->name : NULL; +} + +errno_t kcm_cc_get_uuid(struct kcm_ccache *cc, uuid_t _uuid) +{ + if (cc == NULL) { + return EINVAL; + } + uuid_copy(_uuid, cc->uuid); + return EOK; +} + +krb5_principal kcm_cc_get_client_principal(struct kcm_ccache *cc) +{ + return cc ? cc->client : NULL; +} + +bool kcm_cc_access(struct kcm_ccache *cc, + struct cli_creds *client) +{ + bool ok; + uid_t uid = cli_creds_get_uid(client); + gid_t gid = cli_creds_get_gid(client); + + if (cc == NULL) { + return false; + } + + if (uid == 0 && gid == 0) { + /* root can access any ccache */ + return true; + } + + ok = ((cc->owner.uid == uid) && (cc->owner.gid == gid)); + if (!ok) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Client %"SPRIuid":%"SPRIgid" has no access to ccache %s\n", + cli_creds_get_uid(client), + cli_creds_get_gid(client), + cc->name); + } + return ok; +} + +int32_t kcm_cc_get_offset(struct kcm_ccache *cc) +{ + return cc ? cc->kdc_offset : INT32_MAX; +} + +errno_t kcm_cc_store_cred_blob(struct kcm_ccache *cc, + struct sss_iobuf *cred_blob) +{ + struct kcm_cred *kcreds; + uuid_t uuid; + errno_t ret; + + if (cc == NULL || cred_blob == NULL) { + return EINVAL; + } + + uuid_generate(uuid); + kcreds = kcm_cred_new(cc, uuid, cred_blob); + if (kcreds == NULL) { + return ENOMEM; + } + + ret = kcm_cc_store_creds(cc, kcreds); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +struct kcm_cred *kcm_cc_get_cred(struct kcm_ccache *cc) +{ + if (cc == NULL) { + return NULL; + } + + return cc->creds; +} + +struct kcm_cred *kcm_cc_next_cred(struct kcm_cred *crd) +{ + if (crd == NULL) { + return NULL; + } + + return crd->next; +} + +struct kcm_cred *kcm_cred_new(TALLOC_CTX *mem_ctx, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + struct kcm_cred *kcreds; + + kcreds = talloc_zero(mem_ctx, struct kcm_cred); + if (kcreds == NULL) { + return NULL; + } + uuid_copy(kcreds->uuid, uuid); + kcreds->cred_blob = talloc_steal(kcreds, cred_blob); + return kcreds; +} + +static struct kcm_cred *kcm_cred_dup(TALLOC_CTX *mem_ctx, + struct kcm_cred *crd) +{ + struct kcm_cred *dup; + + dup = talloc_zero(mem_ctx, struct kcm_cred); + if (dup == NULL) { + return NULL; + } + + uuid_copy(dup->uuid, crd->uuid); + dup->cred_blob = crd->cred_blob; + + return dup; +} + +#ifdef HAVE_KRB5_UNMARSHAL_CREDENTIALS +static krb5_creds *kcm_cred_to_krb5(krb5_context kctx, struct kcm_cred *kcm_crd) +{ + krb5_error_code kerr; + krb5_creds *kcrd; + krb5_data data; + + get_krb5_data_from_cred(kcm_crd->cred_blob, &data); + + kerr = krb5_unmarshal_credentials(kctx, &data, &kcrd); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unmarshal credentials\n"); + return NULL; + } + + return kcrd; +} +#endif + +static errno_t +kcm_cc_remove_duplicates(struct kcm_ccache *cc, + struct kcm_cred *kcm_crd) +{ +#ifdef HAVE_KRB5_UNMARSHAL_CREDENTIALS + struct kcm_cred *p, *q; + krb5_error_code kerr; + krb5_context kctx; + krb5_creds *kcrd_cc; + krb5_creds *kcrd; + errno_t ret; + bool bret; + + kerr = krb5_init_context(&kctx); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n"); + return EIO; + } + + kcrd = kcm_cred_to_krb5(kctx, kcm_crd); + if (kcrd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n"); + ret = ERR_INTERNAL; + goto done; + } + + DLIST_FOR_EACH_SAFE(p, q, cc->creds) { + kcrd_cc = kcm_cred_to_krb5(kctx, p); + if (kcrd_cc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n"); + ret = ERR_INTERNAL; + goto done; + } + + bret = sss_krb5_creds_compare(kctx, kcrd, kcrd_cc); + krb5_free_creds(kctx, kcrd_cc); + if (!bret) { + continue; + } + + /* This cred is the same ticket. We will replace it with the new one. */ + DLIST_REMOVE(cc->creds, p); + } + + ret = EOK; + +done: + krb5_free_creds(kctx, kcrd); + krb5_free_context(kctx); + + return ret; +#else + return EOK; +#endif +} + +/* Add a cred to ccache */ +errno_t kcm_cc_store_creds(struct kcm_ccache *cc, + struct kcm_cred *crd) +{ + errno_t ret; + + ret = kcm_cc_remove_duplicates(cc, crd); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to remove duplicate credentials " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + DLIST_ADD(cc->creds, crd); + talloc_steal(cc, crd); + return EOK; +} + +errno_t kcm_cc_set_header(struct kcm_ccache *cc, + const char *sec_key, + struct cli_creds *client) +{ + errno_t ret; + + ret = sec_key_parse(cc, sec_key, &cc->name, cc->uuid); + if (ret != EOK) { + return ret; + } + + /* We rely on sssd-secrets only searching the user's subtree so we + * set the ownership to the client + */ + cc->owner.uid = cli_creds_get_uid(client); + cc->owner.gid = cli_creds_get_gid(client); + + return EOK; +} + +#ifdef HAVE_KCM_RENEWAL +static int kcm_cc_unmarshal_destructor(krb5_creds **creds) +{ + krb5_error_code kerr; + krb5_context krb_ctx; + int i; + + kerr = krb5_init_context(&krb_ctx); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n"); + return 1; + } + + for (i = 0; creds[i] != NULL; i++) { + krb5_free_creds(krb_ctx, creds[i]); + } + + krb5_free_context(krb_ctx); + + return 0; +} +#endif + +krb5_creds **kcm_cc_unmarshal(TALLOC_CTX *mem_ctx, + krb5_context krb_context, + struct kcm_ccache *cc) +{ +#ifndef HAVE_KCM_RENEWAL + return NULL; +#else + TALLOC_CTX *tmp_ctx; + struct kcm_cred *cred; + krb5_creds **cred_list; + int i = 0; + int count = 0; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + goto done; + } + + for (cred = kcm_cc_get_cred(cc); cred != NULL; cred = kcm_cc_next_cred(cred)) { + count++; + } + + cred_list = talloc_array(tmp_ctx, krb5_creds *, count + 1); + + for (cred = kcm_cc_get_cred(cc); cred != NULL; cred = kcm_cc_next_cred(cred), i++) { + cred_list[i] = kcm_cred_to_krb5(krb_context, cred); + if (cred_list[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n"); + goto done; + } + } + + cred_list[count] = NULL; + talloc_set_destructor(cred_list, kcm_cc_unmarshal_destructor); + + talloc_steal(mem_ctx, cred_list); + + return cred_list; +done: + talloc_free(tmp_ctx); + return NULL; +#endif +} + +errno_t kcm_cred_get_uuid(struct kcm_cred *crd, uuid_t _uuid) +{ + if (crd == NULL) { + return EINVAL; + } + uuid_copy(_uuid, crd->uuid); + return EOK; +} + +struct sss_iobuf *kcm_cred_get_creds(struct kcm_cred *crd) +{ + return crd ? crd->cred_blob : NULL; +} + +errno_t kcm_ccdb_renew_tgts(TALLOC_CTX *mem_ctx, + struct krb5_ctx *krb5_ctx, + struct tevent_context *ev, + struct kcm_ccdb *ccdb, + struct kcm_ccache ***_cc_list) +{ + struct kcm_ccache **cc; + errno_t ret; + + if (krb5_ctx == NULL || ev == NULL || ccdb == NULL) { + ret = EINVAL; + return ret; + } + + if (ccdb->ops->list_all_cc == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_TRACE_INTERNAL, "List all cc function not available\n"); + goto done; + } + + ret = ccdb->ops->list_all_cc(mem_ctx, krb5_ctx, ev, ccdb, &cc); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failure to retrieve list of ccaches" + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } else if (ret == ENOENT) { + goto done; + } + + *_cc_list = talloc_steal(mem_ctx, cc); + + ret = EOK; +done: + + return ret; +} + +struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + const char *confdb_service_path, + enum kcm_ccdb_be cc_be) +{ + errno_t ret; + struct kcm_ccdb *ccdb = NULL; + + if (ev == NULL) { + return NULL; + } + + ccdb = talloc_zero(mem_ctx, struct kcm_ccdb); + if (ccdb == NULL) { + return NULL; + } + ccdb->ev = ev; + + switch (cc_be) { + case CCDB_BE_MEMORY: + DEBUG(SSSDBG_FUNC_DATA, "KCM back end: memory\n"); + ccdb->ops = &ccdb_mem_ops; + break; + case CCDB_BE_SECDB: + DEBUG(SSSDBG_FUNC_DATA, "KCM back end: libsss_secrets\n"); + ccdb->ops = &ccdb_secdb_ops; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown ccache database backend\n"); + break; + } + + if (ccdb->ops == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Ccache database not initialized\n"); + talloc_free(ccdb); + return NULL; + } + + ret = ccdb->ops->init(ccdb, cdb, confdb_service_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot initialize ccache database\n"); + talloc_free(ccdb); + return NULL; + } + + return ccdb; +} + +struct kcm_ccdb_nextid_state { + char *next_cc; + struct kcm_ccdb *db; + struct cli_creds *client; +}; + +static void kcm_ccdb_nextid_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_nextid_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_nextid_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->nextid_send(state, ev, state->db, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_nextid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_nextid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_nextid_state *state = tevent_req_data(req, + struct kcm_ccdb_nextid_state); + errno_t ret; + unsigned int nextid; + + ret = state->db->ops->nextid_recv(subreq, &nextid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to generate next UID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->next_cc = talloc_asprintf(state, "%"SPRIuid":%u", + cli_creds_get_uid(state->client), + nextid); + if (state->next_cc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed\n"); + tevent_req_error(req, ENOMEM); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "generated %s\n", state->next_cc); + tevent_req_done(req); +} + +errno_t kcm_ccdb_nextid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_next_cc) +{ + struct kcm_ccdb_nextid_state *state = tevent_req_data(req, + struct kcm_ccdb_nextid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_next_cc = talloc_steal(mem_ctx, state->next_cc); + return EOK; +} + +struct kcm_ccdb_list_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + uuid_t *uuid_list; +}; + +static void kcm_ccdb_list_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_list_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_list_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->list_send(state, + ev, + state->db, + client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_list_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_list_state *state = tevent_req_data(req, + struct kcm_ccdb_list_state); + errno_t ret; + + ret = state->db->ops->list_recv(subreq, state, &state->uuid_list); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to list all ccaches [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list) +{ + struct kcm_ccdb_list_state *state = tevent_req_data(req, + struct kcm_ccdb_list_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_uuid_list = talloc_steal(mem_ctx, state->uuid_list); + return EOK; +} + +struct kcm_ccdb_get_default_state { + struct kcm_ccdb *db; + uuid_t uuid; +}; + +static void kcm_ccdb_get_default_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_get_default_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_get_default_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->get_default_send(state, ev, db, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_get_default_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_get_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_get_default_state *state = tevent_req_data(req, + struct kcm_ccdb_get_default_state); + errno_t ret; + + ret = state->db->ops->get_default_recv(subreq, state->uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_get_default_recv(struct tevent_req *req, + uuid_t *uuid) +{ + struct kcm_ccdb_get_default_state *state = tevent_req_data(req, + struct kcm_ccdb_get_default_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (uuid != NULL) { + /* The caller might supply a NULL dfl to just check if there is + * some default ccache + */ + uuid_copy(*uuid, state->uuid); + } + + return EOK; +} + +struct kcm_ccdb_set_default_state { + struct tevent_context *ev; + struct kcm_ccdb *db; + struct cli_creds *client; + uuid_t uuid; +}; + +static void kcm_ccdb_set_default_uuid_resolved(struct tevent_req *subreq); +static void kcm_ccdb_set_default_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_set_default_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_set_default_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->ev = ev; + state->client = client; + uuid_copy(state->uuid, uuid); + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + if (uuid_is_null(uuid)) { + /* NULL UUID means to just reset the default to 'no default' */ + subreq = state->db->ops->set_default_send(state, + state->ev, + state->db, + state->client, + state->uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_set_default_done, req); + } else { + /* Otherwise we need to check if the client can access the UUID + * about to be set as default + */ + subreq = db->ops->getbyuuid_send(state, ev, db, client, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_set_default_uuid_resolved, req); + } + + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_set_default_uuid_resolved(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_set_default_state *state = tevent_req_data(req, + struct kcm_ccdb_set_default_state); + errno_t ret; + bool ok; + struct kcm_ccache *cc; + + ret = state->db->ops->getbyuuid_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (cc == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "No cache found by UUID\n"); + tevent_req_error(req, ERR_KCM_CC_END); + return; + } + + ok = kcm_cc_access(cc, state->client); + if (!ok) { + tevent_req_error(req, EACCES); + return; + } + + subreq = state->db->ops->set_default_send(state, + state->ev, + state->db, + state->client, + state->uuid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_ccdb_set_default_done, req); +} + +static void kcm_ccdb_set_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_set_default_state *state = tevent_req_data(req, + struct kcm_ccdb_set_default_state); + errno_t ret; + + ret = state->db->ops->set_default_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_set_default_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct kcm_ccdb_getbyname_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + struct kcm_ccache *cc; +}; + +static void kcm_ccdb_getbyname_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_getbyname_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_getbyname_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL || name == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->getbyname_send(state, ev, db, client, name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_getbyname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_getbyname_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyname_state); + errno_t ret; + bool ok; + + ret = state->db->ops->getbyname_recv(subreq, state, &state->cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get cache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->cc == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "No cache found by name\n"); + tevent_req_done(req); + return; + } + + ok = kcm_cc_access(state->cc, state->client); + if (!ok) { + tevent_req_error(req, EACCES); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct kcm_ccdb_getbyname_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyname_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct kcm_ccdb_getbyuuid_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + struct kcm_ccache *cc; +}; + +static void kcm_ccdb_getbyuuid_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_getbyuuid_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_getbyuuid_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->getbyuuid_send(state, ev, db, client, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_getbyuuid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_getbyuuid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_getbyuuid_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyuuid_state); + errno_t ret; + bool ok; + + ret = state->db->ops->getbyuuid_recv(subreq, state, &state->cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->cc == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "No cache found by UUID\n"); + tevent_req_done(req); + return; + } + + ok = kcm_cc_access(state->cc, state->client); + if (!ok) { + tevent_req_error(req, EACCES); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct kcm_ccdb_getbyuuid_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyuuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct kcm_ccdb_name_by_uuid_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + const char *name; +}; + +static void kcm_ccdb_name_by_uuid_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_name_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_name_by_uuid_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, + &state, + struct kcm_ccdb_name_by_uuid_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL || uuid_is_null(uuid)) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->name_by_uuid_send(state, ev, db, client, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_name_by_uuid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_name_by_uuid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_name_by_uuid_state *state = tevent_req_data(req, + struct kcm_ccdb_name_by_uuid_state); + errno_t ret; + + ret = state->db->ops->name_by_uuid_recv(subreq, state, &state->name); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name) +{ + struct kcm_ccdb_name_by_uuid_state *state = tevent_req_data(req, + struct kcm_ccdb_name_by_uuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_name = talloc_steal(mem_ctx, state->name); + return EOK; +} + +struct kcm_ccdb_uuid_by_name_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + uuid_t uuid; +}; + +static void kcm_ccdb_uuid_by_name_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_uuid_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_uuid_by_name_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, + &state, + struct kcm_ccdb_uuid_by_name_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL || name == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->uuid_by_name_send(state, ev, db, client, name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_uuid_by_name_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_uuid_by_name_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_uuid_by_name_state *state = tevent_req_data(req, + struct kcm_ccdb_uuid_by_name_state); + errno_t ret; + + ret = state->db->ops->uuid_by_name_recv(subreq, state, state->uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid) +{ + struct kcm_ccdb_uuid_by_name_state *state = tevent_req_data(req, + struct kcm_ccdb_uuid_by_name_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + uuid_copy(_uuid, state->uuid); + return EOK; +} + +struct kcm_ccdb_create_cc_state { + struct kcm_ccdb *db; +}; + +static void kcm_ccdb_create_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_create_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_create_cc_state *state = NULL; + errno_t ret; + bool ok; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_create_cc_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL || cc == NULL) { + ret = EINVAL; + goto immediate; + } + + ok = kcm_cc_access(cc, client); + if (!ok) { + ret = EACCES; + goto immediate; + } + + subreq = state->db->ops->create_send(state, + ev, + state->db, + client, + cc); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_create_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_create_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_create_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_create_cc_state); + errno_t ret; + + ret = state->db->ops->create_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_create_cc_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static void kcm_mod_ctx_clear(struct kcm_mod_ctx *mod_ctx) +{ + if (mod_ctx == NULL) { + return; + } + + mod_ctx->kdc_offset = INT32_MAX; + if (mod_ctx->client != NULL) { + krb5_free_principal(NULL, mod_ctx->client); + mod_ctx->client = NULL; + } + + return; +} + +struct kcm_mod_ctx *kcm_mod_ctx_new(TALLOC_CTX *mem_ctx) +{ + struct kcm_mod_ctx *mod_ctx; + + mod_ctx = talloc_zero(mem_ctx, struct kcm_mod_ctx); + if (mod_ctx == NULL) { + return NULL; + } + + kcm_mod_ctx_clear(mod_ctx); + return mod_ctx; +} + +errno_t kcm_mod_cc(struct kcm_ccache *cc, struct kcm_mod_ctx *mod_ctx) +{ + if (cc == NULL || mod_ctx == NULL) { + return EINVAL; + } + + if (mod_ctx->kdc_offset != INT32_MAX) { + cc->kdc_offset = mod_ctx->kdc_offset; + } + + if (mod_ctx->client != NULL) { + krb5_error_code kret; + + kret = krb5_copy_principal(NULL, mod_ctx->client, &cc->client); + if (kret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_copy_principal failed: %d\n", kret); + return ERR_INTERNAL; + } + } + + return EOK; +} + +struct kcm_ccdb_mod_cc_state { + struct kcm_ccdb *db; +}; + +static void kcm_ccdb_mod_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_mod_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_mod_cc_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_mod_cc_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL || mod_cc == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->mod_send(state, + ev, + state->db, + client, + uuid, + mod_cc); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_mod_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_mod_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_mod_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_mod_cc_state); + errno_t ret; + + ret = state->db->ops->mod_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_mod_cc_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct kcm_ccdb_store_cred_blob_state { + struct kcm_ccdb *db; +}; + +static void kcm_ccdb_store_cred_blob_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_store_cred_blob_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_store_cred_blob_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_store_cred_blob_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL || cred_blob == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->store_cred_send(state, + ev, + state->db, + client, + uuid, + cred_blob); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_store_cred_blob_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_store_cred_blob_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_store_cred_blob_state *state = tevent_req_data(req, + struct kcm_ccdb_store_cred_blob_state); + errno_t ret; + + ret = state->db->ops->store_cred_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_store_cred_blob_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct kcm_ccdb_delete_cc_state { + struct tevent_context *ev; + struct kcm_ccdb *db; + struct cli_creds *client; + uuid_t uuid; +}; + +static void kcm_ccdb_delete_done(struct tevent_req *subreq); +static void kcm_ccdb_delete_get_default_done(struct tevent_req *subreq); +static void kcm_ccdb_delete_default_reset_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_delete_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_delete_cc_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_delete_cc_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->ev = ev; + state->client = client; + uuid_copy(state->uuid, uuid); + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->delete_send(state, + state->ev, + state->db, + state->client, + state->uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_delete_done, req); + + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_delete_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_delete_cc_state); + errno_t ret; + + ret = state->db->ops->delete_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to delete ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* The delete operation must also check if the deleted ccache was + * the default and reset the default if it was + */ + subreq = state->db->ops->get_default_send(state, + state->ev, + state->db, + state->client); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_ccdb_delete_get_default_done, req); +} + +static void kcm_ccdb_delete_get_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_delete_cc_state); + errno_t ret; + uuid_t dfl_uuid; + uuid_t null_uuid; + + ret = state->db->ops->get_default_recv(subreq, dfl_uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (uuid_compare(dfl_uuid, state->uuid) != 0) { + /* The ccache about to be deleted was not the default, quit */ + tevent_req_done(req); + return; + } + + /* If we deleted the default ccache, reset the default ccache to 'none' */ + uuid_clear(null_uuid); + + subreq = state->db->ops->set_default_send(state, + state->ev, + state->db, + state->client, + null_uuid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_ccdb_delete_default_reset_done, req); +} + +static void kcm_ccdb_delete_default_reset_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_delete_cc_state); + errno_t ret; + + ret = state->db->ops->set_default_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to NULL the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_delete_cc_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +void kcm_debug_uuid(uuid_t uuid) +{ + char dbgbuf[UUID_STR_SIZE]; + + if (!(debug_level & SSSDBG_TRACE_ALL) || uuid == NULL) { + return; + } + + uuid_unparse(uuid, dbgbuf); + DEBUG(SSSDBG_TRACE_ALL, "UUID: %s\n", dbgbuf); +} + +errno_t kcm_check_name(const char *name, struct cli_creds *client) +{ + char prefix[64]; + size_t prefix_len; + + prefix_len = snprintf(prefix, sizeof(prefix), + "%"SPRIuid, cli_creds_get_uid(client)); + + if (strncmp(name, prefix, prefix_len) != 0) { + return ERR_KCM_WRONG_CCNAME_FORMAT; + } + return EOK; +} diff --git a/src/responder/kcm/kcmsrv_ccache.h b/src/responder/kcm/kcmsrv_ccache.h new file mode 100644 index 0000000..1061ee2 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache.h @@ -0,0 +1,380 @@ +/* + SSSD + + KCM Server - the KCM ccache operations + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef _KCMSRV_CCACHE_H_ +#define _KCMSRV_CCACHE_H_ + +#include "config.h" + +#include <krb5/krb5.h> +#include <uuid/uuid.h> + +#include "util/util.h" +#include "util/sss_iobuf.h" +#include "util/util_creds.h" +#include "providers/krb5/krb5_common.h" +#include "responder/kcm/kcmsrv_pvt.h" + +#define UUID_BYTES 16 +#define UUID_STR_SIZE 37 + +/* Just to keep the name of the ccache readable */ +#define MAX_CC_NUM 99999 + +/* + * Credentials are opaque to the KCM server + * + * Each ccache has a unique UUID. + */ +struct kcm_cred; + +/* + * An opaque ccache type and its operations + * + * Contains zero or some KCM credentials. One credential in the cache + * is marked as the default one. The client can set and get the default + * cache (e.g. with kswitch) but one cache is always the default -- we + * fall back to the one created first. + * + * Each cache has a name and a UUID. Heimdal allows the name to be changed, + * we don't (yet, because the MIT client doesn't allow that either) + * + * Each ccache also stores a client principal. + */ +struct kcm_ccache; + +/* + * Create a new KCM ccache owned by mem_ctx on the + * memory level. + * + * When created, the ccache contains no credentials + */ +errno_t kcm_cc_new(TALLOC_CTX *mem_ctx, + krb5_context k5c, + struct cli_creds *owner, + const char *name, + krb5_principal princ, + struct kcm_ccache **_cc); + +/* + * Duplicate the ccache. Only ccache and credentials are duplicated, + * but their data are a shallow copy. + */ +struct kcm_ccache *kcm_cc_dup(TALLOC_CTX *mem_ctx, + const struct kcm_ccache *cc); + +/* + * Returns true if a client can access a ccache. + * + * Note that root can access any ccache */ +bool kcm_cc_access(struct kcm_ccache *cc, + struct cli_creds *client); + +/* + * Since the kcm_ccache structure is opaque, the kcmsrv_ccache + * layer contains a number of getsetters to read and write + * properties of the kcm_ccache structure + */ +const char *kcm_cc_get_name(struct kcm_ccache *cc); +errno_t kcm_cc_get_uuid(struct kcm_ccache *cc, uuid_t _uuid); +krb5_principal kcm_cc_get_client_principal(struct kcm_ccache *cc); +int32_t kcm_cc_get_offset(struct kcm_ccache *cc); + +/* Mainly useful for creating a cred structure from a persistent + * storage + */ +struct kcm_cred *kcm_cred_new(TALLOC_CTX *mem_ctx, + uuid_t uuid, + struct sss_iobuf *cred_blob); + +/* Add a cred to ccache */ +errno_t kcm_cc_store_creds(struct kcm_ccache *cc, + struct kcm_cred *crd); + +/* Set cc header information from sec key and client */ +errno_t kcm_cc_set_header(struct kcm_ccache *cc, + const char *sec_key, + struct cli_creds *client); + +krb5_creds **kcm_cc_unmarshal(TALLOC_CTX *mem_ctx, + krb5_context krb_context, + struct kcm_ccache *cc); + +errno_t kcm_cred_get_uuid(struct kcm_cred *crd, uuid_t uuid); + +/* + * At the moment, the credentials are stored without unmarshalling + * them, just as the clients sends the credentials. + */ +struct sss_iobuf *kcm_cred_get_creds(struct kcm_cred *crd); +errno_t kcm_cc_store_cred_blob(struct kcm_ccache *cc, + struct sss_iobuf *cred_blob); + /* + * The KCM server can call kcm_cred_get_creds to fetch the first + * credential, then iterate over the credentials with + * kcm_cc_next_cred until it returns NULL + */ +struct kcm_cred *kcm_cc_get_cred(struct kcm_ccache *cc); +struct kcm_cred *kcm_cc_next_cred(struct kcm_cred *crd); + +/* An opaque database that contains all the ccaches */ +struct kcm_ccdb; + +/* + * Initialize a ccache database of type cc_be + */ +struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + const char *confdb_service_path, + enum kcm_ccdb_be cc_be); +/* + * Prepare KCM ccache list for renewals + */ +errno_t kcm_ccdb_renew_tgts(TALLOC_CTX *mem_ctx, + struct krb5_ctx *kctx, + struct tevent_context *ev, + struct kcm_ccdb *cdb, + struct kcm_ccache ***_cc_list); + +/* + * In KCM, each ccache name is usually in the form of "UID:<num> + * + * The <num> is generated by the KCM ccache database. Use this function + * to retrieve the next number + */ +struct tevent_req *kcm_ccdb_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +errno_t kcm_ccdb_nextid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_nextid); + +/* + * List all ccaches that belong to a given client + * + * The cc_list the recv function returns is NULL-terminated. + * + * NOTE: Contrary to how Heimdal behaves, root CAN NOT list all ccaches + * of all users. This is a deliberate decision to treat root as any other + * user, except it can access a ccache of another user by name, just not + * list them. + * + * If a client has no ccaches, the function returns OK, but an empty list + * containing just the NULL sentinel. + */ +struct tevent_req *kcm_ccdb_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +errno_t kcm_ccdb_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list); + +/* + * Retrieve a ccache by name. + * + * If there is no such ccache, return EOK, but a NULL _cc pointer + */ +struct tevent_req *kcm_ccdb_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +errno_t kcm_ccdb_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +/* + * Retrieve a ccache by UUID + * + * If there is no such ccache, return EOK, but a NULL _cc pointer + */ +struct tevent_req *kcm_ccdb_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +/* + * Retrieve the default ccache. If there is no default cache, + * return EOK, but a NULL UUID. + */ +struct tevent_req *kcm_ccdb_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +errno_t kcm_ccdb_get_default_recv(struct tevent_req *req, + uuid_t *uuid); + +/* + * Translating name to UUID is often considerably faster than doing a full + * CC retrieval, hence this function and the converse. If the UUID cannot + * be found in the database, return ERR_KCM_CC_END + */ +struct tevent_req *kcm_ccdb_name_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name); + +/* + * Translating UUID to name is often considerably faster than doing a full + * CC retrieval, hence this function and the converse. If the UUID cannot + * be found in the database, return ERR_KCM_CC_END + */ +struct tevent_req *kcm_ccdb_uuid_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +errno_t kcm_ccdb_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid); + +/* + * Set the default ccache. Passing a NULL UUID is a legal operation + * that 'unsets' the default ccache. + */ +struct tevent_req *kcm_ccdb_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_set_default_recv(struct tevent_req *req); + +/* + * Add a ccache to the database. + */ +struct tevent_req *kcm_ccdb_create_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc); +errno_t kcm_ccdb_create_cc_recv(struct tevent_req *req); + +/* + * Modify cache properties in a db + */ +struct kcm_mod_ctx { + int32_t kdc_offset; + krb5_principal client; + /* More settable properties (like name, when we support renames + * will be added later + */ +}; + +struct kcm_mod_ctx *kcm_mod_ctx_new(TALLOC_CTX *mem_ctx); +errno_t kcm_mod_cc(struct kcm_ccache *cc, struct kcm_mod_ctx *mod_ctx); + +struct tevent_req *kcm_ccdb_mod_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc); +errno_t kcm_ccdb_mod_cc_recv(struct tevent_req *req); + +/* + * Store a credential in a cache + */ +struct tevent_req *kcm_ccdb_store_cred_blob_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob); +errno_t kcm_ccdb_store_cred_blob_recv(struct tevent_req *req); + +/* + * Delete a ccache from the database + */ +struct tevent_req *kcm_ccdb_delete_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_delete_cc_recv(struct tevent_req *req); + +void kcm_debug_uuid(uuid_t uuid); + +/* + * The KCM clients are not allowed (except root) to create ccaches + * with arbitrary names. Instead, we assert that the ccache name + * begins with UID where UID is the stringified representation of + * the client's UID number + */ +errno_t kcm_check_name(const char *name, struct cli_creds *client); + +/* + * ccahe marshalling to and from JSON. This is used when the ccaches + * are stored in the secrets store + */ + +/* + * The secrets store is a key-value store at heart. We store the UUID + * and the name in the key to allow easy lookups be either key + */ +bool sec_key_match_name(const char *sec_key, + const char *name); + +bool sec_key_match_uuid(const char *sec_key, + uuid_t uuid); + +errno_t sec_key_parse(TALLOC_CTX *mem_ctx, + const char *sec_key, + const char **_name, + uuid_t uuid); + +const char *sec_key_get_name(const char *sec_key); + +errno_t sec_key_get_uuid(const char *sec_key, + uuid_t uuid); + +const char *sec_key_create(TALLOC_CTX *mem_ctx, + const char *name, + uuid_t uuid); + +/* + * sec_key is a concatenation of the ccache's UUID and name + * sec_value is the binary representation of ccache. + */ +errno_t sec_kv_to_ccache_binary(TALLOC_CTX *mem_ctx, + const char *sec_key, + struct sss_iobuf *sec_value, + struct cli_creds *client, + struct kcm_ccache **_cc); + +/* Convert a kcm_ccache to its binary representation. */ +errno_t kcm_ccache_to_sec_input_binary(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + struct sss_iobuf **_payload); + +errno_t bin_to_krb_data(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + krb5_data *out); +#endif /* _KCMSRV_CCACHE_H_ */ diff --git a/src/responder/kcm/kcmsrv_ccache_be.h b/src/responder/kcm/kcmsrv_ccache_be.h new file mode 100644 index 0000000..78d314e --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_be.h @@ -0,0 +1,216 @@ +/* + SSSD + + KCM Server - the KCM ccache database interface + + This file should only be included from the ccache.c module. + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KCMSRV_CCACHE_BE_ +#define _KCMSRV_CCACHE_BE_ + +#include "config.h" + +#include <talloc.h> +#include "responder/kcm/kcmsrv_ccache.h" + +typedef errno_t +(*ccdb_init_fn)(struct kcm_ccdb *db, + struct confdb_ctx *cdb, + const char *confdb_service_path); + +typedef struct tevent_req * +(*ccdb_nextid_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +typedef errno_t +(*ccdb_nextid_recv_fn)(struct tevent_req *req, + unsigned int *_nextid); + +typedef struct tevent_req * +(*ccdb_set_default_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_set_default_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*ccdb_get_default_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +typedef errno_t +(*ccdb_get_default_recv_fn)(struct tevent_req *req, + uuid_t dfl); + + +typedef errno_t +(*ccdb_list_all_cc_fn)(TALLOC_CTX *mem_ctx, + struct krb5_ctx *kctx, + struct tevent_context *ev, + struct kcm_ccdb *cdb, + struct kcm_ccache ***_cc_list); + +typedef struct tevent_req * +(*ccdb_list_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +typedef errno_t +(*ccdb_list_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list); + +typedef struct tevent_req * +(*ccdb_getbyname_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +typedef errno_t +(*ccdb_getbyname_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +typedef struct tevent_req * +(*ccdb_getbyuuid_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_getbyuuid_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +typedef struct tevent_req * +(*ccdb_name_by_uuid_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_name_by_uuid_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name); + +typedef struct tevent_req * +(*ccdb_uuid_by_name_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +typedef errno_t +(*ccdb_uuid_by_name_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid); + +typedef struct tevent_req * +(*ccdb_create_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc); +typedef errno_t +(*ccdb_create_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*ccdb_mod_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc); +typedef errno_t +(*ccdb_mod_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*kcm_ccdb_store_cred_blob_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob); +typedef errno_t +(*kcm_ccdb_store_cred_blob_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*ccdb_delete_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_delete_recv_fn)(struct tevent_req *req); + +/* + * Each ccache back end (for example memory or secdb) must implement + * all these functions. The functions are wrapped by the kcm_ccdb + * interface that performs additional sanity checks or contains shared + * logic such as access checks but in general doesn't assume anything + * about how the operations work. + */ +struct kcm_ccdb_ops { + ccdb_init_fn init; + + ccdb_nextid_send_fn nextid_send; + ccdb_nextid_recv_fn nextid_recv; + + ccdb_set_default_send_fn set_default_send; + ccdb_set_default_recv_fn set_default_recv; + + ccdb_get_default_send_fn get_default_send; + ccdb_get_default_recv_fn get_default_recv; + + ccdb_list_all_cc_fn list_all_cc; + + ccdb_list_send_fn list_send; + ccdb_list_recv_fn list_recv; + + ccdb_getbyname_send_fn getbyname_send; + ccdb_getbyname_recv_fn getbyname_recv; + + ccdb_getbyuuid_send_fn getbyuuid_send; + ccdb_getbyuuid_recv_fn getbyuuid_recv; + + ccdb_name_by_uuid_send_fn name_by_uuid_send; + ccdb_name_by_uuid_recv_fn name_by_uuid_recv; + + ccdb_uuid_by_name_send_fn uuid_by_name_send; + ccdb_uuid_by_name_recv_fn uuid_by_name_recv; + + ccdb_create_send_fn create_send; + ccdb_create_recv_fn create_recv; + + ccdb_mod_send_fn mod_send; + ccdb_mod_recv_fn mod_recv; + + kcm_ccdb_store_cred_blob_send_fn store_cred_send; + kcm_ccdb_store_cred_blob_recv_fn store_cred_recv; + + ccdb_delete_send_fn delete_send; + ccdb_delete_recv_fn delete_recv; +}; + +extern const struct kcm_ccdb_ops ccdb_mem_ops; +extern const struct kcm_ccdb_ops ccdb_secdb_ops; + +#endif /* _KCMSRV_CCACHE_BE_ */ diff --git a/src/responder/kcm/kcmsrv_ccache_binary.c b/src/responder/kcm/kcmsrv_ccache_binary.c new file mode 100644 index 0000000..cc4191a --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_binary.c @@ -0,0 +1,308 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2020 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdio.h> +#include <talloc.h> + +#include "util/util.h" +#include "util/util_creds.h" +#include "util/crypto/sss_crypto.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" + +static errno_t krb_data_to_bin(krb5_data *data, struct sss_iobuf *buf) +{ + return sss_iobuf_write_varlen(buf, (uint8_t *)data->data, data->length); +} + +static errno_t princ_to_bin(krb5_principal princ, struct sss_iobuf *buf) +{ + errno_t ret; + + if (princ == NULL) { + return sss_iobuf_write_uint8(buf, 0); + } + + /* Mark that principal is not empty. */ + ret = sss_iobuf_write_uint8(buf, 1); + if (ret != EOK) { + return ret; + } + + ret = krb_data_to_bin(&princ->realm, buf); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_write_int32(buf, princ->type); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_write_int32(buf, princ->length); + if (ret != EOK) { + return ret; + } + + for (krb5_int32 i = 0; i < princ->length; i++) { + ret = krb_data_to_bin(&princ->data[i], buf); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +static errno_t creds_to_bin(struct kcm_cred *creds, struct sss_iobuf *buf) +{ + struct kcm_cred *crd; + uint32_t count = 0; + errno_t ret; + + DLIST_FOR_EACH(crd, creds) { + count++; + } + + ret = sss_iobuf_write_uint32(buf, count); + if (ret != EOK) { + return ret; + } + + DLIST_FOR_EACH(crd, creds) { + ret = sss_iobuf_write_len(buf, (uint8_t *)crd->uuid, sizeof(uuid_t)); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_write_iobuf(buf, crd->cred_blob); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +errno_t kcm_ccache_to_sec_input_binary(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + struct sss_iobuf **_payload) +{ + struct sss_iobuf *buf; + errno_t ret; + + buf = sss_iobuf_init_empty(mem_ctx, sizeof(krb5_principal_data), 0); + if (buf == NULL) { + return ENOMEM; + } + + ret = sss_iobuf_write_int32(buf, cc->kdc_offset); + if (ret != EOK) { + goto done; + } + + ret = princ_to_bin(cc->client, buf); + if (ret != EOK) { + goto done; + } + + ret = creds_to_bin(cc->creds, buf); + if (ret != EOK) { + goto done; + } + + *_payload = buf; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + + return ret; +} + +errno_t bin_to_krb_data(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + krb5_data *out) +{ + uint8_t *data; + size_t len; + errno_t ret; + + ret = sss_iobuf_read_varlen(mem_ctx, buf, &data, &len); + if (ret != EOK) { + return ret; + } + + out->magic = 0; + out->data = (char*)data; + out->length = len; + + return EOK; +} + +static errno_t bin_to_princ(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + krb5_principal *_princ) +{ + krb5_principal princ; + uint8_t non_empty; + krb5_int32 i; + errno_t ret; + + ret = sss_iobuf_read_uint8(buf, &non_empty); + if (ret != EOK) { + return ret; + } + + if (non_empty == 0) { + *_princ = NULL; + return EOK; + } + + princ = talloc_zero(mem_ctx, struct krb5_principal_data); + if (princ == NULL) { + return ENOMEM; + } + princ->magic = KV5M_PRINCIPAL; + + ret = bin_to_krb_data(princ, buf, &princ->realm); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_read_int32(buf, &princ->type); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_read_int32(buf, &princ->length); + if (ret != EOK) { + return ret; + } + + princ->data = talloc_array(princ, krb5_data, princ->length); + if (princ->length > 0 && princ->data == NULL) { + return ENOMEM; + } + + for (i = 0; i < princ->length; i++) { + ret = bin_to_krb_data(princ, buf, &princ->data[i]); + if (ret != EOK) { + return ret; + } + } + + *_princ = princ; + + return EOK; +} + +static errno_t bin_to_creds(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + struct kcm_cred **_creds) +{ + struct kcm_cred *creds = NULL; + struct kcm_cred *crd; + struct sss_iobuf *cred_blob; + uint32_t count; + uuid_t uuid; + errno_t ret; + + ret = sss_iobuf_read_uint32(buf, &count); + if (ret != EOK) { + return ret; + } + + for (uint32_t i = 0; i < count; i++) { + ret = sss_iobuf_read_len(buf, sizeof(uuid_t), (uint8_t*)uuid); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_read_iobuf(NULL, buf, &cred_blob); + if (ret != EOK) { + return ret; + } + + crd = kcm_cred_new(mem_ctx, uuid, cred_blob); + if (crd == NULL) { + talloc_free(cred_blob); + return ENOMEM; + } + + DLIST_ADD(creds, crd); + } + + *_creds = creds; + + return EOK; +} + +errno_t sec_kv_to_ccache_binary(TALLOC_CTX *mem_ctx, + const char *sec_key, + struct sss_iobuf *sec_value, + struct cli_creds *client, + struct kcm_ccache **_cc) +{ + struct kcm_ccache *cc; + errno_t ret; + + cc = talloc_zero(mem_ctx, struct kcm_ccache); + if (cc == NULL) { + return ENOMEM; + } + + ret = kcm_cc_set_header(cc, sec_key, client); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot store ccache header [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_iobuf_read_int32(sec_value, &cc->kdc_offset); + if (ret != EOK) { + goto done; + } + + ret = bin_to_princ(cc, sec_value, &cc->client); + if (ret != EOK) { + goto done; + } + + ret = bin_to_creds(cc, sec_value, &cc->creds); + if (ret != EOK) { + goto done; + } + + *_cc = cc; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cc); + } + + return ret; +} diff --git a/src/responder/kcm/kcmsrv_ccache_key.c b/src/responder/kcm/kcmsrv_ccache_key.c new file mode 100644 index 0000000..59d6045 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_key.c @@ -0,0 +1,144 @@ +/* + SSSD + + Copyright (C) Red Hat, 2020 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdio.h> +#include <talloc.h> + +#include "util/util.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" + +/* + * The secrets store is a key-value store at heart. We store the UUID + * and the name in the key to allow easy lookups by either part. + */ +#define SEC_KEY_SEPARATOR '-' + +const char *sec_key_create(TALLOC_CTX *mem_ctx, + const char *name, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + uuid_unparse(uuid, uuid_str); + return talloc_asprintf(mem_ctx, + "%s%c%s", uuid_str, SEC_KEY_SEPARATOR, name); +} + +static bool sec_key_valid(const char *sec_key) +{ + if (sec_key == NULL) { + return false; + } + + if (strlen(sec_key) < UUID_STR_SIZE + 1) { + /* One char for separator (at UUID_STR_SIZE, because strlen doesn't + * include the '\0', but UUID_STR_SIZE does) and at least one for + * the name */ + DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key); + return false; + } + + if (sec_key[UUID_STR_SIZE - 1] != SEC_KEY_SEPARATOR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n"); + return false; + } + + return true; +} + +errno_t sec_key_parse(TALLOC_CTX *mem_ctx, + const char *sec_key, + const char **_name, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + if (!sec_key_valid(sec_key)) { + return EINVAL; + } + + strncpy(uuid_str, sec_key, sizeof(uuid_str) - 1); + if (sec_key[UUID_STR_SIZE - 1] != SEC_KEY_SEPARATOR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n"); + return EINVAL; + } + uuid_str[UUID_STR_SIZE - 1] = '\0'; + + *_name = talloc_strdup(mem_ctx, sec_key + UUID_STR_SIZE); + if (*_name == NULL) { + return ENOMEM; + } + uuid_parse(uuid_str, uuid); + + return EOK; +} + +errno_t sec_key_get_uuid(const char *sec_key, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + if (!sec_key_valid(sec_key)) { + return EINVAL; + } + + strncpy(uuid_str, sec_key, UUID_STR_SIZE - 1); + uuid_str[UUID_STR_SIZE - 1] = '\0'; + uuid_parse(uuid_str, uuid); + return EOK; +} + +const char *sec_key_get_name(const char *sec_key) +{ + if (!sec_key_valid(sec_key)) { + return NULL; + } + + return sec_key + UUID_STR_SIZE; +} + +bool sec_key_match_name(const char *sec_key, + const char *name) +{ + if (!sec_key_valid(sec_key) || name == NULL) { + return false; + } + + return strcmp(sec_key + UUID_STR_SIZE, name) == 0; +} + +bool sec_key_match_uuid(const char *sec_key, + uuid_t uuid) +{ + errno_t ret; + uuid_t key_uuid; + + /* Clear uuid value to avoid cppcheck warning. */ + uuid_clear(key_uuid); + + ret = sec_key_get_uuid(sec_key, key_uuid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot convert key to UUID\n"); + return false; + } + + return uuid_compare(key_uuid, uuid) == 0; +} diff --git a/src/responder/kcm/kcmsrv_ccache_mem.c b/src/responder/kcm/kcmsrv_ccache_mem.c new file mode 100644 index 0000000..0e3a7b2 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_mem.c @@ -0,0 +1,826 @@ +/* + SSSD + + KCM Server - ccache in-memory storage + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <talloc.h> +#include <stdio.h> + +#include "util/util.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcmsrv_ccache_be.h" + +struct ccdb_mem; + +/* + * The KCM memory database is just a double-linked list of kcm_ccache structures + */ +struct ccache_mem_wrap { + struct kcm_ccache *cc; + bool is_default; + + struct ccache_mem_wrap *next; + struct ccache_mem_wrap *prev; + + struct ccdb_mem *mem_be; +}; + +struct ccdb_mem { + /* Both ccaches and the next-id are kept in memory */ + struct ccache_mem_wrap *head; + unsigned int nextid; +}; + +static struct ccache_mem_wrap *memdb_get_by_uuid(struct ccdb_mem *memdb, + struct cli_creds *client, + uuid_t uuid) +{ + uid_t uid; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccache_mem_wrap *out = NULL; + + uid = cli_creds_get_uid(client); + + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid) { + if (uuid_compare(uuid, ccwrap->cc->uuid) == 0) { + out = ccwrap; + break; + } + } + } + + return out; +} + +static struct ccache_mem_wrap *memdb_get_by_name(struct ccdb_mem *memdb, + struct cli_creds *client, + const char *name) +{ + uid_t uid; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccache_mem_wrap *out = NULL; + + uid = cli_creds_get_uid(client); + + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid) { + if (strcmp(ccwrap->cc->name, name) == 0) { + out = ccwrap; + break; + } + } + } + + return out; +} + +/* Since with the in-memory database, the database operations are just + * fake-async wrappers around otherwise sync operations, we don't often + * need any state, so we use this empty structure instead + */ +struct ccdb_mem_dummy_state { +}; + +static int ccwrap_destructor(void *ptr) +{ + struct ccache_mem_wrap *ccwrap = talloc_get_type(ptr, struct ccache_mem_wrap); + + if (ccwrap == NULL) { + return 0; + } + + if (ccwrap->cc != NULL) { + if (ccwrap->cc->creds) { + sss_erase_mem_securely( + sss_iobuf_get_data(ccwrap->cc->creds->cred_blob), + sss_iobuf_get_size(ccwrap->cc->creds->cred_blob)); + } + } + + + DLIST_REMOVE(ccwrap->mem_be->head, ccwrap); + + return 0; +} + +static errno_t ccdb_mem_init(struct kcm_ccdb *db, + struct confdb_ctx *cdb, + const char *confdb_service_path) +{ + struct ccdb_mem *memdb = NULL; + + memdb = talloc_zero(db, struct ccdb_mem); + if (memdb == NULL) { + return ENOMEM; + } + db->db_handle = memdb; + + return EOK; +} + +struct ccdb_mem_nextid_state { + unsigned int nextid; +}; + +static struct tevent_req *ccdb_mem_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_nextid_state *state = NULL; + struct ccdb_mem *memdb = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_nextid_state); + if (req == NULL) { + return NULL; + } + + memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + if (memdb == NULL) { + ret = EIO; + goto immediate; + } + + state->nextid = memdb->nextid++; + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_nextid_recv(struct tevent_req *req, + unsigned int *_nextid) +{ + struct ccdb_mem_nextid_state *state = tevent_req_data(req, + struct ccdb_mem_nextid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_nextid = state->nextid; + return EOK; +} + +struct ccdb_mem_list_state { + uuid_t *uuid_list; +}; + +static struct tevent_req *ccdb_mem_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem_list_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + size_t num_ccaches = 0; + size_t cc_index = 0; + errno_t ret; + uid_t uid; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_list_state); + if (req == NULL) { + return NULL; + } + + uid = cli_creds_get_uid(client); + + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc->owner.uid == uid) { + num_ccaches++; + } + } + + state->uuid_list = talloc_zero_array(state, uuid_t, num_ccaches+1); + if (state->uuid_list == NULL) { + ret = ENOMEM; + goto immediate; + } + + cc_index = 0; + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc->owner.uid == uid) { + uuid_copy(state->uuid_list[cc_index], ccwrap->cc->uuid); + cc_index++; + } + } + uuid_clear(state->uuid_list[num_ccaches]); + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list) +{ + struct ccdb_mem_list_state *state = tevent_req_data(req, + struct ccdb_mem_list_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_uuid_list = talloc_steal(mem_ctx, state->uuid_list); + return EOK; +} + +static struct tevent_req *ccdb_mem_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + uid_t uid = cli_creds_get_uid(client); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + /* Reset all ccache defaults first */ + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid) { + ccwrap->is_default = false; + } + } + + /* Then set the default for the right ccache. This also allows to + * pass a null uuid to just reset the old ccache (for example after + * deleting the default + */ + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap != NULL) { + ccwrap->is_default = true; + } + + tevent_req_done(req); + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_set_default_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ccdb_mem_get_default_state { + uuid_t dfl_uuid; +}; + +static struct tevent_req *ccdb_mem_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_get_default_state *state = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + uid_t uid = cli_creds_get_uid(client); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_get_default_state); + if (req == NULL) { + return NULL; + } + + + /* Reset all ccache defaults first */ + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid && ccwrap->is_default == true) { + break; + } + } + + if (ccwrap == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "No ccache marked as default, returning null ccache\n"); + uuid_clear(state->dfl_uuid); + } else { + uuid_copy(state->dfl_uuid, ccwrap->cc->uuid); + } + + tevent_req_done(req); + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_get_default_recv(struct tevent_req *req, + uuid_t dfl) +{ + struct ccdb_mem_get_default_state *state = tevent_req_data(req, + struct ccdb_mem_get_default_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + uuid_copy(dfl, state->dfl_uuid); + return EOK; +} + +struct ccdb_mem_getbyuuid_state { + struct kcm_ccache *cc; +}; + +static struct tevent_req *ccdb_mem_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_getbyuuid_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_getbyuuid_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap != NULL) { + /* In order to provide a consistent interface, we need to let the caller + * of getbyXXX own the ccache, therefore the memory back end returns a shallow + * copy of the ccache + */ + state->cc = kcm_cc_dup(state, ccwrap->cc); + if (state->cc == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_mem_getbyuuid_state *state = tevent_req_data(req, + struct ccdb_mem_getbyuuid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct ccdb_mem_getbyname_state { + struct kcm_ccache *cc; +}; + +static struct tevent_req *ccdb_mem_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_getbyname_state *state = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_getbyname_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_name(memdb, client, name); + if (ccwrap != NULL) { + /* In order to provide a consistent interface, we need to let the caller + * of getbyXXX own the ccache, therefore the memory back end returns a shallow + * copy of the ccache + */ + state->cc = kcm_cc_dup(state, ccwrap->cc); + if (state->cc == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_mem_getbyname_state *state = tevent_req_data(req, + struct ccdb_mem_getbyname_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct ccdb_mem_name_by_uuid_state { + const char *name; +}; + +struct tevent_req *ccdb_mem_name_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_name_by_uuid_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_name_by_uuid_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + ret = ERR_KCM_CC_END; + goto immediate; + } + + state->name = talloc_strdup(state, ccwrap->cc->name); + if (state->name == NULL) { + ret = ENOMEM; + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +errno_t ccdb_mem_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name) +{ + struct ccdb_mem_name_by_uuid_state *state = tevent_req_data(req, + struct ccdb_mem_name_by_uuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_name = talloc_steal(mem_ctx, state->name); + return EOK; +} + +struct ccdb_mem_uuid_by_name_state { + uuid_t uuid; +}; + +struct tevent_req *ccdb_mem_uuid_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_uuid_by_name_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_uuid_by_name_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_name(memdb, client, name); + if (ccwrap == NULL) { + ret = ERR_NO_CREDS; + goto immediate; + } + + uuid_copy(state->uuid, ccwrap->cc->uuid); + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +errno_t ccdb_mem_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid) +{ + struct ccdb_mem_uuid_by_name_state *state = tevent_req_data(req, + struct ccdb_mem_uuid_by_name_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + uuid_copy(_uuid, state->uuid); + return EOK; +} + +static struct tevent_req *ccdb_mem_create_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccache_mem_wrap *ccwrap; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + ccwrap = talloc_zero(memdb, struct ccache_mem_wrap); + if (ccwrap == NULL) { + ret = ENOMEM; + goto immediate; + } + ccwrap->cc = cc; + ccwrap->mem_be = memdb; + talloc_steal(ccwrap, cc); + + DLIST_ADD(memdb->head, ccwrap); + talloc_set_destructor((TALLOC_CTX *) ccwrap, ccwrap_destructor); + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_create_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_mem_mod_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + /* UUID is immutable, so search by that */ + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + ret = ERR_KCM_CC_END; + goto immediate; + } + + ret = kcm_mod_cc(ccwrap->cc, mod_cc); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot modify ccache [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_mod_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_mem_store_cred_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + ret = ERR_KCM_CC_END; + goto immediate; + } + + ret = kcm_cc_store_cred_blob(ccwrap->cc, cred_blob); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store credentials to ccache [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_store_cred_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_mem_delete_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccache_mem_wrap *ccwrap; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "BUG: Attempting to free unknown ccache\n"); + ret = ERR_KCM_CC_END; + goto immediate; + } + + ret = EOK; + /* Destructor takes care of everything */ + talloc_free(ccwrap); +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_delete_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +const struct kcm_ccdb_ops ccdb_mem_ops = { + .init = ccdb_mem_init, + + .nextid_send = ccdb_mem_nextid_send, + .nextid_recv = ccdb_mem_nextid_recv, + + .set_default_send = ccdb_mem_set_default_send, + .set_default_recv = ccdb_mem_set_default_recv, + + .get_default_send = ccdb_mem_get_default_send, + .get_default_recv = ccdb_mem_get_default_recv, + + .list_send = ccdb_mem_list_send, + .list_recv = ccdb_mem_list_recv, + + .getbyname_send = ccdb_mem_getbyname_send, + .getbyname_recv = ccdb_mem_getbyname_recv, + + .getbyuuid_send = ccdb_mem_getbyuuid_send, + .getbyuuid_recv = ccdb_mem_getbyuuid_recv, + + .name_by_uuid_send = ccdb_mem_name_by_uuid_send, + .name_by_uuid_recv = ccdb_mem_name_by_uuid_recv, + + .uuid_by_name_send = ccdb_mem_uuid_by_name_send, + .uuid_by_name_recv = ccdb_mem_uuid_by_name_recv, + + .create_send = ccdb_mem_create_send, + .create_recv = ccdb_mem_create_recv, + + .mod_send = ccdb_mem_mod_send, + .mod_recv = ccdb_mem_mod_recv, + + .store_cred_send = ccdb_mem_store_cred_send, + .store_cred_recv = ccdb_mem_store_cred_recv, + + .delete_send = ccdb_mem_delete_send, + .delete_recv = ccdb_mem_delete_recv, +}; diff --git a/src/responder/kcm/kcmsrv_ccache_pvt.h b/src/responder/kcm/kcmsrv_ccache_pvt.h new file mode 100644 index 0000000..9d5afed --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_pvt.h @@ -0,0 +1,61 @@ +/* + SSSD + + KCM Server - the KCM ccache operations - private structures + + Should be accessed only from the ccache layer. + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef _KCMSRV_CCACHE_PVT_H +#define _KCMSRV_CCACHE_PVT_H + +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_ccache_be.h" + +struct kcm_ccache_owner { + uid_t uid; + gid_t gid; +}; + +struct kcm_cred { + struct sss_iobuf *cred_blob; + /* Randomly generated 16 bytes */ + uuid_t uuid; + + struct kcm_cred *next; + struct kcm_cred *prev; +}; + +struct kcm_ccdb { + struct tevent_context *ev; + + void *db_handle; + const struct kcm_ccdb_ops *ops; +}; + +struct kcm_ccache { + const char *name; + struct kcm_ccache_owner owner; + uuid_t uuid; + + krb5_principal client; + int32_t kdc_offset; + + struct kcm_cred *creds; +}; + +#endif /* _KCMSRV_CCACHE_PVT_H */ diff --git a/src/responder/kcm/kcmsrv_ccache_secdb.c b/src/responder/kcm/kcmsrv_ccache_secdb.c new file mode 100644 index 0000000..ff5e6e2 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_secdb.c @@ -0,0 +1,1671 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <talloc.h> +#include <stdio.h> + +#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, +}; diff --git a/src/responder/kcm/kcmsrv_cmd.c b/src/responder/kcm/kcmsrv_cmd.c new file mode 100644 index 0000000..9c37e3c --- /dev/null +++ b/src/responder/kcm/kcmsrv_cmd.c @@ -0,0 +1,667 @@ +/* + SSSD + + KCM Server - the KCM server request and reply parsing and dispatching + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/uio.h> +#include <krb5/krb5.h> + +#include "config.h" +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "responder/kcm/kcmsrv_ops.h" + +/* The first four bytes of a message is always the size */ +#define KCM_MSG_LEN_SIZE 4 + +/* The return code is 32bits */ +#define KCM_RETCODE_SIZE 4 + +/* KCM operation, its raw input and raw output and result */ +struct kcm_op_io { + struct kcm_op *op; + struct kcm_data request; + struct sss_iobuf *reply; +}; + +/** + * KCM IO-vector operations + */ +struct kcm_iovec { + /* We don't use iovec b/c void pointers don't allow for + * pointer arithmetics and it's convenient to keep track + * of processed bytes + */ + uint8_t *kiov_base; + size_t kiov_len; + size_t nprocessed; +}; + +static errno_t kcm_iovec_op(int fd, struct kcm_iovec *kiov, bool do_read) +{ + ssize_t len; + struct iovec iov[1]; + + iov[0].iov_base = kiov->kiov_base + kiov->nprocessed; + iov[0].iov_len = kiov->kiov_len - kiov->nprocessed; + if (iov[0].iov_len == 0) { + /* This iovec is full (read) or depleted (write), proceed to the next one */ + return EOK; + } + + if (do_read) { + len = readv(fd, iov, 1); + } else { + len = writev(fd, iov, 1); + } + + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return EAGAIN; + } else { + return errno; + } + } + + if (len == 0) { + /* Read event on fd that doesn't yield data? error */ + return ENODATA; + } + + /* Decrease the amount of available free space in the iovec */ + kiov->nprocessed += len; + return EOK; +} + +static errno_t kcm_read_iovec(int fd, struct kcm_iovec *kiov) +{ + return kcm_iovec_op(fd, kiov, true); +} + +static errno_t kcm_write_iovec(int fd, struct kcm_iovec *kiov) +{ + return kcm_iovec_op(fd, kiov, false); +} + +/** + * Parsing KCM input + * + * The request is received as two IO vectors: + * + * first iovec: + * length 32-bit big-endian integer + * + * second iovec: + * major protocol number 8-bit big-endian integer + * minor protocol number 8-bit big-endian integer + * opcode 16-bit big-endian integer + * message payload buffer + */ +struct kcm_reqbuf { + uint8_t lenbuf[KCM_MSG_LEN_SIZE]; + struct kcm_iovec v_len; + + /* Includes the major, minor versions etc */ + struct kcm_iovec v_msg; +}; + +static uint32_t kcm_input_get_payload_len(struct kcm_iovec *v) +{ + size_t lc = 0; + uint32_t len_be = 0; + + /* The first 4 bytes before the payload is message length */ + SAFEALIGN_COPY_UINT32_CHECK(&len_be, v->kiov_base, v->kiov_len, &lc); + + return be32toh(len_be); +} + +static errno_t kcm_input_parse(struct kcm_reqbuf *reqbuf, + struct kcm_op_io *op_io) +{ + size_t mc = 0; + uint16_t opcode_be = 0; + uint32_t msglen; + uint8_t proto_maj = 0; + uint8_t proto_min = 0; + + msglen = kcm_input_get_payload_len(&reqbuf->v_len); + DEBUG(SSSDBG_TRACE_LIBS, + "Received message with length %"PRIu32"\n", msglen); + + if (msglen == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal zero-length message\n"); + return EBADMSG; + } + + if (msglen != reqbuf->v_msg.nprocessed) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Sender claims the message is %"PRIu32" bytes, " + "but received %zu\n", + msglen, reqbuf->v_msg.nprocessed); + return EBADMSG; + } + + /* First 16 bits are 8 bit major and 8bit minor protocol version */ + SAFEALIGN_COPY_UINT8_CHECK(&proto_maj, + reqbuf->v_msg.kiov_base + mc, + reqbuf->v_msg.kiov_len, + &mc); + SAFEALIGN_COPY_UINT8_CHECK(&proto_min, + reqbuf->v_msg.kiov_base + mc, + reqbuf->v_msg.kiov_len, + &mc); + + if (proto_maj != KCM_PROTOCOL_VERSION_MAJOR) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected major version %d, got %"PRIu16"\n", + KCM_PROTOCOL_VERSION_MAJOR, (uint16_t) proto_maj); + return ERR_KCM_MALFORMED_IN_PKT; + } + + if (proto_min != KCM_PROTOCOL_VERSION_MINOR) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected minor version %d, got %"PRIu16"\n", + KCM_PROTOCOL_VERSION_MINOR, (uint16_t) proto_maj); + return ERR_KCM_MALFORMED_IN_PKT; + } + + SAFEALIGN_COPY_UINT16_CHECK(&opcode_be, + reqbuf->v_msg.kiov_base + mc, + reqbuf->v_msg.kiov_len, + &mc); + + op_io->op = kcm_get_opt(be16toh(opcode_be)); + if (op_io->op == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Did not find a KCM operation handler for the requested opcode\n"); + return ERR_KCM_OP_NOT_IMPLEMENTED; + } + + /* The operation only receives the payload, not the opcode or the protocol info */ + op_io->request.data = reqbuf->v_msg.kiov_base + mc; + op_io->request.length = reqbuf->v_msg.nprocessed - mc; + + return EOK; +} + +/** + * Constructing a reply for failure and success + * + * The reply consists of three IO vectors: + * 1) length iovec: + * length: 32-bit big-endian + * + * 2) return code iovec: + * retcode: 32-bit big-endian. Non-zero on failure in the KCM server, + * zero if the KCM operation ran (even if the operation itself + * failed) + * + * 3) reply iovec + * message: buffer, first 32-bits of the buffer is the return code of + * the KCM operation, the rest depends on the operation itself. + * The buffer's length is specified by the first integer in the + * reply (very intuitive, right?) + * + * The client always reads the length and return code iovectors. However, the + * client reads the reply iovec only if retcode is 0 in the return code iovector + * (see kcmio_unix_socket_read() in the MIT tree) + */ +struct kcm_repbuf { + uint8_t lenbuf[KCM_MSG_LEN_SIZE]; + struct kcm_iovec v_len; + + uint8_t rcbuf[KCM_RETCODE_SIZE]; + struct kcm_iovec v_rc; + + struct kcm_iovec v_msg; +}; + +static errno_t kcm_failbuf_construct(errno_t ret, + struct kcm_repbuf *repbuf) +{ + size_t c; + + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->lenbuf, 0, &c); + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, htobe32(ret), &c); + + DEBUG(SSSDBG_TRACE_LIBS, "Sent reply with error %d\n", ret); + return EOK; +} + +/* retcode is 0 if the operation at least ran, non-zero if there + * was some kind of internal KCM error, like input couldn't be parsed + */ +static errno_t kcm_output_construct(TALLOC_CTX *mem_ctx, + struct kcm_op_io *op_io, + struct kcm_repbuf *repbuf) +{ + uint8_t *rep; + size_t replen; + size_t c; + + replen = sss_iobuf_get_len(op_io->reply); + if (replen > KCM_PACKET_MAX_SIZE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Reply exceeds the KCM protocol limit, aborting\n"); + return E2BIG; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Sending a reply with %zu bytes of payload\n", replen); + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->lenbuf, htobe32(replen), &c); + + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, 0, &c); + + if (replen > 0) { + rep = talloc_array(mem_ctx, uint8_t, replen); + if (rep == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate memory for the message\n"); + return ENOMEM; + } + + c = 0; + SAFEALIGN_MEMCPY_CHECK(rep, + sss_iobuf_get_data(op_io->reply), + replen, + replen, + &c); + + /* Set the buffer and its length to send to KCM client */ + repbuf->v_msg.kiov_base = rep; + repbuf->v_msg.kiov_len = replen; + } + + return EOK; +} + +/** + * Construct a reply buffer and send it to the KCM client + */ +static void kcm_reply_error(struct cli_ctx *cctx, + errno_t retcode, + struct kcm_repbuf *repbuf) +{ + errno_t ret; + krb5_error_code kerr; + + DEBUG(retcode == ERR_KCM_OP_NOT_IMPLEMENTED ? + SSSDBG_MINOR_FAILURE : SSSDBG_OP_FAILURE, + "KCM operation returns failure [%d]: %s\n", + retcode, sss_strerror(retcode)); + kerr = sss2krb5_error(retcode); + + ret = kcm_failbuf_construct(kerr, repbuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot construct the reply buffer, terminating client\n"); + talloc_free(cctx); + return; + } + + TEVENT_FD_WRITEABLE(cctx->cfde); +} + +/** + * Request-reply dispatcher + */ +struct kcm_req_ctx { + /* client context owns per-client buffers including this one */ + struct cli_ctx *cctx; + + /* raw IO buffers */ + struct kcm_reqbuf reqbuf; + struct kcm_repbuf repbuf; + + /* long-lived responder structures */ + struct kcm_ctx *kctx; + + struct kcm_op_io op_io; +}; + +static void kcm_send_reply(struct kcm_req_ctx *req_ctx) +{ + struct cli_ctx *cctx; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Sending a reply\n"); + + cctx = req_ctx->cctx; + + ret = kcm_output_construct(req_ctx, &req_ctx->op_io, &req_ctx->repbuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot construct the reply buffer, terminating client\n"); + kcm_reply_error(cctx, ret, &req_ctx->repbuf); + return; + } + + TEVENT_FD_WRITEABLE(cctx->cfde); +} + +static void kcm_cmd_request_done(struct tevent_req *req); + +static errno_t kcm_cmd_dispatch(struct kcm_ctx *kctx, + struct kcm_req_ctx *req_ctx) +{ + struct tevent_req *req; + struct cli_ctx *cctx; + struct kcm_conn_data *conn_data; + + cctx = req_ctx->cctx; + conn_data = talloc_get_type(cctx->state_ctx, struct kcm_conn_data); + + req = kcm_cmd_send(req_ctx, + cctx->ev, + kctx->qctx, + req_ctx->kctx->kcm_data, + conn_data, + req_ctx->cctx->creds, + &req_ctx->op_io.request, + req_ctx->op_io.op); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to schedule KCM operation.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, kcm_cmd_request_done, req_ctx); + return EOK; +} + +static void kcm_cmd_request_done(struct tevent_req *req) +{ + struct kcm_req_ctx *req_ctx; + errno_t ret; + + req_ctx = tevent_req_callback_data(req, struct kcm_req_ctx); + + ret = kcm_cmd_recv(req_ctx, req, + &req_ctx->op_io.reply); + talloc_free(req); + if (ret != EOK) { + if (ret == ERR_KCM_OP_NOT_IMPLEMENTED) { + DEBUG(SSSDBG_MINOR_FAILURE, "%s\n", sss_strerror(ret)); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "KCM operation failed [%d]: %s\n", ret, sss_strerror(ret)); + } + kcm_reply_error(req_ctx->cctx, ret, &req_ctx->repbuf); + return; + } + + kcm_send_reply(req_ctx); +} + +static errno_t kcm_recv_data(TALLOC_CTX *mem_ctx, + int fd, + struct kcm_reqbuf *reqbuf) +{ + uint8_t *msg; + uint32_t msglen; + errno_t ret; + + ret = kcm_read_iovec(fd, &reqbuf->v_len); + if (ret != EOK) { + /* Not all errors are fatal, hence we don't print DEBUG messages + * here, but in the caller + */ + return ret; + } + + msglen = kcm_input_get_payload_len(&reqbuf->v_len); + if (msglen > KCM_PACKET_MAX_SIZE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Request exceeds the KCM protocol limit, aborting\n"); + return E2BIG; + } + + msg = talloc_array(mem_ctx, uint8_t, msglen); + if (msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate memory for the message\n"); + return ENOMEM; + } + + /* Set the buffer and its expected len to receive the data */ + reqbuf->v_msg.kiov_base = msg; + reqbuf->v_msg.kiov_len = msglen; + + ret = kcm_read_iovec(fd, &reqbuf->v_msg); + if (ret != EOK) { + /* Not all errors are fatal, hence we don't print DEBUG messages + * here, but in the caller + */ + return ret; + } + + return EOK; +} + +/* Mind that kcm_new_req() does not take a mem_ctx argument on purpose as we + * really want the cctx to be the memory context here so that if the client + * disconnects, the request goes away. */ +static struct kcm_req_ctx *kcm_new_req(struct cli_ctx *cctx, + struct kcm_ctx *kctx) +{ + struct kcm_req_ctx *req; + + req = talloc_zero(cctx, struct kcm_req_ctx); + if (req == NULL) { + return NULL; + } + + req->reqbuf.v_len.kiov_base = req->reqbuf.lenbuf; + req->reqbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE; + + req->repbuf.v_len.kiov_base = req->repbuf.lenbuf; + req->repbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE; + + req->repbuf.v_rc.kiov_base = req->repbuf.rcbuf; + req->repbuf.v_rc.kiov_len = KCM_RETCODE_SIZE; + + req->cctx = cctx; + req->kctx = kctx; + + return req; +} + +static void kcm_recv(struct cli_ctx *cctx) +{ + struct kcm_req_ctx *req; + struct kcm_ctx *kctx; + int ret; + + kctx = talloc_get_type(cctx->rctx->pvt_ctx, struct kcm_ctx); + req = talloc_get_type(cctx->protocol_ctx, struct kcm_req_ctx); + if (req == NULL) { + /* A new request comes in, setup data structures. */ + req = kcm_new_req(cctx, kctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set up client connection\n"); + talloc_free(cctx); + return; + } + + cctx->protocol_ctx = req; + } + + /* Shared data between requests that originates in the same connection. */ + if (cctx->state_ctx == NULL) { + cctx->state_ctx = talloc_zero(cctx, struct kcm_conn_data); + if (cctx->state_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up client state\n"); + talloc_free(cctx); + return; + } + } + + ret = kcm_recv_data(req, cctx->cfd, &req->reqbuf); + switch (ret) { + case ENODATA: + DEBUG(SSSDBG_TRACE_ALL, "Client closed connection.\n"); + talloc_free(cctx); + return; + case EAGAIN: + DEBUG(SSSDBG_TRACE_ALL, "Retry later\n"); + return; + case EOK: + /* all fine */ + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to receive data (%d, %s), aborting client\n", + ret, sss_strerror(ret)); + talloc_free(cctx); + return; + } + + ret = kcm_input_parse(&req->reqbuf, &req->op_io); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse data (%d, %s), aborting client\n", + ret, sss_strerror(ret)); + talloc_free(cctx); + return; + } + + /* do not read anymore, client is done sending */ + TEVENT_FD_NOT_READABLE(cctx->cfde); + + ret = kcm_cmd_dispatch(kctx, req); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to dispatch KCM operation [%d]: %s\n", + ret, sss_strerror(ret)); + /* Fail with reply */ + kcm_reply_error(cctx, ret, &req->repbuf); + return; + } + + /* Dispatched request resumes in kcm_cmd_request_done */ + return; +} + +static int kcm_send_data(struct cli_ctx *cctx) +{ + struct kcm_req_ctx *req; + errno_t ret; + + req = talloc_get_type(cctx->protocol_ctx, struct kcm_req_ctx); + + ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the length iovec [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_rc); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the retcode iovec [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the msg iovec [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static void kcm_send(struct cli_ctx *cctx) +{ + errno_t ret; + + ret = kcm_send_data(cctx); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, "Sending data again..\n"); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n"); + talloc_free(cctx); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All data sent!\n"); + TEVENT_FD_NOT_WRITEABLE(cctx->cfde); + TEVENT_FD_READABLE(cctx->cfde); + talloc_zfree(cctx->protocol_ctx); + return; +} + +static void kcm_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + sss_client_fd_handler(ptr, kcm_recv, kcm_send, flags); +} + +int kcm_connection_setup(struct cli_ctx *cctx) +{ + cctx->cfd_handler = kcm_fd_handler; + return EOK; +} + +krb5_error_code sss2krb5_error(errno_t err) +{ + switch (err) { + case EOK: + return 0; + case ENOMEM: + return KRB5_CC_NOMEM; + case EACCES: + return KRB5_FCC_PERM; + case ERR_KCM_OP_NOT_IMPLEMENTED: + return KRB5_FCC_INTERNAL; + case ERR_WRONG_NAME_FORMAT: + return KRB5_CC_BADNAME; + case ERR_NO_MATCHING_CREDS: + return KRB5_FCC_NOFILE; + case ERR_NO_CREDS: + return KRB5_CC_NOTFOUND; + case ERR_KCM_CC_END: + return KRB5_CC_END; + case ERR_KCM_MALFORMED_IN_PKT: + case EINVAL: + case EIO: + return KRB5_CC_IO; + } + + return KRB5_FCC_INTERNAL; +} + +/* Dummy, not used here but required to link to other responder files */ +struct cli_protocol_version *register_cli_protocol_version(void) +{ + return NULL; +} diff --git a/src/responder/kcm/kcmsrv_op_queue.c b/src/responder/kcm/kcmsrv_op_queue.c new file mode 100644 index 0000000..29af521 --- /dev/null +++ b/src/responder/kcm/kcmsrv_op_queue.c @@ -0,0 +1,332 @@ +/* + SSSD + + KCM Server - the KCM operations wait queue + + Copyright (C) Red Hat, 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/util_creds.h" +#include "responder/kcm/kcmsrv_pvt.h" + +struct kcm_ops_queue_entry { + struct tevent_req *req; + + struct kcm_ops_queue *queue; + + struct kcm_ops_queue_entry *next; + struct kcm_ops_queue_entry *prev; +}; + +struct kcm_ops_queue { + uid_t uid; + struct tevent_context *ev; + struct kcm_ops_queue_ctx *qctx; + + struct kcm_ops_queue_entry *head; +}; + +struct kcm_ops_queue_ctx { + struct kcm_ctx *kctx; + + /* UID:kcm_ops_queue */ + hash_table_t *wait_queue_hash; +}; + +/* + * Per-UID wait queue + * + * They key in the hash table is the UID of the peer. The value of each + * hash table entry is kcm_ops_queue structure which in turn contains a + * linked list of kcm_ops_queue_entry structures * which primarily hold the + * tevent request being queued. + */ +struct kcm_ops_queue_ctx *kcm_ops_queue_create(TALLOC_CTX *mem_ctx, + struct kcm_ctx *kctx) +{ + errno_t ret; + struct kcm_ops_queue_ctx *queue_ctx; + + queue_ctx = talloc_zero(mem_ctx, struct kcm_ops_queue_ctx); + if (queue_ctx == NULL) { + return NULL; + } + + ret = sss_hash_create_ex(mem_ctx, 0, + &queue_ctx->wait_queue_hash, 0, 0, 0, 0, + NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_hash_create failed [%d]: %s\n", ret, sss_strerror(ret)); + talloc_free(queue_ctx); + return NULL; + } + + queue_ctx->kctx = kctx; + + return queue_ctx; +} + +void queue_removal_cb(struct tevent_context *ctx, + struct tevent_immediate *imm, + void *private_data) +{ + struct kcm_ops_queue *kq = talloc_get_type(private_data, + struct kcm_ops_queue); + int ret; + hash_key_t key; + + talloc_free(imm); + + if (kq->head != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "The queue is no longer empty\n"); + return; + } + + key.type = HASH_KEY_ULONG; + key.ul = kq->uid; + + /* If this was the last entry, remove the key (the UID) from the + * hash table to signal the queue is empty + */ + ret = hash_delete(kq->qctx->wait_queue_hash, &key); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to remove wait queue for user %"SPRIuid"\n", + kq->uid); + return; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Removed queue for %"SPRIuid" \n", kq->uid); + talloc_free(kq); +} + +static int kcm_op_queue_entry_destructor(struct kcm_ops_queue_entry *entry) +{ + struct kcm_ops_queue_entry *next_entry; + struct tevent_immediate *imm; + + if (entry == NULL) { + return 1; + /* Prevent use-after-free of req when shutting down with non-empty queue */ + } else if (entry->queue->qctx->kctx->rctx->shutting_down) { + return 0; + } + + /* Take the next entry from the queue */ + next_entry = entry->next; + + /* Remove the current entry from the queue */ + DLIST_REMOVE(entry->queue->head, entry); + + if (next_entry == NULL) { + /* If there was no other entry, schedule removal of the queue. Do it + * in another tevent tick to avoid issues with callbacks invoking + * the destructor while another request is touching the queue + */ + imm = tevent_create_immediate(entry->queue); + if (imm == NULL) { + return 1; + } + + tevent_schedule_immediate(imm, entry->queue->ev, queue_removal_cb, entry->queue); + return 0; + } + + /* Otherwise, mark the current head as done to run the next request */ + tevent_req_done(next_entry->req); + return 0; +} + +static struct kcm_ops_queue *kcm_op_queue_get(struct kcm_ops_queue_ctx *qctx, + struct tevent_context *ev, + uid_t uid) +{ + errno_t ret; + hash_key_t key; + hash_value_t value; + struct kcm_ops_queue *kq; + + key.type = HASH_KEY_ULONG; + key.ul = uid; + + ret = hash_lookup(qctx->wait_queue_hash, &key, &value); + switch (ret) { + case HASH_SUCCESS: + if (value.type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n"); + return NULL; + } + + kq = talloc_get_type(value.ptr, struct kcm_ops_queue); + if (kq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid queue pointer\n"); + return NULL; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Found existing queue for this ID\n"); + break; + + case HASH_ERROR_KEY_NOT_FOUND: + /* No request for this UID yet. Enqueue this request in case + * another one comes in and return EOK to run the current request + * immediately + */ + DEBUG(SSSDBG_TRACE_LIBS, "No existing queue for this ID\n"); + + kq = talloc_zero(qctx->wait_queue_hash, struct kcm_ops_queue); + if (kq == NULL) { + return NULL; + } + kq->uid = uid; + kq->qctx = qctx; + kq->ev = ev; + + value.type = HASH_VALUE_PTR; + value.ptr = kq; + + ret = hash_enter(qctx->wait_queue_hash, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n"); + return NULL; + } + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n"); + return NULL; + } + + return kq; +} + +struct kcm_op_queue_state { + struct kcm_ops_queue_entry *entry; +}; + +static errno_t kcm_op_queue_add_req(struct kcm_ops_queue *kq, + struct tevent_req *req); + +/* + * Enqueue a request. + * + * If the request queue /for the given ID/ is empty, that is, if this + * request is the first one in the queue, run the request immediately. + * + * Otherwise just add it to the queue and wait until the previous request + * finishes and only at that point mark the current request as done, which + * will trigger calling the recv function and allow the request to continue. + */ +struct tevent_req *kcm_op_queue_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ops_queue_ctx *qctx, + struct cli_creds *client) +{ + errno_t ret; + struct tevent_req *req; + struct kcm_ops_queue *kq; + struct kcm_op_queue_state *state; + uid_t uid; + + uid = cli_creds_get_uid(client); + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_queue_state); + if (req == NULL) { + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Adding request by %"SPRIuid" to the wait queue\n", uid); + + kq = kcm_op_queue_get(qctx, ev, uid); + if (kq == NULL) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get queue [%d]: %s\n", ret, sss_strerror(ret)); + goto immediate; + } + + ret = kcm_op_queue_add_req(kq, req); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "Queue was empty, running the request immediately\n"); + goto immediate; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot enqueue request [%d]: %s\n", ret, sss_strerror(ret)); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Waiting our turn in the queue\n"); + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t kcm_op_queue_add_req(struct kcm_ops_queue *kq, + struct tevent_req *req) +{ + errno_t ret; + struct kcm_op_queue_state *state = tevent_req_data(req, + struct kcm_op_queue_state); + + state->entry = talloc_zero(kq->qctx->wait_queue_hash, struct kcm_ops_queue_entry); + if (state->entry == NULL) { + return ENOMEM; + } + state->entry->req = req; + state->entry->queue = kq; + talloc_set_destructor(state->entry, kcm_op_queue_entry_destructor); + + if (kq->head == NULL) { + /* First entry, will run callback at once */ + ret = EOK; + } else { + /* Will wait for the previous callbacks to finish */ + ret = EAGAIN; + } + + DLIST_ADD_END(kq->head, state->entry, struct kcm_ops_queue_entry *); + return ret; +} + +/* + * The queue recv function is called when this request is 'activated'. The queue + * entry should be allocated on the same memory context as the enqueued request + * to trigger freeing the kcm_ops_queue_entry structure destructor when the + * parent request is done and its tevent_req freed. This would in turn unblock + * the next request in the queue + */ +errno_t kcm_op_queue_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ops_queue_entry **_entry) +{ + struct kcm_op_queue_state *state = tevent_req_data(req, + struct kcm_op_queue_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_entry = talloc_steal(mem_ctx, state->entry); + return EOK; +} diff --git a/src/responder/kcm/kcmsrv_ops.c b/src/responder/kcm/kcmsrv_ops.c new file mode 100644 index 0000000..8935a79 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ops.c @@ -0,0 +1,2458 @@ +/* + SSSD + + KCM Server - the KCM server operations + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <krb5/krb5.h> +#include <dhash.h> + +#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; +} diff --git a/src/responder/kcm/kcmsrv_ops.h b/src/responder/kcm/kcmsrv_ops.h new file mode 100644 index 0000000..5d80352 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ops.h @@ -0,0 +1,67 @@ +/* + SSSD + + KCM Server - private header file + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __KCMSRV_OPS_H__ +#define __KCMSRV_OPS_H__ + +#include "config.h" + +#include <dhash.h> +#include <sys/types.h> +#include "util/sss_iobuf.h" +#include "responder/kcm/kcmsrv_pvt.h" + +/* The initial packet size, which can later be grown up to KCM_PACKET_MAX_SIZE. + * The initial size is a trade off that is expected to best serve most of the + * cases (typical credentials size). + */ +#define KCM_PACKET_INITIAL_SIZE 4096 + +/* The maximum length of a request or reply as defined by the RPC + * protocol. This is the same constant size as MIT KRB5 uses + * This limit comes from: + * https://github.com/krb5/krb5/blob/c20251dafd6120fa08c76b19315cb9deb1a1b24e/src/lib/krb5/ccache/cc_kcm.c#L54 + */ +#define KCM_PACKET_MAX_SIZE 10*1024*1024 + +struct kcm_op; +struct kcm_op *kcm_get_opt(uint16_t opcode); +const char *kcm_opt_name(struct kcm_op *op); + +struct kcm_conn_data { + /* Credentials obtained by GET_CRED_UUID_LIST. We use to improve performance + * by avoiding ccache lookups in GET_CRED_BY_UUID. */ + hash_table_t *creds; +}; + +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); +errno_t kcm_cmd_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sss_iobuf **_reply); + +#endif /* __KCMSRV_OPS_H__ */ diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h new file mode 100644 index 0000000..0536a2c --- /dev/null +++ b/src/responder/kcm/kcmsrv_pvt.h @@ -0,0 +1,106 @@ +/* + SSSD + + KCM Server - private header file + + Copyright (C) Red Hat, 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __KCMSRV_PVT_H__ +#define __KCMSRV_PVT_H__ + +#include "config.h" + +#include <sys/types.h> +#include <krb5/krb5.h> +#include "responder/common/responder.h" + +#define KCM_PROTOCOL_VERSION_MAJOR 2 +#define KCM_PROTOCOL_VERSION_MINOR 0 + +/* This should ideally be in RUNSTATEDIR, but Heimdal uses a hardcoded + * /var/run, and we need to use the same default path. */ +#define DEFAULT_KCM_SOCKET_PATH "/var/run/.heim_org.h5l.kcm-socket" + +/* + * KCM IO structure + * + * In theory we cold use sss_iobuf there, but since iobuf was + * made opaque, this allows it to allocate the structures on + * the stack in one go. + * */ +struct kcm_data { + uint8_t *data; + size_t length; +}; + +/* + * To avoid leaking the sssd-specific responder data to other + * modules, the ccache databases and other KCM specific data + * are kept separately + */ +struct kcm_resp_ctx { + krb5_context k5c; + struct kcm_ccdb *db; +}; + +/* Supported ccache back ends */ +enum kcm_ccdb_be { + CCDB_BE_MEMORY, + CCDB_BE_SECDB, +}; + +/* + * responder context that contains both the responder data, + * like the ccaches and the sssd-specific stuff like the + * generic responder ctx + */ +struct kcm_ctx { + struct resp_ctx *rctx; + int fd_limit; + char *socket_path; + enum kcm_ccdb_be cc_be; + struct kcm_ops_queue_ctx *qctx; + + struct kcm_resp_ctx *kcm_data; +}; + +int kcm_connection_setup(struct cli_ctx *cctx); + +/* + * Internally in SSSD-KCM we use SSSD-internal error codes so that we + * can always the same sss_strerror() functions to format the errors + * nicely, but the client expects libkrb5 error codes. + */ +krb5_error_code sss2krb5_error(errno_t err); + +/* We enqueue all requests by the same UID to avoid concurrency issues. + */ +struct kcm_ops_queue_entry; + +struct kcm_ops_queue_ctx *kcm_ops_queue_create(TALLOC_CTX *mem_ctx, + struct kcm_ctx *kctx); + +struct tevent_req *kcm_op_queue_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ops_queue_ctx *qctx, + struct cli_creds *client); + +errno_t kcm_op_queue_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ops_queue_entry **_entry); + +#endif /* __KCMSRV_PVT_H__ */ diff --git a/src/responder/kcm/secrets/config.c b/src/responder/kcm/secrets/config.c new file mode 100644 index 0000000..84462b8 --- /dev/null +++ b/src/responder/kcm/secrets/config.c @@ -0,0 +1,92 @@ +/* + SSSD + + Local secrets database -- configuration + + Copyright (C) Red Hat 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "secrets.h" + +errno_t sss_sec_get_quota(struct confdb_ctx *cdb, + const char *section_config_path, + struct sss_sec_quota_opt *dfl_max_containers_nest_level, + struct sss_sec_quota_opt *dfl_max_num_secrets, + struct sss_sec_quota_opt *dfl_max_num_uid_secrets, + struct sss_sec_quota_opt *dfl_max_payload, + struct sss_sec_quota *quota) +{ + int ret; + + if (cdb == NULL || section_config_path == NULL || quota == NULL) { + return EINVAL; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_containers_nest_level->opt_name, + dfl_max_containers_nest_level->default_value, + "a->containers_nest_level); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get container nesting level for %s\n", + section_config_path); + return ret; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_num_secrets->opt_name, + dfl_max_num_secrets->default_value, + "a->max_secrets); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get maximum number of entries for %s\n", + section_config_path); + return ret; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_num_uid_secrets->opt_name, + dfl_max_num_uid_secrets->default_value, + "a->max_uid_secrets); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get maximum number of per-UID entries for %s\n", + section_config_path); + return ret; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_payload->opt_name, + dfl_max_payload->default_value, + "a->max_payload_size); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get payload's maximum size for an entry in %s\n", + section_config_path); + return ret; + } + + return EOK; +} diff --git a/src/responder/kcm/secrets/sec_pvt.h b/src/responder/kcm/secrets/sec_pvt.h new file mode 100644 index 0000000..4118eaa --- /dev/null +++ b/src/responder/kcm/secrets/sec_pvt.h @@ -0,0 +1,47 @@ +/* + SSSD + + Local secrets database - private header + + Copyright (C) Red Hat 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SECRETS_PVT_H_ +#define __SECRETS_PVT_H_ + +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> + +#include "secrets.h" + +struct sss_sec_ctx { + struct ldb_context *ldb; + + struct sss_sec_quota *quota_kcm; +}; + +struct sss_sec_req { + char *path; + const char *basedn; + struct ldb_dn *req_dn; + struct sss_sec_quota *quota; + + struct sss_sec_ctx *sctx; +}; + +#endif /* __SECRETS_PVT_H_ */ diff --git a/src/responder/kcm/secrets/secrets.c b/src/responder/kcm/secrets/secrets.c new file mode 100644 index 0000000..a37edcc --- /dev/null +++ b/src/responder/kcm/secrets/secrets.c @@ -0,0 +1,1229 @@ +/* + SSSD + + Local secrets database + + Copyright (C) Red Hat 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include "config.h" + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <uuid/uuid.h> + +#include "responder/kcm/kcmsrv_ccache.h" +#include "util/util.h" +#include "util/util_creds.h" +#include "util/sss_iobuf.h" +#include "util/strtonum.h" +#include "util/crypto/sss_crypto.h" +#include "sec_pvt.h" +#include "secrets.h" + +#define KCM_PEER_UID 0 + +#define KCM_BASEDN "cn=kcm" + +#define LOCAL_CONTAINER_FILTER "(type=container)" +#define LOCAL_NON_CONTAINER_FILTER "(!"LOCAL_CONTAINER_FILTER")" + +#define SEC_ATTR_SECRET "secret" +#define SEC_ATTR_TYPE "type" +#define SEC_ATTR_CTIME "creationTime" + +static struct sss_sec_quota default_kcm_quota = { + .max_secrets = DEFAULT_SEC_KCM_MAX_SECRETS, + .max_uid_secrets = DEFAULT_SEC_KCM_MAX_UID_SECRETS, + .max_payload_size = DEFAULT_SEC_KCM_MAX_PAYLOAD_SIZE, + .containers_nest_level = DEFAULT_SEC_CONTAINERS_NEST_LEVEL, +}; + +static char *local_dn_to_path(TALLOC_CTX *mem_ctx, + struct ldb_dn *basedn, + struct ldb_dn *dn); + +static int local_db_check_containers(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + struct ldb_dn *leaf_dn) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { NULL}; + struct ldb_result *res = NULL; + struct ldb_dn *dn; + int num; + int ret; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + dn = ldb_dn_copy(tmp_ctx, leaf_dn); + if (!dn) { + ret = ENOMEM; + goto done; + } + + /* We need to exclude the leaf as that will be the new child entry, + * We also do not care for the synthetic containers that constitute the + * base path (cn=<uidnumber>,cn=users,cn=secrets), so in total we remove + * 4 components */ + num = ldb_dn_get_comp_num(dn) - 4; + + for (int i = 0; i < num; i++) { + /* remove the child first (we do not want to check the leaf) */ + if (!ldb_dn_remove_child_components(dn, 1)) return EFAULT; + + /* and check the parent container exists */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching for [%s] at [%s] with scope=base\n", + LOCAL_CONTAINER_FILTER, ldb_dn_get_linearized(dn)); + + ret = ldb_search(sec_ctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_BASE, + attrs, LOCAL_CONTAINER_FILTER); + if (ret != LDB_SUCCESS || res->count != 1) { + DEBUG(SSSDBG_TRACE_LIBS, + "DN [%s] does not exist\n", ldb_dn_get_linearized(dn)); + ret = ENOENT; + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int local_db_check_number_of_secrets(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { NULL }; + struct ldb_result *res = NULL; + struct ldb_dn *dn; + int ret; + + if (req->quota->max_secrets == 0) { + return EOK; + } + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + dn = ldb_dn_new(tmp_ctx, req->sctx->ldb, req->basedn); + if (!dn) { + ret = ENOMEM; + goto done; + } + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count >= req->quota->max_secrets) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store any more secrets as the maximum allowed limit (%d) " + "has been reached\n", req->quota->max_secrets); + ret = ERR_SEC_INVALID_TOO_MANY_SECRETS; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static struct ldb_dn *per_uid_container(TALLOC_CTX *mem_ctx, + struct ldb_dn *req_dn) +{ + int user_comp; + int num_comp; + struct ldb_dn *uid_base_dn; + + uid_base_dn = ldb_dn_copy(mem_ctx, req_dn); + if (uid_base_dn == NULL) { + return NULL; + } + + /* Remove all the components up to the per-user base path which consists + * of three components: + * cn=<uidnumber>,cn=users,cn=secrets + */ + user_comp = ldb_dn_get_comp_num(uid_base_dn) - 3; + + if (!ldb_dn_remove_child_components(uid_base_dn, user_comp)) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot remove child components\n"); + talloc_free(uid_base_dn); + return NULL; + } + + num_comp = ldb_dn_get_comp_num(uid_base_dn); + if (num_comp != 3) { + DEBUG(SSSDBG_OP_FAILURE, "Expected 3 components got %d\n", num_comp); + talloc_free(uid_base_dn); + return NULL; + } + + return uid_base_dn; +} + +static errno_t get_secret_expiration_time(uint8_t *key, size_t key_length, + uint8_t *sec, size_t sec_length, + time_t *_expiration) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + time_t expiration = 0; + struct cli_creds client = {}; + struct kcm_ccache *cc; + struct sss_iobuf *iobuf; + krb5_creds **cred_list, **cred; + const char *key_str; + + if (_expiration == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + key_str = talloc_strndup(tmp_ctx, (const char *) key, key_length); + if (key_str == NULL) { + ret = ENOMEM; + goto done; + } + + iobuf = sss_iobuf_init_readonly(tmp_ctx, sec, sec_length); + if (iobuf == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sec_kv_to_ccache_binary(tmp_ctx, key_str, iobuf, &client, &cc); + if (ret != EOK) { + goto done; + } + + cred_list = kcm_cc_unmarshal(tmp_ctx, NULL, cc); + if (cred_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (cred = cred_list; *cred != NULL; cred++) { + if ((*cred)->times.endtime != 0) { + expiration = (time_t) (*cred)->times.endtime; + break; + } + } + + *_expiration = expiration; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t local_db_remove_oldest_expired_secret(struct ldb_result *res, + struct sss_sec_req *req) +{ + struct sss_sec_req *new_req = NULL; + const struct ldb_val *val; + const struct ldb_val *rdn; + struct ldb_message *msg; + struct ldb_message_element *elem; + struct ldb_dn *basedn; + struct ldb_dn *oldest_dn = NULL; + time_t oldest_time = time(NULL); + time_t expiration; + unsigned int i; + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Removing the oldest expired credential\n"); + /* Between all the messages in result, there is also the key we are + * currently treating, but because yet it doesn't have an expiration time, + * it will be skipped. + */ + for (i = 0; i < res->count; i++) { + msg = res->msgs[i]; + + /* Skip cn=default,... or any non cn=... */ + rdn = ldb_dn_get_rdn_val(msg->dn); + if (strcmp(ldb_dn_get_rdn_name(msg->dn), "cn") != 0 + || strncmp("default", (char *) rdn->data, rdn->length) == 0) { + continue; + } + + elem = ldb_msg_find_element(msg, SEC_ATTR_SECRET); + if (elem != NULL) { + if (elem->num_values != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Element %s has %u values. Ignoring it.\n", + SEC_ATTR_SECRET, elem->num_values); + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + val = &elem->values[0]; + ret = get_secret_expiration_time(rdn->data, rdn->length, + val->data, val->length, + &expiration); + if (ret != EOK) { + goto done; + } + if (expiration > 0 && expiration < oldest_time) { + oldest_dn = msg->dn; + oldest_time = expiration; + } + } + } + + if (oldest_dn == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found no expired credential to remove\n"); + ret = ERR_NO_MATCHING_CREDS; + goto done; + } + + new_req = talloc_zero(NULL, struct sss_sec_req); + if (new_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the new request\n"); + ret = ENOMEM; + goto done; + } + + basedn = ldb_dn_new(new_req, req->sctx->ldb, req->basedn); + if (basedn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create a dn: %s\n", req->basedn); + ret = EINVAL; + goto done; + } + + new_req->basedn = req->basedn; + new_req->quota = req->quota; + new_req->req_dn = oldest_dn; + new_req->sctx = req->sctx; + new_req->path = local_dn_to_path(new_req, basedn, oldest_dn); + if (new_req->path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create the path\n"); + ret = EINVAL; + goto done; + } + + ret = sss_sec_delete(new_req); + +done: + if (new_req != NULL) + talloc_free(new_req); + + return ret; +} + + +static int local_db_check_peruid_number_of_secrets(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { SEC_ATTR_SECRET, NULL }; + struct ldb_result *res = NULL; + struct ldb_dn *cli_basedn = NULL; + int ret; + + if (req->quota->max_uid_secrets == 0) { + return EOK; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cli_basedn = per_uid_container(tmp_ctx, req->req_dn); + if (cli_basedn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, cli_basedn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count >= req->quota->max_uid_secrets) { + /* We reached the limit. Let's try to removed the + * oldest expired credential to free some space. */ + ret = local_db_remove_oldest_expired_secret(res, req); + if (ret != EOK) { + if (ret == ERR_NO_MATCHING_CREDS) { + /* max_uid_secrets is incremented for internal entries. */ + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store any more secrets for this client (basedn %s) " + "as the maximum allowed limit (%d) has been reached\n", + ldb_dn_get_linearized(cli_basedn), + req->quota->max_uid_secrets - KCM_MAX_UID_EXTRA_SECRETS); + ret = ERR_SEC_INVALID_TOO_MANY_SECRETS; + } + goto done; + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static int local_check_max_payload_size(struct sss_sec_req *req, + int payload_size) +{ + int max_payload_size; + + if (req->quota->max_payload_size == 0) { + return EOK; + } + + max_payload_size = req->quota->max_payload_size * 1024; /* KiB */ + if (payload_size > max_payload_size) { + DEBUG(SSSDBG_OP_FAILURE, + "Secrets' payload size [%d KiB (%d B)] exceeds the maximum " + "allowed payload size [%d KiB (%d B)]\n", + payload_size / 1024, /* KiB */ + payload_size, + req->quota->max_payload_size, /* KiB */ + max_payload_size); + + return ERR_SEC_PAYLOAD_SIZE_IS_TOO_LARGE; + } + + return EOK; +} + +static int local_db_check_containers_nest_level(struct sss_sec_req *req, + struct ldb_dn *leaf_dn) +{ + int nest_level; + + if (req->quota->containers_nest_level == 0) { + return EOK; + } + + /* We need do not care for the synthetic containers that constitute the + * base path (cn=<uidnumber>,cn=user,cn=secrets). */ + nest_level = ldb_dn_get_comp_num(leaf_dn) - 3; + if (nest_level > req->quota->containers_nest_level) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create a nested container of depth %d as the maximum" + "allowed number of nested containers is %d.\n", + nest_level, req->quota->containers_nest_level); + + return ERR_SEC_INVALID_CONTAINERS_NEST_LEVEL; + } + + return EOK; +} + +static int local_db_create(struct sss_sec_req *req) +{ + struct ldb_message *msg; + int ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Creating a container at [%s]\n", req->path); + + msg = ldb_msg_new(req); + if (!msg) { + ret = ENOMEM; + goto done; + } + msg->dn = req->req_dn; + + /* make sure containers exist */ + ret = local_db_check_containers(msg, req->sctx, msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_containers failed for [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_containers_nest_level(req, msg->dn); + if (ret != EOK) goto done; + + ret = ldb_msg_add_string(msg, SEC_ATTR_TYPE, "container"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding type:container [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_msg_add_fmt(msg, SEC_ATTR_CTIME, "%lu", time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding creationTime [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_add(req->sctx->ldb, msg); + if (ret != LDB_SUCCESS) { + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + DEBUG(SSSDBG_FUNC_DATA, + "Secret %s already exists\n", ldb_dn_get_linearized(msg->dn)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add secret [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, ldb_strerror(ret)); + } + ret = sss_ldb_error_to_errno (ret); + goto done; + } + + ret = EOK; + +done: + talloc_free(msg); + return ret; +} + +/* non static since it is used in test_kcm_renewals.c */ +errno_t sss_sec_init_with_path(TALLOC_CTX *mem_ctx, + struct sss_sec_quota *quota, + const char *dbpath, + struct sss_sec_ctx **_sec_ctx) +{ + struct sss_sec_ctx *sec_ctx; + TALLOC_CTX *tmp_ctx; + errno_t ret; + + if (_sec_ctx == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + sec_ctx = talloc_zero(tmp_ctx, struct sss_sec_ctx); + if (sec_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (quota) { + sec_ctx->quota_kcm = quota; + } else { + DEBUG(SSSDBG_TRACE_LIBS, "No custom quota set, using defaults\n"); + sec_ctx->quota_kcm = &default_kcm_quota; + } + + sec_ctx->ldb = ldb_init(sec_ctx, NULL); + if (sec_ctx->ldb == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ldb_connect(sec_ctx->ldb, dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_connect(%s) returned %d: %s\n", + dbpath, ret, ldb_strerror(ret)); + talloc_free(sec_ctx->ldb); + ret = EIO; + goto done; + } + + ret = EOK; + *_sec_ctx = talloc_steal(mem_ctx, sec_ctx); +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_init(TALLOC_CTX *mem_ctx, + struct sss_sec_quota *quota, + struct sss_sec_ctx **_sec_ctx) +{ + const char *dbpath = SECRETS_DB_PATH"/secrets.ldb"; + errno_t ret; + + ret = sss_sec_init_with_path(mem_ctx, quota, dbpath, _sec_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize secdb [%d]: %s\n", + ret, sss_strerror(ret)); + ret = EIO; + goto done; + } + + ret = EOK; +done: + return ret; +} + +static int local_db_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *basedn, + const char *req_path, + struct ldb_dn **req_dn) +{ + struct ldb_dn *dn; + const char *s, *e; + int ret; + + dn = ldb_dn_new(mem_ctx, ldb, basedn); + if (!dn) { + ret = ENOMEM; + goto done; + } + + s = req_path; + + while (s && *s) { + e = strchr(s, '/'); + if (e) { + if (e == s) { + s++; + continue; + } + if (!ldb_dn_add_child_fmt(dn, "cn=%.*s", (int)(e - s), s)) { + ret = ENOMEM; + goto done; + } + s = e + 1; + } else { + if (!ldb_dn_add_child_fmt(dn, "cn=%s", s)) { + ret = ENOMEM; + goto done; + } + s = NULL; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Local path for [%s] is [%s]\n", + req_path, ldb_dn_get_linearized(dn)); + *req_dn = dn; + ret = EOK; + +done: + return ret; +} + +errno_t sss_sec_new_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + const char *url, + uid_t client, + struct sss_sec_req **_req) +{ + struct sss_sec_req *req; + TALLOC_CTX *tmp_ctx; + errno_t ret; + + if (sec_ctx == NULL || url == NULL || _req == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + req = talloc_zero(tmp_ctx, struct sss_sec_req); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + req->sctx = sec_ctx; + + /* drop the prefix and select a basedn instead */ + if (geteuid() != KCM_PEER_UID && client != KCM_PEER_UID) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UID %"SPRIuid" is not allowed to access the KCM hive\n", + client); + ret = EPERM; + goto done; + } + + req->basedn = KCM_BASEDN; + req->quota = sec_ctx->quota_kcm; + req->path = talloc_strdup(req, url); + if (req->path == NULL) { + ret = ENOMEM; + goto done; + } + + ret = local_db_dn(req, sec_ctx->ldb, req->basedn, req->path, &req->req_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to map request to local db DN\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Local DB path is %s\n", req->path); + + ret = EOK; + *_req = talloc_steal(mem_ctx, req); +done: + talloc_free(tmp_ctx); + return ret; +} + +static char *local_dn_to_path(TALLOC_CTX *mem_ctx, + struct ldb_dn *basedn, + struct ldb_dn *dn) +{ + int basecomps; + int dncomps; + char *path = NULL; + + basecomps = ldb_dn_get_comp_num(basedn); + dncomps = ldb_dn_get_comp_num(dn); + + for (int i = dncomps - basecomps; i > 0; i--) { + const struct ldb_val *val; + + val = ldb_dn_get_component_val(dn, i - 1); + if (!val) return NULL; + + if (path) { + path = talloc_strdup_append_buffer(path, "/"); + if (!path) return NULL; + path = talloc_strndup_append_buffer(path, (char *)val->data, + val->length); + } else { + path = talloc_strndup(mem_ctx, (char *)val->data, val->length); + } + if (!path) return NULL; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Secrets path for [%s] is [%s]\n", + ldb_dn_get_linearized(dn), path); + return path; +} + +/* Complete list of ccache names(UUID:name) */ +errno_t sss_sec_list_cc_uuids(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec, + const char ***_uuid_list, + const char ***_uid_list, + size_t *_uuid_list_count) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + struct ldb_dn *dn; + const struct ldb_val *name_val; + const struct ldb_val *uid_val; + static const char *attrs[] = { "distinguishedName", NULL }; + const char **uuid_list; + const char **uid_list; + size_t real_count = 0; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + dn = ldb_dn_new(tmp_ctx, sec->ldb, "cn=persistent,cn=kcm"); + + ret = ldb_search(sec->ldb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned [%d]: %s\n", ret, ldb_strerror(ret)); + ret = EIO; + goto done; + } + + if (res->count == 0) { + DEBUG(SSSDBG_TRACE_LIBS, "No ccaches found\n"); + ret = ENOENT; + goto done; + } + + uuid_list = talloc_zero_array(tmp_ctx, const char *, res->count); + if (uuid_list == NULL) { + ret = ENOMEM; + goto done; + } + + uid_list = talloc_zero_array(tmp_ctx, const char *, res->count); + if (uid_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < res->count; i++) { + name_val = ldb_dn_get_component_val(res->msgs[i]->dn, 0); + uid_val = ldb_dn_get_component_val(res->msgs[i]->dn, 2); + if (strcmp((const char *)name_val->data, "default") == 0) { + continue; + } + + uuid_list[real_count] = talloc_strdup(uuid_list, (const char *)name_val->data); + if (uuid_list[real_count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate UUID\n"); + ret = ENOMEM; + goto done; + } + + uid_list[real_count] = talloc_strdup(uid_list, (const char *)uid_val->data); + if (uid_list[real_count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate uid\n"); + ret = ENOMEM; + goto done; + } + + real_count++; + } + + *_uid_list = talloc_steal(mem_ctx, uid_list); + *_uuid_list = talloc_steal(mem_ctx, uuid_list); + *_uuid_list_count = real_count; + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_list(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + char ***_keys, + size_t *_num_keys) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { SEC_ATTR_SECRET, NULL }; + struct ldb_result *res; + char **keys; + int ret; + + if (req == NULL || _keys == NULL || _num_keys == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_FUNC, "Listing keys at [%s]\n", req->path); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching at [%s] with scope=subtree\n", + ldb_dn_get_linearized(req->req_dn)); + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned [%d]: %s\n", ret, ldb_strerror(ret)); + ret = ENOENT; + goto done; + } + + if (res->count == 0) { + DEBUG(SSSDBG_TRACE_LIBS, "No secrets found\n"); + ret = ENOENT; + goto done; + } + + keys = talloc_array(mem_ctx, char *, res->count); + if (!keys) { + ret = ENOMEM; + goto done; + } + + for (unsigned i = 0; i < res->count; i++) { + keys[i] = local_dn_to_path(keys, req->req_dn, res->msgs[i]->dn); + if (!keys[i]) { + ret = ENOMEM; + goto done; + } + } + + *_keys = keys; + DEBUG(SSSDBG_TRACE_LIBS, "Returning %d secrets\n", res->count); + *_num_keys = res->count; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_get(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + uint8_t **_secret, + size_t *_secret_len) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { SEC_ATTR_SECRET, NULL }; + struct ldb_result *res; + const struct ldb_val *attr_secret; + int ret; + + if (req == NULL || _secret == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving a secret from [%s]\n", req->path); + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching at [%s] with scope=base\n", + ldb_dn_get_linearized(req->req_dn)); + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_BASE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned [%d]: %s\n", ret, ldb_strerror(ret)); + ret = ENOENT; + goto done; + } + + switch (res->count) { + case 0: + DEBUG(SSSDBG_TRACE_LIBS, "No secret found\n"); + ret = ENOENT; + goto done; + case 1: + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Too many secrets returned with BASE search\n"); + ret = E2BIG; + goto done; + } + + attr_secret = ldb_msg_find_ldb_val(res->msgs[0], SEC_ATTR_SECRET); + if ((!attr_secret) || (attr_secret->length == 0)) { + DEBUG(SSSDBG_CRIT_FAILURE, "The 'secret' attribute is missing\n"); + ret = ENOENT; + goto done; + } + + *_secret = talloc_memdup(mem_ctx, attr_secret->data, attr_secret->length); + if (*_secret == NULL) { + ret = ENOMEM; + goto done; + } + + if (_secret_len) { + *_secret_len = attr_secret->length; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_put(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len) +{ + struct ldb_message *msg; + struct ldb_val secret_val; + int ret; + + if (req == NULL || secret == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Adding a secret to [%s]\n", req->path); + + msg = ldb_msg_new(req); + if (!msg) { + ret = ENOMEM; + goto done; + } + msg->dn = req->req_dn; + + /* make sure containers exist */ + ret = local_db_check_containers(msg, req->sctx, msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_containers failed for [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_peruid_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_peruid_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_check_max_payload_size(req, secret_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_check_max_payload_size failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + secret_val.length = secret_len; + secret_val.data = talloc_memdup(req->sctx, secret, secret_len); + if (!secret_val.data) { + ret = ENOMEM; + goto done; + } + + ret = ldb_msg_add_value(msg, SEC_ATTR_SECRET, &secret_val, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding secret [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_msg_add_fmt(msg, SEC_ATTR_CTIME, "%lu", time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding creationTime [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_add(req->sctx->ldb, msg); + if (ret != LDB_SUCCESS) { + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + DEBUG(SSSDBG_OP_FAILURE, + "Secret %s already exists\n", ldb_dn_get_linearized(msg->dn)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add secret [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, ldb_strerror(ret)); + } + ret = sss_ldb_error_to_errno (ret); + goto done; + } + + ret = EOK; +done: + talloc_free(msg); + return ret; +} + +errno_t sss_sec_update(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len) +{ + struct ldb_message *msg; + struct ldb_val secret_val; + int ret; + + if (req == NULL || secret == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Adding a secret to [%s]\n", req->path); + + msg = ldb_msg_new(req); + if (!msg) { + ret = ENOMEM; + goto done; + } + msg->dn = req->req_dn; + + /* make sure containers exist */ + ret = local_db_check_containers(msg, req->sctx, msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_containers failed for [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_peruid_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_peruid_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_check_max_payload_size(req, secret_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_check_max_payload_size failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + secret_val.length = secret_len; + secret_val.data = talloc_memdup(req->sctx, secret, secret_len); + if (!secret_val.data) { + ret = ENOMEM; + goto done; + } + + /* FIXME - should we have a lastUpdate timestamp? */ + ret = ldb_msg_add_empty(msg, SEC_ATTR_SECRET, LDB_FLAG_MOD_REPLACE, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldb_msg_add_empty failed: [%s]\n", ldb_strerror(ret)); + ret = EIO; + goto done; + } + + ret = ldb_msg_add_value(msg, SEC_ATTR_SECRET, &secret_val, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldb_msg_add_string failed: [%s]\n", ldb_strerror(ret)); + ret = EIO; + goto done; + } + + ret = ldb_modify(req->sctx->ldb, msg); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(SSSDBG_MINOR_FAILURE, "No such object to modify\n"); + ret = sss_ldb_error_to_errno (ret); + goto done; + } else if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldb_modify failed: [%s](%d)[%s]\n", + ldb_strerror(ret), ret, ldb_errstring(req->sctx->ldb)); + ret = sss_ldb_error_to_errno (ret); + goto done; + } + + ret = EOK; +done: + talloc_free(msg); + return ret; +} + +errno_t sss_sec_delete(struct sss_sec_req *req) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { NULL }; + struct ldb_result *res; + int ret; + + if (req == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Removing a secret from [%s]\n", req->path); + + tmp_ctx = talloc_new(req); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching for [%s] at [%s] with scope=base\n", + LOCAL_CONTAINER_FILTER, ldb_dn_get_linearized(req->req_dn)); + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_BASE, + attrs, LOCAL_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count == 1) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching for children of [%s]\n", ldb_dn_get_linearized(req->req_dn)); + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_ONELEVEL, + attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count > 0) { + ret = EEXIST; + DEBUG(SSSDBG_OP_FAILURE, + "Failed to remove '%s': Container is not empty\n", + ldb_dn_get_linearized(req->req_dn)); + + goto done; + } + } + + ret = ldb_delete(req->sctx->ldb, req->req_dn); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_delete returned %d: %s\n", ret, ldb_strerror(ret)); + /* fall through */ + } + + if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "LDB returned unexpected error: [%s]\n", + ldb_strerror(ret)); + } + ret = sss_ldb_error_to_errno (ret); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_create_container(struct sss_sec_req *req) +{ + int plen; + + if (req == NULL) { + return EINVAL; + } + + plen = strlen(req->path); + + if (req->path[plen - 1] != '/') { + return EINVAL; + } + + req->path[plen - 1] = '\0'; + return local_db_create(req); +} diff --git a/src/responder/kcm/secrets/secrets.h b/src/responder/kcm/secrets/secrets.h new file mode 100644 index 0000000..537f6c9 --- /dev/null +++ b/src/responder/kcm/secrets/secrets.h @@ -0,0 +1,114 @@ +/* + SSSD + + Local secrets database + + Copyright (C) Red Hat 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SECRETS_H_ +#define __SECRETS_H_ + +#include <errno.h> +#include <stdlib.h> +#include <stdbool.h> +#include <talloc.h> +#include <uuid/uuid.h> + +#include "confdb/confdb.h" + +#define DEFAULT_SEC_CONTAINERS_NEST_LEVEL 4 + +/* The number of secrets in the /kcm hive should be quite small, + * but the secret size must be large because one secret in the /kcm + * hive holds the whole ccache which consists of several credentials + */ +#define DEFAULT_SEC_KCM_MAX_SECRETS 0 /* unlimited */ +#define DEFAULT_SEC_KCM_MAX_UID_SECRETS 64 +#define DEFAULT_SEC_KCM_MAX_PAYLOAD_SIZE 65536 + +/* Even cn=default is considered a secret that adds up to + * the quota. To avoid off-by-one-confusion, increase + * the quota by two to 1) account for the cn=default object + * and 2) always allow writing to cn=defaults even if we + * are exactly at the quota limit + */ +#define KCM_MAX_UID_EXTRA_SECRETS 2 + +struct sss_sec_ctx; + +struct sss_sec_req; + +struct sss_sec_quota_opt { + const char *opt_name; + int default_value; +}; + +struct sss_sec_quota { + int max_secrets; + int max_uid_secrets; + int max_payload_size; + int containers_nest_level; +}; + +errno_t sss_sec_init(TALLOC_CTX *mem_ctx, + struct sss_sec_quota *quota, + struct sss_sec_ctx **_sec_ctx); + +errno_t sss_sec_new_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + const char *url, + uid_t client, + struct sss_sec_req **_req); + +errno_t sss_sec_delete(struct sss_sec_req *req); + +errno_t sss_sec_list_cc_uuids(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + const char ***_uuid_list, + const char ***_uid_list, + size_t *uuid_list_count); + +errno_t sss_sec_list(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + char ***_keys, + size_t *num_keys); + +errno_t sss_sec_get(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + uint8_t **_secret, + size_t *_secret_len); + +errno_t sss_sec_put(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len); + +errno_t sss_sec_update(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len); + +errno_t sss_sec_create_container(struct sss_sec_req *req); + + +errno_t sss_sec_get_quota(struct confdb_ctx *cdb, + const char *section_config_path, + struct sss_sec_quota_opt *dfl_max_containers_nest_level, + struct sss_sec_quota_opt *dfl_max_num_secrets, + struct sss_sec_quota_opt *dfl_max_num_uid_secrets, + struct sss_sec_quota_opt *dfl_max_payload, + struct sss_sec_quota *quota); + +#endif /* __SECRETS_H_ */ |