diff --git a/src/responder/kcm/secrets/secrets.c b/src/responder/kcm/secrets/secrets.c index 025d1c42136..3ba063485da 100644 --- a/src/responder/kcm/secrets/secrets.c +++ b/src/responder/kcm/secrets/secrets.c @@ -18,15 +18,18 @@ 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 -#include "config.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" @@ -50,6 +53,10 @@ static struct sss_sec_quota default_kcm_quota = { .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) @@ -181,11 +188,170 @@ static struct ldb_dn *per_uid_container(TALLOC_CTX *mem_ctx, 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) +{ + TALLOC_CTX *tmp_ctx; + struct sss_sec_req *new_req; + 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"); + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memory\n"); + return ENOMEM; + } + + // 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; + } + + basedn = ldb_dn_new(tmp_ctx, 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 = talloc_zero(tmp_ctx, struct sss_sec_req); + if (new_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the new request\n"); + ret = ENOMEM; + 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: + talloc_free(tmp_ctx); + 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[] = { NULL }; + static const char *attrs[] = { SEC_ATTR_SECRET, NULL }; struct ldb_result *res = NULL; struct ldb_dn *cli_basedn = NULL; int ret; @@ -214,13 +380,20 @@ static int local_db_check_peruid_number_of_secrets(TALLOC_CTX *mem_ctx, } if (res->count >= req->quota->max_uid_secrets) { - 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); - ret = ERR_SEC_INVALID_TOO_MANY_SECRETS; - goto done; + /* 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) { + 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); + ret = ERR_SEC_INVALID_TOO_MANY_SECRETS; + } + goto done; + } } ret = EOK; @@ -808,15 +981,15 @@ errno_t sss_sec_put(struct sss_sec_req *req, goto done; } - ret = local_db_check_number_of_secrets(msg, req); + ret = local_db_check_peruid_number_of_secrets(msg, req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, - "local_db_check_number_of_secrets failed [%d]: %s\n", + "local_db_check_peruid_number_of_secrets failed [%d]: %s\n", ret, sss_strerror(ret)); goto done; } - ret = local_db_check_peruid_number_of_secrets(msg, req); + 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", @@ -905,15 +1078,15 @@ errno_t sss_sec_update(struct sss_sec_req *req, goto done; } - ret = local_db_check_number_of_secrets(msg, req); + ret = local_db_check_peruid_number_of_secrets(msg, req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, - "local_db_check_number_of_secrets failed [%d]: %s\n", + "local_db_check_peruid_number_of_secrets failed [%d]: %s\n", ret, sss_strerror(ret)); goto done; } - ret = local_db_check_peruid_number_of_secrets(msg, req); + 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",