summaryrefslogtreecommitdiffstats
path: root/src/responder/kcm/secrets/secrets.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/responder/kcm/secrets/secrets.c1229
1 files changed, 1229 insertions, 0 deletions
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);
+}