/*
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 .
*/
#include "config.h"
#include
#include
#include
#include
#include
#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=,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=,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=,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);
}