summaryrefslogtreecommitdiffstats
path: root/src/responder/kcm
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder/kcm')
-rw-r--r--src/responder/kcm/kcm.c374
-rw-r--r--src/responder/kcm/kcm_renew.c775
-rw-r--r--src/responder/kcm/kcm_renew.h53
-rw-r--r--src/responder/kcm/kcmsrv_ccache.c1724
-rw-r--r--src/responder/kcm/kcmsrv_ccache.h380
-rw-r--r--src/responder/kcm/kcmsrv_ccache_be.h216
-rw-r--r--src/responder/kcm/kcmsrv_ccache_binary.c308
-rw-r--r--src/responder/kcm/kcmsrv_ccache_key.c144
-rw-r--r--src/responder/kcm/kcmsrv_ccache_mem.c826
-rw-r--r--src/responder/kcm/kcmsrv_ccache_pvt.h61
-rw-r--r--src/responder/kcm/kcmsrv_ccache_secdb.c1671
-rw-r--r--src/responder/kcm/kcmsrv_cmd.c667
-rw-r--r--src/responder/kcm/kcmsrv_op_queue.c332
-rw-r--r--src/responder/kcm/kcmsrv_ops.c2458
-rw-r--r--src/responder/kcm/kcmsrv_ops.h67
-rw-r--r--src/responder/kcm/kcmsrv_pvt.h106
-rw-r--r--src/responder/kcm/secrets/config.c92
-rw-r--r--src/responder/kcm/secrets/sec_pvt.h47
-rw-r--r--src/responder/kcm/secrets/secrets.c1229
-rw-r--r--src/responder/kcm/secrets/secrets.h114
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,
+ &quota->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,
+ &quota->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,
+ &quota->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,
+ &quota->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_ */